Commit Graph

2799 Commits

Author SHA1 Message Date
Zoltan Kochan
fcdd50aaa7 chore(release): 11.0.0-rc.3 2026-04-21 00:17:38 +02:00
Zoltan Kochan
e03e8f4d8d fix(directory-fetcher): respect absolute paths in resolution.directory (#11318)
`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.
2026-04-20 21:11:29 +02:00
Zoltan Kochan
5a293d250c refactor: rename @pnpm/exe platform packages to @pnpm/exe.<platform>-<arch>[-musl] (#11316)
* 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.
2026-04-20 15:42:04 +02:00
Zoltan Kochan
72c1e050e9 feat: add pnpm pack-app command for packing CJS entries into standalone executables (#11312)
* 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
2026-04-20 14:29:49 +02:00
Allan Kimmer Jensen
bcc88a1239 fix(sbom): resolve licenses for git-sourced dependencies (#11310)
* 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.
2026-04-20 14:29:35 +02:00
Allan Kimmer Jensen
61952c24ec fix(sbom): detect legacy licenses array and LICENSE files (#11255)
- `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>
2026-04-20 02:44:47 +02:00
btea
390ee62577 feat(audit): add interactive mode to pnpm audit --fix and respect auditLevel (#11233)
close #11163

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-04-20 00:26:19 +00:00
Zoltan Kochan
7d25bc1136 fix: suppress packageManager/devEngines.packageManager conflict warning when values match exactly (#11307)
- 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
2026-04-20 01:08:15 +02:00
Zoltan Kochan
53668a42b8 fix: support explicit versions and --no-commit-hooks / --no-git-tag-version in pnpm version (#11299)
* 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
2026-04-20 00:26:32 +02:00
Zoltan Kochan
c86c423bdc fix: preserve pnpm 10 peer suffix encoding for linked paths (#11297)
* 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
2026-04-19 12:57:16 +02:00
Zoltan Kochan
7d9aae9662 fix: prevent frozen-lockfile error when approving builds in global install (#11294)
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.
2026-04-19 01:39:24 +02:00
Zoltan Kochan
9e0833c3cc feat: add minimumReleaseAgeIgnoreMissingTime setting (#11293)
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.
2026-04-19 00:22:32 +02:00
Zoltan Kochan
96ece9d736 chore(release): 11.0.0-rc.2 2026-04-17 18:21:35 +02:00
Zoltan Kochan
ea2a7fb244 feat: skip lockfile writes for legacy packageManager field (#11284)
* 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.
2026-04-17 14:45:51 +02:00
Zoltan Kochan
2dd8712c87 fix: pnpm init skips devEngines.packageManager in workspace subpackages (#11285)
The devEngines.packageManager field should only live at the workspace root.
When init runs inside a subpackage, the field is now omitted.
2026-04-17 14:30:28 +02:00
Zoltan Kochan
1c95cb6392 feat: pnpm init writes devEngines.packageManager field (#11283)
* 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>
2026-04-17 13:52:19 +02:00
Zoltan Kochan
ff7733ce21 feat: add runtimeOnFail setting (#11277)
* 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.
2026-04-17 12:00:17 +02:00
Alessio Attilio
75942bfac5 feat: implement native star, stars, unstar, and whoami commands (#11242)
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.
2026-04-17 11:02:12 +02:00
Zoltan Kochan
cee550a57d feat!: remove deprecated managePackageManagerVersions / packageManagerStrict / packageManagerStrictVersion (#11278)
* 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.
2026-04-17 00:57:33 +02:00
Khải
4ab3d9ba9e feat(dlx): accept more local configs (#11240)
* 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 #11183

https://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>
2026-04-17 00:38:41 +02:00
Zoltan Kochan
9af708a613 feat: add pnpm with <version|current> command (#11275)
## 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.
2026-04-16 22:34:34 +02:00
Zoltan Kochan
f7c23231a9 chore(release): 11.0.0-rc.1 2026-04-16 01:18:55 +02:00
John van Leeuwen
ff28085997 fix: adapt audit client to npmjs /advisories/bulk endpoint (#11268)
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>
2026-04-16 01:07:48 +02:00
Alessio Attilio
b738043ab1 feat: implement native pnpm ping command (#11258)
Add native `pnpm ping` command to test connectivity to registries.

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-04-14 21:21:55 +00:00
Alessio Attilio
f2083f4d7a feat(cli): implement native pnpm search (#11243)
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>
2026-04-14 19:58:11 +00:00
Zoltan Kochan
b989a4a1f4 fix: prevent store prune from breaking globally installed pnpm (#11253)
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
2026-04-14 17:30:41 +02:00
Alessio Attilio
2410cf4092 feat: add pnpm docs command and home alias (#11244)
* 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>
2026-04-14 16:20:40 +02:00
Zoltan Kochan
06d6c2d405 chore(release): 11.0.0-rc.0 2026-04-10 18:30:33 +02:00
Zoltan Kochan
ac944ef1d9 feat: add minimumReleaseAgeStrict setting (#11234)
- 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)
2026-04-10 17:49:02 +02:00
Brandon Cheng
9b0a460c8d fix: non-deterministic resolution causing pnpm dedupe --check to unexpectedly fail (#11110)
* 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>
2026-04-07 23:46:27 +02:00
Zoltan Kochan
7721d2e7f0 feat(audit): add patched versions to minimumReleaseAgeExclude on audit --fix (#11216)
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
2026-04-07 16:38:57 +02:00
Zoltan Kochan
51b04c3e9a refactor!: remove ignoreDepScripts and neverBuiltDependencies (#11220)
* 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
2026-04-07 13:41:13 +02:00
Khải
16cfde66ec feat: pnpm logout (#11213)
* 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>
2026-04-07 11:19:42 +02:00
Zoltan Kochan
853be661d8 feat: add native pnpm dist-tag command (#11218)
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.
2026-04-07 10:22:03 +02:00
Alessio Attilio
2c90d4061d feat: add native pnpm deprecate and undeprecate commands (#11120)
Adds native `pnpm deprecate` and `pnpm undeprecate` commands that interact with the npm registry directly instead of delegating to the npm CLI.

## Changes

- **New package `@pnpm/registry-access.commands`** — a new top-level domain for commands that manage packages on the registry
- **`pnpm deprecate <package>[@<version>] <message>`** — sets a deprecation message on matching versions
- **`pnpm undeprecate <package>[@<version>]`** — removes deprecation from already-deprecated versions
- Updated `pnpm-workspace.yaml`, CLI registration, and meta-updater config
- Removed `deprecate` from the not-implemented commands list
- Added changeset

## Implementation Details

- `registry-access/commands/src/deprecation/common.ts` — shared logic (auth, registry fetch, version matching)
- `registry-access/commands/src/deprecation/deprecate.ts` — deprecate command handler
- `registry-access/commands/src/deprecation/undeprecate.ts` — undeprecate command handler
- Uses `pickRegistryForPackage` for per-scope registry support
- Uses `createFetchFromRegistry`/`fetchWithDispatcher` for proxy/TLS support
- Uses `@pnpm/npm-package-arg` for package spec parsing
- Reuses `PackageMeta`/`PackageInRegistry` types from `@pnpm/resolving.registry.types`
- Sends minimal payload (only `name` + modified `versions.deprecated` fields) to the registry

## Usage

```bash
# Deprecate all versions
pnpm deprecate my-package "This package is deprecated"

# Deprecate a specific version
pnpm deprecate my-package@1.0.0 "Use v2 instead"

# Undeprecate all versions
pnpm undeprecate my-package

# Undeprecate a specific version
pnpm undeprecate my-package@1.0.0
```

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-04-06 15:30:11 +02:00
Zoltan Kochan
96704a1c58 refactor(config): rename rawConfig to authConfig, add nodeDownloadMirrors, simplify config reader (#11194)
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
2026-04-04 20:33:43 +02:00
sotanengel
c7203b99ad feat!: set default minimumReleaseAge to 1 day (1440 minutes) (#11158)
set default minimumReleaseAge to 1 day

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-04-04 13:26:22 +02:00
Zoltan Kochan
8bba5c3858 refactor(config): only read auth/registry from .npmrc, add registries to pnpm-workspace.yaml (#11189)
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.)
2026-04-04 02:44:12 +02:00
Khải
de3dc74439 feat(auth): keyboard event to open browser (#11148)
* 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>
2026-04-03 01:36:00 +02:00
Zoltan Kochan
6b3d87a4ca perf: optimize undici connection settings and tarball buffering (#11151)
- 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
2026-03-31 00:33:42 +02:00
Zoltan Kochan
9b1e5da6f7 fix: auto import mode falls through to hardlinks on ENOTSUP (#11150)
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.
2026-03-30 18:25:58 +02:00
Zoltan Kochan
f92517bec1 feat: error on deprecated CLI options instead of warning
Remove special handling for --independent-leaves, --lock, and
--resolution-strategy. They are now treated as unknown options.
2026-03-30 14:29:53 +02:00
Zoltan Kochan
00dcdfd38d feat: add pnpm pm prefix to force built-in commands (#11147)
- 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`
2026-03-30 09:51:04 +02:00
Zoltan Kochan
2df8b71467 refactor(config): stop shelling out to npm for auth settings (#11146)
* 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.
2026-03-29 23:28:23 +02:00
Zoltan Kochan
6c480a4375 perf: replace node-fetch with undici (#10537)
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
2026-03-29 12:44:00 +02:00
Alessio Attilio
d8be9706d9 fix: respect frozen-lockfile flag when migrating config dependencies (#11067)
* 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>
2026-03-28 18:17:52 +01:00
Zoltan Kochan
64393a3148 refactor: suggest "pnpm peers check" instead of rendering peer issues tree during install (#11133)
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.
2026-03-28 16:06:59 +01:00
Zoltan Kochan
f871365adb feat!: use cleaner output for script execution (#11132)
* 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
2026-03-28 15:58:03 +01:00
Zoltan Kochan
366cabeec8 fix: stop setting npm_config_ env vars during lifecycle scripts (#11116)
* 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)
2026-03-27 19:02:07 +01:00
Alessio Attilio
d3d6938414 feat: add native view/info/show/v command (#11064)
* 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>
2026-03-27 19:01:10 +01:00