import assert from 'node:assert/strict' import { readdirSync, readFileSync } from 'node:fs' import { join, relative } from 'node:path' import test from 'node:test' const MAX_SOURCE_UNIT_LINES = 800 const REPO_ROOT = new URL('../..', import.meta.url).pathname const FRONTEND_SOURCE_ROOTS = [ 'web/src/components', 'web/src/composables', 'web/src/utils', 'web/src/views', ] const FRONTEND_EXTENSIONS = new Set(['.html', '.js', '.mjs', '.ts', '.tsx', '.vue']) function extensionOf(path) { const index = path.lastIndexOf('.') return index === -1 ? '' : path.slice(index) } function countLines(source) { return source.length === 0 ? 0 : source.split('\n').length } function walkFiles(root, shouldInclude) { const result = [] const stack = [root] while (stack.length > 0) { const current = stack.pop() const entries = readdirSync(current, { withFileTypes: true }) for (const entry of entries) { const path = join(current, entry.name) if (entry.isDirectory()) { if (!['node_modules', 'dist', 'build', 'coverage'].includes(entry.name)) { stack.push(path) } continue } if (shouldInclude(path)) { result.push(path) } } } return result } function listOversizedFrontendUnits() { return FRONTEND_SOURCE_ROOTS.flatMap((root) => { const absoluteRoot = join(REPO_ROOT, root) return walkFiles( absoluteRoot, (path) => FRONTEND_EXTENSIONS.has(extensionOf(path)) ) }) .map((path) => ({ path: relative(REPO_ROOT, path), lines: countLines(readFileSync(path, 'utf8')), })) .filter((item) => item.lines > MAX_SOURCE_UNIT_LINES) .sort((left, right) => right.lines - left.lines) } test('前端核心组件和模块不超过 800 行', () => { const oversized = listOversizedFrontendUnits() assert.deepEqual( oversized, [], `以下前端核心源文件超过 ${MAX_SOURCE_UNIT_LINES} 行:\n${ oversized.map((item) => `- ${item.path}: ${item.lines}`).join('\n') }` ) })