From 249e361b590f549a7157745356d6fe15f0dd0ca9 Mon Sep 17 00:00:00 2001 From: hanruo <552455797@qq.com> Date: Fri, 22 May 2026 14:11:15 +0800 Subject: [PATCH] fix --- src/lib/runner.js | 32 ++++++++++++++++++++++++++++---- src/lib/tasks.js | 2 +- src/screens/RunLog.jsx | 41 ++++++++++++++++++++++++++++++++++++++--- 3 files changed, 67 insertions(+), 8 deletions(-) diff --git a/src/lib/runner.js b/src/lib/runner.js index aab523a..b2fc2b9 100644 --- a/src/lib/runner.js +++ b/src/lib/runner.js @@ -7,9 +7,10 @@ const { spawn } = require("node:child_process"); function startRun(steps) { const emitter = new EventEmitter(); - const state = { cancelled: false, child: null }; + const state = { cancelled: false, child: null, pendingDecision: null }; setImmediate(async () => { + let hadFailure = false; for (let index = 0; index < steps.length; index += 1) { if (state.cancelled) { emitter.emit("event", { type: "done", success: false, cancelled: true }); @@ -35,12 +36,27 @@ function startRun(steps) { status: "failed", error: error.message || String(error), }); - emitter.emit("event", { type: "done", success: false }); - return; + + if (index === steps.length - 1) { + emitter.emit("event", { type: "done", success: false, hadFailure: true }); + return; + } + + const decision = await new Promise((resolve) => { + state.pendingDecision = resolve; + emitter.emit("event", { type: "awaiting-decision", index }); + }); + state.pendingDecision = null; + + if (decision !== "skip") { + emitter.emit("event", { type: "done", success: false, hadFailure: true }); + return; + } + hadFailure = true; } } - emitter.emit("event", { type: "done", success: true }); + emitter.emit("event", { type: "done", success: !hadFailure, hadFailure }); }); return { @@ -48,8 +64,16 @@ function startRun(steps) { emitter.on(event, handler); return this; }, + decide(value) { + if (state.pendingDecision) { + state.pendingDecision(value); + } + }, cancel() { state.cancelled = true; + if (state.pendingDecision) { + state.pendingDecision("abort"); + } if (state.child) { try { state.child.kill("SIGTERM"); diff --git a/src/lib/tasks.js b/src/lib/tasks.js index c725efd..f62c1c9 100644 --- a/src/lib/tasks.js +++ b/src/lib/tasks.js @@ -21,7 +21,7 @@ const { } = require("./frp-config"); const NVM_INSTALL_VERSION = "v0.40.4"; -const NVM_INSTALL_COMMAND = `set -euo pipefail +const NVM_INSTALL_COMMAND = `set -eo pipefail export NVM_DIR="$HOME/.nvm" if [ ! -s "$NVM_DIR/nvm.sh" ]; then if command -v curl >/dev/null 2>&1; then diff --git a/src/screens/RunLog.jsx b/src/screens/RunLog.jsx index 702ac48..b9eaf10 100644 --- a/src/screens/RunLog.jsx +++ b/src/screens/RunLog.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useReducer } from "react"; +import React, { useEffect, useReducer, useRef } from "react"; import { Box, Text } from "ink"; import Spinner from "ink-spinner"; import SelectInput from "ink-select-input"; @@ -31,8 +31,18 @@ function reducer(state, action) { } return { ...state, log: next }; } + case "awaiting-decision": + return { ...state, awaitingDecision: action.index }; + case "decision-made": + return { ...state, awaitingDecision: null }; case "done": - return { ...state, finished: true, success: action.success, cancelled: !!action.cancelled }; + return { + ...state, + finished: true, + success: action.success, + cancelled: !!action.cancelled, + awaitingDecision: null, + }; default: return state; } @@ -47,16 +57,26 @@ export default function RunLog({ nav, plan, origin }) { finished: false, success: false, cancelled: false, + awaitingDecision: null, }); + const runRef = useRef(null); useEffect(() => { const run = startRun(plan.steps); + runRef.current = run; run.on("event", (event) => { dispatch(event); }); return () => run.cancel(); }, [plan]); + const handleDecision = (item) => { + if (runRef.current) { + runRef.current.decide(item.value); + } + dispatch({ type: "decision-made" }); + }; + const recentLog = state.log.slice(-MAX_LOG_LINES); return ( @@ -82,7 +102,20 @@ export default function RunLog({ nav, plan, origin }) { ))} ) : null} - {state.finished ? : ( + {state.finished ? ( + + ) : state.awaitingDecision != null ? ( + + Step {state.awaitingDecision + 1} failed. Continue? + + + ) : ( @@ -168,6 +201,8 @@ function FinishedFooter({ state, nav, origin }) { Cancelled. ) : state.success ? ( All steps completed successfully. + ) : Object.values(state.statuses).includes("failed") && !state.cancelled ? ( + Run finished with skipped failures. ) : ( Run failed. )}