`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.
`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.
Closespnpm/pnpm#14888
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>