fix
This commit is contained in:
@@ -7,9 +7,10 @@ const { spawn } = require("node:child_process");
|
|||||||
|
|
||||||
function startRun(steps) {
|
function startRun(steps) {
|
||||||
const emitter = new EventEmitter();
|
const emitter = new EventEmitter();
|
||||||
const state = { cancelled: false, child: null };
|
const state = { cancelled: false, child: null, pendingDecision: null };
|
||||||
|
|
||||||
setImmediate(async () => {
|
setImmediate(async () => {
|
||||||
|
let hadFailure = false;
|
||||||
for (let index = 0; index < steps.length; index += 1) {
|
for (let index = 0; index < steps.length; index += 1) {
|
||||||
if (state.cancelled) {
|
if (state.cancelled) {
|
||||||
emitter.emit("event", { type: "done", success: false, cancelled: true });
|
emitter.emit("event", { type: "done", success: false, cancelled: true });
|
||||||
@@ -35,12 +36,27 @@ function startRun(steps) {
|
|||||||
status: "failed",
|
status: "failed",
|
||||||
error: error.message || String(error),
|
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 {
|
return {
|
||||||
@@ -48,8 +64,16 @@ function startRun(steps) {
|
|||||||
emitter.on(event, handler);
|
emitter.on(event, handler);
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
decide(value) {
|
||||||
|
if (state.pendingDecision) {
|
||||||
|
state.pendingDecision(value);
|
||||||
|
}
|
||||||
|
},
|
||||||
cancel() {
|
cancel() {
|
||||||
state.cancelled = true;
|
state.cancelled = true;
|
||||||
|
if (state.pendingDecision) {
|
||||||
|
state.pendingDecision("abort");
|
||||||
|
}
|
||||||
if (state.child) {
|
if (state.child) {
|
||||||
try {
|
try {
|
||||||
state.child.kill("SIGTERM");
|
state.child.kill("SIGTERM");
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ const {
|
|||||||
} = require("./frp-config");
|
} = require("./frp-config");
|
||||||
|
|
||||||
const NVM_INSTALL_VERSION = "v0.40.4";
|
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"
|
export NVM_DIR="$HOME/.nvm"
|
||||||
if [ ! -s "$NVM_DIR/nvm.sh" ]; then
|
if [ ! -s "$NVM_DIR/nvm.sh" ]; then
|
||||||
if command -v curl >/dev/null 2>&1; then
|
if command -v curl >/dev/null 2>&1; then
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useReducer } from "react";
|
import React, { useEffect, useReducer, useRef } from "react";
|
||||||
import { Box, Text } from "ink";
|
import { Box, Text } from "ink";
|
||||||
import Spinner from "ink-spinner";
|
import Spinner from "ink-spinner";
|
||||||
import SelectInput from "ink-select-input";
|
import SelectInput from "ink-select-input";
|
||||||
@@ -31,8 +31,18 @@ function reducer(state, action) {
|
|||||||
}
|
}
|
||||||
return { ...state, log: next };
|
return { ...state, log: next };
|
||||||
}
|
}
|
||||||
|
case "awaiting-decision":
|
||||||
|
return { ...state, awaitingDecision: action.index };
|
||||||
|
case "decision-made":
|
||||||
|
return { ...state, awaitingDecision: null };
|
||||||
case "done":
|
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:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
@@ -47,16 +57,26 @@ export default function RunLog({ nav, plan, origin }) {
|
|||||||
finished: false,
|
finished: false,
|
||||||
success: false,
|
success: false,
|
||||||
cancelled: false,
|
cancelled: false,
|
||||||
|
awaitingDecision: null,
|
||||||
});
|
});
|
||||||
|
const runRef = useRef(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const run = startRun(plan.steps);
|
const run = startRun(plan.steps);
|
||||||
|
runRef.current = run;
|
||||||
run.on("event", (event) => {
|
run.on("event", (event) => {
|
||||||
dispatch(event);
|
dispatch(event);
|
||||||
});
|
});
|
||||||
return () => run.cancel();
|
return () => run.cancel();
|
||||||
}, [plan]);
|
}, [plan]);
|
||||||
|
|
||||||
|
const handleDecision = (item) => {
|
||||||
|
if (runRef.current) {
|
||||||
|
runRef.current.decide(item.value);
|
||||||
|
}
|
||||||
|
dispatch({ type: "decision-made" });
|
||||||
|
};
|
||||||
|
|
||||||
const recentLog = state.log.slice(-MAX_LOG_LINES);
|
const recentLog = state.log.slice(-MAX_LOG_LINES);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -82,7 +102,20 @@ export default function RunLog({ nav, plan, origin }) {
|
|||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
) : null}
|
) : null}
|
||||||
{state.finished ? <FinishedFooter state={state} nav={nav} origin={origin} /> : (
|
{state.finished ? (
|
||||||
|
<FinishedFooter state={state} nav={nav} origin={origin} />
|
||||||
|
) : state.awaitingDecision != null ? (
|
||||||
|
<Box flexDirection="column" marginTop={1}>
|
||||||
|
<Text color="yellow">Step {state.awaitingDecision + 1} failed. Continue?</Text>
|
||||||
|
<SelectInput
|
||||||
|
items={[
|
||||||
|
{ label: "Skip and continue", value: "skip" },
|
||||||
|
{ label: "Abort run", value: "abort" },
|
||||||
|
]}
|
||||||
|
onSelect={handleDecision}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
<Box marginTop={1}>
|
<Box marginTop={1}>
|
||||||
<Text color="cyan">
|
<Text color="cyan">
|
||||||
<Spinner type="dots" />
|
<Spinner type="dots" />
|
||||||
@@ -168,6 +201,8 @@ function FinishedFooter({ state, nav, origin }) {
|
|||||||
<Text color="yellow">Cancelled.</Text>
|
<Text color="yellow">Cancelled.</Text>
|
||||||
) : state.success ? (
|
) : state.success ? (
|
||||||
<Text color="green">All steps completed successfully.</Text>
|
<Text color="green">All steps completed successfully.</Text>
|
||||||
|
) : Object.values(state.statuses).includes("failed") && !state.cancelled ? (
|
||||||
|
<Text color="yellow">Run finished with skipped failures.</Text>
|
||||||
) : (
|
) : (
|
||||||
<Text color="red">Run failed.</Text>
|
<Text color="red">Run failed.</Text>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user