Files
admin-web/src/core/dependencyBoundaries.test.ts
T
2026-08-03 18:49:18 +08:00

49 lines
1.8 KiB
TypeScript

import { readdirSync, readFileSync } from 'node:fs'
import { relative, resolve } from 'node:path'
import { describe, expect, it } from 'vitest'
const sourceRoot = resolve(process.cwd(), 'src')
function sourceFiles(directory: string): string[] {
return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
const path = resolve(directory, entry.name)
if (entry.isDirectory()) return sourceFiles(path)
return /\.(ts|vue)$/.test(entry.name) ? [path] : []
})
}
function aliasImports(path: string): string[] {
const source = readFileSync(path, 'utf8')
return [...source.matchAll(/['"](@\/[^'"]+)['"]/g)].map((match) => match[1])
}
describe('source dependency boundaries', () => {
it('keeps app, core, modules and shared dependencies pointing inward', () => {
const violations: string[] = []
for (const path of sourceFiles(sourceRoot)) {
const sourcePath = relative(sourceRoot, path).replaceAll('\\', '/')
const sourceModule = sourcePath.match(/^modules\/([^/]+)\//)?.[1]
for (const dependency of aliasImports(path)) {
if (sourcePath.startsWith('shared/') && /^@\/(app|core|modules|prototype)\//.test(dependency)) {
violations.push(`${sourcePath} -> ${dependency}`)
}
if (sourcePath.startsWith('core/') && /^@\/(app|modules|prototype)\//.test(dependency)) {
violations.push(`${sourcePath} -> ${dependency}`)
}
if (sourceModule && /^@\/(app|prototype)\//.test(dependency)) {
violations.push(`${sourcePath} -> ${dependency}`)
}
const targetModule = dependency.match(/^@\/modules\/([^/]+)(\/.*)?$/)
if (sourceModule && targetModule && targetModule[1] !== sourceModule && targetModule[2]) {
violations.push(`${sourcePath} -> ${dependency}`)
}
}
}
expect(violations).toEqual([])
})
})