Commit Graph

73 Commits

Author SHA1 Message Date
Zoltan Kochan
a662de44dd fix: pnpm runtime set defaults to devEngines (#11951)
* fix: pnpm runtime set defaults to devEngines

Previously `pnpm runtime set <name> <version>` wrote to `engines.runtime`
because it ran `pnpm add` with the default `--save-prod`. Default to
`--save-dev` so the runtime lands in `devEngines.runtime`; pass
`--save-prod` (or `-P`) to opt back into `engines.runtime`.

Closes #11948

* fix: honor --save-dev precedence in pnpm runtime set

When both `--save-dev` and `--save-prod` are passed, prefer `--save-dev`
to match `getSaveType`'s precedence elsewhere in pnpm. Also makes the
explicit `--save-dev` flag actually consulted instead of relying solely
on the default branch.

* ci: trigger
2026-05-26 16:04:10 +02:00
Puneet Dixit
35d235542e fix: validate devEngines runtime onFail (#11822)
Fixes #11818

## Summary

`devEngines.runtime` / `engines.runtime` entries with `onFail: error` or `warn` silently did nothing — only `onFail: download` had any effect. This PR wires up validation for all three supported runtimes (node, deno, bun).

- Add `getSystemDenoVersion` / `getSystemBunVersion` and a generic `getSystemRuntimeVersion(name)` dispatcher in the runtime-version helper package.
- Walk each runtime entry in the manifest during pnpm startup, compare to the live system runtime, and throw `ERR_PNPM_BAD_RUNTIME_VERSION` (or warn) on a mismatch. Invalid ranges (e.g. `"invalid range"`) are reported instead of crashing `semver.minVersion`. Missing runtimes ("no Node.js on the system") get the same error path.
- The shell-out for deno/bun only runs when the manifest configures them AND `onFail` is `error`/`warn`. `download`/`ignore` short-circuit, and projects with no runtime pin pay nothing. Memoized per runtime.
- `pnpm --version`, `pnpm --help`, and `pnpm <cmd> --global` are exempt from the check.
- Rename `@pnpm/engine.runtime.system-node-version` → `@pnpm/engine.runtime.system-version` to match its broader scope; hoist `RuntimeName` / `RUNTIME_NAMES` / `isRuntimeAlias` to `@pnpm/types` so callers don't need to depend on `pkg-manifest.utils` just for the alias check.

## Tests

- `pnpm --filter pnpm run compile`
- `pnpm --filter pnpm exec jest packageManagerCheck.test` — 42 passing. New coverage: node/deno/bun version mismatch, invalid range, missing range, multi-entry runtime arrays, `engines.runtime` path (not just `devEngines.runtime`), and the `pnpm --version` exemption.
- `pnpm --filter @pnpm/engine.runtime.system-version test` — 10 passing, 100% statement coverage; unit tests for each helper and the dispatcher.
- Manual end-to-end smoke tests against the rebuilt bundle for deno and bun version mismatch.

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

## Release Notes

* **New Features**
  * Added runtime version validation for Node.js, Deno, and Bun. The system now enforces `devEngines.runtime` and `engines.runtime` declarations with configurable failure behavior (`error`, `warn`, or `ignore`).
  * Enhanced error messages for runtime version mismatches with helpful suggestions for overrides.

* **Improvements**
  * Improved system runtime detection and version checking across multiple runtime environments.

---------

Co-authored-by: Puneet Dixit <236133619+puneetdixit200@users.noreply.github.com>
Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-05-26 10:29:40 +02:00
Zoltan Kochan
f2a4d2caef chore(release): 11.3.0 (#11894) 2026-05-24 02:23:07 +02:00
Zoltan Kochan
11a43b15da chore(release): 11.2.1 (#11777) 2026-05-20 16:51:13 +02:00
Zoltan Kochan
0fb723323f chore(release): 11.2.0 (#11764) 2026-05-20 12:41:09 +02:00
Zoltan Kochan
1627943d2a feat(outdated): include node, deno, and bun runtimes (#11739)
`pnpm outdated` and `pnpm update --interactive` previously skipped runtime dependencies (`node`/`deno`/`bun` installed via the `runtime:` protocol). Both commands go through `outdatedDepsOfProjects` → `outdated()`, and the inner loop bailed out for anything `parseBareSpecifier` couldn't parse — which is everything `runtime:`-shaped. A runtime was only ever reported if the current install differed from the wanted lockfile entry, so the latest available version was never surfaced. The same gap silently affected `jsr:` and named-registry deps too.

Commits, smallest fix first → progressively cleaner architecture:

1. **`feat(outdated)`** — minimal fix: special-case runtime deps in `outdated.ts` so they appear in the table and the interactive update picker.
2. **`refactor(outdated)`** — per-resolver dispatch. Each protocol resolver gets its own "what's the latest?" function; `@pnpm/resolving.default-resolver` composes them.
3. **`refactor(outdated)`** — rename to `resolveLatest` (the function returns info regardless of whether the dep is outdated; "outdated" described a state, not an action).
4. **`refactor(outdated)`** — let the local-resolver own the `link:`/`file:` skip, drop the matching short-circuit in `outdated.ts`.
5. **`refactor(outdated)`** — slim `LatestQuery` / `LatestInfo` to the bare essentials; move `pickRegistryForPackage` into the npm-resolver where it belongs; derive `current`/`wanted` display from `pkgSnapshot.version` in `outdated.ts`.
6. **`chore(outdated)`** — drop stale tsconfig project reference left behind by #5.
7. **`refactor(outdated)`** — drop `wantedRef` from the query; resolvers detect protocol from `bareSpecifier` alone.

## Final architecture

`@pnpm/resolving.resolver-base` defines a single tiny protocol:

```ts
interface LatestQuery {
  wantedDependency: WantedDependency
  compatible?: boolean
}

interface LatestInfo {
  latestManifest?: PackageManifest
}

type ResolveLatestFunction = (query: LatestQuery, opts: ResolveOptions) =>
  Promise<LatestInfo | undefined>
```

- `undefined` from a resolver means "I don't claim this dep — try the next one."
- `{}` means "I claim it, but I can't tell you what's latest" (policy-blocked, network unavailable, or a protocol with no concept of latest — git/tarball).
- `{ latestManifest }` is the happy path.

Each protocol resolver (npm/jsr/named-registry, git, tarball, local, node/bun/deno runtimes) exports its own `resolveLatest*` function alongside its `resolve*`. `@pnpm/resolving.default-resolver` composes them into a single first-match dispatcher, surfaced through `@pnpm/installing.client` as `createResolver(...).resolveLatest`.

`outdated.ts` is protocol-agnostic: dispatches, then derives `current`/`wanted` display from `pkgSnapshot.version` (falling back to the raw ref for URL-shaped refs where the URL is the only diff signal between commits), uses raw `wantedRef !== currentRef` for the lockfile-shifted check, and pulls `packageName` from `dp.parse(relativeDepPath).name` so aliased deps still report under the real package name.

Per-resolver responsibilities:
- **npm-resolver** (`resolveLatestFromNpm` / `resolveLatestFromJsr` / `resolveLatestFromNamedRegistry`): match their respective spec shapes, call the matching `resolveFromX` with `'latest'` (or the original spec under `--compatible`), handle `MINIMUM_RELEASE_AGE_VIOLATION` and `ERR_PNPM_NO_MATCHING_VERSION` so policy-blocked deps don't surface as available updates. Picks the per-package registry internally via its ctx.
- **node/bun/deno runtime resolvers**: claim deps via `bareSpecifier.startsWith('runtime:')` + alias match, query their release sources for the latest version (only the version — no asset-hash fetches), return `{ latestManifest }`.
- **git / tarball resolvers**: claim deps via spec shape, return `{}` (no concept of "latest"); the caller still surfaces a ref-mismatch report if the lockfile shifted to a different commit/URL.
- **local-resolver**: returns `undefined` so `link:`/`file:`/`workspace:` deps fall through and get silently skipped.
2026-05-19 19:15:07 +02:00
Zoltan Kochan
cd80b2c8ae chore(release): 11.1.3 (#11717) 2026-05-18 15:42:32 +02:00
Zoltan Kochan
4195766f10 feat: tighten minimumReleaseAge — auto-exclude, lockfile verification, and interactive prompt (#11705)
Three coordinated changes that close the silent-bypass gap in loose `minimumReleaseAge` mode AND the discover-by-loop UX problem in strict mode (#10488), plus a parallel hardening of the lockfile verifier:

1. **Auto-collect into `minimumReleaseAgeExclude` (loose mode)** — fresh resolutions that fall back to a version newer than the cutoff are auto-recorded into the workspace manifest's `minimumReleaseAgeExclude`. A single info message lists what was persisted. The workspace manifest writer dedupes against existing entries.

2. **Lockfile verifier runs in loose mode too** — `createNpmResolutionVerifier` no longer gates on `minimumReleaseAgeStrict`. With auto-collect keeping the exclude list explicit, every accepted-immature pin must be on the list — same contract strict mode enforces. Lockfiles produced under a weaker (or absent) policy that still hold immature entries are rejected the same way strict mode would.

3. **Strict mode prompts on the aggregate set instead of throwing on the first** — the resolver always collects every immature direct and transitive in one pass; the install command's `handleResolutionPolicyViolations` checkpoint decides what to do with the set. Interactive (TTY) prompts the user once with the full list (default = No) and asks whether to add them all to `minimumReleaseAgeExclude` and proceed. Approve → install continues, persisted at the end. Decline → resolution aborts before the lockfile, package.json, or modules dir is touched. Non-interactive (CI) keeps `ERR_PNPM_NO_MATURE_MATCHING_VERSION` as the exit code but lists every offending entry instead of just the first one the resolver happened to hit.

4. **The lockfile verifier now also covers `trustPolicy: 'no-downgrade'`.** The same post-resolution gate that re-checks `minimumReleaseAge` on lockfile entries now re-runs `failIfTrustDowngraded` for every npm-registry entry whose name isn't on `trustPolicyExclude`. The two checks share a single full-metadata fetch per package, so the extra coverage doesn't cost an extra round trip when both policies are active. Resolver-time trust checks still run as before — this just closes the gap when an entry bypasses resolution (peek path, `--frozen-lockfile`, restored CI cache).

The steady-state flows:

- **Loose mode, `pnpm add foo@immature`**: lockfile clean, verifier no-op, resolver picks via lowest-version fallback, `foo@immature` lands in `minimumReleaseAgeExclude`, install succeeds. Subsequent `pnpm install --frozen-lockfile` in CI verifies against the populated list and succeeds.
- **Strict mode (interactive), security bump to `next@15.5.9`**: resolver collects `next@15.5.9` AND every immature `@next/swc-*@15.5.9` shim. pnpm prompts once with the full list. User approves → install completes, all entries persisted in `pnpm-workspace.yaml`. CI then runs the populated config cleanly.
- **Strict mode (non-interactive / CI)**: aborts with `ERR_PNPM_NO_MATURE_MATCHING_VERSION` listing every immature entry's `name@version` and publish time — no more discover-by-loop dance.
- **Teammate commits a poisoned lockfile**: single-policy batches reject with `ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION` (or `ERR_PNPM_TRUST_DOWNGRADE`); a batch that trips both policies escalates to the generic `ERR_PNPM_LOCKFILE_RESOLUTION_VERIFICATION` and lists each entry's per-policy code in the breakdown.

### Implementation

- The npm resolver always falls back to the lowest matching version when no mature version satisfies the range, and flags the result with `ResolveResult.policyViolation` instead of throwing `NO_MATURE_MATCHING_VERSION`. `deferImmatureDecision` and `strictPublishedByCheck` are gone — every caller (install, dlx, outdated, self-update) inspects the violation and decides what to do.
- `policyViolation` flows from `ResolveResult` → `PackageResponse.body.policyViolation` → a shared accumulator in `ResolutionContext` → the `resolutionPolicyViolations` field on `resolveDependencyTree`'s return → out through `mutateModules` / `addDependenciesToPackage` to the install command.
- The violation type lives in `@pnpm/resolving.resolver-base` as `ResolutionPolicyViolation`; the npm resolver exports the two built-in codes (`MINIMUM_RELEASE_AGE_VIOLATION_CODE`, `TRUST_DOWNGRADE_VIOLATION_CODE`) as constants so consumers reference one source of truth.
- `handleResolutionPolicyViolations` runs between `resolveDependencyTree` and `resolvePeers` — the resolver-agnostic checkpoint where the install command's plan prompts (TTY) or aborts (no-TTY) with the full violation list.
- `setupPolicyHandlers` (in `installing/commands/src/policyHandlers.ts`) composes per-policy handlers behind a uniform plan interface: each handler has its own `handleResolutionPolicyViolations` (filter by code, decide what to do) and `pickManifestUpdates` (return a typed `WorkspaceManifestPolicyUpdates` patch the install command spreads into `updateWorkspaceManifest`). Today the only registered handler is `createMinimumReleaseAgeHandler` — strict + TTY prompts via `enquirer`, strict no-TTY throws `ERR_PNPM_NO_MATURE_MATCHING_VERSION` with every entry listed, loose mode auto-persists at the tail. Strict + `--no-save` is rejected up-front via `ERR_PNPM_STRICT_MIN_RELEASE_AGE_REQUIRES_SAVE`. Future policies plug in via a sibling factory + push into the handlers list, with no changes to `installDeps.ts` / `recursive.ts`.
- `installDeps` / `recursive` drain `pickManifestUpdates` after install and spread the patch into `updateWorkspaceManifest`. Plain `pnpm install` (no `--update`, no params) now still updates the workspace manifest when any handler contributes a patch. The `install` command's CLI schema gained `save: Boolean` so `--no-save` actually flows through to `opts.save = false` instead of being silently dropped by nopt.
- `makeResolutionStrict` (in `installing/client`) wraps a `ResolveFunction` and rethrows any `policyViolation` as a `PnpmError`. Used by `dlx` and `self-update` under strict `minimumReleaseAge` OR `trustPolicy: 'no-downgrade'`, since one-shot callers have nowhere to defer a violation to. Violation-code → error-code mapping lives in one place so future violation kinds get consistent UX.
- `createNpmResolutionVerifier` extends its check to `trustPolicy: 'no-downgrade'` — same per-entry fan-out, same cache key, sharing the full-metadata fetch with the maturity check. Trust-fetch errors now propagate up so the violation reason carries the underlying message (network code, 404 detail) instead of a generic "metadata is unavailable".
- `verifyLockfileResolutions`'s aggregate throw uses the per-policy code when every violation in the batch shares it, and escalates to a generic `LOCKFILE_RESOLUTION_VERIFICATION` (with per-entry codes in the breakdown) for mixed batches.
- The pnpm agent path refuses installs under `trustPolicy: 'no-downgrade'` (`ERR_PNPM_TRUST_POLICY_INCOMPATIBLE_WITH_AGENT`) — the agent has no server-side counterpart to that check yet, so silently allowing it would land a lockfile the local verifier would later reject. `minimumReleaseAge` is forwarded to the agent and enforced server-side, so that combination is fine.

### Pacquet parity

Pacquet only carries a stub reference to `minimumReleaseAgeExclude` (see `pacquet/crates/package-manager/src/version_policy.rs`); the broader `minimumReleaseAge` and `trustPolicy` policies aren't ported yet, so this feature is outside pacquet's current surface area. It'll come along when pacquet ports the policies.

### Closes

- Closes #10488 (resolves the discover-by-loop dance for security bumps without needing `withTransitives`).
2026-05-18 09:51:11 +02:00
Zoltan Kochan
5dc8be8a42 fix(graph-hasher): resolve GVS engine per-snapshot for runtime-pinned deps (#11693)
Closes #11690.

A dependency that declares `engines.runtime` in its manifest carries the desugared `dependencies.node: 'runtime:<version>'` pin in the lockfile, and pnpm's bin linker spawns that dep's lifecycle scripts through the pinned Node downloaded into `<pkgDir>/node_modules/node/`. The GVS hash and the side-effects-cache key prefix were still anchored to the install-wide runtime — so the pinning snapshot's slot encoded the wrong Node major, and a reinstall on the same host could read the cached side-effects under a key whose `<platform>;<arch>;node<major>` triple disagreed with the Node the build actually ran on.

Per-snapshot resolution now matches what `bins/linker` already does on a per-package basis: a snapshot's own pin wins; the install-wide value (from #11689's `findRuntimeNodeVersion`) is the fallback.

### TypeScript

- `deps/graph-hasher/src/index.ts:72-77` — adds `readSnapshotRuntimePin(children)`: pulls the bare Node version from a graph node's `children.node` entry when that points at a `node@runtime:<version>` snapshot. Factors out a small `extractRuntimeNodeVersion(snapshotKey)` parser shared with `findRuntimeNodeVersion`.
- `deps/graph-hasher/src/index.ts:115-116,245-246` — `calcDepState` and `calcGraphNodeHash` consult `readSnapshotRuntimePin(graph[depPath].children)` first and only fall back to the install-wide `nodeVersion` parameter when the snapshot doesn't pin its own Node. No caller changes required — install-wide fallback continues to be computed via `findRuntimeNodeVersion(Object.keys(graph))` at each call site.
- **Refactor (separate commit):** `findRuntimeNodeVersion` moved from `@pnpm/engine.runtime.system-node-version` to `@pnpm/deps.graph-hasher` (along with the new `readSnapshotRuntimePin`). `system-node-version` is about probing the *host* Node — `getSystemNodeVersion`, `engineName`. The lockfile-shape parsers fit better next to the package that actually composes the engine string. Every caller already depended on graph-hasher, so no new deps; six packages drop the now-unused dependency on `system-node-version`.

### Pacquet

- `pacquet/crates/package-manager/src/install_frozen_lockfile.rs:1309-1345` — new `find_own_runtime_node_major(snapshot)` reads a snapshot's `dependencies` for a `node` entry with `Prefix::Runtime`, returning the bare major.
- `pacquet/crates/package-manager/src/virtual_store_layout.rs:178-205` — `VirtualStoreLayout::new` resolves engine per-snapshot inside the hash loop via `engine_name(own_major, None, None)` when the snapshot pins, otherwise inherits the install-wide `engine` argument.

### Migration

Snapshots of dependencies that declare their own `engines.runtime` re-hash under that dep's pinned Node instead of the install-wide value. Old slots become prune-eligible on next install.
2026-05-17 13:25:05 +02:00
Zoltan Kochan
3ddde2b975 fix(pacquet): align GVS slot layout with pnpm (#11689)
## Summary

Adds three end-to-end **GVS parity tests** under `pacquet/crates/cli/tests/pnpm_compatibility.rs` that run `pnpm install` and `pacquet install --frozen-lockfile` against the same workspace + lockfile with `enableGlobalVirtualStore: true`, then diff the resulting `<store>/v11/links/` slot trees. The tests surfaced three independent divergences, each fixed in its own commit set:

1. **`<store>/v11/links` prefix.** `getStorePath` appends `STORE_VERSION` (`v11`) to the configured `storeDir` before `extendInstallOptions.ts:352` joins `'links'` onto it, so pnpm's GVS lives at `<store>/v11/links/` — pacquet's `StoreDir::links()` was one level shallower, joining onto `self.root`. Same gap on `projects()`. Anchored both under `self.v11()` so the on-disk paths agree.

2. **GVS engine-name resolution.** `ENGINE_NAME` was computed from `process.version`, which is wrong in two cases:
   - **`@pnpm/exe` SEA bundle.** The bundle has its own embedded Node, not the `node` on PATH that runs lifecycle scripts. Two pnpm installs on the same machine (one SEA, one npm-package) therefore disagreed on the cache key, partitioning the side-effects cache and the global virtual store.
   - **`engines.runtime` / `devEngines.runtime` pin.** When a project pins a Node version, pnpm downloads that Node into `node_modules/node/` and uses it to run lifecycle scripts. But the hash still anchored to whichever Node ran pnpm itself, not to the pinned Node.

   `@pnpm/engine.runtime.system-node-version` now exports `engineName(nodeVersion?)` and `findRuntimeNodeVersion(snapshotKeys)`. The override has priority; otherwise the helper falls through to `getSystemNodeVersion()` — which already prefers shell `node --version` over `process.version` in SEA contexts — and finally to `process.version` as a last resort. `@pnpm/deps.graph-hasher`'s `calcDepState`, `calcGraphNodeHash`, and `iterateHashedGraphNodes` accept an optional `nodeVersion`. Every install-side caller (`deps.graph-builder`, `installing.deps-resolver`, `installing.deps-restorer`, `installing.deps-installer/install/link`, `building.during-install`, `building.after-install`) derives the project's pinned runtime via `findRuntimeNodeVersion` once per invocation and forwards it. The legacy `ENGINE_NAME` constant in `@pnpm/constants` is unchanged so external consumers and existing tests keep working.

   Pacquet mirrors this with `find_runtime_node_major` in `install_frozen_lockfile.rs` — it scans the lockfile's `snapshots:` map for a `node@runtime:<version>` entry and uses that major outright, only falling back to the host probe when no pin is present.

3. **Slot bin-shim layout.** Pacquet was emitting `.cmd` / `.ps1` shims on every host platform, even though pnpm only writes them on Windows ([`@zkochan/cmd-shim` `createCmdFile: isWindows`](https://github.com/pnpm/cmd-shim/blob/0d79ca9534/src/index.ts#L32) + `bins/linker`'s [`POWER_SHELL_IS_SUPPORTED = IS_WINDOWS`](https://github.com/pnpm/pnpm/blob/29a42efc3b/bins/linker/src/index.ts#L28) gate). Pacquet also excluded the slot's own package from the slot-local `node_modules/.bin/` based on a stale assumption ("which pnpm doesn't"), but pnpm's [`linkBinsOfDependencies`](https://github.com/pnpm/pnpm/blob/29a42efc3b/building/during-install/src/index.ts#L272-L298) appends `depNode` to the bin-source list unconditionally, so a leaf package like `hello-world-js-bin` writes a self-shim at `<slot>/node_modules/<pkg>/node_modules/.bin/<pkg>`. Both behaviors now match pnpm.

## Test plan

- [x] `cargo nextest run -p pacquet-cli --test pnpm_compatibility` — 5 active tests pass, 1 ignored (see below)
- [x] `cargo nextest run -p pacquet-store-dir -p pacquet-config -p pacquet-cmd-shim -p pacquet-package-manager` — 600+ tests pass after the prefix / bin-shim updates
- [x] `same_global_virtual_store_layout_pure_js` — pacquet & pnpm produce byte-identical `<store>/v11/links/` trees for `@pnpm.e2e/hello-world-js-bin-parent`
- [x] `same_global_virtual_store_layout_diamond` — same for `pkg-with-1-dep` + `parent-of-pkg-with-1-dep`, verifying `calc_dep_graph_hash` memoization parity
- [x] Three new TS unit tests in `engine/runtime/system-node-version/test/` cover the `engineName(version)` override branch and `findRuntimeNodeVersion`'s extraction rule (with and without peer suffix)
- [ ] `same_global_virtual_store_layout_with_approved_postinstall` is currently `#[ignore]`d. It requires pnpm and pacquet to agree on the `<platform>;<arch>;node<major>` triple in the engine-included hash branch. The `pnpm/setup` action on CI installs an `@pnpm/exe` SEA bundle whose embedded Node (node26) differs from the runner's PATH `node` (node24), so the digests don't line up. The pnpm-side fix in this PR resolves `engineName()` via `getSystemNodeVersion()` which prefers the shell `node`, so once a published pnpm version with the fix reaches `pnpm/setup` the test will pass without modification — re-enable it then. The other two GVS parity tests are unaffected since they exercise the engine-agnostic branch.

## Notes

- Two pacquet integration tests in `package-manager/src/install/tests.rs` had hard-coded `<store_dir>/projects/` assertions; updated to `<store_dir>/v11/projects/` to follow the prefix fix.
- The `link_bins_rewrites_when_only_sh_flavor_exists` cmd-shim test is now `#[cfg(windows)]` — the upgrade-recovery scenario it exercises is meaningless on Unix where `.cmd`/`.ps1` are no longer written in the first place.
- Review feedback addressed: (a) test YAML helper now guarantees a trailing newline before appending GVS keys; (b) `findRuntimeNodeVersion` calls in `installing/deps-restorer/` switched from `Object.keys(graph)` (install-dir-keyed in that module) to extracting `depPath` per node, with the computation lifted out of the recursion; (c) `dlx.e2e.ts`'s `jest.unstable_mockModule` against `@pnpm/engine.runtime.system-node-version` now forwards every exported symbol so transitive importers of `engineName` don't break.
- Known caveat: pacquet's non-lockfile install path (`run_with_readdir`) still excludes the slot's own bin via `link_bins_excluding`. That path runs only for the legacy flat layout where GVS parity isn't a constraint, so it's deliberately out of scope here.
- Known caveat tracked in #11690: when a dependency's own manifest declares `engines.runtime`, the resolver desugars it into a regular `dependencies.node: 'runtime:<v>'` entry on that package, so the **deps** portion of the hash captures it on both sides. The **engine** portion is still install-wide rather than per-snapshot, so cached side-effects for dep-pinned runtimes can be reused under the wrong host Node. pnpm has this same gap today; closing it on both sides requires per-snapshot engine resolution and is outside this PR's scope.
2026-05-16 23:58:53 +02:00
btea
b6e2c8c5ac fix(engine.pm.commands): honor minimumReleaseAgeExclude in self-update (#11664)
* fix(engine.pm.commands): honor minimumReleaseAgeExclude in self-update

* refactor(config.version-policy): centralize publishedBy policy derivation

Extract the publishedBy / publishedByExclude derivation duplicated across
selfUpdate, dlx, outdated, and deps-resolver into a new
`getPublishedByPolicy()` helper, and the version-policy error rewrap
into `createPackageVersionPolicyOrThrow()`.

Also adds the global self-update test branch (no wantedPackageManager)
requested in PR review, and harmonizes the dlx/outdated error code
for invalid minimumReleaseAgeExclude patterns with install/self-update.

* style(config.version-policy): rename 'callsite' to 'call site' to satisfy cspell

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-05-15 08:12:23 +00:00
Zoltan Kochan
8a80235c7b chore(release): 11.1.2 2026-05-14 13:31:53 +02:00
Zoltan Kochan
9a327522ce chore(release): 11.1.1 2026-05-12 12:56:32 +02:00
Zoltan Kochan
732312f49e chore(release): 11.1.0 2026-05-11 19:56:10 +02:00
Zoltan Kochan
a575dd2461 fix: allow pnpm runtime set in workspace root (#11584)
* fix: allow `pnpm runtime set` in workspace root

`pnpm runtime set <name> <version>` previously failed in the root of a
multi-package workspace with the `ADDING_TO_ROOT` error. Since installing
a runtime is workspace-wide configuration, pass `--workspace-root` to the
underlying `pnpm add` call when not running globally.

* fix: use --ignore-workspace-root-check instead of --workspace-root

The previous fix forced every non-global runtime install to land in
the workspace root, which broke running the command from a workspace
subproject. Switch to --ignore-workspace-root-check, which only
suppresses the safety warning and leaves the install target as the
current directory.
2026-05-11 19:53:44 +02:00
Pig Fang
6bd335e65c fix: change message for case that downgrading pnpm (#11560) 2026-05-11 12:13:52 +02:00
Zoltan Kochan
f2b28f85ff chore(release): 11.0.9 2026-05-09 02:06:35 +02:00
Zoltan Kochan
a516c24ce4 chore(release): 11.0.8 2026-05-07 08:35:07 +02:00
Zoltan Kochan
0c3ef0ec94 chore(release): 11.0.7 2026-05-07 00:21:03 +02:00
Zoltan Kochan
997a8ca9bf fix(exe): route pn/pnpx/pnx through .exe hardlinks on Windows (#11486) (#11501)
* 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.
2026-05-06 22:45:44 +02:00
Zoltan Kochan
65f9327014 chore(release): 11.0.6 2026-05-05 19:50:32 +02:00
Zoltan Kochan
0219ab2484 fix(self-update): refresh legacy v10 bootstrap shim at PNPM_HOME (#11467)
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.
2026-05-05 19:37:03 +02:00
Zoltan Kochan
cc373c39f1 chore(release): 11.0.5 2026-05-04 22:14:24 +02:00
Zoltan Kochan
3a5534d75e chore(release): 11.0.4 2026-05-03 01:24:22 +02:00
Zoltan Kochan
c1d29d2258 fix(self-update): do not downgrade when latest dist-tag is older (#11435)
* fix(self-update): do not downgrade when latest dist-tag is older

`pnpm self-update` defaults to the `latest` dist-tag, but `latest` on the
registry can lag the installed version when a new major has shipped
without being tagged. Refuse to downgrade in that case. Users can still
run `pnpm self-update latest` (explicit) to force the downgrade.

Closes #11418

* fix(self-update): use lockfile-pinned version for project-pin downgrade check

When a project pins pnpm via a range (e.g. `devEngines.packageManager.version: ">=8.0.0"`)
and the env lockfile pins an exact version above the range's lower bound,
the previous guard compared the resolved `latest` against `semver.minVersion(spec)`
and missed the downgrade. Read `packageManagerDependencies.pnpm.version` from
`pnpm-lock.yaml` and use the max of (lockfile-pinned, spec.minVersion) as the
current version. Also fix the explicit-`latest` test which mocked `latest`
as newer than the current version, defeating its own assertion.

* chore(engine.pm.commands): add lockfile/fs project reference to tsconfig
2026-05-03 01:14:54 +02:00
Zoltan Kochan
6ef34b7a11 chore(release): 11.0.3 2026-04-30 23:03:46 +02:00
Zoltan Kochan
184ce26f3f docs: fix package names in README files (#11409)
* docs: fix package names in README files

* docs: update links to point to npmx.dev
2026-04-30 22:59:17 +02:00
Zoltan Kochan
a53f78b111 chore(release): 11.0.2 2026-04-30 17:16:34 +02:00
Zoltan Kochan
38ffda2a18 chore(release): 11.0.1 2026-04-29 23:00:21 +02:00
Zoltan Kochan
0fbcf74aac fix: sync packageManager and devEngines.packageManager on self-update (#11395)
* fix: sync packageManager and devEngines.packageManager on self-update

When `package.json` declares both `packageManager` and
`devEngines.packageManager`, `pnpm self-update` previously bumped only
the latter — leaving Corepack (which reads `packageManager`) pinned to
the old version until a manual edit.

Now, when `packageManager` pins pnpm, both fields are rewritten to the
new exact version on update: `packageManager` to `pnpm@<version>`
(without an integrity hash) and `devEngines.packageManager.version` to
the same exact `<version>` (dropping any range operator). When only
`devEngines.packageManager` is declared, the existing range-preserving
behavior is unchanged.

Closes #11388

* refactor: export and reuse parsePackageManager from @pnpm/config.reader

Drop the inline duplicate in self-updater and use the existing
parser from config.reader. Same parsing rules (strips integrity
hash, rejects URL-style refs).

* refactor: collapse devEngines.packageManager array/object branches

Resolve to the underlying pnpm entry first (whether the field is an
array or an object) and run the version-update logic once, instead of
duplicating it across both branches.
2026-04-29 22:57:38 +02:00
Zoltan Kochan
8aeeff4c46 chore(release): 11.0.0 2026-04-28 11:27:43 +02:00
Zoltan Kochan
77e913c0b4 feat(self-update): point users at the migration guide across major bumps (#11354)
When `pnpm self-update <version>` crosses a pnpm major (upward) from
the version being upgraded from, print a one-line pointer to the
versioned migration guide on pnpm.io.

The "from" version is the project's `packageManager`/`devEngines.packageManager`
pin when present (so the hint still fires if the running pnpm is already
the new major — e.g. corepack-managed), falling back to the running
binary's version otherwise. No-op updates (target === previous) are
silent.

v11 points at https://pnpm.io/11.x/migration. Future majors register an
entry in the in-file `MAJOR_UPGRADE_HINTS` table.
2026-04-28 02:01:06 +02:00
Yuriy Matskanyuk
390b9d188d feat: add info about update started (#11334) 2026-04-28 02:00:58 +02:00
Akihiro Nagai
9b23098cf6 docs(env): update Node.js examples in help output (#11369) 2026-04-28 02:00:15 +02:00
Zoltan Kochan
499bc677e2 chore: remove unused getNodeExecPath helpers (#11336)
Drops `getNodeExecPath`, `getNodeExecPathInBinDir`, and
`getNodeExecPathInNodeDir` along with their now-unused `which` dependency.
None of these helpers were referenced anywhere in the codebase.
2026-04-28 01:59:23 +02:00
Zoltan Kochan
4d7cd56ccc chore: upgrade @typescript/native-preview to 7.0.0-dev.20260421.2 (#11332)
* chore: upgrade @typescript/native-preview to 7.0.0-dev.20260421.2

- Add explicit `types: ["node"]` to the shared tsconfig because tsgo
  20260421 no longer auto-acquires `@types/*` from `node_modules`.
- Refactor test files to explicitly import jest globals (`describe`,
  `it`, `test`, `expect`, `beforeEach`, etc.) from `@jest/globals`
  instead of relying on `@types/jest` ambient declarations. Under the
  new tsgo build, `import { jest } from '@jest/globals'` shadows the
  ambient `jest` namespace, breaking `@types/jest`'s `declare var
  describe: jest.Describe;` globals.
- Add `@jest/globals` to each package's devDependencies where tests
  now import from it, and add `@types/node` to packages that need it
  but were relying on hoisted resolution.
- Replace `fail()` calls with `throw new Error(...)` since `fail` is
  no longer globally available.

* chore: fix remaining tsgo type-strictness errors

- Strip `as <PnpmType>` casts on objects passed to toMatchObject /
  toStrictEqual / toEqual; @jest/globals rejects the typed objects
  (which include AsymmetricMatchers) vs. the repo-specific type.
- Type `jest.fn<...>()` explicitly where the mock's signature matters
  for toHaveBeenCalledWith.
- Replace `beforeEach(() => X)` with `beforeEach(() => { X })` so the
  return value is void, as the stricter jest typing requires.
- Use `expect.objectContaining({...})` in one place where the full
  expected object triggered stricter type resolution.
- Cast `prompt.mock.calls` arg through `as unknown as Record<...>[]`
  for patch.test.ts's nested-array matchers.
- Fix off-by-one `<reference path>` in pnpm/test/getConfig.test.ts
  that only surfaced now.
- Move `@jest/globals` from devDependencies to dependencies in the
  two `__utils__` packages that import it from `src/`.
- Clean up unused imports from the @jest/globals migration.

* chore: address Copilot review on #11332

- Move misplaced `@jest/globals` imports to the top import block in
  checkEngine, run.ts, and workspace/root-finder tests where the
  script dropped them below executable code.
- Replace `try { await x(); throw new Error('should have thrown') } catch`
  in bins/linker, lockfile/fs, and resolving/local-resolver tests with
  `await expect(x()).rejects.toMatchObject({...})`. The old pattern
  swallowed an unrelated `throw` if the under-test call silently
  succeeded, which would fail on the catch-block assertion with a
  misleading message.
2026-04-21 23:21:52 +02:00
Zoltan Kochan
fd437ded13 chore(release): 11.0.0-rc.4 2026-04-21 15:03:02 +02:00
Zoltan Kochan
ef4ef7b72d fix(exe): restore legacy @pnpm/{macos,win,linux,linuxstatic}-{x64,arm64} names (#11327)
* fix(exe): restore legacy @pnpm/{macos,win,linux,linuxstatic}-{x64,arm64} package names

Reverts the published package names renamed in #11316 back to the legacy
scheme so `pnpm self-update` from v10 continues to resolve. v10's
self-updater looks up the platform child by its legacy name; the
scope-nested `@pnpm/exe.<platform>-<arch>[-musl]` rename broke that lookup.

Workspace directory layout (`pnpm/artifacts/<platform>-<arch>[-musl]/`)
and the GitHub release asset filenames (`pnpm-linux-x64-musl.tar.gz`,
`pnpm-darwin-*.tar.gz`, `pnpm-win32-*.zip`) stay on the new scheme.

`linkExePlatformBinary` now checks both legacy and future naming
schemes, so a later rename can ship without a v10-compatibility hazard.

* style: fix indentation in setup.test.ts

* refactor: extract legacyOsSegment into a switch helper

* refactor: defer platform detection until after exe dir check

* test: use familySync() in fixtures so musl hosts match implementation

Test fixtures were passing null for libcFamily while linkExePlatformBinary
and setup.js both use detect-libc at runtime. On musl Linux the fixtures
built linux-*/exe.linux-* while the implementation looked up
linuxstatic-*/exe.linux-*-musl. Also bump @pnpm/exe in the changeset.
2026-04-21 14:52:41 +02:00
Zoltan Kochan
421317c31a feat!: skip npm, npx, and corepack when installing node runtime (#11325)
## Summary

- pnpm installing a Node.js runtime (`node@runtime:<ver>`, `pnpm env use`, `pnpm runtime set node`) no longer extracts the bundled `npm`, `npx`, and `corepack`. These make up ~2,800 of ~5,800 files in a typical Node.js archive, so skipping them materially reduces hashing, CAS writes, SQLite index inserts, and import/link work.
- Users who still need `npm` can install it as a separate package.

## How

A new optional `ignoreFilePattern` (regex source string, serializable across the worker boundary) threads through `FetchOptions` → `tarball-fetcher` → `@pnpm/worker` → `cafs.addFilesFromTarball`. `cafs.addFilesFromTarball` now accepts a per-call ignore on top of the existing cafs-level `ignoreFile`; the two are combined.

`@pnpm/fetching.binary-fetcher` defines the Node-specific regex and applies it when `opts.pkg.name === 'node'`:

- Tarball path: sets `ignoreFilePattern`.
- Windows zip path: new `ignoreEntry?: RegExp` on `AssetInfo`; `extractZipToTarget` strips the `basename/` prefix and skips matching entries before `zip.extractEntryTo`.

`@pnpm/engine.runtime.node-resolver`'s `getNodeBinsForCurrentOS` drops `npm`/`npx` so pnpm no longer creates shims for bins that no longer exist.

## Breaking change

Shipping in v11. After this lands, `pnpm runtime set node` / `node@runtime:<version>` no longer puts `npm`, `npx`, or `corepack` on `$PATH`. Scripts that call them directly will need to install npm separately.
2026-04-21 13:44:48 +02:00
Zoltan Kochan
aa93759d9b chore(release): drop eslint from lib prepublishOnly (#11320)
Library packages had `prepublishOnly: pn compile`, which expands to
`tsgo --build && pn lint --fix`. During `pn release` that runs eslint
against ~150 packages for no benefit — the code has already been linted
in CI and the release flow's upfront compile has already built dist/.
Switch lib prepublishOnly to a bare `tsgo --build` so the safety-net
compile stays but the per-package eslint cost is gone.
2026-04-21 01:18:03 +02:00
Zoltan Kochan
fcdd50aaa7 chore(release): 11.0.0-rc.3 2026-04-21 00:17:38 +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
96ece9d736 chore(release): 11.0.0-rc.2 2026-04-17 18:21:35 +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
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
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
Zoltan Kochan
06d6c2d405 chore(release): 11.0.0-rc.0 2026-04-10 18:30:33 +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
Zoltan Kochan
45a6cb6b2a refactor(auth): unify auth/SSL into structured configByUri (#11201)
Replaces the dual `authConfig` (raw .npmrc) + `authInfos` (parsed auth) + `sslConfigs` (parsed SSL) pattern with a single structured `configByUri: Record<string, RegistryConfig>` field on Config.

### New types (`@pnpm/types`)
- **`RegistryConfig`** — per-registry config: `{ creds?: Creds, tls?: TlsConfig }`
- **`Creds`** — auth credentials: `{ authToken?, basicAuth?, tokenHelper? }`
- **`TlsConfig`** — TLS config: `{ cert?, key?, ca? }`

### Key changes
- Rewrite `createGetAuthHeaderByURI` to accept `Record<string, RegistryConfig>` instead of raw .npmrc key-value pairs
- Eliminate duplicate auth parsing between `getAuthHeadersFromConfig` and `getNetworkConfigs`
- Remove `authConfig` from the install pipeline (`StrictInstallOptions`, `HeadlessOptions`), replaced by `configByUri`
- Remove `sslConfigs` from Config — SSL fields now live in `configByUri[uri].tls`
- Remove `authConfig['registry']` mutation in `extendInstallOptions` (default registry now passed directly to `createGetAuthHeaderByURI`)
- `authConfig` remains on Config only for raw .npmrc access (config commands, error reporting, config inheritance)

### Security
- tokenHelper in project .npmrc now throws instead of being silently stripped
- tokenHelper execution uses `shell: false` to prevent shell metacharacter injection
- Basic auth uses `Buffer.from().toString('base64')` instead of `btoa()` for Unicode safety
- Dispatcher only creates custom agents when entries actually have TLS fields
2026-04-05 20:15:10 +02:00