openclaw/scripts/check-cli-bootstrap-imports.mts
Peter Steinberger 375fb61402
fix: preserve large CUA responses under Bun (#163456)
* fix: preserve large CUA responses under Bun

Use runtime stdio streams for anchor output backpressure, then publish
socket EOF through Bun's built-in POSIX shutdown binding after flush.
This preserves process cleanup authority and works before the fork's
duplicated-descriptor fix as well as on the current fork.

Keep stdin and signal handling unchanged, and fund the output adapter
with behavior-preserving type and envelope simplification (net zero LOC).

The existing CUA consumer passes 13/13 on Node 24, old Bun b368, and the
current fork on AWS Linux; old Bun previously failed two large-output
cases. All three pass real retirement and stdin readiness tests. Both
output descriptors preserve 11 MiB and publish EOF while the owner stays
alive, including stock musl Bun and macOS. Scoped P2 review, changed-file
checks, and import-cycle checks pass.

* fix(build): recognize Bun FFI in sealed worker artifacts

The CUA output fix uses Bun's runtime-provided FFI module behind a Bun-only
guard. Admit that exact builtin in the worker closure check while retaining
the Node CLI eager-import policy and rejecting native npm dependencies.

The added fixture fails before this correction and passes afterward on
Node and Bun. A real qaRuntime build passes its artifact guard, and the
sealed anchor runs without project dependencies on Node, old Bun, and the
current fork with full output and EOF before process-owner retirement.

Scoped P2 review and changed-script checks pass. Production LOC stays flat.
2026-10-02 06:40:31 -05:00

564 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 } 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 }> = [];
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.`];
}
}
try {
for (const entry of fsImpl.readdirSync(artifactDir, { withFileTypes: true })) {
if (artifactNames.has(entry.name)) {
continue;
}
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 { relativeEntrypoint, source } of sources) {
try {
for (const specifier of listRuntimeImportSpecifiers(source)) {
if (isBuiltinSpecifier(specifier) || specifier === "bun:ffi") {
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);
}
}