refactor: clarify restoreTerminalState stdin resume option

This commit is contained in:
Peter Steinberger
2026-02-14 20:33:07 +01:00
parent e03dc987e3
commit 994bcbf670
7 changed files with 35 additions and 9 deletions

View File

@@ -47,7 +47,7 @@ describe("runInteractiveOnboarding", () => {
expect(runtime.exit).toHaveBeenCalledWith(1);
expect(mocks.restoreTerminalState).toHaveBeenCalledWith("onboarding finish", {
resumeStdin: false,
resumeStdinIfPaused: false,
});
});
@@ -59,7 +59,7 @@ describe("runInteractiveOnboarding", () => {
expect(runtime.exit).not.toHaveBeenCalled();
expect(mocks.restoreTerminalState).toHaveBeenCalledWith("onboarding finish", {
resumeStdin: false,
resumeStdinIfPaused: false,
});
});
});

View File

@@ -41,7 +41,7 @@ describe("runInteractiveOnboarding", () => {
expect(mocks.runOnboardingWizard).toHaveBeenCalledOnce();
expect(mocks.restoreTerminalState).toHaveBeenCalledWith("onboarding finish", {
resumeStdin: false,
resumeStdinIfPaused: false,
});
});
@@ -60,7 +60,7 @@ describe("runInteractiveOnboarding", () => {
expect(runtime.exit).toHaveBeenCalledWith(1);
expect(mocks.restoreTerminalState).toHaveBeenCalledWith("onboarding finish", {
resumeStdin: false,
resumeStdinIfPaused: false,
});
const restoreOrder =
mocks.restoreTerminalState.mock.invocationCallOrder[0] ?? Number.MAX_SAFE_INTEGER;

View File

@@ -23,7 +23,7 @@ export async function runInteractiveOnboarding(
throw err;
} finally {
// Keep stdin paused so non-daemon runs can exit cleanly (e.g. Docker setup).
restoreTerminalState("onboarding finish", { resumeStdin: false });
restoreTerminalState("onboarding finish", { resumeStdinIfPaused: false });
if (exitCode !== null) {
runtime.exit(exitCode);
}

View File

@@ -31,7 +31,7 @@ export const defaultRuntime: RuntimeEnv = {
console.error(...args);
},
exit: (code) => {
restoreTerminalState("runtime exit", { resumeStdin: false });
restoreTerminalState("runtime exit", { resumeStdinIfPaused: false });
process.exit(code);
throw new Error("unreachable"); // satisfies tests when mocked
},

View File

@@ -58,9 +58,26 @@ describe("restoreTerminalState", () => {
(process.stdin as { resume?: () => void }).resume = resume;
(process.stdin as { isPaused?: () => boolean }).isPaused = isPaused;
restoreTerminalState("test", { resumeStdin: true });
restoreTerminalState("test", { resumeStdinIfPaused: true });
expect(setRawMode).toHaveBeenCalledWith(false);
expect(resume).toHaveBeenCalledOnce();
});
it("does not touch stdin when stdin is not a TTY", () => {
const setRawMode = vi.fn();
const resume = vi.fn();
const isPaused = vi.fn(() => true);
Object.defineProperty(process.stdin, "isTTY", { value: false, configurable: true });
Object.defineProperty(process.stdout, "isTTY", { value: false, configurable: true });
(process.stdin as { setRawMode?: (mode: boolean) => void }).setRawMode = setRawMode;
(process.stdin as { resume?: () => void }).resume = resume;
(process.stdin as { isPaused?: () => boolean }).isPaused = isPaused;
restoreTerminalState("test", { resumeStdinIfPaused: true });
expect(setRawMode).not.toHaveBeenCalled();
expect(resume).not.toHaveBeenCalled();
});
});

View File

@@ -10,6 +10,13 @@ type RestoreTerminalStateOptions = {
* Default: false (safer for "cleanup then exit" call sites).
*/
resumeStdin?: boolean;
/**
* Alias for resumeStdin. Prefer this name to make the behavior explicit.
*
* Default: false.
*/
resumeStdinIfPaused?: boolean;
};
function reportRestoreFailure(scope: string, err: unknown, reason?: string): void {
@@ -26,7 +33,9 @@ export function restoreTerminalState(
reason?: string,
options: RestoreTerminalStateOptions = {},
): void {
const resumeStdin = options.resumeStdin ?? false;
// Docker TTY note: resuming stdin can keep a container process alive even
// after the wizard is "done" (stdin_open: true), making installers appear hung.
const resumeStdin = options.resumeStdinIfPaused ?? options.resumeStdin ?? false;
try {
clearActiveProgressLine();
} catch (err) {

View File

@@ -329,7 +329,7 @@ export async function finalizeOnboardingWizard(
});
if (hatchChoice === "tui") {
restoreTerminalState("pre-onboarding tui", { resumeStdin: true });
restoreTerminalState("pre-onboarding tui", { resumeStdinIfPaused: true });
await runTui({
url: links.wsUrl,
token: settings.authMode === "token" ? settings.gatewayToken : undefined,