Skip to content

feat(pacquet/deps-resolver): port dedupePeers setting#12022

Merged
zkochan merged 2 commits into
mainfrom
dedupe-peers
May 28, 2026
Merged

feat(pacquet/deps-resolver): port dedupePeers setting#12022
zkochan merged 2 commits into
mainfrom
dedupe-peers

Conversation

@zkochan

@zkochan zkochan commented May 28, 2026

Copy link
Copy Markdown
Member

Summary

Port the dedupePeers setting from pnpm. When enabled, peer-dependency suffixes in depPaths use version-only identifiers (name@version) instead of recursive dep paths, collapsing nested suffixes like (@emotion/react@11.0.0(react@18.0.0)) into (@emotion/react@11.0.0). Default false, matching pnpm.

  • Config plumbing: Config.dedupe_peers, WorkspaceSettings.dedupe_peers, PACQUET_DEDUPE_PEERS env overlay, and the clear_workspace_only_fields / apply_to wiring.
  • Resolver behavior: ResolvePeersOptions.dedupe_peers threaded through ResolveImporterOptions and consumed in Walker::build_peer_id — when on, emits PeerId::Pair { name, version } from the resolved package instead of the peer's own DepPath. Mirrors pnpm's peerNodeIdToPeerId dedupePeers branch.
  • Lockfile round-trip: LockfileSettings.dedupe_peers: Option<bool> (omitted when false, mirroring pnpm's dedupePeers: opts.dedupePeers || undefined). GraphToLockfileOptions.dedupe_peers plumbed from install_with_fresh_lockfile.
  • Optimistic-repeat-install drift: WorkspaceStateSettings.dedupe_peers is now compared by settings_match and written by current_settings. A switch to the setting between installs invalidates the cached-modules fast path.

Closes one of the items on #12009.

Test plan

Ported from upstream pnpm:

Not ported (multi-importer / multi-workspace e2e scope, separate from this slice):

  • resolvePeers.ts 'multi-project: different peer versions produce different instances' — pacquet's resolve_peers runs per-importer.
  • peerDependencies.ts 'dedupePeers: workspace projects with different peer versions get different instances' — needs multi-workspace e2e infrastructure.

Local checks:

  • cargo check --workspace --all-targets, cargo clippy -- --deny warnings, cargo fmt, taplo format, typos all clean.
  • All ~800 tests across pacquet-config, pacquet-lockfile, pacquet-package-manager, and pacquet-resolving-deps-resolver pass locally.

Written by an agent (Claude Code, claude-opus-4-7).

Summary by CodeRabbit

  • New Features
    • Added dedupe_peers configuration option to control how peer dependency identifiers are generated and deduplicated across nested dependencies
    • New PNPM_CONFIG_DEDUPE_PEERS environment variable and workspace YAML configuration support
    • Setting is now recorded in lockfile for consistency across installs

Review Change Stack

Ports the `dedupePeers` setting from pnpm. When enabled, the peer-resolution
stage emits `name@version` peer-ids instead of full depPaths, collapsing
recursive peer suffixes like `(foo@1.0.0(bar@2.0.0))` into `(foo@1.0.0)`.

Wires the setting through `pnpm-workspace.yaml`, env overlay, `Config`,
`ResolveImporterOptions`, and `ResolvePeersOptions`, then rounds-trips it
through the lockfile's `settings:` block and the workspace-state used by
`optimistic_repeat_install`.

Refs #12009.
@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR implements the dedupePeers configuration setting for pacquet: adds the configuration field to Config and workspace YAML, wires it through dependency resolution to flatten nested peer-suffix chains, serializes it into lockfiles, and detects configuration drift during optimistic repeat-install checks.

Changes

dedupePeers Feature Implementation

Layer / File(s) Summary
Configuration and Settings Schema
pacquet/crates/config/src/lib.rs, pacquet/crates/config/src/workspace_yaml.rs, pacquet/crates/config/src/env_overlay.rs, pacquet/crates/lockfile/src/lib.rs, pacquet/crates/config/src/workspace_yaml/tests.rs
Adds dedupe_peers: bool (default false) to Config, tri-state Option<bool> to WorkspaceSettings with field-clearing for global config and apply_to wiring, environment variable support via PNPM_CONFIG_DEDUPE_PEERS, LockfileSettings serialization with camelCase and skip_if_none, and YAML deserialization tests.
Core Peer-Deduplication Logic
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs
Adds dedupe_peers: bool flag to ResolvePeersOptions and updates build_peer_id to emit flat name@version peer identifiers when enabled, collapsing nested depPath-based peer suffixes into version-only references.
Resolution Options and Importer Integration
pacquet/crates/resolving-deps-resolver/src/resolve_importer.rs, pacquet/crates/resolving-deps-resolver/src/resolve_importer/tests.rs
Adds dedupe_peers: bool to ResolveImporterOptions and threads it through resolve_importer by constructing peer_opts once and reusing it for both inner-loop auto-install-peers and final tree resolution, with test defaults set to false.
Installation Pipeline and Lockfile Emission
pacquet/crates/package-manager/src/dependencies_graph_to_lockfile.rs, pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs
Adds dedupe_peers: bool to GraphToLockfileOptions and emits it to LockfileSettings (as Some(true) when enabled, omitted when disabled). Wires config.dedupe_peers into both per-importer ResolveImporterOptions and GraphToLockfileOptions to maintain consistent deduplication behavior through resolution and lockfile recording.
Lockfile Serialization and Test Setup
pacquet/crates/package-manager/src/dependencies_graph_to_lockfile/tests.rs, pacquet/crates/package-manager/src/install/tests.rs, pacquet/crates/package-manager/src/install_package_from_registry/tests.rs
Test helpers explicitly set dedupe_peers: false for multi-importer scenarios. New dedupe_peers_round_trips_through_lockfile_settings test validates correct YAML emission (includes dedupePeers key when true, omits when false). Workspace-state and registry-install tests assert the recorded dedupe_peers value.
Optimistic Install Cache Invalidation
pacquet/crates/package-manager/src/optimistic_repeat_install.rs, pacquet/crates/package-manager/src/optimistic_repeat_install/tests.rs
Extends check_optimistic_repeat_install to compare dedupe_peers in settings-freshness checks (previously intentionally uncompared). Records dedupe_peers in current_settings for workspace-state serialization. New returns_skipped_when_dedupe_peers_drift test confirms cache invalidation when deduplication setting changes between installs.
Peer Resolution Feature Tests
pacquet/crates/resolving-deps-resolver/src/tests.rs
Adds dedupe_peers_collapses_nested_peer_suffixes and no_dedupe_peers_keeps_nested_peer_suffixes tests validating core deduplication behavior, dedupe_peers_propagates_transitive_peer_to_parent confirming correct suffix propagation to parents, and resolve_emotion_fixture helper for repeatable React/Emotion dependency-tree peer-resolution validation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • pnpm/pnpm#12009: Directly implements the end-to-end porting of pnpm's dedupePeers setting into pacquet's configuration, resolution, and lockfile layers.

Possibly related PRs

  • pnpm/pnpm#11943: Both PRs extend check_optimistic_repeat_install and current_settings to include a new setting in the freshness comparison; main PR adds dedupe_peers drift detection while the retrieved PR introduces the optimistic repeat install framework and settings-match model.
  • pnpm/pnpm#11905: Both PRs modify GraphToLockfileOptions in dependencies_graph_to_lockfile.rs; main PR threads dedupe_peers into lockfile settings while retrieved PR refactors the multi-importer input model.
  • pnpm/pnpm#11816: Both PRs modify the lockfile-generation pipeline in dependencies_graph_to_lockfile; main PR adds dedupe_peers serialization to settings while retrieved PR introduces the initial fresh-install lockfile writing and GraphToLockfileOptions plumbing.

Poem

🐰 A rabbit hops through the config tree,
Flattening peer suffixes wild and free,
With dedupe magic, no nested chains,
The lockfile sings, deduplication reigns!
One hop per peer, the setting's clear—
Pacquet bounces near! 🌱

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: porting the dedupePeers setting to pacquet's dependency resolver, which is the core objective of the PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dedupe-peers

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Adds two tests for fuller coverage of #12009:

- `dedupe_peers_propagates_transitive_peer_to_parent` — ports the
  `'transitive peers use version-only suffixes'` case from upstream's
  `resolvePeers.ts` test suite. Locks the contract that a non-peer
  parent inherits its child's external peer into its own depPath
  suffix.
- `dedupe_peers_round_trips_through_lockfile_settings` — combines
  upstream's `expect(lockfile.settings.dedupePeers).toBe(true)`
  assertion from `installing/deps-installer/test/install/peerDependencies.ts`
  with `lockfile/fs/test/write.test.ts`'s
  `'dedupePeers' in (written.settings ?? {})` omission check.
@zkochan zkochan marked this pull request as ready for review May 28, 2026 15:01
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Review Summary by Qodo

Port dedupePeers setting for peer-dependency suffix collapsing

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Port dedupePeers setting from pnpm to collapse nested peer suffixes
• Thread setting through config, workspace YAML, env overlay, and resolver
• Round-trip dedupePeers through lockfile settings block for install drift detection
• Add comprehensive tests covering peer-suffix collapsing and transitive peers
Diagram
flowchart LR
  Config["Config.dedupe_peers"]
  WorkspaceYAML["pnpm-workspace.yaml<br/>dedupePeers"]
  EnvOverlay["PACQUET_DEDUPE_PEERS<br/>env overlay"]
  ResolverOpts["ResolveImporterOptions<br/>dedupe_peers"]
  PeersOpts["ResolvePeersOptions<br/>dedupe_peers"]
  BuildPeerId["Walker.build_peer_id<br/>emits name@version"]
  Lockfile["LockfileSettings<br/>dedupePeers"]
  WorkspaceState["WorkspaceStateSettings<br/>dedupe_peers"]
  
  Config --> ResolverOpts
  WorkspaceYAML --> Config
  EnvOverlay --> Config
  ResolverOpts --> PeersOpts
  PeersOpts --> BuildPeerId
  Config --> Lockfile
  Config --> WorkspaceState
  BuildPeerId -- "collapses nested<br/>suffixes" --> Lockfile

Loading

Grey Divider

File Changes

1. pacquet/crates/config/src/env_overlay.rs ⚙️ Configuration changes +1/-0

Add DEDUPE_PEERS environment variable overlay

pacquet/crates/config/src/env_overlay.rs


2. pacquet/crates/config/src/lib.rs ✨ Enhancement +9/-0

Add dedupe_peers boolean field to Config struct

pacquet/crates/config/src/lib.rs


3. pacquet/crates/config/src/workspace_yaml.rs ✨ Enhancement +3/-1

Add dedupe_peers to WorkspaceSettings YAML parsing

pacquet/crates/config/src/workspace_yaml.rs


View more (13)
4. pacquet/crates/config/src/workspace_yaml/tests.rs 🧪 Tests +2/-0

Test dedupePeers YAML round-trip in workspace settings

pacquet/crates/config/src/workspace_yaml/tests.rs


5. pacquet/crates/lockfile/src/lib.rs ✨ Enhancement +9/-0

Add optional dedupe_peers field to LockfileSettings

pacquet/crates/lockfile/src/lib.rs


6. pacquet/crates/package-manager/src/dependencies_graph_to_lockfile.rs ✨ Enhancement +13/-1

Thread dedupe_peers through lockfile generation options

pacquet/crates/package-manager/src/dependencies_graph_to_lockfile.rs


7. pacquet/crates/package-manager/src/dependencies_graph_to_lockfile/tests.rs 🧪 Tests +62/-0

Test dedupe_peers lockfile settings round-trip behavior

pacquet/crates/package-manager/src/dependencies_graph_to_lockfile/tests.rs


8. pacquet/crates/package-manager/src/install/tests.rs 🧪 Tests +1/-0

Assert dedupe_peers in workspace state after install

pacquet/crates/package-manager/src/install/tests.rs


9. pacquet/crates/package-manager/src/install_package_from_registry/tests.rs 🧪 Tests +1/-0

Initialize dedupe_peers in test config creation

pacquet/crates/package-manager/src/install_package_from_registry/tests.rs


10. pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs ✨ Enhancement +2/-0

Pass dedupe_peers from config to resolver and lockfile

pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs


11. pacquet/crates/package-manager/src/optimistic_repeat_install.rs ✨ Enhancement +2/-1

Compare dedupe_peers in settings drift detection

pacquet/crates/package-manager/src/optimistic_repeat_install.rs


12. pacquet/crates/package-manager/src/optimistic_repeat_install/tests.rs 🧪 Tests +41/-1

Add test for dedupe_peers settings drift invalidation

pacquet/crates/package-manager/src/optimistic_repeat_install/tests.rs


13. pacquet/crates/resolving-deps-resolver/src/resolve_importer.rs ✨ Enhancement +9/-2

Add dedupe_peers field to ResolveImporterOptions

pacquet/crates/resolving-deps-resolver/src/resolve_importer.rs


14. pacquet/crates/resolving-deps-resolver/src/resolve_importer/tests.rs 🧪 Tests +1/-0

Initialize dedupe_peers in default resolver options

pacquet/crates/resolving-deps-resolver/src/resolve_importer/tests.rs


15. pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs ✨ Enhancement +24/-5

Add dedupe_peers option to ResolvePeersOptions struct

pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs


16. pacquet/crates/resolving-deps-resolver/src/tests.rs 🧪 Tests +187/-0

Add three comprehensive dedupe_peers resolver tests

pacquet/crates/resolving-deps-resolver/src/tests.rs


Grey Divider

Qodo Logo

@github-actions

Copy link
Copy Markdown
Contributor

Micro-Benchmark Results

Linux

group                          main                                   pr
-----                          ----                                   --
tarball/download_dependency    1.02      6.5±0.25ms   663.9 KB/sec    1.00      6.4±0.21ms   679.9 KB/sec

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
pacquet/crates/resolving-deps-resolver/src/tests.rs (1)

758-768: ⚡ Quick win

Trim test doc comments to non-obvious rationale only.

These comment blocks mostly restate fixture setup and expected outputs that are already explicit in the assertions, which risks doc drift over time. Keep only the upstream-parity/context “why”.

As per coding guidelines: “Tests are documentation. Do not duplicate them in prose.” and “Doc comments (///, //!) document the contract ... They are not a re-narration of the body.”

Also applies to: 788-792, 808-820, 882-885

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pacquet/crates/resolving-deps-resolver/src/tests.rs` around lines 758 - 768,
Trim the long doc-comment block describing the `dedupePeers` test to only
include the non-obvious rationale and upstream/context (keep the upstream parity
link and a short note why this behavior matters), removing the prose that
restates fixture setup and the expected output already covered by assertions;
apply the same trimming to the other doc-comment blocks mentioned (around
788-792, 808-820, 882-885) so tests remain concise documentation rather than
narrating the test body.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@pacquet/crates/resolving-deps-resolver/src/tests.rs`:
- Around line 758-768: Trim the long doc-comment block describing the
`dedupePeers` test to only include the non-obvious rationale and
upstream/context (keep the upstream parity link and a short note why this
behavior matters), removing the prose that restates fixture setup and the
expected output already covered by assertions; apply the same trimming to the
other doc-comment blocks mentioned (around 788-792, 808-820, 882-885) so tests
remain concise documentation rather than narrating the test body.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0bf3cbcb-a9c6-4f09-921a-a3fc08403d3f

📥 Commits

Reviewing files that changed from the base of the PR and between 39101f5 and 8ca4109.

📒 Files selected for processing (16)
  • pacquet/crates/config/src/env_overlay.rs
  • pacquet/crates/config/src/lib.rs
  • pacquet/crates/config/src/workspace_yaml.rs
  • pacquet/crates/config/src/workspace_yaml/tests.rs
  • pacquet/crates/lockfile/src/lib.rs
  • pacquet/crates/package-manager/src/dependencies_graph_to_lockfile.rs
  • pacquet/crates/package-manager/src/dependencies_graph_to_lockfile/tests.rs
  • pacquet/crates/package-manager/src/install/tests.rs
  • pacquet/crates/package-manager/src/install_package_from_registry/tests.rs
  • pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs
  • pacquet/crates/package-manager/src/optimistic_repeat_install.rs
  • pacquet/crates/package-manager/src/optimistic_repeat_install/tests.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_importer.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_importer/tests.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs
  • pacquet/crates/resolving-deps-resolver/src/tests.rs
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Lint and Test (windows-latest)
  • GitHub Check: Lint and Test (macos-latest)
  • GitHub Check: Cargo Deny
  • GitHub Check: Lint and Test (ubuntu-latest)
  • GitHub Check: Doc
  • GitHub Check: Code Coverage
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Compile & Lint
🧰 Additional context used
📓 Path-based instructions (1)
pacquet/**/*.rs

📄 CodeRabbit inference engine (pacquet/AGENTS.md)

pacquet/**/*.rs: When porting a function that fires pnpm:<channel> events through globalLogger, logger.debug(), or streamParser.write(), mirror the call site, payload, and ordering so the reporter parses pacquet's NDJSON the same way it parses pnpm's.
Declare a newtype wrapper for branded string types. Do not collapse the brand into a plain String or &str.
If upstream always validates before construction, validate in pacquet's wrapper too. The wrapper must construct only via TryFrom<String> and/or FromStr. Do not provide an infallible public constructor.
If upstream never validates, just brand for type-safety. Expose an infallible From<String> (and From<&str> when convenient).
If upstream occasionally constructs without validation, expose from_str_unchecked as an escape hatch alongside the validating constructor.
Match upstream serde behavior for branded types that cross JSON, YAML, or INI boundaries. Use #[serde(try_from = "String")] for deserialization and #[serde(into = "String")] for serialization.
Use #[derive(derive_more::From)] and #[derive(derive_more::Into)] for mechanical conversion impls. Fall back to manual impl only when conversion needs custom logic.
String-literal unions should become enums, not newtype wrappers. Model closed sets of valid string values as enums.
Template literal types should be treated as branded strings with validation discipline from rules 2-5.
Choose owned vs. borrowed parameters to minimize copies. Widen to the most encompassing type (&Path over &PathBuf, &str over &String) when it doesn't force extra copies.
Prefer Arc::clone(&x) / Rc::clone(&x) over x.clone() for reference-counted types, so the cost is visible at the call site.
Follow Rust API Guidelines for naming conventions.
Do not use star imports inside module bodies. Write use super::{Foo, bar} instead of use super::*;. Two forms stay allowed: external-crate preludes like use rayon::prelude::*; and root-of-module re-...

Files:

  • pacquet/crates/resolving-deps-resolver/src/resolve_importer/tests.rs
  • pacquet/crates/lockfile/src/lib.rs
  • pacquet/crates/package-manager/src/install_package_from_registry/tests.rs
  • pacquet/crates/config/src/env_overlay.rs
  • pacquet/crates/package-manager/src/install/tests.rs
  • pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs
  • pacquet/crates/package-manager/src/dependencies_graph_to_lockfile.rs
  • pacquet/crates/config/src/workspace_yaml/tests.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_importer.rs
  • pacquet/crates/config/src/workspace_yaml.rs
  • pacquet/crates/config/src/lib.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs
  • pacquet/crates/package-manager/src/optimistic_repeat_install.rs
  • pacquet/crates/package-manager/src/optimistic_repeat_install/tests.rs
  • pacquet/crates/package-manager/src/dependencies_graph_to_lockfile/tests.rs
  • pacquet/crates/resolving-deps-resolver/src/tests.rs
🧠 Learnings (6)
📚 Learning: 2026-05-20T19:40:55.051Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11774
File: pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs:0-0
Timestamp: 2026-05-20T19:40:55.051Z
Learning: In the pacquet Rust code, ensure the semver implementation uses the `node-semver` crate (not `nodejs-semver`). `node-semver`’s public API does not include a `satisfies_with_prerelease`-style method; prerelease-tolerant matching should be implemented inline by first calling `Range::satisfies`, and when it rejects a prerelease version, retry matching against a stripped `MAJOR.MINOR.PATCH` base of the prerelease version.

Applied to files:

  • pacquet/crates/resolving-deps-resolver/src/resolve_importer/tests.rs
  • pacquet/crates/lockfile/src/lib.rs
  • pacquet/crates/package-manager/src/install_package_from_registry/tests.rs
  • pacquet/crates/config/src/env_overlay.rs
  • pacquet/crates/package-manager/src/install/tests.rs
  • pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs
  • pacquet/crates/package-manager/src/dependencies_graph_to_lockfile.rs
  • pacquet/crates/config/src/workspace_yaml/tests.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_importer.rs
  • pacquet/crates/config/src/workspace_yaml.rs
  • pacquet/crates/config/src/lib.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs
  • pacquet/crates/package-manager/src/optimistic_repeat_install.rs
  • pacquet/crates/package-manager/src/optimistic_repeat_install/tests.rs
  • pacquet/crates/package-manager/src/dependencies_graph_to_lockfile/tests.rs
  • pacquet/crates/resolving-deps-resolver/src/tests.rs
📚 Learning: 2026-05-22T00:08:44.646Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11837
File: pacquet/crates/resolving-npm-resolver/src/pick_package.rs:33-51
Timestamp: 2026-05-22T00:08:44.646Z
Learning: In the pnpm/pnpm repo’s pacquet Rust crates, do not flag Unicode ellipsis characters (U+2026, `…`) in Rust doc comments (`///` / `/** */`) as a lint violation. The pacquet crate’s `dylint.toml` only enables `perfectionist::derive_ordering`, and the Dylint `unicode-ellipsis` rule is not enabled for this project—so `…` in doc comments is an intentional, repo-consistent style.

Applied to files:

  • pacquet/crates/resolving-deps-resolver/src/resolve_importer/tests.rs
  • pacquet/crates/lockfile/src/lib.rs
  • pacquet/crates/package-manager/src/install_package_from_registry/tests.rs
  • pacquet/crates/config/src/env_overlay.rs
  • pacquet/crates/package-manager/src/install/tests.rs
  • pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs
  • pacquet/crates/package-manager/src/dependencies_graph_to_lockfile.rs
  • pacquet/crates/config/src/workspace_yaml/tests.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_importer.rs
  • pacquet/crates/config/src/workspace_yaml.rs
  • pacquet/crates/config/src/lib.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs
  • pacquet/crates/package-manager/src/optimistic_repeat_install.rs
  • pacquet/crates/package-manager/src/optimistic_repeat_install/tests.rs
  • pacquet/crates/package-manager/src/dependencies_graph_to_lockfile/tests.rs
  • pacquet/crates/resolving-deps-resolver/src/tests.rs
📚 Learning: 2026-05-20T23:07:58.444Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11784
File: pacquet/crates/resolving-deps-resolver/src/hoist_peers.rs:120-133
Timestamp: 2026-05-20T23:07:58.444Z
Learning: When reviewing code in this pacquet Rust port, follow the upstream pnpm compatibility rule: only match pnpm’s behavior exactly. Do not propose review changes that intentionally deviate from pnpm’s documented/observed behavior, even if pnpm appears buggy. If you identify a real bug in pnpm behavior, the review should prioritize fixing it upstream in pnpm first, and avoid implementing a pnpm-behavior workaround here unless the same fix has already landed upstream.

Applied to files:

  • pacquet/crates/resolving-deps-resolver/src/resolve_importer/tests.rs
  • pacquet/crates/lockfile/src/lib.rs
  • pacquet/crates/package-manager/src/install_package_from_registry/tests.rs
  • pacquet/crates/config/src/env_overlay.rs
  • pacquet/crates/package-manager/src/install/tests.rs
  • pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs
  • pacquet/crates/package-manager/src/dependencies_graph_to_lockfile.rs
  • pacquet/crates/config/src/workspace_yaml/tests.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_importer.rs
  • pacquet/crates/config/src/workspace_yaml.rs
  • pacquet/crates/config/src/lib.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs
  • pacquet/crates/package-manager/src/optimistic_repeat_install.rs
  • pacquet/crates/package-manager/src/optimistic_repeat_install/tests.rs
  • pacquet/crates/package-manager/src/dependencies_graph_to_lockfile/tests.rs
  • pacquet/crates/resolving-deps-resolver/src/tests.rs
📚 Learning: 2026-05-24T21:10:50.292Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11915
File: pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs:553-617
Timestamp: 2026-05-24T21:10:50.292Z
Learning: When implementing dependency “revisit”/optional-folding in pacquet, mirror pnpm’s `resolveDependencies.ts` behavior: on revisit, update the `optional` flag only for the directly visited package, and do not automatically change `optional` for transitive descendants. pnpm corrects stale optional flags later via `copyDependencySubGraph` (a BFS in `lockfile/pruner/src/index.ts`/`copyDependencySubGraph`). Until pacquet has an equivalent pruner/copy-subgraph step, be aware that the raw `node.optional` can flow through to the lockfile/virtual store via `dependencies_graph_to_lockfile.rs` → `create_virtual_store.rs` → `installability.rs`, so review changes that affect optional flag propagation for this mismatch.

Applied to files:

  • pacquet/crates/resolving-deps-resolver/src/resolve_importer/tests.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_importer.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs
  • pacquet/crates/resolving-deps-resolver/src/tests.rs
📚 Learning: 2026-05-24T21:10:50.292Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11915
File: pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs:553-617
Timestamp: 2026-05-24T21:10:50.292Z
Learning: In pacquet’s Rust resolver layer, the behavior of `ResolvedPackage.optional` “AND-folding”/revisit updates is intentionally aligned with pnpm: when a package is directly revisited, only that package’s `optional` flag is updated; transitive descendants are not re-walked/rescored in this stage. pnpm later corrects any stale optional flags downstream via its pruner/BFS logic (`copyDependencySubGraph` + `nonOptional` reachability stamping in the lockfile pruner). Since pacquet does not yet have that pruner equivalent, review should NOT flag the downstream/stale optional propagation as a bug in pacquet PRs; treat the resolver-layer optional propagation gap as expected parity until the pruning/stamping step is ported.

Applied to files:

  • pacquet/crates/resolving-deps-resolver/src/resolve_importer/tests.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_importer.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs
  • pacquet/crates/resolving-deps-resolver/src/tests.rs
📚 Learning: 2026-05-20T23:23:40.606Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11788
File: pacquet/crates/lockfile/src/snapshot_dep_ref.rs:52-56
Timestamp: 2026-05-20T23:23:40.606Z
Learning: In the pacquet lockfile crate, the link target is intentionally represented as a raw `Link(String)` (not a `LinkTarget` newtype) for both `SnapshotDepRef::Link` and `ImporterDepVersion::Link`. pnpm upstream preserves `link:` targets verbatim with only a prefix check (e.g., `refToRelative`), so do not suggest introducing a `LinkTarget` newtype in a bug-fix/targeted PR. If type branding is ever desired, it should be done together for both variants in a separate dedicated refactor PR rather than as part of a small fix.

Applied to files:

  • pacquet/crates/lockfile/src/lib.rs
🔇 Additional comments (15)
pacquet/crates/resolving-deps-resolver/src/resolve_importer.rs (1)

70-74: LGTM!

Also applies to: 138-145, 189-190, 270-271

pacquet/crates/resolving-deps-resolver/src/resolve_importer/tests.rs (1)

90-90: LGTM!

pacquet/crates/package-manager/src/dependencies_graph_to_lockfile.rs (1)

63-70: LGTM!

Also applies to: 99-100, 117-121

pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs (1)

560-561: LGTM!

Also applies to: 1041-1041

pacquet/crates/package-manager/src/dependencies_graph_to_lockfile/tests.rs (1)

39-39: LGTM!

Also applies to: 176-232, 867-867, 988-988, 1138-1138

pacquet/crates/package-manager/src/install/tests.rs (1)

861-861: LGTM!

pacquet/crates/package-manager/src/install_package_from_registry/tests.rs (1)

54-54: LGTM!

pacquet/crates/package-manager/src/optimistic_repeat_install.rs (1)

201-201: LGTM!

Also applies to: 262-262

pacquet/crates/package-manager/src/optimistic_repeat_install/tests.rs (1)

387-426: LGTM!

pacquet/crates/config/src/lib.rs (1)

599-607: LGTM!

pacquet/crates/config/src/workspace_yaml.rs (1)

157-157: LGTM!

Also applies to: 414-415, 507-507

pacquet/crates/config/src/workspace_yaml/tests.rs (1)

17-17: LGTM!

Also applies to: 27-27

pacquet/crates/config/src/env_overlay.rs (1)

145-145: LGTM!

pacquet/crates/lockfile/src/lib.rs (1)

52-60: LGTM!

pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs (1)

91-103: LGTM!

Also applies to: 821-845

@github-actions

Copy link
Copy Markdown
Contributor

Integrated-Benchmark Report (Linux)

Scenario: Isolated linker: fresh restore, cold cache + cold store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 2.163 ± 0.124 2.021 2.471 1.05 ± 0.07
pacquet@main 2.068 ± 0.063 1.996 2.172 1.00
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 2.1634438017000006,
      "stddev": 0.12355599731814734,
      "median": 2.1411243837000002,
      "user": 2.6430539199999994,
      "system": 3.4696646199999996,
      "min": 2.0214977152,
      "max": 2.4707623442,
      "times": [
        2.1961448572,
        2.1673319162,
        2.0214977152,
        2.0611222562,
        2.2081450322,
        2.1139322962000002,
        2.4707623442,
        2.0992388982000003,
        2.1813458502,
        2.1149168512000003
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 2.0682424966,
      "stddev": 0.06263845874312111,
      "median": 2.0492527502,
      "user": 2.6466647199999995,
      "system": 3.3824483200000004,
      "min": 1.9962379712,
      "max": 2.1723616632000002,
      "times": [
        2.1723616632000002,
        2.1178463532,
        2.0538158892,
        2.0190802892,
        2.0446896112000004,
        1.9962379712,
        2.0390688582000003,
        2.0096867832000003,
        2.0646797992000003,
        2.1649577482
      ]
    }
  ]
}

Scenario: Isolated linker: fresh restore, hot cache + hot store

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 661.9 ± 24.5 640.4 721.1 1.05 ± 0.04
pacquet@main 631.3 ± 7.0 619.9 640.1 1.00
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 0.6618694566600001,
      "stddev": 0.02453285323695208,
      "median": 0.65143563636,
      "user": 0.35916398000000005,
      "system": 1.31966912,
      "min": 0.64037941436,
      "max": 0.7210874473600001,
      "times": [
        0.7210874473600001,
        0.64696880436,
        0.6734632713600001,
        0.6559024683600001,
        0.64037941436,
        0.67631900136,
        0.6681601933600001,
        0.6446408823600001,
        0.6458120113600001,
        0.64596107236
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 0.6312812132600001,
      "stddev": 0.007006881291052687,
      "median": 0.6307824823600001,
      "user": 0.34979408,
      "system": 1.32309272,
      "min": 0.61989341436,
      "max": 0.6401041363600001,
      "times": [
        0.6401041363600001,
        0.62482578736,
        0.6395556813600001,
        0.61989341436,
        0.6395004323600001,
        0.62861371036,
        0.6329512543600001,
        0.6274530593600001,
        0.6261769903600001,
        0.6337376663600001
      ]
    }
  ]
}

Scenario: Isolated linker: fresh install, cold cache + cold store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 2.258 ± 0.031 2.201 2.310 1.00
pacquet@main 2.280 ± 0.057 2.211 2.386 1.01 ± 0.03
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 2.2576644258399994,
      "stddev": 0.030894401855814418,
      "median": 2.25800544104,
      "user": 3.6899773600000003,
      "system": 3.0569423199999997,
      "min": 2.20078040404,
      "max": 2.3103377790399997,
      "times": [
        2.3103377790399997,
        2.29112605604,
        2.26647509204,
        2.20078040404,
        2.2360540150399997,
        2.26348925204,
        2.25252163004,
        2.2743936850399997,
        2.24173427004,
        2.23973207504
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 2.28013484814,
      "stddev": 0.056773869247328766,
      "median": 2.27936651054,
      "user": 3.7404098599999998,
      "system": 3.056477519999999,
      "min": 2.2112282040399998,
      "max": 2.3855201510399997,
      "times": [
        2.28018046104,
        2.25571542404,
        2.2921325220399997,
        2.3475614130399998,
        2.2190257830399998,
        2.2112282040399998,
        2.22261124104,
        2.27855256004,
        2.30882072204,
        2.3855201510399997
      ]
    }
  ]
}

Scenario: Isolated linker: fresh install, hot cache + hot store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 1.448 ± 0.029 1.411 1.507 1.00
pacquet@main 1.463 ± 0.059 1.408 1.579 1.01 ± 0.05
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 1.4479406573,
      "stddev": 0.028657675794763908,
      "median": 1.4463211487,
      "user": 1.6700689399999997,
      "system": 1.7590522799999995,
      "min": 1.4109235592,
      "max": 1.5066773252,
      "times": [
        1.4248077792,
        1.4172546882000001,
        1.4396825682,
        1.4376764382,
        1.4109235592,
        1.4607866112,
        1.4741699532,
        1.4544679212,
        1.4529597292,
        1.5066773252
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 1.4626722879999998,
      "stddev": 0.05888935062296556,
      "median": 1.4298540307,
      "user": 1.67493874,
      "system": 1.76079498,
      "min": 1.4078343992,
      "max": 1.5789725652,
      "times": [
        1.4864307782,
        1.4078343992,
        1.4266639712,
        1.5789725652,
        1.4747201662,
        1.4293951332000001,
        1.4303129282,
        1.5477401412,
        1.4259192742,
        1.4187335232
      ]
    }
  ]
}

@github-actions

Copy link
Copy Markdown
Contributor

🐰 Bencher Report

Branchpr/12022
Testbedpacquet
Click to view all benchmark results
BenchmarkLatencyBenchmark Result
milliseconds (ms)
(Result Δ%)
Upper Boundary
milliseconds (ms)
(Limit %)
isolated-linker.fresh-install.cold-cache.cold-store📈 view plot
🚷 view threshold
2,257.66 ms
(-3.69%)Baseline: 2,344.26 ms
2,813.11 ms
(80.26%)
isolated-linker.fresh-install.hot-cache.hot-store📈 view plot
🚷 view threshold
1,447.94 ms
(-1.50%)Baseline: 1,470.01 ms
1,764.02 ms
(82.08%)
isolated-linker.fresh-restore.cold-cache.cold-store📈 view plot
🚷 view threshold
2,163.44 ms
(+4.90%)Baseline: 2,062.38 ms
2,474.86 ms
(87.42%)
isolated-linker.fresh-restore.hot-cache.hot-store📈 view plot
🚷 view threshold
661.87 ms
(-2.37%)Baseline: 677.92 ms
813.51 ms
(81.36%)
🐰 View full continuous benchmarking report in Bencher

@zkochan zkochan merged commit 7ecaf3d into main May 28, 2026
27 of 28 checks passed
@zkochan zkochan deleted the dedupe-peers branch May 28, 2026 15:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant