#!/usr/bin/env node // Lint de frontières entre modules (ARCHITECTURE §4). Échoue si un import franchit une frontière interdite. // ui/ ne doit importer ni gen/ ni fx/ ni horscamp/ (il passe par core/state) // gen/ fx/ horscamp/ ne doivent pas importer ui/ // personne n'importe main.ts import { readdirSync, readFileSync, statSync } from 'node:fs'; import { join, relative, dirname, resolve } from 'node:path'; const racine = resolve(new URL('..', import.meta.url).pathname, 'src'); const interdits = { ui: ['gen', 'fx', 'horscamp'], gen: ['ui'], fx: ['ui'], horscamp: ['ui'], core: ['ui', 'gen', 'fx', 'horscamp'], content: ['ui', 'gen', 'fx', 'horscamp'], }; function fichiers(d) { return readdirSync(d).flatMap((n) => { const p = join(d, n); return statSync(p).isDirectory() ? fichiers(p) : p.endsWith('.ts') ? [p] : []; }); } let erreurs = 0; for (const f of fichiers(racine)) { const rel = relative(racine, f); const module = rel.includes('/') ? rel.split('/')[0] : '(racine)'; const src = readFileSync(f, 'utf8'); for (const m of src.matchAll(/from\s+['"](\.[^'"]+)['"]/g)) { const cible = relative(racine, resolve(dirname(f), m[1])); const moduleCible = cible.includes('/') ? cible.split('/')[0] : cible.replace(/\.ts$/, ''); if (moduleCible === 'main' && rel !== 'main.ts') { console.error(`✗ ${rel} importe main.ts`); erreurs++; } if ((interdits[module] ?? []).includes(moduleCible)) { console.error(`✗ ${rel} (${module}) importe ${cible} (${moduleCible}) : frontière interdite`); erreurs++; } } } if (erreurs) { console.error(`${erreurs} franchissement(s) de frontière.`); process.exit(1); } console.log('frontières respectées');