






















@@ -10,6 +10,7 @@ import {
1010realpathSync,
1111rmSync,
1212} from "node:fs";
13+import { builtinModules } from "node:module";
1314import { tmpdir } from "node:os";
1415import { isAbsolute, join, relative } from "node:path";
1516import { pathToFileURL } from "node:url";
@@ -20,9 +21,11 @@ import {
2021collectBundledPluginRootRuntimeMirrorErrors,
2122collectRootDistBundledRuntimeMirrors,
2223collectRuntimeDependencySpecs,
24+packageNameFromSpecifier,
2325} from "./lib/bundled-plugin-root-runtime-mirrors.mjs";
2426import { runInstalledWorkspaceBootstrapSmoke } from "./lib/workspace-bootstrap-smoke.mjs";
2527import { parseReleaseVersion, resolveNpmCommandInvocation } from "./openclaw-npm-release-check.ts";
28+import { createRequire } from "node:module";
26292730type InstalledPackageJson = {
2831version?: string;
@@ -47,6 +50,13 @@ const LEGACY_CONTEXT_ENGINE_UNRESOLVED_RUNTIME_MARKER =
4750const PUBLISHED_BUNDLED_RUNTIME_SIDECAR_PATHS = BUNDLED_RUNTIME_SIDECAR_PATHS.filter(
4851(relativePath) => listBundledPluginPackArtifacts().includes(relativePath),
4952);
53+const NODE_BUILTIN_MODULES = new Set(builtinModules.map((name) => name.replace(/^node:/u, "")));
54+const MAX_INSTALLED_ROOT_PACKAGE_JSON_BYTES = 1024 * 1024;
55+const MAX_INSTALLED_ROOT_DIST_JS_BYTES = 2 * 1024 * 1024;
56+const MAX_INSTALLED_ROOT_DIST_JS_FILES = 5000;
57+const ROOT_DIST_JAVASCRIPT_MODULE_FILE_RE = /\.(?:c|m)?js$/u;
58+const require = createRequire(import.meta.url);
59+const acorn = require("acorn") as typeof import("acorn");
50605161export type PublishedInstallScenario = {
5262name: string;
@@ -101,6 +111,7 @@ export function collectInstalledPackageErrors(params: {
101111}
102112103113errors.push(...collectInstalledContextEngineRuntimeErrors(params.packageRoot));
114+errors.push(...collectInstalledRootDependencyManifestErrors(params.packageRoot));
104115errors.push(...collectInstalledMirroredRootDependencyManifestErrors(params.packageRoot));
105116106117return errors;
@@ -131,7 +142,7 @@ function listDistJavaScriptFiles(packageRoot: string): string[] {
131142pending.push(entryPath);
132143continue;
133144}
134-if (entry.isFile() && entry.name.endsWith(".js")) {
145+if (entry.isFile() && ROOT_DIST_JAVASCRIPT_MODULE_FILE_RE.test(entry.name)) {
135146files.push(entryPath);
136147}
137148}
@@ -154,6 +165,183 @@ export function collectInstalledContextEngineRuntimeErrors(packageRoot: string):
154165return errors;
155166}
156167168+function listInstalledRootDistJavaScriptFiles(packageRoot: string): string[] {
169+const distDir = join(packageRoot, "dist");
170+if (!existsSync(distDir)) {
171+return [];
172+}
173+174+const pending = [distDir];
175+const files: string[] = [];
176+while (pending.length > 0) {
177+const currentDir = pending.pop();
178+if (!currentDir) {
179+continue;
180+}
181+for (const entry of readdirSync(currentDir, { withFileTypes: true })) {
182+const entryPath = join(currentDir, entry.name);
183+const relativePath = relative(distDir, entryPath).replaceAll("\\", "/");
184+if (relativePath.startsWith("extensions/")) {
185+continue;
186+}
187+if (entry.isDirectory()) {
188+pending.push(entryPath);
189+continue;
190+}
191+if (entry.isFile() && ROOT_DIST_JAVASCRIPT_MODULE_FILE_RE.test(entry.name)) {
192+files.push(entryPath);
193+}
194+}
195+}
196+197+return files;
198+}
199+200+type ParsedImportSpecifiersResult =
201+| { ok: true; specifiers: Set<string> }
202+| { ok: false; error: string };
203+204+function extractLiteralSpecifier(node: unknown): string | null {
205+if (!node || typeof node !== "object") {
206+return null;
207+}
208+const candidate = node as { type?: string; value?: unknown };
209+if (candidate.type === "Literal" && typeof candidate.value === "string") {
210+return candidate.value;
211+}
212+return null;
213+}
214+215+function extractJavaScriptImportSpecifiers(source: string): ParsedImportSpecifiersResult {
216+const specifiers = new Set<string>();
217+ let program: unknown;
218+try {
219+program = acorn.parse(source, {
220+allowHashBang: true,
221+ecmaVersion: "latest",
222+sourceType: "module",
223+});
224+} catch (error) {
225+return { ok: false, error: formatErrorMessage(error) };
226+}
227+228+const visited = new Set<unknown>();
229+const pending: unknown[] = [program];
230+while (pending.length > 0) {
231+const current = pending.pop();
232+if (!current || typeof current !== "object" || visited.has(current)) {
233+continue;
234+}
235+visited.add(current);
236+const node = current as Record<string, unknown>;
237+const nodeType = typeof node.type === "string" ? node.type : null;
238+239+if (nodeType === "ImportDeclaration") {
240+const specifier = extractLiteralSpecifier(node.source);
241+if (specifier) {
242+specifiers.add(specifier);
243+}
244+} else if (nodeType === "ExportAllDeclaration" || nodeType === "ExportNamedDeclaration") {
245+const specifier = extractLiteralSpecifier(node.source);
246+if (specifier) {
247+specifiers.add(specifier);
248+}
249+} else if (nodeType === "ImportExpression") {
250+const specifier = extractLiteralSpecifier(node.source);
251+if (specifier) {
252+specifiers.add(specifier);
253+}
254+} else if (nodeType === "CallExpression") {
255+const callee = node.callee as { type?: string; name?: string } | undefined;
256+const args = Array.isArray(node.arguments) ? node.arguments : [];
257+if (callee?.type === "Identifier" && callee.name === "require" && args.length === 1) {
258+const specifier = extractLiteralSpecifier(args[0]);
259+if (specifier) {
260+specifiers.add(specifier);
261+}
262+}
263+}
264+265+for (const value of Object.values(node)) {
266+if (Array.isArray(value)) {
267+pending.push(...value);
268+} else if (value && typeof value === "object") {
269+pending.push(value);
270+}
271+}
272+}
273+274+return { ok: true, specifiers };
275+}
276+277+export function collectInstalledRootDependencyManifestErrors(packageRoot: string): string[] {
278+const packageJsonPath = join(packageRoot, "package.json");
279+if (!existsSync(packageJsonPath)) {
280+return ["installed package is missing package.json."];
281+}
282+const packageJsonStat = lstatSync(packageJsonPath);
283+if (!packageJsonStat.isFile() || packageJsonStat.size > MAX_INSTALLED_ROOT_PACKAGE_JSON_BYTES) {
284+return [
285+`installed package.json is invalid or exceeds ${MAX_INSTALLED_ROOT_PACKAGE_JSON_BYTES} bytes.`,
286+];
287+}
288+ let rootPackageJson: InstalledPackageJson;
289+try {
290+rootPackageJson = JSON.parse(readFileSync(packageJsonPath, "utf8")) as InstalledPackageJson;
291+} catch (error) {
292+return [`installed package.json could not be parsed: ${formatErrorMessage(error)}.`];
293+}
294+const declaredRuntimeDeps = new Set([
295+ ...Object.keys(rootPackageJson.dependencies ?? {}),
296+ ...Object.keys(rootPackageJson.optionalDependencies ?? {}),
297+]);
298+const distFiles = listInstalledRootDistJavaScriptFiles(packageRoot);
299+if (distFiles.length > MAX_INSTALLED_ROOT_DIST_JS_FILES) {
300+return [
301+`installed package root dist contains ${distFiles.length} JavaScript files, exceeding the ${MAX_INSTALLED_ROOT_DIST_JS_FILES} file scan limit.`,
302+];
303+}
304+const missingImporters = new Map<string, Set<string>>();
305+306+for (const filePath of distFiles) {
307+const fileStat = lstatSync(filePath);
308+if (!fileStat.isFile() || fileStat.size > MAX_INSTALLED_ROOT_DIST_JS_BYTES) {
309+const relativePath = relative(join(packageRoot, "dist"), filePath).replaceAll("\\", "/");
310+return [
311+`installed package root dist file '${relativePath}' is invalid or exceeds ${MAX_INSTALLED_ROOT_DIST_JS_BYTES} bytes.`,
312+];
313+}
314+const source = readFileSync(filePath, "utf8");
315+const relativePath = relative(join(packageRoot, "dist"), filePath).replaceAll("\\", "/");
316+const parsedSpecifiers = extractJavaScriptImportSpecifiers(source);
317+if (!parsedSpecifiers.ok) {
318+return [
319+`installed package root dist file '${relativePath}' could not be parsed for runtime dependency verification: ${parsedSpecifiers.error}.`,
320+];
321+}
322+for (const specifier of parsedSpecifiers.specifiers) {
323+const dependencyName = packageNameFromSpecifier(specifier);
324+if (
325+!dependencyName ||
326+NODE_BUILTIN_MODULES.has(dependencyName) ||
327+declaredRuntimeDeps.has(dependencyName)
328+) {
329+continue;
330+}
331+const importers = missingImporters.get(dependencyName) ?? new Set<string>();
332+importers.add(relativePath);
333+missingImporters.set(dependencyName, importers);
334+}
335+}
336+337+return [...missingImporters.entries()]
338+.map(([dependencyName, importers]) => {
339+const importerList = [...importers].toSorted((left, right) => left.localeCompare(right));
340+return `installed package root is missing declared runtime dependency '${dependencyName}' for dist importers: ${importerList.join(", ")}. Add it to package.json dependencies/optionalDependencies.`;
341+})
342+.toSorted((left, right) => left.localeCompare(right));
343+}
344+157345export function resolveInstalledBinaryPath(prefixDir: string, platform = process.platform): string {
158346return platform === "win32"
159347 ? join(prefixDir, "openclaw.cmd")
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。