diff --git a/bin/cli/commands/dashboard.mjs b/bin/cli/commands/dashboard.mjs index 44d2da1df9..42157d27fb 100644 --- a/bin/cli/commands/dashboard.mjs +++ b/bin/cli/commands/dashboard.mjs @@ -49,22 +49,22 @@ export async function runDashboardCommand(opts = {}) { return 0; } +/** + * Resolve the command and args to open a URL in the default browser + * for a given platform. Exported for testing — callers should use openFallback(). + * @param {"darwin"|"win32"|string} platform + * @param {string} url + * @returns {{ cmd: string, args: string[] }} + */ +export function resolveOpenCommand(platform, url) { + if (platform === "darwin") return { cmd: "open", args: [url] }; + if (platform === "win32") return { cmd: "rundll32", args: ["url.dll,FileProtocolHandler", url] }; + return { cmd: "xdg-open", args: [url] }; +} + function openFallback(url) { return new Promise((resolve) => { - const { platform } = process; - let cmd, args; - - if (platform === "darwin") { - cmd = "open"; - args = [url]; - } else if (platform === "win32") { - cmd = "cmd"; - args = ["/c", "start", "", url]; - } else { - cmd = "xdg-open"; - args = [url]; - } - + const { cmd, args } = resolveOpenCommand(process.platform, url); execFile(cmd, args, { stdio: "ignore" }, () => resolve()); }); } diff --git a/changelog.d/fixes/7844-dashboard-no-cmd-fallback.md b/changelog.d/fixes/7844-dashboard-no-cmd-fallback.md new file mode 100644 index 0000000000..5f603edaa8 --- /dev/null +++ b/changelog.d/fixes/7844-dashboard-no-cmd-fallback.md @@ -0,0 +1 @@ +- **fix(cli):** Windows `omniroute dashboard` browser fallback uses `rundll32` instead of `cmd.exe` to open the dashboard URL, avoiding an unnecessary shell spawn ([#7844](https://github.com/diegosouzapw/OmniRoute/pull/7842)) diff --git a/tests/unit/cli-dashboard-open-fallback.test.ts b/tests/unit/cli-dashboard-open-fallback.test.ts new file mode 100644 index 0000000000..ecc6cc2436 --- /dev/null +++ b/tests/unit/cli-dashboard-open-fallback.test.ts @@ -0,0 +1,35 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { resolveOpenCommand } from "../../bin/cli/commands/dashboard.mjs"; + +test("openFallback: darwin uses 'open' command", () => { + const { cmd, args } = resolveOpenCommand("darwin", "http://localhost:20128"); + assert.equal(cmd, "open"); + assert.deepEqual(args, ["http://localhost:20128"]); +}); + +test("openFallback: win32 uses 'rundll32 url.dll,FileProtocolHandler' instead of cmd.exe", () => { + const { cmd, args } = resolveOpenCommand("win32", "http://localhost:20128"); + assert.equal(cmd, "rundll32"); + assert.deepEqual(args, ["url.dll,FileProtocolHandler", "http://localhost:20128"]); + assert.notEqual(cmd, "cmd"); +}); + +test("openFallback: linux/other uses 'xdg-open' command", () => { + const { cmd, args } = resolveOpenCommand("linux", "http://localhost:20128"); + assert.equal(cmd, "xdg-open"); + assert.deepEqual(args, ["http://localhost:20128"]); +}); + +test("openFallback: unknown platform falls back to xdg-open", () => { + const { cmd, args } = resolveOpenCommand("aix", "http://localhost:20128"); + assert.equal(cmd, "xdg-open"); + assert.deepEqual(args, ["http://localhost:20128"]); +}); + +test("openFallback: URL with special characters is passed through verbatim", () => { + const url = "http://localhost:20128/dashboard?q=test&filter=a+b"; + const { cmd, args } = resolveOpenCommand("win32", url); + assert.equal(cmd, "rundll32"); + assert.equal(args[1], url); +});