@changesets/read treats every directory inside .changeset/ as a legacy
v1 changeset and tries to read changes.md from it, which made
`changeset version` fail with ENOENT on .changeset/.released/changes.md.
Move the per-branch ledger to .changeset-released/ at the repo root.
* test(exe): add Windows-only repro for #11486 (pn/pnpx/pnx aliases)
Captures the user-reported failure on a fresh Windows CI: when the
@pnpm/exe install rewrites bin entries to point at .cmd files,
@zkochan/cmd-shim's Bash shim does `exec cmd /C ...target.cmd`, MSYS2
mangles the lone `/C` into a Windows path, and cmd.exe falls into
interactive mode (printing its banner instead of running the alias).
These tests will fail on `windows-latest` until the follow-up commit
points the bin entries at .exe hardlinks of the SEA binary.
* fix(exe): route pn/pnpx/pnx through .exe hardlinks on Windows (#11486)
The @pnpm/exe install rewrote bin to point pn/pnpx/pnx at .cmd files,
which cmd-shim wraps as `exec cmd /C ...target.cmd "$@"` in its Bash
shim. MSYS2 / Git Bash mangles the lone `/C` into a Windows path
before cmd.exe sees it, so cmd.exe finds no /C or /K and falls into
interactive mode — the user sees its banner instead of `pnpm dlx`.
Hardlink pn.exe / pnpx.exe / pnx.exe to the SEA pnpm.exe (in setup.js
preinstall and in self-update's linkExePlatformBinary) and rewrite
those bin entries to the .exe names. cmd-shim emits a direct exec for
.exe sources, taking cmd.exe out of the chain entirely. The SEA reads
process.execPath's basename and prepends `dlx` when launched as
pnpx / pnx.
* test(exe): make Windows alias tests robust to local-dev environments
Two follow-ups from Copilot review on #11501:
* Use `'junction'` instead of `'dir'` for the detect-libc symlink on
Windows. Non-junction directory symlinks need Developer Mode or
admin, which the existing failure-path tests already skip on Windows
for; junctions don't.
* Probe \`bash --version\` before running the Git Bash / MSYS2 alias
test, and skip cleanly if it isn't on PATH (local Windows dev
machines often lack it; CI windows-latest ships it). Fold the status
check into the assertion so a non-zero exit surfaces in the diff.
* test(exe): wire @pnpm/exe into the recursive test runner
The setup.test.ts in this package wasn't running in CI — `@pnpm/exe`
had no `.test` script, so `pn -r .test` (what `test-pkgs-all` runs)
silently skipped it. The existing tests there have apparently been
dead since they were added; the Windows alias repro added in 1e93a1d
inherited the same gap.
Add `.test` (jest invocation, matching every other workspace
package's shape) and a `test` alias so it's picked up by the
recursive runner. meta-updater's @pnpm/exe / artifacts branch
short-circuits before adding test scripts; preserve that behavior by
hand-writing them rather than restructuring the rule.
## Summary
`pnpm publish` will now let an OIDC-derived token from npm's trusted publishing flow take precedence over a statically configured `_authToken` (e.g. one written from an `NPM_TOKEN` CI secret), mirroring the [npm CLI's behavior](https://github.com/npm/cli/blob/7d900c46/lib/utils/oidc.js).
### Background
We noticed that `pnpm@11.0.0-alpha.5` was published with trusted publishing on npm (its registry metadata has `_npmUser.trustedPublisher` and a SLSA `attestations.provenance` block) — but `pnpm@11.0.6` was not (`_npmUser: pnpmuser`, no attestations). The two were published by different clients: alpha.5 went out via `npm publish` (its metadata carries `_npmVersion: 11.6.2`, which `pnpm publish` never sets), while 11.0.6 went out via `pn release` from `.github/workflows/release.yml`.
Both paths run in CI with `id-token: write` granted, and both have the same `pn config set //registry.npmjs.org/:_authToken "\${NPM_TOKEN}"` step before publish. The difference is purely in how each client orders the auth precedence:
- **npm CLI**: tries the OIDC exchange first; on success **overwrites** the configured `_authToken`. The static token only acts as a fallback when OIDC isn't applicable (no trusted publisher configured, exchange fails, etc.).
- **pnpm publish (before this PR)**: bailed out of OIDC entirely as soon as a token was already configured (`releasing/commands/src/publish/publishPackedPkg.ts`, the old `fetchTokenAndProvenanceByOidcIfApplicable`). Worse, the call site used `??=` so OIDC could never overwrite the static token even if the bail-out had been removed.
So even though pnpm has a complete, working OIDC implementation, any release workflow that still wired in `NPM_TOKEN` silently downgraded to legacy token-based publishing — no `trustedPublisher` on the metadata, no provenance attestation. That's the fix here.
## Changes
- \`fetchTokenAndProvenanceByOidcIfApplicable\` → \`fetchTokenAndProvenanceByOidc\`. Dropped the now-unused \`targetPublishOptions\` parameter and removed the early bail-out — OIDC is always attempted when running in a supported CI environment with \`id-token: write\`.
- At the call site in \`createPublishOptions\`: when OIDC returns an authToken, it now overwrites \`publishOptions.token\` instead of nullish-assigning. The static token still wins when OIDC isn't applicable (no CI env, no trusted publisher configured, exchange fails).
- \`appendAuthOptionsForRegistry\` propagates the (possibly OIDC-overridden) token to the registry-scoped \`\${configKey}:_authToken\`, so libnpmpublish picks it up correctly.
- A \`ProvenanceError\` from \`determineProvenance\` no longer discards the freshly-fetched OIDC authToken — the publish itself can still go through with the OIDC token, again matching npm CLI behavior.
- Exported \`fetchTokenAndProvenanceByOidc\` (marked \`@internal\`) so the precedence rules are unit-testable.
### Recursive publish
The recursive publish loop in \`recursivePublish.ts\` calls \`publishPackedPkg\` once per workspace package, and OIDC token exchange is package-scoped on the npm side (\`/-/npm/v1/oidc/token/exchange/package/\${name}\`). With this fix, every workspace package independently attempts trusted publishing and only falls back to the static token if its own exchange fails. No structural change needed there.
The previous "Publish Packages" step ran `pn release` after writing
NPM_TOKEN into pnpm's config. With a static `_authToken` configured,
`pnpm publish` bails out of OIDC entirely (see #11495 for the longer-
term fix), so every package — including `pnpm` and `@pnpm/exe` — was
silently being published with the legacy token instead of using npm's
trusted publishing. The result: published metadata showed
`_npmUser: pnpmuser` and no provenance attestation.
Until #11495 ships, work around the precedence bug by structuring the
job so the packages we *want* trusted publishing for never see a
static token at all:
1. `@pnpm/exe` — published in a step with no NPM_TOKEN. pnpm has no
token to short-circuit on, performs OIDC, gets a `trustedPublisher`
entry on npm.
2. Internal workspace packages — these don't have trusted publishing
configured on npm, so they still need the static token. The token
is written, the publish runs, then `pn config delete` removes the
token before the next step.
3. `pnpm` — published in a step with no NPM_TOKEN, same rationale as
step 1.
CI-only change; no changeset needed.
Fixes [#11492](https://github.com/pnpm/pnpm/issues/11492).
In pnpm v11 a scoped registry resolved to different URLs depending on which command read it:
- `pnpm config get @<scope>:registry` returned the value from `.npmrc`
- `pnpm publish` used the value from `pnpm-workspace.yaml`'s `registries` block
When the two sources disagreed, `pnpm publish` silently targeted the URL from `pnpm-workspace.yaml`, even though `pnpm config get` reported a different (and seemingly authoritative) URL — so users could publish to the wrong registry without any indication.
This PR makes `pnpm config get @<scope>:registry` read from the merged `Config.registries` map (the same map `publish` and the resolvers use) before falling back to `authConfig`. Both commands now report and use the same URL.
Adds a native `pnpm bugs` command (npm-compatible) that opens a package's bug tracker URL in the browser.
- With no arguments, reads `bugs` / `repository` from the current project's `package.json`.
- With one or more package names, fetches each package's metadata from the registry (via `fetchPackageInfo`, so auth, scoped names, and proxies are handled) and opens its bug tracker.
- Falls back to `<repository>/issues` when `bugs` is missing, normalizing `git+`, `.git`, `git://`, and `git@github.com:` forms first.
- Implemented in `@pnpm/deps.inspection.commands` alongside `docs`, reusing the `open` package for cross-platform browser launching.
Picked up from #11279 (kairosci's branch) and reworked per @zkochan's review:
- moved out of `pnpm/src/cmd/` into the inspection commands package
- supports `pnpm bugs [<pkgname> [<pkgname> ...]]` per npm
- proper scoped-name encoding via the npm resolver
- changeset split (no more `bins.linker` bump)
- dropped unrelated test/build noise
Closes#11279.
Closes#11488.
`pnpm fetch` writes forced-empty `hoistPattern: []` and `publicHoistPattern: []` into `.modules.yaml` (because its `virtualStoreOnly` install path skips hoisting). In v10 the follow-up `pnpm install` ignored these unless the user had explicitly set a hoist-pattern in their config. v11's [#11199](https://github.com/pnpm/pnpm/pull/11199) removed that explicit-config gate, so `validateModules` now always sees the empty patterns as a hoist-pattern change and purges `node_modules` — slow on every CI run, and per the bug report sometimes leaves the modules dir in an `ERR_MODULE_NOT_FOUND` state on subsequent runs.
The fix marks `.modules.yaml` with a new `virtualStoreOnly: true` field after a fetch. `validateModules` recognizes this flag as "incomplete install state" and skips the `PUBLIC_HOIST_PATTERN_DIFF` / `HOIST_PATTERN_DIFF` comparisons. The next install then completes the missing post-import linking in place rather than purging. The flag is dropped from `.modules.yaml` once a normal install runs.
A genuine hoist-pattern change (without a fetch in between) still triggers the purge as before — verified manually with `publicHoistPattern` in `pnpm-workspace.yaml`.
The CLI argument parser short-circuits `--help` and `--version` and was discarding every other parsed option in the process — including universal flags like `--pm-on-fail`. So `pnpm audit --pm-on-fail=ignore --help` and `pnpm --pm-on-fail=ignore --version` failed with the strict `packageManager` mismatch error instead of doing what was asked. Users had no documented way out: the suggested escape hatch in the error message itself didn't work.
The fix plucks universal options back out of the exploratory `nopt` parse and surfaces them through both short-circuits. They were already typed correctly there; only the regular per-command parse adds command-specific options. Command-specific options (e.g. `--frozen-lockfile`) stay dropped, since the matching command isn't being executed.
Closes [#11487](https://github.com/pnpm/pnpm/issues/11487).
For git-hosted tarballs (`codeload.github.com` / `gitlab.com` / `bitbucket.org`) the fetcher dropped the integrity it computed while downloading, so the lockfile only ever stored the URL. A compromised git host or man-in-the-middle could serve a substituted tarball on subsequent installs and pnpm would install it — the lockfile had no hash to compare against.
This pins the SHA-512 SRI of the raw tarball in the lockfile, in the same `sha512-<base64>` form npm-registry tarballs use. The only difference is the source: for npm we pass through `dist.integrity`, for git we compute it locally from the downloaded buffer. Subsequent installs validate the download against that integrity in the worker (`addTarballToStore` → `parseIntegrity` → hash compare), so a tampered tarball fails with `TarballIntegrityError`.
## Why git-hosted stays on `gitHostedStoreIndexKey`
The lockfile pins integrity for security, but the *store key* for git-hosted resolutions stays on `gitHostedStoreIndexKey(pkgId, { built })` rather than collapsing under the integrity-based key. Reason: git-hosted tarballs are post-processed (`preparePackage` / `packlist`), so the cached file set depends on whether build scripts ran during fetch. The integrity-only key would fold the built and not-built variants into a single slot, letting one overwrite the other and serving the wrong content if `ignoreScripts` was toggled between runs. Keeping git-hosted on the existing key shape preserves that dimension; the integrity is still validated on every fresh download.
## How the routing stays clean
The naive way to express "use gitHostedStoreIndexKey for git-hosted, integrity key for npm" is to call `isGitHostedPkgUrl(resolution.tarball)` everywhere a store key is computed — fragile, scattered, and easy to forget when adding new readers (Copilot caught two of those during review). Instead, a typed annotation: `TarballResolution` gets an optional `gitHosted: boolean` field. The git resolver sets it; the lockfile loader (`convertToLockfileObject`) backfills it for entries written by older pnpm versions; `toLockfileResolution` carries it through on serialize. Every consumer reads `resolution.gitHosted` directly. URL detection lives in exactly two places — the resolver and the loader — instead of seven.
## Changes
### Security fix
- `fetching/tarball-fetcher/src/gitHostedTarballFetcher.ts` — return the `integrity` that the inner remote-tarball fetch already computed (was being silently dropped by the destructure).
### Lockfile schema (additive)
- `@pnpm/lockfile.types` and `@pnpm/resolving.resolver-base` — `TarballResolution` gains optional `gitHosted: boolean`.
- `@pnpm/resolving.git-resolver` — sets `gitHosted: true` on every git-hosted tarball it produces.
- `@pnpm/lockfile.fs` (`convertToLockfileObject`) — backfills the field on load for older lockfiles via inlined URL detection.
- `@pnpm/lockfile.utils` (`toLockfileResolution`, `pkgSnapshotToResolution`) — preserve / read the field.
### Store-key consumers (now one-line typed reads, dropped the URL-sniffing dep)
- `installing/package-requester` (`getFilesIndexFilePath`)
- `store/pkg-finder` (`readPackageFileMap`)
- `modules-mounter/daemon` (`createFuseHandlers`)
- `building/after-install` (side-effects-cache lookup + write)
- `store/commands/storeStatus`
- `installing/deps-installer` (agent-mode store-controller wrapper)
### Fetcher routing
- `fetching/pick-fetcher` — `pickFetcher` prefers `resolution.gitHosted`; URL fallback retained for ad-hoc resolutions.
### Tests
- New integrity-validation test in `tarball-fetcher` (mismatched `integrity` on the resolution must throw `TarballIntegrityError`).
- New git-hosted lookup test in `pkg-finder` asserting routing through `gitHostedStoreIndexKey` even when integrity is present.
- New `toLockfileResolution` test asserting `gitHosted: true` flows through serialization.
- `fromRepo.ts` lockfile snapshot updated for the now-pinned integrity + `gitHosted: true`.
- `git-resolver` tests updated to assert `gitHosted: true` in produced resolutions.
- Closes#11483.
- `@pnpm/exe@11.x` was packing `dist/node-gyp-bin/node-gyp`, `dist/node-gyp-bin/node-gyp.cmd`, and `dist/node_modules/node-gyp/bin/node-gyp.js` with mode `0644` instead of `0755`. Any install whose lifecycle script invokes `node-gyp rebuild` while pnpm has prepended `dist/node-gyp-bin/` to `PATH` failed with `sh: 1: node-gyp: Permission denied` — most visibly via `pnpm/action-setup@v6`'s standalone path on GitHub Actions runners with system Node < 22.13.
- The regular `pnpm` package's `package.json` already declares `publishConfig.executableFiles` for these three shims, which `pnpm publish` honors by packing matching tar entries with mode `0755` (see `releasing/commands/src/publish/pack.ts:273` and the `bins`-driven mode selection at `pack.ts:360-361`). `@pnpm/exe`'s `package.json` was missing the same field, so the shims it copies from `pnpm/dist/` shipped with `0644`. This PR mirrors the field into `@pnpm/exe`.
- To prevent the two manifests from drifting again, meta-updater is now the single source of truth for the executable-files list and writes it into both `pnpm/package.json` and `pnpm/artifacts/exe/package.json`.
* fix: include workspace root only when --filter is positive
PR #10465 stopped the implicit workspace-root exclusion for `pnpm -r
run/exec/test/add` whenever any --filter was provided. That gate was
too broad: with a negative-only filter set (e.g. `--filter '!a'`) the
workspace root started showing up in the matched projects, contradicting
the documented default behavior.
Only suppress the implicit root exclusion when the user provided at
least one positive (non-`!`) filter that could have been intended to
select the root. Negative-only filter sets keep the documented default,
and `--include-workspace-root` continues to opt the root in explicitly.
Close#11341.
* test: fix root-exclusion assertion to match --stream prefix
The --stream reporter prefixes output lines with the project's relative
directory, so the workspace root appears as `. which$`, not
`root which$`. The original assertion would have passed even if the
regression were still present. Verified the corrected assertion fails
against the buggy code and passes against the fix.
Moves 20 user-level preference settings from the workspace-only exclusion list into the global config allowlist (`config/reader/src/configFileKey.ts`):
- Shell / scripts: `scriptShell`, `shellEmulator`
- Notifications & UI: `updateNotifier`, `useStderr`
- Trust policy (already DLX-inherited as user-level posture): `trustPolicy`, `trustPolicyExclude`, `trustPolicyIgnoreAfter`
- Store / virtual store: `globalVirtualStoreDir`, `virtualStoreDir`, `virtualStoreDirMaxLength`, `verifyStoreIntegrity`, `sideEffectsCache`, `sideEffectsCacheReadonly`
- Build / dep verification: `strictDepBuilds`, `verifyDepsBeforeRun`
- Misc personal/system prefs: `stateDir`, `registrySupportsTimeField`, `initPackageManager`, `initType`, `agent`
These are personal/system preferences rather than workspace structure. In v10 they could be set in `~/.npmrc`. v11 silently dropped them from both `~/.npmrc` and the new global `config.yaml`, leaving `pnpm-workspace.yaml` as the only working location — which the issue author rightly points out is impractical for system-level defaults like `scriptShell`.
After this change:
- Settings in `~/.config/pnpm/config.yaml` are applied instead of being filtered out by `isConfigFileKey` (`config/reader/src/index.ts:296`).
- `pnpm config set --location global scriptShell <path>` succeeds instead of throwing `ConfigSetUnsupportedYamlConfigKeyError` (same predicate used in `config/commands/src/configSet.ts:237`).
`pmOnFail` and `runtimeOnFail` are intentionally left workspace-only because they would cause lockfile divergence between contributors when set globally. `~/.npmrc` support for non-auth/non-network keys is also intentionally not restored — the team has moved those settings to YAML config.
Closes#11474.
* chore(release): wrap changeset version with cross-branch consumed-id ledger
When a fix is cherry-picked from main to a release branch (or vice
versa), the changeset file ends up on both branches. The release
branch's release consumes and deletes its copy, but the cherry-picked
copy on main survives the merge back and would be re-applied on the
next main release.
Introduce a small wrapper around `changeset version` that maintains a
per-branch ledger at .changeset/.released/<branch>.txt. Each entry is a
consumed changeset id; the file is written only by the branch it is
named after, so the records merge across branches without conflicts.
Before running `changeset version` the wrapper reads the union of every
ledger file, hides matching .changeset/<id>.md files (rename to
.md.released), then runs `changeset version` against the remaining set.
Newly consumed ids are appended to the current branch's ledger; hidden
files are removed afterward (their consumption is already on record
elsewhere). On failure the hidden files are restored to keep the
working tree clean.
* docs: move release-ledger explanation out of AGENTS.md
AGENTS.md is for instructions to AI agents working on the codebase, but
the cross-branch ledger is release machinery that the maintainer running
`pnpm bump` interacts with — agents authoring changesets do not need to
know about it. Move the explanation to where someone runs into it:
- .changeset/.released/README.md — discovered by anyone exploring the
directory.
- A short doc-comment header at the top of __utils__/scripts/src/bump.ts
pointing readers there.
* fix(scripts): harden bump wrapper edge cases from PR review
- Use url.pathToFileURL(realpathSync(...)) to compare against
import.meta.url so the direct-invocation guard works on Windows
paths and through symlinks (Copilot review).
- hideReleased() now iterates the changeset directory and filters by
the released set instead of iterating the (potentially long) ledger
and probing existsSync per entry (Copilot review).
- hideReleased() restores already-renamed files if a later rename
throws, so a partial failure leaves the .changeset directory in its
original state (CodeRabbit review).
- Move deleteHidden() into a finally so the .md.released files are
cleaned up even if appendReleased() throws after a successful
changeset version run (CodeRabbit review).
- Add a unit test that forces hideReleased() to fail mid-loop and
asserts the rollback.
pnpm 10 transitively emitted an npm-CLI-shaped per-package JSON object on stdout for
`pnpm publish --json` because the publish command shelled out to npm. pnpm 11 (#10591)
reimplemented publish natively and the new handler returned undefined, so `--json` now
produces 0 bytes on stdout on success — silently breaking downstream tooling that grepped
that contract. The most impactful regression is `nx release publish`, which parses
stdout JSON to confirm success; under `--nx-bail=true` it aborts the rest of a release
run when JSON is missing (see nrwl/nx#35575).
This restores the contract for both single-package and recursive publish, and aligns
`--report-summary` to use the same per-package shape:
- `pnpm publish --json` → single object `{ id, name, version, size,
unpackedSize, shasum, integrity, filename, files,
entryCount, bundled }`, mirroring `npm publish --json`.
- `pnpm publish -r --json` → array of those objects, mirroring `pnpm pack --json`'s
shape choice for single vs multi.
- `pnpm publish -r --report-summary` → existing `pnpm-publish-summary.json` envelope
`{ publishedPackages: [...] }` is preserved, but
each entry is upgraded to the same per-package shape
(additive — `name` and `version` are still present).
Implementation:
- `pack.api()` computes `unpackedSize` while it still has the filesMap.
- `publishPackedPkg` returns a `PublishSummary` (SHA-1 `shasum` + SHA-512 `integrity`
via `node:crypto`, no new deps) instead of `void`.
- `bundled` normalizes `bundledDependencies` / `bundleDependencies` per npm's semantics
(including `true` → all dep names).
- `recursivePublish` collects per-package `PublishSummary` objects and returns them; the
existing manifest-pick fallback is kept for paths that don't produce a summary.
- `publish.handler` returns `{ output, exitCode }` with the serialized summary (object
or array) when `opts.json` is set; main.ts already writes `output` to stdout.
Refs #11476
Refs nrwl/nx#35575
Amp-Thread-ID: https://ampcode.com/threads/T-019df90f-8f75-763f-b528-4602e870a972
Co-authored-by: Charlie Croom <charlie+amp@noreply.com>
Co-authored-by: Amp <amp@ampcode.com>
* chore: update Node.js to 26.0.0
* fix(jest-config): use amaro for type stripping on Node.js 26
Node.js v26 removed the `transform` mode and `sourceMap` option from
`module.stripTypeScriptTypes`. Switch the Jest transform to call
`amaro.transformSync` directly (the same wasm transformer Node.js wraps)
so we keep inline source maps for tests.
Add a "Working with GitHub PRs, Issues, and Comments" section to AGENTS.md covering three rules:
Keep PR title and description current when pushing new changes.
Reply to resolved review comments with the resolution + commit hash and close the conversation.
Sign all agent-authored comments, issues, and PRs with a footer that names the agent and model.
Expose the helpers that build the `configByUri` registry-config map
from a flat rawConfig-style auth dict so that downstream consumers
(e.g. third-party clients of `@pnpm/installing.client` /
`@pnpm/store.connection-manager`) can produce the same configByUri
shape pnpm itself uses, without re-implementing the parsing logic.
- pnpm v11 silently drops local-only settings (e.g. `nodeLinker`, `hoistPattern`, `linkWorkspacePackages`) when they appear in the global `config.yaml`. Users had no way to tell their global configuration was being ignored.
- The reader now emits a warning that names the ignored keys and the path of the global config file, and directs users to either move the settings to a project-level `pnpm-workspace.yaml` or share them across projects via [config dependencies](https://pnpm.io/11.x/config-dependencies).
- Allowed keys (e.g. `dangerouslyAllowAllBuilds`, proxy settings) continue to be accepted with no warning.
close#11429
- When updating `pnpm-workspace.yaml`, existing keys keep their positions instead of being globally re-sorted alphabetically.
- New keys are inserted in alphabetical position when the existing layout is sorted; otherwise they are appended at the end. A leading `packages` key is treated as a sorted-first slot, matching the conventional pnpm layout.
- Blank lines between top-level fields are propagated to newly inserted entries so they match the surrounding style — including the case where a new key sorts to the front and demotes the previously-first pair.
* fix(config): accept uppercase PNPM_CONFIG_* env vars
Env vars are case-sensitive on macOS/Linux, so PNPM_CONFIG_USERCONFIG —
the rename suggested by the v11 migration guide — was silently ignored
because parseEnvVars only matched lowercase pnpm_config_*. Also wire the
env var into the early npmrcAuthFile lookup so it actually decides which
user-level .npmrc gets read.
Closes#11465
* chore: add changeset for npmrc auth file env-var load order
* test: cover lowercase pnpm_config_npmrc_auth_file env var
Matches the exact env var name reported in #11465. Without the early
env-var lookup before loadNpmrcConfig, this case is parsed too late
to actually load the custom .npmrc.
* test: lock precedence when both lowercase and uppercase env vars are set
Closes#10684. Running `pnpm config set` triggers `resolveAndInstallConfigDeps` before the new config (e.g. auth token) is saved to `.npmrc`, causing a 401 crash when the config dependency is hosted in a private registry.
This patch adds a `tolerateConfigDependenciesErrors` flag to `installConfigDepsAndLoadHooks`. For `cmd === 'config'`, `'set'` and `'get'`, installation failures are now caught and logged at debug level so the command can proceed and actually write / read the auth token.
`pnpm set` and `pnpm get` are separate top-level commands whose handlers delegate to the `config` command internally, so they are explicitly listed alongside `'config'`; users can hit #10684 via any of the three entry points.
Plugin pnpmfile paths from `configDependencies` are resolved by checking each file's existence on disk, so previously-installed hooks survive a transient install failure and `requireHooks` won't throw `PNPMFILE_NOT_FOUND` when nothing has been installed yet.
Covers the fix with:
- Unit tests in `pnpm/test/getConfig.test.ts` exercising the `installConfigDepsAndLoadHooks` contract (success, tolerated error, rethrown error, store creation/close errors propagating) and the on-disk pnpmfile resolution behavior.
- Four e2e tests in `pnpm/test/configurationalDependencies.test.ts` that spawn the pnpm CLI against a bogus `configDependency` and assert each entrypoint (`pnpm config set`, `pnpm config get`, `pnpm set`, `pnpm get`) still works.
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
pnpm v10 setup added PNPM_HOME (not PNPM_HOME/bin) to PATH and wrote
a pnpm bootstrap shim there. After upgrading to v11, that shim still
points into the old .tools/<version> install, so PATH continues to
resolve `pnpm` to the pre-update version even though the new version
was installed under global/v11.
Detect that layout during self-update, refresh the shims at PNPM_HOME
so the upgrade actually takes effect, and warn the user to run
`pnpm setup` for a clean migration to the v11 PATH layout.
Closes#11464.
Fixes#10594, catalogs not being read from the workspace when using the `catalog:` protocol with the `pnpm dlx` / `pnpx` command, resulting in a catalog entry not found error.
Added e2e tests to check if the workspace config is actually loaded. Also added that pnpm dlx reads the retry options from the workspace (Could potentially put that in a separate PR)
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit
* **Bug Fixes**
* Fixed catalog resolution when using the `catalog:` protocol with `pnpm dlx` / `pnpx` so catalogs are correctly read from the workspace.
* **New Features**
* `dlx` now inherits workspace catalog and fetch retry/timeout settings so CLI runs respect those local configs.
* **Tests**
* Added tests validating catalog inheritance and failure cases for `dlx` catalog resolution.
* **Chores**
* Updated changeset metadata to mark related packages for patch releases.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* fix: refresh ignored builds when allowBuilds changes
* refactor: extract isBuildExplicitlyDisallowed into @pnpm/building.policy
Removes the duplicated ignored-build filter from deps-installer and
deps-restorer and exposes it as `isBuildExplicitlyDisallowed` on
`@pnpm/building.policy`, alongside `createAllowBuildFunction`.
* fix: respect ignoredWorkspaceStateSettings in allowBuilds stale-state check
The fallback that flagged installs when allowBuilds went from unset to
non-empty bypassed the ignoredSettings filter, so callers that explicitly
opted out of allowBuilds tracking (via ignoredWorkspaceStateSettings)
could still be forced into a redundant install.
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
Expose the helpers that build the `configByUri` registry-config map
from a flat rawConfig-style auth dict so that downstream consumers
(e.g. third-party clients of `@pnpm/installing.client` /
`@pnpm/store.connection-manager`) can produce the same configByUri
shape pnpm itself uses, without re-implementing the parsing logic.
- pnpm v11 silently drops local-only settings (e.g. `nodeLinker`, `hoistPattern`, `linkWorkspacePackages`) when they appear in the global `config.yaml`. Users had no way to tell their global configuration was being ignored.
- The reader now emits a warning that names the ignored keys and the path of the global config file, and directs users to either move the settings to a project-level `pnpm-workspace.yaml` or share them across projects via [config dependencies](https://pnpm.io/11.x/config-dependencies).
- Allowed keys (e.g. `dangerouslyAllowAllBuilds`, proxy settings) continue to be accepted with no warning.
close#11429
- When updating `pnpm-workspace.yaml`, existing keys keep their positions instead of being globally re-sorted alphabetically.
- New keys are inserted in alphabetical position when the existing layout is sorted; otherwise they are appended at the end. A leading `packages` key is treated as a sorted-first slot, matching the conventional pnpm layout.
- Blank lines between top-level fields are propagated to newly inserted entries so they match the surrounding style — including the case where a new key sorts to the front and demotes the previously-first pair.
* fix(config): accept uppercase PNPM_CONFIG_* env vars
Env vars are case-sensitive on macOS/Linux, so PNPM_CONFIG_USERCONFIG —
the rename suggested by the v11 migration guide — was silently ignored
because parseEnvVars only matched lowercase pnpm_config_*. Also wire the
env var into the early npmrcAuthFile lookup so it actually decides which
user-level .npmrc gets read.
Closes#11465
* chore: add changeset for npmrc auth file env-var load order
* test: cover lowercase pnpm_config_npmrc_auth_file env var
Matches the exact env var name reported in #11465. Without the early
env-var lookup before loadNpmrcConfig, this case is parsed too late
to actually load the custom .npmrc.
* test: lock precedence when both lowercase and uppercase env vars are set
Closes#10684. Running `pnpm config set` triggers `resolveAndInstallConfigDeps` before the new config (e.g. auth token) is saved to `.npmrc`, causing a 401 crash when the config dependency is hosted in a private registry.
This patch adds a `tolerateConfigDependenciesErrors` flag to `installConfigDepsAndLoadHooks`. For `cmd === 'config'`, `'set'` and `'get'`, installation failures are now caught and logged at debug level so the command can proceed and actually write / read the auth token.
`pnpm set` and `pnpm get` are separate top-level commands whose handlers delegate to the `config` command internally, so they are explicitly listed alongside `'config'`; users can hit #10684 via any of the three entry points.
Plugin pnpmfile paths from `configDependencies` are resolved by checking each file's existence on disk, so previously-installed hooks survive a transient install failure and `requireHooks` won't throw `PNPMFILE_NOT_FOUND` when nothing has been installed yet.
Covers the fix with:
- Unit tests in `pnpm/test/getConfig.test.ts` exercising the `installConfigDepsAndLoadHooks` contract (success, tolerated error, rethrown error, store creation/close errors propagating) and the on-disk pnpmfile resolution behavior.
- Four e2e tests in `pnpm/test/configurationalDependencies.test.ts` that spawn the pnpm CLI against a bogus `configDependency` and assert each entrypoint (`pnpm config set`, `pnpm config get`, `pnpm set`, `pnpm get`) still works.
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
pnpm v10 setup added PNPM_HOME (not PNPM_HOME/bin) to PATH and wrote
a pnpm bootstrap shim there. After upgrading to v11, that shim still
points into the old .tools/<version> install, so PATH continues to
resolve `pnpm` to the pre-update version even though the new version
was installed under global/v11.
Detect that layout during self-update, refresh the shims at PNPM_HOME
so the upgrade actually takes effect, and warn the user to run
`pnpm setup` for a clean migration to the v11 PATH layout.
Closes#11464.
Fixes#10594, catalogs not being read from the workspace when using the `catalog:` protocol with the `pnpm dlx` / `pnpx` command, resulting in a catalog entry not found error.
Added e2e tests to check if the workspace config is actually loaded. Also added that pnpm dlx reads the retry options from the workspace (Could potentially put that in a separate PR)
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit
* **Bug Fixes**
* Fixed catalog resolution when using the `catalog:` protocol with `pnpm dlx` / `pnpx` so catalogs are correctly read from the workspace.
* **New Features**
* `dlx` now inherits workspace catalog and fetch retry/timeout settings so CLI runs respect those local configs.
* **Tests**
* Added tests validating catalog inheritance and failure cases for `dlx` catalog resolution.
* **Chores**
* Updated changeset metadata to mark related packages for patch releases.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* fix: refresh ignored builds when allowBuilds changes
* refactor: extract isBuildExplicitlyDisallowed into @pnpm/building.policy
Removes the duplicated ignored-build filter from deps-installer and
deps-restorer and exposes it as `isBuildExplicitlyDisallowed` on
`@pnpm/building.policy`, alongside `createAllowBuildFunction`.
* fix: respect ignoredWorkspaceStateSettings in allowBuilds stale-state check
The fallback that flagged installs when allowBuilds went from unset to
non-empty bypassed the ignoredSettings filter, so callers that explicitly
opted out of allowBuilds tracking (via ignoredWorkspaceStateSettings)
could still be forced into a redundant install.
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
Currently only supported for cyclonedx. Allowed values are 1.5, 1.6, and 1.7 (defaulting to 1.7); the lower bound is 1.5 because the generated JSON uses fields (e.g. `metadata.lifecycles`) that are only valid from CycloneDX 1.5+.
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
The `WARN` and `ERR_PNPM_*` labels in pnpm's output relied entirely on a colored background to stand out from the surrounding text. In terminals without color — `NO_COLOR` set, output piped, dumb terminals — the badge collapsed into bare `WARN` / `ERR_PNPM_FOO` and became hard to spot inside the message.
This PR wraps each label in brackets (`[WARN]`, `[ERR_PNPM_FOO]`, `[ERROR]`). The bracket characters are painted in the same color as the badge background, so in a color-capable terminal they appear as plain padding inside the colored badge — the rendering matches what we had before. When ANSI is stripped the brackets reappear as ordinary text, giving the label a clear delimiter.