fix(pacquet/store-dir): append STORE_VERSION to storeDir so pnpm and pacquet stay interchangeable#11891
Conversation
…stay interchangeable pnpm's `getStorePath` appends `STORE_VERSION` (`"v11"`) to whatever the user configured, so the `.modules.yaml` it writes records the v11-suffixed path. pacquet stored the suffix only as an internal sub-path accessor (`StoreDir::v11`), which meant `config.store_dir.display()` — the value pacquet writes to `.modules.yaml`, prints from `pacquet store path`, and emits in the NDJSON `context` log — yielded the un-suffixed parent. Switching between the two CLIs in the same project tripped pnpm's `checkCompatibility` with `ERR_PNPM_UNEXPECTED_STORE`. Fix is centralised in `From<PathBuf> for StoreDir`, mirroring pnpm's `if (endsWith(v11)) return; else append(v11)` branch at store/path/src/index.ts:39-42. Every consumer reading from `StoreDir` (`display()`, `root()`, `files()`, `tmp()`, `links()`, `projects()`) now sees the v11-suffixed path through one source of truth, so the on-disk layout is unchanged and the externally-reported `storeDir` matches pnpm's exactly. Ref: https://github.com/pnpm/pnpm/blob/29a42efc3b/store/path/src/index.ts#L39-L42
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
📝 WalkthroughWalkthroughThis PR refactors how the pnpm v11 store version suffix is incorporated into store paths. StoreDir.root now includes STORE_VERSION; From auto-appends it when missing. Path helpers build from root, v11() is removed, index.db moves to root, and tests/config are updated accordingly. ChangesStore version path layout refactor
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Micro-Benchmark ResultsLinux |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #11891 +/- ##
==========================================
- Coverage 87.84% 87.83% -0.02%
==========================================
Files 206 206
Lines 24509 24520 +11
==========================================
+ Hits 21531 21538 +7
- Misses 2978 2982 +4 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
pacquet/crates/store-dir/src/project_registry.rs (1)
457-468: ⚡ Quick winDocumentation inconsistency: comment mentions
StoreDir::frombut code usesStoreDir::new.The comment on line 458 states "after [
StoreDir::from] applies the suffix" but the actual code on line 468 callsStoreDir::new(&store_path). Either update the comment to referencenewinstead offrom, or clarify that both constructors apply the suffix logic.📝 Suggested fix for comment consistency
/// Subdir guard: when the store lives inside the project, the /// function is a silent no-op — registering would otherwise create /// a self-referential symlink. The `STORE_VERSION` subdir - /// (`store_dir.root()` after [`StoreDir::from`] applies the suffix) + /// (`store_dir.root()` after `StoreDir::new` applies the suffix) /// is materialised on disk so [`path_contains`]'s canonical-form /// comparison sees both sides as canonical paths even on macOS, /// where `/tmp` symlinks to `/private/tmp` and a missing target /// would silently fall back to lexical comparison and miss the /// containment.🤖 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/store-dir/src/project_registry.rs` around lines 457 - 468, The doc comment incorrectly references StoreDir::from while the test constructs a StoreDir via StoreDir::new; update the comment to reference StoreDir::new (or explicitly state that both StoreDir::from and StoreDir::new apply the suffix) so the documentation matches the usage in the test; locate the comment near the test function register_skips_when_store_is_inside_project and change the bracketed reference [`StoreDir::from`] to [`StoreDir::new`] or add a brief note mentioning both constructors.
🤖 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/store-dir/src/store_dir.rs`:
- Around line 29-37: The current #[serde(transparent)] on StoreDir allows Serde
to deserialize directly into StoreDir.root (PathBuf) and bypass the
normalization in impl From<PathBuf> for StoreDir, so persisted unsuffixed paths
can violate the STORE_VERSION suffix invariant; replace the derive-based
Deserialize for StoreDir with a custom impl Deserialize that deserializes a
PathBuf (or String) and constructs StoreDir via the existing From<PathBuf> (or a
normalize method) to ensure the suffix is applied, remove or keep
serde(transparent) only if compatible, and add a serde round-trip test that
deserializes an unsuffixed persisted path and asserts the resulting
StoreDir.root has the STORE_VERSION suffix (referencing StoreDir, root, and the
From<PathBuf> for locating code).
---
Nitpick comments:
In `@pacquet/crates/store-dir/src/project_registry.rs`:
- Around line 457-468: The doc comment incorrectly references StoreDir::from
while the test constructs a StoreDir via StoreDir::new; update the comment to
reference StoreDir::new (or explicitly state that both StoreDir::from and
StoreDir::new apply the suffix) so the documentation matches the usage in the
test; locate the comment near the test function
register_skips_when_store_is_inside_project and change the bracketed reference
[`StoreDir::from`] to [`StoreDir::new`] or add a brief note mentioning both
constructors.
🪄 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: 91a7dd80-167e-4aad-bd5b-c49cafc37e5f
📒 Files selected for processing (10)
pacquet/crates/cli/tests/store.rspacquet/crates/config/src/defaults/tests.rspacquet/crates/config/src/lib.rspacquet/crates/config/src/store_path.rspacquet/crates/package-manager/src/install/tests.rspacquet/crates/store-dir/src/project_registry.rspacquet/crates/store-dir/src/store_dir.rspacquet/crates/store-dir/src/store_dir/tests.rspacquet/crates/store-dir/src/store_index.rspacquet/crates/store-dir/src/store_index/tests.rs
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
- GitHub Check: Run benchmark on ubuntu-latest
- GitHub Check: Lint and Test (ubuntu-latest)
- GitHub Check: Lint and Test (macos-latest)
- GitHub Check: Lint and Test (windows-latest)
- GitHub Check: Dylint
- GitHub Check: Run benchmark on ubuntu-latest
- GitHub Check: Code Coverage
- GitHub Check: Compile & Lint
🧰 Additional context used
📓 Path-based instructions (2)
pacquet/**/*.rs
📄 CodeRabbit inference engine (pacquet/AGENTS.md)
pacquet/**/*.rs: When porting a function that firespnpm:<channel>events throughglobalLogger,logger.debug(), orstreamParser.write(), mirror the call site, payload, and ordering so the reporter parses pacquet's NDJSON the same way it parses pnpm's.
Declare a newtype wrapper for branded string types. Do not collapse the brand into a plainStringor&str.
If upstream always validates before construction, validate in pacquet's wrapper too. The wrapper must construct only viaTryFrom<String>and/orFromStr. Do not provide an infallible public constructor.
If upstream never validates, just brand for type-safety. Expose an infallibleFrom<String>(andFrom<&str>when convenient).
If upstream occasionally constructs without validation, exposefrom_str_uncheckedas an escape hatch alongside the validating constructor.
Match upstream serde behavior for branded types that cross JSON, YAML, or INI boundaries. Use#[serde(try_from = "String")]for deserialization and#[serde(into = "String")]for serialization.
Use#[derive(derive_more::From)]and#[derive(derive_more::Into)]for mechanical conversion impls. Fall back to manualimplonly when conversion needs custom logic.
String-literal unions should becomeenums, not newtype wrappers. Model closed sets of valid string values as enums.
Template literal types should be treated as branded strings with validation discipline from rules 2-5.
Choose owned vs. borrowed parameters to minimize copies. Widen to the most encompassing type (&Pathover&PathBuf,&strover&String) when it doesn't force extra copies.
PreferArc::clone(&x)/Rc::clone(&x)overx.clone()for reference-counted types, so the cost is visible at the call site.
Follow Rust API Guidelines for naming conventions.
Do not use star imports inside module bodies. Writeuse super::{Foo, bar}instead ofuse super::*;. Two forms stay allowed: external-crate preludes likeuse rayon::prelude::*;and root-of-module re-...
Files:
pacquet/crates/config/src/store_path.rspacquet/crates/cli/tests/store.rspacquet/crates/store-dir/src/store_dir/tests.rspacquet/crates/store-dir/src/project_registry.rspacquet/crates/store-dir/src/store_index/tests.rspacquet/crates/package-manager/src/install/tests.rspacquet/crates/config/src/defaults/tests.rspacquet/crates/config/src/lib.rspacquet/crates/store-dir/src/store_index.rspacquet/crates/store-dir/src/store_dir.rs
pacquet/**/{tests,test}/**/*.rs
📄 CodeRabbit inference engine (pacquet/AGENTS.md)
pacquet/**/{tests,test}/**/*.rs: Prefer real fixtures; reach for the dependency-injection seam only when they can't cover the branch. Most happy paths and error paths should be tested withtempfile::TempDir, the mocked registry, or an integration test that spawns the actual binary.
Use the DI seam — a capability trait on theHostprovider, threaded asSys: <Bounds>— only for branches a real fixture can't reach portably: filesystem error kinds, deterministic time, shared process-global state, or external-service happy paths.
Log before non-assert_eq!assertions,dbg!complex structures, skip logging for simple scalarassert_eq!. Follow the test-logging guidance in CODE_STYLE_GUIDE.md.
Tests must not be tolerant of missing build/runtime environment by silently returning early when a tool isn't found. If the test needs a tool, let it panic when the tool is absent.
Prefer#[cfg_attr(target_os = "windows", ignore = "...")](or#[cfg(unix)]) over a runtime probe-and-skip helper for platform-locked tools.
Port relevant tests from pnpm when porting behavior. Matching test coverage is the easiest way to prove behavioral parity.
When porting behavior from pnpm, consult plans/TEST_PORTING.md before adding ported tests. Useallow_known_failure!at the not-yet-implemented boundary and update checkboxes as items land.
Files:
pacquet/crates/cli/tests/store.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/config/src/store_path.rspacquet/crates/cli/tests/store.rspacquet/crates/store-dir/src/store_dir/tests.rspacquet/crates/store-dir/src/project_registry.rspacquet/crates/store-dir/src/store_index/tests.rspacquet/crates/package-manager/src/install/tests.rspacquet/crates/config/src/defaults/tests.rspacquet/crates/config/src/lib.rspacquet/crates/store-dir/src/store_index.rspacquet/crates/store-dir/src/store_dir.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/config/src/store_path.rspacquet/crates/cli/tests/store.rspacquet/crates/store-dir/src/store_dir/tests.rspacquet/crates/store-dir/src/project_registry.rspacquet/crates/store-dir/src/store_index/tests.rspacquet/crates/package-manager/src/install/tests.rspacquet/crates/config/src/defaults/tests.rspacquet/crates/config/src/lib.rspacquet/crates/store-dir/src/store_index.rspacquet/crates/store-dir/src/store_dir.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/config/src/store_path.rspacquet/crates/cli/tests/store.rspacquet/crates/store-dir/src/store_dir/tests.rspacquet/crates/store-dir/src/project_registry.rspacquet/crates/store-dir/src/store_index/tests.rspacquet/crates/package-manager/src/install/tests.rspacquet/crates/config/src/defaults/tests.rspacquet/crates/config/src/lib.rspacquet/crates/store-dir/src/store_index.rspacquet/crates/store-dir/src/store_dir.rs
🔇 Additional comments (8)
pacquet/crates/config/src/store_path.rs (1)
35-45: LGTM!pacquet/crates/config/src/lib.rs (1)
1225-1235: LGTM!Also applies to: 1430-1433, 1461-1464
pacquet/crates/config/src/defaults/tests.rs (1)
7-7: LGTM!Also applies to: 51-51, 80-83, 116-117
pacquet/crates/cli/tests/store.rs (1)
2-2: LGTM!Also applies to: 43-47
pacquet/crates/package-manager/src/install/tests.rs (1)
15-15: LGTM!Also applies to: 582-582, 679-679
pacquet/crates/store-dir/src/store_dir/tests.rs (1)
1-4: LGTM!Also applies to: 23-54
pacquet/crates/store-dir/src/store_index.rs (1)
123-127: LGTM!Also applies to: 404-447
pacquet/crates/store-dir/src/store_index/tests.rs (1)
138-138: LGTM!
…ng multi-line assert
…de(from)] CodeRabbit flagged that the previous `#[serde(transparent)]` derive on `StoreDir` deserialised straight into `StoreDir::root`, bypassing the auto-append in `impl From<PathBuf> for StoreDir`. A persisted unsuffixed path would therefore violate the [`STORE_VERSION`] invariant on the live struct until the next reconstruction. Pacquet doesn't currently deserialize `StoreDir` from any disk shape, but the type-level guarantee is part of the public contract — future serialised state must round-trip through the suffix logic. Route both directions through `PathBuf` with `#[serde(from = "PathBuf", into = "PathBuf")]`. Deserialize now flows through `From<PathBuf>` (which applies the suffix); serialize converts to `PathBuf` and back to the same wire shape `transparent` produced, so no on-disk format change. `Clone` is required by `into` and was added. Also fix CodeRabbit's doc-comment nit at project_registry::register_skips_when_store_is_inside_project — the comment referenced `StoreDir::from` while the test calls `StoreDir::new`; clarified that `new` routes through `From<PathBuf>`. Added round-trip tests in `store_dir::tests`: - `deserialize_applies_store_version_to_unsuffixed_path` - `deserialize_preserves_already_suffixed_path`
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
pacquet/crates/store-dir/src/store_dir.rs (1)
177-179:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winUpdate stale helper reference in
root()docs.The doc text still lists
v11as a helper, butStoreDir::v11()was removed. Please update this list to avoid a stale API contract in generated docs.Suggested patch
- /// purpose-built helpers (`v11`, `tmp`, `links`, `projects`); this + /// purpose-built helpers (`tmp`, `links`, `projects`); this🤖 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/store-dir/src/store_dir.rs` around lines 177 - 179, Update the doc comment for the root() method to remove the stale reference to StoreDir::v11() (which was removed); edit the sentence that lists helpers so it only mentions the existing helper methods (e.g., tmp, links, projects) and any other current helpers, ensuring the root() docs no longer reference v11.
🤖 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.
Outside diff comments:
In `@pacquet/crates/store-dir/src/store_dir.rs`:
- Around line 177-179: Update the doc comment for the root() method to remove
the stale reference to StoreDir::v11() (which was removed); edit the sentence
that lists helpers so it only mentions the existing helper methods (e.g., tmp,
links, projects) and any other current helpers, ensuring the root() docs no
longer reference v11.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 2933edf0-0fd3-44f8-8911-fbd7eb1f77c3
📒 Files selected for processing (3)
pacquet/crates/store-dir/src/project_registry.rspacquet/crates/store-dir/src/store_dir.rspacquet/crates/store-dir/src/store_dir/tests.rs
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
- GitHub Check: Run benchmark on ubuntu-latest
- GitHub Check: Lint and Test (windows-latest)
- GitHub Check: Lint and Test (ubuntu-latest)
- GitHub Check: Lint and Test (macos-latest)
- GitHub Check: Doc
- 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 firespnpm:<channel>events throughglobalLogger,logger.debug(), orstreamParser.write(), mirror the call site, payload, and ordering so the reporter parses pacquet's NDJSON the same way it parses pnpm's.
Declare a newtype wrapper for branded string types. Do not collapse the brand into a plainStringor&str.
If upstream always validates before construction, validate in pacquet's wrapper too. The wrapper must construct only viaTryFrom<String>and/orFromStr. Do not provide an infallible public constructor.
If upstream never validates, just brand for type-safety. Expose an infallibleFrom<String>(andFrom<&str>when convenient).
If upstream occasionally constructs without validation, exposefrom_str_uncheckedas an escape hatch alongside the validating constructor.
Match upstream serde behavior for branded types that cross JSON, YAML, or INI boundaries. Use#[serde(try_from = "String")]for deserialization and#[serde(into = "String")]for serialization.
Use#[derive(derive_more::From)]and#[derive(derive_more::Into)]for mechanical conversion impls. Fall back to manualimplonly when conversion needs custom logic.
String-literal unions should becomeenums, not newtype wrappers. Model closed sets of valid string values as enums.
Template literal types should be treated as branded strings with validation discipline from rules 2-5.
Choose owned vs. borrowed parameters to minimize copies. Widen to the most encompassing type (&Pathover&PathBuf,&strover&String) when it doesn't force extra copies.
PreferArc::clone(&x)/Rc::clone(&x)overx.clone()for reference-counted types, so the cost is visible at the call site.
Follow Rust API Guidelines for naming conventions.
Do not use star imports inside module bodies. Writeuse super::{Foo, bar}instead ofuse super::*;. Two forms stay allowed: external-crate preludes likeuse rayon::prelude::*;and root-of-module re-...
Files:
pacquet/crates/store-dir/src/project_registry.rspacquet/crates/store-dir/src/store_dir/tests.rspacquet/crates/store-dir/src/store_dir.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/store-dir/src/project_registry.rspacquet/crates/store-dir/src/store_dir/tests.rspacquet/crates/store-dir/src/store_dir.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/store-dir/src/project_registry.rspacquet/crates/store-dir/src/store_dir/tests.rspacquet/crates/store-dir/src/store_dir.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/store-dir/src/project_registry.rspacquet/crates/store-dir/src/store_dir/tests.rspacquet/crates/store-dir/src/store_dir.rs
🔇 Additional comments (3)
pacquet/crates/store-dir/src/store_dir.rs (1)
29-37: LGTM!Also applies to: 54-57, 61-65
pacquet/crates/store-dir/src/store_dir/tests.rs (1)
56-74: LGTM!pacquet/crates/store-dir/src/project_registry.rs (1)
458-460: LGTM!
…oin so the test passes on Windows `modules_yaml_serialized_store_dir_carries_store_version` (added in 3209c25) hardcoded `/tmp/.pnpm-store/v11` on the right-hand side while the left-hand side flows through `StoreDir::from`'s `PathBuf::join`. On Windows that join emits `\v11`, so the test panicked with `\v11` vs `/v11` — and has been failing in the post-merge `Pacquet CI` run for #11891 since it landed. Pnpm itself uses Node's `path.join` (via `getStorePath` → `path.join(storePath, STORE_VERSION)`), which is also backslash-joined on Windows. Building the expected value through `Path::join` here mirrors that path and keeps the test asserting the real parity contract on every platform.
…npm (#11896) * fix(pacquet/modules-yaml): write GVS-aware virtualStoreDir to match pnpm Under `enableGlobalVirtualStore: true`, upstream pnpm mutates `virtualStoreDir` in place at `extendInstallOptions.ts:419-422` so every consumer that reads `ctx.virtualStoreDir` — including `writeModulesManifest` and the `pnpm:context` debug log — sees the GVS-derived `<storeDir>/v11/links` path. Pacquet kept `Config::virtual_store_dir` at its project-local value (deliberately, see `apply_global_virtual_store_derivation`'s rationale) and wrote that field straight into `.modules.yaml` and the context log. With `pnpm install` delegating to pacquet via `configDependencies`, every run came back through pnpm's `checkCompatibility`, the recorded project-local `virtualStoreDir` didn't match the GVS-mutated value pnpm computed, and per-importer purges fired the "modules directories will be reinstalled from scratch" prompt on every install. Route both externally-visible consumers through a new `Config::effective_virtual_store_dir` helper that returns `global_virtual_store_dir` when GVS is on (which already encodes "user pinned or fall back to `<storeDir>/links`" via `apply_global_virtual_store_derivation`) and the project-local `virtual_store_dir` otherwise. Pacquet's internal layout consumers still read the field directly — the divergence the helper bridges is only at the parity boundary. Test pins both halves: `.modules.yaml` round-trips to `<storeDir>/v11/links` under GVS, and the `pnpm:context` event reports the same path. * fix(pacquet/store-dir): build modules_yaml expected path with Path::join so the test passes on Windows `modules_yaml_serialized_store_dir_carries_store_version` (added in 3209c25) hardcoded `/tmp/.pnpm-store/v11` on the right-hand side while the left-hand side flows through `StoreDir::from`'s `PathBuf::join`. On Windows that join emits `\v11`, so the test panicked with `\v11` vs `/v11` — and has been failing in the post-merge `Pacquet CI` run for #11891 since it landed. Pnpm itself uses Node's `path.join` (via `getStorePath` → `path.join(storePath, STORE_VERSION)`), which is also backslash-joined on Windows. Building the expected value through `Path::join` here mirrors that path and keeps the test asserting the real parity contract on every platform.
Summary
When you install with pacquet and then run pnpm in the same project (or vice versa), pnpm refuses to proceed with:
getStorePathin pnpm appendsSTORE_VERSION(\"v11\") to whatever the user configured, so the.modules.yamlit writes records the v11-suffixed path. pacquet stored the suffix only as an internal sub-path accessor (StoreDir::v11), which meantconfig.store_dir.display()— the value pacquet writes to.modules.yaml, prints frompacquet store path, and emits in the NDJSONcontextlog — yielded the un-suffixed parent. pnpm'scheckCompatibilitythen disagreed with the recorded value and threw.Fix
Centralised in
From<PathBuf> for StoreDir, mirroring pnpm'sif (endsWith(v11)) return; else append(v11)branch:StoreDir::rootis now theSTORE_VERSION-suffixed path itself, matching pnpm'sstoreDir.From<PathBuf>), so every consumer reading fromStoreDir(display(),root(),files(),tmp(),links(),projects()) gets the suffixed value through one source of truth.StoreDir::v11()helper is gone —files/links/tmp/projectsnow join directly offroot.pacquet_store_dir::STORE_VERSION(\"v11\") so tests and consumers reference the same constant pnpm exports as@pnpm/constants.STORE_VERSION.The fix is the same one already in place for the GVS path (
<root>/links) —links()previously composedself.v11().join(\"links\")so it landed at<storeDir>/linksmatching pnpm. The bug was thatdisplay()and the field underneath it bypassed that suffix. Collapsingrootinto the suffixed form unifies the two paths.On-disk store layout is unchanged.
From<PathBuf>doesn't double-append when the input already ends withv11, so explicit user-setstoreDirvalues that already include/v11round-trip cleanly.Test plan
typos pacquet(matches CI scope)cargo fmt --checkjust checkjust lintcargo dylint --all(catches Perfectionist lintsjust readydoesn't)cargo nextest run --no-fail-fast— all 2004 tests passpacquet_store_dir::store_dir::tests:from_pathbuf_auto_appends_store_version_when_missingfrom_pathbuf_does_not_double_append_when_already_suffixedmodules_yaml_serialized_store_dir_carries_store_versionWritten by an agent (Claude Code, claude-opus-4-7).
Summary by CodeRabbit