openclaw/scripts/check-cli-bootstrap-imports.mts
Peter Steinberger 4aa24e3a13
perf(nodes): load only what a worker turn needs (#160442)
* perf(nodes): split sealed worker bundles and defer turn imports

Preserve and hash every emitted worker chunk in the v1 bundle manifest, including SSH and node installs. Load the turn runtime after admission and retain explicit prewarming. Keep only import-boundary repairs required by splitting, the session type-cycle repair, and lazy MCP OAuth loading.

* fix(nodes): update moved session context type imports

* test(nodes): exercise the split worker artifact layout

* test(nodes): distinguish built-ins from bundled packages

* fix(nodes): keep maintenance contracts outside runtime readers

* test(build): include worker inputs in declaration fixtures

* fix(browser): keep tracked tab cleanup lazy

Move internal raw-target closing into a private lazy runtime so empty cleanup does not load the browser client. Revalidate the selected registration after loading, and preserve cleanup reservation coverage.
2026-10-02 14:02:07 -07:00

576 lines
19 KiB
TypeScript

#!/usr/bin/env node
// Checks CLI bootstrap chunks for forbidden eager imports and size regressions.
import fs from "node:fs";
import module from "node:module";
import path from "node:path";
import type { Node as AcornNode } from "acorn";
import {
WORKER_BUNDLE_ARTIFACT_PATHS,
WORKER_BUNDLE_CHUNK_PATH_PATTERN,
} from "../src/shared/worker-bundle-hash.js";
import { reportLimitViolations, type LimitViolation } from "./lib/check-limits.mts";
import { isDirectRunUrl } from "./lib/direct-run.mjs";
import { readGatewayRunChunks } from "./lib/gateway-run-chunk-metadata.mts";
import { visitJavaScriptStatements } from "./lib/javascript-statements.mjs";
import { isUnstagedWorkerDeployRuntimeArtifact } from "./lib/worker-deploy-build-plugin.mts";
const DEFAULT_ENTRYPOINTS = ["dist/entry.js", "dist/cli/run-main.js"];
const DEFAULT_NATIVE_HOOK_RELAY_ENTRYPOINT = "dist/native-hook-relay/entry.js";
const DEFAULT_NATIVE_HOOK_RELAY_STATIC_MAX_BYTES = 512 * 1024;
const NATIVE_HOOK_RELAY_FORBIDDEN_STATIC_MARKERS = [
"MAX_NATIVE_HOOK_RELAY_INVOCATIONS",
"getActivePluginSessionExtensionRegistry",
"requestDeferredPluginToolApproval",
"runBeforeToolCallHook",
];
// fs-safe must retain its package scope for optional native-platform loading.
const NATIVE_HOOK_RELAY_ALLOWED_EXTERNAL_IMPORTS = ["kysely", "@openclaw/fs-safe"];
const WORKER_DEPLOY_ENTRYPOINTS = WORKER_BUNDLE_ARTIFACT_PATHS.map(
(entry) => `dist/worker/${entry}`,
);
const DEFAULT_GATEWAY_RUN_CHUNK_MAX_BYTES = 70 * 1024;
const GATEWAY_RUN_CHUNK_MARKER_SETS = [
["const GATEWAY_AUTH_MODES", "function addGatewayRunCommand"],
["const GATEWAY_RUN_VALUE_KEYS", "function addGatewayRunCommand"],
];
const GATEWAY_RUN_FORBIDDEN_STATIC_IMPORTS = [
"control-ui-assets",
"diagnostic-stability-bundle",
"onboard-helpers",
"process-respawn",
"restart-sentinel",
"server-close",
"server-reload-hot",
"server-reload-managed",
];
const STATIC_IMPORT_RE =
/\b(?:import|export)\s+(?:(?:[^'"()]*?\s+from\s+)|)["'](?<specifier>[^"']+)["']/gu;
type CliBootstrapCheckParams = {
rootDir?: string;
entrypoints?: string[];
workerDeployEntrypoints?: readonly string[];
distDir?: string;
gatewayRunChunkMaxBytes?: number;
legacyGatewayChunkDiscovery?: boolean;
nativeHookRelayEntrypoint?: string;
requireNativeHookRelay?: boolean;
nativeHookRelayStaticMaxBytes?: number;
fs?: typeof fs;
logger?: { error(message: string): void };
};
function isBuiltinSpecifier(specifier: string) {
return specifier.startsWith("node:") || module.isBuiltin(specifier);
}
function isRelativeSpecifier(specifier: string) {
return specifier.startsWith("./") || specifier.startsWith("../") || specifier.startsWith("/");
}
function resolveRelativeImport(importer: string, specifier: string, fsImpl: typeof fs = fs) {
const base = specifier.startsWith("/")
? specifier
: path.resolve(path.dirname(importer), specifier);
const candidates = [
base,
`${base}.js`,
`${base}.mjs`,
`${base}.cjs`,
path.join(base, "index.js"),
path.join(base, "index.mjs"),
path.join(base, "index.cjs"),
];
return candidates.find((candidate) => {
try {
return fsImpl.statSync(candidate).isFile();
} catch {
return false;
}
});
}
/**
* Lists static import/export specifiers from a JavaScript source string.
*/
export function listStaticImportSpecifiers(source: string) {
return [...source.matchAll(STATIC_IMPORT_RE)].map((match) => match.groups?.specifier ?? "");
}
function literalString(value: unknown): string | undefined {
if (!value || typeof value !== "object" || Array.isArray(value)) {
return undefined;
}
const node = value as { type?: unknown; value?: unknown };
return node.type === "Literal" && typeof node.value === "string" ? node.value : undefined;
}
function isRequireCallName(value: unknown): boolean {
return typeof value === "string" && /^(?:require|_*require\d*)$/u.test(value);
}
function isRequireLikeCallee(value: unknown): boolean {
if (!value || typeof value !== "object" || Array.isArray(value)) {
return false;
}
const node = value as Record<string, unknown>;
if (node.type === "Identifier") {
return isRequireCallName(node.name);
}
if (node.type !== "CallExpression") {
return false;
}
const callee = node.callee;
if (!callee || typeof callee !== "object" || Array.isArray(callee)) {
return false;
}
const member = callee as Record<string, unknown>;
if (member.type === "Identifier" && member.name === "createRequire") {
return true;
}
const property = member.property;
return (
member.type === "MemberExpression" &&
property !== null &&
typeof property === "object" &&
!Array.isArray(property) &&
(property as Record<string, unknown>).type === "Identifier" &&
(property as Record<string, unknown>).name === "createRequire"
);
}
function listRuntimeImportSpecifiers(source: string): string[] {
const specifiers: string[] = [];
visitJavaScriptStatements(source, { sourceType: "module", allowHashBang: true }, (statements) => {
const stack: unknown[] = statements;
while (stack.length > 0) {
const value = stack.pop();
if (!value || typeof value !== "object") {
continue;
}
if (Array.isArray(value)) {
stack.push(...value);
continue;
}
const node = value as AcornNode & Record<string, unknown>;
if (
node.type === "ImportDeclaration" ||
node.type === "ExportNamedDeclaration" ||
node.type === "ExportAllDeclaration" ||
node.type === "ImportExpression"
) {
const specifier = literalString(node.source);
if (specifier) {
specifiers.push(specifier);
}
} else if (node.type === "CallExpression") {
const callee = node.callee;
const args = node.arguments;
if (isRequireLikeCallee(callee) && Array.isArray(args)) {
const specifier = literalString(args[0]);
if (specifier) {
specifiers.push(specifier);
}
}
}
// Large worker bundles contain millions of nodes; avoid a pair allocation per property.
for (const key of Object.keys(node)) {
const child = node[key];
if (key === "start" || key === "end" || key === "loc" || key === "range") {
continue;
}
if (child && typeof child === "object") {
stack.push(child);
}
}
}
});
return [...new Set(specifiers)].toSorted((left, right) => left.localeCompare(right));
}
function walkStaticImportGraph(
fsImpl: typeof fs,
rootDir: string,
roots: string[],
onExternalSpecifier?: (filePath: string, specifier: string) => string,
onRelativeSpecifier?: (
filePath: string,
resolved: string,
specifier: string,
) => string | undefined,
onSource?: (filePath: string, source: string) => string[],
) {
const queue = roots.map((entrypoint) => path.resolve(rootDir, entrypoint));
const visited = new Set<string>();
const errors = [];
for (const filePath of queue) {
if (!filePath || visited.has(filePath)) {
continue;
}
visited.add(filePath);
let source;
try {
source = fsImpl.readFileSync(filePath, "utf8");
} catch {
errors.push(
`CLI bootstrap import guard could not read ${path.relative(rootDir, filePath) || filePath}. Run pnpm build first.`,
);
continue;
}
errors.push(...(onSource?.(filePath, source) ?? []));
for (const specifier of listStaticImportSpecifiers(source)) {
if (!specifier || isBuiltinSpecifier(specifier)) {
continue;
}
if (!isRelativeSpecifier(specifier)) {
const error = onExternalSpecifier?.(filePath, specifier);
if (error) {
errors.push(error);
}
continue;
}
const resolved = resolveRelativeImport(filePath, specifier, fsImpl);
if (!resolved) {
errors.push(
`CLI bootstrap import guard could not resolve "${specifier}" from ${path.relative(
rootDir,
filePath,
)}.`,
);
continue;
}
const error = onRelativeSpecifier?.(filePath, resolved, specifier);
if (error) {
errors.push(error);
}
if (!visited.has(resolved)) {
queue.push(resolved);
}
}
}
return errors;
}
/** Collects isolation and static-graph budget errors for the native hook relay executable. */
export function collectNativeHookRelayBundleErrors(params: CliBootstrapCheckParams = {}) {
const rootDir = params.rootDir ?? process.cwd();
const fsImpl = params.fs ?? fs;
const entrypoint = params.nativeHookRelayEntrypoint ?? DEFAULT_NATIVE_HOOK_RELAY_ENTRYPOINT;
const entrypointPath = path.resolve(rootDir, entrypoint);
// Release tooling also validates older packages whose supported relay is the general CLI.
// Current builds require this artifact; every present relay is checked in either mode.
if (!params.requireNativeHookRelay && !fsImpl.existsSync(entrypointPath)) {
return [];
}
const bundleDir = path.resolve(rootDir, params.distDir ?? "dist");
const maxBytes =
params.nativeHookRelayStaticMaxBytes ?? DEFAULT_NATIVE_HOOK_RELAY_STATIC_MAX_BYTES;
let staticBytes = 0;
const errors: Array<string | LimitViolation> = walkStaticImportGraph(
fsImpl,
rootDir,
[entrypoint],
(filePath, specifier) =>
NATIVE_HOOK_RELAY_ALLOWED_EXTERNAL_IMPORTS.some(
(dependency) => specifier === dependency || specifier.startsWith(`${dependency}/`),
)
? ""
: `Native hook relay static graph imports unexpected package "${specifier}" from ${
path.relative(rootDir, filePath) || filePath
}.`,
(filePath, resolved, specifier) => {
const relativeToBundle = path.relative(bundleDir, resolved);
return !relativeToBundle.startsWith("..") && !path.isAbsolute(relativeToBundle)
? undefined
: `Native hook relay static graph escapes the built runtime via "${specifier}" from ${
path.relative(rootDir, filePath) || filePath
}.`;
},
(filePath, source) => {
try {
staticBytes += fsImpl.statSync(filePath).size;
} catch {
staticBytes += Buffer.byteLength(source, "utf8");
}
return NATIVE_HOOK_RELAY_FORBIDDEN_STATIC_MARKERS.flatMap((marker) =>
source.includes(marker)
? [
`Native hook relay static graph contains server marker "${marker}" in ${
path.relative(rootDir, filePath) || filePath
}.`,
]
: [],
);
},
).filter(Boolean);
if (staticBytes > maxBytes) {
errors.push({
file: entrypoint,
title: "Native hook relay bundle budget",
message: `Native hook relay static graph is ${staticBytes} bytes, above budget ${maxBytes} bytes.`,
});
}
return errors.toSorted((left, right) =>
(typeof left === "string" ? left : left.message).localeCompare(
typeof right === "string" ? right : right.message,
),
);
}
/**
* Collects forbidden external import errors for CLI bootstrap entrypoints.
*/
export function collectCliBootstrapExternalImportErrors(params: CliBootstrapCheckParams = {}) {
const rootDir = params.rootDir ?? process.cwd();
const entrypoints = params.entrypoints ?? DEFAULT_ENTRYPOINTS;
const fsImpl = params.fs ?? fs;
const errors = walkStaticImportGraph(
fsImpl,
rootDir,
entrypoints,
(filePath, specifier) =>
`CLI bootstrap static graph imports external package "${specifier}" from ${path.relative(
rootDir,
filePath,
)}.`,
);
return errors.toSorted((left, right) => left.localeCompare(right));
}
function listJsFiles(dirPath: string, fsImpl: typeof fs = fs): string[] {
let entries;
try {
entries = fsImpl.readdirSync(dirPath, { withFileTypes: true });
} catch {
return [];
}
const files = [];
for (const entry of entries) {
const fullPath = path.join(dirPath, entry.name);
if (entry.isDirectory()) {
files.push(...listJsFiles(fullPath, fsImpl));
continue;
}
if (entry.isFile() && /\.m?js$/u.test(entry.name)) {
files.push(fullPath);
}
}
return files;
}
/**
* Collects gateway-run chunk budget errors from built CLI output.
*/
export function collectGatewayRunChunkBudgetErrors(params: CliBootstrapCheckParams = {}) {
const rootDir = params.rootDir ?? process.cwd();
const fsImpl = params.fs ?? fs;
const distDir = path.resolve(rootDir, params.distDir ?? "dist");
const maxBytes = params.gatewayRunChunkMaxBytes ?? DEFAULT_GATEWAY_RUN_CHUNK_MAX_BYTES;
let chunks: Array<{ filePath: string; source: string }> = [];
if (params.legacyGatewayChunkDiscovery) {
// Current release tooling also qualifies frozen targets predating build-owned locators.
// Only that explicit caller may retain the historical full-tree discovery contract.
for (const filePath of listJsFiles(distDir, fsImpl)) {
let source;
try {
source = fsImpl.readFileSync(filePath, "utf8");
} catch {
continue;
}
if (
GATEWAY_RUN_CHUNK_MARKER_SETS.some((markers) =>
markers.every((marker) => source.includes(marker)),
)
) {
chunks.push({ filePath, source });
}
}
} else {
try {
chunks = readGatewayRunChunks(distDir, fsImpl);
} catch (error) {
return [
`CLI bootstrap import guard could not read gateway run chunk metadata: ${error instanceof Error ? error.message : String(error)}. Run pnpm build first.`,
];
}
}
if (chunks.length === 0) {
return [
"CLI bootstrap import guard could not find the bundled gateway run chunk. Run pnpm build first.",
];
}
const errors: Array<string | LimitViolation> = [];
for (const { filePath, source } of chunks) {
const relativePath = path.relative(rootDir, filePath) || filePath;
let size = Buffer.byteLength(source, "utf8");
try {
size = fsImpl.statSync(filePath).size;
} catch {
// Fall back to source byte length for in-memory test fixtures.
}
if (size > maxBytes) {
errors.push({
file: relativePath,
title: "Gateway run chunk budget",
message: `Gateway run chunk ${relativePath} is ${size} bytes, above budget ${maxBytes} bytes.`,
});
}
errors.push(
...walkStaticImportGraph(
fsImpl,
rootDir,
[filePath],
undefined,
(importerPath, resolved, specifier) => {
const resolvedRelativePath = path.relative(rootDir, resolved) || resolved;
const coldPath = [specifier, resolvedRelativePath].find((candidate) =>
GATEWAY_RUN_FORBIDDEN_STATIC_IMPORTS.some((forbidden) => candidate.includes(forbidden)),
);
return coldPath
? `Gateway run chunk ${relativePath} static graph imports cold path "${coldPath}" from ${
path.relative(rootDir, importerPath) || importerPath
}.`
: undefined;
},
),
);
}
return errors.toSorted((left, right) =>
(typeof left === "string" ? left : left.message).localeCompare(
typeof right === "string" ? right : right.message,
),
);
}
/** Collects closure and layout errors for the standalone worker deploy artifact. */
export function collectWorkerDeployArtifactErrors(params: CliBootstrapCheckParams = {}) {
const rootDir = params.rootDir ?? process.cwd();
const fsImpl = params.fs ?? fs;
const entrypoints = (params.workerDeployEntrypoints ?? WORKER_DEPLOY_ENTRYPOINTS).map(
(entrypoint) => path.resolve(rootDir, entrypoint),
);
const artifactDir = path.resolve(rootDir, "dist/worker");
const artifactNames = new Set(
entrypoints.flatMap((entrypoint) => {
const name = path.basename(entrypoint);
return [name, `${name}.map`];
}),
);
const errors: string[] = [];
const sources: Array<{ relativeEntrypoint: string; source: string }> = [];
try {
for (const entry of fsImpl.readdirSync(artifactDir, { withFileTypes: true })) {
if (artifactNames.has(entry.name)) {
continue;
}
if (WORKER_BUNDLE_CHUNK_PATH_PATTERN.test(entry.name)) {
artifactNames.add(entry.name);
entrypoints.push(path.join(artifactDir, entry.name));
} else if (entry.name === "package.json") {
errors.push(
"Worker deploy artifact must not contain a dependency manifest or lifecycle scripts.",
);
} else if (entry.name === "node_modules") {
errors.push("Worker deploy artifact must not contain materialized dependencies.");
} else if (isUnstagedWorkerDeployRuntimeArtifact(entry.name, artifactNames)) {
errors.push(
`Worker deploy artifact emits unstaged runtime asset ${path.relative(
rootDir,
path.join(artifactDir, entry.name),
)}.`,
);
}
}
} catch (error) {
if (entrypoints.length === 0 && (error as NodeJS.ErrnoException).code === "ENOENT") {
return [];
}
errors.push(
`Worker deploy artifact directory ${path.relative(rootDir, artifactDir)} is unreadable.`,
);
}
for (const entrypoint of entrypoints) {
const relativeEntrypoint = path.relative(rootDir, entrypoint) || entrypoint;
try {
const stats = fsImpl.lstatSync(entrypoint);
if (stats.isSymbolicLink() || !stats.isFile()) {
return [`Worker deploy artifact ${relativeEntrypoint} must be a regular file.`];
}
sources.push({
relativeEntrypoint,
source: fsImpl.readFileSync(entrypoint, "utf8"),
});
} catch {
return [`Worker deploy artifact ${relativeEntrypoint} is missing. Run pnpm build first.`];
}
}
for (const { relativeEntrypoint, source } of sources) {
try {
for (const specifier of listRuntimeImportSpecifiers(source)) {
if (
isBuiltinSpecifier(specifier) ||
specifier === "bun:ffi" ||
(specifier.startsWith("./") &&
specifier.endsWith(".mjs") &&
artifactNames.has(specifier.slice(2)))
) {
continue;
}
errors.push(
`Worker deploy artifact ${relativeEntrypoint} retains runtime import "${specifier}" instead of bundling it.`,
);
}
} catch (error) {
errors.push(
`Worker deploy artifact ${relativeEntrypoint} is not parseable JavaScript: ${
error instanceof Error ? error.message : String(error)
}.`,
);
}
}
return errors.toSorted((left, right) => left.localeCompare(right));
}
/**
* Runs the CLI bootstrap import, chunk-budget, and worker deploy checks.
*/
export function checkCliBootstrapExternalImports(params: CliBootstrapCheckParams = {}) {
const findings = [
...collectCliBootstrapExternalImportErrors(params),
...collectGatewayRunChunkBudgetErrors(params),
...collectNativeHookRelayBundleErrors(params),
...collectWorkerDeployArtifactErrors(params),
];
const errors = findings.filter((finding) => typeof finding === "string");
const limitsFailed = reportLimitViolations(
findings.filter((finding) => typeof finding !== "string"),
);
if (errors.length === 0 && !limitsFailed) {
return;
}
const logger = params.logger ?? console;
logger.error("CLI bootstrap import guard failed:");
for (const error of errors) {
logger.error(` - ${error}`);
}
throw new Error("CLI bootstrap static graph imports external packages.");
}
if (isDirectRunUrl(process.argv[1], import.meta.url)) {
try {
checkCliBootstrapExternalImports({ requireNativeHookRelay: true });
console.log("CLI bootstrap import guard passed.");
} catch {
process.exit(1);
}
}