Commit Graph

11955 Commits

Author SHA1 Message Date
Zoltan Kochan
b7e3ea202c test(npm-resolver): name the closure param to satisfy dylint 2026-06-19 00:29:51 +02:00
Zoltan Kochan
7ffe9ab283 docs: drop cross-crate intra-doc link to satisfy cargo doc -D warnings 2026-06-19 00:26:54 +02:00
Zoltan Kochan
9dd6fbf4d8 docs: reword native binary comment to satisfy cspell 2026-06-19 00:20:02 +02:00
Zoltan Kochan
c8f0dc36cf style: rename libcs and avoid %2F paths to satisfy cspell 2026-06-19 00:18:39 +02:00
Zoltan Kochan
a9dbe11673 test(npm-resolver): widen optionalDependencies type in native bin test 2026-06-19 00:16:15 +02:00
Zoltan Kochan
9556de077d chore: remove scratch implementation plan 2026-06-19 00:14:59 +02:00
Zoltan Kochan
d9e1241396 feat(resolving-npm-resolver): port native bin dependencies synthesis
Port the pnpm-side feature: when a resolved registry package is a native
bin dependency (built-in defaults `pacquet`, `@pnpm/pacquet`, plus the
`nativeBinDependencies` setting), the npm resolver fetches each platform
`optionalDependencies` packument and returns a `variations` resolution
over those platform packages instead of the wrapper tarball. Only the
host's binary is then fetched and linked directly — no launcher shim, no
lifecycle scripts — reusing the existing binary/variations install
pipeline (`select_platform_variant`, `fetch_binary_resolution_to_cas`,
bin-linking).

Adds `Config::native_bin_dependencies` (default list + `pnpm-workspace.yaml`
override), threads it into `NpmResolver`, and synthesizes the variants
concurrently. The platform binary path is the launcher's command name at
the package root (`.exe` on Windows), matching the pacquet / `@pnpm/exe`
layouts.
2026-06-19 00:07:01 +02:00
Zoltan Kochan
1d73805c93 feat(store): thread nativeBinDependencies config into the resolver
Pass the `nativeBinDependencies` setting from Config through the store
controller into the client/resolver options so user-added packages take
the native bin variants path (the built-in defaults already applied).

Adds the changeset for the new setting.
2026-06-18 23:30:08 +02:00
Zoltan Kochan
16f51b11f7 feat(npm-resolver): synthesize native bin dependencies into platform variants
A package that ships a JS launcher shim plus per-platform native binaries
as optionalDependencies (e.g. `pacquet`) otherwise installs the wrapper +
every optional dep and links a Node launcher shim that re-spawns the real
binary. When such a package is a native bin dependency, the npm resolver
now returns a `variations` resolution over its platform packages instead
of the wrapper tarball, so only the host's binary is fetched and linked
directly — no launcher shim, no lifecycle scripts. The whole downstream
variants pipeline (selection, BinaryFetcher, bin-linking) is reused.

The set of native bin dependencies is the hardcoded default list
(`pacquet`, `@pnpm/pacquet`) plus the new `nativeBinDependencies` setting.

Adds the `nativeBinDependencies` config setting and the synthesis in
`resolveNativeBinVariations`, gated so a wrapper with no platform-tagged
optional dependency falls back to its normal tarball resolution.
2026-06-18 23:18:08 +02:00
Zoltan Kochan
4f8ffd59e3 docs: revise plan to synthesize variants inside the npm resolver
Move the synthesis into resolveFromNpm (one more resolution type) instead
of a separate resolver that re-runs npm resolution; native-deps packages
are plain registry packages, so the npm resolver is their natural home.
2026-06-18 22:34:18 +02:00
Zoltan Kochan
8b1b6c2a9b docs: add native-binary-deps implementation plan
Working plan for synthesizing a VariationsResolution for packages that
ship a JS launcher shim plus per-platform native binaries as
optionalDependencies (`pacquet`, `@pnpm/pacquet`, `@pnpm/exe`), reusing
the existing variants pipeline so only the matching platform binary is
installed and linked with no launcher shim and no lifecycle scripts.

Scratch doc; to be removed before the real PR.
2026-06-18 22:29:38 +02:00
Zoltan Kochan
511e74200d fix: update vulnerable dependencies (#12503) 2026-06-18 19:42:35 +02:00
Zoltan Kochan
33745b892b fix(ci): grant pull-requests: write so the review label step can label PRs (#12501) 2026-06-18 16:11:04 +02:00
Zoltan Kochan
cb3d8e75bd test(exec): fix Windows failure in PnP require option test (#12499)
The 'exec should merge node options with PnP require option' test (added in
#12430) hardcoded an unquoted '--require=<path>' expectation. On Windows the
.pnp.cjs path contains backslashes, which makeNodeRequireOption quotes and
escapes for Node's NODE_OPTIONS tokenizer, so the assertion mismatched and the
test failed on Windows runners.

Derive the expected NODE_OPTIONS from makeNodeRequireOption itself so the
expectation matches the implementation's quoting on every platform.
2026-06-18 14:02:12 +00:00
Zoltan Kochan
8d17c7b8cd chore: update lockfile, Node.js, pnpm, and pacquet versions (#12441)
* chore: update lockfile, Node.js, pnpm, and pacquet versions

* fix: node range

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-18 14:35:34 +02:00
Zoltan Kochan
6ee484e3c5 fix(ci): run PR review automation for fork PRs via workflow_run (#12493)
Approvals on PRs from forks never got the `reviewed: coderabbit` label or a
Discord announcement. A pull_request_review run triggered by a forked PR is
granted a read-only token and no secrets, so the label step failed with HTTP
403 and the Discord step had no webhook.

Split the automation in two: the pull_request_review workflow now only records
the approval as an artifact (no token, no secrets), and a new workflow_run
companion runs from the default branch in base-repo context — where it has the
write-scoped token and secrets — to add the label and post to Discord.

The privileged half never checks out or executes PR content: it reads inert
data files, validates the PR number is an integer, maps the reviewer to a fixed
label, requests only actions:read + issues:write, surfaces a failed (vs absent)
artifact download instead of passing as a silent no-op, and scopes the Discord
webhook secret to the announce step.

Also documents that agents must open PRs using .github/pull_request_template.md,
since gh pr create does not apply it automatically.
2026-06-18 14:25:52 +02:00
Zoltan Kochan
93458600a8 chore(release): 11.8.0 (#12492)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
v11.8.0
2026-06-18 12:17:52 +02:00
Maël Nison
0474a9c3b1 feat: add support for package maps (#12430)
Generate a Node.js package map at `node_modules/.package-map.json` on every
isolated or hoisted install, including under the global virtual store, so that
third-party tooling can start experimenting with package maps. The file is
serialized compactly.

Two settings control how the map is consumed:

- `node-experimental-package-map` (default: off): inject
  `--experimental-package-map` into `NODE_OPTIONS` for the Node.js scripts pnpm
  runs — dependency lifecycle scripts, `pnpm exec`, and `pnpm run` (including
  recursive runs).
- `node-package-map-type` (`standard` | `loose`): choose between a strict map
  and one that tolerates hoisting-like access.

Covered by both the pnpm CLI and the pacquet (Rust) implementation.

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-06-18 11:51:50 +02:00
Alessio Attilio
c2414e8236 docs: add PR template and clarify changeset purpose (#11290)
Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-06-18 11:47:53 +02:00
Zoltan Kochan
fe66535046 fix: converge incremental install by refreshing stale transitive pins (#12472)
When a package is reused from the lockfile, its child edges are taken
verbatim and bypass the preferred-versions walk, so a transitive
dependency can stay pinned to an older version even after a direct
dependency resolved to a higher version that satisfies the same range —
leaving the lockfile non-convergent (an incremental install keeps a
duplicate that a fresh install would not).

The resolver now refreshes such a stale pin to the higher
direct-dependency version during resolution, via `preferredVersion`
(singular), which overrides the EXISTING_VERSION_SELECTOR_WEIGHT stability
bias. The older version is never resolved or fetched, and the incremental
result converges to what a fresh install produces. The pick is anchored to
direct dependencies (which resolve first), so it restores the automatic
dedupe removed in pnpm/pnpm#11110 without reintroducing its
non-determinism, and unlike the post-pass in pnpm/pnpm#11502 it does not
over-fetch.

pacquet is ported in the same change. Its full-subtree lockfile reuse is
coarser than pnpm's per-edge reuse, so it records per importer which direct
deps changed and their resolved versions, declines full-subtree reuse for a
parent that depends on a changed direct dep, and forces the higher version
in the child walk. Range satisfaction uses plain semver (not
prerelease-inclusive), matching pnpm's semver.satisfies(.., true).
2026-06-18 10:59:15 +02:00
Juan Picado
ff9a1cfa1d feat(pnpr): per-uplink verdaccio settings (maxage, timeout, max_fails, fail_timeout, cache) (#12375)
Add verdaccio's per-uplink tuning knobs to pnpr's proxy uplinks, parsed
from the same config.yaml shape verdaccio uses:

- maxage:       per-uplink packument freshness window; overrides the
                global packument_ttl when set, otherwise defers to it.
- timeout:      per-request deadline applied to every upstream fetch
                (default 30s).
- max_fails /   circuit breaker: after max_fails consecutive failures the
  fail_timeout  uplink short-circuits with a 503 until the fail_timeout
                cooldown lapses, then one probe per window is allowed
                through (defaults 2 / 5m). max_fails: 0 disables it.
- cache:        when false, tarballs stream through without being written
                to the local mirror (default true).

Interval values accept verdaccio's format ("2m", "30s", "1h30m", or a
bare number of seconds, as either a YAML string or a numeric scalar);
an unparsable or out-of-range value is a named InvalidConfig error
rather than a silent fallback or a panic. Defaults match verdaccio
(timeout 30s, max_fails 2, fail_timeout 5m, cache true).

Circuit breaker behavior:
- Only a 5xx or transport error counts as a failure; a non-404 4xx
  (auth / rate-limit / bad request) surfaces verbatim and never opens
  the circuit.
- The half-open probe is gated on the cooldown window, so exactly one
  probe runs per fail_timeout and a cancelled or dropped probe cannot
  stick the breaker open.
- An open breaker reports the configured uplink name, not its upstream
  URL, so the 503 does not disclose internal endpoints.

Part of the verdaccio-parity tracking issue pnpm/pnpm#11973.

Co-authored-by: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-06-17 23:44:03 +00:00
Zoltan Kochan
bee4bf41ca fix: reject path-traversal config dependency names from the env lockfile (#12470)
Config dependency names and versions are read from the committed env lockfile
(pnpm-lock.yaml) and the legacy inline-integrity format in pnpm-workspace.yaml,
and both become path segments of the directories pnpm creates during install
(node_modules/.pnpm-config/<name> and the global virtual store's
<name>/<version>/<hash>). They were used unvalidated, so a malicious repository
could commit a traversal-shaped name (../../PWNED) or version (../../../PWNED)
and make `pnpm install` create symlinks or write package files outside those
roots — triggered on install, even with --ignore-scripts.

Add verifyEnvLockfile, an offline structural gate that validates every config
dependency and optional-subdependency name (must be a valid npm package name)
and version (must be an exact semver version) before any path is built from it.
It runs at the install boundary and, through a single writeVerifiedEnvLockfile
seam, before the env lockfile is ever persisted, so an invalid entry is rejected
with no write side effect. __proto__ names are rejected too (the validation
accumulators use null-prototype objects so the key can't slip past Object.keys).

The same fix and structure land in pacquet to keep the two stacks in sync.

Fixes GHSA-qrv3-253h-g69c.
2026-06-17 23:03:38 +00:00
Zoltan Kochan
6e218db8cb fix(pacquet): resolve package-manager bootstrap through trusted registries (#12471)
Port the GHSA-j2hc-m6cf-6jm8 fix (pnpm/pnpm#12296) to pacquet.

When pnpm auto-switches to the version requested by `packageManager` /
`devEngines.packageManager`, the bootstrap (`pnpm` / `@pnpm/exe`) must be
resolved through trusted registries only. Pacquet was resolving it through
`config.resolved_registries()`, which a malicious repository controls via
the workspace `.npmrc` or `pnpm-workspace.yaml` `registries:` block.

Add `Config::package_manager_bootstrap`, built in `Config::current()` from a
trusted-only fold of the URL-scoped env, `auth.ini`, and user `.npmrc`
sources (the project `.npmrc` is excluded), reusing the existing
registry/proxy/TLS/auth application logic. It defaults to the public npm
registry, and `PNPM_CONFIG_REGISTRY` still overrides the default because it
is user-controlled.

`EnvInstallerContext::for_package_manager` routes only the package-manager
bootstrap path (`sync_package_manager_dependencies`) through this trusted
config; project `configDependencies` resolution keeps the project
registries, matching the narrow scope of the upstream TypeScript fix.
2026-06-18 00:46:15 +02:00
subaru
223d060a0f docs: document --cpu, --os and --libc flags in pnpm install --help (#12478)
These flags are already supported via rcOptionsTypes but were only
documented on the website, not in the CLI help output.

Close pnpm/pnpm#12359
2026-06-17 23:20:15 +02:00
Zoltan Kochan
83f06a6046 refactor(pacquet): trim comments that restate code or record history (#12482)
## What

A repository-wide sweep of the Rust source under `pacquet/` to bring comments in line with the comment policy in `AGENTS.md` / `pacquet/AGENTS.md`: **comments are for the non-obvious _why_, not a translation of the _what_, and behavior already proven by a test should not be re-narrated in code.**

Two focused commits:

**1. Trim comments that restate code or record history** (`refactor(pacquet): trim comments that restate code or record history`)
- comments that merely restate the adjacent code;
- history / refactor-shape narration (`used to`, `before this PR`, `Copilot flagged`, "the old X", removed-type references);
- call-site comments duplicating a callee's own doc comment;
- test prose that just re-describes what a test's name and assertions demonstrate.

**2. Drop comments that narrate test-covered behavior** (`refactor(pacquet): drop comments that narrate test-covered behavior`) — enforcing the "tests are documentation" rule. Each implementation file was paired with its co-located test module so the duplication was visible, then comments enumerating tested scenarios, edge cases, failure modes, or worked examples were removed — **including when they carried a pnpm-parity reference/link**, since that context lives in the test names and git history.

Preserved throughout: genuine `///`/`//!` contracts (guarantees, preconditions, postconditions, panics), hidden invariants, workarounds, ordering/safety reasons, parity notes that aren't behavior-restatement, and `SAFETY:`/`TODO:` notes.

## Scope

Comments only — **no code, identifiers, or string literals were modified** in either commit. ~500 file edits in total; several thousand comment lines removed.

## How it was done

Multi-agent sweeps: one agent per batch applied the policy, and a second agent diff-checked each edited batch for over-deletion (lost contracts) or accidental code changes. Over-trimmed contracts were restored; zero code changes were introduced (independently re-verified: every added line is identical to a removed line modulo a stripped trailing comment).
2026-06-17 23:05:55 +02:00
Zoltan Kochan
2bb1fe7ecd feat: let Qodo auto-approve clean PRs (#12481)
Auto-approval was enabled (enable_auto_approval + auto_approve_for_no_suggestions)
but never fired because it is evaluated by the /improve tool, which was not in
pr_commands — only /agentic_describe and /agentic_review ran. Add /improve to
pr_commands so the approval check runs, and add auto_approve_for_low_review_effort
= 2 so small PRs are approved even when /improve has minor suggestions.

/improve is added to pr_commands only (not push_commands) to keep Qodo's per-PR
usage low; it runs once when a PR opens rather than on every commit.
2026-06-17 20:45:00 +02:00
dependabot[bot]
23c8efeffc chore(cargo): bump dialoguer from 0.11.0 to 0.12.0 (#12355)
Bumps [dialoguer](https://github.com/console-rs/dialoguer) from 0.11.0 to 0.12.0.
- [Release notes](https://github.com/console-rs/dialoguer/releases)
- [Changelog](https://github.com/console-rs/dialoguer/blob/main/CHANGELOG-OLD.md)
- [Commits](https://github.com/console-rs/dialoguer/compare/v0.11.0...v0.12.0)

---
updated-dependencies:
- dependency-name: dialoguer
  dependency-version: 0.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-17 20:29:26 +02:00
Khải
7e8abe26a0 refactor(pacquet): replace OnceLock with LazyLock where init is self-contained (#12477) 2026-06-17 18:06:54 +00:00
Zoltan Kochan
c18cbd4cad fix: enable CodeRabbit approvals via request_changes_workflow (#12476)
CodeRabbit's effective config (from the `@coderabbitai configuration`
command) showed request_changes_workflow: false, which is why it only
ever commented and never approved — CodeRabbit submits approving reviews
only when this is true. The dashboard toggle wasn't taking effect, so
declare it in-repo.

Also explicitly disable pre_merge_checks: they're enabled by default and
in the dashboard (removing the block from the config did not turn them
off), and they add a status block without review value here. Title
enforcement stays covered by the commit-msg hook and squash-title
convention.
2026-06-17 14:51:08 +02:00
Zoltan Kochan
7a313077fe fix: restore CodeRabbit and Qodo PR auto-approval (#12474)
* chore: stop configuring CodeRabbit pre-merge checks

Defining any pre-merge check turned on the whole pre-merge-checks
subsystem, which under Request Changes Workflow replaces the
auto-approve flow: CodeRabbit emits a pre-merge-checks status block
instead of approving once review threads are resolved, leaving PRs at
REVIEW_REQUIRED even with all threads cleared and all checks green.

Remove the title check (added in pnpm/pnpm#12460) to restore
auto-approval and document why the block is intentionally absent.
Conventional Commits on the squash title stay covered by the commit-msg
hook and the squash-title convention.

* fix: declare Qodo auto-approval under [config]

enable_auto_approval and auto_approve_for_no_suggestions were declared
under [pr_reviewer], where Qodo never reads them, so auto-approval never
ran. Per the Qodo docs both keys belong under [config]. Move them and
drop the now-empty [pr_reviewer] section.

* chore: stop applying labels from Qodo

Product labels are applied by CodeRabbit (auto_apply_labels +
labeling_instructions in .coderabbit.yaml). Having Qodo also publish
labels via enable_custom_labels/publish_labels/custom_labels meant two
tools managing the same labels. Drop the Qodo label config and leave
labeling to CodeRabbit.

* ci: tag Qodo-approved PRs and fix the label step

Add an on-qodo-approval job that applies the 'reviewed: qodo' label when
qodo-free-for-open-source-projects[bot] approves, mirroring the existing
CodeRabbit job.

The label step used 'gh pr edit --add-label', which is broken (it errors
on the deprecated Projects-v1 GraphQL field), so adding the label failed
on approval. Switch all three jobs to the issues REST API
(POST /repos/{owner}/{repo}/issues/{number}/labels) instead.
2026-06-17 14:15:46 +02:00
dependabot[bot]
de74c58f58 chore(cargo): bump object_store from 0.12.5 to 0.13.2 (#12354)
* chore(cargo): bump object_store from 0.12.5 to 0.13.2

Bumps [object_store](https://github.com/apache/arrow-rs-object-store) from 0.12.5 to 0.13.2.
- [Changelog](https://github.com/apache/arrow-rs-object-store/blob/main/CHANGELOG-old.md)
- [Commits](https://github.com/apache/arrow-rs-object-store/compare/v0.12.5...v0.13.2)

---
updated-dependencies:
- dependency-name: object_store
  dependency-version: 0.13.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix(pnpr): import ObjectStoreExt for object_store 0.13 method move

object_store 0.13 moved get/put/delete off the ObjectStore trait onto
the new ObjectStoreExt trait. Import it so the S3 hosted-store backend
keeps compiling.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-06-17 11:37:36 +00:00
Ben Scholzen
96bdd57bf4 fix: dedupe workspace deps with children in single-project mode (#11448)
## Issue

When `injectWorkspacePackages: true` is set and a workspace package depends on another workspace package that has its own dependencies, running `pnpm rm` from inside the dependent package's directory switches the lockfile protocol from `link:` to `file:`.

Reproduction (workspace where `a` depends on workspace `b`, and `b` has any dependency of its own):

```
cd packages/a
pnpm add redis
pnpm rm redis
# pnpm-lock.yaml: a's "b" entry switched from link:../b to file:packages/b
```

## Root Cause

The fix in #10575 added a defensive guard in `dedupeInjectedDeps` that skipped deduplication whenever the target workspace project's children weren't in `dependenciesByProjectId`:

```ts
if (!targetProjectDeps) {
  if (children.length > 0) continue
}
```

In single-project operations (`mutateModulesInSingleProject`, used by `pnpm rm` from inside a package directory) only the operated-on project is resolved. `dependenciesByProjectId` then only has that one project, so the guard fires for any workspace dependency whose target has children, and the protocol stays `file:`.

## Solution

In single-project mode the injected dep is resolved against the same workspace package source, so dedupe is safe — *except* for peer-suffixed depPaths, whose resolution depends on the importer's peer context (a plain `link:` would lose it). The new code dedupes whenever `targetProjectDeps` is missing for a known workspace project and the depPath has no peer suffix. The peer-suffix check compares the depPath against its peer-free `pkgIdWithPatchHash` (depPaths are built as `${pkgIdWithPatchHash}${peerDepGraphHash}`), so it's exact rather than a `(`-substring heuristic.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-06-17 13:37:09 +02:00
Victor Sumner
1c0587698d fix: narrow warm install relinking (#11169)
## Problem
During warm installs, pnpm relinked existing packages more broadly than necessary when only some child dependencies changed.

In the narrowed relinking path, removed child aliases could also remain behind as stale links after dependency updates.

## Solution
Only pass changed child edges through the relinking path for existing packages.

When a child alias is no longer present in the updated dependency set, remove the obsolete link before relinking. Added regression tests for both cases:
- unchanged child dependencies are not relinked unnecessarily
- deleted child dependencies do not remain as stale links after a warm install

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-06-17 13:36:01 +02:00
Zoltan Kochan
c86559c9bc docs: add new silver sponsor (#12473) 2026-06-17 13:31:01 +02:00
Hiten Shah
1b02b47669 fix: remove macOS Gatekeeper quarantine xattr from native binaries (#11095)
Fixes #11056

## Problem

On macOS, pnpm imports files from its content-addressable store into `node_modules` via copy, reflink/clone, or hardlink. All three preserve extended attributes, including `com.apple.quarantine`. If a store blob carries that xattr — e.g. it was first written under a Gatekeeper-enabled app such as a Git client (`LSFileQuarantineEnabled=YES`) — the quarantine propagates into `node_modules`. Gatekeeper then blocks ad-hoc-signed native binaries (`.node`, `.dylib`, `.so`) from loading, even though pnpm has already verified each file's integrity against `pnpm-lock.yaml`.

## Solution

After importing a package from the store, strip `com.apple.quarantine` from its native binaries — mirroring Homebrew's behaviour of dropping quarantine from downloads after checksum verification.

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-06-17 10:05:37 +00:00
Khải
268c97d122 refactor(pacquet): remove redundant Arc (#12461)
* perf(package-manager): drop redundant Arc around process-global compat extender

The built-in `@yarnpkg/extensions` compatibility database is built once
per process and cached in a `OnceLock`. Wrapping it in an `Arc` added an
allocation plus per-install refcount churn for no benefit: a `OnceLock`
already hands out a stable `&'static` reference, and every consumer
(`PackageExtender::apply` / `apply_to_arc`, both `&self`) is satisfied by
`&'static PackageExtender` — which is `Copy + Send + Sync + 'static` and
so moves cleanly into the resolver's `ManifestHook` closure.

Store the extender as `OnceLock<PackageExtender>` returning a
`&'static` reference. The per-install user-provided extender keeps its
`Arc`, since it is constructed fresh each install and shared into the
resolver hook.

* refactor(store-dir): borrow &StoreIndexWriter in upload instead of &Arc

`upload` only reads through the writer — a single `queue_side_effects_upload`
call, no `Arc::clone` — so it never needed shared ownership. Borrowing
`&Arc<StoreIndexWriter>` forced the signature to advertise reference
counting it does not use. Take `&StoreIndexWriter` instead; the sole
caller passes a `&Arc<StoreIndexWriter>` that deref-coerces at the call
site, so it is unchanged.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-17 11:30:00 +02:00
Ayush Baluni
9d0a3006ff fix(version): honor workspace selection for recursive version bumps (#11425)
## Problem
`pnpm version --recursive` did not bump the workspace packages the user selected. In recursive mode the command re-derived the workspace selection itself (via `filterProjectsFromDir`) using an incomplete set of options, so it could resolve a different set of packages than the CLI's actual `--filter`/`--recursive` resolution.

Fixes #11348.

## Change
- In recursive mode, bump the projects in `selectedProjectsGraph` — the selection the pnpm CLI (`main.ts`) already computes from the workspace filter, exactly the way `pnpm publish --recursive` works.
- Remove the in-handler `filterProjectsFromDir` fallback. It duplicated the CLI's filtering logic and was unreachable in production (`main.ts` always passes `selectedProjectsGraph` for a recursive run). `@pnpm/workspace.projects-filter` becomes a dev-only dependency of `@pnpm/releasing.commands`.
- No global/config plumbing is needed for `--recursive`: `@pnpm/cli.parse-cli-args` already recognizes `recursive` (and the `-r` shorthand) for every command, the config reader passes it through to the handler, and the `version` command already declares `recursive` in its own `cliOptionsTypes`.

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-06-17 11:22:15 +02:00
Zoltan Kochan
c1f9cdfe95 ci: remove TS CI / Success aggregate gate job (#12466) 2026-06-17 10:03:40 +02:00
Zoltan Kochan
9de161c386 chore: keep test fixtures out of GitHub's dependency graph (#12465)
Mark fixture trees (package.json, lockfiles) as linguist-generated in
.gitattributes so GitHub's dependency graph ignores them. This stops
Dependabot from raising alerts and opening automatic security-update PRs
for packages that appear in fixtures only as test data — e.g. the
js-cookie bump in pnpm/pnpm#11840.

The `@pnpm/test-fixtures` helper package is real source and is
intentionally left unmarked.
2026-06-17 09:47:14 +02:00
Allan Kimmer Jensen
1495cb080c feat(sbom): per-package SBOM generation with --out and --split (#12097)
* feat(sbom): add monorepo workspace support for SBOM generation

When --filter selects a single workspace, the SBOM root component now
uses that workspace's name, version, description, license, and author
instead of the workspace root's metadata. Author, repository, and
license fall back to the root manifest when the workspace package
doesn't define them.

Workspace inter-dependencies (workspace: protocol) and their transitive
dependencies are now included in the SBOM. Dev-only workspace deps are
correctly excluded when --prod is used, and lockfile-only mode skips
workspace resolution entirely to avoid unexpected disk reads.

* fix(sbom): add recursiveByDefault so --filter works in workspaces

Without recursiveByDefault, the sbom command in a workspace didn't
enter the recursive code path that populates selectedProjectsGraph from
--filter. The handler received no filter info and always used the
workspace root manifest for the root component.

* feat(sbom): add --out and --split for per-package SBOM generation

Add --out <path> to write SBOM to a file instead of stdout. Supports
%s (package name) and %v (version) placeholders. When %s is present,
generates one SBOM per workspace package automatically.

Add --split to output NDJSON (one compact JSON per line) to stdout,
for piping into tooling that processes multiple SBOMs.

Add compact option to CycloneDX and SPDX serializers so NDJSON lines
are single-line JSON without re-parsing.

Sanitize %s and %v values to prevent path traversal in output paths.

* fix: use sbom-out instead of sboms to pass spellcheck

* fix(sbom): expand %v in single-output path and fix workspace dep parent relationships

Two bugs found by CodeRabbit:

1. --out with %v but no %s wrote a literal %v filename. Now both %s
   and %v are expanded in the single-output path using the SBOM root
   component's name and version.

2. The lockfile walker used rootPurl as parent for every importer,
   including additional workspace dep importers. This caused
   shared-lib's external deps (like is-odd) to appear as direct
   deps of the root package. Now each importer's walk uses the
   correct parent PURL based on whether it's an original or
   workspace dep importer.

* fix(sbom): fall back to workspace root manifest for filtered package metadata

Read the root manifest from rootProjectManifest/rootProjectManifestDir instead
of opts.dir so license/author/repository/description fall back to the workspace
root when a filtered package omits them. Add the missing rootDescription
fallback. Fix the per-package CycloneDX test assertions to match the standard
group/name split for scoped names.

* fix(sbom): harden path handling and fix lockfile-only/versionless workspace deps

- Neutralize '.', '..' and blank segments in --out path templates so a crafted
  package name/version cannot escape the output directory.
- Skip lockfile link: targets that resolve outside the workspace root, and guard
  the workspace manifest reads with a containment check, preventing arbitrary
  package.json reads from a malicious lockfile.
- Honor --lockfile-only inside collectSbomComponents so workspace links and their
  transitive deps are no longer traversed.
- Include workspace packages that omit a version (default to 0.0.0, matching the
  root component).
- Bound workspace manifest reads with p-limit to avoid EMFILE on large monorepos.

* fix(sbom): error on per-package output path collisions and use O(n) workspace BFS

- Throw SBOM_OUT_PATH_COLLISION when two workspace packages sanitize to the same
  --out path instead of silently overwriting one SBOM with another.
- Replace the resolveWorkspaceDeps BFS queue.shift() (O(n) per dequeue) with a
  moving head index for O(n) traversal on large workspaces.

* fix(sbom): strip control chars from output paths and skip unreadable workspace importers

- Strip ASCII control characters (incl. newlines) in sanitizePathSegment so a
  crafted package name/version cannot inject lines into the --split --out summary
  or produce confusing filenames.
- Skip walking a reachable workspace importer when its package info is missing
  (e.g. unreadable manifest) instead of misattributing its deps to the root.
- Bound importer-walker fan-out with p-limit to avoid resource pressure on large
  workspaces.

* perf(sbom): reuse workspace manifests across split outputs and use a Set for path collisions

- Read workspace package manifests once into SharedContext (keyed by importer id)
  from the project graph, so split mode no longer re-reads them from disk for
  every emitted SBOM.
- Track written --out paths in a Set instead of Array#includes to make collision
  detection O(1) per package rather than O(n2) overall.

* fix(sbom): support %s in single-project --out, harden importer lookup, cache root license

- Only treat %s in --out as per-package (split) mode when a workspace project
  graph is present; in a single-project repo %s/%v interpolate from the root
  component on the single-output path. Adds a regression test.
- Use an own-property check for lockfile importer existence so a crafted link:
  target cannot follow inherited keys (e.g. toString) via the prototype chain.
- Fast-path resolveRootLicense when the manifest already declares an SPDX license
  and cache the workspace-root license in SharedContext, avoiding redundant
  on-disk LICENSE probing per emitted SBOM.

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-06-17 09:43:27 +02:00
Orgad Shaneh
9d79ba181e fix: expose update --no-save in CLI help (#12091)
The update command already honors --no-save and the docs already
mention it, but the flag was missing from the update command
metadata.

Add the option entry so pnpm update --help shows it and the CLI
surface matches the documented behavior.
2026-06-17 08:22:09 +02:00
Mayank Maurya
3f0fb219b6 fix(default-reporter): erase trailing characters on progress line (#12351)
External processes like SSH passphrase prompts can write to the terminal between progress updates. The previous renderer used `ansi-diff`, which only overwrites the characters it knows changed, so leftover characters from the external output stayed visible on the progress line — e.g. `added 0sa':`, where `sa':` is a fragment of `Enter passphrase for key '.../.ssh/id_rsa':`.

Closes https://github.com/pnpm/pnpm/issues/12350

## Summary

The interactive (non-append-only) reporter now redraws the whole frame in place on each update instead of incrementally diffing it:

- return the cursor to the top-left of the previous frame (`ESC[<rows>A` followed by a carriage return, so the redraw starts at column 0 even if an external process left the cursor mid-line),
- erase from there to the end of the display (`ESC[0J`),
- reprint the frame — all in a single atomic write, so there is no flicker.

Because the whole region is erased on every frame, any characters an external process wrote in between are cleared. This matches pacquet's `Output::Frame` rendering (the column-reset hardening was applied to both stacks). The now-unused `ansi-diff` dependency has been removed.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-06-17 08:17:15 +02:00
MK (fengmk2)
3b54d79521 fix(deps-installer): keep catalog-referencing overrides in sync on update (#12158)
* fix(deps-installer): re-resolve catalog-referencing overrides on update

When `pnpm.overrides` reference a catalog (e.g. `overrides: { foo: 'catalog:' }`),
`pnpm update` bumped the catalog entry during resolution but left the resolved
`overrides` in the lockfile pointing at the old version. The lockfile's
`catalogs` advanced while `overrides` stayed stale, producing an internally
inconsistent lockfile that fails a later `pnpm install --frozen-lockfile` with
ERR_PNPM_LOCKFILE_CONFIG_MISMATCH.

After resolution, re-resolve the overrides against the catalog merged with the
update's `updatedCatalogs`, so the lockfile `overrides` track the bumped catalog
just like `catalogs` and direct catalog dependencies do.

* fix(deps-installer): re-resolve catalog overrides before afterAllResolved

Address review feedback:

- Run the catalog-override re-resolution before the `afterAllResolved`
  pnpmfile hook instead of after it, so a hook that edits `lockfile.overrides`
  still sees and can amend the final value (the block previously ran after the
  hook and would clobber its edits whenever a catalog entry was updated).
- Drop the dead `opts.catalogs ?? {}` fallback; `opts.catalogs` is required on
  the install options and always defaulted to `{}`, so it is never nullish here.

* test(pacquet): cover catalog-referencing override sync on update --latest

Mirrors pnpm's regression test for keeping lockfile overrides that resolve
through a catalog in sync when `update --latest` bumps that catalog. pacquet
already behaves correctly (it threads the bumped catalogs through to override
parsing), so this is a guard against a future refactor reintroducing the
inconsistency that pnpm/pnpm#12158 fixes on the TypeScript side.

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-06-17 08:10:12 +02:00
Zoltan Kochan
c7950e7fdb fix(ci): grant issues: write so the approval label step can run (#12464)
The pr-review-automation workflow merged in pnpm/pnpm#12462 fails at the
label step with 'Resource not accessible by integration
(addLabelsToLabelable)'. gh pr edit --add-label uses the GraphQL
addLabelsToLabelable mutation, which requires issues: write even when
labeling a PR; pull-requests: write alone is insufficient. Grant it per
job, matching pacquet-integrated-benchmark-comment.yml.
2026-06-17 08:07:08 +02:00
Abdullah Alaqeel
5c12968ad6 fix(update): handle mixed direct and transitive selectors (#12105)
* fix(update): handle mixed direct and transitive selectors

* test(update): strengthen regression test and port to pacquet

The pnpm regression test passed with and without the fix: the fixture's
`latest` dist-tag made a fresh install of `^100.0.0` already resolve to
100.1.0, so the assertion was trivially true. Pin the transitive
dep-of-pkg-with-1-dep to 100.0.0 before install so the test genuinely
fails without the fix and passes with it.

Add pacquet parity regression tests for the same mixed direct/transitive
selector scenario (exact-name and glob forms). pacquet has no equivalent
source change to make — its `update` matches every bare-name/glob
selector against direct deps and locked snapshot names in one pass, so a
direct selector never gates the transitive one — but the behavior is
guarded by tests to lock in pnpm/pnpm#12103 parity.

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-06-17 07:29:57 +02:00
morning-verlu
531f2a307c fix: preserve workspace specs on update (#12140)
## What
- preserve existing `workspace:` dependency specifiers when `updateProjectManifest` saves updated direct dependencies and `preserveWorkspaceProtocol` is enabled
- keep catalog specifiers taking precedence over resolver-normalized specs
- add focused coverage for preserved and normalized local spec behavior
- add a changeset for the published `@pnpm/installing.deps-resolver` change

### pacquet parity
Ported the same fix to pacquet's `update` command. Previously `pacquet update --latest` routed every direct dependency through a registry `latest` lookup, so a `workspace:` local-path dependency (e.g. `workspace:../packages/foo/dist`) was rewritten into a registry version — corrupting the manifest (in the regression test it became `0.0.1-security`). Both `--latest` rewrite sites now skip registry resolution for such specs via `is_workspace_local_path_specifier`, a faithful port of pnpm's `isWorkspaceLocalPathSpecifier`. The gate is unconditional in the `--latest` path because `preserveWorkspaceProtocol` is always on there (its only override derives from `linkWorkspacePackages` under `--workspace`, which cannot be combined with `--latest`).

Fixes #3902

---------

Co-authored-by: morning-verlu <258725120+morning-verlu@users.noreply.github.com>
Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-06-16 23:38:05 +00:00
Zoltan Kochan
c38f68aecc ci: PR review automation (CodeRabbit approval + maintainer automerge) (#12462)
* ci: label and notify on CodeRabbit approval

Add a workflow that fires on pull_request_review and, when CodeRabbit
submits an approving review, applies the informational "reviewed:
coderabbit" label and (if a DISCORD_WEBHOOK secret is configured) posts
a notice to Discord.

PR fields reach the steps through the environment rather than script
interpolation, so an attacker-controlled PR title cannot inject shell
commands. The Discord step is skipped when the webhook secret is unset.

* ci: flag maintainer-approved PRs for automerge

Rename the approval workflow to cover both review automations and add a
second job: when zkochan submits an approving review, apply the existing
"state: automerge" label.

Like the CodeRabbit job, the PR number is passed through the environment
rather than interpolated into the run script.

* ci: harden PR review automation per review feedback

- Scope permissions to each job (top-level permissions: {}) instead of
  granting pull-requests: write workflow-wide (zizmor).
- Set allowed_mentions to {parse: []} on the Discord payload so a PR
  title containing `@everyone`/`@here` cannot ping the server.
- Add connect/overall timeouts and retries to the Discord curl call.
2026-06-17 01:33:50 +02:00
btea
293921a788 feat(view): support searching package.json upward when package name is omitted (#11696)
* feat(view): support searching package.json upward when package name is omitted

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* fix: apply review

* fix: apply review

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* fix: update

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-17 01:04:25 +02:00
Zoltan Kochan
cc4cadad8d chore: tune CodeRabbit config (#12460)
Add several CodeRabbit settings:

- path_filters: skip generated/vendored/snapshot files (lockfiles, dist,
  fixtures, snapshots, changelogs) so reviews focus on hand-written code.
- pre_merge_checks.title: enforce Conventional Commits on the PR title,
  which becomes the commit message under squash merge (the commit-msg
  hook only validates per-commit messages).
- knowledge_base.learnings scoped to local so maintainer corrections are
  remembered without crossing repo boundaries.
- issue_enrichment labeling: auto-apply the durable area/type taxonomy to
  issues, where long-lived labels actually aid triage and search.
- poem disabled.

Secret scanners (gitleaks, semgrep, trufflehog) are already enabled by
default, so they are not added explicitly.
2026-06-17 01:00:10 +02:00
Abdullah Alaqeel
61969fbddf fix(deps-status): detect lockfile-only changes (#12106)
## Summary

Fixes `pnpm install` with `optimisticRepeatInstall` incorrectly returning `Already up to date` when `pnpm-lock.yaml` changed but project manifests did not.

Fixes #12100.

## Root Cause

`checkDepsStatus` used modified manifest mtimes as the only signal for whether it needed to validate dependency status. If no manifest was newer than `workspaceState.lastValidatedTimestamp`, it returned `upToDate: true` before checking whether the wanted lockfile had changed.

That skipped lockfile validation for workflows like:

- `git checkout HEAD~1 -- pnpm-lock.yaml`
- restoring only `pnpm-lock.yaml` from a stash
- external tools rewriting the lockfile without touching manifests

## Changes

- Check wanted lockfile mtimes before taking the optimistic fast path.
- If any wanted lockfile is missing or newer than the workspace state timestamp, validate all projects instead of only modified manifests.
- Add a regression test proving a lockfile-only change does not skip wanted-lockfile validation.
- Add a patch changeset for `@pnpm/deps.status` and `pnpm`.

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-06-16 22:04:07 +00:00