import fs from 'node:fs'; import path from 'node:path'; const ROOT = process.cwd(); const APP_ROOT = path.join(ROOT, 'src', 'app'); function listTsFiles(dir) { const files = []; const entries = fs.readdirSync(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { files.push(...listTsFiles(fullPath)); continue; } if (entry.isFile() && fullPath.endsWith('.ts') && !fullPath.endsWith('.spec.ts')) { files.push(fullPath); } } return files; } function normalize(filePath) { return filePath.split(path.sep).join('/'); } function checkFile(filePath, content) { const violations = []; const normalized = normalize(filePath); const importRegex = /^\s*import\s+[^'"\n]+\s+from\s+['"]([^'"]+)['"];?/gm; const imports = []; let match; while ((match = importRegex.exec(content)) !== null) { imports.push(match[1]); } const isWidgetUi = normalized.includes('/src/app/widgets/ui/'); const isFacade = normalized.includes('/src/app/facades/'); const isRoutesFile = normalized.endsWith('/src/app/app.routes.ts'); for (const specifier of imports) { const isEnvironmentImport = specifier.includes('environments/environment'); if (isWidgetUi && isEnvironmentImport) { violations.push(`${normalized}: widget UI must not import environment (${specifier})`); } if (isWidgetUi && ( specifier.includes('/services/') || specifier.includes('/facades/') || specifier.includes('/core/') || specifier.includes('/interceptors/') || specifier === '@angular/router' || specifier === '@angular/common/http' )) { violations.push(`${normalized}: widget UI import boundary violation (${specifier})`); } if (isFacade && specifier === '@angular/common/http') { violations.push(`${normalized}: facade must not import HttpClient directly`); } if (isRoutesFile && specifier.includes('/brands/brand-routes')) { violations.push(`${normalized}: routes must not import tenant brand-routes`); } } return violations; } const files = listTsFiles(APP_ROOT); let violations = []; for (const file of files) { const content = fs.readFileSync(file, 'utf8'); violations = violations.concat(checkFile(file, content)); } if (violations.length > 0) { console.error('Architecture boundary violations found:'); for (const violation of violations) { console.error(`- ${violation}`); } process.exit(1); } console.log('Architecture boundary checks passed.');