83 lines
2.6 KiB
JavaScript
83 lines
2.6 KiB
JavaScript
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)
|
|
})
|
|
})
|