Commit Graph
3 Commits
Author SHA1 Message Date
Zoltan Kochan 3c217a9deb fix(exec): re-invoke an entry script only when it belongs to the running pnpm (#15408)
`resolvePnpmSelfCommand` decided whether `process.argv[1]` was pnpm from
its basename and the manifest above it, rejecting a script only when
another package claimed it as a bin. A script that no package claims, a
project's own `scripts/pnpm.mjs` among them, still passed.

pnpm's own code knows where it lives. The entry script is now accepted
only when its real path is the running module itself, as when a bundle
is run directly or copied elsewhere, or lies in the same package as that
module, as with `bin/pnpm.mjs` loading `dist/pnpm.mjs`. The nearest
`package.json` marks the package; the published `pnpm` and `@pnpm/exe`
tarballs carry only the one at their root. The basename set stays to keep
out hosts such as Jest, and a `pnpx` entry maps to the `pnpm.mjs` beside
it, since `pnpx` prepends `dlx` to `process.argv`. An entry that cannot
be resolved now
falls back to the pnpm on PATH, which is safe because the bundle-copy
case no longer depends on failing open.

The resolver moves to `@pnpm/cli.meta`, next to
`detectIfCurrentPkgIsExecutable`, so `npm_execpath` can share it without
`@pnpm/exec.npm-lifecycle` depending on the CLI runner. `npm_execpath`
previously took `process.argv[1]` unchecked, so a script run under
`pnpx` got `pnpx.mjs` and `$npm_execpath install` became `dlx install`.
It now gets the running pnpm's entry or executable, or the pnpm on
pnpm's own PATH when there is none, matching the runner's fallback. The `process.pkg` branch it
replaces was dead since `@pnpm/exe` moved to a Node.js SEA.

The lookup is cached for the process, since lifecycle scripts ask once
per script and neither `process.argv[1]` nor the module moves.

On Windows, `@pnpm/exe` installs `pnpx.exe` and `pnx.exe` as hardlinks
of the pnpm binary, which runs as `pnpm dlx` when launched through
either name. Re-invoking the current executable under one of them
prepended `dlx` again. Both versions now re-invoke the `pnpm` executable
beside the alias: v11 in `resolvePnpmSelfCommand` and `npm_execpath`,
and v12 through `pnpm_executor::current_pnpm_exe()` in `npm_execpath`,
`doctor`, the `verifyDepsBeforeRun` install, the pipeline agent and the
git-dependency build.

The `npm_execpath` fallback is resolved to an absolute path before a
script's PATH puts `node_modules/.bin` first, so a dependency's `pnpm`
bin cannot stand in for it.
2026-09-23 21:50:35 +02:00
Zoltan Kochan 626f015afa fix(exec): confirm the entry script is pnpm before re-invoking it (#14889)
`runPnpmCli` decided whether to re-invoke `process.argv[1]` from its
basename alone, so a script that merely carried one of pnpm's bin names
was run as if it were pnpm. The names cannot be dropped -- a Jest host's
`process.argv[1]` must not be re-executed, and `pnpx` must not be, since
that entry rewrites `process.argv` to prepend `dlx` -- so the basename
set stays as a pre-filter and the manifest above the script now decides.

The check resolves symlinks first. `process.argv[1]` keeps the name the
process was launched through, so an npm-installed pnpm arrives as
`node_modules/.bin/pn`, whose nearest manifest is the consuming
project's rather than pnpm's. Walking up without resolving would reject
a real pnpm and fall back to the PATH lookup this exists to avoid.

Only a manifest that names some other package rejects the script. An
entry with no manifest above it, a copied bundle among them, is still
pnpm as far as anything here can tell, so every filesystem failure
leaves the script trusted. The walk up only moves past a missing
`package.json`; one that exists but cannot be read ends it.

`pnpm doctor` asked the same question -- what re-invokes the pnpm
running now -- and answered it with no check at all. It now shares
`resolvePnpmSelfCommand`. Its fallback changes with that: a process
with no usable entry script gets the pnpm on PATH rather than a bare
`node`, which was never a runnable command.

`pnpm setup`'s `getExecPath` is left alone. It asks where this
executable lives so it can install from that path, not what to run.

pnpm v12 resolves its own path with `std::env::current_exe()` and has
no name-based guess to correct.

Closes pnpm/pnpm#14888
2026-09-23 20:20:51 +02:00
Ayush SinghandZoltan Kochan eb5ad9268a fix(exec): re-use process.argv[1] entry script in runPnpmCli (#14839)
Re-use process.argv[1] script when spawning pnpm CLI subprocesses via Node.js so that the invoked pnpm version is preserved (Fixes #14829).

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-09-14 13:19:13 +02:00