Files
crawler-plugin/admin-frontend-vue/src/config/workspace.ts
T
huangzd1997 ec7d56d566 task-1(壳层/路由): 冻结独立后台工程工作区边界契约与基线测试
admin-frontend-vue/src/config/workspace.ts 提供可编程边界校验(独立包名、
必需清单/源码目录、客户端耦合 token 扫描、可操作缺失消息),供后续壳层
任务与验收脚本复用;tests/task-1.test.ts 以 8 用例锁定依赖/构建/测试/边界。
2026-09-05 14:43:51 +08:00

114 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}