import fs from "node:fs"; import { builtinModules } from "node:module"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { build as analyzeBuild } from "esbuild"; const BUILTIN_MODULES = new Set([...builtinModules, ...builtinModules.map((name) => `node:${name}`)]); const OPAQUE_REQUIRE_PATTERN = /\brequireFromHere\(\s*["'`]([^"'`]+)["'`]\s*\)/gu; // A dynamic import whose specifier is held in a local string variable is opaque to esbuild, so the two // halves are matched separately and paired by position afterwards. Spanning both halves in one pattern // (declaration, then a lazy `[\s\S]{0,500}?` gap, then the import) overflows the regex stack on a bundled // server artifact, where a single generated line can run into the megabytes. const OPAQUE_STRING_BINDING_PATTERN = /\b(?:const|let|var)\s+([$_A-Za-z][\w$]*)\s*=\s*["'`]([^"'`]+)["'`]\s*;/gu; // The comment body uses an unrolled loop rather than a lazy span so it cannot backtrack across the file. const OPAQUE_DYNAMIC_IMPORT_PATTERN = /\bimport\(\s*(?:\/\*[^*]*(?:\*(?!\/)[^*]*)*\*\/\s*)?([$_A-Za-z][\w$]*)\s*\)/gu; /** How far after its declaration the variable may be imported and still count as the same binding. */ const OPAQUE_BINDING_MAX_DISTANCE = 500; export const RUNTIME_CLOSURE_TARGETS = { web: { entryPoints: [ "build/server-runtime/server.js", "apps/web/build/server/index.js", "apps/web/build-portal/server/index.js", "packages/db/dist/deploy-permission-catalog-sync.server.js", ], manifestPath: "runtime/web/package.json", }, api: { entryPoints: ["apps/api/dist/server.js"], manifestPath: "runtime/api/package.json", }, worker: { entryPoints: [ "apps/worker/dist/index.js", "apps/worker/dist/health.js", "apps/worker/dist/jobs/plugin-sandbox-worker.js", "apps/worker/dist/jobs/file-preview-decoder-process.js", ], manifestPath: "runtime/worker/package.json", }, mcp: { entryPoints: ["apps/mcp/dist/server.js"], manifestPath: "runtime/mcp/package.json", }, hocuspocus: { entryPoints: ["apps/hocuspocus/dist/index.js"], manifestPath: "runtime/hocuspocus/package.json", }, "portal-domain-controller": { entryPoints: ["apps/portal-domain-controller/dist/index.js"], manifestPath: "runtime/portal-domain-controller/package.json", }, }; export function packageNameFromSpecifier(specifier) { if (!specifier || specifier.startsWith(".") || specifier.startsWith("/") || specifier.startsWith("#") || BUILTIN_MODULES.has(specifier)) { return null; } const segments = specifier.split("/"); return specifier.startsWith("@") ? segments.slice(0, 2).join("/") : segments[0]; } export async function collectRuntimePackageImports(rootDir, entryPoints) { const packageNames = new Set(); const absoluteEntryPoints = entryPoints.map((entryPoint) => path.join(rootDir, entryPoint)); for (const [index, entryPoint] of absoluteEntryPoints.entries()) { if (!fs.existsSync(entryPoint)) { throw new Error(`missing runtime artifact entry point: ${entryPoints[index]}`); } } const analysis = await analyzeBuild({ absWorkingDir: rootDir, bundle: true, entryPoints: absoluteEntryPoints, format: "esm", logLevel: "silent", metafile: true, outdir: path.join(rootDir, ".runtime-closure-analysis"), packages: "external", platform: "node", write: false, }); for (const input of Object.values(analysis.metafile.inputs)) { for (const imported of input.imports) { const packageName = packageNameFromSpecifier(imported.path); if (imported.external && packageName) packageNames.add(packageName); } } for (const inputPath of Object.keys(analysis.metafile.inputs)) { const absoluteInputPath = path.isAbsolute(inputPath) ? inputPath : path.join(rootDir, inputPath); if (!fs.existsSync(absoluteInputPath) || !/\.[cm]?js$/u.test(absoluteInputPath)) continue; const source = fs.readFileSync(absoluteInputPath, "utf8"); for (const specifier of collectOpaquePackageSpecifiers(source)) { const packageName = packageNameFromSpecifier(specifier); if (packageName) packageNames.add(packageName); } } return [...packageNames].sort(); } export function collectOpaquePackageSpecifiers(source) { const specifiers = []; for (const match of source.matchAll(OPAQUE_REQUIRE_PATTERN)) { if (match[1]) specifiers.push(match[1]); } // Declarations first, keyed by name and kept in source order, so each import can be paired with the // nearest preceding one. Re-assigning the same name later shadows the earlier value, as it would at runtime. const bindings = new Map(); for (const match of source.matchAll(OPAQUE_STRING_BINDING_PATTERN)) { if (!match[1] || !match[2]) continue; const declarations = bindings.get(match[1]) ?? []; declarations.push({ specifier: match[2], endIndex: match.index + match[0].length }); bindings.set(match[1], declarations); } for (const match of source.matchAll(OPAQUE_DYNAMIC_IMPORT_PATTERN)) { const declarations = bindings.get(match[1]); if (!declarations) continue; const declaration = lastDeclarationWithinReach(declarations, match.index); if (declaration) specifiers.push(declaration.specifier); } return specifiers; } function lastDeclarationWithinReach(declarations, importIndex) { for (let index = declarations.length - 1; index >= 0; index -= 1) { const declaration = declarations[index]; if (declaration.endIndex > importIndex) continue; return importIndex - declaration.endIndex <= OPAQUE_BINDING_MAX_DISTANCE ? declaration : null; } return null; } export async function verifyRuntimeClosure(rootDir, targetName, targetConfig = RUNTIME_CLOSURE_TARGETS[targetName]) { if (!targetConfig) throw new Error(`unknown runtime closure target: ${targetName}`); const manifest = JSON.parse(fs.readFileSync(path.join(rootDir, targetConfig.manifestPath), "utf8")); const declaredDependencies = Object.keys(manifest.dependencies ?? {}).sort(); const importedPackages = await collectRuntimePackageImports(rootDir, targetConfig.entryPoints); const missingDependencies = importedPackages.filter((packageName) => !declaredDependencies.includes(packageName)); return { targetName, manifestPath: targetConfig.manifestPath, declaredDependencies, importedPackages, missingDependencies, }; } export function parseRuntimeSelection(args) { const selected = []; for (let index = 0; index < args.length; index += 1) { const argument = args[index]; let targetName; if (argument === "--") { continue; } else if (argument === "--runtime") { targetName = args[index + 1]; index += 1; } else if (argument.startsWith("--runtime=")) { targetName = argument.slice("--runtime=".length); } else { throw new Error(`unknown runtime closure argument: ${argument}`); } if (!targetName) throw new Error("--runtime requires a target name"); if (!(targetName in RUNTIME_CLOSURE_TARGETS)) { throw new Error(`unknown runtime closure target: ${targetName}`); } if (!selected.includes(targetName)) selected.push(targetName); } return selected.length > 0 ? selected : Object.keys(RUNTIME_CLOSURE_TARGETS); } export async function verifyRuntimeClosures( rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."), targetNames = Object.keys(RUNTIME_CLOSURE_TARGETS) ) { return Promise.all(targetNames.map((targetName) => verifyRuntimeClosure(rootDir, targetName))); } async function runCli() { let targetNames; try { targetNames = parseRuntimeSelection(process.argv.slice(2)); } catch (error) { console.error(`FAIL ${(error instanceof Error && error.message) || String(error)}`); process.exitCode = 1; return; } const results = await verifyRuntimeClosures(undefined, targetNames); let failed = false; for (const result of results) { if (result.missingDependencies.length > 0) { failed = true; console.error(`FAIL runtime=${result.targetName} missing=${result.missingDependencies.join(",")}`); } else { console.log(`PASS runtime=${result.targetName} imports=${result.importedPackages.length} dependencies=${result.declaredDependencies.length}`); } } if (failed) process.exitCode = 1; } if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) await runCli();