* fix(installation): skip pnpm exe when no Node.js is on PATH
When pnpm is installed as @pnpm/exe (a Single Executable Application
that bundles Node.js into the pnpm binary) and the user has no separate
Node.js on PATH, `which('node')` fails and `getNodeExecPath` used to
fall back to `process.execPath` - which in @pnpm/exe is the pnpm binary
itself, not a Node binary. That path got baked into generated global
bin shims via nodeExecPath, so running any globally-installed CLI
invoked pnpm with the target script as its first positional arg,
producing `ERR_PNPM_NO_IMPORTER_MANIFEST_FOUND` from the current
working directory.
Detect the @pnpm/exe case via `detectIfCurrentPkgIsExecutable` and
return undefined from the fallback so the shim emits a plain
`exec node <target>` instead.
Closes#11291
Refs #4645
* Update pkg-manager/plugin-commands-installation/src/nodeExecPath.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Allow skipping the lockfile settings validation during frozen and
preferFrozenLockfile installs. When enabled, pnpm proceeds as if the
lockfile settings are up to date.
When enabled, peer dependency suffixes use version-only identifiers
(name@version) instead of full dep paths, eliminating nested suffixes
like (foo@1.0.0(bar@2.0.0)). Transitive peers are still tracked but
identified by version instead of full dep path.
Backport of #11071 to v10.
Closes#11070
Global Virtual Store (#10939)
Content-verified skip in GVS mode, tolerate EPERM during
bin creation on Windows, handle EPERM in DLX cache symlink.
(cherry picked from commit 62f760ec3d)
Cherry-pick of 2b14c742e from main, adapted for v10:
- Use CJS module type for @pnpm/yaml.document-sync
- Use ramda/src/equals import style (v10 CJS)
- Remove GLOBAL_CONFIG_YAML_FILENAME (v11-only)
- Replace write-yaml-file with yaml + write-file-atomic + patchDocument
Co-Authored-By: Brandon Cheng <gluxon@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use Object.keys() instead of Map.keys() for Record<string, string> filenames
- Use tempy.directory() instead of temporaryDirectory() (v10 CJS style)
- Remove storeDir from ClientOptions (not in v10 API)
- Add missing WorkspaceState imports and mocks in test
- Fix lint: use commas in type literals, fix require import style
- Fix spellcheck in changeset
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When lockfile-include-tarball-url is explicitly set to false, tarball URLs
are now always excluded from the lockfile. Previously, packages hosted under
non-standard tarball URLs would still have their tarball field written to the
lockfile even when the setting was false, causing flaky and inconsistent
behavior across environments.
The fix makes the option tri-state internally:
- true: always include tarball URLs
- false: never include tarball URLs
- undefined (not set): use the existing heuristic that includes tarball URLs
only for packages with non-standard registry URLs
close#6667
When CI=true, pnpm automatically enables frozen-lockfile mode. Previously,
this could only be overridden via .npmrc files or CLI flags because the
code checked rawLocalConfig (which excludes env vars and hook changes).
Now checks the fully resolved config values (frozenLockfile and
preferFrozenLockfile) instead of rawLocalConfig, allowing:
- Environment variables (pnpm_config_frozen_lockfile=false)
- updateConfig hook in .pnpmfile.cjs
- .npmrc files (already worked)
- CLI flags (already worked)
Fixes#9861
fs.realpathSync uses a JS-only implementation that only resolves symlinks,
not Windows 8.3 short names (e.g., RUNNER~1). Switch to fs.promises.realpath
which uses the native uv_fs_realpath (GetFinalPathNameByHandleW on Windows)
to properly resolve 8.3 short paths to their long form.
On Windows, temporaryDirectory() may return 8.3 short paths (e.g.,
RUNNER~1) but getBinNodePaths resolves via fs.realpath, returning long
paths (e.g., runneradmin). Use realpathSync to normalize expected paths.
Fixed "input line too long" error on Windows when running lifecycle scripts with the
global virtual store enabled. The `NODE_PATH` in command shims no longer includes all
paths from `Module._nodeModulePaths()`. Instead, it includes only the package's
bundled dependencies directory (e.g., `.pnpm/pkg@version/node_modules/pkg/node_modules`),
the package's sibling dependencies directory (e.g., `.pnpm/pkg@version/node_modules`),
and the hoisted `node_modules` directory. These paths are needed so that tools like
`import-local` (used by jest, eslint, etc.) which resolve from CWD can find the correct
dependency versions.
* fix: respect peer dep range in hoistPeers when preferred versions exist
Previously, hoistPeers used semver.maxSatisfying(versions, '*') which
picked the highest preferred version from the lockfile regardless of the
peer dep range. This caused overrides that narrow a peer dep range to be
ignored when a stale version existed in the lockfile.
Now hoistPeers first tries semver.maxSatisfying(versions, range) to find
a preferred version that satisfies the actual peer dep range. If none
satisfies it and autoInstallPeers is enabled, it falls back to the range
itself so pnpm resolves a matching version from the registry.
* fix: only fall back to exact-version range for overrides, handle workspace: protocol
- When no preferred version satisfies the peer dep range, only use the
range directly if it is an exact version (e.g. "4.3.0" from an override).
For semver ranges (e.g. "1", "^2.0.0"), fall back to the old behavior
of picking the highest preferred version for deduplication.
- Guard against workspace: protocol ranges that would cause
semver.maxSatisfying to throw.
- Add unit tests for hoisting deduplication and workspace: ranges.
* fix: only apply range-constrained peer selection for exact versions
The previous approach used semver.maxSatisfying(versions, range) for all
peer dep ranges, which broke aliased-dependency deduplication — e.g. when
three aliases of @pnpm.e2e/peer-c existed at 1.0.0, 1.0.1, and 2.0.0,
range ^1.0.0 would pick 1.0.1 instead of 2.0.0.
Now the range-aware logic only activates when the range is an exact
version (semver.valid), which is the override case (e.g. "4.3.0").
Regular semver ranges fall back to picking the highest preferred version.
This fixes an issue where pnpm fetch would fail in Docker builds when
local directory dependencies (file: protocol) were not available.
The fix adds an ignoreLocalPackages option that is passed from the fetch
command to skip local dependencies during graph building, since pnpm
fetch only downloads packages from the registry and doesn't need local
packages that won't be available in Docker builds.
close#10460
* refactor: factor out a `getRealNameAndSpec` function
* test: `pnpm add` does not modify existing catalog entries
* fix: resolve preferred version without mutating bare specifier
close#9759
* refactor: factor out a `getRealNameAndSpec` function
* fix: handle npm aliases without a bare specifier in `unwrapPackageName`
* Fix formatting in unwrapPackageName.ts