From fe1c7fae990b7827e58624a922909b2907622e8b Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 28 Apr 2026 02:58:04 +0100 Subject: [PATCH] test: catch transitive gateway cold imports --- scripts/check-cli-bootstrap-imports.mjs | 71 +++++++++++++------ .../check-cli-bootstrap-imports.test.ts | 15 +++- 2 files changed, 65 insertions(+), 21 deletions(-) diff --git a/scripts/check-cli-bootstrap-imports.mjs b/scripts/check-cli-bootstrap-imports.mjs index 82374278da5..92d0aa86bc3 100644 --- a/scripts/check-cli-bootstrap-imports.mjs +++ b/scripts/check-cli-bootstrap-imports.mjs @@ -58,11 +58,9 @@ export function listStaticImportSpecifiers(source) { return [...source.matchAll(STATIC_IMPORT_RE)].map((match) => match.groups?.specifier ?? ""); } -export function collectCliBootstrapExternalImportErrors(params = {}) { - const rootDir = params.rootDir ?? process.cwd(); - const entrypoints = params.entrypoints ?? DEFAULT_ENTRYPOINTS; - const fsImpl = params.fs ?? fs; - const queue = entrypoints.map((entrypoint) => path.resolve(rootDir, entrypoint)); +function walkStaticImportGraph(params) { + const { fsImpl, rootDir } = params; + const queue = params.roots.map((entrypoint) => path.resolve(rootDir, entrypoint)); const visited = new Set(); const errors = []; @@ -82,18 +80,12 @@ export function collectCliBootstrapExternalImportErrors(params = {}) { ); continue; } - for (const specifier of listStaticImportSpecifiers(source)) { if (!specifier || isBuiltinSpecifier(specifier)) { continue; } if (!isRelativeSpecifier(specifier)) { - errors.push( - `CLI bootstrap static graph imports external package "${specifier}" from ${path.relative( - rootDir, - filePath, - )}.`, - ); + params.onExternalSpecifier?.({ filePath, specifier, errors }); continue; } const resolved = resolveRelativeImport(filePath, specifier, fsImpl); @@ -106,12 +98,34 @@ export function collectCliBootstrapExternalImportErrors(params = {}) { ); continue; } + params.onRelativeSpecifier?.({ filePath, resolved, specifier, errors }); if (!visited.has(resolved)) { queue.push(resolved); } } } + return errors; +} + +export function collectCliBootstrapExternalImportErrors(params = {}) { + const rootDir = params.rootDir ?? process.cwd(); + const entrypoints = params.entrypoints ?? DEFAULT_ENTRYPOINTS; + const fsImpl = params.fs ?? fs; + const errors = walkStaticImportGraph({ + fsImpl, + rootDir, + roots: entrypoints, + onExternalSpecifier: ({ filePath, specifier, errors: graphErrors }) => { + graphErrors.push( + `CLI bootstrap static graph imports external package "${specifier}" from ${path.relative( + rootDir, + filePath, + )}.`, + ); + }, + }); + return errors.toSorted((left, right) => left.localeCompare(right)); } @@ -176,15 +190,32 @@ export function collectGatewayRunChunkBudgetErrors(params = {}) { ); } - for (const specifier of listStaticImportSpecifiers(source)) { - for (const forbidden of GATEWAY_RUN_FORBIDDEN_STATIC_IMPORTS) { - if (specifier.includes(forbidden)) { - errors.push( - `Gateway run chunk ${relativePath} statically imports cold path "${specifier}".`, + errors.push( + ...walkStaticImportGraph({ + fsImpl, + rootDir, + roots: [filePath], + onRelativeSpecifier: ({ + filePath: importerPath, + resolved, + specifier, + errors: graphErrors, + }) => { + const resolvedRelativePath = path.relative(rootDir, resolved) || resolved; + const coldPath = [specifier, resolvedRelativePath].find((candidate) => + GATEWAY_RUN_FORBIDDEN_STATIC_IMPORTS.some((forbidden) => candidate.includes(forbidden)), ); - } - } - } + if (!coldPath) { + return; + } + graphErrors.push( + `Gateway run chunk ${relativePath} static graph imports cold path "${coldPath}" from ${ + path.relative(rootDir, importerPath) || importerPath + }.`, + ); + }, + }), + ); } return errors.toSorted((left, right) => left.localeCompare(right)); diff --git a/test/scripts/check-cli-bootstrap-imports.test.ts b/test/scripts/check-cli-bootstrap-imports.test.ts index 8faccef76ea..6a1de514ddf 100644 --- a/test/scripts/check-cli-bootstrap-imports.test.ts +++ b/test/scripts/check-cli-bootstrap-imports.test.ts @@ -24,6 +24,7 @@ function writeFixture(root: string, relativePath: string, source: string): void } function writeGatewayRunChunk(root: string, source = ""): void { + writeFixture(root, "dist/string-coerce.js", "export const normalize = true;"); writeFixture( root, "dist/run-gateway.js", @@ -96,9 +97,21 @@ describe("check-cli-bootstrap-imports", () => { it("reports cold static imports in the gateway run chunk", () => { const root = makeTempRoot(); writeGatewayRunChunk(root, 'import "./restart-sentinel-abc123.js";'); + writeFixture(root, "dist/restart-sentinel-abc123.js", "export const sentinel = true;"); expect(collectGatewayRunChunkBudgetErrors({ rootDir: root })).toEqual([ - 'Gateway run chunk dist/run-gateway.js statically imports cold path "./restart-sentinel-abc123.js".', + 'Gateway run chunk dist/run-gateway.js static graph imports cold path "./restart-sentinel-abc123.js" from dist/run-gateway.js.', + ]); + }); + + it("reports transitive cold static imports from the gateway run chunk graph", () => { + const root = makeTempRoot(); + writeGatewayRunChunk(root, 'import "./gateway-bridge.js";'); + writeFixture(root, "dist/gateway-bridge.js", 'import "./server-close-abc123.js";'); + writeFixture(root, "dist/server-close-abc123.js", "export const close = true;"); + + expect(collectGatewayRunChunkBudgetErrors({ rootDir: root })).toEqual([ + 'Gateway run chunk dist/run-gateway.js static graph imports cold path "./server-close-abc123.js" from dist/gateway-bridge.js.', ]); });