fix(server): share Windows launch handling across providers

This commit is contained in:
Mohamed Boudra
2026-04-16 16:44:24 +07:00
parent 917c2f56f4
commit de33dfd034
2 changed files with 17 additions and 10 deletions
+6 -3
View File
@@ -35,7 +35,7 @@ async function enumerateCandidates(name: string): Promise<string[]> {
}); });
} }
function isWindowsCommandScript(executablePath: string): boolean { export function isWindowsCommandScript(executablePath: string): boolean {
const extension = extname(executablePath).toLowerCase(); const extension = extname(executablePath).toLowerCase();
return process.platform === "win32" && (extension === ".cmd" || extension === ".bat"); return process.platform === "win32" && (extension === ".cmd" || extension === ".bat");
} }
@@ -141,8 +141,11 @@ function escapeWindowsCmdValue(value: string): string {
const unquoted = isQuoted ? value.slice(1, -1) : value; const unquoted = isQuoted ? value.slice(1, -1) : value;
const escaped = unquoted.replace(/%/g, "%%").replace(/([&|^<>()!])/g, "^$1"); const escaped = unquoted.replace(/%/g, "%%").replace(/([&|^<>()!])/g, "^$1");
if (isQuoted || escaped.includes(" ")) { if (isQuoted || /[\s"]/u.test(unquoted)) {
return `"${escaped}"`; const quoted = escaped
.replace(/(\\*)"/g, (_match, slashes: string) => `${slashes}${slashes}\\"`)
.replace(/\\+$/u, (slashes) => `${slashes}${slashes}`);
return `"${quoted}"`;
} }
return escaped; return escaped;
+11 -7
View File
@@ -1,7 +1,7 @@
import { execFile, spawn, type ChildProcess, type SpawnOptions } from "node:child_process"; import { execFile, spawn, type ChildProcess, type SpawnOptions } from "node:child_process";
import { promisify } from "node:util"; import { promisify } from "node:util";
import { quoteWindowsArgument, quoteWindowsCommand } from "./executable.js"; import { isWindowsCommandScript, quoteWindowsArgument, quoteWindowsCommand } from "./executable.js";
const execFileAsync = promisify(execFile); const execFileAsync = promisify(execFile);
@@ -24,13 +24,15 @@ export function spawnProcess(
options?: SpawnOptions, options?: SpawnOptions,
): ChildProcess { ): ChildProcess {
const isWindows = process.platform === "win32"; const isWindows = process.platform === "win32";
const shell = isWindowsCommandScript(command) ? true : (options?.shell ?? isWindows);
const resolvedCommand = isWindows ? quoteWindowsCommand(command) : command; const shouldQuoteForShell = isWindows && shell !== false;
const resolvedArgs = isWindows ? args.map(quoteWindowsArgument) : args; const resolvedCommand = shouldQuoteForShell ? quoteWindowsCommand(command) : command;
const resolvedArgs = shouldQuoteForShell ? args.map(quoteWindowsArgument) : args;
return spawn(resolvedCommand, resolvedArgs, { return spawn(resolvedCommand, resolvedArgs, {
...options, ...options,
shell: options?.shell ?? isWindows, shell,
windowsHide: true, windowsHide: true,
}); });
} }
@@ -41,8 +43,10 @@ export async function execCommand(
options?: ExecCommandOptions, options?: ExecCommandOptions,
): Promise<ExecCommandResult> { ): Promise<ExecCommandResult> {
const isWindows = process.platform === "win32"; const isWindows = process.platform === "win32";
const resolvedCommand = isWindows ? quoteWindowsCommand(command) : command; const shell = isWindowsCommandScript(command) ? true : isWindows;
const resolvedArgs = isWindows ? args.map(quoteWindowsArgument) : args; const shouldQuoteForShell = isWindows && shell !== false;
const resolvedCommand = shouldQuoteForShell ? quoteWindowsCommand(command) : command;
const resolvedArgs = shouldQuoteForShell ? args.map(quoteWindowsArgument) : args;
return execFileAsync(resolvedCommand, resolvedArgs, { return execFileAsync(resolvedCommand, resolvedArgs, {
cwd: options?.cwd, cwd: options?.cwd,
@@ -50,7 +54,7 @@ export async function execCommand(
encoding: options?.encoding ?? "utf8", encoding: options?.encoding ?? "utf8",
timeout: options?.timeout, timeout: options?.timeout,
maxBuffer: options?.maxBuffer, maxBuffer: options?.maxBuffer,
shell: isWindows, shell,
windowsHide: true, windowsHide: true,
}) as Promise<ExecCommandResult>; }) as Promise<ExecCommandResult>;
} }