Files
crawler-plugin/frontend-vue/src/shared/release-checklist.ts
T

140 lines
4.3 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.
/**
* 发布前检查清单(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<ReleaseCommit[]>
/** 执行回滚演练 */
rollback: () => Promise<RollbackResult>
/** 验证发布后页面/核心请求 */
verify: () => Promise<RollbackResult>
}
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<ReleaseCheckResult>
}
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<ReleaseCheckResult> {
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<string>()
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 }
}