From 3ccd535eada872f7f66c17a60b8364190c1c1a8f Mon Sep 17 00:00:00 2001 From: artale Date: Mon, 15 Jun 2026 03:37:51 +0200 Subject: [PATCH] fix: keep CLI runner cross-platform --- src/core/cli-runner.test.ts | 14 ++++++++++++++ src/core/cli-runner.ts | 14 +++++--------- 2 files changed, 19 insertions(+), 9 deletions(-) create mode 100644 src/core/cli-runner.test.ts diff --git a/src/core/cli-runner.test.ts b/src/core/cli-runner.test.ts new file mode 100644 index 0000000..46d87d1 --- /dev/null +++ b/src/core/cli-runner.test.ts @@ -0,0 +1,14 @@ +import { describe, expect, it } from "vitest"; +import { runCli } from "./cli-runner.js"; + +describe("runCli", () => { + it("runs argv without shell interpolation", async () => { + const out = await runCli(process.execPath, ["-e", "console.log(process.argv[1])", "hello&world"], 10_000); + expect(out).toBe("hello&world"); + }); + + it("reports nonzero exits as errors", async () => { + const out = await runCli(process.execPath, ["-e", "console.log('bad'); process.exit(7)"], 10_000); + expect(out).toContain("CLI exited 7"); + }); +}); diff --git a/src/core/cli-runner.ts b/src/core/cli-runner.ts index ca4e645..d656bb0 100644 --- a/src/core/cli-runner.ts +++ b/src/core/cli-runner.ts @@ -2,19 +2,15 @@ import { spawn } from "node:child_process"; const MAX_OUTPUT_BYTES = 10 * 1024 * 1024; -function cmdQuote(value: string): string { - // ponytail: minimal cmd.exe quoting for Windows .cmd shims; callers still pass argv, not shell text. - return `"${value.replace(/(["^&|<>()%!])/g, "^$1")}"`; +function isCmdShim(bin: string): boolean { + return process.platform === "win32" && /(?:\.cmd|\.bat)$/i.test(bin); } export function runCli(bin: string, args: string[], timeoutMs = 120_000): Promise { return new Promise((resolve) => { - const isWin = process.platform === "win32"; - const command = isWin ? "cmd.exe" : bin; - const commandArgs = isWin - ? ["/d", "/s", "/c", [cmdQuote(bin), ...args.map(cmdQuote)].join(" ")] - : args; - + // ponytail: only .cmd/.bat need cmd.exe; real .exe paths keep true argv semantics. + const command = isCmdShim(bin) ? process.env.ComSpec ?? "cmd.exe" : bin; + const commandArgs = isCmdShim(bin) ? ["/d", "/s", "/c", bin, ...args] : args; const child = spawn(command, commandArgs, { shell: false, timeout: timeoutMs,