import fs from 'node:fs' import path from 'node:path' const workspaceRoot = process.cwd() const referenceRoot = path.resolve(workspaceRoot, '..', 'Jiapu-App') const referencePagesPath = path.join(referenceRoot, 'pages.json') const parityDocumentPath = path.join( workspaceRoot, 'docs', 'frontend-reference-parity-2026-08-17.md' ) const stripJsonLineComments = (source) => source .split(/\r?\n/u) .map((line) => { let inString = false let escaped = false for (let index = 0; index < line.length - 1; index += 1) { const character = line[index] if (escaped) { escaped = false continue } if (character === '\\' && inString) { escaped = true continue } if (character === '"') { inString = !inString continue } if (!inString && character === '/' && line[index + 1] === '/') { return line.slice(0, index) } } return line }) .join('\n') const fail = (message) => { console.error(`FRONTEND PARITY CHECK FAIL: ${message}`) process.exit(1) } if (!fs.existsSync(referencePagesPath)) { fail(`参考项目不存在:${referencePagesPath}`) } if (!fs.existsSync(parityDocumentPath)) { fail(`对比总表不存在:${parityDocumentPath}`) } const referenceConfig = JSON.parse( stripJsonLineComments(fs.readFileSync(referencePagesPath, 'utf8')) ) const referenceRoutes = referenceConfig.pages.map(({ path: routePath }) => routePath) if (referenceRoutes.length !== 78) { fail(`参考项目活动路由应为 78 条,实际为 ${referenceRoutes.length} 条`) } if (new Set(referenceRoutes).size !== referenceRoutes.length) { fail('参考项目 pages.json 存在重复活动路由') } for (const routePath of referenceRoutes) { const vuePath = path.join(referenceRoot, `${routePath}.vue`) const nvuePath = path.join(referenceRoot, `${routePath}.nvue`) if (!fs.existsSync(vuePath) && !fs.existsSync(nvuePath)) { fail(`参考路由没有 .vue 或 .nvue 页面文件:${routePath}`) } } const parityDocument = fs.readFileSync(parityDocumentPath, 'utf8') const documentedRouteRows = parityDocument .split(/\r?\n/u) .filter((line) => /^\| `[^`]+` \|/u.test(line)) .map((line) => line.split('|').slice(1, -1).map((column) => column.trim())) const documentedRoutes = documentedRouteRows.map(([routeCell]) => routeCell.slice(1, -1)) const allowedStatuses = new Set([ '覆盖', '合并覆盖', '升级替代', '非产品页', '本轮补齐', '部分覆盖', '部分合并', '部分升级', '前端待后端' ]) for (const columns of documentedRouteRows) { if (columns.length !== 5 || columns.some((column) => !column)) { fail(`对比总表路由行字段不完整:${columns[0] || '未知路由'}`) } if (!allowedStatuses.has(columns[3])) { fail(`对比总表路由状态无效:${columns[0]} -> ${columns[3]}`) } } const documentedRouteCounts = new Map() for (const routePath of documentedRoutes) { documentedRouteCounts.set( routePath, (documentedRouteCounts.get(routePath) || 0) + 1 ) } const missingRoutes = referenceRoutes.filter( (routePath) => !documentedRouteCounts.has(routePath) ) const duplicateRoutes = [...documentedRouteCounts.entries()] .filter(([, count]) => count !== 1) .map(([routePath]) => routePath) const unknownRoutes = documentedRoutes.filter( (routePath) => !referenceRoutes.includes(routePath) ) if (missingRoutes.length) fail(`对比总表遗漏路由:${missingRoutes.join(', ')}`) if (duplicateRoutes.length) fail(`对比总表重复路由:${duplicateRoutes.join(', ')}`) if (unknownRoutes.length) fail(`对比总表包含未知路由:${unknownRoutes.join(', ')}`) console.log( `FRONTEND PARITY CHECK PASS referenceRoutes=${referenceRoutes.length} documentedRoutes=${documentedRoutes.length}` )