Skip to content

fix(pacquet/resolving-npm-resolver): honor linkWorkspacePackages for bare-semver deps#11930

Merged
zkochan merged 5 commits into
mainfrom
fix/11929
May 25, 2026
Merged

fix(pacquet/resolving-npm-resolver): honor linkWorkspacePackages for bare-semver deps#11930
zkochan merged 5 commits into
mainfrom
fix/11929

Conversation

@zkochan

@zkochan zkochan commented May 25, 2026

Copy link
Copy Markdown
Member

Summary

  • Mirrors pnpm's three workspace branches around pickPackage in pacquet's npm resolver, so a bare-semver dependency on a workspace package can resolve to the local copy instead of going to (or 404-ing on) the registry.
  • Adds tri-state linkWorkspacePackages (true | false | "deep") parsing to pnpm-workspace.yaml, threads it through Config, and encodes it into ResolveOptions::always_try_workspace_packages at the install layer.
  • Includes the new entry in .pnpm-workspace-state-v1.json so a subsequent pnpm invocation can detect a setting change.

Closes #11929.

Why

Pacquet's NpmResolver::resolve_impl only routed workspace:-prefixed specs through try_resolve_from_workspace. For bare-semver specs (e.g. \"@dev/build-tools\": \"^1.0.0\" where @dev/build-tools is a workspace package), it fell straight through to the registry picker. The vlt.sh babylon fixture hits this path, gets a 404 from npm for @dev/build-tools, and reports DNF on every variation.

For users whose workspace package shares a name with a published npm package, the same gap silently links the wrong copy.

How

In resolving-npm-resolver/src/npm_resolver.rs::resolve_impl:

  • Compute workspace_packages_active = always_try_workspace_packages ? workspace_packages : None. Matches pnpm's opts.alwaysTryWorkspacePackages !== false gate at index.ts#L435.
  • On pick_package returning Ok(None) or Err(_), try try_resolve_from_workspace_packages (already used by the workspace: path) before reporting "no matching version" or surfacing the original fetch error. Workspace errors are swallowed.
  • On Ok(Some(picked)), run the registry-pick + workspace-shadow logic from index.ts#L550-L582: exact name@version match wins, a higher workspace version wins, preferWorkspacePackages wins.

The existing try_resolve_from_workspace_packages, pick_matching_local_version_or_null, and resolve_from_local_package helpers in resolve_from_workspace.rs are promoted to pub(crate) so the npm resolver can call them directly (matching the way upstream factors the same helpers).

In pacquet-config:

  • New LinkWorkspacePackages tri-state enum with a custom Deserialize accepting bool | "deep", mirroring Config.linkWorkspacePackages.
  • Added to Config (default Off) and to WorkspaceSettings::apply_to. Listed in clear_workspace_only_fields to match the global config filter at configFileKey.ts#L113.

In pacquet-package-manager:

  • install_with_fresh_lockfile.rs sets always_try_workspace_packages from config.link_workspace_packages != Off.
  • install.rs serializes the value into WorkspaceStateSettings.link_workspace_packages as the same tri-state JSON shape pnpm writes.

Behavioral note

Pacquet's deps-resolver currently passes a single base_opts to every resolve call, with no per-depth differentiation. Pnpm gates the workspace lookup by linkWorkspacePackagesDepth >= currentDepth so true applies only to depth 0 and "deep" applies to every depth. With pacquet's current shape, true and "deep" both collapse onto a single flag that applies at every depth. The (rare) result is that a transitive registry dep whose name and version match a workspace package may resolve to the workspace instead of the registry under linkWorkspacePackages: true. Threading per-call depth into the resolver is a follow-up.

Test plan

  • cargo nextest run -p pacquet-config -p pacquet-resolving-npm-resolver -p pacquet-package-manager — 743 passed
  • cargo clippy --locked --workspace --all-targets -- --deny warnings
  • cargo fmt -- --check
  • Ported the upstream tests at resolving/npm-resolver/test/index.ts covering branches 2 and 3 (falls_back_to_workspace_when_registry_returns_404, workspace_shadows_registry_when_name_and_version_match, always_try_workspace_packages_false_skips_workspace_match, registry_version_higher_than_workspace_keeps_registry_pick, prefer_workspace_packages_keeps_workspace_over_newer_registry, workspace_higher_version_shadows_registry_pick).

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

Summary by CodeRabbit

  • New Features

    • Added a tri-state linkWorkspacePackages setting (off, direct-only, or "deep") — accepts true/false/"deep".
    • Resolver can consult, fallback to, or shadow registry results with workspace packages according to the new setting.
    • Workspace-level setting can be specified in workspace YAML and is persisted in workspace state so installs remember behavior.
  • Tests

    • Added tests validating deserialization, config application, and resolver scenarios (fallback, shadowing, preference).

Review Change Stack

…bare-semver deps

Pacquet's npm resolver only consulted the workspace map for
`workspace:`-prefixed wanted deps; bare-semver ranges always went
straight to the registry. When the workspace package isn't on npm
(e.g. babylon's `@dev/build-tools`), the install errored out with
404; when a same-named package existed on the registry, pacquet
silently linked the wrong copy.

Mirror pnpm's three workspace branches around `pickPackage`:

* registry pick succeeded + workspace shadow (exact `name@version`
  match, higher local version, or `preferWorkspacePackages`),
* registry pick returned `null` → workspace fallback,
* registry fetch errored → workspace fallback (swallow workspace
  errors and re-raise the registry error).

Gated by `link-workspace-packages` (true / false / "deep") which is
now parsed from `pnpm-workspace.yaml`, flows through `Config`, and
is encoded into `ResolveOptions::always_try_workspace_packages` at
the install layer. Tri-state semantics are preserved on the config
side; pacquet's single-`base_opts` deps-resolver collapses `true`
and `"deep"` onto the same per-call flag until depth threading
lands.

Closes #11929.
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented May 25, 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: 2e98bef5-0d62-44e5-bbfc-496b04008020

📥 Commits

Reviewing files that changed from the base of the PR and between dad6209 and d2d2e16.

📒 Files selected for processing (1)
  • pacquet/crates/resolving-npm-resolver/src/npm_resolver/tests.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). (7)
  • GitHub Check: Lint and Test (ubuntu-latest)
  • GitHub Check: Dylint
  • GitHub Check: Lint and Test (windows-latest)
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Code Coverage
  • 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-npm-resolver/src/npm_resolver/tests.rs
🧠 Learnings (3)
📚 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-npm-resolver/src/npm_resolver/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-npm-resolver/src/npm_resolver/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-npm-resolver/src/npm_resolver/tests.rs
🔇 Additional comments (1)
pacquet/crates/resolving-npm-resolver/src/npm_resolver/tests.rs (1)

525-527: LGTM!

Also applies to: 552-552, 584-584, 614-614, 643-643, 673-673, 701-702, 719-719, 753-753, 781-782, 803-803, 831-833, 862-862, 885-885


📝 Walkthrough

Walkthrough

Adds a tri-state LinkWorkspacePackages config, wires it through workspace settings and persisted workspace state, and implements workspace fallback/shadowing in the npm resolver with new tests.

Changes

Workspace Package Linking

Layer / File(s) Summary
LinkWorkspacePackages config contract
pacquet/crates/config/src/lib.rs
Tri-state enum (Off, DirectOnly, Deep) with serde deserialization from boolean or "deep" string, depth-gating enabled_at_depth helper, and integration into Config struct with default value.
Workspace settings pipeline integration
pacquet/crates/config/src/workspace_yaml.rs, pacquet/crates/config/src/workspace_yaml/tests.rs
link_workspace_packages field on WorkspaceSettings with workspace-only filtering to prevent global config override, apply_to integration, and tests validating YAML deserialization (true, false, "deep" accepted; invalid values rejected).
Workspace state serialization
pacquet/crates/package-manager/src/install.rs
JSON helper converts tri-state to false/true/"deep" wire format; serialized into .pnpm-workspace-state-v1.json via build_workspace_state.
Resolver option wiring
pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs, pacquet/crates/package-manager/src/install_package_from_registry/tests.rs
Maps Config::link_workspace_packages to ResolveOptions::always_try_workspace_packages during fresh-lockfile install; test setup initializes the new config field.
Workspace resolution helper functions
pacquet/crates/resolving-npm-resolver/src/resolve_from_workspace.rs
Exports try_resolve_from_workspace_packages, pick_matching_local_version_or_null, and resolve_from_local_package to pub(crate) visibility for reuse by the resolver.
Npm resolver fallback and shadowing
pacquet/crates/resolving-npm-resolver/src/npm_resolver.rs
Routes registry 404/error to workspace fallback when active; on registry success, attempts workspace shadowing (exact match, higher semver, or prefer-workspace); adds helpers (try_workspace_fallback, try_workspace_shadow, workspace_fallback_options).
Resolver test coverage
pacquet/crates/resolving-npm-resolver/src/npm_resolver/tests.rs
Helpers to build packument JSON and WorkspacePackages fixtures; multiple tokio tests validate fallback on 404, exact-version shadowing, workspace bypass, registry-wins semantics, prefer-workspace behavior, and semver-driven higher-version shadowing.

Sequence Diagram

sequenceDiagram
  participant WorkspaceYAML as pnpm-workspace.yaml
  participant Install as install_with_fresh_lockfile
  participant Resolver as NpmResolver::resolve_impl
  participant Registry as Registry
  participant Workspace as workspace_packages

  WorkspaceYAML->>Install: linkWorkspacePackages: true
  Install->>Resolver: ResolveOptions { always_try_workspace_packages: true }
  
  Resolver->>Registry: pick_package(spec)
  alt Registry returns Ok(Some(version))
    Registry-->>Resolver: Some(version)
    Resolver->>Workspace: try_workspace_shadow (exact match / higher version / prefer flag)
    Workspace-->>Resolver: workspace_version or None
    Resolver-->>Install: workspace_version or registry_version
  else Registry returns Ok(None) or Err
    Registry-->>Resolver: Ok(None) / Err
    Resolver->>Workspace: try_workspace_fallback
    Workspace-->>Resolver: workspace_version or Err
    Resolver-->>Install: workspace_version or Err
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • pnpm/pnpm#11665: Related workspace-state serialization changes touching .pnpm-workspace-state-v1.json.
  • pnpm/pnpm#11905: Changes fresh-lockfile install/resolver flow related to per-importer workspace resolution.
  • pnpm/pnpm#11789: Modifies resolver workspace resolution paths overlapping with this PR's resolver changes.

Suggested reviewers

  • KSXGitHub

Possibly related issues

🐰 I nibble at config, three states in a row,
Off, DirectOnly, Deep — which way shall we go?
I shadow and fallback, match names with delight,
From workspace or registry, I choose what is right.
Hop, link, and resolve — no wrong installs tonight.

🚥 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 PR title accurately describes the main change: adding support for the linkWorkspacePackages configuration for bare-semver dependencies in the npm resolver.
Linked Issues check ✅ Passed The PR fully addresses all coding objectives from issue #11929: implements the tri-state LinkWorkspacePackages enum, plumbs configuration through Config to ResolveOptions, implements workspace fallback and shadowing logic, promotes helper functions, and ports comprehensive upstream tests.
Out of Scope Changes check ✅ Passed All changes are directly aligned with the scope of issue #11929. No unrelated modifications to other features, test utilities, or infrastructure were introduced.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ 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 fix/11929

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.

@github-actions

github-actions Bot commented May 25, 2026

Copy link
Copy Markdown
Contributor

Micro-Benchmark Results

Linux

group                          main                                   pr
-----                          ----                                   --
tarball/download_dependency    1.00      6.4±0.06ms   676.4 KB/sec    1.03      6.6±0.40ms   655.7 KB/sec

zkochan added 2 commits May 25, 2026 16:00
…kages tests from pnpm

Cover the six pnpm tests the initial port skipped:

* `injected_workspace_match_emits_file_resolution` — workspace
  shadow branch with `injected: true` emits a `file:` resolution.
* `workspace_fallback_picks_highest_version_for_latest_tag` — 404
  fallback into a multi-version workspace via the Tag branch of
  `pick_matching_local_version_or_null`.
* `workspace_fallback_picks_local_prerelease_for_latest_tag` — 404
  fallback into a prerelease-only workspace, exercising
  `resolve_workspace_range`'s `includePrerelease` arm.
* `workspace_fallback_resolves_specific_version_request` — 404
  fallback against a pinned version-spec lookup.
* `workspace_fallback_kicks_in_when_registry_lacks_requested_version`
  — `Ok(None)` fallback path (registry serves the packument but no
  version matches), distinct from the `Err` 404 path.
* `registry_error_propagates_when_workspace_has_no_matching_version`
  — negative test verifying the original 404 surfaces when the
  workspace can't satisfy the request.
* `registry_pick_wins_when_workspace_version_does_not_match` —
  workspace shadow no-op when the workspace carries a different
  version than the registry pick.

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

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

718-722: ⚡ Quick win

Trim verbose test doc comments that duplicate the test body.

Most /// blocks here restate scenario/setup/assertion details already encoded by the tests. Prefer a short purpose note (or a single module-level note) and keep non-obvious rationale only.

As per coding guidelines, "Tests are documentation. Do not duplicate them in prose... If a behavioral scenario... is already captured by a test, do not also narrate it in the doc comment."

Also applies to: 739-743, 778-782, 809-813, 834-838, 865-869, 901-905, 927-930

🤖 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-npm-resolver/src/npm_resolver/tests.rs` around lines
718 - 722, These test doc comments duplicate the test body; replace each verbose
`///` block (e.g., the workspace-map comment above the
`build_workspace_packages` helper and the similar docblocks at the other listed
ranges) with a short purpose note or remove them entirely so the test itself
serves as the documentation; keep only non-obvious rationale or intent (one
brief sentence) and avoid restating setup/assertions already encoded by the
tests, updating the docblocks near `build_workspace_packages` and the other
nearby test blocks accordingly.
🤖 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-npm-resolver/src/npm_resolver/tests.rs`:
- Around line 718-722: These test doc comments duplicate the test body; replace
each verbose `///` block (e.g., the workspace-map comment above the
`build_workspace_packages` helper and the similar docblocks at the other listed
ranges) with a short purpose note or remove them entirely so the test itself
serves as the documentation; keep only non-obvious rationale or intent (one
brief sentence) and avoid restating setup/assertions already encoded by the
tests, updating the docblocks near `build_workspace_packages` and the other
nearby test blocks accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f9c9b772-4405-437c-ae09-88b86b6c3931

📥 Commits

Reviewing files that changed from the base of the PR and between 5c3c32b and dad6209.

📒 Files selected for processing (1)
  • pacquet/crates/resolving-npm-resolver/src/npm_resolver/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). (7)
  • GitHub Check: Lint and Test (macos-latest)
  • GitHub Check: Lint and Test (windows-latest)
  • GitHub Check: Lint and Test (ubuntu-latest)
  • GitHub Check: Code Coverage
  • GitHub Check: Run benchmark on ubuntu-latest
  • 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-npm-resolver/src/npm_resolver/tests.rs
🧠 Learnings (3)
📚 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-npm-resolver/src/npm_resolver/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-npm-resolver/src/npm_resolver/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-npm-resolver/src/npm_resolver/tests.rs
🧬 Code graph analysis (1)
pacquet/crates/resolving-npm-resolver/src/npm_resolver/tests.rs (1)
pacquet/crates/resolving-resolver-base/src/resolve.rs (5)
  • WorkspacePackages (175-175)
  • WorkspacePackagesByVersion (171-171)
  • WorkspacePackage (164-167)
  • WantedDependency (82-102)
  • as_str (38-40)

@codecov-commenter

codecov-commenter commented May 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.44118% with 13 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.77%. Comparing base (97391bf) to head (d2d2e16).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
pacquet/crates/config/src/lib.rs 66.66% 9 Missing ⚠️
pacquet/crates/package-manager/src/install.rs 77.77% 2 Missing ⚠️
.../crates/resolving-npm-resolver/src/npm_resolver.rs 97.67% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #11930      +/-   ##
==========================================
+ Coverage   87.67%   87.77%   +0.10%     
==========================================
  Files         220      224       +4     
  Lines       26800    27295     +495     
==========================================
+ Hits        23497    23959     +462     
- Misses       3303     3336      +33     

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

@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.916 ± 0.446 2.250 3.702 1.01 ± 0.23
pacquet@main 2.881 ± 0.501 2.280 3.732 1.00
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 2.9160571812600002,
      "stddev": 0.44613895289447253,
      "median": 2.8353298237600004,
      "user": 2.16437372,
      "system": 2.5120189999999996,
      "min": 2.24956521926,
      "max": 3.70236078926,
      "times": [
        2.7807484542600003,
        2.6877338672600004,
        2.51888748026,
        2.88991119326,
        3.18180026826,
        3.07391659626,
        3.47072934926,
        3.70236078926,
        2.24956521926,
        2.60491859526
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 2.88054608886,
      "stddev": 0.5006534277445681,
      "median": 2.79352854376,
      "user": 2.1616188199999997,
      "system": 2.5006565000000003,
      "min": 2.28002756426,
      "max": 3.73192662926,
      "times": [
        2.40823908726,
        2.96696604226,
        2.34964860126,
        2.28002756426,
        2.66698209426,
        3.73192662926,
        2.7976661642600003,
        3.2619158612600003,
        2.78939092326,
        3.55269792126
      ]
    }
  ]
}

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

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 913.6 ± 56.3 850.6 1018.2 1.00
pacquet@main 1371.3 ± 309.9 951.9 1964.8 1.50 ± 0.35
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 0.91363729154,
      "stddev": 0.05632467726844137,
      "median": 0.92428626434,
      "user": 0.28914193999999993,
      "system": 1.04850082,
      "min": 0.8505567658400001,
      "max": 1.0182092278400001,
      "times": [
        0.9487154978400001,
        0.93263610884,
        0.8638016148400001,
        0.91741573584,
        0.8505567658400001,
        1.0182092278400001,
        0.9630149278400001,
        0.85169552384,
        0.9311567928400001,
        0.85917071984
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 1.37131064634,
      "stddev": 0.30992541055631967,
      "median": 1.23023543534,
      "user": 0.28758523999999996,
      "system": 1.0543704200000001,
      "min": 0.95190318084,
      "max": 1.96478052884,
      "times": [
        1.16532768484,
        1.69374206184,
        0.95190318084,
        1.14368465784,
        1.2480993148400001,
        1.2123715558400001,
        1.21227804384,
        1.96478052884,
        1.58145199184,
        1.53946744284
      ]
    }
  ]
}

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

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 2.949 ± 0.367 2.647 3.907 1.00
pacquet@main 2.969 ± 0.142 2.792 3.291 1.01 ± 0.13
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 2.94926280236,
      "stddev": 0.3666784807487576,
      "median": 2.87895286556,
      "user": 3.5083229400000002,
      "system": 2.5568506599999994,
      "min": 2.64735486206,
      "max": 3.90701147306,
      "times": [
        2.96589209606,
        2.79855130806,
        2.74466443406,
        3.07680207706,
        2.95935442306,
        2.97658897906,
        3.90701147306,
        2.66659449606,
        2.74981387506,
        2.64735486206
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 2.9692967575599996,
      "stddev": 0.14199256560510357,
      "median": 2.93779582256,
      "user": 3.5365151399999997,
      "system": 2.5878009599999996,
      "min": 2.79182139706,
      "max": 3.29069119406,
      "times": [
        3.06188372706,
        2.8714433280600002,
        2.8940253770599997,
        3.29069119406,
        2.8599847660599997,
        2.92324304506,
        3.0595487870599998,
        2.98797735406,
        2.79182139706,
        2.95234860006
      ]
    }
  ]
}

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

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 1.802 ± 0.149 1.697 2.101 1.00
pacquet@main 2.064 ± 0.575 1.705 3.578 1.15 ± 0.33
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 1.8019833166800001,
      "stddev": 0.14910467241306774,
      "median": 1.7408192903800002,
      "user": 1.6340099799999996,
      "system": 1.60122296,
      "min": 1.69678704988,
      "max": 2.1006880948799997,
      "times": [
        2.1006880948799997,
        1.7367082608800002,
        2.06374602888,
        1.7567918878800002,
        1.7562765078800002,
        1.7202528668800001,
        1.7146907668800002,
        1.7449303198800001,
        1.7289613828800001,
        1.69678704988
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 2.0643945593799997,
      "stddev": 0.5746877496040788,
      "median": 1.83152034588,
      "user": 1.6495202799999997,
      "system": 1.60357976,
      "min": 1.7051538028800002,
      "max": 3.5782038528799998,
      "times": [
        1.7051538028800002,
        2.00440652688,
        3.5782038528799998,
        2.4248780218799997,
        2.02999928788,
        1.79183171988,
        1.7329243148800002,
        1.71350737488,
        1.8564805428800002,
        1.80656014888
      ]
    }
  ]
}

@github-actions

Copy link
Copy Markdown
Contributor

🐰 Bencher Report

Branchpr/11930
Testbedpacquet

🚨 2 Alerts

BenchmarkMeasure
Units
ViewBenchmark Result
(Result Δ%)
Upper Boundary
(Limit %)
isolated-linker.fresh-restore.cold-cache.cold-storeLatency
seconds (s)
📈 plot
🚷 threshold
🚨 alert (🔔)
2.92 s
(+30.22%)Baseline: 2.24 s
2.69 s
(108.52%)

isolated-linker.fresh-restore.hot-cache.hot-storeLatency
milliseconds (ms)
📈 plot
🚷 threshold
🚨 alert (🔔)
913.64 ms
(+39.38%)Baseline: 655.52 ms
786.62 ms
(116.15%)

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,949.26 ms
(-26.23%)Baseline: 3,997.92 ms
4,797.50 ms
(61.47%)
isolated-linker.fresh-install.hot-cache.hot-store📈 view plot
🚷 view threshold
1,801.98 ms
(-40.43%)Baseline: 3,024.96 ms
3,629.95 ms
(49.64%)
isolated-linker.fresh-restore.cold-cache.cold-store📈 view plot
🚷 view threshold
🚨 view alert (🔔)
2,916.06 ms
(+30.22%)Baseline: 2,239.36 ms
2,687.23 ms
(108.52%)

isolated-linker.fresh-restore.hot-cache.hot-store📈 view plot
🚷 view threshold
🚨 view alert (🔔)
913.64 ms
(+39.38%)Baseline: 655.52 ms
786.62 ms
(116.15%)

🐰 View full continuous benchmarking report in Bencher

…kWorkspacePackages tests

Per pacquet/CLAUDE.md "tests are documentation" — the new test
docblocks restated what the test name plus body already say. Keep
only the upstream-link citation (required by the porting rule) and
drop the trailing narrative. Two ports keep one extra sentence
because the distinction they exercise (the `Ok(None)` vs `Err`
fallback split, the `includePrerelease` arm) is not recoverable
from the test body alone. Also drop the inline `lockfile_dir` /
"Registry returns a packument..." comments that narrate setup the
helper code already encodes; keep the `latest`-back-stamp comment
because it explains why a workspace-resolved result still carries
that field.
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.

pacquet: linkWorkspacePackages ignored for bare-semver deps (workspace packages bypassed; can wrong-install or 404)

2 participants