/** * 发布前检查清单(Task 100)。 * * 收敛发布前的三个验收步骤:回滚演练、git commit 对应关系检查、 * 交付清单核对。每步由 deps 提供真实实现(SSH 回滚、git log、 * 产物校验),清单产出结构化结果。 * * 语义: * - 回滚演练失败或 commit 缺失时整单 ok=false,但不中断后续步骤, * 其余检查照常产出完整报告; * - releaseCommits 为空、deliverables 为空:安全跳过对应检查,视为通过; * - maxCommits 限制 commit 检查数量(超出部分计入 unverified),检查有界; * - deps 抛错(SSH/git 故障):整轮 run 抛错,零部分结果,依赖恢复后 * 同一清单重跑即全绿; * - 校验失败 fail-fast:deps 非对象、缺函数、数组字段非法、maxCommits * 非正数、commit 记录缺 hash 均抛错。 */ export interface ReleaseCommit { hash: string subject?: string } export interface RollbackResult { ok: boolean output: string } export interface ReleaseCheckDeps { /** 列出当前仓库 commit(含 hash 与 subject) */ listCommits: () => Promise /** 执行回滚演练 */ rollback: () => Promise /** 验证发布后页面/核心请求 */ verify: () => Promise } export interface ReleaseChecklistOptions { deps: ReleaseCheckDeps /** 本次发布对应的 commit hash 列表 */ releaseCommits: string[] /** 交付物清单(jar、exe、vue-dist 等) */ deliverables?: string[] /** commit 检查数量上限,必须为正数,默认 100 */ maxCommits?: number } export interface ReleaseCheckResult { ok: boolean steps: Array<{ id: string; ok: boolean }> rollback: RollbackResult verify: RollbackResult commitMap: { checked: number missing: string[] unverified: string[] } deliverables: { passed: boolean total: number items: string[] } } export interface ReleaseChecklist { run: () => Promise } export function createReleaseChecklist(options: ReleaseChecklistOptions): ReleaseChecklist { if (typeof options !== 'object' || options == null || !options.deps) { throw new Error('deps 必须是对象') } if (typeof options.deps.listCommits !== 'function') { throw new Error('listCommits 必须是函数') } if (typeof options.deps.rollback !== 'function') { throw new Error('rollback 必须是函数') } if (typeof options.deps.verify !== 'function') { throw new Error('verify 必须是函数') } if (!Array.isArray(options.releaseCommits)) { throw new Error('releaseCommits 必须是数组') } const deliverables = options.deliverables ?? [] if (!Array.isArray(deliverables)) { throw new Error('deliverables 必须是数组') } const maxCommits = options.maxCommits ?? 100 if (!(maxCommits > 0)) { throw new Error('maxCommits 必须为正数: ' + maxCommits) } async function run(): Promise { const steps: Array<{ id: string; ok: boolean }> = [] let ok = true const rollback = await options.deps.rollback() steps.push({ id: 'rollback-drill', ok: rollback.ok }) if (!rollback.ok) ok = false const commits = await options.deps.listCommits() const available = new Set() for (const commit of commits) { if (typeof commit.hash !== 'string' || commit.hash.length === 0) { throw new Error('commit 缺少 hash') } available.add(commit.hash) } const missing: string[] = [] const unverified: string[] = [] for (let i = 0; i < options.releaseCommits.length; i++) { const hash = options.releaseCommits[i] if (i >= maxCommits) { unverified.push(hash) continue } if (!available.has(hash)) { missing.push(hash) ok = false } } steps.push({ id: 'commit-map', ok: missing.length === 0 && unverified.length === 0 }) const verify = await options.deps.verify() steps.push({ id: 'deliverables', ok: verify.ok }) if (!verify.ok) ok = false return { ok, steps, rollback, verify, commitMap: { checked: options.releaseCommits.length, missing, unverified }, deliverables: { passed: verify.ok, total: deliverables.length, items: [...deliverables] }, } } return { run } }