docs: document root build check scripts

This commit is contained in:
Peter Steinberger
2026-06-04 23:28:04 -04:00
parent deff9ea180
commit 74f3baebb7
10 changed files with 133 additions and 12 deletions

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env node #!/usr/bin/env node
// Builds browser runtime bundles for the diffs viewer assets.
import fs from "node:fs/promises"; import fs from "node:fs/promises";
import path from "node:path"; import path from "node:path";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
@@ -26,6 +27,9 @@ function toPosixPath(value) {
return String(value ?? "").replaceAll("\\", "/"); return String(value ?? "").replaceAll("\\", "/");
} }
/**
* Creates the esbuild plugin that neutralizes Pierre diffs' browser side-effect import.
*/
export function createPierreDiffsSideEffectImportPlugin() { export function createPierreDiffsSideEffectImportPlugin() {
return { return {
name: "openclaw-diffs-pierre-side-effect-imports", name: "openclaw-diffs-pierre-side-effect-imports",
@@ -55,6 +59,9 @@ export function createPierreDiffsSideEffectImportPlugin() {
}; };
} }
/**
* Builds one configured diffs viewer runtime target.
*/
export async function buildDiffsViewerRuntime(targetName) { export async function buildDiffsViewerRuntime(targetName) {
const target = targets[targetName]; const target = targets[targetName];
if (!target) { if (!target) {

View File

@@ -1,4 +1,6 @@
#!/usr/bin/env node #!/usr/bin/env node
// Writes the local build stamp and re-exports build metadata helpers.
import process from "node:process"; import process from "node:process";
import { pathToFileURL } from "node:url"; import { pathToFileURL } from "node:url";
import { writeBuildStamp } from "./lib/local-build-metadata.mjs"; import { writeBuildStamp } from "./lib/local-build-metadata.mjs";

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env node #!/usr/bin/env node
// Runs bundled asset build hooks for the Canvas A2UI runtime.
import { pathToFileURL } from "node:url"; import { pathToFileURL } from "node:url";
import { runBundledPluginAssetHooks } from "./bundled-plugin-assets.mjs"; import { runBundledPluginAssetHooks } from "./bundled-plugin-assets.mjs";

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env node #!/usr/bin/env node
// Discovers and runs bundled plugin package asset hooks.
import { spawnSync } from "node:child_process"; import { spawnSync } from "node:child_process";
import fs from "node:fs/promises"; import fs from "node:fs/promises";
import path from "node:path"; import path from "node:path";
@@ -57,6 +58,9 @@ function resolveAssetCommand(packageJson, phase) {
return typeof command === "string" && command.trim() ? command.trim() : null; return typeof command === "string" && command.trim() ? command.trim() : null;
} }
/**
* Reads bundled plugin asset hook commands for a build or copy phase.
*/
export async function readBundledPluginAssetHooks(options = {}) { export async function readBundledPluginAssetHooks(options = {}) {
const repoRoot = options.rootDir ?? rootDir; const repoRoot = options.rootDir ?? rootDir;
const phase = options.phase; const phase = options.phase;
@@ -108,6 +112,9 @@ export async function readBundledPluginAssetHooks(options = {}) {
return hooks.toSorted((left, right) => left.pluginDir.localeCompare(right.pluginDir)); return hooks.toSorted((left, right) => left.pluginDir.localeCompare(right.pluginDir));
} }
/**
* Runs bundled plugin asset hook commands for the selected phase/plugins.
*/
export async function runBundledPluginAssetHooks(options = {}) { export async function runBundledPluginAssetHooks(options = {}) {
const phase = options.phase; const phase = options.phase;
const hooks = await readBundledPluginAssetHooks(options); const hooks = await readBundledPluginAssetHooks(options);
@@ -131,6 +138,9 @@ export async function runBundledPluginAssetHooks(options = {}) {
} }
} }
/**
* Parses `--phase` and repeated `--plugin` flags for asset hook scripts.
*/
export function parseBundledPluginAssetArgs(argv) { export function parseBundledPluginAssetArgs(argv) {
const args = [...argv]; const args = [...argv];
const plugins = []; const plugins = [];

View File

@@ -1,3 +1,4 @@
// Classifies changed files into CI lanes and release metadata scopes.
import { execFileSync } from "node:child_process"; import { execFileSync } from "node:child_process";
import { appendFileSync, existsSync, readFileSync } from "node:fs"; import { appendFileSync, existsSync, readFileSync } from "node:fs";
import { booleanFlag, parseFlagArgs, stringFlag } from "./lib/arg-utils.mjs"; import { booleanFlag, parseFlagArgs, stringFlag } from "./lib/arg-utils.mjs";
@@ -24,6 +25,9 @@ const TEST_PATH_RE =
/(?:^|\/)(?:test|__tests__)\/|(?:\.|\/)(?:test|spec|e2e|browser\.test)\.[cm]?[jt]sx?$/u; /(?:^|\/)(?:test|__tests__)\/|(?:\.|\/)(?:test|spec|e2e|browser\.test)\.[cm]?[jt]sx?$/u;
const PUBLIC_EXTENSION_CONTRACT_RE = const PUBLIC_EXTENSION_CONTRACT_RE =
/^(?:src\/plugin-sdk\/|src\/plugins\/contracts\/|src\/channels\/plugins\/|scripts\/lib\/plugin-sdk-entrypoints\.json$|scripts\/sync-plugin-sdk-exports\.mjs$|scripts\/generate-plugin-sdk-api-baseline\.ts$)/u; /^(?:src\/plugin-sdk\/|src\/plugins\/contracts\/|src\/channels\/plugins\/|scripts\/lib\/plugin-sdk-entrypoints\.json$|scripts\/sync-plugin-sdk-exports\.mjs$|scripts\/generate-plugin-sdk-api-baseline\.ts$)/u;
/**
* Files whose changes are treated as release metadata only.
*/
export const RELEASE_METADATA_PATHS = new Set([ export const RELEASE_METADATA_PATHS = new Set([
"CHANGELOG.md", "CHANGELOG.md",
"apps/android/app/build.gradle.kts", "apps/android/app/build.gradle.kts",
@@ -49,6 +53,9 @@ export const RELEASE_METADATA_PATHS = new Set([
* }} ChangedLaneResult * }} ChangedLaneResult
*/ */
/**
* Normalizes a changed file path into repo-relative POSIX form.
*/
export function normalizeChangedPath(inputPath) { export function normalizeChangedPath(inputPath) {
return String(inputPath ?? "") return String(inputPath ?? "")
.trim() .trim()
@@ -56,6 +63,9 @@ export function normalizeChangedPath(inputPath) {
.replace(/^\.\/+/u, ""); .replace(/^\.\/+/u, "");
} }
/**
* Creates the default changed-lanes result object.
*/
export function createEmptyChangedLanes() { export function createEmptyChangedLanes() {
return { return {
core: false, core: false,
@@ -76,6 +86,9 @@ export function createEmptyChangedLanes() {
* @param {{ packageJsonChangeKind?: "liveDockerTooling" | "tooling" | null }} [options] * @param {{ packageJsonChangeKind?: "liveDockerTooling" | "tooling" | null }} [options]
* @returns {ChangedLaneResult} * @returns {ChangedLaneResult}
*/ */
/**
* Classifies a list of changed paths into docs, app, extension, core, and tooling lanes.
*/
export function detectChangedLanes(changedPaths, options = {}) { export function detectChangedLanes(changedPaths, options = {}) {
const paths = [...new Set(changedPaths.map(normalizeChangedPath).filter(Boolean))] const paths = [...new Set(changedPaths.map(normalizeChangedPath).filter(Boolean))]
.toSorted((left, right) => left.localeCompare(right)) .toSorted((left, right) => left.localeCompare(right))
@@ -217,6 +230,9 @@ export function detectChangedLanes(changedPaths, options = {}) {
* @param {{ paths: string[]; base: string; head?: string; staged?: boolean; mergeHeadFirstParent?: boolean }} params * @param {{ paths: string[]; base: string; head?: string; staged?: boolean; mergeHeadFirstParent?: boolean }} params
* @returns {ChangedLaneResult} * @returns {ChangedLaneResult}
*/ */
/**
* Classifies changed paths with optional package.json before/after contents.
*/
export function detectChangedLanesForPaths(params) { export function detectChangedLanesForPaths(params) {
const base = params.staged const base = params.staged
? params.base ? params.base
@@ -240,6 +256,9 @@ export function detectChangedLanesForPaths(params) {
* @param {{ base: string; head?: string; includeWorktree?: boolean; cwd?: string; mergeHeadFirstParent?: boolean }} params * @param {{ base: string; head?: string; includeWorktree?: boolean; cwd?: string; mergeHeadFirstParent?: boolean }} params
* @returns {string[]} * @returns {string[]}
*/ */
/**
* Lists changed paths from git for a base/head comparison.
*/
export function listChangedPathsFromGit(params) { export function listChangedPathsFromGit(params) {
const head = params.head ?? "HEAD"; const head = params.head ?? "HEAD";
const cwd = params.cwd ?? process.cwd(); const cwd = params.cwd ?? process.cwd();
@@ -318,6 +337,9 @@ function runGitLsFiles(extraArgs, cwd = process.cwd()) {
return output.split("\n").map(normalizeChangedPath).filter(Boolean); return output.split("\n").map(normalizeChangedPath).filter(Boolean);
} }
/**
* Lists staged changed paths for pre-commit checks.
*/
export function listStagedChangedPaths(cwd = process.cwd()) { export function listStagedChangedPaths(cwd = process.cwd()) {
const output = execFileSync("git", ["diff", "--cached", "--name-only", "--diff-filter=ACMRD"], { const output = execFileSync("git", ["diff", "--cached", "--name-only", "--diff-filter=ACMRD"], {
cwd, cwd,
@@ -328,6 +350,9 @@ export function listStagedChangedPaths(cwd = process.cwd()) {
return output.split("\n").map(normalizeChangedPath).filter(Boolean); return output.split("\n").map(normalizeChangedPath).filter(Boolean);
} }
/**
* Classifies package.json script-only changes from git content.
*/
export function classifyPackageJsonChangeFromGit(params) { export function classifyPackageJsonChangeFromGit(params) {
try { try {
const { before, after } = readPackageJsonBeforeAfter(params); const { before, after } = readPackageJsonBeforeAfter(params);
@@ -340,6 +365,9 @@ export function classifyPackageJsonChangeFromGit(params) {
} }
} }
/**
* Checks whether package scripts changed only live Docker script entries.
*/
export function isLiveDockerPackageScriptOnlyChange(before, after) { export function isLiveDockerPackageScriptOnlyChange(before, after) {
const beforePackage = JSON.parse(before); const beforePackage = JSON.parse(before);
const afterPackage = JSON.parse(after); const afterPackage = JSON.parse(after);
@@ -354,6 +382,9 @@ export function isLiveDockerPackageScriptOnlyChange(before, after) {
); );
} }
/**
* Checks whether package.json changes are limited to scripts.
*/
export function isPackageScriptOnlyChange(before, after) { export function isPackageScriptOnlyChange(before, after) {
const beforePackage = JSON.parse(before); const beforePackage = JSON.parse(before);
const afterPackage = JSON.parse(after); const afterPackage = JSON.parse(after);
@@ -444,6 +475,9 @@ function stableJson(value) {
return JSON.stringify(value); return JSON.stringify(value);
} }
/**
* Writes changed-lane booleans to the GitHub Actions output file.
*/
export function writeChangedLaneGitHubOutput(result, outputPath = process.env.GITHUB_OUTPUT) { export function writeChangedLaneGitHubOutput(result, outputPath = process.env.GITHUB_OUTPUT) {
if (!outputPath) { if (!outputPath) {
throw new Error("GITHUB_OUTPUT is required"); throw new Error("GITHUB_OUTPUT is required");

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env node #!/usr/bin/env node
// Finds core/plugin architecture boundary smells in TypeScript sources.
import { promises as fs } from "node:fs"; import { promises as fs } from "node:fs";
import path from "node:path"; import path from "node:path";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
@@ -10,12 +11,12 @@ import {
resolveRepoSpecifier, resolveRepoSpecifier,
writeLine, writeLine,
} from "./lib/guard-inventory-utils.mjs"; } from "./lib/guard-inventory-utils.mjs";
import { mapWithConcurrency } from "./lib/source-file-scan-cache.mjs";
import { import {
collectTypeScriptFilesFromRoots, collectTypeScriptFilesFromRoots,
resolveSourceRoots, resolveSourceRoots,
runAsScript, runAsScript,
} from "./lib/ts-guard-utils.mjs"; } from "./lib/ts-guard-utils.mjs";
import { mapWithConcurrency } from "./lib/source-file-scan-cache.mjs";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const scanRoots = resolveSourceRoots(repoRoot, ["src/plugin-sdk", "src/plugins/runtime"]); const scanRoots = resolveSourceRoots(repoRoot, ["src/plugin-sdk", "src/plugins/runtime"]);
@@ -165,23 +166,22 @@ function scanRuntimeServiceLocatorSmells(source, filePath) {
return entries; return entries;
} }
/**
* Collects architecture smell findings from the configured source roots.
*/
export async function collectArchitectureSmells() { export async function collectArchitectureSmells() {
if (!architectureSmellsPromise) { if (!architectureSmellsPromise) {
architectureSmellsPromise = (async () => { architectureSmellsPromise = (async () => {
const files = (await collectTypeScriptFilesFromRoots(scanRoots)).toSorted((left, right) => const files = (await collectTypeScriptFilesFromRoots(scanRoots)).toSorted((left, right) =>
normalizeRepoPath(repoRoot, left).localeCompare(normalizeRepoPath(repoRoot, right)), normalizeRepoPath(repoRoot, left).localeCompare(normalizeRepoPath(repoRoot, right)),
); );
const entriesByFile = await mapWithConcurrency( const entriesByFile = await mapWithConcurrency(files, undefined, async (filePath) => {
files, const source = await fs.readFile(filePath, "utf8");
undefined, const entries = scanPluginSdkExtensionFacadeSmells(source, filePath);
async (filePath) => { entries.push(...scanRuntimeTypeImplementationSmells(source, filePath));
const source = await fs.readFile(filePath, "utf8"); entries.push(...scanRuntimeServiceLocatorSmells(source, filePath));
const entries = scanPluginSdkExtensionFacadeSmells(source, filePath); return entries;
entries.push(...scanRuntimeTypeImplementationSmells(source, filePath)); });
entries.push(...scanRuntimeServiceLocatorSmells(source, filePath));
return entries;
},
);
return entriesByFile.flat().toSorted(compareEntries); return entriesByFile.flat().toSorted(compareEntries);
})(); })();
try { try {
@@ -235,6 +235,9 @@ async function runArchitectureSmellsCheck(argv, io) {
return 0; return 0;
} }
/**
* Runs the architecture smell check and writes human/JSON output.
*/
export async function main(argv, io) { export async function main(argv, io) {
return await runArchitectureSmellsCheck(argv, io); return await runArchitectureSmellsCheck(argv, io);
} }

View File

@@ -1,9 +1,13 @@
#!/usr/bin/env node #!/usr/bin/env node
// Rejects changelog thanks entries that credit bots or internal handles.
import fs from "node:fs"; import fs from "node:fs";
import path from "node:path"; import path from "node:path";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
/**
* Exact handles that changelog thanks entries must not credit.
*/
export const FORBIDDEN_CHANGELOG_THANKS_HANDLES = [ export const FORBIDDEN_CHANGELOG_THANKS_HANDLES = [
"codex", "codex",
"openclaw", "openclaw",
@@ -13,20 +17,38 @@ export const FORBIDDEN_CHANGELOG_THANKS_HANDLES = [
"clawsweeper[bot]", "clawsweeper[bot]",
"openclaw-clawsweeper[bot]", "openclaw-clawsweeper[bot]",
]; ];
/**
* Handle prefixes that identify forbidden changelog thanks credits.
*/
export const FORBIDDEN_CHANGELOG_THANKS_HANDLE_PREFIXES = ["app/"]; export const FORBIDDEN_CHANGELOG_THANKS_HANDLE_PREFIXES = ["app/"];
/**
* Handle suffixes that identify forbidden changelog thanks credits.
*/
export const FORBIDDEN_CHANGELOG_THANKS_HANDLE_SUFFIXES = ["[bot]"]; export const FORBIDDEN_CHANGELOG_THANKS_HANDLE_SUFFIXES = ["[bot]"];
/**
* Handles that require an explicit human credit instead.
*/
export const CHANGELOG_THANKS_REQUIRE_HUMAN_CREDIT_HANDLES = [ export const CHANGELOG_THANKS_REQUIRE_HUMAN_CREDIT_HANDLES = [
"clawsweeper", "clawsweeper",
"openclaw-clawsweeper", "openclaw-clawsweeper",
"clawsweeper[bot]", "clawsweeper[bot]",
"openclaw-clawsweeper[bot]", "openclaw-clawsweeper[bot]",
]; ];
/**
* Handle prefixes that require explicit human credit instead.
*/
export const CHANGELOG_THANKS_REQUIRE_HUMAN_CREDIT_HANDLE_PREFIXES = ["app/"]; export const CHANGELOG_THANKS_REQUIRE_HUMAN_CREDIT_HANDLE_PREFIXES = ["app/"];
/**
* Handle suffixes that require explicit human credit instead.
*/
export const CHANGELOG_THANKS_REQUIRE_HUMAN_CREDIT_HANDLE_SUFFIXES = ["[bot]"]; export const CHANGELOG_THANKS_REQUIRE_HUMAN_CREDIT_HANDLE_SUFFIXES = ["[bot]"];
const THANKS_PATTERN = /\bThanks\b/iu; const THANKS_PATTERN = /\bThanks\b/iu;
const THANKED_HANDLE_PATTERN = /@([-_/A-Za-z0-9]+(?:\[bot\])?)/giu; const THANKED_HANDLE_PATTERN = /@([-_/A-Za-z0-9]+(?:\[bot\])?)/giu;
/**
* Reports whether a handle is forbidden in changelog thanks text.
*/
export function isForbiddenChangelogThanksHandle(handle, options = {}) { export function isForbiddenChangelogThanksHandle(handle, options = {}) {
const { strictBotHandle = false } = options; const { strictBotHandle = false } = options;
const normalized = handle.toLowerCase(); const normalized = handle.toLowerCase();
@@ -48,6 +70,9 @@ export function isForbiddenChangelogThanksHandle(handle, options = {}) {
return false; return false;
} }
/**
* Reports whether a handle needs a separate human credit.
*/
export function requiresExplicitHumanChangelogThanks(handle) { export function requiresExplicitHumanChangelogThanks(handle) {
const normalized = handle.toLowerCase(); const normalized = handle.toLowerCase();
if (normalized === "" || normalized === "null") { if (normalized === "" || normalized === "null") {
@@ -64,6 +89,9 @@ export function requiresExplicitHumanChangelogThanks(handle) {
); );
} }
/**
* Finds changelog lines that thank forbidden handles.
*/
export function findForbiddenChangelogThanks(content) { export function findForbiddenChangelogThanks(content) {
return content return content
.split(/\r?\n/u) .split(/\r?\n/u)
@@ -82,6 +110,9 @@ export function findForbiddenChangelogThanks(content) {
.filter(Boolean); .filter(Boolean);
} }
/**
* Runs the changelog attribution check.
*/
export async function main(argv = process.argv.slice(2)) { export async function main(argv = process.argv.slice(2)) {
if (argv[0] === "--is-forbidden-handle") { if (argv[0] === "--is-forbidden-handle") {
process.exitCode = isForbiddenChangelogThanksHandle(argv[1] ?? "", { process.exitCode = isForbiddenChangelogThanksHandle(argv[1] ?? "", {

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env node #!/usr/bin/env node
// Checks channel-agnostic core surfaces for channel-specific coupling.
import { promises as fs } from "node:fs"; import { promises as fs } from "node:fs";
import path from "node:path"; import path from "node:path";
import ts from "typescript"; import ts from "typescript";
@@ -117,6 +118,9 @@ function isModuleSpecifierStringNode(node) {
); );
} }
/**
* Finds channel-specific references inside channel-agnostic protected sources.
*/
export function findChannelAgnosticBoundaryViolations( export function findChannelAgnosticBoundaryViolations(
content, content,
fileName = "source.ts", fileName = "source.ts",
@@ -236,6 +240,9 @@ export function findChannelAgnosticBoundaryViolations(
return violations; return violations;
} }
/**
* Finds reverse dependencies from channel core into plugin/runtime surfaces.
*/
export function findChannelCoreReverseDependencyViolations(content, fileName = "source.ts") { export function findChannelCoreReverseDependencyViolations(content, fileName = "source.ts") {
return findChannelAgnosticBoundaryViolations(content, fileName, { return findChannelAgnosticBoundaryViolations(content, fileName, {
checkModuleSpecifiers: true, checkModuleSpecifiers: true,
@@ -246,6 +253,9 @@ export function findChannelCoreReverseDependencyViolations(content, fileName = "
}); });
} }
/**
* Finds user-facing channel names in ACP-owned text sources.
*/
export function findAcpUserFacingChannelNameViolations(content, fileName = "source.ts") { export function findAcpUserFacingChannelNameViolations(content, fileName = "source.ts") {
const sourceFile = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true); const sourceFile = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true);
const violations = []; const violations = [];
@@ -265,6 +275,9 @@ export function findAcpUserFacingChannelNameViolations(content, fileName = "sour
return violations; return violations;
} }
/**
* Finds raw system mark literals where shared constants should be used.
*/
export function findSystemMarkLiteralViolations(content, fileName = "source.ts") { export function findSystemMarkLiteralViolations(content, fileName = "source.ts") {
const sourceFile = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true); const sourceFile = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true);
const violations = []; const violations = [];
@@ -307,6 +320,9 @@ const boundaryRuleSets = [
}, },
]; ];
/**
* Runs all channel-agnostic boundary checks.
*/
export async function main() { export async function main() {
const violations = []; const violations = [];
for (const ruleSet of boundaryRuleSets) { for (const ruleSet of boundaryRuleSets) {

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env node #!/usr/bin/env node
// Checks CLI bootstrap chunks for forbidden eager imports and size regressions.
import fs from "node:fs"; import fs from "node:fs";
import module from "node:module"; import module from "node:module";
import path from "node:path"; import path from "node:path";
@@ -57,6 +58,9 @@ function resolveRelativeImport(importer, specifier, fsImpl = fs) {
}); });
} }
/**
* Lists static import/export specifiers from a JavaScript source string.
*/
export function listStaticImportSpecifiers(source) { export function listStaticImportSpecifiers(source) {
return [...source.matchAll(STATIC_IMPORT_RE)].map((match) => match.groups?.specifier ?? ""); return [...source.matchAll(STATIC_IMPORT_RE)].map((match) => match.groups?.specifier ?? "");
} }
@@ -110,6 +114,9 @@ function walkStaticImportGraph(params) {
return errors; return errors;
} }
/**
* Collects forbidden external import errors for CLI bootstrap entrypoints.
*/
export function collectCliBootstrapExternalImportErrors(params = {}) { export function collectCliBootstrapExternalImportErrors(params = {}) {
const rootDir = params.rootDir ?? process.cwd(); const rootDir = params.rootDir ?? process.cwd();
const entrypoints = params.entrypoints ?? DEFAULT_ENTRYPOINTS; const entrypoints = params.entrypoints ?? DEFAULT_ENTRYPOINTS;
@@ -152,6 +159,9 @@ function listJsFiles(dirPath, fsImpl = fs) {
return files; return files;
} }
/**
* Collects gateway-run chunk budget errors from built CLI output.
*/
export function collectGatewayRunChunkBudgetErrors(params = {}) { export function collectGatewayRunChunkBudgetErrors(params = {}) {
const rootDir = params.rootDir ?? process.cwd(); const rootDir = params.rootDir ?? process.cwd();
const fsImpl = params.fs ?? fs; const fsImpl = params.fs ?? fs;
@@ -227,6 +237,9 @@ export function collectGatewayRunChunkBudgetErrors(params = {}) {
return errors.toSorted((left, right) => left.localeCompare(right)); return errors.toSorted((left, right) => left.localeCompare(right));
} }
/**
* Runs the CLI bootstrap import and chunk-budget checks.
*/
export function checkCliBootstrapExternalImports(params = {}) { export function checkCliBootstrapExternalImports(params = {}) {
const errors = [ const errors = [
...collectCliBootstrapExternalImportErrors(params), ...collectCliBootstrapExternalImportErrors(params),

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env node #!/usr/bin/env node
// Measures CLI startup memory with an isolated home and RSS hook.
import { spawnSync as defaultSpawnSync } from "node:child_process"; import { spawnSync as defaultSpawnSync } from "node:child_process";
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import os from "node:os"; import os from "node:os";
@@ -358,6 +359,9 @@ function runStartupMemoryCheck(argv = process.argv.slice(2), params = {}) {
return { skipped: false, results }; return { skipped: false, results };
} }
/**
* Test-only access to pure startup memory helper functions.
*/
export const testing = { export const testing = {
cases, cases,
parseArgs, parseArgs,