Skip to content

feat(pacquet): port injectWorkspacePackages setting#12021

Merged
zkochan merged 4 commits into
mainfrom
inject-work
May 28, 2026
Merged

feat(pacquet): port injectWorkspacePackages setting#12021
zkochan merged 4 commits into
mainfrom
inject-work

Conversation

@zkochan

@zkochan zkochan commented May 28, 2026

Copy link
Copy Markdown
Member

Summary

Ports injectWorkspacePackages to 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 of link: symlinks back to the source. Per-dependency dependenciesMeta[<name>].injected = true opts a single dep into the same behavior even when the global flag is off.

Changes

Config layer (commit b3a6895):

  • Adds inject_workspace_packages: bool to Config (default false, matching pnpm's 'inject-workspace-packages': false).
  • Adds the field to WorkspaceSettings so it parses from pnpm-workspace.yaml (camelCase).
  • Threads config.inject_workspace_packages into ResolveOptions at the install entry point.
  • Joins it to the workspace-state freshness comparison: current_settings writes the value, settings_match compares it, a flip between runs invalidates the optimistic-repeat-install cache.

End-to-end pipeline (commit 44e2ea9):

  • LockfileSettings.inject_workspace_packages round-trips through the v9 lockfile. false omits the key on save, matching pnpm's lockfileFormatConverters.ts:70-72.
  • dependencies_graph_to_lockfile populates the new field from Config.inject_workspace_packages.
  • check_lockfile_settings adds a Boolean-normalized drift gate for settings.injectWorkspacePackages, mirroring upstream's getOutdatedLockfileSetting.ts:80-82.
  • 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. 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 still triggers.

Cross-platform fix (commit 38a1db9):

  • PkgNameVerPeer::to_virtual_store_name previously escaped only /, leaving : in file:-resolution slot names. NTFS / FAT reject these with ERROR_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's depPathToFilename regex.

Per-dep dependenciesMeta thread (commit 7cc0f98):

  • importer_injected_dependency_names(manifest) reads dependenciesMeta from the importer manifest and returns the names whose injected flag is true.
  • The wanted-spec tuple in extend_tree / resolve_catalog_specifiers widens from (alias, range, optional) to (alias, range, optional, injected). extend_tree lifts the spec's injected onto WantedDependency::injected as Some(true) / None, matching pnpm's injected: opts.dependenciesMeta[alias]?.injected shape so absent meta yields None (not Some(false)).
  • WantedKey cache key gains a fourth slot 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 buckets.
  • Importer-only scope: the child-spec walker keeps its 3-tuple shape; no resolved package's own dependenciesMeta is inherited. Hoisted-required / hoisted-optional peer arms default injected to false since they construct fresh wanteds without dep meta.

Refs #12009.

Test plan

  • parses_inject_workspace_packages_true_from_yaml — yaml injectWorkspacePackages: true applies to Config.
  • parses_inject_workspace_packages_false_from_yaml — explicit false overrides a pre-set config value.
  • inject_workspace_packages_defaults_off_when_absent — missing yaml key leaves Config::new()'s false default.
  • 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 — explicit true round-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 canonical injectLocalPackages.ts:218. Three-project workspace with a peer-dep chain, asserting file: resolutions, peer-suffix shape, the recorded settings.injectWorkspacePackages: true in the lockfile, and the Windows-safe slot-name escape.
  • dependencies_meta_injected_per_dep_overrides_global_off — per-dep integration test: 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. Pins that the per-dep flag flowed from manifest read all the way to resolver output.
  • to_virtual_store_name unit test pins the file:-+-peer-suffix → file+-prefixed slot name on every platform.
  • Existing inject_workspace_packages_writes_file_resolution and injected_workspace_match_emits_file_resolution resolver tests still pass.
  • All 1147 affected-crate tests 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 warnings clean on touched crates.
  • cargo fmt --check clean.

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

Summary by CodeRabbit

  • New Features

    • Added injectWorkspacePackages setting to control whether workspace packages are materialized as file: resolutions and recorded in lockfiles.
  • Bug Fixes

    • Freshness checks and install flows now respect this setting to avoid stale or incorrect installs.
  • Tests

    • Added unit and end-to-end tests for YAML parsing, per-dependency overrides, lockfile writing, install behavior, and freshness checks.
  • Other

    • Improved virtual-store filename escaping for safer directory names.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds pnpm's inject_workspace_packages support: new Config and WorkspaceSettings fields, lockfile settings persistence and freshness checks, resolver and fresh-lockfile wiring, an end-to-end CLI test, virtual-store escaping updates, and related unit tests.

Changes

inject_workspace_packages configuration support

Layer / File(s) Summary
Config definition and workspace YAML parsing
pacquet/crates/config/src/lib.rs, pacquet/crates/config/src/workspace_yaml.rs, pacquet/crates/config/src/workspace_yaml/tests.rs, pacquet/crates/package-manager/src/install_package_from_registry/tests.rs
Adds Config.inject_workspace_packages: bool; WorkspaceSettings.inject_workspace_packages: Option<bool>; clear/apply logic for workspace-only/global config; unit tests and a test helper update.
Optimistic repeat install settings tracking
pacquet/crates/package-manager/src/optimistic_repeat_install.rs, pacquet/crates/package-manager/src/optimistic_repeat_install/tests.rs
Record inject_workspace_packages in recorded workspace-state and include it in settings_match; add test asserting drift causes Decision::Skipped and adjust regression test payload.
Lockfile freshness checks and tests
pacquet/crates/lockfile/src/lib.rs, pacquet/crates/lockfile/src/freshness.rs, pacquet/crates/lockfile/src/freshness/tests.rs
Add LockfileSettings.inject_workspace_packages, extend check_lockfile_settings signature and logic to compare lockfile vs config inject_workspace_packages and surface StalenessReason::InjectWorkspacePackagesChanged; update tests to cover equality/drift and ordering.
Graph->Lockfile wiring and tests
pacquet/crates/package-manager/src/dependencies_graph_to_lockfile.rs, pacquet/crates/package-manager/src/dependencies_graph_to_lockfile/tests.rs
Add GraphToLockfileOptions.inject_workspace_packages, destructure it in dependencies_graph_to_lockfile, thread into produced LockfileSettings, and set the option in test helpers/literals.
Install fresh-lockfile wiring
pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs, pacquet/crates/package-manager/src/install.rs
Pass config.inject_workspace_packages into resolver base options during fresh resolution and into GraphToLockfileOptions when building fresh lockfile; include it in check_lockfile_settings call.
CLI E2E test
pacquet/crates/cli/Cargo.toml, pacquet/crates/cli/tests/inject_workspace_packages.rs
Add serde-saphyr dep and an end-to-end test that enables injectWorkspacePackages in pnpm-workspace.yaml, runs install, and asserts lockfile settings.injectWorkspacePackages: true, file: resolutions, virtual-store contents and escaping; also add per-dep dependenciesMeta[*].injected override test.
Virtual-store name escaping
pacquet/crates/lockfile/src/pkg_name_ver_peer.rs, pacquet/crates/lockfile/src/pkg_name_ver_peer/tests.rs
Expand escaping to handle additional filesystem-restricted characters and add tests asserting file: keys convert to escaped virtual-store names.
Resolver wanted-spec/injected flag and pkg-id logic
pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs, pacquet/crates/resolving-deps-resolver/src/resolve_importer.rs
Thread injected boolean through WantedSpec/WantedKey, extend extend_tree/resolve_catalog_specifiers, add importer_injected_dependency_names, key memoization on injected, and make build_pkg_id_with_patch_hash defensive for link: ids and missing name/version.

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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

  • pnpm/pnpm#11787: Modifies resolver catalog/specifier rewriting—the resolver changes here are directly related.
  • pnpm/pnpm#11943: Related optimistic repeat-install enhancements touching the same area.
  • pnpm/pnpm#11945: Also modifies optimistic repeat-install checks that intersect this change.

Suggested reviewers

  • KSXGitHub

🐰 I found a YAML key in a garden patch,
Injected packages, a tidy match.
From workspace YAML to lockfile lore,
Freshness checks hop and tests explore,
Hooray — the workspace link rules catch!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title directly and specifically describes the main change: porting the injectWorkspacePackages setting into pacquet from pnpm.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch inject-work

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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

Copy link
Copy Markdown

Review Summary by Qodo

Port injectWorkspacePackages config setting to pacquet

✨ Enhancement

Grey Divider

Walkthroughs

Description
• 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
Diagram
flowchart 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"]

Loading

Grey Divider

File Changes

1. pacquet/crates/config/src/lib.rs ✨ Enhancement +11/-0

Add inject_workspace_packages field to Config

• Add inject_workspace_packages: bool field to Config struct with default false
• Document behavior: workspace resolutions materialize as file: hard-linked copies instead of
 link: symlinks when enabled
• Reference pnpm's upstream implementation and per-dependency dependenciesMeta[*].injected
 override mechanism

pacquet/crates/config/src/lib.rs


2. pacquet/crates/config/src/workspace_yaml.rs ✨ Enhancement +7/-0

Add inject_workspace_packages to WorkspaceSettings

• Add inject_workspace_packages: Option field to WorkspaceSettings struct
• Document camelCase yaml parsing from pnpm-workspace.yaml
• Include field in reset() method to clear it alongside other workspace settings
• Add field to struct destructuring in apply_to() method

pacquet/crates/config/src/workspace_yaml.rs


3. pacquet/crates/config/src/workspace_yaml/tests.rs 🧪 Tests +36/-0

Add inject_workspace_packages yaml parsing tests

• Add parses_inject_workspace_packages_true_from_yaml() test for yaml true value propagation
• Add parses_inject_workspace_packages_false_from_yaml() test for explicit false override
• Add inject_workspace_packages_defaults_off_when_absent() test for missing yaml key behavior
• All tests verify correct parsing and application to Config struct

pacquet/crates/config/src/workspace_yaml/tests.rs


View more (4)
4. pacquet/crates/package-manager/src/install_package_from_registry/tests.rs 🧪 Tests +1/-0

Initialize inject_workspace_packages in test config

• Initialize inject_workspace_packages: false in test Config creation helper
• Ensures test config matches production struct definition

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


5. pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs ✨ Enhancement +1/-0

Thread inject_workspace_packages to ResolveOptions

• Thread config.inject_workspace_packages into ResolveOptions at install entry point
• Enables resolver chain to access user's global setting for workspace package materialization

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


6. pacquet/crates/package-manager/src/optimistic_repeat_install.rs ✨ Enhancement +2/-1

Include inject_workspace_packages in freshness comparison

• Add inject_workspace_packages comparison to settings_match() function
• Add inject_workspace_packages to current_settings() output for workspace state persistence
• Remove injectWorkspacePackages from "deliberately not compared" comment since it's now tracked
• Ensures cache invalidation when flag toggles between runs

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


7. pacquet/crates/package-manager/src/optimistic_repeat_install/tests.rs 🧪 Tests +43/-1

Add cache invalidation test for inject_workspace_packages

• Add returns_skipped_when_inject_workspace_packages_drifts() test
• Verifies that toggling inject_workspace_packages between runs invalidates
 optimistic-repeat-install cache
• Removes inject_workspace_packages from unported settings in
 returns_up_to_date_when_state_carries_unported_pnpm_settings() test
• Confirms setting is now properly tracked and compared

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


Grey Divider

Qodo Logo

@github-actions

github-actions Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

Micro-Benchmark Results

Linux

group                          main                                   pr
-----                          ----                                   --
tarball/download_dependency    1.01      8.0±0.14ms   542.7 KB/sec    1.00      7.9±0.44ms   550.0 KB/sec

@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown

Actionable comments posted: 0

@github-actions

github-actions Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

Integrated-Benchmark Report (Linux)

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

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 2.066 ± 0.057 1.957 2.139 1.01 ± 0.03
pacquet@main 2.041 ± 0.030 1.990 2.092 1.00
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

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 664.7 ± 44.3 644.2 790.2 1.00
pacquet@main 675.4 ± 55.1 647.8 825.5 1.02 ± 0.11
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

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 2.327 ± 0.051 2.251 2.436 1.01 ± 0.03
pacquet@main 2.300 ± 0.031 2.232 2.346 1.00
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

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 1.474 ± 0.011 1.463 1.495 1.00
pacquet@main 1.529 ± 0.100 1.457 1.808 1.04 ± 0.07
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
      ]
    }
  ]
}

@github-actions

github-actions Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

🐰 Bencher Report

Branchpr/12021
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,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%)
🐰 View full continuous benchmarking report in Bencher

@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown

Actionable comments posted: 0

@codecov-commenter

codecov-commenter commented May 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.58824% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.50%. Comparing base (4b60515) to head (6ac505b).

Files with missing lines Patch % Lines
...es/resolving-deps-resolver/src/resolve_importer.rs 75.00% 2 Missing ⚠️
...lving-deps-resolver/src/resolve_dependency_tree.rs 97.43% 1 Missing ⚠️
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.
📢 Have feedback on the report? Share it here.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
pacquet/crates/lockfile/src/pkg_name_ver_peer.rs (1)

36-46: ⚡ Quick win

Update 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

📥 Commits

Reviewing files that changed from the base of the PR and between 0ea1ac5 and 38db6d6.

📒 Files selected for processing (3)
  • pacquet/crates/cli/tests/inject_workspace_packages.rs
  • pacquet/crates/lockfile/src/pkg_name_ver_peer.rs
  • pacquet/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 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/lockfile/src/pkg_name_ver_peer.rs
  • pacquet/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.rs
  • pacquet/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.rs
  • pacquet/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.rs
  • pacquet/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.rs
  • pacquet/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!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
pacquet/crates/lockfile/src/pkg_name_ver_peer.rs (1)

22-22: ⚡ Quick win

Update 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

📥 Commits

Reviewing files that changed from the base of the PR and between 38db6d6 and 38a1db9.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (18)
  • pacquet/crates/cli/Cargo.toml
  • pacquet/crates/cli/tests/inject_workspace_packages.rs
  • pacquet/crates/config/src/lib.rs
  • pacquet/crates/config/src/workspace_yaml.rs
  • pacquet/crates/config/src/workspace_yaml/tests.rs
  • pacquet/crates/lockfile/src/freshness.rs
  • pacquet/crates/lockfile/src/freshness/tests.rs
  • pacquet/crates/lockfile/src/lib.rs
  • pacquet/crates/lockfile/src/pkg_name_ver_peer.rs
  • pacquet/crates/lockfile/src/pkg_name_ver_peer/tests.rs
  • pacquet/crates/package-manager/src/dependencies_graph_to_lockfile.rs
  • pacquet/crates/package-manager/src/dependencies_graph_to_lockfile/tests.rs
  • pacquet/crates/package-manager/src/install.rs
  • pacquet/crates/package-manager/src/install_package_from_registry/tests.rs
  • pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs
  • pacquet/crates/package-manager/src/optimistic_repeat_install.rs
  • pacquet/crates/package-manager/src/optimistic_repeat_install/tests.rs
  • pacquet/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 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
  • pacquet/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.rs
  • pacquet/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.rs
  • pacquet/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.rs
  • pacquet/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!

@zkochan zkochan changed the title feat(pacquet/config): port injectWorkspacePackages setting feat(pacquet): port injectWorkspacePackages setting May 28, 2026
@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown

Actionable comments posted: 0

@zkochan zkochan force-pushed the inject-work branch 2 times, most recently from 059ba76 to e5247dd Compare May 28, 2026 18:03
zkochan added 4 commits May 28, 2026 20:18
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.
@zkochan zkochan merged commit a0eb2ad into main May 28, 2026
25 of 28 checks passed
@zkochan zkochan deleted the inject-work branch May 28, 2026 19:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants