aboutsummaryrefslogtreecommitdiff
path: root/test/daemon/entry-imports-test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'test/daemon/entry-imports-test.ts')
-rw-r--r--test/daemon/entry-imports-test.ts50
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(", ")}`,
+ );
+ });
+});