fix(utils): support PowerShell as custom shellPath
This commit is contained in:
@@ -2,6 +2,10 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Support PowerShell (`powershell.exe` / `pwsh`) as a custom `shellPath`: spawn paths now pass `-NoLogo -Command` (plus `-NoProfile` under `PI_BASH_NO_LOGIN`) instead of the POSIX `-l -c` pair, which PowerShell rejected with `The term '-l' is not recognized`.
|
||||
|
||||
## [17.2.9] - 2026-08-05
|
||||
|
||||
### Added
|
||||
|
||||
@@ -50,12 +50,19 @@ function buildSpawnEnv(shell: string): Record<string, string> {
|
||||
|
||||
/**
|
||||
* Get shell args for the resolved shell.
|
||||
* cmd.exe takes `/c`; POSIX shells take `-c`, with `-l` unless
|
||||
* PI_BASH_NO_LOGIN / CLAUDE_BASH_NO_LOGIN is set.
|
||||
* cmd.exe takes `/c`; PowerShell (powershell.exe / pwsh) takes
|
||||
* `-NoLogo -Command`, with `-NoProfile` when PI_BASH_NO_LOGIN /
|
||||
* CLAUDE_BASH_NO_LOGIN is set (profile scripts are PowerShell's login-shell
|
||||
* analog); POSIX shells take `-c`, with `-l` unless the same env is set.
|
||||
*
|
||||
* Exported for tests; `env` overrides the process env gate.
|
||||
*/
|
||||
function getShellArgs(shell: string): string[] {
|
||||
export function getShellArgs(shell: string, env: Record<string, string | undefined> = $env): string[] {
|
||||
if (isCmdShell(shell)) return ["/c"];
|
||||
const noLogin = $env.PI_BASH_NO_LOGIN || $env.CLAUDE_BASH_NO_LOGIN;
|
||||
const noLogin = env.PI_BASH_NO_LOGIN || env.CLAUDE_BASH_NO_LOGIN;
|
||||
if (isPowerShell(shell)) {
|
||||
return noLogin ? ["-NoLogo", "-NoProfile", "-Command"] : ["-NoLogo", "-Command"];
|
||||
}
|
||||
return noLogin ? ["-c"] : ["-l", "-c"];
|
||||
}
|
||||
|
||||
@@ -65,6 +72,16 @@ export function isCmdShell(shell: string): boolean {
|
||||
return basename === "cmd.exe" || basename === "cmd";
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the shell is Windows PowerShell or PowerShell Core (pwsh). Spawn
|
||||
* paths must use `-Command`: passing the POSIX `-l -c` pair makes PowerShell
|
||||
* parse `-l` as the command and fail with `The term '-l' is not recognized`.
|
||||
*/
|
||||
export function isPowerShell(shell: string): boolean {
|
||||
const basename = shell.replace(/\\/g, "/").split("/").pop()?.toLowerCase();
|
||||
return basename === "powershell.exe" || basename === "powershell" || basename === "pwsh.exe" || basename === "pwsh";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get shell prefix for wrapping commands (profilers, strace, etc.).
|
||||
*/
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as fs from "node:fs";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import { getAgentDir, MAIN_CONFIG_FILENAMES } from "../src/dirs";
|
||||
import { getShellConfig, resolveWindowsShell } from "../src/procmgr";
|
||||
import { getShellArgs, getShellConfig, resolveWindowsShell } from "../src/procmgr";
|
||||
|
||||
describe("getShellConfig", () => {
|
||||
it("directs invalid custom shell paths to the canonical config file", () => {
|
||||
@@ -15,6 +15,30 @@ describe("getShellConfig", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("getShellArgs", () => {
|
||||
it("uses -Command for PowerShell shells instead of the POSIX -l -c pair", () => {
|
||||
// `powershell -l -c <cmd>` parses `-l` as the command and fails with
|
||||
// `The term '-l' is not recognized`, breaking every spawn path for a
|
||||
// shellPath pointed at PowerShell.
|
||||
expect(getShellArgs("C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", {})).toEqual([
|
||||
"-NoLogo",
|
||||
"-Command",
|
||||
]);
|
||||
expect(getShellArgs("C:\\Program Files\\PowerShell\\7\\pwsh.exe", {})).toEqual(["-NoLogo", "-Command"]);
|
||||
expect(getShellArgs("/usr/bin/pwsh", {})).toEqual(["-NoLogo", "-Command"]);
|
||||
});
|
||||
|
||||
it("maps the no-login env gate to -NoProfile for PowerShell", () => {
|
||||
expect(getShellArgs("pwsh.exe", { PI_BASH_NO_LOGIN: "1" })).toEqual(["-NoLogo", "-NoProfile", "-Command"]);
|
||||
});
|
||||
|
||||
it("keeps cmd.exe and POSIX shell args unchanged", () => {
|
||||
expect(getShellArgs("C:\\Windows\\System32\\cmd.exe", {})).toEqual(["/c"]);
|
||||
expect(getShellArgs("/bin/bash", {})).toEqual(["-l", "-c"]);
|
||||
expect(getShellArgs("/bin/bash", { PI_BASH_NO_LOGIN: "1" })).toEqual(["-c"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveWindowsShell", () => {
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user