diff --git a/src/cli/progress.test.ts b/src/cli/progress.test.ts index 2f9004ee40c..fc8d5210a70 100644 --- a/src/cli/progress.test.ts +++ b/src/cli/progress.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it, vi } from "vitest"; -import { createCliProgress } from "./progress.js"; +import { createCliProgress, shouldUseInteractiveProgressSpinner } from "./progress.js"; describe("cli progress", () => { it("logs progress when non-tty and fallback=log", () => { @@ -43,4 +43,22 @@ describe("cli progress", () => { expect(write).not.toHaveBeenCalled(); }); + + it("does not use readline-backed spinners while raw TUI input is active", () => { + expect( + shouldUseInteractiveProgressSpinner({ + streamIsTty: true, + stdinIsRaw: true, + }), + ).toBe(false); + }); + + it("keeps the normal interactive spinner for regular tty commands", () => { + expect( + shouldUseInteractiveProgressSpinner({ + streamIsTty: true, + stdinIsRaw: false, + }), + ).toBe(true); + }); }); diff --git a/src/cli/progress.ts b/src/cli/progress.ts index 4ec8f58211a..d2a4a5b4fd9 100644 --- a/src/cli/progress.ts +++ b/src/cli/progress.ts @@ -33,6 +33,15 @@ export type ProgressTotalsUpdate = { label?: string; }; +export function shouldUseInteractiveProgressSpinner(params: { + fallback?: ProgressOptions["fallback"]; + streamIsTty?: boolean; + stdinIsRaw?: boolean; +}): boolean { + const spinnerRequested = params.fallback === undefined || params.fallback === "spinner"; + return spinnerRequested && params.streamIsTty === true && params.stdinIsRaw !== true; +} + const noopReporter: ProgressReporter = { setLabel: () => {}, setPercent: () => {}, @@ -57,7 +66,11 @@ export function createCliProgress(options: ProgressOptions): ProgressReporter { const delayMs = typeof options.delayMs === "number" ? options.delayMs : DEFAULT_DELAY_MS; const canOsc = isTty && supportsOscProgress(process.env, isTty); - const allowSpinner = isTty && (options.fallback === undefined || options.fallback === "spinner"); + const allowSpinner = shouldUseInteractiveProgressSpinner({ + fallback: options.fallback, + streamIsTty: isTty, + stdinIsRaw: process.stdin.isRaw, + }); const allowLine = isTty && options.fallback === "line"; let started = false;