Commit Graph
4 Commits
Author SHA1 Message Date
Zoltan Kochan 266cbd9615 fix(run): pass script arguments through cmd unchanged on Windows (#16262)
`pnpm run` appended the arguments after the script name with JSON string
quoting when cmd parsed the script. cmd then expanded `%VAR%`, and the
target program's C runtime received doubled backslashes and `\t` for a tab.

Both CLIs now quote each argument the way npm does: quoted for the C
runtime, then with every cmd metacharacter escaped with `^`. When the
script's first word resolves to a .cmd or .bat file on the script's PATH,
such as a node_modules/.bin shim, the arguments are escaped a second time
because the batch file parses them again. The command that receives the
arguments is the last one of a chain, and the lookup searches the script
directory before PATH, as cmd does. Line breaks, which cmd cannot carry,
are still passed as the two characters `\n` / `\r`.

pnpm prints the script with its arguments quoted the POSIX way on every
platform, so the `^` escapes stay out of the `$` line, the lifecycle log
event and error messages. npm_lifecycle_script keeps the command that
runs.

Closes pnpm/pnpm#16257
2026-09-27 13:57:54 +02:00
16f56b91be fix: honor scriptShell when shellEmulator is set (#15865)
A configured scriptShell is spawned for pnpm run and lifecycle scripts even when shellEmulator is also enabled. The built-in emulator does not implement bash export or relative script paths the way Git Bash does, and the reported config sets both options, so the emulator was hiding the shell the user configured.

shellEmulator still selects the built-in shell when scriptShell is unset. A blank scriptShell is unset on both runners, so the fallback shell and the extra-argument quoting match. The shell is still selected first, so a .bat or .cmd scriptShell is rejected before anything runs.

Extra arguments are JSON-quoted only when cmd will parse the script (windowsVerbatimArguments). A non-cmd scriptShell, including a path such as C:\Program Files\Git\bin\bash.exe, gets POSIX quoting and is spawned without verbatim arguments, so the path stays one argument.

PATH, bin lookup, and auth are not changed.

Closes pnpm/pnpm#14719

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Zoltan Kochan <z@kochan.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 11:23:16 +02:00
79c2bfc352 fix: exit with the script's status after Ctrl+C (#15854)
dash holds a terminal SIGINT until the foreground command exits, then
dies from that signal even when the command already returned a status.
`pnpm run` treated that shell death as a lifecycle failure and lost the
script's code.

A Bourne shell now runs the script behind a trap that re-raises SIGINT
when the command itself died from it (status 130) and otherwise lets the
script continue, as bash does. A deliberate exit of 130 is
indistinguishable from that report, so it is re-raised too. A second
interrupt always re-raises, so a loop of builtins can still be stopped.

Closes pnpm/pnpm#9945

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Zoltan Kochan <z@kochan.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 15:40:46 +02:00
e8cfd554dc fix: pass /d /s /c to a cmd.exe scriptShell on Windows (#15509)
A scriptShell pointing at cmd.exe was spawned with `-c`, like any custom
shell. cmd.exe ignores `-c` and takes the first `/c` on its command line as
its switch, so a script such as `node install/can-compile` ran `an-compile`.

When the configured scriptShell's basename is `cmd` or `cmd.exe` on Windows,
pass `/d /s /c` and the verbatim script, as the default ComSpec path already
does. Applied to both the v11 lifecycle runner and the v12 executor.

Closes pnpm/pnpm#7181

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 11:15:18 +02:00