feat(pacquet/deps-resolver): port dedupePeers setting#12022
Conversation
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.
📝 WalkthroughWalkthroughThis PR implements the ChangesdedupePeers Feature Implementation
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
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.
Review Summary by QodoPort dedupePeers setting for peer-dependency suffix collapsing
WalkthroughsDescription• 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 Diagramflowchart 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
File Changes1. pacquet/crates/config/src/env_overlay.rs
|
Micro-Benchmark ResultsLinux |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
pacquet/crates/resolving-deps-resolver/src/tests.rs (1)
758-768: ⚡ Quick winTrim 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
📒 Files selected for processing (16)
pacquet/crates/config/src/env_overlay.rspacquet/crates/config/src/lib.rspacquet/crates/config/src/workspace_yaml.rspacquet/crates/config/src/workspace_yaml/tests.rspacquet/crates/lockfile/src/lib.rspacquet/crates/package-manager/src/dependencies_graph_to_lockfile.rspacquet/crates/package-manager/src/dependencies_graph_to_lockfile/tests.rspacquet/crates/package-manager/src/install/tests.rspacquet/crates/package-manager/src/install_package_from_registry/tests.rspacquet/crates/package-manager/src/install_with_fresh_lockfile.rspacquet/crates/package-manager/src/optimistic_repeat_install.rspacquet/crates/package-manager/src/optimistic_repeat_install/tests.rspacquet/crates/resolving-deps-resolver/src/resolve_importer.rspacquet/crates/resolving-deps-resolver/src/resolve_importer/tests.rspacquet/crates/resolving-deps-resolver/src/resolve_peers.rspacquet/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 firespnpm:<channel>events throughglobalLogger,logger.debug(), orstreamParser.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 plainStringor&str.
If upstream always validates before construction, validate in pacquet's wrapper too. The wrapper must construct only viaTryFrom<String>and/orFromStr. Do not provide an infallible public constructor.
If upstream never validates, just brand for type-safety. Expose an infallibleFrom<String>(andFrom<&str>when convenient).
If upstream occasionally constructs without validation, exposefrom_str_uncheckedas 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 manualimplonly when conversion needs custom logic.
String-literal unions should becomeenums, 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 (&Pathover&PathBuf,&strover&String) when it doesn't force extra copies.
PreferArc::clone(&x)/Rc::clone(&x)overx.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. Writeuse super::{Foo, bar}instead ofuse super::*;. Two forms stay allowed: external-crate preludes likeuse rayon::prelude::*;and root-of-module re-...
Files:
pacquet/crates/resolving-deps-resolver/src/resolve_importer/tests.rspacquet/crates/lockfile/src/lib.rspacquet/crates/package-manager/src/install_package_from_registry/tests.rspacquet/crates/config/src/env_overlay.rspacquet/crates/package-manager/src/install/tests.rspacquet/crates/package-manager/src/install_with_fresh_lockfile.rspacquet/crates/package-manager/src/dependencies_graph_to_lockfile.rspacquet/crates/config/src/workspace_yaml/tests.rspacquet/crates/resolving-deps-resolver/src/resolve_importer.rspacquet/crates/config/src/workspace_yaml.rspacquet/crates/config/src/lib.rspacquet/crates/resolving-deps-resolver/src/resolve_peers.rspacquet/crates/package-manager/src/optimistic_repeat_install.rspacquet/crates/package-manager/src/optimistic_repeat_install/tests.rspacquet/crates/package-manager/src/dependencies_graph_to_lockfile/tests.rspacquet/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.rspacquet/crates/lockfile/src/lib.rspacquet/crates/package-manager/src/install_package_from_registry/tests.rspacquet/crates/config/src/env_overlay.rspacquet/crates/package-manager/src/install/tests.rspacquet/crates/package-manager/src/install_with_fresh_lockfile.rspacquet/crates/package-manager/src/dependencies_graph_to_lockfile.rspacquet/crates/config/src/workspace_yaml/tests.rspacquet/crates/resolving-deps-resolver/src/resolve_importer.rspacquet/crates/config/src/workspace_yaml.rspacquet/crates/config/src/lib.rspacquet/crates/resolving-deps-resolver/src/resolve_peers.rspacquet/crates/package-manager/src/optimistic_repeat_install.rspacquet/crates/package-manager/src/optimistic_repeat_install/tests.rspacquet/crates/package-manager/src/dependencies_graph_to_lockfile/tests.rspacquet/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.rspacquet/crates/lockfile/src/lib.rspacquet/crates/package-manager/src/install_package_from_registry/tests.rspacquet/crates/config/src/env_overlay.rspacquet/crates/package-manager/src/install/tests.rspacquet/crates/package-manager/src/install_with_fresh_lockfile.rspacquet/crates/package-manager/src/dependencies_graph_to_lockfile.rspacquet/crates/config/src/workspace_yaml/tests.rspacquet/crates/resolving-deps-resolver/src/resolve_importer.rspacquet/crates/config/src/workspace_yaml.rspacquet/crates/config/src/lib.rspacquet/crates/resolving-deps-resolver/src/resolve_peers.rspacquet/crates/package-manager/src/optimistic_repeat_install.rspacquet/crates/package-manager/src/optimistic_repeat_install/tests.rspacquet/crates/package-manager/src/dependencies_graph_to_lockfile/tests.rspacquet/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.rspacquet/crates/lockfile/src/lib.rspacquet/crates/package-manager/src/install_package_from_registry/tests.rspacquet/crates/config/src/env_overlay.rspacquet/crates/package-manager/src/install/tests.rspacquet/crates/package-manager/src/install_with_fresh_lockfile.rspacquet/crates/package-manager/src/dependencies_graph_to_lockfile.rspacquet/crates/config/src/workspace_yaml/tests.rspacquet/crates/resolving-deps-resolver/src/resolve_importer.rspacquet/crates/config/src/workspace_yaml.rspacquet/crates/config/src/lib.rspacquet/crates/resolving-deps-resolver/src/resolve_peers.rspacquet/crates/package-manager/src/optimistic_repeat_install.rspacquet/crates/package-manager/src/optimistic_repeat_install/tests.rspacquet/crates/package-manager/src/dependencies_graph_to_lockfile/tests.rspacquet/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.rspacquet/crates/resolving-deps-resolver/src/resolve_importer.rspacquet/crates/resolving-deps-resolver/src/resolve_peers.rspacquet/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.rspacquet/crates/resolving-deps-resolver/src/resolve_importer.rspacquet/crates/resolving-deps-resolver/src/resolve_peers.rspacquet/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
Integrated-Benchmark Report (Linux)Scenario: Isolated linker: fresh restore, cold cache + cold store
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
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
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
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
]
}
]
} |
|
| Branch | pr/12022 |
| Testbed | pacquet |
Click to view all benchmark results
| Benchmark | Latency | Benchmark 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%) |
Summary
Port the
dedupePeerssetting from pnpm. When enabled, peer-dependency suffixes indepPaths 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). Defaultfalse, matching pnpm.Config.dedupe_peers,WorkspaceSettings.dedupe_peers,PACQUET_DEDUPE_PEERSenv overlay, and theclear_workspace_only_fields/apply_towiring.ResolvePeersOptions.dedupe_peersthreaded throughResolveImporterOptionsand consumed inWalker::build_peer_id— when on, emitsPeerId::Pair { name, version }from the resolved package instead of the peer's ownDepPath. Mirrors pnpm'speerNodeIdToPeerIddedupePeersbranch.LockfileSettings.dedupe_peers: Option<bool>(omitted whenfalse, mirroring pnpm'sdedupePeers: opts.dedupePeers || undefined).GraphToLockfileOptions.dedupe_peersplumbed frominstall_with_fresh_lockfile.WorkspaceStateSettings.dedupe_peersis now compared bysettings_matchand written bycurrent_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:
resolvePeers.ts'uses version-only peer suffixes without nested dep paths'→dedupe_peers_collapses_nested_peer_suffixes+no_dedupe_peers_keeps_nested_peer_suffixes(same fixture under the default, nested suffix preserved).resolvePeers.ts'transitive peers use version-only suffixes'→dedupe_peers_propagates_transitive_peer_to_parent.peerDependencies.tsexpect(lockfile.settings.dedupePeers).toBe(true)+lockfile/fs/test/write.test.tsomission check →dedupe_peers_round_trips_through_lockfile_settings(assertsdedupePeers: trueon and absence of the key off).getOutdatedLockfileSetting'settings.dedupePeers'branch →returns_skipped_when_dedupe_peers_driftin the optimistic-repeat-install drift suite.parses_common_settings_from_yamlto assertdedupePeers: trueround-trips frompnpm-workspace.yaml.Not ported (multi-importer / multi-workspace e2e scope, separate from this slice):
resolvePeers.ts'multi-project: different peer versions produce different instances'— pacquet'sresolve_peersruns 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,typosall clean.pacquet-config,pacquet-lockfile,pacquet-package-manager, andpacquet-resolving-deps-resolverpass locally.Written by an agent (Claude Code, claude-opus-4-7).
Summary by CodeRabbit
dedupe_peersconfiguration option to control how peer dependency identifiers are generated and deduplicated across nested dependenciesPNPM_CONFIG_DEDUPE_PEERSenvironment variable and workspace YAML configuration support