From 2ebafaedceeba653ed90c1848f79b6541a0f55b7 Mon Sep 17 00:00:00 2001 From: Prabhjot Singh Date: Fri, 18 Sep 2026 20:56:26 +0530 Subject: [PATCH] fix(windows): hide supervised server console (#13992) * fix(windows): hide supervised server console * fix(windows): port icon.ico fix from #13991 and add regression tests Adds a source-pattern test asserting the supervised server spawn() passes windowsHide: true (Hard Rule #8 gap noted in review), and ports the icon.ico-on-win32 fix from #13991 (credit @prabhtheone) with its own regression test, so both real fixes ship without #13991's unrelated comment purge. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --- bin/cli/runtime/processSupervisor.mjs | 4 ++++ bin/cli/tray/traySystray.mjs | 4 +++- tests/unit/cli-process-supervisor.test.ts | 14 ++++++++++++++ tests/unit/tray-systray-loader-4605.test.ts | 19 +++++++++++++++++++ 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/bin/cli/runtime/processSupervisor.mjs b/bin/cli/runtime/processSupervisor.mjs index b93dfb57bf..e8205c39d5 100644 --- a/bin/cli/runtime/processSupervisor.mjs +++ b/bin/cli/runtime/processSupervisor.mjs @@ -90,6 +90,10 @@ export class ServerSupervisor { cwd: dirname(this.serverPath), env: this.env, stdio: showLog ? "inherit" : ["ignore", "pipe", "pipe"], + // Tray mode has no visible console. Keep the supervised server hidden on Windows, + // including when it is restarted after a crash. Without this, each supervised + // spawn can create a visible terminal window. + windowsHide: true, }); writePidFile("server", this.child.pid); diff --git a/bin/cli/tray/traySystray.mjs b/bin/cli/tray/traySystray.mjs index e720e3d45d..f01b3f02ed 100644 --- a/bin/cli/tray/traySystray.mjs +++ b/bin/cli/tray/traySystray.mjs @@ -29,7 +29,9 @@ async function loadSystray2() { function getIconBase64() { // Icon ships at bin/cli/tray/icon.png — the previous "icons/icon.png" path // never existed, so the tray was created with an empty icon (#4605). - const iconPath = join(__dirname, "icon.png"); + // systray2 expects an ICO payload on Windows; the PNG asset is used elsewhere. + // (ported from #13991, credit @prabhtheone) + const iconPath = join(__dirname, process.platform === "win32" ? "icon.ico" : "icon.png"); if (existsSync(iconPath)) return readFileSync(iconPath).toString("base64"); return ""; } diff --git a/tests/unit/cli-process-supervisor.test.ts b/tests/unit/cli-process-supervisor.test.ts index ffb1efeef3..f781493c6d 100644 --- a/tests/unit/cli-process-supervisor.test.ts +++ b/tests/unit/cli-process-supervisor.test.ts @@ -335,3 +335,17 @@ test("every Bun server spawn (supervisor, --daemon, --no-recovery) uses the shar ); assert.doesNotMatch(serveSrc, /join\(APP_DIR,\s*"open-sse/); }); + +test("#13992: supervised server spawn hides the console window on Windows", () => { + const supervisorSrc = fs.readFileSync( + path.join(REPO_ROOT, "bin/cli/runtime/processSupervisor.mjs"), + "utf8" + ); + + assert.match( + supervisorSrc, + /spawn\(process\.execPath,\s*buildServerSpawnArgs\([\s\S]*?\),\s*\{[\s\S]*?windowsHide:\s*true[\s\S]*?\}\)/, + "the supervised server spawn() must pass windowsHide: true so a tray-mode restart never " + + "flashes a visible console on Windows" + ); +}); diff --git a/tests/unit/tray-systray-loader-4605.test.ts b/tests/unit/tray-systray-loader-4605.test.ts index adbf760308..e99066d633 100644 --- a/tests/unit/tray-systray-loader-4605.test.ts +++ b/tests/unit/tray-systray-loader-4605.test.ts @@ -19,8 +19,12 @@ import test from "node:test"; import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; import { initSystrayUnix } from "../../bin/cli/tray/traySystray.mjs"; +const REPO_ROOT = path.resolve(import.meta.dirname, "../.."); + class FakeSysTray { static lastOpts: unknown = null; onClickFn: unknown = null; @@ -78,3 +82,18 @@ test("initSystrayUnix returns null without throwing when the loader yields null const tray = await initSystrayUnix(opts, async () => null); assert.equal(tray, null, "must degrade to null when systray2 cannot be loaded"); }); + +// #13992 (ported from #13991, credit @prabhtheone): systray2 expects an ICO +// payload on Windows — icon.png alone left the Windows tray icon blank. +test("getIconBase64 selects icon.ico on win32 and icon.png elsewhere (#13991/#13992)", () => { + const traySrc = fs.readFileSync(path.join(REPO_ROOT, "bin/cli/tray/traySystray.mjs"), "utf8"); + assert.match( + traySrc, + /join\(__dirname,\s*process\.platform === "win32" \? "icon\.ico" : "icon\.png"\)/, + "getIconBase64 must resolve icon.ico on Windows instead of always using icon.png" + ); + assert.ok( + fs.existsSync(path.join(REPO_ROOT, "bin/cli/tray/icon.ico")), + "bin/cli/tray/icon.ico must exist for the win32 branch to resolve to a real file" + ); +});