fix(runtime): hide cli child windows by default

This commit is contained in:
iliya 2026-05-16 00:03:26 +03:00
parent d5f87a286a
commit 5610e13b98
2 changed files with 39 additions and 6 deletions

View file

@ -242,12 +242,16 @@ export function killTrackedCliProcesses(signal: NodeJS.Signals = 'SIGKILL'): voi
} }
} }
/** Merge CLI_ENV_DEFAULTS into spawn/exec options.env (or process.env if absent). */ /** Apply shared CLI process defaults without overriding explicit caller choices. */
function withCliEnv<T extends { env?: NodeJS.ProcessEnv | Record<string, string | undefined> }>( function withCliProcessDefaults<
options: T T extends {
): T { env?: NodeJS.ProcessEnv | Record<string, string | undefined>;
windowsHide?: boolean;
},
>(options: T): T & { windowsHide: boolean } {
return { return {
...options, ...options,
windowsHide: options.windowsHide ?? true,
env: { ...(options.env ?? process.env), ...CLI_ENV_DEFAULTS }, env: { ...(options.env ?? process.env), ...CLI_ENV_DEFAULTS },
}; };
} }
@ -270,7 +274,7 @@ export async function execCli(
); );
} }
const target = binaryPath; const target = binaryPath;
const opts = withCliEnv(options); const opts = withCliProcessDefaults(options);
const directLauncher = resolveDirectWindowsLauncher(target); const directLauncher = resolveDirectWindowsLauncher(target);
if (directLauncher) { if (directLauncher) {
const result = await execFileAsync( const result = await execFileAsync(
@ -316,7 +320,7 @@ export function spawnCli(
args: string[], args: string[],
options: SpawnOptions = {} options: SpawnOptions = {}
): ReturnType<typeof spawn> { ): ReturnType<typeof spawn> {
const opts = withCliEnv(options); const opts = withCliProcessDefaults(options);
const directLauncher = resolveDirectWindowsLauncher(binaryPath); const directLauncher = resolveDirectWindowsLauncher(binaryPath);
if (directLauncher) { if (directLauncher) {
const directOpts = { ...opts }; const directOpts = { ...opts };

View file

@ -134,6 +134,18 @@ describe('cli child process helpers', () => {
expect(result).toEqual({} as any); expect(result).toEqual({} as any);
}); });
it('hides spawned CLI windows by default but preserves explicit opt-out', () => {
setPlatform('win32');
const spawnMock = child.spawn as unknown as Mock;
spawnMock.mockReturnValue({} as any);
spawnCli('C:\\bin\\claude.exe', ['--version']);
expect(spawnMock.mock.calls[0][2]).toMatchObject({ windowsHide: true });
spawnCli('C:\\bin\\claude.exe', ['--version'], { windowsHide: false });
expect(spawnMock.mock.calls[1][2]).toMatchObject({ windowsHide: false });
});
it('falls back to shell when spawn throws EINVAL', () => { it('falls back to shell when spawn throws EINVAL', () => {
setPlatform('win32'); setPlatform('win32');
const error: any = new Error('spawn EINVAL'); const error: any = new Error('spawn EINVAL');
@ -296,6 +308,23 @@ describe('cli child process helpers', () => {
expect(result.stdout).toBe('ok'); expect(result.stdout).toBe('ok');
}); });
it('hides exec CLI windows by default but preserves explicit opt-out', async () => {
setPlatform('win32');
const execFileMock = child.execFile as unknown as Mock;
execFileMock.mockImplementation(
(_cmd: string, _args: string[], _opts: unknown, cb: ExecCallback) => {
cb(null, 'ok', '');
return {} as any;
}
);
await execCli('C:\\bin\\claude.exe', ['--version']);
expect(execFileMock.mock.calls[0][2]).toMatchObject({ windowsHide: true });
await execCli('C:\\bin\\claude.exe', ['--version'], { windowsHide: false });
expect(execFileMock.mock.calls[1][2]).toMatchObject({ windowsHide: false });
});
it('skips straight to shell for Windows cmd launchers', async () => { it('skips straight to shell for Windows cmd launchers', async () => {
setPlatform('win32'); setPlatform('win32');
const execFileMock = child.execFile as unknown as Mock; const execFileMock = child.execFile as unknown as Mock;