import { readdir, readFile } from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { describe, expect, it } from "vitest"; const sourceDirectory = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "src"); async function sourceFiles(): Promise { const entries = await readdir(sourceDirectory); return entries.filter((entry) => entry.endsWith(".ts")).sort(); } /** * This package is bundled into a browser. `@ekklesia/module-sdk` re-exports all of * `@ekklesia/contracts`, which pulls Zod, Drizzle, and semver behind one barrel export, so a single * value import from a backend package here would drag that whole graph into every module UI bundle * (ADR 0022). The dependency on `@ekklesia/contracts` is deliberately type-only and must stay that * way; `import type` is erased at build time, a plain `import` is not. */ describe("module UI SDK stays browser-safe", () => { it("imports nothing from another workspace package at runtime", async () => { for (const file of await sourceFiles()) { const source = await readFile(path.join(sourceDirectory, file), "utf8"); const workspaceImports = [ ...source.matchAll(/^import\s+(.*?)\s+from\s+"(@ekklesia\/[^"]+)"/gmu), ]; for (const [, clause, specifier] of workspaceImports) { expect( clause?.startsWith("type "), `${file} imports ${specifier} at runtime; it must be a type-only import`, ).toBe(true); } } }); it("declares no runtime dependency other than the type-only contracts package", async () => { const manifestPath = path.join(sourceDirectory, "..", "package.json"); const manifest = JSON.parse(await readFile(manifestPath, "utf8")) as { dependencies?: Record; }; expect(Object.keys(manifest.dependencies ?? {})).toEqual(["@ekklesia/contracts"]); }); it("does not depend on a UI framework, so the contract stays framework-neutral", async () => { const manifestPath = path.join(sourceDirectory, "..", "package.json"); const manifest = JSON.parse(await readFile(manifestPath, "utf8")) as { dependencies?: Record; devDependencies?: Record; peerDependencies?: Record; }; const declared = [ ...Object.keys(manifest.dependencies ?? {}), ...Object.keys(manifest.devDependencies ?? {}), ...Object.keys(manifest.peerDependencies ?? {}), ]; // React is the supported way to *author* module UI. It is deliberately absent from the host // contract, so a host never has to match a module's rendering library. for (const framework of ["react", "react-dom", "vue", "svelte", "preact"]) { expect(declared).not.toContain(framework); } }); });