import { readdir, readFile } from "node:fs/promises"; import path from "node:path"; import { describe, expect, it } from "vitest"; const repoRoot = process.cwd(); const sourceRoot = path.join(repoRoot, "src"); const testsRoot = path.join(repoRoot, "tests"); const generatedImportPattern = /(?:from\s+|import\s*\(\s*|require\s*\(\s*)["'][^"']*generated\/app-server\//; const sourceExtensions = new Set([".ts", ".tsx"]); describe("generated app-server import boundary", () => { it("detects static, import type, and re-export generated app-server references", () => { expect(generatedImportPattern.test('import type { Thread } from "../../src/generated/app-server/v2/Thread";')).toBe(true); expect(generatedImportPattern.test('type Thread = import("../../src/generated/app-server/v2/Thread").Thread;')).toBe(true); expect(generatedImportPattern.test('const thread = require("../../src/generated/app-server/v2/Thread");')).toBe(true); expect(generatedImportPattern.test('export type { Thread } from "../../src/generated/app-server/v2/Thread";')).toBe(true); expect(generatedImportPattern.test('export * from "../../src/generated/app-server/v2/Thread";')).toBe(true); }); it("keeps generated app-server types behind the app-server boundary", async () => { const files = await sourceFiles(sourceRoot); const offenders: string[] = []; for (const file of files) { const relativePath = slashPath(path.relative(repoRoot, file)); if (relativePath.startsWith("src/generated/")) continue; if (relativePath.startsWith("src/app-server/")) continue; if (generatedImportPattern.test(await readFile(file, "utf8"))) offenders.push(relativePath); } expect(offenders).toEqual([]); }); it("keeps generated app-server types out of feature tests", async () => { const files = await sourceFiles(testsRoot); const offenders: string[] = []; for (const file of files) { const relativePath = slashPath(path.relative(repoRoot, file)); if (relativePath.startsWith("tests/app-server/")) continue; if (generatedImportPattern.test(await readFile(file, "utf8"))) offenders.push(relativePath); } expect(offenders).toEqual([]); }); }); async function sourceFiles(directory: string): Promise { const entries = await readdir(directory, { withFileTypes: true }); const files = await Promise.all( entries.map(async (entry) => { const fullPath = path.join(directory, entry.name); if (entry.isDirectory()) return sourceFiles(fullPath); if (entry.isFile() && sourceExtensions.has(path.extname(entry.name))) return [fullPath]; return []; }), ); return files.flat(); } function slashPath(value: string): string { return value.split(path.sep).join("/"); }