#!/usr/bin/env node import { readFile } from 'node:fs/promises' import { resolve } from 'node:path' import { pathToFileURL } from 'node:url' import { describeAcceptanceScope } from './admin-test-env-acceptance.mjs' /** 只输出白名单统计和模块名,不转抄报告中的账号、地址、凭据或业务载荷。 */ export function summarizeReport(report) { if (!report || !Array.isArray(report.steps) || report.steps.length === 0 || report.steps.some((step) => typeof step.passed !== 'boolean')) { throw new Error('报告缺少有效 steps,不能认定验收结果') } const steps = report.steps const failed = steps.filter((step) => !step.passed && !step.skipped) const skipped = steps.filter((step) => step.skipped) const skippedModules = Array.isArray(report.modules) ? report.modules.filter((module) => module.skipped).length : 0 const gaps = Array.isArray(report.coverageGaps) ? report.coverageGaps : [] const blockers = gaps.filter((gap) => gap.blocking || gap.deploymentBlocker) const scope = report.executionScope // 历史报告不包含计划信息,不能从断言数量或模块名字推定完整执行。 const scopeKnown = Array.isArray(scope?.plannedModules) && scope.plannedModules.length > 0 && Array.isArray(scope.defaultModules) && scope.defaultModules.length > 0 const observedModules = new Set(steps.map((step) => step.module)) const missingModules = scopeKnown ? scope.plannedModules.filter((name) => !observedModules.has(name)) : [] const limited = Boolean(scope?.preparationOnly || scope?.returnMainChainOnly || report.fullAcceptance === false) const failedResult = Boolean(report.fatal) || report.success === false || failed.length > 0 || blockers.length > 0 return { result: failedResult ? 'FAILED' : skipped.length || skippedModules || gaps.length || missingModules.length || limited ? 'INCOMPLETE' : 'RECORDED_CHECKS_PASSED', passed: steps.filter((step) => step.passed && !step.skipped).length, failed: failed.length, skipped: skipped.length, skippedModules, blockingGaps: blockers.length, nonBlockingGaps: gaps.length - blockers.length, failedModules: [...new Set(failed.map((step) => step.module).filter(Boolean))], // 无请求的失败包含前置不足和模块退出,不能计作独立接口缺陷。 failuresWithRequest: failed.filter((step) => step.request).length, failuresWithoutRequest: failed.filter((step) => !step.request).length, scope: !scopeKnown ? 'UNKNOWN_LEGACY_REPORT' : limited ? 'LIMITED_SCOPE' : scope.defaultModules.every((name) => scope.plannedModules.includes(name)) ? 'DEFAULT_ADMIN_PLAN' : 'SELECTED_MODULES', missingPlannedModules: missingModules, currentVersionVerified: false, pageAcceptance: 'NOT_VERIFIED_BY_HTTP', } } /** 默认仅展示范围;显式报告只读分析,不发现历史目录、不联网、不执行验收。 */ async function main(argv) { if (argv.length && !(argv.length === 2 && argv[0] === '--report')) { throw new Error('用法:node scripts/acceptance-overview.mjs [--report <明确JSON路径>]') } const scope = describeAcceptanceScope() const result = { purpose: '离线验收范围总览,不执行HTTP,不证明当前部署通过', admin: { entry: 'scripts/admin-test-env-acceptance.mjs', ...scope }, otherEntries: [ { entry: 'scripts/client-test-env-acceptance.mjs', scope: 'Client全量HTTP,独立执行' }, { entry: 'scripts/client-phase2-test-env-acceptance.mjs', scope: '测量计费专项,不替代Client全量' }, { entry: 'scripts/fleet-test-env-acceptance.mjs', scope: '车辆专项,不计入Admin默认全量' }, ], boundaries: ['可选模块不等于默认执行;其他专项入口详见scripts/README.md', '本地合同、HTTP、页面与真机验收分别报告', '报告通过只代表该次记录;需另核对源码、配置与实际部署版本', '环境与凭据前置仍由实际入口校验;本工具不读取环境文件'], } if (argv.length) result.report = summarizeReport(JSON.parse(await readFile(resolve(argv[1]), 'utf8'))) console.log(JSON.stringify(result, null, 2)) } if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) { main(process.argv.slice(2)).catch((error) => { console.error(error.message); process.exitCode = 1 }) }