import { execFile } from "node:child_process"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { promisify } from "node:util"; import { describe, expect, it } from "vitest"; const run = promisify(execFile); const packageDirectory = path.dirname(path.dirname(fileURLToPath(import.meta.url))); const fixtureDirectory = path.join(packageDirectory, "tests", "browser-boundary"); const typescript = path.join(packageDirectory, "node_modules", ".bin", "tsc"); async function typecheck(project: string): Promise<{ code: number; output: string }> { try { await run(typescript, ["--noEmit", "--project", path.join(fixtureDirectory, project)]); return { code: 0, output: "" }; } catch (error) { const failure = error as { code?: number; stdout?: string; stderr?: string }; return { code: failure.code ?? 1, output: `${failure.stdout ?? ""}${failure.stderr ?? ""}` }; } } /** * The browser boundary, tested the way a module author experiences it rather than by inspecting the * dependency graph. * * Both fixtures compile under the *exact* project a module uses for UI source — * `browser-react.json` with `"types": []`. Before this package dropped its `@ekklesia/contracts` * dependency, the negative fixture below compiled successfully: that import reached a backend * barrel whose Drizzle types re-introduced Node's global declarations, so `process.env` typechecked * inside browser-only code (ADR 0024). */ describe("module UI SDK browser boundary", () => { it("compiles supported UI source that imports the public SDK", async () => { const result = await typecheck("tsconfig.supported.json"); expect(result.output).toBe(""); expect(result.code).toBe(0); }, 60_000); it("refuses Node globals in UI source that imports the public SDK", async () => { const result = await typecheck("tsconfig.node-global.json"); expect(result.code).not.toBe(0); // Specifically because `process` does not exist — not because of an unrelated error. expect(result.output).toContain("Cannot find name 'process'"); expect(result.output).toContain("node-global.tsx"); }, 60_000); });