Skip to content

[pull] main from pnpm:main#330

Merged
pull[bot] merged 21 commits into
dwongdev:mainfrom
pnpm:main
Jun 17, 2026
Merged

[pull] main from pnpm:main#330
pull[bot] merged 21 commits into
dwongdev:mainfrom
pnpm:main

Conversation

@pull

@pull pull Bot commented Jun 17, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

zkochan and others added 21 commits June 17, 2026 01:00
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.
…s 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>
…#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.
## 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>
* 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 #12103 parity.

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
The pr-review-automation workflow merged in #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.
…date (#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 #12158 fixes on the TypeScript side.

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
…2351)

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 #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>
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.
* 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>
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 #11840.

The `@pnpm/test-fixtures` helper package is real source and is
intentionally left unmarked.
…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>
* 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>
…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>
## 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>
## 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>
* 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](apache/arrow-rs-object-store@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>
* 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 #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.
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.
@pull pull Bot locked and limited conversation to collaborators Jun 17, 2026
@pull pull Bot added the ⤵️ pull label Jun 17, 2026
@pull pull Bot merged commit c18cbd4 into dwongdev:main Jun 17, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.