mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-04 06:12:10 +03:00
The server-ws closure test hardcoded ONE wrapper and ONE import form. This generalizes it: every dist-root wrapper in EXTRA_MODULE_ENTRIES that ships in the npm channel has its local imports (static, dynamic import(), require()) required in both APP_STAGING_ALLOWED_EXACT_PATHS and PACK_ARTIFACT_REQUIRED_PATHS, and the bin/omniroute.mjs CLI boot path is closure-checked too — its direct imports bin/cli/data-dir.mjs and bin/cli/utils/storageKeyProvision.mjs were only covered by an allowlist PREFIX (absence from the tarball had no gate) and are now required paths. TDD: the bin closure test failed on those two before the policy fix.
This commit is contained in:
committed by
GitHub
parent
2c62333b0b
commit
9767b7eb34
1
changelog.d/fixes/pack-entrypoint-closures.md
Normal file
1
changelog.d/fixes/pack-entrypoint-closures.md
Normal file
@@ -0,0 +1 @@
|
||||
- **Packaging**: pack-artifact closure tests now cover EVERY npm-shipped dist wrapper (derived from `EXTRA_MODULE_ENTRIES`) and the `bin/omniroute.mjs` CLI entry — including dynamic `import()` and `require()` forms the original server-ws test missed — requiring each local import in both the prepublish prune allowlist and `check:pack-artifact`; `bin/cli/data-dir.mjs` and `bin/cli/utils/storageKeyProvision.mjs` are now required tarball paths (#7065 class hardening, WS1.1)
|
||||
@@ -160,6 +160,12 @@ export const PACK_ARTIFACT_REQUIRED_PATHS: string[] = [
|
||||
"dist/head-response-guard.cjs",
|
||||
"dist/webdav-handler.mjs",
|
||||
"bin/cli/program.mjs",
|
||||
// Direct imports of bin/omniroute.mjs — bin/cli/ is only an allowlist PREFIX, so a
|
||||
// file vanishing from the tarball never fails the unexpected-paths check; only these
|
||||
// required entries make its absence loud (#7065 class; derived + enforced by
|
||||
// tests/unit/pack-artifact-entrypoint-closures.test.ts).
|
||||
"bin/cli/data-dir.mjs",
|
||||
"bin/cli/utils/storageKeyProvision.mjs",
|
||||
"bin/mcp-server.mjs",
|
||||
"bin/nodeRuntimeSupport.mjs",
|
||||
"bin/omniroute.mjs",
|
||||
|
||||
125
tests/unit/pack-artifact-entrypoint-closures.test.ts
Normal file
125
tests/unit/pack-artifact-entrypoint-closures.test.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import {
|
||||
APP_STAGING_ALLOWED_EXACT_PATHS,
|
||||
PACK_ARTIFACT_REQUIRED_PATHS,
|
||||
} from "../../scripts/build/pack-artifact-policy.ts";
|
||||
|
||||
// Generalization of pack-artifact-server-ws-closure.test.ts (#7065 class, 3rd recurrence:
|
||||
// tls-options/3.8.41, head-response-guard VPS #7040 + npm #7065). assembleStandalone copies
|
||||
// wrapper modules to the dist ROOT; the prepublish prune then deletes anything not in
|
||||
// APP_STAGING_ALLOWED_EXACT_PATHS, and check:pack-artifact only fails for entries in
|
||||
// PACK_ARTIFACT_REQUIRED_PATHS. The original test hardcoded ONE wrapper (server-ws.mjs)
|
||||
// and ONE import form (static `from "./x"`). This suite derives the full closure from the
|
||||
// sources of truth — EXTRA_MODULE_ENTRIES in assembleStandalone.mjs plus each wrapper's own
|
||||
// imports (static, dynamic import() and require()) — so adding an import to ANY npm-shipped
|
||||
// wrapper without updating both lists fails here instead of shipping a boot-crashing tarball.
|
||||
|
||||
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
||||
const ASSEMBLE = path.join(ROOT, "scripts", "build", "assembleStandalone.mjs");
|
||||
const BIN_ENTRY = path.join(ROOT, "bin", "omniroute.mjs");
|
||||
|
||||
interface WrapperEntry {
|
||||
src: string;
|
||||
dest: string;
|
||||
}
|
||||
|
||||
// EXTRA_MODULE_ENTRIES entries whose dest is a bare module filename at the dist root —
|
||||
// these are the boot-path wrappers the prune can silently drop (the #7065 shape).
|
||||
function distRootWrappers(): WrapperEntry[] {
|
||||
const text = fs.readFileSync(ASSEMBLE, "utf8");
|
||||
const entries = [...text.matchAll(/src:\s*\[([^\]]+)\],?\s*dest:\s*\[([^\]]+)\]/gs)];
|
||||
const toPath = (segmentList: string) =>
|
||||
segmentList
|
||||
.split(",")
|
||||
.map((s) => s.trim().replace(/^"|"$/g, ""))
|
||||
.filter(Boolean)
|
||||
.join("/");
|
||||
return entries
|
||||
.map((m) => ({ src: toPath(m[1]), dest: toPath(m[2]) }))
|
||||
.filter((e) => !e.dest.includes("/") && /\.(mjs|cjs|js)$/.test(e.dest));
|
||||
}
|
||||
|
||||
// Local sibling imports of a module: static `from "./x"`, dynamic `import("./x")`,
|
||||
// and CommonJS `require("./x")`. The original test missed the dynamic form — server-ws
|
||||
// boots dist/server.js via `await import("./server.js")`.
|
||||
function localImports(filePath: string): string[] {
|
||||
const src = fs.readFileSync(filePath, "utf8");
|
||||
const patterns = [
|
||||
/from\s+["']\.\/([^"']+)["']/g,
|
||||
/import\(\s*["']\.\/([^"']+)["']\s*\)/g,
|
||||
/require\(\s*["']\.\/([^"']+)["']\s*\)/g,
|
||||
];
|
||||
return [...new Set(patterns.flatMap((re) => [...src.matchAll(re)].map((m) => m[1])))];
|
||||
}
|
||||
|
||||
// Wrappers that ship in the npm channel are exactly those whose dest survives the prune.
|
||||
// Wrappers intentionally outside the npm tarball (e.g. healthcheck.mjs, Docker-only) are
|
||||
// excluded: their imports live or die with them, consistently.
|
||||
function npmShippedWrappers(): WrapperEntry[] {
|
||||
return distRootWrappers().filter((e) => APP_STAGING_ALLOWED_EXACT_PATHS.includes(e.dest));
|
||||
}
|
||||
|
||||
test("sanity: EXTRA_MODULE_ENTRIES parsing finds the known dist-root wrappers", () => {
|
||||
const dests = distRootWrappers().map((e) => e.dest);
|
||||
for (const known of ["server-ws.mjs", "peer-stamp.mjs", "head-response-guard.cjs"]) {
|
||||
assert.ok(dests.includes(known), `parser lost known wrapper ${known}: got ${dests.join(", ")}`);
|
||||
}
|
||||
assert.ok(dests.length >= 7, `parsed only ${dests.length} dist-root wrappers`);
|
||||
});
|
||||
|
||||
test("every local import of every npm-shipped wrapper survives the prune (allowlist)", () => {
|
||||
for (const wrapper of npmShippedWrappers()) {
|
||||
const srcPath = path.join(ROOT, wrapper.src);
|
||||
assert.ok(fs.existsSync(srcPath), `EXTRA_MODULE_ENTRIES src missing on disk: ${wrapper.src}`);
|
||||
const missing = localImports(srcPath).filter(
|
||||
(f) => !APP_STAGING_ALLOWED_EXACT_PATHS.includes(f)
|
||||
);
|
||||
assert.deepEqual(
|
||||
missing,
|
||||
[],
|
||||
`${wrapper.dest}: add to APP_STAGING_ALLOWED_EXACT_PATHS: ${missing.join(", ")}`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("every local import of every npm-shipped wrapper is enforced by check:pack-artifact", () => {
|
||||
for (const wrapper of npmShippedWrappers()) {
|
||||
const missing = localImports(path.join(ROOT, wrapper.src)).filter(
|
||||
(f) => !PACK_ARTIFACT_REQUIRED_PATHS.includes(`dist/${f}`)
|
||||
);
|
||||
assert.deepEqual(
|
||||
missing,
|
||||
[],
|
||||
`${wrapper.dest}: add dist/<file> to PACK_ARTIFACT_REQUIRED_PATHS: ${missing.join(", ")}`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("dynamic import() closure is covered (server-ws boots dist/server.js)", () => {
|
||||
const serverWs = distRootWrappers().find((e) => e.dest === "server-ws.mjs");
|
||||
assert.ok(serverWs, "server-ws.mjs wrapper not found in EXTRA_MODULE_ENTRIES");
|
||||
const imports = localImports(path.join(ROOT, serverWs.src));
|
||||
assert.ok(
|
||||
imports.includes("server.js"),
|
||||
`dynamic import extraction broken — server.js not among: ${imports.join(", ")}`
|
||||
);
|
||||
});
|
||||
|
||||
test("every bin/omniroute.mjs local import is enforced by check:pack-artifact", () => {
|
||||
// The CLI boot path (bin/omniroute.mjs → bin/cli/*) is covered by allowlist PREFIXES,
|
||||
// so a file vanishing from the tarball never fails the unexpected-paths check — only
|
||||
// PACK_ARTIFACT_REQUIRED_PATHS makes its absence loud. Derive the requirement from
|
||||
// the entrypoint's own imports.
|
||||
const missing = localImports(BIN_ENTRY).filter(
|
||||
(f) => !PACK_ARTIFACT_REQUIRED_PATHS.includes(`bin/${f}`)
|
||||
);
|
||||
assert.deepEqual(
|
||||
missing,
|
||||
[],
|
||||
`add bin/<file> to PACK_ARTIFACT_REQUIRED_PATHS: ${missing.join(", ")}`
|
||||
);
|
||||
});
|
||||
@@ -105,7 +105,9 @@ test("findMissingArtifactPaths flags missing root runtime files in the tarball",
|
||||
// alphabetically (bin/ < dist/ < scripts/ < src/), minus the paths present
|
||||
// above (dist/server.js, bin/omniroute.mjs, package.json, the postinstall scripts).
|
||||
assert.deepEqual(missingPaths, [
|
||||
"bin/cli/data-dir.mjs",
|
||||
"bin/cli/program.mjs",
|
||||
"bin/cli/utils/storageKeyProvision.mjs",
|
||||
"bin/mcp-server.mjs",
|
||||
"bin/nodeRuntimeSupport.mjs",
|
||||
"dist/head-response-guard.cjs",
|
||||
|
||||
Reference in New Issue
Block a user