#!/usr/bin/env node // Enforces D15: core never reaches into packages/enterprise internals, // and packages/enterprise never imports from apps/* (it must stay standalone). import { readdirSync, readFileSync, statSync } from "node:fs"; import { join, relative } from "node:path"; const ROOT = process.cwd(); const SKIP_DIRS = new Set(["node_modules", "dist", ".git", ".turbo", "coverage"]); function* walk(dir) { for (const entry of readdirSync(dir)) { if (SKIP_DIRS.has(entry)) continue; const full = join(dir, entry); if (statSync(full).isDirectory()) { yield* walk(full); } else if (/\.(ts|tsx|js|mjs)$/.test(entry)) { yield full; } } } const ENTERPRISE_DIR = join(ROOT, "packages/enterprise"); // Catches static `from "..."`, side-effect `import "..."`, and dynamic `import("...")` // forms. The bare package entry "@snapotter/enterprise" (no trailing slash) stays // allowed; only reaching INTO the package is a violation. const INTERNAL_IMPORT = /(from|import)\s*[\s(]\s*["'](@snapotter\/enterprise\/|[./]*packages\/enterprise\/)/; const APP_IMPORT = /(from|import)\s*[\s(]\s*["'][^"']*apps\/(api|web)\//; const violations = []; for (const root of [join(ROOT, "apps"), join(ROOT, "packages")]) { for (const file of walk(root)) { const insideEnterprise = file.startsWith(ENTERPRISE_DIR); const text = readFileSync(file, "utf8"); for (const [lineNo, line] of text.split("\n").entries()) { if (!insideEnterprise && INTERNAL_IMPORT.test(line)) { violations.push(`core imports enterprise internals: ${relative(ROOT, file)}:${lineNo + 1}`); } if (insideEnterprise && APP_IMPORT.test(line)) { violations.push(`enterprise imports app code: ${relative(ROOT, file)}:${lineNo + 1}`); } } } } if (violations.length > 0) { console.error("License boundary violations (D15):"); for (const v of violations) console.error(` ${v}`); process.exit(1); } console.log("License boundary check passed.");