import { readFileSync, readdirSync } from 'node:fs' import { resolve, dirname, relative } from 'node:path' import { fileURLToPath } from 'node:url' import { describe, it, expect } from 'vitest' const __dirname = dirname(fileURLToPath(import.meta.url)) const projectRoot = resolve(__dirname, '../..') const serverDir = resolve(projectRoot, 'server') /** Collects all .js file paths under dir (recursive). */ function listJsFiles(dir, base = dir) { const entries = readdirSync(dir, { withFileTypes: true }) const files = [] for (const e of entries) { const full = resolve(dir, e.name) if (e.isDirectory()) { files.push(...listJsFiles(full, base)) } else if (e.isFile() && e.name.endsWith('.js')) { files.push(relative(base, full)) } } return files } /** Extracts relative import/require paths from file content (no node_modules). */ function getRelativeImports(content) { const paths = [] const fromRegex = /from\s+['"]([^'"]+)['"]/g const requireRegex = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g const extractMatches = (regex, text) => { const matches = [] const execRegex = (r) => { const match = r.exec(text) if (match) { matches.push(match[1]) return execRegex(r) } return matches } return execRegex(regex) } for (const re of [fromRegex, requireRegex]) { const matches = extractMatches(re, content) matches.forEach((p) => { if (p.startsWith('.')) paths.push(p) }) } return paths } describe('server imports', () => { it('only import from within server/ (no shared/ or other outside paths)', () => { const violations = [] const files = listJsFiles(serverDir).map(f => resolve(serverDir, f)) for (const filePath of files) { const content = readFileSync(filePath, 'utf-8') const fileDir = dirname(filePath) for (const importPath of getRelativeImports(content)) { const resolved = resolve(fileDir, importPath) const relToServer = relative(serverDir, resolved) if (relToServer.startsWith('..') || relToServer.startsWith('/')) { violations.push({ file: relative(projectRoot, filePath), import: importPath, resolved: relative(projectRoot, resolved), }) } } } expect( violations, violations.length ? `Server files must not import from outside server/. Violations:\n${violations .map(v => ` ${v.file} → ${v.import} (resolves to ${v.resolved})`) .join('\n')}` : undefined, ).toHaveLength(0) }) })