diff options
Diffstat (limited to 'test/daemon/entry-imports-test.ts')
| -rw-r--r-- | test/daemon/entry-imports-test.ts | 50 |
1 files changed, 50 insertions, 0 deletions
diff --git a/test/daemon/entry-imports-test.ts b/test/daemon/entry-imports-test.ts new file mode 100644 index 0000000..24119a6 --- /dev/null +++ b/test/daemon/entry-imports-test.ts @@ -0,0 +1,50 @@ +import { test, describe } from "node:test"; +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +// The daemon is spawned as a separate Node process that resolves modules from the package directory +// upward. pi installs packages with dev and peer dependencies omitted and maps host-provided packages +// (typebox) only inside the extension host, so anything the daemon imports must be a runtime +// dependency of this package or a Node builtin. Issue #34 was `Cannot find module 'typebox'`. +const RUNTIME_DEPENDENCIES: ReadonlySet<string> = new Set( + Object.keys(JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf8")).dependencies ?? {}), +); + +const srcRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "src"); + +const importSpecifiers = (file: string): ReadonlyArray<string> => + [...readFileSync(file, "utf8").matchAll(/^(?:import|export)[^'"\n]*from\s*["']([^'"]+)["']/gm)].map((m) => m[1] ?? ""); + +const externalImportsReachableFrom = (entry: string): ReadonlyMap<string, string> => { + const external = new Map<string, string>(); + const seen = new Set<string>(); + const walk = (file: string): void => { + if (seen.has(file)) return; + seen.add(file); + for (const spec of importSpecifiers(file)) { + if (spec.startsWith(".")) { + let target = resolve(dirname(file), spec); + if (!target.endsWith(".ts")) target += ".ts"; + walk(target); + } else if (!spec.startsWith("node:")) { + external.set(spec, file.slice(srcRoot.length + 1)); + } + } + }; + walk(entry); + return external; +}; + +describe("daemon entrypoint imports", () => { + test("the daemon only reaches runtime dependencies, never host-provided or dev packages", () => { + const external = externalImportsReachableFrom(join(srcRoot, "daemon", "index.ts")); + const offenders = [...external].filter(([spec]) => !RUNTIME_DEPENDENCIES.has(spec.split("/")[0] ?? spec)); + assert.deepEqual( + offenders, + [], + `daemon imports packages that are not in "dependencies": ${offenders.map(([s, f]) => `${s} (via ${f})`).join(", ")}`, + ); + }); +}); |
