115 lines
4.0 KiB
TypeScript
115 lines
4.0 KiB
TypeScript
import { readdirSync, readFileSync, statSync } from 'node:fs'
|
|
import { dirname, relative, resolve } from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
import { describe, expect, it } from 'vitest'
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
const ROOT_DIR = resolve(__dirname, '../..')
|
|
const SRC_DIR = resolve(ROOT_DIR, 'src')
|
|
const NETPROTO_DIR = resolve(SRC_DIR, 'netproto')
|
|
|
|
function collectTsFiles(dir: string): string[] {
|
|
const results: string[] = []
|
|
let entries: string[] = []
|
|
try {
|
|
entries = readdirSync(dir)
|
|
} catch {
|
|
return results
|
|
}
|
|
for (const name of entries) {
|
|
const full = resolve(dir, name)
|
|
const st = statSync(full)
|
|
if (st.isDirectory()) {
|
|
results.push(...collectTsFiles(full))
|
|
} else if (name.endsWith('.ts')) {
|
|
results.push(full)
|
|
}
|
|
}
|
|
return results
|
|
}
|
|
|
|
function extractImportSpecifiers(source: string): string[] {
|
|
const specs: string[] = []
|
|
// Match static import/export ... from '...' and dynamic import('...')
|
|
const fromRe = /(?:import|export)\s+(?:type\s+)?(?:[^'"]*?\s+from\s+)?['"]([^'"]+)['"]/g
|
|
const dynRe = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g
|
|
let m: RegExpExecArray | null
|
|
while ((m = fromRe.exec(source)) !== null) {
|
|
if (m[1]) specs.push(m[1])
|
|
}
|
|
while ((m = dynRe.exec(source)) !== null) {
|
|
if (m[1]) specs.push(m[1])
|
|
}
|
|
return specs
|
|
}
|
|
|
|
describe('src/netproto architectural boundary enforcement (F1)', () => {
|
|
it('configures tsconfig.netproto.json with ES2022 lib and empty types', () => {
|
|
const tsconfigPath = resolve(ROOT_DIR, 'tsconfig.netproto.json')
|
|
const cfg = JSON.parse(readFileSync(tsconfigPath, 'utf8'))
|
|
expect(cfg.compilerOptions.lib).toEqual(['ES2022'])
|
|
expect(cfg.compilerOptions.types).toEqual([])
|
|
expect(cfg.include).toContain('src/netproto/**/*.ts')
|
|
})
|
|
|
|
it('ensures src/netproto/** never imports files outside src/netproto/**', () => {
|
|
const netprotoFiles = collectTsFiles(NETPROTO_DIR)
|
|
expect(netprotoFiles.length).toBeGreaterThan(0)
|
|
|
|
const violations: string[] = []
|
|
for (const file of netprotoFiles) {
|
|
const relFile = relative(ROOT_DIR, file)
|
|
const isNodeTransport = relFile.startsWith('src/netproto/transport/node-')
|
|
const content = readFileSync(file, 'utf8')
|
|
const specs = extractImportSpecifiers(content)
|
|
|
|
for (const spec of specs) {
|
|
if (spec.startsWith('.')) {
|
|
const target = resolve(dirname(file), spec)
|
|
const relToNetproto = relative(NETPROTO_DIR, target)
|
|
if (relToNetproto.startsWith('..') || relToNetproto.startsWith('/')) {
|
|
violations.push(`${relFile} imports outside netproto: ${spec}`)
|
|
}
|
|
} else if (spec.startsWith('node:')) {
|
|
if (!isNodeTransport) {
|
|
violations.push(`${relFile} imports node builtin "${spec}" outside transport/node-*.ts`)
|
|
}
|
|
} else {
|
|
violations.push(`${relFile} imports external specifier "${spec}"`)
|
|
}
|
|
}
|
|
}
|
|
|
|
expect(violations).toEqual([])
|
|
})
|
|
|
|
it('ensures no file outside src/netproto/** imports protocol internals (bncs, mcp, d2gs, crypto)', () => {
|
|
const allSrcFiles = collectTsFiles(SRC_DIR).filter(f => !f.startsWith(NETPROTO_DIR + '/'))
|
|
const forbiddenDirs = [
|
|
resolve(NETPROTO_DIR, 'bncs'),
|
|
resolve(NETPROTO_DIR, 'mcp'),
|
|
resolve(NETPROTO_DIR, 'd2gs'),
|
|
resolve(NETPROTO_DIR, 'crypto'),
|
|
]
|
|
|
|
const violations: string[] = []
|
|
for (const file of allSrcFiles) {
|
|
const relFile = relative(ROOT_DIR, file)
|
|
const content = readFileSync(file, 'utf8')
|
|
const specs = extractImportSpecifiers(content)
|
|
|
|
for (const spec of specs) {
|
|
if (!spec.startsWith('.')) continue
|
|
const target = resolve(dirname(file), spec)
|
|
for (const forbidden of forbiddenDirs) {
|
|
if (target === forbidden || target.startsWith(forbidden + '/') || target.startsWith(forbidden + '.')) {
|
|
violations.push(`${relFile} imports forbidden netproto internal: ${spec}`)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
expect(violations).toEqual([])
|
|
})
|
|
})
|