From 086b3098b1075b9149771b00822db0f0cba31b5b Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Thu, 16 Apr 2026 17:58:12 +0700 Subject: [PATCH] fix(server): support Windows PowerShell shims in shared launcher --- packages/server/src/utils/executable.ts | 27 ++++++++++++++++++------ packages/server/src/utils/spawn.ts | 28 ++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/packages/server/src/utils/executable.ts b/packages/server/src/utils/executable.ts index e259a2171..3191699c9 100644 --- a/packages/server/src/utils/executable.ts +++ b/packages/server/src/utils/executable.ts @@ -40,6 +40,14 @@ export function isWindowsCommandScript(executablePath: string): boolean { return process.platform === "win32" && (extension === ".cmd" || extension === ".bat"); } +export function isWindowsPowerShellScript(executablePath: string): boolean { + return process.platform === "win32" && extname(executablePath).toLowerCase() === ".ps1"; +} + +export function windowsPowerShellScriptArgs(scriptPath: string, args: string[]): string[] { + return ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", scriptPath, ...args]; +} + async function probeExecutable(executablePath: string): Promise { return await new Promise((resolve) => { let settled = false; @@ -59,12 +67,19 @@ async function probeExecutable(executablePath: string): Promise { let child: ChildProcess; try { - child = spawn(executablePath, ["--version"], { - stdio: "ignore", - windowsHide: true, - // Windows batch shims (.cmd/.bat) require cmd.exe; native binaries do not. - shell: isWindowsCommandScript(executablePath), - }); + const isPowerShellScript = isWindowsPowerShellScript(executablePath); + child = spawn( + isPowerShellScript ? "powershell.exe" : executablePath, + isPowerShellScript + ? windowsPowerShellScriptArgs(executablePath, ["--version"]) + : ["--version"], + { + stdio: "ignore", + windowsHide: true, + // Windows batch shims (.cmd/.bat) require cmd.exe; native binaries do not. + shell: isWindowsCommandScript(executablePath), + }, + ); } catch { settle(false); return; diff --git a/packages/server/src/utils/spawn.ts b/packages/server/src/utils/spawn.ts index f7a0b0881..b5de30366 100644 --- a/packages/server/src/utils/spawn.ts +++ b/packages/server/src/utils/spawn.ts @@ -1,7 +1,13 @@ import { execFile, spawn, type ChildProcess, type SpawnOptions } from "node:child_process"; import { promisify } from "node:util"; -import { isWindowsCommandScript, quoteWindowsArgument, quoteWindowsCommand } from "./executable.js"; +import { + isWindowsCommandScript, + isWindowsPowerShellScript, + quoteWindowsArgument, + quoteWindowsCommand, + windowsPowerShellScriptArgs, +} from "./executable.js"; const execFileAsync = promisify(execFile); @@ -24,6 +30,14 @@ export function spawnProcess( options?: SpawnOptions, ): ChildProcess { const isWindows = process.platform === "win32"; + if (isWindowsPowerShellScript(command)) { + return spawn("powershell.exe", windowsPowerShellScriptArgs(command, args), { + ...options, + shell: false, + windowsHide: true, + }); + } + const shell = isWindowsCommandScript(command) ? true : (options?.shell ?? isWindows); const shouldQuoteForShell = isWindows && shell !== false; @@ -43,6 +57,18 @@ export async function execCommand( options?: ExecCommandOptions, ): Promise { const isWindows = process.platform === "win32"; + if (isWindowsPowerShellScript(command)) { + return execFileAsync("powershell.exe", windowsPowerShellScriptArgs(command, args), { + cwd: options?.cwd, + env: options?.env, + encoding: options?.encoding ?? "utf8", + timeout: options?.timeout, + maxBuffer: options?.maxBuffer, + shell: false, + windowsHide: true, + }) as Promise; + } + const shell = isWindowsCommandScript(command) ? true : isWindows; const shouldQuoteForShell = isWindows && shell !== false; const resolvedCommand = shouldQuoteForShell ? quoteWindowsCommand(command) : command;