task-97: Java/Python/Vue 三端验证工作流编排器

This commit is contained in:
2026-08-30 23:13:55 +08:00
parent a707e754f8
commit c08869f109
2 changed files with 255 additions and 0 deletions
@@ -0,0 +1,103 @@
/**
* 三端验证工作流编排器(Task 97)。
*
* 把 Java 全量测试、Python unittest、Vue 类型检查与构建四个验证步骤
* 收敛为可注入执行器的确定性流程:步骤按序执行,产出逐项结果。
*
* 语义:
* - 每个步骤的 run(stepId) 返回 { ok, output }ok=false 时计入 failed
* - stopOnFailure(默认 true)下失败即停止,剩余步骤计入 skipped;
* - maxSteps 限制可执行步骤数量(超出部分计入 skipped),执行有界;
* - 步骤执行器抛错:整次 runAll 抛错,不产生部分结果记录(调用方可
* 捕获后恢复重跑同一 runner);
* - 校验失败 fail-faststeps 非数组、step id 为空、run 非函数、maxSteps
* 非正数、run 返回结果缺 output 均抛错。
*/
export interface VerificationStep {
id: string
label: string
}
export interface StepRunResult {
ok: boolean
output: string
}
export interface VerificationRunnerDeps {
/** 执行单个步骤,返回结果;抛错时整次 runAll 抛错 */
run: (stepId: string) => Promise<StepRunResult>
}
export interface VerificationRunnerOptions {
steps: VerificationStep[]
run: (stepId: string) => Promise<StepRunResult>
/** 失败即停止(默认 true);false 时全部执行、失败累计 */
stopOnFailure?: boolean
/** 可执行步骤数上限,必须为正数,默认 100 */
maxSteps?: number
}
export interface VerificationResult {
ok: boolean
passed: Array<VerificationStep & { output: string }>
failed: Array<VerificationStep & { output: string }>
skipped: string[]
}
export interface VerificationRunner {
runAll: () => Promise<VerificationResult>
}
export function createVerificationRunner(options: VerificationRunnerOptions): VerificationRunner {
if (!Array.isArray(options.steps)) {
throw new Error('steps 必须是数组')
}
for (const step of options.steps) {
if (typeof step.id !== 'string' || step.id.length === 0) {
throw new Error('step id 不能为空')
}
}
if (typeof options.run !== 'function') {
throw new Error('run 必须是函数')
}
const stopOnFailure = options.stopOnFailure ?? true
const maxSteps = options.maxSteps ?? 100
if (!(maxSteps > 0)) {
throw new Error('maxSteps 必须为正数: ' + maxSteps)
}
async function runAll(): Promise<VerificationResult> {
const passed: Array<VerificationStep & { output: string }> = []
const failed: Array<VerificationStep & { output: string }> = []
const skipped: string[] = []
let ok = true
for (let i = 0; i < options.steps.length; i++) {
const step = options.steps[i]
if (i >= maxSteps) {
skipped.push(step.id)
continue
}
const result = await options.run(step.id)
if (typeof result.output !== 'string') {
throw new Error('output 必须是字符串')
}
if (result.ok) {
passed.push({ ...step, output: result.output })
} else {
ok = false
failed.push({ ...step, output: result.output })
if (stopOnFailure) {
for (const rest of options.steps.slice(i + 1)) {
skipped.push(rest.id)
}
break
}
}
}
return { ok, passed, failed, skipped }
}
return { runAll }
}