From 89205665b1a70cea061c43705ffb44b234655ab1 Mon Sep 17 00:00:00 2001 From: Kevin Rajan <7121943+kvnloo@users.noreply.github.com> Date: Mon, 24 Aug 2026 23:22:20 -0500 Subject: [PATCH] fix(cli): hide detached Windows spawns --- .../cli/src/commands/previewLifecycle.test.ts | 5 ++++ packages/cli/src/commands/previewLifecycle.ts | 2 ++ packages/cli/src/telemetry/client.test.ts | 3 ++- packages/cli/src/telemetry/transport.ts | 2 +- packages/cli/src/utils/openBrowser.test.ts | 24 ++++++++++++++++++- packages/cli/src/utils/openBrowser.ts | 1 + 6 files changed, 34 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/commands/previewLifecycle.test.ts b/packages/cli/src/commands/previewLifecycle.test.ts index c9635677f5..b1532f8e5e 100644 --- a/packages/cli/src/commands/previewLifecycle.test.ts +++ b/packages/cli/src/commands/previewLifecycle.test.ts @@ -355,6 +355,11 @@ describe("background preview lifecycle", () => { expect(result).toMatchObject({ type: "started", port: 3210, pid: 4321 }); expect(unref).toHaveBeenCalledOnce(); + expect(spawn).toHaveBeenCalledWith( + "/usr/bin/node", + expect.any(Array), + expect.objectContaining({ detached: true, windowsHide: true }), + ); expect(existsSync(previewSessionPath(projectDir, stateHome))).toBe(true); }); diff --git a/packages/cli/src/commands/previewLifecycle.ts b/packages/cli/src/commands/previewLifecycle.ts index b892cf9c9d..b62961ddc9 100644 --- a/packages/cli/src/commands/previewLifecycle.ts +++ b/packages/cli/src/commands/previewLifecycle.ts @@ -33,6 +33,7 @@ type SpawnPreview = ( detached: boolean; stdio: ["ignore", number, number]; env: NodeJS.ProcessEnv; + windowsHide: boolean; }, ) => SpawnResult; @@ -219,6 +220,7 @@ function spawnDetachedPreview( detached: true, stdio: ["ignore", logFd, logFd], env: process.env, + windowsHide: true, }, ); } finally { diff --git a/packages/cli/src/telemetry/client.test.ts b/packages/cli/src/telemetry/client.test.ts index bc194dc397..d6d66bc4c6 100644 --- a/packages/cli/src/telemetry/client.test.ts +++ b/packages/cli/src/telemetry/client.test.ts @@ -140,13 +140,14 @@ describe("telemetry queue delivery", () => { const [execPath, args, opts] = spawnMock.mock.calls[0] as unknown as [ string, string[], - { detached: boolean }, + { detached: boolean; windowsHide?: boolean }, ]; expect(execPath).toBe(process.execPath); expect(args[0]).toBe("-e"); expect(args[1]).toContain("render_complete"); expect(args[1]).toMatch(/[0-9a-f-]{36}/); // event uuid rides along expect(opts.detached).toBe(true); + expect(opts.windowsHide).toBe(true); // Queue handed to the child — nothing left for a regular flush. const fetchMock = vi.fn(() => Promise.resolve(new Response(""))); diff --git a/packages/cli/src/telemetry/transport.ts b/packages/cli/src/telemetry/transport.ts index 0605280a06..f9b782f54e 100644 --- a/packages/cli/src/telemetry/transport.ts +++ b/packages/cli/src/telemetry/transport.ts @@ -155,7 +155,7 @@ export function flushSync(): void { "-e", `fetch(${JSON.stringify(`${POSTHOG_HOST}/batch/`)},{method:"POST",headers:{"Content-Type":"application/json"},body:${JSON.stringify(payload)},signal:AbortSignal.timeout(${FLUSH_TIMEOUT_MS})}).catch(()=>{})`, ], - { detached: true, stdio: "ignore" }, + { detached: true, stdio: "ignore", windowsHide: true }, ); // Let the parent exit without waiting for the child child.unref(); diff --git a/packages/cli/src/utils/openBrowser.test.ts b/packages/cli/src/utils/openBrowser.test.ts index 7dbe859d4f..b9de3aeaaa 100644 --- a/packages/cli/src/utils/openBrowser.test.ts +++ b/packages/cli/src/utils/openBrowser.test.ts @@ -1,10 +1,32 @@ -import { describe, it, expect } from "vitest"; +import { describe, it, expect, vi } from "vitest"; + +const spawnMock = vi.hoisted(() => + vi.fn(() => ({ + on: vi.fn(), + unref: vi.fn(), + })), +); +vi.mock("node:child_process", () => ({ spawn: spawnMock })); + import { buildBrowserArgs, + openBrowser, parseRemoteDebuggingPort, validateRemoteDebuggingPortDeps, } from "./openBrowser.js"; +describe("openBrowser", () => { + it("hides the console window when spawning a detached browser", () => { + openBrowser("http://localhost:3002", { browserPath: "C:\\Browser\\browser.exe" }); + + expect(spawnMock).toHaveBeenCalledWith( + "C:\\Browser\\browser.exe", + ["http://localhost:3002"], + expect.objectContaining({ detached: true, windowsHide: true }), + ); + }); +}); + describe("buildBrowserArgs", () => { it("returns only the URL when no options are given", () => { expect(buildBrowserArgs("http://localhost:3002", {})).toEqual(["http://localhost:3002"]); diff --git a/packages/cli/src/utils/openBrowser.ts b/packages/cli/src/utils/openBrowser.ts index 0cde28af9d..5fd0f4316d 100644 --- a/packages/cli/src/utils/openBrowser.ts +++ b/packages/cli/src/utils/openBrowser.ts @@ -81,6 +81,7 @@ export function openBrowser(url: string, options: OpenBrowserOptions = {}): void const child = spawn(options.browserPath, args, { detached: true, stdio: "ignore", + windowsHide: true, }); child.on("error", () => {}); child.unref();