fix(tui): prevented test env from suppressing interactive launch

Scoped test-runtime detection to explicit runner markers and Bun test entrypoints, so application NODE_ENV/BUN_ENV values no longer make ProcessTerminal headless.

Added subprocess regression coverage and propagated the private marker to test children.

Fixes #7261
This commit is contained in:
roboomp
2026-08-01 11:39:57 +00:00
parent 8baa3300bc
commit 9f9c9758a1
7 changed files with 54 additions and 5 deletions
+4
View File
@@ -2,6 +2,10 @@
## [Unreleased]
### Fixed
- Fixed interactive launches hanging silently when a host project or its `.env` sets `NODE_ENV=test` or `BUN_ENV=test` ([#7261](https://github.com/can1357/oh-my-pi/issues/7261)).
## [17.2.3] - 2026-08-01
### Changed
+4
View File
@@ -2,6 +2,10 @@
## [Unreleased]
### Fixed
- Fixed interactive terminals suppressing all output and input when the host project sets `NODE_ENV=test` or `BUN_ENV=test` ([#7261](https://github.com/can1357/oh-my-pi/issues/7261)).
## [17.2.2] - 2026-07-31
### Added
+4
View File
@@ -2,6 +2,10 @@
## [Unreleased]
### Fixed
- Fixed Bun test-runtime detection treating application-owned `NODE_ENV=test` and `BUN_ENV=test` values as test-runner signals ([#7261](https://github.com/can1357/oh-my-pi/issues/7261)).
## [17.2.1] - 2026-07-30
### Added
+6 -2
View File
@@ -258,9 +258,13 @@ export function $envpos(name: string, defaultValue: number): number {
return parsed;
}
/** True when `BUN_ENV` or `NODE_ENV` is the string `test`. */
const BUN_TEST_ENTRY_PATTERN = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
/** True when the process is an explicitly marked test child or Bun is running a test entrypoint. */
export function isBunTestRuntime(): boolean {
return Bun.env.BUN_ENV === "test" || Bun.env.NODE_ENV === "test";
if (Bun.env.PI_TEST_RUNTIME === "1") return true;
const hasTestEnvironment = Bun.env.BUN_ENV === "test" || Bun.env.NODE_ENV === "test";
return hasTestEnvironment && BUN_TEST_ENTRY_PATTERN.test(Bun.main);
}
let terminalHeadless = isBunTestRuntime();
+29
View File
@@ -5,6 +5,7 @@ import * as path from "node:path";
import { filterProcessEnv, parseEnvFile } from "@oh-my-pi/pi-utils/env";
const tempDirs: string[] = [];
const runtimeProbePath = path.join(import.meta.dir, "fixtures", "test-runtime-probe.ts");
afterEach(() => {
for (const dir of tempDirs.splice(0)) {
@@ -20,6 +21,23 @@ function writeTempEnv(content: string): string {
return filePath;
}
async function runRuntimeProbe(env: Record<string, string | undefined>): Promise<boolean> {
const cwd = path.dirname(writeTempEnv(""));
const proc = Bun.spawn([process.execPath, runtimeProbePath], {
cwd,
env: { ...process.env, ...env },
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
proc.exited,
]);
expect(exitCode, stderr).toBe(0);
return JSON.parse(stdout) as boolean;
}
describe("parseEnvFile", () => {
it("ignores malformed names and nul-containing values", () => {
const filePath = writeTempEnv(
@@ -121,3 +139,14 @@ describe("filterProcessEnv", () => {
});
});
});
describe("isBunTestRuntime", () => {
it("does not treat shared application env names as a test runner signal", async () => {
expect(await runRuntimeProbe({ NODE_ENV: "test", BUN_ENV: undefined, PI_TEST_RUNTIME: undefined })).toBe(false);
expect(await runRuntimeProbe({ NODE_ENV: undefined, BUN_ENV: "test", PI_TEST_RUNTIME: undefined })).toBe(false);
});
it("honors the private test runner signal", async () => {
expect(await runRuntimeProbe({ NODE_ENV: undefined, BUN_ENV: undefined, PI_TEST_RUNTIME: "1" })).toBe(true);
});
});
+3
View File
@@ -0,0 +1,3 @@
import { isBunTestRuntime } from "@oh-my-pi/pi-utils/env";
process.stdout.write(JSON.stringify(isBunTestRuntime()));
+4 -3
View File
@@ -467,9 +467,9 @@ async function runTestCommand(testCommand: TestCommand): Promise<void> {
}
}
// Child env shared by every spawned test process: the parent env with all CI
// credential / cloud-config variables scrubbed (see SCRUBBED_ENV_* above) and
// GITHUB_ACTIONS cleared so suites resolve only against their own fixtures.
// Child env shared by every spawned test process: the parent env with the
// private test-runtime marker set, all CI credential / cloud-config variables
// scrubbed (see SCRUBBED_ENV_* above), and GITHUB_ACTIONS cleared.
//
// GC knobs (both needed — they gate different JSC mechanisms):
// - `BUN_JSC_useConcurrentGC=0` stops the collector from marking concurrently
@@ -493,6 +493,7 @@ function buildChildEnv(): Record<string, string | undefined> {
const env: Record<string, string | undefined> = {
...Bun.env,
GITHUB_ACTIONS: "",
PI_TEST_RUNTIME: "1",
BUN_JSC_useConcurrentGC: "0",
BUN_JSC_numberOfGCMarkers: "1",
};