From cce59a03335ca691c1f6e9ac94f4561826f67831 Mon Sep 17 00:00:00 2001 From: Radek Sienkiewicz Date: Thu, 30 Apr 2026 11:46:16 +0200 Subject: [PATCH] fix(cli): silence raw TUI progress --- src/cli/progress.test.ts | 40 ++++++++++++++++++++++++++++++++++++++++ src/cli/progress.ts | 6 +++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/cli/progress.test.ts b/src/cli/progress.test.ts index fc8d5210a70..3f7dd673c43 100644 --- a/src/cli/progress.test.ts +++ b/src/cli/progress.test.ts @@ -1,6 +1,23 @@ import { describe, expect, it, vi } from "vitest"; import { createCliProgress, shouldUseInteractiveProgressSpinner } from "./progress.js"; +function withStdinIsRaw(isRaw: boolean, run: () => T): T { + const original = Object.getOwnPropertyDescriptor(process.stdin, "isRaw"); + Object.defineProperty(process.stdin, "isRaw", { + configurable: true, + value: isRaw, + }); + try { + return run(); + } finally { + if (original) { + Object.defineProperty(process.stdin, "isRaw", original); + } else { + Reflect.deleteProperty(process.stdin, "isRaw"); + } + } +} + describe("cli progress", () => { it("logs progress when non-tty and fallback=log", () => { const writes: string[] = []; @@ -61,4 +78,27 @@ describe("cli progress", () => { }), ).toBe(true); }); + + it("does not write terminal controls when raw TUI input suppresses the default spinner", () => { + const writes: string[] = []; + const stream = { + isTTY: true, + write: vi.fn((chunk: string) => { + writes.push(chunk); + }), + } as unknown as NodeJS.WriteStream; + + withStdinIsRaw(true, () => { + const progress = createCliProgress({ + label: "Scanning", + total: 2, + stream, + }); + progress.setLabel("Still scanning"); + progress.tick(); + progress.done(); + }); + + expect(writes).toEqual([]); + }); }); diff --git a/src/cli/progress.ts b/src/cli/progress.ts index d2a4a5b4fd9..dcf8f4dcfae 100644 --- a/src/cli/progress.ts +++ b/src/cli/progress.ts @@ -66,12 +66,16 @@ 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 stdinIsRaw = process.stdin.isRaw === true; const allowSpinner = shouldUseInteractiveProgressSpinner({ fallback: options.fallback, streamIsTty: isTty, - stdinIsRaw: process.stdin.isRaw, + stdinIsRaw, }); const allowLine = isTty && options.fallback === "line"; + if (isTty && stdinIsRaw && (options.fallback === undefined || options.fallback === "spinner")) { + return noopReporter; + } let started = false; let label = options.label;