task-1(壳层/路由): 冻结独立后台工程工作区边界契约与基线测试

admin-frontend-vue/src/config/workspace.ts 提供可编程边界校验(独立包名、
必需清单/源码目录、客户端耦合 token 扫描、可操作缺失消息),供后续壳层
任务与验收脚本复用;tests/task-1.test.ts 以 8 用例锁定依赖/构建/测试/边界。
This commit is contained in:
2026-09-05 12:11:48 +08:00
parent 445c139bce
commit ec7d56d566
2 changed files with 245 additions and 0 deletions
+113
View File
@@ -0,0 +1,113 @@
import { existsSync, readFileSync, readdirSync } from 'node:fs'
import { join, resolve } from 'node:path'
export const STANDALONE_PACKAGE_NAME = 'crawler-plugin-admin-frontend-vue'
/** 后台壳层允许存在的源码目录(任务 1 冻结的工作区边界,供验收脚本复用)。 */
export const ADMIN_SOURCE_DIRS = [
'src/layout',
'src/router',
'src/styles',
'src/types',
'src/config',
'src/pages',
] as const
export const REQUIRED_MANIFEST_FILES = [
'package.json',
'index.html',
'vite.config.ts',
'tsconfig.json',
'src/main.ts',
'src/App.vue',
] as const
// 客户端工程耦合标记:后台工程内出现即代表越界。用带路径/产物语义的标记,
// 避免与本工程自身包名后缀(…admin-frontend-vue)误匹配。扫描时排除本文件自身。
export const FORBIDDEN_COUPLING_TOKENS = [
'new_web_source',
'frontend-vue/',
'app_client',
] as const
const SELF_SCAN_EXCLUSIONS = new Set(['src/config/workspace.ts'])
const SCAN_EXTENSIONS = /\.(ts|tsx|vue|css|html|json|js)$/
export function walk(rootDir: string, base = ''): string[] {
const found: string[] = []
for (const entry of readdirSync(join(rootDir, base), { withFileTypes: true })) {
if (['node_modules', 'dist', '.git', 'tests'].includes(entry.name)) continue
const rel = base ? `${base}/${entry.name}` : entry.name
if (entry.isDirectory()) found.push(...walk(rootDir, rel))
else if (SCAN_EXTENSIONS.test(entry.name)) found.push(rel)
}
return found
}
function fileExists(rootDir: string, path: string): boolean {
return existsSync(join(rootDir, path))
}
/** 从当前目录向上定位独立后台工程根目录,找不到时给出可操作错误。 */
export function resolveWorkspaceRoot(cwd = process.cwd()): string {
let dir = resolve(cwd)
for (let i = 0; i < 6; i += 1) {
if (fileExists(dir, 'package.json')) return dir
const parent = resolve(dir, '..')
if (parent === dir) break
dir = parent
}
throw new Error(`${cwd} 向上 6 层内找不到 package.json,请在 admin-frontend-vue 工程目录内执行`)
}
export interface WorkspacePackage {
name: string
private: boolean
scripts: Record<string, string>
dependencies: Record<string, string>
devDependencies: Record<string, string>
}
export function readWorkspacePackage(rootDir = resolveWorkspaceRoot()): WorkspacePackage {
const raw = JSON.parse(readFileSync(join(rootDir, 'package.json'), 'utf8')) as Partial<WorkspacePackage> & { private?: boolean }
return {
name: raw.name || '',
private: raw.private !== false,
scripts: raw.scripts || {},
dependencies: raw.dependencies || {},
devDependencies: raw.devDependencies || {},
}
}
/** 校验独立工程边界,返回违规清单(空数组 = 边界成立)。 */
export function collectBoundaryViolations(rootDir = resolveWorkspaceRoot()): string[] {
const violations: string[] = []
const pkgPath = join(rootDir, 'package.json')
const pkg: WorkspacePackage = fileExists(rootDir, 'package.json')
? readWorkspacePackage(rootDir)
: { name: '', private: false, scripts: {}, dependencies: {}, devDependencies: {} }
if (!fileExists(rootDir, 'package.json')) violations.push(`缺少必需清单文件 package.json(在 ${pkgPath}`)
if (pkg.name !== STANDALONE_PACKAGE_NAME) {
violations.push(`package.name 应为 ${STANDALONE_PACKAGE_NAME},实际为 ${pkg.name || '(空)'}`)
}
if (!pkg.private) violations.push('package.private 应为 true(后台工程不允许被外部发布依赖)')
for (const file of REQUIRED_MANIFEST_FILES) {
if (!fileExists(rootDir, file)) violations.push(`缺少必需清单文件 ${file}`)
}
for (const dir of ADMIN_SOURCE_DIRS) {
if (!fileExists(rootDir, dir)) violations.push(`缺少允许源码目录 ${dir}`)
}
for (const file of walk(rootDir)) {
if (SELF_SCAN_EXCLUSIONS.has(file)) continue
const content = readFileSync(join(rootDir, file), 'utf8')
for (const token of FORBIDDEN_COUPLING_TOKENS) {
if (content.includes(token)) {
violations.push(`${file} 引用了客户端耦合 token "${token}",超出后台工程边界`)
}
}
}
return violations
}