mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-03 05:45:04 +03:00
* fix(build): server-ws timeout helper as shipped sibling — ../../src import crashed every packed boot (#7065 class) * test(build): align pack-artifact-policy fixture with the new dist/main-server-timeouts.mjs required path
153 lines
6.7 KiB
TypeScript
153 lines
6.7 KiB
TypeScript
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])))];
|
|
}
|
|
|
|
/** Parent-relative specifiers (../) in a wrapper file — ALWAYS a packaging bug. */
|
|
export function parentRelativeImports(src: string): string[] {
|
|
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(", ")}`
|
|
);
|
|
});
|
|
|
|
test("no npm-shipped wrapper uses a parent-relative (../) import — it escapes the package after the dist-root copy", () => {
|
|
// 2026-07-15 live incident: standalone-server-ws.mjs imported
|
|
// ../../src/shared/utils/runtimeTimeouts.ts (merged in #7191); copied to the dist
|
|
// root, the specifier resolved to node_modules/src/... OUTSIDE the package and
|
|
// every boot of the packed tarball crashed with ERR_MODULE_NOT_FOUND (#7065
|
|
// class — caught by check:pack-boot). Wrapper dependencies must be SIBLINGS
|
|
// (./x.mjs) with their own EXTRA_MODULE_ENTRIES copy + pack allowlist entry.
|
|
for (const wrapper of npmShippedWrappers()) {
|
|
const escaping = parentRelativeImports(fs.readFileSync(path.join(ROOT, wrapper.src), "utf8"));
|
|
assert.deepEqual(
|
|
escaping,
|
|
[],
|
|
`${wrapper.src} has package-escaping imports: ${escaping.join(", ")} — extract to a sibling module instead`
|
|
);
|
|
}
|
|
});
|