Skip to content

fix(pacquet/package-manager): build workspace state from project list, not lockfile#11946

Merged
zkochan merged 2 commits into
mainfrom
fix/workspace-state-records-all-importers
May 26, 2026
Merged

fix(pacquet/package-manager): build workspace state from project list, not lockfile#11946
zkochan merged 2 commits into
mainfrom
fix/workspace-state-records-all-importers

Conversation

@zkochan

@zkochan zkochan commented May 25, 2026

Copy link
Copy Markdown
Member

Summary

build_projects_map derived workspace projects from lockfile.importers.keys(). On the fresh-install path the wanted lockfile is None (no pnpm-lock.yaml on disk yet), so the function fell into its no-lockfile arm and recorded only the root importer — even for an 87-project workspace like babylon.

That broke the optimistic_repeat_install fast path on the next install: project_structure_matches compared the recorded project list (len = 1) against the workspace projects the resolver discovered (len = 87), returned false, and the install fell through to the full resolve + verify path even though the on-disk state was already a no-op.

Fix

Upstream pnpm's createWorkspaceState takes allProjects: ProjectsList as input and emits one entry per project — the lockfile isn't involved. Pacquet already builds the matching project_manifests: &[(PathBuf, &PackageManifest)] at the top of Install::run (for the optimistic-repeat fast path); thread it through to build_workspace_state instead of re-deriving from the lockfile.

The new build_projects_map is half the size of the old one and can't fall into a "lockfile missing" arm — the project list always comes from the same scan the rest of the install uses.

Impact

Re-bench against the vlt babylon fixture (87-project monorepo) shows every *+node_modules cell collapsing from "very slow" to "faster than pnpm":

Variation Before After
node_modules 37.87× 0.09× (11× faster than pnpm)
lockfile+node_modules 25.52× 0.07× (14× faster)
cache+node_modules 11.55× 0.15× (6.7× faster)
cache+lockfile+node_modules 11.35× 0.17× (5.9× faster)

These were the four worst cells in the vlt benchmark chart for babylon (all flagged DNF before #11944 fixed the underlying panic). After #11944 babylon stopped DNF'ing but stayed 11-37× slower because of this workspace-state writing bug.

The fast path only failed for workspace installs whose wanted lockfile was absent on the first install of the iteration — i.e. exactly the vlt benchmark's node_modules and *+node_modules shape. Single-project installs always recorded the root correctly because the no-lockfile fallback already emitted the root entry.

Found while validating #11944's claim that the babylon DNF cells would collapse on the next pacquet release (#11902).

Tests

Ports the two scenarios from upstream's createWorkspaceState.test.ts into install::tests::build_workspace_state_tests:

  • empty_project_list_produces_empty_projects_map — zero projects in, empty projects map out, timestamp still populated. Mirrors pnpm's createWorkspaceState() on empty list.
  • records_every_workspace_project_keyed_by_root_dir — four projects in, four entries in projects, each keyed by root_dir. Mirrors pnpm's createWorkspaceState() on non-empty list. Verified to fail on the pre-fix shape (returned 1 entry instead of 4) when build_projects_map is temporarily restricted to the root.

Test plan

  • New unit tests pass.
  • Confirmed the regression test fails on the pre-fix shape (verified locally by temporarily restricting build_projects_map to .take(1)).
  • cargo nextest run -p pacquet-package-manager — 335/335 pass.
  • cargo clippy --locked --workspace --all-targets -- --deny warnings — clean.
  • cargo fmt --check — clean.
  • End-to-end: babylon's node_modules/.pnpm-workspace-state-v1.json after a fresh install now contains 87 projects (was 1); 2nd install fires the no-op fast path in 0.097s.
  • Re-ran bench run --pms=pnpm,pacquet --fixtures=babylon --variation={node_modules,cache+node_modules,lockfile+node_modules,cache+lockfile+node_modules} — every cell now under 1.0× and per-iteration times stable at 0.06–0.11s (was 0.09–29s).

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

Summary by CodeRabbit

  • Bug Fixes
    • Install now reliably records workspace projects from provided manifests so project entries (name/version) remain consistent after install, including fresh installs without a prior lockfile or prior state.
  • Tests
    • Added unit tests that validate workspace state and project recording behavior for empty and multi-project workspaces, ensuring correct project entries and timestamps.

Review Change Stack

@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: 54fc6132-2102-4e2d-a610-f0af25e7d44d

📥 Commits

Reviewing files that changed from the base of the PR and between 106d60b and e86b98a.

📒 Files selected for processing (1)
  • pacquet/crates/package-manager/src/install.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). (9)
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Code Coverage
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Analyze (javascript)
  • GitHub Check: Lint and Test (macos-latest)
  • GitHub Check: Dylint
  • GitHub Check: Lint and Test (ubuntu-latest)
  • GitHub Check: Lint and Test (windows-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/package-manager/src/install.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/package-manager/src/install.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/package-manager/src/install.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/package-manager/src/install.rs
🔇 Additional comments (1)
pacquet/crates/package-manager/src/install.rs (1)

1303-1305: LGTM!

Also applies to: 1309-1312


📝 Walkthrough

Walkthrough

build_workspace_state and build_projects_map were changed to accept the precomputed project_manifests slice and populate WorkspaceState.projects directly from those in-memory manifests; call sites were updated accordingly and unit tests were added to validate empty and non-empty manifest lists.

Changes

Workspace projects from in-memory manifests

Layer / File(s) Summary
Update build_workspace_state call sites
pacquet/crates/package-manager/src/install.rs
Call sites now pass &project_manifests into build_workspace_state in both the frozen fast-path and at the end of the install flow.
Reimplement build_projects_map
pacquet/crates/package-manager/src/install.rs
build_projects_map now accepts &[(PathBuf, &PackageManifest)] and constructs the BTreeMap<String, ProjectEntry> directly from provided manifests' name/version and directories.
Update build_workspace_state to use project_manifests
pacquet/crates/package-manager/src/install.rs
build_workspace_state signature updated to take project_manifests and to call the new build_projects_map, removing prior lockfile/importer-based project assembly.
Unit tests for workspace state builders
pacquet/crates/package-manager/src/install/tests.rs
New tests create minimal package manifests, verify empty manifest lists produce empty projects (but set timestamp), and verify non-empty lists produce one projects entry per project root with correct name/version.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Possibly related PRs

  • pnpm/pnpm#11905: Related refactor using in-memory workspace manifests for per-importer workspace handling.
  • pnpm/pnpm#11665: Related workspace-state writer and build_workspace_state flow changes.
  • pnpm/pnpm#11943: Adjusts how recorded workspace projects are computed/validated; closely related to this change.

Poem

"I hopped through manifests, snug and spry,
Collected names and versions by and by,
No lockfile guessing, no disk-time roam,
Projects recorded straight from home,
A cheerful jar of carrots — install feels right!" 🐇

🚥 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 accurately reflects the main change: refactoring workspace state building to use pre-scanned project manifests instead of deriving projects from lockfile importers, fixing a bug in multisite workspaces.
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 fix/workspace-state-records-all-importers

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.04      8.0±0.46ms   544.1 KB/sec    1.00      7.7±0.08ms   564.9 KB/sec

@codecov-commenter

codecov-commenter commented May 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 87.94%. Comparing base (198c661) to head (e86b98a).

Additional details and impacted files
@@           Coverage Diff           @@
##             main   #11946   +/-   ##
=======================================
  Coverage   87.93%   87.94%           
=======================================
  Files         227      227           
  Lines       27741    27720   -21     
=======================================
- Hits        24393    24377   -16     
+ Misses       3348     3343    -5     

☔ 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

github-actions Bot commented May 25, 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.961 ± 0.106 1.821 2.223 1.00
pacquet@main 1.965 ± 0.070 1.860 2.090 1.00 ± 0.07
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 1.9606140617199999,
      "stddev": 0.10626806904123207,
      "median": 1.94871090212,
      "user": 2.8041329999999998,
      "system": 3.30512146,
      "min": 1.82115577712,
      "max": 2.22291815012,
      "times": [
        1.97418629712,
        1.99133876312,
        1.96418940112,
        1.92693841212,
        1.87943541812,
        1.98738327712,
        1.82115577712,
        2.22291815012,
        1.9332324031199999,
        1.9053627181200001
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 1.9648815245200002,
      "stddev": 0.07014412069486586,
      "median": 1.9563398266199998,
      "user": 2.7727329,
      "system": 3.2661323599999994,
      "min": 1.86028563512,
      "max": 2.09002599712,
      "times": [
        2.09002599712,
        2.02357251812,
        1.91019426312,
        1.86028563512,
        2.04595580112,
        1.97035438612,
        1.92396805612,
        1.95741266812,
        1.95526698512,
        1.91177893512
      ]
    }
  ]
}

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

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 647.1 ± 21.2 631.5 705.7 1.00 ± 0.04
pacquet@main 645.6 ± 19.2 628.5 695.3 1.00
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 0.64707307392,
      "stddev": 0.02119321637655272,
      "median": 0.64039558242,
      "user": 0.35773252,
      "system": 1.3509480600000001,
      "min": 0.63146364992,
      "max": 0.7057388469200001,
      "times": [
        0.7057388469200001,
        0.64094749892,
        0.6398436659200001,
        0.6414283209200001,
        0.6512561079200001,
        0.6396236609200001,
        0.63146364992,
        0.64356946792,
        0.6390272389200001,
        0.6378322809200001
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 0.6456031747200001,
      "stddev": 0.019184321406219132,
      "median": 0.6404206054200001,
      "user": 0.36291892000000003,
      "system": 1.35775496,
      "min": 0.6285298489200001,
      "max": 0.6953312339200001,
      "times": [
        0.65462746392,
        0.6299800259200001,
        0.64161015992,
        0.6392310509200001,
        0.6495622519200001,
        0.6953312339200001,
        0.64262269392,
        0.63617174192,
        0.6285298489200001,
        0.63836527592
      ]
    }
  ]
}

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

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 2.228 ± 0.039 2.174 2.293 1.00
pacquet@main 2.244 ± 0.035 2.186 2.300 1.01 ± 0.02
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 2.2279022816,
      "stddev": 0.038722953714916276,
      "median": 2.2338647783,
      "user": 3.8411947199999994,
      "system": 3.0281572399999996,
      "min": 2.1744865633000003,
      "max": 2.2929564153000004,
      "times": [
        2.2531309553,
        2.1964312473,
        2.2461420563,
        2.2371208673000003,
        2.2929564153000004,
        2.1836467413,
        2.1978863983,
        2.2666128823000005,
        2.1744865633000003,
        2.2306086893000003
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 2.2440500532,
      "stddev": 0.0348898225392244,
      "median": 2.2497482598000005,
      "user": 3.8301325199999994,
      "system": 3.0283048399999997,
      "min": 2.1857863263,
      "max": 2.2997240293,
      "times": [
        2.2997240293,
        2.2642369753000002,
        2.2559675803,
        2.1857863263,
        2.2656580993000004,
        2.2430812673,
        2.2435289393000004,
        2.2030550033000003,
        2.2691070563,
        2.2103552553
      ]
    }
  ]
}

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

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 1.455 ± 0.027 1.397 1.490 1.00
pacquet@main 1.462 ± 0.045 1.432 1.573 1.01 ± 0.04
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 1.4548371797400004,
      "stddev": 0.027273813886860874,
      "median": 1.4609234613400002,
      "user": 1.7138468,
      "system": 1.84969636,
      "min": 1.3965592203400001,
      "max": 1.4900935883400002,
      "times": [
        1.4680479933400001,
        1.42166281934,
        1.46470954134,
        1.4900935883400002,
        1.45333152134,
        1.4793701373400001,
        1.4605289053400001,
        1.3965592203400001,
        1.4527500533400002,
        1.46131801734
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 1.4623849039400003,
      "stddev": 0.045439116165343206,
      "median": 1.4416310513400001,
      "user": 1.7225962,
      "system": 1.8526376600000003,
      "min": 1.4323517993400001,
      "max": 1.57292742434,
      "times": [
        1.4393480923400002,
        1.45264341234,
        1.43543805434,
        1.4396294013400002,
        1.57292742434,
        1.4323517993400001,
        1.51305368834,
        1.43683126434,
        1.44363270134,
        1.45799320134
      ]
    }
  ]
}

@github-actions

github-actions Bot commented May 25, 2026

Copy link
Copy Markdown
Contributor

🐰 Bencher Report

Branchpr/11946
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,227.90 ms
(-34.85%)Baseline: 3,419.57 ms
4,103.48 ms
(54.29%)
isolated-linker.fresh-install.hot-cache.hot-store📈 view plot
🚷 view threshold
1,454.84 ms
(-41.50%)Baseline: 2,487.01 ms
2,984.41 ms
(48.75%)
isolated-linker.fresh-restore.cold-cache.cold-store📈 view plot
🚷 view threshold
1,960.61 ms
(-9.10%)Baseline: 2,156.96 ms
2,588.35 ms
(75.75%)
isolated-linker.fresh-restore.hot-cache.hot-store📈 view plot
🚷 view threshold
647.07 ms
(-2.32%)Baseline: 662.46 ms
794.95 ms
(81.40%)
🐰 View full continuous benchmarking report in Bencher

@zkochan zkochan force-pushed the fix/workspace-state-records-all-importers branch from a98b116 to 544ff4f Compare May 25, 2026 23:46
…, not lockfile

`build_projects_map` derived workspace projects from
`lockfile.importers.keys()`. On the fresh-install path the wanted
lockfile is `None` (no `pnpm-lock.yaml` on disk yet), so the function
fell into its no-lockfile arm and recorded only the root importer —
even for an 87-project workspace like babylon.

That broke the [`optimistic_repeat_install`](https://github.com/pnpm/pnpm/blob/6b3ba4d337/pacquet/crates/package-manager/src/optimistic_repeat_install.rs)
fast path on the *next* install: `project_structure_matches` compared
the recorded project list (`len = 1`) against the workspace projects
the resolver discovered (`len = 87`), returned `false`, and the
install fell through to the full resolve + verify path even though
the on-disk state was already a no-op.

## Fix

Upstream pnpm's
[`createWorkspaceState`](https://github.com/pnpm/pnpm/blob/cc4ff817aa/workspace/state/src/createWorkspaceState.ts#L14-L27)
takes `allProjects: ProjectsList` as input and emits one entry per
project — the lockfile isn't involved. Pacquet already builds the
matching `project_manifests: &[(PathBuf, &PackageManifest)]` at the
top of `Install::run` (for the optimistic-repeat fast path); thread
it through to `build_workspace_state` instead of re-deriving from
the lockfile.

The new `build_projects_map` is half the size of the old one and
can't fall into a "lockfile missing" arm — the project list always
comes from the same scan the rest of the install uses.

## Impact

Re-bench against the vlt babylon fixture (87-project monorepo) shows
every \`*+node_modules\` cell collapsing from "very slow" to "faster
than pnpm":

| Variation | Before | After |
|---|---:|---:|
| \`node_modules\` | 37.87x | 0.09x (11x faster than pnpm) |
| \`lockfile+node_modules\` | 25.52x | 0.07x (14x faster) |
| \`cache+node_modules\` | 11.55x | 0.15x (6.7x faster) |
| \`cache+lockfile+node_modules\` | 11.35x | 0.17x (5.9x faster) |

The fast path only failed for workspace installs whose wanted
lockfile was absent on the first install of the iteration — i.e.
exactly the vlt benchmark's \`node_modules\` and \`*+node_modules\`
shape.

Found while validating #11944's claim that the babylon DNF cells
would collapse on the next pacquet release (#11902).

## Tests

Ports the two scenarios from upstream's
[\`createWorkspaceState.test.ts\`](https://github.com/pnpm/pnpm/blob/cc4ff817aa/workspace/state/test/createWorkspaceState.test.ts)
into \`install::tests::build_workspace_state_tests\`:

- \`empty_project_list_produces_empty_projects_map\` — zero projects
  in, empty \`projects\` map out, timestamp still populated.
- \`records_every_workspace_project_keyed_by_root_dir\` — four
  projects in, four entries in \`projects\`, each keyed by
  \`root_dir\`. Verified to fail on the pre-fix shape (returned 1
  entry instead of 4) when \`build_projects_map\` is temporarily
  restricted to the root.
@zkochan zkochan force-pushed the fix/workspace-state-records-all-importers branch from 544ff4f to 106d60b Compare May 25, 2026 23:57
@zkochan zkochan changed the title fix(pacquet/package-manager): pass fresh lockfile to workspace-state writer fix(pacquet/package-manager): build workspace state from project list, not lockfile May 25, 2026

@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/package-manager/src/install.rs (1)

1303-1314: ⚡ Quick win

Update build_projects_map docs to match the new in-memory contract.

This doc block still describes lockfile/importer traversal and read-failure warning behavior, but the function now builds directly from project_manifests. Please align the contract text with the current implementation.

As per coding guidelines: “Doc comments (///, //!) document the contract … They are not a re-narration of the body.”

🤖 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/package-manager/src/install.rs` around lines 1303 - 1314,
Update the doc comment for build_projects_map to reflect the new in-memory
contract: state that the function builds the WorkspaceState projects map
directly from the provided project_manifests (no lockfile/importer traversal or
disk reads), that it does not perform any file I/O or emit read-failure
warnings, and mention how the root importer reuses the in-memory manifest rather
than re-reading from disk; keep the comment focused on the function's contract
rather than re-describing implementation details.
🤖 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/package-manager/src/install.rs`:
- Around line 1303-1314: Update the doc comment for build_projects_map to
reflect the new in-memory contract: state that the function builds the
WorkspaceState projects map directly from the provided project_manifests (no
lockfile/importer traversal or disk reads), that it does not perform any file
I/O or emit read-failure warnings, and mention how the root importer reuses the
in-memory manifest rather than re-reading from disk; keep the comment focused on
the function's contract rather than re-describing implementation details.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1ab66dd7-d3cf-4894-9054-ba8927bda267

📥 Commits

Reviewing files that changed from the base of the PR and between 544ff4f and 106d60b.

📒 Files selected for processing (2)
  • pacquet/crates/package-manager/src/install.rs
  • pacquet/crates/package-manager/src/install/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: Run benchmark on ubuntu-latest
  • GitHub Check: Code Coverage
  • GitHub Check: Lint and Test (windows-latest)
  • GitHub Check: Lint and Test (ubuntu-latest)
  • GitHub Check: Lint and Test (macos-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/package-manager/src/install/tests.rs
  • pacquet/crates/package-manager/src/install.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/package-manager/src/install/tests.rs
  • pacquet/crates/package-manager/src/install.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/package-manager/src/install/tests.rs
  • pacquet/crates/package-manager/src/install.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/package-manager/src/install/tests.rs
  • pacquet/crates/package-manager/src/install.rs
🧬 Code graph analysis (2)
pacquet/crates/package-manager/src/install/tests.rs (2)
pacquet/crates/config/src/lib.rs (2)
  • Config (941-1397)
  • NodeLinker (44-57)
pacquet/crates/package-manifest/src/lib.rs (2)
  • PackageManifest (67-70)
  • PackageManifest (72-260)
pacquet/crates/package-manager/src/install.rs (2)
pacquet/crates/package-manifest/src/lib.rs (2)
  • path (143-145)
  • PackageManifest (67-70)
pacquet/crates/workspace-state/src/lib.rs (3)
  • ProjectEntry (42-47)
  • WorkspaceState (57-65)
  • now_millis (220-225)
🔇 Additional comments (2)
pacquet/crates/package-manager/src/install.rs (1)

671-672: LGTM!

Also applies to: 913-914, 1316-1327, 1343-1347

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

814-894: LGTM!

…er refactor

The doc comment still described the old lockfile-driven shape that
read each sub-importer's package.json from disk. Now that the
function takes pre-loaded `(PathBuf, &PackageManifest)` entries,
update the contract to reflect what actually happens: pure in-memory
mapping, no I/O, no read-failure warnings.
@zkochan zkochan merged commit a1f6f32 into main May 26, 2026
28 checks passed
@zkochan zkochan deleted the fix/workspace-state-records-all-importers branch May 26, 2026 00:24
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.

3 participants