`path.join(lockfileDir, resolution.directory)` mangles absolute cross-drive
Windows paths by literally concatenating them (`path.join('D:\\foo',
'C:\\bar')` → `'D:\\foo\\C:\\bar'`). Switch to `path.resolve` so stored
absolute paths are used as-is.
This surfaced as an ENOENT during `pnpm setup` in CI when `PNPM_HOME` and
the OS temp dir (containing the extracted v11 tarball that setup installs
via `pnpm add -g file:<dir>`) were on different drives.
* refactor: rename @pnpm/exe platform packages to @pnpm/exe.<platform>-<arch>[-musl]
Aligns pnpm's own published platform artifacts with the one naming
convention the rest of the codebase already uses (`process.platform`
values plus an explicit `-musl` libc suffix), matching what `pnpm
pack-app`, `pnpm add --os/--cpu/--libc`, `supportedArchitectures.os`,
and Node.js tarball names all already settled on.
Package renames:
- @pnpm/linux-x64 -> @pnpm/exe.linux-x64
- @pnpm/linux-arm64 -> @pnpm/exe.linux-arm64
- @pnpm/linuxstatic-x64 -> @pnpm/exe.linux-x64-musl (new dir)
- @pnpm/linuxstatic-arm64 -> @pnpm/exe.linux-arm64-musl
- @pnpm/macos-x64 -> @pnpm/exe.darwin-x64
- @pnpm/macos-arm64 -> @pnpm/exe.darwin-arm64
- @pnpm/win-x64 -> @pnpm/exe.win32-x64
- @pnpm/win-arm64 -> @pnpm/exe.win32-arm64
GitHub release asset names follow suit (`pnpm-linuxstatic-x64.tar.gz`
-> `pnpm-linux-x64-musl.tar.gz`, `pnpm-macos-*` -> `pnpm-darwin-*`,
`pnpm-win-*` -> `pnpm-win32-*`). Internal artifact directories under
`pnpm/artifacts/` renamed to match, which drops the awkward mixed
naming between target and directory.
The umbrella package `@pnpm/exe` keeps its name so that `pnpm
self-update` from v10 and any `npm i -g @pnpm/exe` scripts continue to
resolve. Platform children can be renamed freely because npm/pnpm
filter optional deps by each child's `os`/`cpu`/`libc` manifest
fields, not by package names.
Also updates:
- `@pnpm/exe`'s `setup.js` (preinstall) and the self-updater's
`linkExePlatformBinary` to look up the platform package by the new
scheme, using `detect-libc` to append `-musl` on musl Linux hosts.
- `.meta-updater` optional-dependency list for @pnpm/exe.
- `copy-artifacts.ts` target list and Windows detection prefix.
- cspell wordlist (drops `linuxstatic`; it's no longer used anywhere).
Final transition publishes of the old package names (pointing at the
new ones so direct pins keep resolving) are a release-engineering step
handled separately.
Refs #11314.
* chore: keep "linuxstatic" in cspell wordlist for changeset references
* test(pack-app rename): cover the musl branch of platform-package-name lookup
Copilot flagged that the musl -> -musl suffix logic in setup.js's preinstall
and self-updater's linkExePlatformBinary had no regression coverage. Extract
the name-computation from both into small pure helpers and unit-test all
four matrix cases (linux+musl, linux+glibc, darwin, win32) plus the
win32 ia32->x86 arch normalization:
- pnpm/artifacts/exe/platform-pkg-name.js exposes `exePlatformPkgName`
(returns `@pnpm/exe.<platform>-<arch>[-musl]`). setup.js imports it
instead of inlining the logic; the new setup.test.ts block covers the
four-case matrix without having to mock detect-libc or patch
process.platform.
- engine/pm/commands/src/self-updater/installPnpm.ts exports a new
`exePlatformPkgDirName` returning `exe.<platform>-<arch>[-musl]` (the
scope-local dir). linkExePlatformBinary calls it; the new
selfUpdate.test.ts block covers the same matrix.
Both helpers are deliberately pure so the non-musl CI host can still
exercise the musl code path.
* fix: give each runtime variant its own global virtual store entry
When a runtime package (e.g. node@runtime:X.Y.Z) uses a variations
resolution, createFullPkgId() in @pnpm/deps.graph-hasher was hashing
the whole VariationsResolution — the same hash on every host — so the
global virtual store path collided between variants. Whichever variant
installed first won, and a later `pnpm add --libc=musl node@runtime:<v>`
silently reused the cached glibc (or macOS/Windows) binary.
The fix threads supportedArchitectures down to createFullPkgId so the
selected variant's integrity is used as the package fingerprint. Two
related cleanups land with it:
- Extract the platform-variant selection logic to @pnpm/resolving.resolver-base
as selectPlatformVariant/resolvePlatformSelector. The helper's libc
match also required a fix: a variant with no libc is the "default"
build, and a request for a non-default libc (e.g. musl) must require
an exact match so the default variant doesn't silently win.
- @pnpm/installing.package-requester's findResolution now delegates to
the shared helper, and the new supportedArchitectures param is plumbed
through calcDepState / calcGraphNodeHash / iterateHashedGraphNodes /
lockfileToDepGraph and their callers in deps-resolver, deps-restorer,
deps-installer, graph-builder, and building.after-install.
* feat: add pnpm build-sea command for building Node.js SEA executables
Adds `pnpm build-sea` under @pnpm/releasing.commands. Takes a CommonJS
entry file and a set of target triplets (linux-x64, linux-x64-musl,
linux-arm64, linux-arm64-musl, macos-x64, macos-arm64, win-x64,
win-arm64) and produces a standalone executable per target under
dist-sea/<target>/.
Each target's Node.js runtime is fetched via `pnpm add node@runtime:<v>
--os=<os> --cpu=<arch> --libc=<libc>` into $PNPM_HOME/build-sea/<target>-<v>/
so binaries are hardlinked from the global content-addressable store and
`pnpm store prune` can reclaim them.
Requires Node.js v25.5+ to perform the --build-sea injection. If the
running Node is older, a v25 binary is downloaded and used as the builder
automatically. macOS outputs are ad-hoc signed with codesign (on macOS)
or ldid (when cross-compiling from Linux), which is required because SEA
injection invalidates the binary's existing signature.
* fix(build-sea): reject malformed --target, --output-name and use mkdtemp for config
Addresses Copilot review feedback on the build-sea command:
- parseTarget() previously destructured the target string, silently
accepting extra `-` segments. Inputs like `linux-x64-musl-../../outside`
would pass validation and flow into path.join. Validation is now done
with a strict anchored regex.
- --output-name was passed into path.join() without sanitization, so a
caller could escape the output directory with path separators or `..`.
validateOutputName() now rejects anything that isn't a plain basename.
- The per-target SEA config file was written to a predictable path under
os.tmpdir() (derived from the target name and Date.now()), which is
unsafe on multi-user systems. It now lives inside a fresh mkdtemp()
directory and is opened with the exclusive "wx" flag.
- New test cases cover extra-segment targets, uppercase/whitespace
variants, and the full matrix of invalid --output-name inputs.
* rename: build-sea → pack-app
`build-sea` required knowing what a SEA is. `pack-app` is self-describing,
doesn't collide with pnpm's existing `bin` concept, and parallels the
existing `pack` command.
- Command name: build-sea → pack-app
- Default output dir: dist-sea → dist-app
- Error codes: PACK_APP_* (was BUILD_SEA_*)
- Export/type: packApp / PackAppOptions (was buildSea / BuildSeaOptions)
- Install cache dir: $PNPM_HOME/pack-app (was $PNPM_HOME/build-sea)
The Node.js `--build-sea` flag name itself is unchanged — that's a
Node.js feature and outside this project's naming.
* fix(pack-app): reject directory entries, pin builder to >=25.5, refuse macOS target on Windows
Addresses Copilot review feedback on the pack-app command:
- entry validation now rejects non-file paths (directories, symlinks to
non-files) with a dedicated PACK_APP_ENTRY_NOT_FILE instead of
surfacing a less actionable error later in the SEA build.
- DEFAULT_BUILDER_SPEC was the bare major ("25"), which would satisfy
with 25.0.x if that version is still present — those point releases
predate --build-sea support. Tightened to ">=25.5.0 <26.0.0" so the
download is guaranteed to support the flag without ever crossing a
major.
- adHocSignMacBinary() silently skipped re-signing on Windows hosts.
Now throws PACK_APP_MACOS_SIGN_UNSUPPORTED_HOST with a hint to build
the target on macOS/Linux or re-sign manually.
- resolvePlatformSelector() JSDoc now matches what the code actually
does (picks the first entry when it is not "current"; later entries
are ignored).
- New test case covers the directory-as-entry rejection.
* refactor(pack-app): switch target OS names to process.platform constants
Previously `pack-app` accepted `macos-*` / `win-*` as the OS portion of a
target triplet and translated them to `darwin` / `win32` internally. The
translation layer made the CLI surface inconsistent with the values that
`pnpm add --os=…` and `supportedArchitectures.os` already use, and added
a small footgun (e.g. users setting `supportedArchitectures: { os: [darwin] }`
but typing `macos-arm64` for pack-app).
The supported target OS set is now `linux | darwin | win32`, matching
`process.platform`. Old inputs like `macos-arm64` or `win-x64` now fail
validation with a clear error pointing to the new naming. The internal
parseTarget helper drops its TARGET_OS_MAP lookup entirely.
This is a change to an unreleased command so there is no back-compat
concern. pnpm's own artifact directory names (`pnpm/artifacts/macos-*/`,
`pnpm/artifacts/win-*/`) are an internal implementation detail and are
not affected by this change.
* feat(pack-app): read defaults from pnpm.app in package.json
Every pack-app flag (--entry, --target, --node-version, --output-dir,
--output-name) can now be preconfigured in the project's package.json
under a new "pnpm.app" object:
{
"name": "my-cli",
"pnpm": {
"app": {
"entry": "dist/index.cjs",
"targets": ["linux-x64", "darwin-arm64", "win32-x64"],
"nodeVersion": "25",
"outputDir": "release",
"outputName": "my-cli"
}
}
}
CLI flags always win. --target replaces the configured list rather than
appending, so a user can narrow the default set at the command line.
The config loader is strict: unknown keys under pnpm.app and any
type-mismatched values throw PACK_APP_INVALID_CONFIG so mistakes surface
at invocation time instead of silently being ignored.
Chose pnpm.app over pnpm.packApp because it's the shorter, cleaner
namespace for anything related to the app bundle (future sibling
commands like run-app / deploy-app could share the same object without
a naming clash). Chose package.json over pnpm-workspace.yaml because
the config is inherently per-project, whereas pnpm-workspace.yaml is
workspace-root-only.
* fix(pack-app): deterministic libc selection and stricter output-name validation
Addresses Copilot review feedback:
- ensureNodeRuntime() now always passes an explicit --libc for linux
targets. Without a suffix, linux-x64 and linux-arm64 default to
--libc=glibc instead of letting the user's supportedArchitectures.libc
config or the host's detected libc decide the variant. The install
cache directory mirrors this, so glibc and musl variants are always
distinct (linux-x64-glibc vs linux-x64-musl).
- resolveBuilderBinary() now pins the host libc when downloading a
builder Node on Linux. A user whose config sets supportedArchitectures.libc
to musl no longer ends up with a musl Node that the glibc host cannot
execute.
- validateOutputName() rejects Windows-invalid filename characters
(<>:"|?* and NUL), Windows reserved device names (CON, NUL, COM1, etc.),
and names ending in a dot or space — problems surface at invocation
time rather than during writeFile(outputFile, ...) on Windows.
- lockfileToDepGraph variants tests no longer derive the "host"
variant from process.platform/process.arch; they always pass an
explicit supportedArchitectures selector so the expectations hold on
any CI host (including Alpine/musl).
* chore: add "toctou" to cspell wordlist
`TOCTOU` (time-of-check-to-time-of-use) is the standard term for the
race-condition class the pack-app SEA-config comment describes. Adding
it to the wordlist unblocks the Lint CI step.
* fix: lint
* fix(sbom): resolve licenses for git-sourced dependencies
`readPackageFileMap` did not handle `type: 'git'` resolutions, causing
`pnpm sbom` to emit NOASSERTION and `pnpm licenses` to throw for any
dependency installed from a git URL.
Closes#11260
* fix: add missing store.cafs devDep, test tsconfigs, and size field
- Add @pnpm/store.cafs devDependency and tsconfig reference to
license-scanner so CI typecheck resolves the PackageFilesIndex import
- Add test/tsconfig.json to pkg-finder so CI typechecks the new tests
- Add required `size` field to PackageFileInfo test fixtures
* fix: replace spellcheck-failing test strings
* fix: use spellcheck-safe integrity string in test
* style: fix import sort in pkg-finder test
* fix(sbom): use packageIdFromSnapshot to match store index keys
The SBOM used `snapshot.id ?? depPath` as the package ID, which
includes the package name prefix (e.g. `left-pad@git+https://...`).
The store index stores git packages under just the git URL without
the name prefix. Use `packageIdFromSnapshot` which strips the prefix,
matching how the licenses command already does it.
Also fixes test store keys to match the real installer layout so the
mismatch would have been caught by tests.
* refactor: move git resolution check after tarball check
Tarball resolutions are more common than type: 'git', so check them
first. Per review feedback from @zkochan.
- `pnpm sbom` now recognizes the deprecated `licenses` array and falls back to scanning on-disk `LICENSE` files, matching the resolution already used by `pnpm licenses`. Fixes packages like `busboy`, `streamsearch`, `limiter` being reported as `NOASSERTION`.
- Root component scans the project directory for LICENSE files too, matching transitive-dep behavior.
- Shared license resolution lives in a new `@pnpm/deps.compliance.license-resolver` package, with `parseLicenseFromManifest`, `resolveLicense` (store-file-map), and `resolveLicenseFromDir` (on-disk scan).
- LICENSE-file-detected names are filtered against `SPDX_LICENSE_IDS` before being emitted to SBOM — long-form names like "Eclipse Public License 1.0" fall through to `NOASSERTION` rather than producing non-compliant SPDX. Manifest-declared licenses pass through untouched.
- When both `license` and `licenses` are set, the modern `license` wins (previously `pnpm licenses` preferred `licenses`).
- Close#11248.
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
- Suppress the `Cannot use both "packageManager" and "devEngines.packageManager" in package.json. "packageManager" will be ignored` warning only when both fields specify the exact same package manager name and the exact same version string. Any other divergence (different name, range vs. exact version, prefixed versions like `v1.2.3`, etc.) still warns.
- Lets projects keep both fields during migration (e.g. so v10 installs still auto-switch via `packageManager`, while v11 uses `devEngines.packageManager` and `npm install` still errors) without a noisy warning — as long as the two values are kept in sync.
Closes#11301
* fix: support explicit versions and --no-commit-hooks / --no-git-tag-version in `pnpm version`
Registers options under their canonical names so nopt correctly parses the
`--no-*` variants, accepts an explicit semver argument alongside bump types,
and creates a git commit + annotated tag for the bump (honoring
`--no-git-tag-version`, `--no-commit-hooks`, `--sign-git-tag`, `--message`,
and `--tag-version-prefix`). Also fixes `--no-git-checks` which was parsed
incorrectly. Closes#11271.
* chore: add gpgsign and newversion to cspell; set gpg sign config in version test
* fix: address Copilot review feedback on pnpm version
* fix: skip git commit and tag in recursive version runs
* fix: address Copilot review feedback on pnpm version
* fix: preserve pnpm 10 peer suffix encoding for linked paths
The filenamify upgrade from v4 to v7 changed the peer-suffix "version"
token for linked dependency paths: `../packages/b` became `+packages+b`
instead of `packages+b`, causing lockfile churn for workspaces with
packages linked from outside the workspace root.
Replace the filenamify call with a small inline encoder that reproduces
v4's output for link paths, and drop the now-unused dependency.
Closes#11272.
* chore: avoid cspell-flagged word in peer suffix comment
* test: cover linkPathToPeerVersion and clarify its lossy encoding
Address Copilot review feedback on #11297:
- Correct the comment — any leading run of `.` is dropped, not just
`./` and `../` segments (so `.hidden/pkg` becomes `hidden+pkg`).
- Export the helper and add a focused test that pins the exact token
output for link paths, so future lockfile-breaking regressions get
caught by the test suite.
* refactor: extract linkPathToPeerVersion into its own file
In --global mode, globalAdd passes workspaceDir to approve-builds so it can
update the global pnpm-workspace.yaml. approve-builds then forwarded that
workspaceDir into install.handler, which (with workspacePackagePatterns
undefined) recursively discovered sibling install dirs as workspace projects
and failed the frozen-lockfile check on stale @pnpm/exe install dirs.
Skips the minimumReleaseAge maturity check when the registry metadata
lacks the "time" field, instead of throwing ERR_PNPM_MISSING_TIME.
Defaults to true, and prints a warning once per affected package.
* feat: skip lockfile writes for legacy packageManager field
When pnpm is pinned via the `packageManager` field in `package.json`, the
resolved pnpm integrity info is no longer written to `pnpm-lock.yaml`
unless the pinned version is pnpm v12 or newer. `devEngines.packageManager`
still populates and reuses `packageManagerDependencies` as before. This
keeps the v10 -> v11 transition quiet by avoiding unrelated lockfile
churn for projects that pin pnpm the legacy way.
* fix: address Copilot review and CI failure
- Update `configurationalDependencies.test.ts` to assert the new behavior:
the `packageManager` field no longer writes pnpm resolution info to the
env lockfile while config dependencies still are.
- Fast-path in `switchCliVersion`: when the lockfile is not persisted and
the running CLI already matches `pm.version`, skip store access and
integrity resolution entirely.
- Clarify the `resolvePackageManagerIntegrities` docstring to describe
the conditional `save` behavior.
* test: add unit tests for shouldPersistLockfile
Extract the decision logic for persisting pnpm resolution info to the env
lockfile into a dedicated helper so the branches — devEngines source,
legacy `packageManager` field with v11 or older, v12+, and invalid/missing
version — can all be covered without needing an actual pnpm v12 tarball
on the registry.
* feat: pnpm init writes devEngines.packageManager field
Replaces the flat "packageManager" field with a "devEngines.packageManager"
entry, using a caret range and onFail: "download" so the declared pnpm
version auto-updates on install.
* fix: update workspace/commands/src/init.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* feat: add runtimeOnFail setting
Adds a `runtimeOnFail` config setting ('ignore' | 'warn' | 'error' |
'download') that overrides the `onFail` field on `devEngines.runtime`
and `engines.runtime` in the root project's package.json. This makes
it possible to opt into (or out of) runtime auto-download without
changing the project manifest.
* fix: skip runtime download when version is missing
Without a version, convertEnginesRuntimeToDependencies would write
`runtime:undefined` into the manifest. Warn and skip instead.
* feat: apply runtimeOnFail override during install
The config reader override only mutates the context's rootProjectManifest,
but installDeps reads the manifest fresh via tryReadProjectManifest and
findWorkspaceProjects. Apply the override there too so `runtimeOnFail`
actually affects what gets installed. Adds an e2e test covering both
download and ignore overrides through the real CLI bundle.
This PR implements native pnpm commands for starring and unstarring packages, listing stars, and finding the current user (whoami). It follows the project standards and provides fallbacks for various registry API versions.
* feat!: remove managePackageManagerVersions / packageManagerStrict / packageManagerStrictVersion
These three settings existed only to derive the `onFail` behavior for
the legacy `packageManager` field. The `pmOnFail` setting introduced
in #11275 subsumes all three — it directly sets `onFail` for both
`packageManager` and `devEngines.packageManager`.
Legacy `packageManager` now defaults to `onFail: 'download'` when no
override is set. `COREPACK_ENABLE_STRICT` is no longer read (it only
gated `packageManagerStrict`); `pmOnFail` is the replacement.
Also drops pass-through `packageManagerStrict*` option fields from
cli.utils / workspace.projects-reader (they were unused) and the
unused `managePackageManagerVersions` Pick in engine.pm.commands'
`SelfUpdateCommandOptions`.
* fix: use kebab-case setting name in BAD_PM_VERSION hint
Copilot review feedback: user-facing error hints for configuration keys
conventionally use the kebab-case form that matches both the CLI flag
(`--pm-on-fail`) and the `.npmrc` key, consistent with the prior hint
text that referenced `package-manager-strict`. The `pnpm-workspace.yaml`
field (`pmOnFail`) is camelCase but that mapping is documented
elsewhere.
* Revert "fix: use kebab-case setting name in BAD_PM_VERSION hint"
This reverts commit e03c29b17. pnpm-workspace.yaml uses camelCase
(`pmOnFail`) — the primary config location for pnpm 11 — so the
hint keeps the camelCase form. The CLI flag is already shown
alongside.
* feat(config): make dlx inherit security and trust policy settings from local config
Previously, `pnpm dlx` and `pnpm create` only inherited auth/registry
settings from the local project config, ignoring all other settings.
This meant security policy settings like `minimumReleaseAge` and
`trustPolicy` configured in a project's `pnpm-workspace.yaml` were
silently dropped.
Now these commands inherit two categories of local settings:
1. Registry & auth (existing) — needed to reach the same package sources
2. Security & trust policy (new) — settings that gate what is allowed
to be downloaded, reflecting the org's security posture
Project-structural settings (hoisting, linking, workspace layout, etc.)
remain correctly excluded.
Closes#11183https://claude.ai/code/session_01NumMLsTvswMVJpbWp3YJrH
* refactor(config): rename auth.ts to localConfig.ts and clean up tests
Addresses review feedback:
- Rename auth.ts / auth.test.ts to localConfig.ts / localConfig.test.ts
to reflect the broader scope (auth + security/trust policy + npmrc utils)
- Remove unnecessary `as any` casts from tests; the types already work
- Consolidate individual expect() assertions into toMatchObject
https://claude.ai/code/session_01NumMLsTvswMVJpbWp3YJrH
* fix(config): sort imports and exports after rename
Fixes simple-import-sort/imports and simple-import-sort/exports lint
errors introduced when localConfig.js replaced auth.js; the previous
position was correct for auth.* but not for localConfig.*.
https://claude.ai/code/session_01NumMLsTvswMVJpbWp3YJrH
* refactor(config): remove dead RAW_POLICY_CFG_KEYS handling
Policy keys (minimum-release-age*, trust-policy*) are filtered out of
.npmrc by isNpmrcReadableKey, so they can never appear in authConfig.
The RAW_POLICY_CFG_KEYS / isRawPolicyCfgKey / pickRawDlxConfig branch
for those keys was unreachable in production.
inheritDlxConfig now uses pickRawAuthConfig directly for the raw config
pick. The test assertion that placed minimum-release-age in authConfig
(an impossible state) is also dropped.
https://claude.ai/code/session_01NumMLsTvswMVJpbWp3YJrH
* test(dlx): respect minimumReleaseAge from pnpm-workspace.yaml
Integration test for #11183 — verifies that pnpm dlx, invoked via the
bundled CLI, picks up minimumReleaseAge from the project's
pnpm-workspace.yaml and rejects packages that don't meet the cutoff.
Uses the public npm registry (matching the existing minimumReleaseAge
tests in exec/commands/test/dlx.e2e.ts:391) because verdaccio includes
the 'time' field in abbreviated metadata, which short-circuits the
publish-date check.
https://claude.ai/code/session_01NumMLsTvswMVJpbWp3YJrH
* fix(test): allow pnpm-workspace.yaml to override minimumReleaseAge in tests
The execPnpmSync test helper hardcoded
pnpm_config_minimum_release_age: '0'
which forced the value via env var (highest priority) for every test,
overriding any minimumReleaseAge set via pnpm-workspace.yaml.
This was inconsistent with the other settings in the helper (registry,
hoist, storeDir, fetchRetries) which use a `fallback()` reading from
the workspace manifest if present and falling back to a default
otherwise. Apply the same pattern for minimumReleaseAge.
Restores the integration test added in 6bc965b — without this fix the
test passes through dlx without applying the workspace's
minimumReleaseAge, making it not fail as the test expected.
https://claude.ai/code/session_01NumMLsTvswMVJpbWp3YJrH
* refactor(config,test): address review feedback
localConfig.ts doc comment:
- Drop redundant "(camelCase, from Config type)" parenthetical
- Replace em-dash-sandwiched paragraph with two flat sentences
- Switch list-item em dashes to colons (label: definition form)
pnpm/test/dlx.ts:
- Switch em dash in registry-override comment to colon
- Group the minimumReleaseAge tests into a describe block
- Add positive test: dlx succeeds when the pinned version is older
than the computed minimumReleaseAge cutoff
- Add range-resolution test: dlx resolves `shx@0.3.x` to 0.3.2 when
the cutoff is positioned between 0.3.2 (2018-07-11) and 0.3.3
(2020-10-26). The ~2.3 year gap leaves ample room for CI variance;
0.3.2's publish date is hardcoded (npm policy forbids unpublishing
past 72h).
https://claude.ai/code/session_01NumMLsTvswMVJpbWp3YJrH
* fix(test,config): address Copilot review feedback
- execPnpm.ts: only set pnpm_config_minimum_release_age env var when
the workspace manifest does not specify minimumReleaseAge, so tests
that verify dlx's local-config inheritance exercise the real config
path instead of being masked by the env var
- dlx.ts: fix "~19 years" comment to "~27.4 years" (10,000 days)
- dlx.ts: add pnpm create test verifying minimumReleaseAge from
pnpm-workspace.yaml (create delegates to dlx internally)
- changeset: bump @pnpm/config.reader to major (the rename of
ignoreNonAuthSettingsFromLocal → onlyInheritDlxSettingsFromLocal
is a breaking change to the published getConfig API)
https://claude.ai/code/session_01NumMLsTvswMVJpbWp3YJrH
* refactor(test): add noDefaultMinimumReleaseAge option to execPnpmSync
Replace the implicit workspace-yaml auto-detection with an explicit
opt-in flag. Tests that verify dlx/create inherits minimumReleaseAge
from pnpm-workspace.yaml pass `noDefaultMinimumReleaseAge: true` so
the env var default doesn't mask the real inheritance path.
https://claude.ai/code/session_01NumMLsTvswMVJpbWp3YJrH
* refactor(test): use omitEnvDefaults instead of noDefaultMinimumReleaseAge
Replace the single-purpose boolean flag with a general-purpose
`omitEnvDefaults: string[]` option on ExecPnpmSyncOpts. Tests pass the
env var name(s) to skip, e.g.
`omitEnvDefaults: ['pnpm_config_minimum_release_age']`.
https://claude.ai/code/session_01NumMLsTvswMVJpbWp3YJrH
* refactor(test): type omitEnvDefaults as PnpmEnvDefault[] literal union
Provides autocomplete and prevents typos by constraining the array
to known pnpm_config_* env var names set by the test helper.
https://claude.ai/code/session_01NumMLsTvswMVJpbWp3YJrH
* refactor(test): make omitEnvDefaults honor all listed env var names
Previously the code only checked for 'pnpm_config_minimum_release_age',
but the PnpmEnvDefault type listed 7 names, making the option silently
ineffective for the other 6. Now all defaults are set unconditionally
and any listed in omitEnvDefaults are deleted after, so every member
of PnpmEnvDefault actually works.
https://claude.ai/code/session_01NumMLsTvswMVJpbWp3YJrH
* docs(config): remove 'proxies' from inherited-settings examples
dlx does not actually inherit proxy settings (httpProxy / httpsProxy
etc. are neither in AUTH_CFG_KEYS nor RAW_AUTH_CFG_KEYS). The doc
comment in localConfig.ts listed 'proxies' as an example, which
mismatched the code. Drop the mention.
Behavior is unchanged; this is a docs-only fix.
https://claude.ai/code/session_01NumMLsTvswMVJpbWp3YJrH
* fix(dlx): fetch full metadata when minimumReleaseAge is set
Including minimumReleaseAge in the fullMetadata condition (alongside
the existing resolution-mode=time-based and trustPolicy=no-downgrade
triggers) bypasses the abbreviated→full metadata upgrade path in
pickPackage.ts for this case. That upgrade path is fragile on Windows:
the integration test at pnpm/test/dlx.ts:112 was failing with
ERR_PNPM_MISSING_TIME only on windows-latest runners, even though
the registry response is identical across platforms.
When minimumReleaseAge is set, pnpm always needs per-version
timestamps to decide which versions are mature enough. The original
condition only handled the two other time-dependent features
(resolution-mode=time-based and trust-policy=no-downgrade), missing
minimumReleaseAge. Adding it here eliminates an unnecessary round
trip plus the flaky upgrade, and matches the intent of the existing
siblings in the condition.
https://claude.ai/code/session_01NumMLsTvswMVJpbWp3YJrH
* style(test): avoid 'verdaccio: verdaccio' repetition in test comment
https://claude.ai/code/session_01NumMLsTvswMVJpbWp3YJrH
* refactor(config): rename POLICY_CFG_KEYS to SECURITY_POLICY_CFG_KEYS
'POLICY_CFG_KEYS' was too vague — reading it cold didn't convey what
kind of policy. Renamed to match the doc comment's 'security policy'
wording. Also renamed 'isPolicyCfgKey' → 'isSecurityPolicyCfgKey'.
https://claude.ai/code/session_01NumMLsTvswMVJpbWp3YJrH
* test(config): drop impossible 'cache-dir' key from inheritAuthConfig test
Addressing @zkochan's review: 'cache-dir' can never appear in
authConfig in production (pickIniConfig filters it out at .npmrc
load), so the assertion was testing an impossible state. Removed
from both the target's authConfig and the expected assertion.
https://claude.ai/code/session_01NumMLsTvswMVJpbWp3YJrH
---------
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
- **New command `pnpm with <version|current> <args...>`** — runs pnpm at a specific version (or the currently active one) for a single invocation, bypassing the project's `packageManager` and `devEngines.packageManager` pins. Uses the same install mechanism as `pnpm self-update`, caching the downloaded pnpm in the global virtual store for reuse.
- **New config setting `pmOnFail`** — overrides the `onFail` behavior of both `packageManager` and `devEngines.packageManager`. Accepted values: `download`, `error`, `warn`, `ignore`. Readable from CLI flag, env var, `pnpm-workspace.yaml`, or `.npmrc` — useful when version management is handled by an external tool (asdf, mise, Volta, etc.) and the project wants pnpm itself to skip the check.
```
pnpm with current install # one-shot, use running pnpm
pnpm with 11.0.0-rc.1 install # one-shot, use specific version
pnpm install --pm-on-fail=ignore # direct CLI flag
pnpm install --config.pm-on-fail=ignore # equivalent via --config.* sugar
pnpm_config_pm_on_fail=ignore pnpm install # env var
# or in pnpm-workspace.yaml: pmOnFail: ignore
```
## Implementation notes
- Command handler lives in `@pnpm/engine.pm.commands` (next to `self-update` and `setup`).
- `'with'` added to `SPECIALLY_ESCAPED_CMDS` in `cli/parse-cli-args` so args after `<spec>` pass through opaquely like `dlx`/`run`.
- `pnpm with current <cmd> [args]` is rewritten in `pnpm/src/parseCliArgs.ts` to an in-process dispatch — argv is rebuilt in place so any global flags the user put before `with` (e.g. `--dir`, `--filter`) are preserved. `process.env.pnpm_config_pm_on_fail=ignore` is set so the override survives `parseCliArgsLib`'s `-v` / `--help` short-circuits (which discard other parsed options).
- `main.ts` treats `skipPackageManagerCheck: true` as bypassing both the auto-download and the warn/error check (previously only the check). Also skips when `cmd='help'` and the help target is itself a skip-check command, so `pnpm with -h` works in pinned projects without downloading the pinned version first.
- Errors reported to stderr for `with` (aligned with `dlx`/`create`/`sbom`).
- `pmOnFail` wired in `config/reader/src/index.ts`: added to `types`, `Config`, and `pnpmConfigFileKeys`; applied as an override in the `onFail` resolution block.
- The `with <version>` child process sets both `COREPACK_ROOT` (honored by every pnpm release via `isExecutedByCorepack()`) and `pnpm_config_pm_on_fail=ignore` (principled override on new releases that ship the setting). This gives graceful behavior when `pnpm with 9.3.0 install` spawns an older pnpm that predates the new setting.
- Store controller lifecycle in the handler wrapped in `try/finally` to prevent leaks on install errors. Signal-induced child exits return a non-zero exit code so interrupted runs aren't masked as success.
The legacy `/-/npm/v1/security/audits{,/quick}` endpoints have been retired by npmjs.org. This PR rewires the audit client to the replacement `/-/npm/v1/security/advisories/bulk` endpoint.
The new endpoint is not a drop-in rename — the request and response contracts are both different:
- **Request**: a flat `{ pkgName: [versions] }` map. `lockfileToAuditRequest` walks the lockfile once and builds the POST body directly; there is no more nested `AuditTree`.
- **Response**: only `id`, `url`, `title`, `severity`, `vulnerable_versions`, and `cwe` per advisory. Everything else the old endpoint returned is computed locally:
- `findings[].paths` are walked from the lockfile (skipped entirely when the response is empty; the second walk intentionally avoids `@pnpm/lockfile.walker`'s global dedup so alternate install chains to the same shared dep aren't dropped).
- `metadata.vulnerabilities` counts advisories per severity.
- `metadata.dependencies` / `devDependencies` / `optionalDependencies` / `totalDependencies` come from a classified lockfile walk; the classifier respects `--prod`/`--dev` include flags when deciding whether a subgraph is reachable non-optionally.
- `patched_versions` is inferred from the vulnerable range for common `<X.Y.Z` / `<=X.Y.Z` shapes so `audit --fix` can still produce usable overrides; left `undefined` when inference fails.
- `github_advisory_id` is parsed from the advisory URL and canonicalized to the github.com form (uppercase `GHSA-` prefix, lowercase suffix).
- `info` severity is now supported end-to-end (severity type, `--audit-level`, filters, colors).
## Breaking changes (v11)
- Private registries that do not implement `/advisories/bulk` now fail with `AuditEndpointNotExistsError`.
- CVE-based filtering is replaced with GHSA-based filtering, since the bulk endpoint does not return CVE identifiers:
- `auditConfig.ignoreCves` → `auditConfig.ignoreGhsas` (the old key is no longer recognized).
- `pnpm audit --ignore <id>` and `--ignore-unfixable` now read and write GHSAs.
- Migration: replace each `CVE-YYYY-NNNNN` in `auditConfig.ignoreCves` with the matching `GHSA-xxxx-xxxx-xxxx` (visible in the `More info` column of `pnpm audit` output) under `auditConfig.ignoreGhsas`.
- `--ignore-unfixable` now only targets advisories whose patched range couldn't be inferred — the only "no fix available" signal the bulk endpoint provides.
- `AuditReport` and `AuditAdvisory` are trimmed to just the fields the audit client actually populates:
- `AuditReport`: `advisories` + `metadata` only (`actions` and `muted` removed).
- `AuditAdvisory`: `findings`, `id`, `title`, `module_name`, `vulnerable_versions`, `patched_versions?`, `severity`, `cwe`, `github_advisory_id`, `url`. Dropped: `cves`, `created`, `updated`, `deleted`, `access`, `overview`, `recommendation`, `references`, `found_by`, `reported_by`, `metadata`.
- `AuditAction`, `AuditResolution`, `AuditActionRecommendation` removed (no consumers).
## Hardening
- Response body validated: non-object / malformed JSON / non-array package buckets all surface as `ERR_PNPM_AUDIT_BAD_RESPONSE` with a body excerpt. Advisory `id` must be a finite number and `severity` must be a known value before being indexed.
- Name-keyed records use `Object.create(null)` so a hostile/unusual package name can't trigger prototype pollution.
- GHSA ids canonicalized on both read and write so casing drift between config and registry doesn't mask ignores.
- `findings[].paths` are deduped and capped per (name, version) to keep pathologically shared graphs from blowing up memory.
## Internals
- `AuditTree` / `AuditNode` / `lockfileToAuditTree` removed. `lockfileToAuditIndex.ts` exports `lockfileToAuditRequest` (flat POST body + counts) and `buildAuditPathIndex` (only invoked when the response has advisories).
- `AuditAdvisory.findings` is now `AuditFinding[]` (was an unintended 1-tuple).
- Top-level test fixtures regenerated from real `registry.npmjs.org` responses; synthetic `update-*` fixtures converted in place to bulk shape.
---------
Co-authored-by: John van Leeuwen <john.van.leeuwen@priva.com>
Co-authored-by: Zoltan Kochan <z@kochan.io>
This PR implements the native `pnpm search` command and its aliases (`s`, `se`, `find`), removing the fallback to npm CLI. It follows the project standards by being root-cause and avoiding type duplications.
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
When pnpm self-updates via the headless install path, the install
directory was not registered in the store's project registry. This
caused `pnpm store prune` to treat its global virtual store packages
as unreachable and remove them, breaking the global pnpm binary.
Register the install dir after headless install in installPnpmToGlobalDir
* feat: add pnpm docs command and home alias
* chore: update manifests
* fix: address review comments for pnpm docs command
- Remove 'docs' and 'home' from NOT_IMPLEMENTED_COMMANDS to prevent
not-implemented handlers from overriding the real implementations
- Change fallback URL from npmjs.com to npmx.dev
- Remove bugs URL as a docs fallback (it's an issue tracker, not docs)
- Fix ESM mock in tests: use jest.unstable_mockModule instead of jest.mock
* refactor: use open package for browser opening in promptBrowserOpen
Replace the hand-rolled platform-specific execFile logic with the open
npm package, which handles WSL, Docker-in-WSL, and Windows edge cases
better. This removes the execFile dependency injection from
promptBrowserOpen, OtpContext, LoginContext, and SharedContext.
* fix: address copilot review comments
- docs: use www.npmjs.com fallback (not npmx.dev) and validate homepage
URL is http(s) before opening
- promptBrowserOpen: validate authUrl protocol before passing to open(),
and guard against synchronous throws from open()
* fix: restore npmx.dev fallback for pnpm docs
* chore: expand changeset with web-auth refactor impact
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
- Adds a new `minimumReleaseAgeStrict` setting (default: `false`)
- When `false` (default), pnpm falls back to versions that don't meet the `minimumReleaseAge` constraint if no mature versions satisfy the range being resolved
- Set to `true` to preserve the previous strict behavior (error when no mature version matches)
* test: ensure prerelease weighting is correct
* fix: use higher weight for package versions already in lockfile
* test: remove fundamentally incompatible test
* fix(test): use undici MockAgent instead of nock for HTTP mocking
nock only patches Node's built-in http/https modules, but pnpm uses
undici for HTTP requests. Replace nock with @pnpm/testing.mock-agent
(which wraps undici's MockAgent) so the regression test actually
intercepts registry metadata requests.
* fix(benchmarks): show errors from store populate step
The populate step redirected both stdout and stderr to /dev/null,
hiding the actual error when pnpm install fails during benchmarks.
* fix(benchmarks): replace deprecated packages in benchmark fixture
The old fixture used deprecated babel 6, gulp, and other legacy
packages whose transitive dependencies (e.g. es-abstract) are missing
the "time" field in registry metadata, causing ERR_PNPM_MISSING_TIME
with time-based resolution mode.
Replace with modern equivalents (babel 7, webpack 5, MUI, Redux
Toolkit, etc.) that maintain a similar dependency tree size (~1300
packages) while using well-maintained packages with proper registry
metadata.
* fix(benchmarks): drop eslint plugins that pull in es-abstract
eslint-plugin-react, eslint-plugin-import, and eslint-plugin-jsx-a11y
transitively depend on es-abstract, whose registry metadata lacks the
"time" field. Replace them with eslint-plugin-prettier to avoid
ERR_PNPM_MISSING_TIME with time-based resolution.
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
When `pnpm audit --fix` adds overrides to fix vulnerabilities, it now
also adds the minimum patched version for each advisory to
`minimumReleaseAgeExclude` in pnpm-workspace.yaml. This allows
`pnpm install` to install the security fix without waiting for it to
satisfy the minimumReleaseAge constraint.
Closes#10263
* refactor: remove ignoreDepScripts and neverBuiltDependencies settings
These settings are redundant in v11:
- `ignore-dep-scripts` is superseded by the default behavior of `allowBuilds`
- `neverBuiltDependencies` was already dead code, replaced by `allowBuilds`
* chore: add changeset for removed ignore-dep-scripts setting
* feat(auth): implement `pnpm logout` command
Adds a new `pnpm logout` command that logs users out of npm registries.
The command revokes the authentication token on the registry via
DELETE /-/user/token/{token}, then removes it from the local auth.ini
config file. Token revocation is best-effort: local cleanup always
proceeds even if the registry is unreachable or doesn't support
revocation.
Uses the same dependency injection pattern as `pnpm login` for
comprehensive testability.
https://claude.ai/code/session_016fw5sdGFtBiB9QapMKEuXa
* fix(auth): address review feedback on pnpm logout
- Rename revokeToken to tryRevokeToken for self-documenting code
- Extract token removal into removeTokenFromAuthIni function
- Remove redundant comments that restate function names
- Fix toHaveProperty to use array syntax for keys containing dots
(avoids Jest property path parsing pitfall)
- Add globalWarn when token is found in authConfig but not in auth.ini,
informing the user it must be removed manually from .npmrc
- Add tests for the .npmrc-only warning case
https://claude.ai/code/session_016fw5sdGFtBiB9QapMKEuXa
* fix(auth): fix Windows CI failure in logout test
Use path.join in test expectations for the warning message path,
since path.join produces backslashes on Windows.
https://claude.ai/code/session_016fw5sdGFtBiB9QapMKEuXa
* refactor(auth): use jest.fn() for fetch assertions in logout tests
Replace manual fetchedUrls arrays with jest.fn() mocks and use
toHaveBeenCalledWith for cleaner, more idiomatic assertions.
https://claude.ai/code/session_016fw5sdGFtBiB9QapMKEuXa
* refactor: destructure `context`
* refactor: literal types for `method`
* refactor(auth): test cleanup per review feedback
- Rename mockFetch to fetch for shorthand property syntax
- Use platform-aware configDir in warning tests instead of
path.join on Unix-style paths
https://claude.ai/code/session_016fw5sdGFtBiB9QapMKEuXa
* style(auth): remove redundant return in createMockResponse arrow
Single-statement return-with-braces arrow function converted to
expression-body form.
https://claude.ai/code/session_016fw5sdGFtBiB9QapMKEuXa
* fix(auth): address Copilot review on pnpm logout
- Send Authorization: Bearer header in the DELETE token revocation
request, otherwise the registry returns 401 and the token is not
actually revoked
- Make tryRevokeToken return a boolean indicating whether the token
was actually revoked, and use it to choose the right warning when
the token is not in auth.ini
- Drop the misleading "(token removed locally)" suffix from the
registry-failure log messages, since the local removal may not
happen
- Extract getRegistryConfigKey and safeReadIniFile from login.ts and
logout.ts into a shared module to prevent the two commands from
drifting apart over time
- Add tests asserting the Authorization header is sent and that the
warning correctly distinguishes between revoked and not-revoked
cases
https://claude.ai/code/session_016fw5sdGFtBiB9QapMKEuXa
* fix(auth): throw on logout when nothing actually happened
When the registry rejects the token revocation AND the token is not
in auth.ini, neither side effect of logout actually happened — the
user is still authenticated locally and on the registry. Throwing
an ERR_PNPM_LOGOUT_FAILED error in this case avoids the misleading
"Logged out of ..." success message and gives a non-zero exit code.
https://claude.ai/code/session_016fw5sdGFtBiB9QapMKEuXa
---------
Co-authored-by: Claude <noreply@anthropic.com>
Implement dist-tag ls, add, and rm subcommands natively instead of
delegating to npm. Follows the same pattern as the recently added
deprecate and unpublish commands.
Major cleanup of the config system after migrating settings from `.npmrc` to `pnpm-workspace.yaml`.
### Config reader simplification
- Remove `checkUnknownSetting` (dead code, always `false`)
- Trim `npmConfigTypes` from ~127 to ~67 keys (remove unused npm config keys)
- Replace `rcOptions` iteration over all type keys with direct construction from defaults + auth overlay
- Remove `rcOptionsTypes` parameter from `getConfig()` and its assembly chain
### Rename `rawConfig` to `authConfig`
- `rawConfig` was a confusing mix of auth data and general settings
- Non-auth settings are already on the typed `Config` object — stop duplicating them in `rawConfig`
- Rename `rawConfig` → `authConfig` across the codebase to clarify it only contains auth/registry data from `.npmrc`
### Remove `rawConfig` from non-auth consumers
- **Lifecycle hooks**: replace `rawConfig: object` with `userAgent?: string` — only user-agent was read
- **Fetchers**: remove unused `rawConfig` from git fetcher, binary fetcher, tarball fetcher, prepare-package
- **Update command**: use `opts.production/dev/optional` instead of `rawConfig.*`
- **`pnpm init`**: accept typed init properties instead of parsing `rawConfig`
### Add `nodeDownloadMirrors` setting
- New `nodeDownloadMirrors?: Record<string, string>` on `PnpmSettings` and `Config`
- Replaces the `node-mirror:<channel>` pattern that was stored in `rawConfig`
- Configured in `pnpm-workspace.yaml`:
```yaml
nodeDownloadMirrors:
release: https://my-mirror.example.com/download/release/
```
- Remove unused `rawConfig` from deno-resolver and bun-resolver
### Refactor `pnpm config get/list`
- New `configToRecord()` builds display data from typed Config properties on the fly
- Excludes sensitive internals (`authInfos`, `sslConfigs`, etc.)
- Non-types keys (e.g., `package-extensions`) resolve through `configToRecord` instead of direct property access
- Delete `processConfig.ts` (replaced by `configToRecord.ts`)
### Pre-push hook improvement
- Add `compile-only` (`tsgo --build`) to pre-push hook to catch type errors before push
Replace the unmaintained @pnpm/npm-conf package with a purpose-built
module that reads only auth/registry-related settings from .npmrc files
using read-ini-file + @pnpm/config.env-replace (both already deps).
All non-registry settings (hoist-pattern, node-linker, etc.) are now
only read from pnpm-workspace.yaml, CLI options, or environment
variables. Registry-related settings (auth tokens, registry URLs,
SSL certs, proxy settings) continue to be read from .npmrc for
migration compatibility, and can also be set in pnpm-workspace.yaml.
New modules:
- loadNpmrcFiles.ts: reads .npmrc from standard locations, filters to
auth/registry keys, returns structured layers
- npmConfigTypes.ts: inlined npm config type definitions
- npmDefaults.ts: inlined npm defaults (registry, unsafe-perm, etc.)
* feat(auth): add "Press ENTER to open in browser" during web authentication
During web-based authentication (login, publish), users can now press
ENTER to open the authentication URL in their default browser. The
background token polling continues uninterrupted, so users who prefer
to authenticate on their phone can still do so without pressing anything.
The implementation uses Node's readline module (not raw mode), so Ctrl+C
and Ctrl+Z continue to work normally. It is fully error-tolerant: if the
keyboard listener or browser opening fails, a warning is printed and the
polling continues.
https://claude.ai/code/session_01UtDnjrNQ2Cc3GLAPR8BrrW
* fix(auth): inject readline and execFile directly, not wrapper functions
Address review feedback:
- Remove defaultListenForEnter and defaultOpenBrowser wrapper functions
- Inject readline module and execFile function directly via context
- DEFAULT_CONTEXT now references modules directly (no closures)
- Use switch for platform detection, default = no browser prompt
- Rename pollWithBrowserOpen → offerToOpenBrowser (clearer name)
- Add platform-specific tests (darwin, win32, linux, freebsd)
- Use PassThrough streams for stdin mocks in tests
https://claude.ai/code/session_01UtDnjrNQ2Cc3GLAPR8BrrW
* fix(auth): fix CI type errors in test mocks
- Type jest.fn() mocks for readline.createInterface properly
- Use PassThrough streams for stdin mocks in releasing/commands tests
https://claude.ai/code/session_01UtDnjrNQ2Cc3GLAPR8BrrW
* refactor(auth): use generic Stdin parameter to eliminate PassThrough in tests
Per review feedback, add a generic Stdin type parameter to context
interfaces. This ties process.stdin and readline.createInterface together
through the same type, so tests can use simple { isTTY: true } mocks
instead of requiring PassThrough streams.
https://claude.ai/code/session_01UtDnjrNQ2Cc3GLAPR8BrrW
* fix(auth): propagate Stdin generic to releasing/commands OtpContext
The OtpContext in releasing/commands extends BaseOtpContext from
web-auth. Now that BaseOtpContext is generic, the local OtpContext
and publishWithOtpHandling must also be generic so tests can use
simple stdin mocks without PassThrough.
https://claude.ai/code/session_01UtDnjrNQ2Cc3GLAPR8BrrW
* fix: sort imports in releasing/commands otp.ts
https://claude.ai/code/session_01UtDnjrNQ2Cc3GLAPR8BrrW
* refactor(auth): use .bind() for readline injection instead of generics
Per review feedback, revert the generic Stdin approach and instead use
readline.createInterface.bind(null, { input: process.stdin }) as the
injectable dependency. This avoids generics proliferation while keeping
the context clean — no arrow functions or closures in DEFAULT_CONTEXT.
https://claude.ai/code/session_01UtDnjrNQ2Cc3GLAPR8BrrW
* feat(publish): add "Press ENTER to open in browser" during publish OTP
Wire up createReadlineInterface and execFile in the publish
SHARED_CONTEXT so that pnpm publish also offers to open the browser
during web-based OTP authentication.
https://claude.ai/code/session_01UtDnjrNQ2Cc3GLAPR8BrrW
* fix(auth): improve browser-open prompt message
Change "Press ENTER to open in browser..." to
"Press ENTER to open the URL in your browser."
The old message implied the user should press Enter. The new wording
presents it as an available action, not an instruction — users can
also scan the QR code or copy-paste the URL.
https://claude.ai/code/session_01UtDnjrNQ2Cc3GLAPR8BrrW
* style: remove unnecessary arrow wrapper around createMockReadlineInterface
https://claude.ai/code/session_01UtDnjrNQ2Cc3GLAPR8BrrW
* docs: explain why Enter keypress is fire-and-forget, not awaited
Add a comment explaining that only pollPromise is awaited — the Enter
listener is intentionally not part of a Promise.all. This prevents a
future refactor from reintroducing the npm bug where authentication
blocks until Enter is pressed, even when the user authenticates on
another device.
https://claude.ai/code/session_01UtDnjrNQ2Cc3GLAPR8BrrW
* docs: add permalink to npm's Promise.all bug in comment
Link to the specific npm-profile commit (d1a48be4259) so the comment
remains accurate even if npm fixes the bug in the future.
https://claude.ai/code/session_01UtDnjrNQ2Cc3GLAPR8BrrW
* fix: correct line numbers in npm-profile permalink (L85-L98)
https://claude.ai/code/session_01UtDnjrNQ2Cc3GLAPR8BrrW
* style: apply review suggestion for npm-profile permalink format
https://claude.ai/code/session_01UtDnjrNQ2Cc3GLAPR8BrrW
* style: remove duplicate line in npm-profile comment
https://claude.ai/code/session_01UtDnjrNQ2Cc3GLAPR8BrrW
* fix: shadow global process instead of renaming to proc
Destructure as `process` (not `proc`) so the global `process` is
shadowed, preventing accidental direct access to it.
https://claude.ai/code/session_01UtDnjrNQ2Cc3GLAPR8BrrW
* fix: merge process fields in test mock contexts
Restructure createMockContext to merge process fields instead of
replacing the entire object. Tests that only need to override
platform or stdin no longer need to redundantly provide the other.
Also adds a test for undefined platform (default: case).
https://claude.ai/code/session_01UtDnjrNQ2Cc3GLAPR8BrrW
* fix: use Omit+Partial for process overrides in test mock contexts
The process field spread `...overrides?.process` merges at runtime but
TypeScript still requires all fields in the override type. Fix by typing
the process override as Partial via Omit<..., 'process'> & { process?: Partial<...> }.
https://claude.ai/code/session_01UtDnjrNQ2Cc3GLAPR8BrrW
* refactor: extract a type alias
* refactor: extract MockContextOverrides type alias in remaining tests
https://claude.ai/code/session_01UtDnjrNQ2Cc3GLAPR8BrrW
* refactor(auth): extract process types, use NodeJS.Platform, clean up tests
- Extract OfferToOpenBrowserProcess interface from inline process type
- Extract LoginProcess interface from inline process type in LoginContext
- Use NodeJS.Platform instead of string for platform fields (prevents typos)
- Rename simulateEnter → simulateEnterKeypress (clarify it's the key)
- Convert single-return functions to arrow expressions in tests
- Update test descriptions to say "Enter key" / "Enter keypress"
https://claude.ai/code/session_01UtDnjrNQ2Cc3GLAPR8BrrW
* refactor(auth): rename offerToOpenBrowser → promptBrowserOpen
Per review feedback, "offer to open browser" was mouthful. Renamed
function, file, and all associated types (OfferToOpenBrowser* →
PromptBrowserOpen*).
https://claude.ai/code/session_01UtDnjrNQ2Cc3GLAPR8BrrW
* docs: drop "IMPORTANT"
* refactor(auth): extract OtpProcess interface from inline process type
https://claude.ai/code/session_01UtDnjrNQ2Cc3GLAPR8BrrW
* fix(auth): validate authUrl before passing to execFile
On Windows, cmd.exe re-parses execFile arguments with full shell
grammar, so metacharacters (&, |, ^, etc.) in the URL would be
interpreted as operators. Validate that authUrl is a well-formed
http(s) URL before passing it to the platform browser command.
https://claude.ai/code/session_01UtDnjrNQ2Cc3GLAPR8BrrW
* test(auth): add regression test for URLs with query parameters on win32
Verifies that URLs containing & and other query string characters are
passed through to execFile as-is on the win32 platform.
https://claude.ai/code/session_01UtDnjrNQ2Cc3GLAPR8BrrW
* fix(auth): escape cmd.exe metacharacters in Windows browser open URL
On Windows, cmd.exe re-parses execFile arguments and treats & | < > ^ %
as operators. Escape these with ^ so query strings in auth URLs
(e.g. ?token=abc&redirect=...) are not split by cmd.exe.
https://claude.ai/code/session_01UtDnjrNQ2Cc3GLAPR8BrrW
* fix(auth): use canonicalized URL and expand cmd.exe escape set
- Use parsedUrl.href (canonicalized by new URL()) instead of the raw
authUrl string, ensuring percent-encoding of spaces and special chars.
- Expand cmd.exe metacharacter escaping to include () and ! in addition
to & | < > ^ %, covering grouping operators and delayed expansion.
https://claude.ai/code/session_01UtDnjrNQ2Cc3GLAPR8BrrW
* docs(auth): document Windows browser-opening edge cases
Explain why cmd /c start is used instead of ShellExecuteW (not
callable from Node.js without a native addon), why alternatives
like explorer.exe, rundll32, and PowerShell are unreliable, and
note that a Rust/N-API addon could replace this in the future.
https://claude.ai/code/session_01UtDnjrNQ2Cc3GLAPR8BrrW
* fix: fix cspell errors in Windows browser-open comment
Reword to avoid unknown words "rundll" and "metacharacter".
https://claude.ai/code/session_01UtDnjrNQ2Cc3GLAPR8BrrW
---------
Co-authored-by: Claude <noreply@anthropic.com>
- Enable Happy Eyeballs (`autoSelectFamily`) for faster dual-stack (IPv4/IPv6) connection establishment
- Increase keep-alive timeouts (30s idle, 10min max) to reduce connection churn during install
- Set optimized global dispatcher so requests without custom options still benefit
- Pre-allocate `SharedArrayBuffer` for tarball downloads when `Content-Length` is known, avoiding intermediate chunk array and double-copy
The ENOTSUP fallback in createClonePkg() silently converted clone
failures to file copies, preventing the auto-importer from detecting
that cloning is not supported and falling through to hardlinks.
On filesystems without reflink support (e.g. ext4 on Linux CI),
this caused every file to be copied instead of hardlinked — a 2-9x
regression for install operations on large projects.
The fix uses a raw clone (without ENOTSUP fallback) for the auto-mode
probe. If the filesystem doesn't support cloning, the error propagates
and the auto-importer falls through to hardlinks. Once cloning is
confirmed to work, subsequent packages use the full clone importer
with ENOTSUP fallback for transient failures under heavy parallel I/O.
- Added `pnpm pm <command>` syntax that always runs the built-in pnpm command, bypassing any same-named script in `package.json`
- When a project defines a script like `"clean": "rm -rf dist"`, `pnpm clean` runs that script, but `pnpm pm clean` runs the built-in clean command
- This applies to all overridable commands: `clean`, `purge`, `rebuild`, `deploy`, `setup`
* refactor(config): stop shelling out to npm for auth settings
Read and write auth-related settings (registry, tokens, credentials,
scoped registries) directly to INI config files instead of delegating
to `npm config`. Removes the @pnpm/exec.run-npm dependency from
@pnpm/config.commands.
* fix(config): give pnpm global rc priority over ~/.npmrc for auth settings
Auth settings from the pnpm global rc file (e.g. ~/.config/pnpm/rc) now
override ~/.npmrc in rawConfig. This ensures tokens written by `pnpm login`
are correctly picked up by `pnpm publish`, since login writes to the pnpm
global rc but ~/.npmrc previously took priority in the npm-conf chain.
* chore: remove @pnpm/exec.run-npm package
No longer used after removing npm config CLI delegation.
* chore: remove accidentally committed __typecheck__/tsconfig.json
* fix(config): narrow non-string rejection to credential keys, add priority test
Non-string value rejection now only applies to credential keys (_auth,
_authToken, _password, username), registry URLs, and scoped/registry-
prefixed keys — not to INI settings like strict-ssl, proxy, or ca that
can legitimately have boolean/null values.
Added a test verifying that auth tokens from the pnpm global rc take
priority over ~/.npmrc.
Replace node-fetch with native undici for HTTP requests throughout pnpm.
Key changes:
- Replace node-fetch with undici's fetch() and dispatcher system
- Replace @pnpm/network.agent with a new dispatcher module in @pnpm/network.fetch
- Cache dispatchers via LRU cache keyed by connection parameters
- Handle proxies via undici ProxyAgent instead of http/https-proxy-agent
- Convert test mocking from nock to undici MockAgent where applicable
- Add minimatch@9 override to fix ESM incompatibility with brace-expansion
* fix: respect frozen-lockfile flag when migrating config dependencies
* fix: throw FROZEN_LOCKFILE_WITH_OUTDATED_LOCKFILE when installing config deps with --frozen-lockfile
* fix: correct changeset package name and clean up minor issues
- Fix changeset referencing non-existent @pnpm/config.deps-installer
(should be @pnpm/installing.env-installer)
- Fix merge artifact in AGENTS.md
- Revert unnecessary Promise.all refactoring in migrateConfigDeps.ts
- Remove extra blank line in test file
* fix: move frozenLockfile check to call site and add missing tests
Move the frozenLockfile check from migrateConfigDepsToLockfile() to
normalizeForInstall() to minimize the number of check points.
Add unit tests for all frozenLockfile code paths:
- installConfigDeps: migration fails with frozenLockfile
- resolveAndInstallConfigDeps: old-format migration, new-format
resolution, and up-to-date lockfile success
- resolveConfigDeps: fails with frozenLockfile
* refactor: consolidate duplicate frozenLockfile checks in resolveAndInstallConfigDeps
Merge two identical frozenLockfile throw statements into a single check
covering both lockfileChanged and depsToResolve conditions.
* Delete respect-frozen-lockfile.md
* refactor: order fields
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
Instead of rendering the full peer dependency issues tree during installation,
suggest users run "pnpm peers check" to view the issues. Remove the now-unused
@pnpm/installing.render-peer-issues package.
* feat: use yarn-like output for script execution
Print `$ command` instead of `> pkg@version stage path\n> command`.
Show project name and path only when running in a different directory.
* fix: sort chalk dependency after @pnpm packages
* refactor: remove project info line from run output
* chore: add changeset
* refactor: print script command line to stderr
The `$ command` line is metadata, not program output. Printing it to
stderr keeps stdout clean for piping, matching bun's behavior.
* chore: update changeset to major
* fix: stop setting npm_config_ env vars from pnpm config during lifecycle scripts
Update @pnpm/npm-lifecycle to 1100.0.0-0 which no longer dumps the
entire pnpm config as npm_config_* environment variables. This fixes
npm warnings about unknown config when lifecycle scripts invoke npm.
Only well-known npm_* env vars are now set, matching Yarn's behavior.
* fix: fix spellcheck in changeset
* chore: remove obsolete @pnpm/npm-lifecycle patch file
* fix: pass npm_config_user_agent via extraEnv in lifecycle scripts
The npm-lifecycle makeEnv() strips all npm_* vars from process.env,
so npm_config_user_agent must be explicitly passed via extraEnv.
* chore: mark changeset as major (breaking change)
* feat: add native view/info command
* test: add unit tests for native view command
* fix(view): support ranges, aliases, and tags
* chore: update lockfile and tsconfig
* refactor(view): reuse pickPackageFromMeta from npm-resolver
- Share version resolution logic with the npm-resolver instead of
reimplementing tag/range/version matching in the view command.
- Export pickPackageFromMeta and pickVersionByVersionRange from
@pnpm/resolving.npm-resolver.
- Remove redundant double HTTP fetch (metadata already contains all
version data).
- Remove duplicate author/repository fields from PackageInRegistry
(already inherited from BaseManifest).
- Consolidate four changesets into one.
- Revert unrelated .gitignore change.
- Drop direct semver dependency from deps.inspection.commands.
* refactor(view): reuse fetchMetadataFromFromRegistry from npm-resolver
Use the npm-resolver's fetchMetadataFromFromRegistry instead of
hand-rolled fetch logic. This fixes:
- Broken URL encoding for scoped packages (@scope/pkg)
- Missing auth header, proxy, SSL, and retry config
- Duplicated fetch + error handling code
Also pass proper Config options (rawConfig, userAgent, SSL, proxy,
retry, timeout) through to createFetchFromRegistry and
createGetAuthHeaderByURI so the view command works with private
registries and corporate proxies.
* test(view): improve test coverage for view command
Add tests for:
- non-registry spec rejection (git URLs)
- no matching version error
- version range resolution (^1.0.0)
- dist-tag resolution (latest)
- nested field selection (dist.shasum)
- field selection with --json
- text output format (header, dist section, dist-tags)
- scoped package lookup (@pnpm.e2e/pkg-with-1-dep)
- deps count / deps: none in header
- object field rendering as JSON
* revert: undo rename of @pnpm/resolving.registry.types
The rename from @pnpm/resolving.registry.types to
@pnpm/registry.types (and the move from resolving/registry/types/
to registry/types/) is a separate refactoring concern unrelated to
the view command. Revert all rename-related changes.
Keep the legitimate type additions to PackageInRegistry:
maintainers, contributors, and dist.unpackedSize.
* revert: restore pnpm-workspace.yaml (remove registry/* glob)
* fix(view): handle edge cases in formatBytes and unpackedSize
- Use explicit null check for unpackedSize so 0 B is still rendered
- Add TB/PB units and clamp index to prevent undefined output
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>