feat(pacquet): port injectWorkspacePackages setting#12021
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds pnpm's Changesinject_workspace_packages configuration support
Sequence Diagram(s)sequenceDiagram
participant CLI as CLI Test / InstallCommand
participant Installer as InstallWithFreshLockfile
participant Resolver as resolve_importer
participant GraphToLockfile as dependencies_graph_to_lockfile
participant Lockfile as Lockfile::settings
participant Freshness as check_lockfile_settings
CLI->>Installer: run install (config.inject_workspace_packages)
Installer->>Resolver: resolve_importer(base_opts.inject_workspace_packages = config.inject_workspace_packages)
Resolver->>Installer: resolved dependency graph
Installer->>GraphToLockfile: build lockfile (opts.inject_workspace_packages = config.inject_workspace_packages)
GraphToLockfile->>Lockfile: produce Lockfile.settings.inject_workspace_packages
Installer->>Freshness: check_lockfile_settings(..., inject_workspace_packages = config.inject_workspace_packages)
Freshness->>Lockfile: compare settings.inject_workspace_packages vs provided config value
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 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 |
Review Summary by QodoPort injectWorkspacePackages config setting to pacquet
WalkthroughsDescription• Add injectWorkspacePackages config setting to pacquet • Thread setting into resolver chain via ResolveOptions • Include setting in workspace-state freshness comparison • Add comprehensive tests for yaml parsing and cache invalidation Diagramflowchart LR
A["Config struct"] -->|"inject_workspace_packages field"| B["WorkspaceSettings"]
B -->|"parse from pnpm-workspace.yaml"| C["ResolveOptions"]
C -->|"feed to resolver"| D["Workspace resolution"]
D -->|"file: vs link:"| E["Virtual store materialization"]
A -->|"compare in freshness check"| F["Optimistic repeat install cache"]
F -->|"invalidate on drift"| G["Fresh resolution"]
File Changes1. pacquet/crates/config/src/lib.rs
|
Micro-Benchmark ResultsLinux |
|
Actionable comments posted: 0 |
Integrated-Benchmark Report (Linux)Scenario: Isolated linker: fresh restore, cold cache + cold store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 2.0659818962799994,
"stddev": 0.05684771449536354,
"median": 2.06064484098,
"user": 2.7378919999999995,
"system": 3.27332818,
"min": 1.9567459924800001,
"max": 2.13850396348,
"times": [
2.06286625448,
2.11583728948,
2.13850396348,
2.0332209894799997,
2.05034690348,
2.12717149048,
2.0584234274799997,
2.10397086848,
1.9567459924800001,
2.01273178348
]
},
{
"command": "pacquet@main",
"mean": 2.04056082418,
"stddev": 0.03019511872656054,
"median": 2.03780013248,
"user": 2.6825477999999996,
"system": 3.28095238,
"min": 1.98972329448,
"max": 2.0923268664799997,
"times": [
2.02223136248,
2.0923268664799997,
2.06583289548,
2.05755849648,
2.0661103594799997,
2.03679200248,
2.01825276748,
1.98972329448,
2.01797193448,
2.03880826248
]
}
]
}Scenario: Isolated linker: fresh restore, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 0.6647128670999999,
"stddev": 0.04425771491766625,
"median": 0.6517223577,
"user": 0.35438329999999996,
"system": 1.3373645200000002,
"min": 0.6442261082,
"max": 0.7901644762000001,
"times": [
0.7901644762000001,
0.6545161692,
0.6527296122,
0.6523210082,
0.6578938632,
0.6465060712,
0.6511237072,
0.6498703922,
0.6477772632000001,
0.6442261082
]
},
{
"command": "pacquet@main",
"mean": 0.6754161867000001,
"stddev": 0.05511042841472738,
"median": 0.6517725412,
"user": 0.3624755,
"system": 1.32459882,
"min": 0.6478106312,
"max": 0.8255228152,
"times": [
0.6993572132,
0.6485527262,
0.6489907462000001,
0.6485609892,
0.6695610472,
0.6523416762,
0.6622606162,
0.6478106312,
0.6512034062000001,
0.8255228152
]
}
]
}Scenario: Isolated linker: fresh install, cold cache + cold store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 2.3269725806800006,
"stddev": 0.05078723621948081,
"median": 2.32575422318,
"user": 3.8656060399999994,
"system": 2.9944723800000004,
"min": 2.25102872768,
"max": 2.43554291568,
"times": [
2.3006924256800003,
2.34949165368,
2.31432854668,
2.28618423768,
2.33717989968,
2.29122261268,
2.3521874196800003,
2.35186736768,
2.43554291568,
2.25102872768
]
},
{
"command": "pacquet@main",
"mean": 2.30049320518,
"stddev": 0.03147883854514897,
"median": 2.30130783918,
"user": 3.84072064,
"system": 2.98636708,
"min": 2.23195844368,
"max": 2.34587987968,
"times": [
2.3057806276800004,
2.29618248068,
2.33574849968,
2.31541940868,
2.29683505068,
2.23195844368,
2.28673216968,
2.34587987968,
2.30916544468,
2.28123004668
]
}
]
}Scenario: Isolated linker: fresh install, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 1.47420160648,
"stddev": 0.01083322417201359,
"median": 1.47227117888,
"user": 1.6739832799999999,
"system": 1.7717096599999997,
"min": 1.46289082288,
"max": 1.49543898688,
"times": [
1.47050211988,
1.47422156788,
1.48103391788,
1.49543898688,
1.4740402378800002,
1.46533978288,
1.46289082288,
1.46785576088,
1.48750070288,
1.4631921648800001
]
},
{
"command": "pacquet@main",
"mean": 1.52929861768,
"stddev": 0.0999291939780776,
"median": 1.5032466173799999,
"user": 1.6951144799999998,
"system": 1.79829806,
"min": 1.45706039388,
"max": 1.8084565428800001,
"times": [
1.45706039388,
1.8084565428800001,
1.48952197688,
1.48766038688,
1.5254112908800002,
1.4900044808800001,
1.50560568488,
1.51771549988,
1.51066236988,
1.50088754988
]
}
]
} |
|
| Branch | pr/12021 |
| Testbed | pacquet |
Click to view all benchmark results
| Benchmark | Latency | Benchmark Result milliseconds (ms) (Result Δ%) | Upper Boundary milliseconds (ms) (Limit %) |
|---|---|---|---|
| isolated-linker.fresh-install.cold-cache.cold-store | 📈 view plot 🚷 view threshold | 2,326.97 ms(+0.82%)Baseline: 2,308.07 ms | 2,769.68 ms (84.02%) |
| isolated-linker.fresh-install.hot-cache.hot-store | 📈 view plot 🚷 view threshold | 1,474.20 ms(+2.41%)Baseline: 1,439.54 ms | 1,727.45 ms (85.34%) |
| isolated-linker.fresh-restore.cold-cache.cold-store | 📈 view plot 🚷 view threshold | 2,065.98 ms(+0.18%)Baseline: 2,062.21 ms | 2,474.66 ms (83.49%) |
| isolated-linker.fresh-restore.hot-cache.hot-store | 📈 view plot 🚷 view threshold | 664.71 ms(-2.11%)Baseline: 679.07 ms | 814.88 ms (81.57%) |
|
Actionable comments posted: 0 |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #12021 +/- ##
==========================================
+ Coverage 88.42% 88.50% +0.08%
==========================================
Files 230 230
Lines 29177 29225 +48
==========================================
+ Hits 25799 25867 +68
+ Misses 3378 3358 -20 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
pacquet/crates/lockfile/src/pkg_name_ver_peer.rs (1)
36-46: ⚡ Quick winUpdate the function rustdoc to reflect the full escape set.
The behavior now escapes
[\\/:*?"<>|#], but the method-level doc still describes only/→+. Please align the contract doc with the new behavior to avoid future drift.As per coding guidelines, "Doc comments (
///,//!) document the contract."Also applies to: 49-49
🤖 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/lockfile/src/pkg_name_ver_peer.rs` around lines 36 - 46, Update the rustdoc for the filesystem-escaping utility to reflect the full set of characters now escaped (the class [\\/:*?\"<>|#]) and that each of these is replaced with '+' (not just `/`), so locate the doc comment that describes the behavior (near the escape_for_fs closure / the function that builds package filenames in pkg_name_ver_peer.rs) and change the wording to explicitly list the escaped characters and the replacement rule; ensure the same corrected doc text is applied to the other doc occurrence mentioned in the review.
🤖 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/lockfile/src/pkg_name_ver_peer.rs`:
- Around line 36-46: Update the rustdoc for the filesystem-escaping utility to
reflect the full set of characters now escaped (the class [\\/:*?\"<>|#]) and
that each of these is replaced with '+' (not just `/`), so locate the doc
comment that describes the behavior (near the escape_for_fs closure / the
function that builds package filenames in pkg_name_ver_peer.rs) and change the
wording to explicitly list the escaped characters and the replacement rule;
ensure the same corrected doc text is applied to the other doc occurrence
mentioned in the review.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 0cfc1bb1-6175-4931-8cee-bec988ca289b
📒 Files selected for processing (3)
pacquet/crates/cli/tests/inject_workspace_packages.rspacquet/crates/lockfile/src/pkg_name_ver_peer.rspacquet/crates/lockfile/src/pkg_name_ver_peer/tests.rs
🚧 Files skipped from review as they are similar to previous changes (1)
- pacquet/crates/cli/tests/inject_workspace_packages.rs
📜 Review details
🧰 Additional context used
📓 Path-based instructions (1)
pacquet/**/*.rs
📄 CodeRabbit inference engine (pacquet/AGENTS.md)
pacquet/**/*.rs: When porting a function that 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/lockfile/src/pkg_name_ver_peer.rspacquet/crates/lockfile/src/pkg_name_ver_peer/tests.rs
🧠 Learnings (4)
📚 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/lockfile/src/pkg_name_ver_peer.rspacquet/crates/lockfile/src/pkg_name_ver_peer/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/lockfile/src/pkg_name_ver_peer.rspacquet/crates/lockfile/src/pkg_name_ver_peer/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/lockfile/src/pkg_name_ver_peer.rspacquet/crates/lockfile/src/pkg_name_ver_peer/tests.rs
📚 Learning: 2026-05-20T23:23:40.606Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11788
File: pacquet/crates/lockfile/src/snapshot_dep_ref.rs:52-56
Timestamp: 2026-05-20T23:23:40.606Z
Learning: In the pacquet lockfile crate, the link target is intentionally represented as a raw `Link(String)` (not a `LinkTarget` newtype) for both `SnapshotDepRef::Link` and `ImporterDepVersion::Link`. pnpm upstream preserves `link:` targets verbatim with only a prefix check (e.g., `refToRelative`), so do not suggest introducing a `LinkTarget` newtype in a bug-fix/targeted PR. If type branding is ever desired, it should be done together for both variants in a separate dedicated refactor PR rather than as part of a small fix.
Applied to files:
pacquet/crates/lockfile/src/pkg_name_ver_peer.rspacquet/crates/lockfile/src/pkg_name_ver_peer/tests.rs
🔇 Additional comments (1)
pacquet/crates/lockfile/src/pkg_name_ver_peer/tests.rs (1)
95-103: LGTM!
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
pacquet/crates/lockfile/src/pkg_name_ver_peer.rs (1)
22-22: ⚡ Quick winUpdate the high-level summary to reflect broader escaping.
The summary mentions only
/→+, but the code now escapes 10 filesystem-invalid characters to+. Consider revising to "filesystem-invalid chars →+" or similar to avoid giving the impression that only forward slashes are replaced. The detailed explanation at lines 36-43 is accurate; this would align the high-level summary with it.📝 Suggested wording
- /// the lossy escape (parens → underscores, `/` → `+`) runs first, + /// the lossy escape (parens → underscores, filesystem-invalid chars → `+`) runs first,🤖 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/lockfile/src/pkg_name_ver_peer.rs` at line 22, Update the high-level summary comment that currently reads "the lossy escape (parens → underscores, `/` → `+`) runs first," in pkg_name_ver_peer.rs to reflect that multiple filesystem-invalid characters (not just `/`) are escaped to `+` — e.g., change to "the lossy escape (parens → underscores, filesystem-invalid chars → `+`) runs first," so the brief summary matches the more detailed explanation later in the doc comment.
🤖 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/lockfile/src/pkg_name_ver_peer.rs`:
- Line 22: Update the high-level summary comment that currently reads "the lossy
escape (parens → underscores, `/` → `+`) runs first," in pkg_name_ver_peer.rs to
reflect that multiple filesystem-invalid characters (not just `/`) are escaped
to `+` — e.g., change to "the lossy escape (parens → underscores,
filesystem-invalid chars → `+`) runs first," so the brief summary matches the
more detailed explanation later in the doc comment.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: dd0b2d7e-d3ea-4787-bc40-adb81a253a8d
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (18)
pacquet/crates/cli/Cargo.tomlpacquet/crates/cli/tests/inject_workspace_packages.rspacquet/crates/config/src/lib.rspacquet/crates/config/src/workspace_yaml.rspacquet/crates/config/src/workspace_yaml/tests.rspacquet/crates/lockfile/src/freshness.rspacquet/crates/lockfile/src/freshness/tests.rspacquet/crates/lockfile/src/lib.rspacquet/crates/lockfile/src/pkg_name_ver_peer.rspacquet/crates/lockfile/src/pkg_name_ver_peer/tests.rspacquet/crates/package-manager/src/dependencies_graph_to_lockfile.rspacquet/crates/package-manager/src/dependencies_graph_to_lockfile/tests.rspacquet/crates/package-manager/src/install.rspacquet/crates/package-manager/src/install_package_from_registry/tests.rspacquet/crates/package-manager/src/install_with_fresh_lockfile.rspacquet/crates/package-manager/src/optimistic_repeat_install.rspacquet/crates/package-manager/src/optimistic_repeat_install/tests.rspacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs
🚧 Files skipped from review as they are similar to previous changes (16)
- pacquet/crates/cli/Cargo.toml
- pacquet/crates/lockfile/src/lib.rs
- pacquet/crates/config/src/workspace_yaml.rs
- pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs
- pacquet/crates/lockfile/src/pkg_name_ver_peer/tests.rs
- pacquet/crates/config/src/workspace_yaml/tests.rs
- pacquet/crates/package-manager/src/dependencies_graph_to_lockfile/tests.rs
- pacquet/crates/config/src/lib.rs
- pacquet/crates/package-manager/src/install_package_from_registry/tests.rs
- pacquet/crates/lockfile/src/freshness.rs
- pacquet/crates/package-manager/src/optimistic_repeat_install/tests.rs
- pacquet/crates/cli/tests/inject_workspace_packages.rs
- pacquet/crates/package-manager/src/optimistic_repeat_install.rs
- pacquet/crates/lockfile/src/freshness/tests.rs
- pacquet/crates/package-manager/src/dependencies_graph_to_lockfile.rs
- pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.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). (2)
- GitHub Check: Lint and Test (windows-latest)
- GitHub Check: Lint and Test (macos-latest)
🧰 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/package-manager/src/install.rspacquet/crates/lockfile/src/pkg_name_ver_peer.rs
🧠 Learnings (4)
📚 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.rspacquet/crates/lockfile/src/pkg_name_ver_peer.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.rspacquet/crates/lockfile/src/pkg_name_ver_peer.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.rspacquet/crates/lockfile/src/pkg_name_ver_peer.rs
📚 Learning: 2026-05-20T23:23:40.606Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11788
File: pacquet/crates/lockfile/src/snapshot_dep_ref.rs:52-56
Timestamp: 2026-05-20T23:23:40.606Z
Learning: In the pacquet lockfile crate, the link target is intentionally represented as a raw `Link(String)` (not a `LinkTarget` newtype) for both `SnapshotDepRef::Link` and `ImporterDepVersion::Link`. pnpm upstream preserves `link:` targets verbatim with only a prefix check (e.g., `refToRelative`), so do not suggest introducing a `LinkTarget` newtype in a bug-fix/targeted PR. If type branding is ever desired, it should be done together for both variants in a separate dedicated refactor PR rather than as part of a small fix.
Applied to files:
pacquet/crates/lockfile/src/pkg_name_ver_peer.rs
🔇 Additional comments (3)
pacquet/crates/package-manager/src/install.rs (1)
1064-1064: LGTM!pacquet/crates/lockfile/src/pkg_name_ver_peer.rs (2)
36-46: LGTM!
47-53: LGTM!
|
Actionable comments posted: 0 |
059ba76 to
e5247dd
Compare
Adds the `injectWorkspacePackages` config knob to pacquet. When set, workspace-package resolutions materialize as `file:` (hard-linked copies) in the virtual store instead of `link:` symlinks back to the source. The resolver-side wiring (`ResolveFromWorkspaceOptions`, `try_resolve_from_workspace`, per-dep `injected` toggle) already existed; this commit feeds the user's setting into that machinery and joins it to the workspace-state freshness comparison so a flip between runs invalidates the optimistic-repeat-install cache. Refs #12009.
Extends the prior config-layer port (commit ecde513) so the full install pipeline behaves correctly when `injectWorkspacePackages: true` is set in `pnpm-workspace.yaml`. - `LockfileSettings.inject_workspace_packages` round-trips through the v9 lockfile. `false` omits the key on save, matching pnpm's [`lockfileFormatConverters.ts:70-72`](https://github.com/pnpm/pnpm/blob/39101f5e37/lockfile/fs/src/lockfileFormatConverters.ts#L70-L72). - `dependencies_graph_to_lockfile` populates the new field from `Config.inject_workspace_packages` so a fresh-resolve install records what it used. - `check_lockfile_settings` adds a Boolean-normalized drift gate for `settings.injectWorkspacePackages`, mirroring upstream's [`getOutdatedLockfileSetting.ts:80-82`](https://github.com/pnpm/pnpm/blob/39101f5e37/lockfile/settings-checker/src/getOutdatedLockfileSetting.ts#L80-L82). - `build_pkg_id_with_patch_hash` now prefixes file:/git:/tarball: resolutions with `name@` when the resolver didn't set `name_ver`, reading the name from the manifest — matching pnpm at [`resolveDependencies.ts:1502-1507`](https://github.com/pnpm/pnpm/blob/097983fbca/installing/deps-resolver/src/resolveDependencies.ts#L1502-L1507). Without this, file: dep paths end up unprefixed and PkgNameVerPeer's `@`-split parser fails on the `@` inside the peer suffix. `link:` ids are deliberately exempted so the downstream `is_link` short-circuit (`id.starts_with("link:")`) still triggers. - Ports the canonical upstream test `'inject local packages using the injectWorkspacePackages setting'` ([`injectLocalPackages.ts:218`](https://github.com/pnpm/pnpm/blob/39101f5e37/installing/deps-installer/test/install/injectLocalPackages.ts#L218-L414)) as a pacquet CLI integration test asserting the file: resolution scheme, the recorded lockfile setting, and the virtual-store cardinality. Per-dep `dependenciesMeta[*].injected` (the manifest-level opt-in toggle when the global flag is off) is still not threaded onto `WantedDependency.injected`; tracked as a follow-up. Refs #12009.
…ames `to_virtual_store_name` only escaped `/`, leaving `:` (and the other characters Windows refuses in filenames — `\\ : * ? " < > | #`) in the rendered slot directory. With the `injectWorkspacePackages` end-to-end test exercising a `file:` resolution, the slot name becomes `project-1@file:project-1_is-positive@1.0.0` — invalid on NTFS (`ERROR_INVALID_NAME (123)`) but accepted on APFS / ext4, so the bug went unnoticed until a workspace-injected dep hit Windows CI. Mirror upstream's [`depPathToFilename` regex](https://github.com/pnpm/pnpm/blob/1819226b51/deps/path/src/index.ts#L169-L170) `[\\/:*?"<>|#]` → `+`. Adds a unit test for the file: case and an integration-test assertion that pins the FS-safe slot name on every platform. Also drop a trailing comma flagged by Dylint's perfectionist `macro-trailing-comma` lint in `inject_workspace_packages.rs`.
…dep flag Per-dep `dependenciesMeta[<name>].injected = true` from an importer manifest now flips that single dep onto the hard-linked `file:` resolution shape even when the global `injectWorkspacePackages` is off — mirroring upstream's [`injected: opts.dependenciesMeta[alias]?.injected`](https://github.com/pnpm/pnpm/blob/094aa6e57b/installing/deps-resolver/src/getWantedDependencies.ts#L73) thread. - `importer_injected_dependency_names(manifest)` reads `dependenciesMeta` from the importer manifest and returns the name set whose `injected` flag is `true`. - `WantedSpec` widens from `(alias, range, optional)` to `(alias, range, optional, injected)`. `resolve_catalog_specifiers` passes `injected` through verbatim; the hoisted-required / hoisted- optional peer arms default it to `false` (those construct fresh wanteds without dep meta). - `extend_tree` lifts the spec's `injected` onto `WantedDependency::injected` as `Some(true)` / `None`, matching pnpm's `injected: opts.dependenciesMeta[alias]?.injected` `undefined` shape so the wire shape stays aligned. - `WantedKey` cache key gains `Option<bool>` for `injected` — the workspace branch of the npm resolver emits a `file:` resolution when injected and a `link:` one otherwise, so two importers asking for the same workspace dep with different per-dep flags must take different cache slots. - Importer-only scope: child-spec walker keeps its 3-tuple shape; no resolved package's own `dependenciesMeta` is inherited. Adds an integration test (`dependencies_meta_injected_per_dep_overrides_global_off`) that pins the contract: two-project workspace with `injectWorkspacePackages: false` (explicit) + per-dep `dependenciesMeta.project-1.injected = true`. The lockfile must record project-1 as a `file:` resolution inside project-2's importer block. Closes the follow-up gap called out in commit 44e2ea9.
Summary
Ports
injectWorkspacePackagesto pacquet end-to-end — config layer, resolver (global + per-dep), lockfile, and freshness gates — closing one of the eight settings tracked at #12009.What the feature does
When set globally, workspace-package resolutions materialize as
file:(hard-linked copies into the virtual store) instead oflink:symlinks back to the source. Per-dependencydependenciesMeta[<name>].injected = trueopts a single dep into the same behavior even when the global flag is off.Changes
Config layer (commit b3a6895):
inject_workspace_packages: booltoConfig(defaultfalse, matching pnpm's'inject-workspace-packages': false).WorkspaceSettingsso it parses frompnpm-workspace.yaml(camelCase).config.inject_workspace_packagesintoResolveOptionsat the install entry point.current_settingswrites the value,settings_matchcompares it, a flip between runs invalidates the optimistic-repeat-install cache.End-to-end pipeline (commit 44e2ea9):
LockfileSettings.inject_workspace_packagesround-trips through the v9 lockfile.falseomits the key on save, matching pnpm'slockfileFormatConverters.ts:70-72.dependencies_graph_to_lockfilepopulates the new field fromConfig.inject_workspace_packages.check_lockfile_settingsadds a Boolean-normalized drift gate forsettings.injectWorkspacePackages, mirroring upstream'sgetOutdatedLockfileSetting.ts:80-82.build_pkg_id_with_patch_hashnow prefixes file:/git:/tarball: resolutions withname@when the resolver didn't setname_ver, reading the name from the manifest — matching pnpm atresolveDependencies.ts:1502-1507. Without this, file: dep paths end up unprefixed andPkgNameVerPeer's@-split parser fails on the@inside the peer suffix.link:ids are deliberately exempted so the downstreamis_linkshort-circuit still triggers.Cross-platform fix (commit 38a1db9):
PkgNameVerPeer::to_virtual_store_namepreviously escaped only/, leaving:infile:-resolution slot names. NTFS / FAT reject these withERROR_INVALID_NAME (123), but APFS / ext4 accept them, so the bug only surfaced once a workspace-injected dep hit the Windows CI of this PR. The escape now covers the full Windows-invalid charset[\\/:*?"<>|#], matching upstream'sdepPathToFilenameregex.Per-dep
dependenciesMetathread (commit 7cc0f98):importer_injected_dependency_names(manifest)readsdependenciesMetafrom the importer manifest and returns the names whoseinjectedflag istrue.extend_tree/resolve_catalog_specifierswidens from(alias, range, optional)to(alias, range, optional, injected).extend_treelifts the spec'sinjectedontoWantedDependency::injectedasSome(true)/None, matching pnpm'sinjected: opts.dependenciesMeta[alias]?.injectedshape so absent meta yieldsNone(notSome(false)).WantedKeycache key gains a fourth slot forinjected— the workspace branch of the npm resolver emits afile:resolution when injected and alink:one otherwise, so two importers asking for the same workspace dep with different per-dep flags must take different cache buckets.dependenciesMetais inherited. Hoisted-required / hoisted-optional peer arms defaultinjectedtofalsesince they construct fresh wanteds without dep meta.Refs #12009.
Test plan
parses_inject_workspace_packages_true_from_yaml— yamlinjectWorkspacePackages: trueapplies toConfig.parses_inject_workspace_packages_false_from_yaml— explicitfalseoverrides a pre-set config value.inject_workspace_packages_defaults_off_when_absent— missing yaml key leavesConfig::new()'sfalsedefault.returns_skipped_when_inject_workspace_packages_drifts— workspace-state cache invalidates on flip.check_settings_passes_when_inject_workspace_packages_both_false— wire-format false omission round-trips correctly.check_settings_passes_when_inject_workspace_packages_both_true— explicittrueround-trips.check_settings_returns_drift_when_config_enables_inject_workspace_packages— flipping on between installs surfaces drift.check_settings_returns_drift_when_config_disables_inject_workspace_packages— flipping off surfaces drift.inject_workspace_packages_writes_file_resolutions_and_lockfile_setting— end-to-end integration test ported from upstream's canonicalinjectLocalPackages.ts:218. Three-project workspace with a peer-dep chain, assertingfile:resolutions, peer-suffix shape, the recordedsettings.injectWorkspacePackages: truein the lockfile, and the Windows-safe slot-name escape.dependencies_meta_injected_per_dep_overrides_global_off— per-dep integration test: two-project workspace withinjectWorkspacePackages: false(explicit) + per-depdependenciesMeta.project-1.injected = true. The lockfile must record project-1 as afile:resolution inside project-2's importer block. Pins that the per-dep flag flowed from manifest read all the way to resolver output.to_virtual_store_nameunit test pins the file:-+-peer-suffix →file+-prefixed slot name on every platform.inject_workspace_packages_writes_file_resolutionandinjected_workspace_match_emits_file_resolutionresolver tests still pass.pacquet-config,pacquet-lockfile,pacquet-package-manager,pacquet-resolving-npm-resolver,pacquet-resolving-deps-resolver,pacquet-workspace-state,pacquet-cli).just dylint(perfectionist lints) clean.cargo clippy --all-targets -- -D warningsclean on touched crates.cargo fmt --checkclean.Written by an agent (Claude Code, claude-opus-4-7).
Summary by CodeRabbit
New Features
Bug Fixes
Tests
Other