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.
)}