189 lines
8.2 KiB
JavaScript
189 lines
8.2 KiB
JavaScript
// @vitest-environment node
|
|
import assert from "node:assert/strict";
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { afterEach, describe, test } from "vitest";
|
|
import {
|
|
RUNTIME_CLOSURE_TARGETS,
|
|
collectOpaquePackageSpecifiers,
|
|
collectRuntimePackageImports,
|
|
packageNameFromSpecifier,
|
|
parseRuntimeSelection,
|
|
verifyRuntimeClosure,
|
|
} from "./verify-runtime-closures.mjs";
|
|
|
|
const fixtures = [];
|
|
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
|
|
afterEach(() => {
|
|
for (const fixture of fixtures.splice(0)) fs.rmSync(fixture, { force: true, recursive: true });
|
|
});
|
|
|
|
describe("runtime closure verification", () => {
|
|
test("includes separately spawned worker entry points in the runtime closure", () => {
|
|
assert.ok(RUNTIME_CLOSURE_TARGETS.worker.entryPoints.includes("apps/worker/dist/jobs/plugin-sandbox-worker.js"));
|
|
assert.ok(RUNTIME_CLOSURE_TARGETS.worker.entryPoints.includes("apps/worker/dist/jobs/file-preview-decoder-process.js"));
|
|
});
|
|
|
|
test("includes the one-shot permission catalog migrator in the web runtime closure", () => {
|
|
assert.ok(RUNTIME_CLOSURE_TARGETS.web.entryPoints.includes("packages/db/dist/deploy-permission-catalog-sync.server.js"));
|
|
});
|
|
|
|
test("keeps pg external in database-backed service artifacts and runtime closures", () => {
|
|
const buildScripts = {
|
|
api: "apps/api/scripts/build.mjs",
|
|
hocuspocus: "apps/hocuspocus/scripts/build.mjs",
|
|
mcp: "apps/mcp/scripts/build.mjs",
|
|
web: "scripts/build-web-server.mjs",
|
|
};
|
|
|
|
for (const [target, buildScript] of Object.entries(buildScripts)) {
|
|
const buildSource = fs.readFileSync(path.join(rootDir, buildScript), "utf8");
|
|
const runtimeManifest = JSON.parse(fs.readFileSync(path.join(rootDir, `runtime/${target}/package.json`), "utf8"));
|
|
|
|
assert.match(buildSource, /specifier === "pg"/, target);
|
|
assert.match(buildSource, /specifier\.startsWith\("pg\/"\)/, target);
|
|
assert.equal(runtimeManifest.dependencies.pg, "^8.20.0", target);
|
|
}
|
|
});
|
|
|
|
test("normalizes package subpaths while excluding relative and Node imports", () => {
|
|
assert.equal(packageNameFromSpecifier("@scope/package/subpath"), "@scope/package");
|
|
assert.equal(packageNameFromSpecifier("package/subpath"), "package");
|
|
assert.equal(packageNameFromSpecifier("node:http"), null);
|
|
assert.equal(packageNameFromSpecifier("./chunk.js"), null);
|
|
});
|
|
|
|
test("collects static, dynamic, and CommonJS package imports", async () => {
|
|
const rootDir = createFixture({
|
|
"dist/index.js": [
|
|
'import "side-effect";',
|
|
'import value from "@scope/static/subpath";',
|
|
'export { value } from "exported";',
|
|
'await import("dynamic/subpath");',
|
|
'require("commonjs/feature");',
|
|
'import "node:http";',
|
|
'import "./chunk.js";',
|
|
].join("\n"),
|
|
"dist/chunk.js": "export const value = true;\n",
|
|
});
|
|
|
|
assert.deepEqual(await collectRuntimePackageImports(rootDir, ["dist/index.js"]), ["@scope/static", "commonjs", "dynamic", "exported", "side-effect"]);
|
|
});
|
|
|
|
test("collects package imports hidden behind runtime-resolved strings", async () => {
|
|
const source = [
|
|
'const moduleName = "opaque-dynamic/subpath";',
|
|
"await import(/* runtime */ moduleName);",
|
|
'requireFromHere("@scope/opaque/feature");',
|
|
].join("\n");
|
|
assert.deepEqual(collectOpaquePackageSpecifiers(source), ["@scope/opaque/feature", "opaque-dynamic/subpath"]);
|
|
|
|
const rootDir = createFixture({ "dist/index.js": source });
|
|
assert.deepEqual(await collectRuntimePackageImports(rootDir, ["dist/index.js"]), ["@scope/opaque", "opaque-dynamic"]);
|
|
});
|
|
|
|
test("pairs a runtime-resolved import with the nearest preceding declaration of that name", () => {
|
|
const source = [
|
|
'const moduleName = "first-package";',
|
|
"await import(moduleName);",
|
|
'const moduleName = "second-package";',
|
|
"await import(moduleName);",
|
|
].join("\n");
|
|
|
|
assert.deepEqual(collectOpaquePackageSpecifiers(source), ["first-package", "second-package"]);
|
|
});
|
|
|
|
test("ignores a declaration that is too far from the import to be the same binding", () => {
|
|
const near = ['const moduleName = "near-package";', `// ${"x".repeat(400)}`, "await import(moduleName);"].join("\n");
|
|
const far = ['const moduleName = "far-package";', `// ${"x".repeat(600)}`, "await import(moduleName);"].join("\n");
|
|
|
|
assert.deepEqual(collectOpaquePackageSpecifiers(near), ["near-package"]);
|
|
assert.deepEqual(collectOpaquePackageSpecifiers(far), []);
|
|
});
|
|
|
|
test("scans a bundled artifact with megabyte-long generated lines without overflowing", () => {
|
|
// A single generated line of this size used to blow the regex stack and fail the CI check outright.
|
|
const source = [
|
|
'const moduleName = "bundled-package";',
|
|
"await import(moduleName);",
|
|
`const filler = ${JSON.stringify("y".repeat(2_000_000))};`,
|
|
].join("\n");
|
|
|
|
assert.deepEqual(collectOpaquePackageSpecifiers(source), ["bundled-package"]);
|
|
});
|
|
|
|
test("reports artifact imports absent from the target runtime manifest", async () => {
|
|
const rootDir = createFixture({
|
|
"dist/index.js": 'import "present"; import "missing/subpath";\n',
|
|
"runtime/package.json": JSON.stringify({ private: true, dependencies: { present: "1.0.0" } }),
|
|
});
|
|
|
|
const result = await verifyRuntimeClosure(rootDir, "fixture", {
|
|
entryPoints: ["dist/index.js"],
|
|
manifestPath: "runtime/package.json",
|
|
});
|
|
|
|
assert.deepEqual(result.missingDependencies, ["missing"]);
|
|
});
|
|
|
|
test("reports dependencies missing only from a secondary runtime entry point", async () => {
|
|
const rootDir = createFixture({
|
|
"dist/server.js": 'import "present";\n',
|
|
"dist/migrator.js": 'import "migrator-only-missing/subpath";\n',
|
|
"runtime/package.json": JSON.stringify({ private: true, dependencies: { present: "1.0.0" } }),
|
|
});
|
|
|
|
const result = await verifyRuntimeClosure(rootDir, "fixture", {
|
|
entryPoints: ["dist/server.js", "dist/migrator.js"],
|
|
manifestPath: "runtime/package.json",
|
|
});
|
|
|
|
assert.deepEqual(result.missingDependencies, ["migrator-only-missing"]);
|
|
});
|
|
|
|
test("selects one or more explicit runtime targets for post-build CI verification", () => {
|
|
assert.deepEqual(parseRuntimeSelection(["--runtime=api"]), ["api"]);
|
|
assert.deepEqual(parseRuntimeSelection(["--", "--runtime=hocuspocus"]), ["hocuspocus"]);
|
|
assert.deepEqual(parseRuntimeSelection(["--runtime", "worker", "--runtime=mcp"]), ["worker", "mcp"]);
|
|
assert.deepEqual(parseRuntimeSelection(["--runtime=web", "--runtime=web"]), ["web"]);
|
|
assert.throws(() => parseRuntimeSelection(["--runtime=unknown"]), /unknown runtime closure target/);
|
|
assert.throws(() => parseRuntimeSelection(["--other=api"]), /unknown runtime closure argument/);
|
|
assert.throws(() => parseRuntimeSelection(["--runtime"]), /requires a target name/);
|
|
});
|
|
|
|
test("verifies only selected runtime targets", async () => {
|
|
const rootDir = createFixture({
|
|
"dist/first.js": 'import "first-dependency";\n',
|
|
"dist/second.js": 'import "second-dependency";\n',
|
|
"runtime/first.json": JSON.stringify({ dependencies: { "first-dependency": "1.0.0" } }),
|
|
"runtime/second.json": JSON.stringify({ dependencies: { "second-dependency": "1.0.0" } }),
|
|
});
|
|
const targets = {
|
|
first: { entryPoints: ["dist/first.js"], manifestPath: "runtime/first.json" },
|
|
second: { entryPoints: ["dist/second.js"], manifestPath: "runtime/second.json" },
|
|
};
|
|
|
|
const results = await Promise.all(["second"].map((targetName) => verifyRuntimeClosure(rootDir, targetName, targets[targetName])));
|
|
|
|
assert.deepEqual(
|
|
results.map((result) => result.targetName),
|
|
["second"]
|
|
);
|
|
assert.deepEqual(results[0].missingDependencies, []);
|
|
});
|
|
});
|
|
|
|
function createFixture(files) {
|
|
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "runtime-closure-"));
|
|
fixtures.push(rootDir);
|
|
for (const [relativePath, content] of Object.entries(files)) {
|
|
const filePath = path.join(rootDir, relativePath);
|
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
fs.writeFileSync(filePath, content);
|
|
}
|
|
return rootDir;
|
|
}
|