Skip to content

Commit b45f2d6

Browse files
authored
fix(cli): degrade to plain logs on standard non-interactive signals (#2709)
The CLI yaml runner only treated a falsy process.stdout.isTTY (or the MIDSCENE_CLI_LOG_ON_NON_TTY opt-out) as non-interactive. CI pipelines and Kubernetes pods that allocate a pseudo-TTY therefore still got the in-place spinner renderer, flooding stream-based log collectors (Fluentd/Filebeat) with raw \x1b[1A\x1b[2K cursor-control sequences. Honor the well-known NO_COLOR, TERM=dumb and CI signals so the renderer falls back to line-by-line console.log in those environments. Extracted the logic into a testable resolveIsTTY() helper with unit coverage. Refs #2689
1 parent 4f270f8 commit b45f2d6

2 files changed

Lines changed: 58 additions & 3 deletions

File tree

packages/cli/src/printer.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,35 @@ export interface MidsceneYamlFileContext {
1212
player: ScriptPlayer<MidsceneYamlScriptEnv>;
1313
}
1414

15-
export const isTTY = process.env.MIDSCENE_CLI_LOG_ON_NON_TTY
16-
? false
17-
: process.stdout.isTTY;
15+
/**
16+
* Decide whether the interactive (spinner / in-place redraw) renderer is safe
17+
* to use. Besides a real TTY, we honor well-known "I'm not an interactive
18+
* terminal" signals so that CI pipelines and Kubernetes pods do not get their
19+
* stream-based log collectors (Fluentd / Filebeat) flooded with raw
20+
* `\x1b[1A\x1b[2K` cursor-control sequences.
21+
*
22+
* - `MIDSCENE_CLI_LOG_ON_NON_TTY`: explicit opt-out (existing flag).
23+
* - `NO_COLOR`: https://no-color.org convention.
24+
* - `TERM=dumb`: terminal that cannot handle cursor movement.
25+
* - `CI`: generic CI flag set by virtually every CI provider; relevant when the
26+
* environment still allocates a pseudo-TTY (e.g. `kubectl exec -t`).
27+
*/
28+
export function resolveIsTTY(
29+
env: NodeJS.ProcessEnv = process.env,
30+
stdoutIsTTY: boolean | undefined = process.stdout.isTTY,
31+
): boolean {
32+
if (
33+
env.MIDSCENE_CLI_LOG_ON_NON_TTY ||
34+
env.NO_COLOR ||
35+
env.TERM === 'dumb' ||
36+
env.CI
37+
) {
38+
return false;
39+
}
40+
return Boolean(stdoutIsTTY);
41+
}
42+
43+
export const isTTY = resolveIsTTY();
1844
export const indent = ' ';
1945
export const spinnerInterval = 80;
2046
export const spinnerFrames = ['◰', '◳', '◲', '◱']; // https://github.com/sindresorhus/cli-spinners/blob/main/spinners.json
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { resolveIsTTY } from '@/printer';
2+
import { describe, expect, it } from 'vitest';
3+
4+
describe('resolveIsTTY', () => {
5+
it('returns true on a real interactive TTY', () => {
6+
expect(resolveIsTTY({}, true)).toBe(true);
7+
});
8+
9+
it('returns false when stdout is not a TTY', () => {
10+
expect(resolveIsTTY({}, undefined)).toBe(false);
11+
expect(resolveIsTTY({}, false)).toBe(false);
12+
});
13+
14+
it('falls back to non-TTY for the explicit opt-out flag', () => {
15+
expect(resolveIsTTY({ MIDSCENE_CLI_LOG_ON_NON_TTY: '1' }, true)).toBe(
16+
false,
17+
);
18+
});
19+
20+
it('honors standard non-interactive signals even with an allocated TTY', () => {
21+
expect(resolveIsTTY({ NO_COLOR: '1' }, true)).toBe(false);
22+
expect(resolveIsTTY({ TERM: 'dumb' }, true)).toBe(false);
23+
expect(resolveIsTTY({ CI: 'true' }, true)).toBe(false);
24+
});
25+
26+
it('does not treat TERM=xterm as non-interactive', () => {
27+
expect(resolveIsTTY({ TERM: 'xterm-256color' }, true)).toBe(true);
28+
});
29+
});

0 commit comments

Comments
 (0)