ec7d56d566
admin-frontend-vue/src/config/workspace.ts 提供可编程边界校验(独立包名、 必需清单/源码目录、客户端耦合 token 扫描、可操作缺失消息),供后续壳层 任务与验收脚本复用;tests/task-1.test.ts 以 8 用例锁定依赖/构建/测试/边界。
114 lines
4.1 KiB
TypeScript
114 lines
4.1 KiB
TypeScript
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
|
||
}
|