Skip to content

Commit 0f059fd

Browse files
authored
Merge pull request #267 from PaulJPhilp/codex/ep-admin-tty-machine-consistency
fix(ep-admin): suppress progress noise in non-interactive mode
2 parents e220522 + c316f5d commit 0f059fd

3 files changed

Lines changed: 108 additions & 21 deletions

File tree

packages/ep-admin/src/services/execution/__tests__/helpers.test.ts

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import { Effect } from "effect";
66
import { describe, expect, it } from "vitest";
7-
import { withSpinner } from "../helpers.js";
7+
import { shouldRenderProgress, withSpinner } from "../helpers.js";
88

99
describe("Execution Helpers", () => {
1010
describe("withSpinner", () => {
@@ -25,4 +25,59 @@ describe("Execution Helpers", () => {
2525
});
2626
});
2727

28-
});
28+
describe("shouldRenderProgress", () => {
29+
it("returns true in interactive tty mode", () => {
30+
expect(
31+
shouldRenderProgress(undefined, {
32+
isTTY: true,
33+
ci: undefined,
34+
term: "xterm-256color",
35+
})
36+
).toBe(true);
37+
});
38+
39+
it("returns false in non-tty mode", () => {
40+
expect(
41+
shouldRenderProgress(undefined, {
42+
isTTY: false,
43+
ci: undefined,
44+
term: "xterm-256color",
45+
})
46+
).toBe(false);
47+
});
48+
49+
it("returns false in CI mode", () => {
50+
expect(
51+
shouldRenderProgress(undefined, {
52+
isTTY: true,
53+
ci: "true",
54+
term: "xterm-256color",
55+
})
56+
).toBe(false);
57+
});
58+
59+
it("returns false when terminal is dumb", () => {
60+
expect(
61+
shouldRenderProgress(undefined, {
62+
isTTY: true,
63+
ci: undefined,
64+
term: "dumb",
65+
})
66+
).toBe(false);
67+
});
68+
69+
it("returns false in verbose mode", () => {
70+
expect(
71+
shouldRenderProgress(
72+
{ verbose: true },
73+
{
74+
isTTY: true,
75+
ci: undefined,
76+
term: "xterm-256color",
77+
}
78+
)
79+
).toBe(false);
80+
});
81+
});
82+
83+
});

packages/ep-admin/src/services/execution/helpers.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,33 @@ import { spawn } from "node:child_process";
77
import { ExecutionError } from "./errors.js";
88
import type { ExecutionOptions } from "./types.js";
99

10+
type ProgressSignal = {
11+
readonly isTTY: boolean;
12+
readonly ci?: string;
13+
readonly term?: string;
14+
};
15+
16+
const isTruthyEnvFlag = (value: string | undefined): boolean => {
17+
if (!value) return false;
18+
const normalized = value.trim().toLowerCase();
19+
return normalized !== "0" && normalized !== "false" && normalized !== "no";
20+
};
21+
22+
export const shouldRenderProgress = (
23+
options?: ExecutionOptions,
24+
signal: ProgressSignal = {
25+
isTTY: Boolean(process.stdout.isTTY),
26+
ci: process.env.CI,
27+
term: process.env.TERM,
28+
}
29+
): boolean => {
30+
if (options?.verbose) return false;
31+
if (!signal.isTTY) return false;
32+
if (isTruthyEnvFlag(signal.ci)) return false;
33+
if (signal.term?.toLowerCase() === "dumb") return false;
34+
return true;
35+
};
36+
1037
/**
1138
* Convert child process spawn to Effect
1239
* Returns void on success, Error on failure
@@ -65,8 +92,12 @@ export const spawnEffect = (
6592
export const withSpinner = <A, E, R>(
6693
message: string,
6794
effect: Effect.Effect<A, E, R>,
68-
_options?: ExecutionOptions
95+
options?: ExecutionOptions
6996
): Effect.Effect<A, E, R> => {
97+
if (!shouldRenderProgress(options)) {
98+
return effect;
99+
}
100+
70101
const showStart = Console.log(`⣾ ${message}...`);
71102
const showEnd = Console.log(`✓ ${message} completed`);
72103

packages/ep-admin/src/services/execution/service.ts

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { spawn } from "node:child_process";
77
import { TUIService } from "../tui/service.js";
88
import type { ExecutionService } from "./api.js";
99
import { ExecutionError, ScriptExecutionError } from "./errors.js";
10-
import { spawnEffect, withSpinner } from "./helpers.js";
10+
import { shouldRenderProgress, spawnEffect, withSpinner } from "./helpers.js";
1111
import type { ExecutionOptions } from "./types.js";
1212

1313
/**
@@ -25,24 +25,25 @@ export class Execution extends Effect.Service<Execution>()("Execution", {
2525
) =>
2626
Effect.gen(function* () {
2727
const task = spawnEffect(scriptPath, options);
28-
29-
// Try to use TUI spinner if available
30-
// biome-ignore lint/suspicious/noExplicitAny: Dynamic TUI module shape varies at runtime
31-
const tui = yield* tuiService.load().pipe(Effect.catchAll(() => Effect.succeed(null))) as Effect.Effect<Record<string, any> | null>;
32-
if (tui?.InkService && tui?.spinnerEffect) {
33-
const maybeInk = yield* Effect.serviceOption(tui.InkService);
34-
if (Opt.isSome(maybeInk) && !options?.verbose) {
35-
// Use TUI spinner
36-
yield* (tui.spinnerEffect(taskName, task, {
37-
type: "dots",
38-
color: "cyan",
39-
}) as Effect.Effect<void, ExecutionError>);
40-
return;
28+
const showProgress = shouldRenderProgress(options);
29+
30+
if (showProgress) {
31+
// Try to use TUI spinner if available
32+
// biome-ignore lint/suspicious/noExplicitAny: Dynamic TUI module shape varies at runtime
33+
const tui = yield* tuiService.load().pipe(Effect.catchAll(() => Effect.succeed(null))) as Effect.Effect<Record<string, any> | null>;
34+
if (tui?.InkService && tui?.spinnerEffect) {
35+
const maybeInk = yield* Effect.serviceOption(tui.InkService);
36+
if (Opt.isSome(maybeInk)) {
37+
// Use TUI spinner
38+
yield* (tui.spinnerEffect(taskName, task, {
39+
type: "dots",
40+
color: "cyan",
41+
}) as Effect.Effect<void, ExecutionError>);
42+
return;
43+
}
4144
}
42-
}
4345

44-
// Fallback to console with ora-style output
45-
if (!options?.verbose) {
46+
// Fallback to console with ora-style output
4647
yield* Console.log(`⣾ ${taskName}...`);
4748
}
4849

@@ -64,7 +65,7 @@ export class Execution extends Effect.Service<Execution>()("Execution", {
6465
})
6566
);
6667

67-
if (!options?.verbose) {
68+
if (showProgress) {
6869
yield* Console.log(`✓ ${taskName} completed`);
6970
}
7071
});

0 commit comments

Comments
 (0)