/** * Import-boundary law (Bottom-Up §06 dependency law; Critics §12). * * Layers, low → high: contracts < testkit < service. A LOWER layer must never * import a HIGHER one. This guards the "no specialization leaks into the kernel" * rule from day one. Runnable as `pnpm boundary` (CI) and imported by a test. */ import { readFileSync, readdirSync, statSync, existsSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname, join, resolve } from 'node:path'; const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..'); /** Lower rank = lower layer. A package may only import packages of <= its rank. */ const RANK = { '@insignia/iios-contracts': 0, '@insignia/iios-testkit': 1, '@insignia/iios-service': 2, }; function listTsFiles(dir) { if (!existsSync(dir)) return []; const out = []; for (const entry of readdirSync(dir)) { if (entry === 'node_modules' || entry === 'dist') continue; const full = join(dir, entry); if (statSync(full).isDirectory()) out.push(...listTsFiles(full)); else if (full.endsWith('.ts')) out.push(full); } return out; } function packageOf(importPath) { return Object.keys(RANK).find((k) => importPath === k || importPath.startsWith(k + '/')); } export function findBoundaryViolations() { const pkgsDir = join(ROOT, 'packages'); const violations = []; if (!existsSync(pkgsDir)) return violations; for (const pkg of readdirSync(pkgsDir)) { const pkgJsonPath = join(pkgsDir, pkg, 'package.json'); if (!existsSync(pkgJsonPath)) continue; const name = JSON.parse(readFileSync(pkgJsonPath, 'utf8')).name; const ownRank = RANK[name]; if (ownRank === undefined) continue; // Catch every module-specifier form: `from '…'`, side-effect `import '…'`, // dynamic `import('…')`, and `require('…')`. const patterns = [ /from\s*['"](@insignia\/iios-[^'"]+)['"]/g, /import\s*['"](@insignia\/iios-[^'"]+)['"]/g, /import\s*\(\s*['"](@insignia\/iios-[^'"]+)['"]/g, /require\(\s*['"](@insignia\/iios-[^'"]+)['"]/g, ]; for (const file of listTsFiles(join(pkgsDir, pkg, 'src'))) { const content = readFileSync(file, 'utf8'); for (const re of patterns) { let m; while ((m = re.exec(content))) { const targetPkg = packageOf(m[1]); if (targetPkg && RANK[targetPkg] > ownRank) { violations.push({ file: file.replace(ROOT + '/', ''), from: name, imports: targetPkg }); } } } } } return violations; } if (import.meta.url === `file://${process.argv[1]}`) { const violations = findBoundaryViolations(); if (violations.length > 0) { console.error('✗ import-boundary violations (a lower layer imports a higher one):'); for (const v of violations) console.error(` ${v.file}: ${v.from} → ${v.imports}`); process.exit(1); } console.log('✓ import-boundary: OK (no lower→higher imports)'); }