import { readdirSync, readFileSync } from 'node:fs'; import { resolve, relative } from 'node:path'; import { fileURLToPath } from 'node:url'; const root = fileURLToPath(new URL('../', import.meta.url)); function sources(dir, extension) { return readdirSync(resolve(root, dir), { withFileTypes: true }).flatMap((entry) => { const path = `${dir}/${entry.name}`; return entry.isDirectory() ? sources(path, extension) : path.endsWith(extension) ? [path] : []; }); } // Check the boundaries introduced by the refactor. Existing query projections and // composition roots are deliberately outside these use-case-specific rules. const rules = [ { files: sources('packages/core/src', '.ts'), forbidden: /(?:from\s*|import\s*)['"](?:[^'"]*apps\/|(?:@react-native[^'"]*|react(?:-native)?(?:[-\/]|['"]))|expo(?:[-\/]|['"])|\.\.[^'"]*(?:web|mobile|desktop)\/)/, message: 'core must not import platform/UI implementations', }, { files: ['service.go', 'worker.go', 'placement.go', 'batch.go'].map((p) => `services/api/internal/uploads/${p}`), forbidden: /github\.com\/jackc\/pgx|\.store\.pool\b|\b(?:tx|pool)\.(?:Exec|Query|QueryRow)\(/, message: 'upload SQL and pgx belong in store/persistence adapters', }, { files: sources('apps/web/src/features/uploads', '.tsx').filter((p) => !p.endsWith('/common.tsx')), forbidden: /\b(?:session|client)\.(?:GET|POST|PUT|PATCH|DELETE)\(/, message: 'upload views must use feature API/queries', }, ]; const errors = []; for (const rule of rules) { for (const path of rule.files) { const text = readFileSync(resolve(root, path), 'utf8'); text.split(/\r?\n/).forEach((line, i) => { if (rule.forbidden.test(line)) errors.push(`${relative(root, resolve(root, path))}:${i + 1}: ${rule.message}`); }); } } if (errors.length) { console.error(errors.join('\n')); process.exitCode = 1; } else { console.log('Module boundary checks passed.'); }