import { existsSync, readFileSync, readdirSync } from "node:fs"; import { fileURLToPath } from "node:url"; import { dirname, join, resolve } from "node:path"; import { describe, expect, it } from "vitest"; /** * GUARD: `@factory/shared/dto` is the browser-safe DTO surface — the web * bundle (Next.js/webpack) imports it, so it must never pull a Node builtin * (`node:crypto`, `node:fs`, …) into the browser. A Node-only import here * fails the web build with `UnhandledSchemeError` (regression: the * `webhook.dto.ts → events.ts → node:crypto` edge that broke every page). * * We scan every `dto/*.ts` file and its (recursive) relative-import closure * — the barrel transitively reaches `constants.ts`, `positions.ts`, * `webhook-events.ts`, `formula-ast.ts`, … — and assert none of them import * a Node builtin. Only REAL import/export-from statements are considered * (comments, including JSDoc examples, are stripped first). */ describe("@factory/shared/dto browser safety (no Node builtins in the web bundle)", () => { const dtoDir = dirname(fileURLToPath(import.meta.url)); /** Specifiers of every real import/export-from statement in the file. */ const importSpecifiersOf = (file: string): string[] => { const src = readFileSync(file, "utf8"); // Strip comments so doc examples (e.g. JSDoc quoting an import) are ignored. const code = src.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, ""); const specs = new Set(); for (const m of code.matchAll(/\b(?:import|export)\b[\s\S]*?\bfrom\s*["']([^"']+)["']/g)) { const spec = m[1]; if (spec) specs.add(spec); } for (const m of code.matchAll(/^\s*import\s*["']([^"']+)["']/gm)) { const spec = m[1]; if (spec) specs.add(spec); } return [...specs]; }; it("contains no file that imports a node: builtin (directly or transitively)", () => { const seen = new Set(); const queue = readdirSync(dtoDir) .filter((f) => f.endsWith(".ts") && !f.endsWith(".test.ts")) .map((f) => join(dtoDir, f)); const offenders: string[] = []; while (queue.length) { const file = queue.pop()!; if (seen.has(file)) continue; seen.add(file); for (const spec of importSpecifiersOf(file)) { if (spec.startsWith("node:") || /^(crypto|fs|path|os|stream|buffer)$/.test(spec)) { offenders.push(`${file} → "${spec}"`); continue; } if (spec.startsWith(".")) { const target = resolve(dirname(file), spec.replace(/\.js$/, ".ts")); if (!existsSync(target)) { offenders.push(`${file} → "${spec}" (unresolvable relative import)`); continue; } queue.push(target); } } } expect(offenders).toEqual([]); }); });