#!/usr/bin/env node /** * Domain Import Boundary Checker * * Validates: * 1. No @customer-portal/domain (root) imports * 2. No deep imports beyond module/providers * 3. Portal has zero provider imports */ import fs from "node:fs/promises"; import path from "node:path"; const ROOT = process.cwd(); const APPS_DIR = path.join(ROOT, "apps"); const BFF_SRC_DIR = path.join(APPS_DIR, "bff", "src"); const PORTAL_SRC_DIR = path.join(APPS_DIR, "portal", "src"); const FILE_EXTS = new Set([".ts", ".tsx", ".js", ".jsx"]); const IGNORE_DIRS = new Set(["node_modules", "dist", ".next", ".turbo", ".cache"]); function toPos(text, idx) { // 1-based line/column let line = 1; let col = 1; for (let i = 0; i < idx; i += 1) { if (text.charCodeAt(i) === 10) { line += 1; col = 1; } else { col += 1; } } return { line, col }; } async function* walk(dir) { const entries = await fs.readdir(dir, { withFileTypes: true }); for (const e of entries) { const p = path.join(dir, e.name); if (e.isDirectory()) { if (IGNORE_DIRS.has(e.name) || e.name.startsWith(".")) continue; yield* walk(p); continue; } if (!e.isFile()) continue; if (!FILE_EXTS.has(path.extname(e.name))) continue; yield p; } } function collectDomainImports(code) { const results = []; const patterns = [ { kind: "from", re: /\bfrom\s+['"]([^'"]+)['"]/g }, { kind: "import", re: /\bimport\s+['"]([^'"]+)['"]/g }, { kind: "dynamicImport", re: /\bimport\(\s*['"]([^'"]+)['"]\s*\)/g }, { kind: "require", re: /\brequire\(\s*['"]([^'"]+)['"]\s*\)/g }, ]; for (const { kind, re } of patterns) { for (const m of code.matchAll(re)) { const spec = m[1]; if (!spec || !spec.startsWith("@customer-portal/domain")) continue; const idx = typeof m.index === "number" ? m.index : 0; results.push({ kind, spec, idx }); } } return results; } function validateSpecifier({ spec, isPortal }) { if (spec === "@customer-portal/domain") { return "Do not import @customer-portal/domain (root). Use @customer-portal/domain/ instead."; } if (spec.includes("/src/")) { return "Import from @customer-portal/domain/ instead of internals."; } if (spec.startsWith("@customer-portal/domain/toolkit/")) { return "Do not deep-import toolkit internals. Import from @customer-portal/domain/toolkit only."; } if (/^@customer-portal\/domain\/[^/]+\/providers\/.+/.test(spec)) { return "Do not deep-import provider internals. Import from @customer-portal/domain//providers only."; } if (/^@customer-portal\/domain\/[^/]+\/providers$/.test(spec)) { if (isPortal) { return "Portal must not import provider adapters/types. Import normalized domain models from @customer-portal/domain/ instead."; } return null; } // Any 2+ segment import like @customer-portal/domain/a/b is illegal everywhere // (except the explicit ...//providers entrypoint handled above). if (/^@customer-portal\/domain\/[^/]+\/[^/]+/.test(spec)) { return "No deep @customer-portal/domain imports. Use @customer-portal/domain/ (or BFF-only: ...//providers)."; } return null; } async function main() { const errors = []; // Broad scan: both apps for root/deep imports for (const baseDir of [BFF_SRC_DIR, PORTAL_SRC_DIR]) { for await (const file of walk(baseDir)) { const code = await fs.readFile(file, "utf8"); const isPortal = file.startsWith(PORTAL_SRC_DIR + path.sep); for (const imp of collectDomainImports(code)) { const message = validateSpecifier({ spec: imp.spec, isPortal }); if (!message) continue; const pos = toPos(code, imp.idx); errors.push({ file: path.relative(ROOT, file), line: pos.line, col: pos.col, spec: imp.spec, message, }); } } } if (errors.length > 0) { console.error(`[domain] ERROR: illegal domain imports detected (${errors.length})`); for (const e of errors) { console.error(`[domain] ${e.file}:${e.line}:${e.col} ${e.spec}`); console.error(` ${e.message}`); } process.exit(1); } console.log("[domain] OK: import contract checks passed."); } await main();