140 lines
4.3 KiB
TypeScript
140 lines
4.3 KiB
TypeScript
/**
|
||
* 发布前检查清单(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 }
|
||
}
|