Skip to content

feat(pacquet): port excludeLinksFromLockfile#12025

Merged
zkochan merged 1 commit into
mainfrom
excludeLinksFromLockfile
May 28, 2026
Merged

feat(pacquet): port excludeLinksFromLockfile#12025
zkochan merged 1 commit into
mainfrom
excludeLinksFromLockfile

Conversation

@zkochan

@zkochan zkochan commented May 28, 2026

Copy link
Copy Markdown
Member

Summary

Port the excludeLinksFromLockfile setting to pacquet so its behavior matches the TypeScript pnpm CLI (which has had this setting since pnpm/pnpm#6570). Refs pnpm/pnpm#12009.

When excludeLinksFromLockfile: true is set, pacquet now:

  • Omits bare link: direct dependencies from each importer's pnpm-lock.yaml entry (keeping workspace:-resolved links recorded).
  • Remaps external link targets to a stable link:<rel-from-lockfile_dir-to-modules_dir>/<alias> node id when seeding peer-resolution parents, so peer suffixes for snapshots that depend on a linked package don't carry the absolute path of the external link. The remap fires only when the link target is outside the lockfile root (per upstream's isSubdir gate).
  • Round-trips the setting into the lockfile's top-level settings: block so getOutdatedLockfileSetting can spot drift on the next install.

Where it's hooked (mirrors pnpm)

  • Config flag: pacquet-config adds exclude_links_from_lockfile: bool (default false) — mirrors config/reader/src/Config.ts:71 and the false default.
  • Importer entry: dependencies_graph_to_lockfile::build_importer mirrors upstream's addDirectDependenciesToLockfile exclude-link gate — drops link: direct deps from the importer entry unless their manifest specifier starts with workspace:.
  • Peer-resolution remap: resolve_peers::build_importer_parents ports the target rewrite at index.ts:232-244. External link: parents are seeded into ParentRefs with a remapped node id (link:<rel-from-lockfile_dir-to-modules_dir>/<alias>), so the peer suffix stays stable across machines.
  • Peer-id translation: build_peer_id special-cases link: node ids and emits PeerId::Pair { name: peer_alias, version: link_path_to_peer_version(rel) } — the exact port of upstream's peerNodeIdToPeerId link arm.
  • Snapshot child fallback: a peer-resolved child whose node id is link:<rel> and isn't in node_dep_paths uses the link node id verbatim as the snapshot child ref — mirrors upstream's pathsByNodeId.get(childNodeId) ?? (childNodeId as DepPath) in resolveChildren.

New shared helpers

  • pacquet-deps-path::link_path_to_peer_version — faithful port of upstream's linkPathToPeerVersion.ts. Iterates Unicode scalars (not raw bytes), so multi-byte UTF-8 path segments round-trip intact; pinned by a non_ascii_path_segments_round_trip test covering Latin-1, CJK, and a non-BMP emoji.
  • pacquet-fs::is_subdir — promoted from a private pacquet-cmd-shim helper so the resolver and the cmd-shim share one implementation. Mirrors npm's is-subdir.

Test plan

  • cargo nextest run -p pacquet-resolving-deps-resolver — all green, including the new tests::peers::external_link_peer_remaps_to_node_modules_when_exclude_links_on end-to-end check that mirrors the upstream e2e test `path to external link is not added to the lockfile, when it resolves a peer dependency`.
  • cargo nextest run -p pacquet-package-manager — all green, including two new dependencies_graph_to_lockfile tests ported from the upstream e2e tests for the importer-omit and workspace: carve-out behaviors.
  • cargo nextest run -p pacquet-deps-path -p pacquet-fs -p pacquet-cmd-shim -p pacquet-config — all green (new link_path_to_peer_version and is_subdir unit tests).
  • cargo check --locked --workspace --all-targets — clean.
  • cargo clippy --locked --workspace --all-targets -- --deny warnings — clean.
  • cargo fmt -- --check — clean.

Marked draft because

The pacquet-only port is complete and tested. Marking draft to invite review on the hook-point choice and the ResolvePeersOptions/ResolveImporterOptions field additions before landing.


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

Summary by CodeRabbit

Release Notes

  • New Features
    • Added exclude_links_from_lockfile configuration option to control lockfile behavior. When enabled, dependencies using the link: protocol are excluded from the lockfile, while workspace: link dependencies are retained. Configure via pnpm-workspace.yaml or environment variable.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c0b23132-0479-49c8-a26c-4872388dbe31

📥 Commits

Reviewing files that changed from the base of the PR and between 494d203 and fac8817.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (18)
  • pacquet/crates/cmd-shim/src/bin_resolver.rs
  • pacquet/crates/config/README.md
  • pacquet/crates/config/src/env_overlay.rs
  • pacquet/crates/config/src/lib.rs
  • pacquet/crates/config/src/workspace_yaml.rs
  • pacquet/crates/deps-path/src/lib.rs
  • pacquet/crates/deps-path/src/link_path_to_peer_version.rs
  • pacquet/crates/fs/src/is_subdir.rs
  • pacquet/crates/fs/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_package_from_registry/tests.rs
  • pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs
  • pacquet/crates/resolving-deps-resolver/Cargo.toml
  • 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
✅ Files skipped from review due to trivial changes (1)
  • pacquet/crates/config/README.md
🚧 Files skipped from review as they are similar to previous changes (17)
  • pacquet/crates/deps-path/src/lib.rs
  • pacquet/crates/config/src/env_overlay.rs
  • pacquet/crates/config/src/lib.rs
  • pacquet/crates/package-manager/src/install_package_from_registry/tests.rs
  • pacquet/crates/resolving-deps-resolver/Cargo.toml
  • pacquet/crates/resolving-deps-resolver/src/tests.rs
  • pacquet/crates/fs/src/lib.rs
  • pacquet/crates/package-manager/src/dependencies_graph_to_lockfile/tests.rs
  • pacquet/crates/fs/src/is_subdir.rs
  • pacquet/crates/package-manager/src/dependencies_graph_to_lockfile.rs
  • pacquet/crates/config/src/workspace_yaml.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_importer.rs
  • pacquet/crates/deps-path/src/link_path_to_peer_version.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_importer/tests.rs
  • pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs
  • pacquet/crates/cmd-shim/src/bin_resolver.rs
📜 Recent 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). (2)
  • GitHub Check: Lint and Test (windows-latest)
  • GitHub Check: Run benchmark on ubuntu-latest

📝 Walkthrough

Walkthrough

Adds an exclude_links_from_lockfile config (env/workspace/docs), introduces shared fs/link utilities, threads the flag through fresh-install and resolver options, omits external link: direct deps from lockfiles (except workspace:), and remaps/encodes link-based peers for deterministic peer resolution.

Changes

Exclude Links from Lockfile

Layer / File(s) Summary
Configuration schema and sources
pacquet/crates/config/src/lib.rs, pacquet/crates/config/src/workspace_yaml.rs, pacquet/crates/config/src/env_overlay.rs, pacquet/crates/config/README.md
Adds exclude_links_from_lockfile to Config and WorkspaceSettings, reads it from PNPM_CONFIG_EXCLUDE_LINKS_FROM_LOCKFILE, clears workspace-only fields from global config, and documents the setting.
Shared path utilities and encoding
pacquet/crates/fs/src/is_subdir.rs, pacquet/crates/fs/src/lib.rs, pacquet/crates/deps-path/src/link_path_to_peer_version.rs, pacquet/crates/deps-path/src/lib.rs, pacquet/crates/cmd-shim/src/bin_resolver.rs
Introduces fs::is_subdir (lexical normalization) and link_path_to_peer_version() encoder; replaces local is_subdir usage in cmd-shim and adds unit tests for both utilities.
Lockfile filtering for link dependencies
pacquet/crates/package-manager/src/dependencies_graph_to_lockfile.rs, pacquet/crates/package-manager/src/dependencies_graph_to_lockfile/tests.rs
build_importer() accepts exclude_links_from_lockfile and skips external link: direct deps (unless the manifest specifier is workspace:); tests validate external-link omission, workspace-link retention, and that the flag is recorded in lockfile.settings.
Fresh-install threading and resolver wiring
pacquet/crates/resolving-deps-resolver/Cargo.toml, pacquet/crates/resolving-deps-resolver/src/resolve_importer.rs, pacquet/crates/resolving-deps-resolver/src/resolve_importer/tests.rs, pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs, pacquet/crates/package-manager/src/install_package_from_registry/tests.rs
Derives per-importer modules_dir, threads lockfile_dir, modules_dir, and exclude_links_from_lockfile into ResolveImporterOptions, updates resolver calls to use these options, and adjusts resolver crate dependencies and tests.
Peer resolution remapping and encoding
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs, pacquet/crates/resolving-deps-resolver/src/tests.rs
Extends ResolvePeersOptions with link-remap fields; encodes link: leaf peers using link_path_to_peer_version; remaps external link: parents to link:node_modules/<alias> when exclude_links_from_lockfile is enabled; ensures depPath insertion for link: nodes and updates peer-id construction and parent-ref insertion; adds tests for the behavior.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • KSXGitHub

Poem

"I nibble dots and slashy trails, 🐇
I turn link: paths to tidy + tales,
Lockfiles skip the wandering link,
Workspace friends stay—no missing link,
Peers now map where rabbit paws set sails."

🚥 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 'feat(pacquet): port excludeLinksFromLockfile' directly describes the main objective of the pull request, which is to port the pnpm excludeLinksFromLockfile feature into pacquet.
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 excludeLinksFromLockfile

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.

@zkochan zkochan marked this pull request as ready for review May 28, 2026 15:07
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Review Summary by Qodo

Port excludeLinksFromLockfile setting to pacquet for stable peer-suffix hashes across machines

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Port pnpm's excludeLinksFromLockfile setting to pacquet for stable peer-suffix hashes
• Add config flag exclude_links_from_lockfile with env-overlay and workspace YAML support
• Omit bare link: direct deps from lockfile importer entries (keep workspace: links)
• Remap external link targets to stable link:/ node IDs for peer resolution
• Extract shared is_subdir helper and port linkPathToPeerVersion for link-path encoding
Diagram
flowchart LR
  Config["Config: exclude_links_from_lockfile flag"]
  Importer["build_importer: skip bare link: deps"]
  PeerRemap["remap_link_node_id: link:node_modules/alias"]
  PeerId["build_peer_id: link_path_to_peer_version"]
  Snapshot["Snapshot child: use remapped link node ID"]
  
  Config --> Importer
  Config --> PeerRemap
  PeerRemap --> PeerId
  PeerId --> Snapshot

Loading

Grey Divider

File Changes

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

Add exclude_links_from_lockfile config field

pacquet/crates/config/src/lib.rs


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

Wire exclude_links_from_lockfile env overlay

pacquet/crates/config/src/env_overlay.rs


3. pacquet/crates/config/src/workspace_yaml.rs ⚙️ Configuration changes +3/-0

Add exclude_links_from_lockfile to workspace settings

pacquet/crates/config/src/workspace_yaml.rs


View more (15)
4. pacquet/crates/config/README.md 📝 Documentation +1/-0

Document exclude_links_from_lockfile setting

pacquet/crates/config/README.md


5. pacquet/crates/fs/src/lib.rs ✨ Enhancement +2/-0

Export is_subdir helper function

pacquet/crates/fs/src/lib.rs


6. pacquet/crates/fs/src/is_subdir.rs ✨ Enhancement +45/-0

Extract is_subdir from cmd-shim to shared fs module

pacquet/crates/fs/src/is_subdir.rs


7. pacquet/crates/cmd-shim/src/bin_resolver.rs ✨ Enhancement +2/-12

Use is_subdir from pacquet_fs instead of local impl

pacquet/crates/cmd-shim/src/bin_resolver.rs


8. pacquet/crates/deps-path/src/lib.rs ✨ Enhancement +2/-0

Export link_path_to_peer_version helper

pacquet/crates/deps-path/src/lib.rs


9. pacquet/crates/deps-path/src/link_path_to_peer_version.rs ✨ Enhancement +111/-0

Port linkPathToPeerVersion for link-path encoding

pacquet/crates/deps-path/src/link_path_to_peer_version.rs


10. pacquet/crates/package-manager/src/dependencies_graph_to_lockfile.rs ✨ Enhancement +18/-2

Skip bare link: deps when exclude_links_from_lockfile true

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


11. pacquet/crates/package-manager/src/dependencies_graph_to_lockfile/tests.rs 🧪 Tests +98/-0

Add tests for exclude_links_from_lockfile lockfile rendering

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


12. pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs ✨ Enhancement +10/-4

Thread exclude_links_from_lockfile through install pipeline

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


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

Initialize exclude_links_from_lockfile in test config

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


14. pacquet/crates/resolving-deps-resolver/src/resolve_importer.rs ✨ Enhancement +30/-2

Add exclude_links_from_lockfile options to resolver

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


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

Initialize exclude_links_from_lockfile in test defaults

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


16. pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs ✨ Enhancement +145/-17

Remap external link node IDs and encode in peer suffixes

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


17. pacquet/crates/resolving-deps-resolver/src/tests.rs 🧪 Tests +101/-0

Add test for external link peer remapping with exclude_links_from_lockfile

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


18. pacquet/crates/resolving-deps-resolver/Cargo.toml Dependencies +3/-2

Add pacquet-fs and pathdiff dependencies

pacquet/crates/resolving-deps-resolver/Cargo.toml


Grey Divider

Qodo Logo

@github-actions

github-actions Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

Micro-Benchmark Results

Linux

group                          main                                   pr
-----                          ----                                   --
tarball/download_dependency    1.03      5.8±0.47ms   746.9 KB/sec    1.00      5.6±0.14ms   769.8 KB/sec

@codecov-commenter

codecov-commenter commented May 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.41520% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 88.39%. Comparing base (7ecaf3d) to head (fac8817).

Files with missing lines Patch % Lines
...rates/resolving-deps-resolver/src/resolve_peers.rs 98.38% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #12025      +/-   ##
==========================================
+ Coverage   88.30%   88.39%   +0.09%     
==========================================
  Files         228      230       +2     
  Lines       28961    29111     +150     
==========================================
+ Hits        25573    25733     +160     
+ Misses       3388     3378      -10     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@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: 1

🤖 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.

Inline comments:
In `@pacquet/crates/deps-path/src/link_path_to_peer_version.rs`:
- Around line 15-43: The code currently iterates raw bytes and uses out.push(c
as char) which corrupts non-ASCII UTF-8; change the loop to iterate Unicode
scalar values (chars) from rel_path rather than bytes: compute the index/offset
for skipping leading '.' using char iteration (or use
rel_path.trim_start_matches('.') to get the slice after leading dots), then
iterate over rel_path.chars() (or the trimmed &str) checking replacement
conditions on char values (e.g. c < '\u{20}' || c == '"' || c == '*' || c == '+'
... ) and push the char with out.push(c) while preserving the existing
last_was_plus logic and '+' insertion behavior so UTF‑8 segments are preserved
and pnpm token behavior is matched; replace references to bytes, bytes[i], and c
as char with char-based variables (use rel_path, out, last_was_plus to locate
changes).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 25bd2515-2822-4c28-9b19-4d6997ea2a73

📥 Commits

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

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (18)
  • pacquet/crates/cmd-shim/src/bin_resolver.rs
  • pacquet/crates/config/README.md
  • pacquet/crates/config/src/env_overlay.rs
  • pacquet/crates/config/src/lib.rs
  • pacquet/crates/config/src/workspace_yaml.rs
  • pacquet/crates/deps-path/src/lib.rs
  • pacquet/crates/deps-path/src/link_path_to_peer_version.rs
  • pacquet/crates/fs/src/is_subdir.rs
  • pacquet/crates/fs/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_package_from_registry/tests.rs
  • pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs
  • pacquet/crates/resolving-deps-resolver/Cargo.toml
  • 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
🧰 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/package-manager/src/install_package_from_registry/tests.rs
  • pacquet/crates/config/src/env_overlay.rs
  • pacquet/crates/deps-path/src/lib.rs
  • pacquet/crates/deps-path/src/link_path_to_peer_version.rs
  • pacquet/crates/fs/src/is_subdir.rs
  • pacquet/crates/config/src/workspace_yaml.rs
  • pacquet/crates/fs/src/lib.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_importer.rs
  • pacquet/crates/package-manager/src/dependencies_graph_to_lockfile/tests.rs
  • pacquet/crates/package-manager/src/dependencies_graph_to_lockfile.rs
  • pacquet/crates/resolving-deps-resolver/src/tests.rs
  • pacquet/crates/config/src/lib.rs
  • pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs
  • pacquet/crates/cmd-shim/src/bin_resolver.rs
🧠 Learnings (5)
📚 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/package-manager/src/install_package_from_registry/tests.rs
  • pacquet/crates/config/src/env_overlay.rs
  • pacquet/crates/deps-path/src/lib.rs
  • pacquet/crates/deps-path/src/link_path_to_peer_version.rs
  • pacquet/crates/fs/src/is_subdir.rs
  • pacquet/crates/config/src/workspace_yaml.rs
  • pacquet/crates/fs/src/lib.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_importer.rs
  • pacquet/crates/package-manager/src/dependencies_graph_to_lockfile/tests.rs
  • pacquet/crates/package-manager/src/dependencies_graph_to_lockfile.rs
  • pacquet/crates/resolving-deps-resolver/src/tests.rs
  • pacquet/crates/config/src/lib.rs
  • pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs
  • pacquet/crates/cmd-shim/src/bin_resolver.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/package-manager/src/install_package_from_registry/tests.rs
  • pacquet/crates/config/src/env_overlay.rs
  • pacquet/crates/deps-path/src/lib.rs
  • pacquet/crates/deps-path/src/link_path_to_peer_version.rs
  • pacquet/crates/fs/src/is_subdir.rs
  • pacquet/crates/config/src/workspace_yaml.rs
  • pacquet/crates/fs/src/lib.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_importer.rs
  • pacquet/crates/package-manager/src/dependencies_graph_to_lockfile/tests.rs
  • pacquet/crates/package-manager/src/dependencies_graph_to_lockfile.rs
  • pacquet/crates/resolving-deps-resolver/src/tests.rs
  • pacquet/crates/config/src/lib.rs
  • pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs
  • pacquet/crates/cmd-shim/src/bin_resolver.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/package-manager/src/install_package_from_registry/tests.rs
  • pacquet/crates/config/src/env_overlay.rs
  • pacquet/crates/deps-path/src/lib.rs
  • pacquet/crates/deps-path/src/link_path_to_peer_version.rs
  • pacquet/crates/fs/src/is_subdir.rs
  • pacquet/crates/config/src/workspace_yaml.rs
  • pacquet/crates/fs/src/lib.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_importer.rs
  • pacquet/crates/package-manager/src/dependencies_graph_to_lockfile/tests.rs
  • pacquet/crates/package-manager/src/dependencies_graph_to_lockfile.rs
  • pacquet/crates/resolving-deps-resolver/src/tests.rs
  • pacquet/crates/config/src/lib.rs
  • pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs
  • pacquet/crates/cmd-shim/src/bin_resolver.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/tests.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_peers.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/tests.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs
🔇 Additional comments (17)
pacquet/crates/config/src/lib.rs (1)

531-538: LGTM!

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

135-135: LGTM!

Also applies to: 411-411, 504-504

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

141-141: LGTM!

pacquet/crates/config/README.md (1)

31-31: LGTM!

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

103-103: LGTM!

Also applies to: 128-141, 171-173

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

1108-1204: LGTM!

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

49-49: LGTM!

pacquet/crates/fs/src/is_subdir.rs (1)

1-45: LGTM!

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

2-2: LGTM!

Also applies to: 7-7

pacquet/crates/deps-path/src/lib.rs (1)

19-19: LGTM!

Also applies to: 28-28

pacquet/crates/cmd-shim/src/bin_resolver.rs (1)

1-2: LGTM!

pacquet/crates/resolving-deps-resolver/Cargo.toml (1)

17-18: LGTM!

Also applies to: 29-29

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

95-112: LGTM!

Also applies to: 156-165, 210-210, 291-291

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

94-96: LGTM!

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

550-554: LGTM!

Also applies to: 561-561, 590-592, 1049-1049

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

48-49: LGTM!

Also applies to: 76-88, 99-135, 397-418, 694-696, 726-735, 873-905, 1155-1187, 1189-1229

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

1413-1512: LGTM!

Comment thread pacquet/crates/deps-path/src/link_path_to_peer_version.rs Outdated
@github-actions

github-actions Bot commented May 28, 2026

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 1.691 ± 0.080 1.620 1.887 1.00
pacquet@main 1.753 ± 0.237 1.612 2.409 1.04 ± 0.15
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 1.69087667744,
      "stddev": 0.07950632332954874,
      "median": 1.67448450574,
      "user": 2.8220533399999996,
      "system": 1.9964311,
      "min": 1.61967607574,
      "max": 1.88665772074,
      "times": [
        1.63209673574,
        1.88665772074,
        1.7287456287399998,
        1.67983796874,
        1.6439006407399999,
        1.67152391574,
        1.67744509574,
        1.61967607574,
        1.73752659974,
        1.63135639274
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 1.7526575560400002,
      "stddev": 0.2369288043806878,
      "median": 1.67660143874,
      "user": 2.80474074,
      "system": 2.0073233999999998,
      "min": 1.61188601874,
      "max": 2.40861024474,
      "times": [
        2.40861024474,
        1.64437898774,
        1.63234858274,
        1.73575559674,
        1.73458818374,
        1.61188601874,
        1.64562419274,
        1.77317601374,
        1.63262905474,
        1.7075786847399999
      ]
    }
  ]
}

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

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 483.3 ± 17.1 475.0 531.4 1.00
pacquet@main 484.9 ± 25.2 467.9 546.3 1.00 ± 0.06
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 0.4832891943,
      "stddev": 0.01706743488296279,
      "median": 0.4785631518000001,
      "user": 0.35769539999999994,
      "system": 0.7932845799999999,
      "min": 0.47503948130000007,
      "max": 0.5314427233000001,
      "times": [
        0.5314427233000001,
        0.47515647930000005,
        0.47542501130000003,
        0.47828133130000006,
        0.48142292130000003,
        0.4808329633,
        0.47503948130000007,
        0.47745116130000004,
        0.47884497230000006,
        0.4789948983
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 0.4849217586,
      "stddev": 0.025212295682907115,
      "median": 0.4744150838000001,
      "user": 0.3504655,
      "system": 0.8010804800000001,
      "min": 0.46785726530000005,
      "max": 0.5462511523000001,
      "times": [
        0.5462511523000001,
        0.47491178130000006,
        0.5141575403,
        0.4716758643,
        0.4784263503,
        0.4773411993000001,
        0.46785726530000005,
        0.47391838630000005,
        0.47175461730000007,
        0.47292342930000003
      ]
    }
  ]
}

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

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 2.052 ± 0.020 2.013 2.076 1.00 ± 0.02
pacquet@main 2.049 ± 0.026 2.011 2.082 1.00
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 2.0522259606800004,
      "stddev": 0.019750756354975228,
      "median": 2.05655644258,
      "user": 4.07996662,
      "system": 1.9231735,
      "min": 2.0133580935800004,
      "max": 2.0761420845800003,
      "times": [
        2.0133580935800004,
        2.0576363415800003,
        2.03944187958,
        2.05571013558,
        2.0761420845800003,
        2.05740274958,
        2.0696135105800004,
        2.0694173465800003,
        2.0279861735800004,
        2.05555129158
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 2.04943609338,
      "stddev": 0.026195259855351816,
      "median": 2.05869736158,
      "user": 4.083802620000001,
      "system": 1.9149888000000002,
      "min": 2.0107088185800004,
      "max": 2.08186772158,
      "times": [
        2.06951379858,
        2.0107088185800004,
        2.02164560758,
        2.01836582558,
        2.03173477758,
        2.0588559875800003,
        2.07464982958,
        2.0684798315800004,
        2.0585387355800004,
        2.08186772158
      ]
    }
  ]
}

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

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 1.272 ± 0.015 1.253 1.293 1.00
pacquet@main 1.275 ± 0.026 1.254 1.345 1.00 ± 0.02
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 1.2721351328800001,
      "stddev": 0.01466636180914383,
      "median": 1.2671180511800002,
      "user": 1.70210456,
      "system": 1.09306214,
      "min": 1.2528635161800001,
      "max": 1.29268899318,
      "times": [
        1.2528635161800001,
        1.25802159518,
        1.26594320718,
        1.2847676271800001,
        1.2885204731800002,
        1.29268899318,
        1.26307699318,
        1.26829289518,
        1.2598869641800001,
        1.28728906418
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 1.2754044545799998,
      "stddev": 0.026411181800023508,
      "median": 1.27107848668,
      "user": 1.6890561600000003,
      "system": 1.0917308399999999,
      "min": 1.25440778618,
      "max": 1.34495922018,
      "times": [
        1.25535277618,
        1.26838107018,
        1.26059047218,
        1.27384199918,
        1.28486926818,
        1.2602744321800001,
        1.27759161818,
        1.27377590318,
        1.25440778618,
        1.34495922018
      ]
    }
  ]
}

@github-actions

github-actions Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

🐰 Bencher Report

Branchpr/12025
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,052.23 ms
(-11.87%)Baseline: 2,328.67 ms
2,794.40 ms
(73.44%)
isolated-linker.fresh-install.hot-cache.hot-store📈 view plot
🚷 view threshold
1,272.14 ms
(-12.58%)Baseline: 1,455.27 ms
1,746.33 ms
(72.85%)
isolated-linker.fresh-restore.cold-cache.cold-store📈 view plot
🚷 view threshold
1,690.88 ms
(-17.86%)Baseline: 2,058.58 ms
2,470.30 ms
(68.45%)
isolated-linker.fresh-restore.hot-cache.hot-store📈 view plot
🚷 view threshold
483.29 ms
(-28.86%)Baseline: 679.34 ms
815.21 ms
(59.28%)
🐰 View full continuous benchmarking report in Bencher

@zkochan zkochan force-pushed the excludeLinksFromLockfile branch from a85dbbc to 494d203 Compare May 28, 2026 16:18
@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown

Actionable comments posted: 0

Mirror pnpm's `excludeLinksFromLockfile` setting:

* `pacquet-config`: add `exclude_links_from_lockfile` (default `false`)
  with env-overlay and `pnpm-workspace.yaml` wiring.
* `pacquet-package-manager`: thread the flag through
  `install_with_fresh_lockfile` into the lockfile renderer. A `link:`
  direct dep is omitted from the importer's snapshot unless its
  manifest specifier starts with `workspace:`.
* `pacquet-resolving-deps-resolver`: port the peer-resolution arm of
  the feature — `resolve_peers` now seeds `ParentRefs` for external
  `link:` direct deps with a node id rewritten to
  `link:<rel-from-lockfile_dir-to-modules_dir>/<alias>` when the link
  target is outside the lockfile root, mirroring upstream's
  `index.ts:232-244` `target` rewrite plus the `peerNodeIdToPeerId`
  link arm. The snapshot child edge falls back to the link node id
  verbatim when `pathsByNodeId` misses, matching upstream's
  `pathsByNodeId.get(childNodeId) ?? childNodeId` arm in
  `resolveChildren`.
* `pacquet-deps-path`: port `linkPathToPeerVersion`.
* `pacquet-fs`: extract `is_subdir` from `pacquet-cmd-shim` so the
  resolver and the cmd-shim share one implementation.

Ports #6570, refs #12009.
@zkochan zkochan force-pushed the excludeLinksFromLockfile branch from 494d203 to fac8817 Compare May 28, 2026 16:44
@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown

Actionable comments posted: 0

@zkochan zkochan merged commit 82e31d6 into main May 28, 2026
28 checks passed
@zkochan zkochan deleted the excludeLinksFromLockfile branch May 28, 2026 17:16
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.

2 participants