Skip to content

feat(pacquet): peer-dependency resolution stage#11774

Merged
zkochan merged 6 commits into
mainfrom
peers-resolution
May 20, 2026
Merged

feat(pacquet): peer-dependency resolution stage#11774
zkochan merged 6 commits into
mainfrom
peers-resolution

Conversation

@zkochan

@zkochan zkochan commented May 20, 2026

Copy link
Copy Markdown
Member

Summary

Ports pnpm's resolvePeers algorithm to pacquet's resolving-deps-resolver crate and wires it into install_without_lockfile. The depPath-keyed DependenciesGraph produced by the new stage replaces the flat (name, version) keying the install pass used to derive virtual-store slot names from, so two parents sharing the same package can now get different peer suffixes.

What's new

  • pacquet-deps-path crate — ports @pnpm/deps.path helpers: create_peer_dep_graph_hash, dep_path_to_filename, balanced-paren suffix scan (index_of_dep_path_suffix / remove_suffix / get_pkg_id_with_patch_hash), and the PeerId union (Pair { name, version } | DepPath(String)).

  • resolving-deps-resolver data-shape refactor:

    • New NodeId newtype (monotonic counter) for per-occurrence tree identity, mirroring upstream's nextNodeId.
    • ResolvedTree now carries both the flat dedup map (packages keyed by pkgIdWithPatchHash) and the per-occurrence tree (dependencies_tree keyed by NodeId), plus all_peer_dep_names.
    • ResolvedPackage records peer_dependencies extracted with peerDependenciesMeta folding and peerDependenciesWithoutOwn filtering.
    • Cycles in the tree pass break by skipping the cycled edge entirely (Ok(None) from resolve_node), matching upstream's parentIdsContainSequence gate in buildTree.
  • resolve_peers algorithm:

    • DependenciesGraph keyed by DepPath, with children, transitive peer set, depth, and is_pure.
    • ParentRefs propagation — descend with parent + own-children view; resolve own peers against the parent view only.
    • Per-peer semver matching via node-semver, with workspace: prefix stripping, * shortcut, and a literal-equality fallback for unparseable ranges.
    • Missing / bad peer issue collection.
    • Transitive peer propagation — children's external peers fold into ancestors' peer suffixes.
    • Cycle handling via in_progress set + name@version fallback peer-id (synchronous analog of upstream's analyzeGraph-driven async deferment).
  • install_without_lockfile rewiring: runs resolve_dependency_tree → resolve_peers and walks the DependenciesGraph by DepPath. Virtual-store slot names flow through dep_path_to_filename.

Scope limits (explicit follow-ups)

  • autoInstallPeers keeps its existing fold behavior. Upstream's hoistPeers pre-pass is not ported in this PR — peer resolution itself runs regardless, but auto-installed peers don't get hoisted to the importer level yet, so their peer-suffix construction is limited until that lands.
  • Three correctness-preserving optimisations are skipped, called out at the top of resolve_peers.rs:
    • peersCache (always recomputes)
    • purePkgs fast path (general path produces same answer)
    • graph-cycles-driven async deferment (replaced by sync in_progress set + name@version fallback)
  • Peer-issue rendering — issues are logged via tracing::warn! for now; porting the issue renderer is its own slice.

Test plan

  • cargo nextest run --workspace1608 passed, 3 skipped, 0 failed
  • cargo clippy --workspace --all-targets -- --deny warnings
  • cargo fmt --check
  • cargo check --workspace --all-targets
  • should_install_circular_dependencies (cycle-break regression) passes
  • 18 new unit tests in pacquet-deps-path (helpers + balanced-paren scan)
  • 4 new tests in resolving-deps-resolver::tests::peers: pure package → depPath equals pkgId; peer resolved against sibling at parent level; missing peer is reported + node stays pure; bad peer version is reported + install still proceeds with picked candidate.

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

Summary by CodeRabbit

  • New Features

    • Installs are now peer-aware, automatically resolving peers and driving installation.
    • Virtual store slot names are now stable and derived from dependency-paths for more reproducible installs.
    • New dependency-path utilities generate filesystem-safe names and compact peer-suffix hashes.
    • Improved detection and reporting of peer dependency issues with clearer diagnostics.
  • Tests

    • Added unit and integration tests for peer resolution, dep-path formatting, naming, and truncation.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 20, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 82c8c542-e839-4f36-961d-77a082e98a8f

📥 Commits

Reviewing files that changed from the base of the PR and between d827b2d and 293b4a0.

📒 Files selected for processing (1)
  • pacquet/crates/resolving-deps-resolver/src/tests.rs
📜 Recent review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Lint and Test (macos-latest)
  • GitHub Check: Lint and Test (windows-latest)
  • GitHub Check: Lint and Test (ubuntu-latest)
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Code Coverage
  • GitHub Check: Compile & Lint
🧰 Additional context used
📓 Path-based instructions (1)
pacquet/**/*.rs

📄 CodeRabbit inference engine (pacquet/AGENTS.md)

pacquet/**/*.rs: When porting a function that fires pnpm:<channel> events through globalLogger, logger.debug(), or streamParser.write(), mirror the call site, payload, and ordering so the reporter parses pacquet's NDJSON the same way it parses pnpm's.
Declare a newtype wrapper for branded string types. Do not collapse the brand into a plain String or &str.
If upstream always validates before construction, validate in pacquet's wrapper too. The wrapper must construct only via TryFrom<String> and/or FromStr. Do not provide an infallible public constructor.
If upstream never validates, just brand for type-safety. Expose an infallible From<String> (and From<&str> when convenient).
If upstream occasionally constructs without validation, expose from_str_unchecked as an escape hatch alongside the validating constructor.
Match upstream serde behavior for branded types that cross JSON, YAML, or INI boundaries. Use #[serde(try_from = "String")] for deserialization and #[serde(into = "String")] for serialization.
Use #[derive(derive_more::From)] and #[derive(derive_more::Into)] for mechanical conversion impls. Fall back to manual impl only when conversion needs custom logic.
String-literal unions should become enums, not newtype wrappers. Model closed sets of valid string values as enums.
Template literal types should be treated as branded strings with validation discipline from rules 2-5.
Choose owned vs. borrowed parameters to minimize copies. Widen to the most encompassing type (&Path over &PathBuf, &str over &String) when it doesn't force extra copies.
Prefer Arc::clone(&x) / Rc::clone(&x) over x.clone() for reference-counted types, so the cost is visible at the call site.
Follow Rust API Guidelines for naming conventions.
Do not use star imports inside module bodies. Write use super::{Foo, bar} instead of use super::*;. Two forms stay allowed: external-crate preludes like use rayon::prelude::*; and root-of-module re-...

Files:

  • pacquet/crates/resolving-deps-resolver/src/tests.rs
🧠 Learnings (1)
📚 Learning: 2026-05-20T19:40:55.051Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11774
File: pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs:0-0
Timestamp: 2026-05-20T19:40:55.051Z
Learning: In the pacquet Rust code, ensure the semver implementation uses the `node-semver` crate (not `nodejs-semver`). `node-semver`’s public API does not include a `satisfies_with_prerelease`-style method; prerelease-tolerant matching should be implemented inline by first calling `Range::satisfies`, and when it rejects a prerelease version, retry matching against a stripped `MAJOR.MINOR.PATCH` base of the prerelease version.

Applied to files:

  • pacquet/crates/resolving-deps-resolver/src/tests.rs
🔇 Additional comments (1)
pacquet/crates/resolving-deps-resolver/src/tests.rs (1)

481-481: LGTM!


📝 Walkthrough

Walkthrough

Adds a new deps-path crate (DepPath utilities and filename mapping), a two-pass resolver (tree then peer-resolution producing a DepPath-keyed graph), and installs wired to materialize packages using DepPath-derived virtual-store names.

Changes

Peer Dependency Resolution and Install Integration

Layer / File(s) Summary
Workspace and crate manifest
Cargo.toml, pacquet/crates/deps-path/Cargo.toml
Adds pacquet-deps-path to the workspace and creates the crate manifest.
DepPath core and types
pacquet/crates/deps-path/src/dep_path.rs, pacquet/crates/deps-path/src/lib.rs, pacquet/crates/deps-path/src/peer_id.rs
Adds DepPath newtype, crate root re-exports, and PeerId enum with as_segment().
DepPath suffix parsing
pacquet/crates/deps-path/src/suffix_index.rs
Parses/removes pnpm-style trailing peer (…) and (patch_hash=...) suffixes; exposes helpers to strip suffixes and preserve patch-hash when requested; includes tests.
Peer suffix hashing
pacquet/crates/deps-path/src/create_peer_dep_graph_hash.rs
Creates stable lexicographic (peer...)(peer...) suffixes and caps long bodies via short-hash replacement; includes tests.
DepPath → filename transform
pacquet/crates/deps-path/src/dep_path_to_filename.rs
Implements pnpm-compatible filename conversion (file: escape, scoped name handling, peer-suffix flattening) and truncation/hash-suffix behavior; includes tests for casing and edge cases.
Resolver model types
pacquet/crates/resolving-deps-resolver/src/resolved_tree.rs, .../node_id.rs, .../dependencies_graph.rs
Reworks ResolvedTree to per-occurrence NodeId dependencies_tree, adds PeerDep/DependenciesTreeNode, and defines DependenciesGraph node and peer-issue types.
Enhanced dependency-tree builder
pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs
Refactors tree walk to allocate NodeId per occurrence, dedupe shared ResolvedPackage, extract deterministic peer maps, and track peer names for later peer pass.
Peer-resolution pass
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs
Adds post-order peer-resolution algorithm that matches peers against propagated parent refs (prerelease-tolerant semver with equality fallback), computes DepPath (pure vs hashed suffix), builds DependenciesGraph, defers and patches pending peer edges, and reports missing/bad peer issues; includes tests.
Install wiring
pacquet/crates/package-manager/Cargo.toml, pacquet/crates/package-manager/src/install_without_lockfile.rs
Runs resolve_peers after tree build, iterates over direct_dependencies_by_alias, threads (alias, dep_path) through install_subtree, looks up graph nodes by DepPath, uses dep_path_to_filename(dep_path) for virtual-store slot naming, and recurses using node.children (alias -> dep_path).
Public API and tests
pacquet/crates/resolving-deps-resolver/src/lib.rs, pacquet/crates/resolving-deps-resolver/src/tests.rs
Expands public re-exports to include peer-resolution APIs and types; updates existing tests to use dependencies_tree and adds peers test suite covering multiple peer scenarios.
Dependency wiring
pacquet/crates/resolving-deps-resolver/Cargo.toml, pacquet/crates/package-manager/Cargo.toml
Adds pacquet-deps-path as a workspace dependency to resolver and package-manager crates.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Install Client
  participant Resolver
  participant Tree as resolve_dependency_tree
  participant Peers as resolve_peers
  participant Graph as DependenciesGraph
  participant InstallFlow as install_subtree

  Client->>Resolver: request install
  Resolver->>Tree: resolve_dependency_tree(direct_specs)
  Tree->>Tree: allocate NodeId per occurrence
  Tree-->>Resolver: ResolvedTree
  Resolver->>Peers: resolve_peers(ResolvedTree)
  Peers->>Peers: walk tree post-order / match peers / compute DepPath
  Peers-->>Resolver: ResolvePeersResult{graph, direct_by_alias}
  Resolver->>InstallFlow: for alias in direct_by_alias
  InstallFlow->>Graph: lookup(dep_path)
  Graph-->>InstallFlow: DependenciesGraphNode
  InstallFlow->>InstallFlow: derive slot = dep_path_to_filename(dep_path)
  InstallFlow->>InstallFlow: materialize package from node.resolve_result
  InstallFlow->>InstallFlow: recurse on node.children(alias→dep_path)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • pnpm/pnpm#11768: Touches virtual-store naming/shortening logic used by dep_path_to_filename and related helpers.
  • pnpm/pnpm#11760: Related refactors to the no-lockfile install flow and resolver wiring.

"I hop through trees and join the peers,
I wrap their names and shorten fears,
From tree to graph, each path is cast,
Install proceeds — the lot stands fast,
Rabbit claps paws: it's landed at last!"

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'peer-dependency resolution stage' clearly and directly summarizes the main objective: implementing peer-dependency resolution into the pacquet resolver.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch peers-resolution

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.

Ports pnpm's `resolvePeers` algorithm to pacquet's `resolving-deps-resolver`
crate and wires it into `install_without_lockfile`. The depPath-keyed
`DependenciesGraph` produced by the new stage replaces the flat `(name, version)`
keying the install pass used to drive virtual-store slot names from.

New `pacquet-deps-path` crate ports the `@pnpm/deps.path` helpers
(`createPeerDepGraphHash`, `depPathToFilename`, balanced-paren suffix scan).

`ResolvedTree` now carries both the flat dedup map (`packages`) and the
per-occurrence tree (`dependencies_tree`, keyed by a new `NodeId`) so two
parents sharing the same package can compute different peer suffixes — the
whole point of the stage. Cycles in the tree pass are broken by skipping the
cycled edge entirely (matches upstream's `parentIdsContainSequence` gate in
`buildTree`), and peer-resolution falls back to `name@version` peer-ids when
re-entering an in-progress node.

Three upstream optimisations are intentionally not ported in this slice:
`peersCache`, the `purePkgs` fast path, and `graph-cycles`-driven async
deferment. Each is correctness-preserving — the algorithm produces the same
depPaths, just without the short-circuits. `autoInstallPeers` keeps its existing
"fold peers into the regular walk" behavior until the full `hoistPeers`
algorithm lands.
@zkochan zkochan force-pushed the peers-resolution branch from a8d109c to 9b86b79 Compare May 20, 2026 14:20
@codecov-commenter

codecov-commenter commented May 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.08955% with 53 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.06%. Comparing base (c068720) to head (293b4a0).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
...rates/resolving-deps-resolver/src/resolve_peers.rs 91.01% 30 Missing ⚠️
...lving-deps-resolver/src/resolve_dependency_tree.rs 89.42% 11 Missing ⚠️
pacquet/crates/deps-path/src/dep_path.rs 75.00% 3 Missing ⚠️
pacquet/crates/deps-path/src/suffix_index.rs 95.83% 3 Missing ⚠️
...quet/crates/resolving-deps-resolver/src/node_id.rs 50.00% 3 Missing ⚠️
...es/package-manager/src/install_without_lockfile.rs 92.30% 2 Missing ⚠️
...cquet/crates/deps-path/src/dep_path_to_filename.rs 98.41% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #11774      +/-   ##
==========================================
+ Coverage   89.98%   90.06%   +0.08%     
==========================================
  Files         155      163       +8     
  Lines       18114    18851     +737     
==========================================
+ Hits        16299    16979     +680     
- Misses       1815     1872      +57     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

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

@github-actions

github-actions Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor

Micro-Benchmark Results

Linux

group                          main                                   pr
-----                          ----                                   --
tarball/download_dependency    1.02      7.7±0.21ms   562.4 KB/sec    1.00      7.6±0.32ms   572.0 KB/sec

@github-actions

github-actions Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor

Integrated-Benchmark Report (Linux)

Scenario: Frozen Lockfile

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 2.362 ± 0.059 2.274 2.439 1.00
pacquet@main 2.378 ± 0.077 2.284 2.519 1.01 ± 0.04
pnpm 4.586 ± 0.035 4.540 4.643 1.94 ± 0.05
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 2.3619910438999994,
      "stddev": 0.05875614954362005,
      "median": 2.3778716586,
      "user": 2.7658078,
      "system": 3.5840501799999998,
      "min": 2.2740831501,
      "max": 2.4392318411,
      "times": [
        2.4392318411,
        2.3294963320999997,
        2.4034802841,
        2.2740831501,
        2.4345233990999997,
        2.3773978221,
        2.2781506491,
        2.3783454951,
        2.3240698991,
        2.3811315670999997
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 2.3777274775999997,
      "stddev": 0.07650174422987759,
      "median": 2.3526554591,
      "user": 2.7748862999999995,
      "system": 3.6055872800000004,
      "min": 2.2842781560999996,
      "max": 2.5192480411,
      "times": [
        2.3958508051,
        2.5192480411,
        2.2842781560999996,
        2.3310021660999998,
        2.3495459491,
        2.3415241251,
        2.3557649691,
        2.4165260831,
        2.4837272910999997,
        2.2998071900999997
      ]
    },
    {
      "command": "pnpm",
      "mean": 4.586463266600001,
      "stddev": 0.0345219472581763,
      "median": 4.5831092481,
      "user": 7.7825982,
      "system": 4.0517433800000004,
      "min": 4.5397146251,
      "max": 4.6430763501,
      "times": [
        4.5842393671,
        4.6105880451,
        4.5819791291000005,
        4.5963001271,
        4.632895667100001,
        4.6430763501,
        4.5397146251,
        4.549436662100001,
        4.5598536761,
        4.566549017100001
      ]
    }
  ]
}

Scenario: Frozen Lockfile (Hot Cache)

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 721.0 ± 72.6 679.7 864.7 1.04 ± 0.13
pacquet@main 693.8 ± 47.9 662.2 826.9 1.00
pnpm 2510.1 ± 128.4 2369.9 2777.0 3.62 ± 0.31
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 0.7210467862599999,
      "stddev": 0.07264737613580068,
      "median": 0.6881882636600001,
      "user": 0.39082986000000003,
      "system": 1.58393956,
      "min": 0.67966380616,
      "max": 0.86468975416,
      "times": [
        0.86468975416,
        0.68365345416,
        0.68598981516,
        0.68534557816,
        0.67978132816,
        0.85210580216,
        0.67966380616,
        0.69710837116,
        0.69038671216,
        0.69174324116
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 0.69384038926,
      "stddev": 0.04787643553817553,
      "median": 0.68084482016,
      "user": 0.36675095999999996,
      "system": 1.5812649600000004,
      "min": 0.66218540016,
      "max": 0.82687791316,
      "times": [
        0.70046749416,
        0.66720963316,
        0.82687791316,
        0.67508631316,
        0.68165364516,
        0.66218540016,
        0.68222428416,
        0.6856079591600001,
        0.67705525516,
        0.68003599516
      ]
    },
    {
      "command": "pnpm",
      "mean": 2.51014631436,
      "stddev": 0.12840981946638252,
      "median": 2.5105569446600002,
      "user": 2.99844576,
      "system": 2.22757086,
      "min": 2.36985144216,
      "max": 2.77695256116,
      "times": [
        2.39219830016,
        2.48997537916,
        2.36985144216,
        2.77695256116,
        2.38651660916,
        2.40535117516,
        2.56992493416,
        2.53113851016,
        2.57854545616,
        2.60100877616
      ]
    }
  ]
}

zkochan added 2 commits May 20, 2026 16:56
- Dylint perfectionist::single-letter-let-binding: rename `i` → `cursor`
  in `suffix_index.rs`.
- Dylint perfectionist::macro-trailing-comma: drop trailing comma in the
  `dep_path_to_filename` `file:` test.
- rustdoc broken-intra-doc-links: link `[`DependenciesTree`]` to the
  newly-exported `pacquet_resolving_deps_resolver::DependenciesTree`
  type alias and downgrade the `pacquet_lockfile::PkgNameVerPeer` mention
  in `pacquet-deps-path`'s module doc to plain text (the crate
  deliberately doesn't depend on `pacquet-lockfile`).
- rustdoc fn-vs-mod ambiguity: prefix [`resolve_dependency_tree`],
  [`resolve_peers`], and [`create_peer_dep_graph_hash`] doc-links with
  `fn@` so they bind to the function rather than the same-named module.
- typos: rename `unparseable` → `unparsable` in a test name.
- `derive_ordering`: reorder `#[derive(...)]` on `DepPath` and `NodeId`
  to match the configured `prefix_then_alphabetical` style — `PartialOrd,
  Ord, Hash` rather than `Hash, PartialOrd, Ord`. See `dylint.toml`'s
  `[perfectionist::derive_ordering]` prefix list for the canonical order.
- `single_letter_function_param`: rename `s` → `value` on
  `impl From<String> for DepPath`.
@zkochan zkochan marked this pull request as ready for review May 20, 2026 15:19
@qodo-code-review

Copy link
Copy Markdown

Review Summary by Qodo

Implement peer-dependency resolution stage with depPath-keyed graph

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Ports pnpm's peer-dependency resolution algorithm to pacquet
• New pacquet-deps-path crate with depPath string manipulation helpers
• Refactors ResolvedTree to carry per-occurrence tree keyed by NodeId
• Implements resolve_peers stage producing depPath-keyed DependenciesGraph
• Wires peer resolution into install_without_lockfile install pass
Diagram
flowchart LR
  A["resolve_dependency_tree<br/>per-occurrence tree"] --> B["resolve_peers<br/>match peer requirements"]
  B --> C["DependenciesGraph<br/>depPath-keyed"]
  C --> D["install_subtree<br/>virtual-store slots"]
  E["pacquet-deps-path<br/>string helpers"] -.-> B
  E -.-> D

Loading

File Changes

1. pacquet/crates/deps-path/src/lib.rs ✨ Enhancement +25/-0

New crate for depPath string manipulation

pacquet/crates/deps-path/src/lib.rs


2. pacquet/crates/deps-path/src/peer_id.rs ✨ Enhancement +30/-0

PeerId union for peer suffix construction

pacquet/crates/deps-path/src/peer_id.rs


3. pacquet/crates/deps-path/src/create_peer_dep_graph_hash.rs ✨ Enhancement +93/-0

Build peer suffix from peer identifiers

pacquet/crates/deps-path/src/create_peer_dep_graph_hash.rs


View more (14)
4. pacquet/crates/deps-path/src/dep_path_to_filename.rs ✨ Enhancement +114/-0

Convert depPath to filesystem-safe directory name

pacquet/crates/deps-path/src/dep_path_to_filename.rs


5. pacquet/crates/deps-path/src/suffix_index.rs ✨ Enhancement +135/-0

Parse balanced-paren peer and patch-hash suffixes

pacquet/crates/deps-path/src/suffix_index.rs


6. pacquet/crates/deps-path/Cargo.toml ⚙️ Configuration changes +17/-0

New crate manifest and dependencies

pacquet/crates/deps-path/Cargo.toml


7. pacquet/crates/resolving-deps-resolver/src/lib.rs ✨ Enhancement +45/-18

Add peer resolution module and exports

pacquet/crates/resolving-deps-resolver/src/lib.rs


8. pacquet/crates/resolving-deps-resolver/src/node_id.rs ✨ Enhancement +29/-0

Per-occurrence node identifier with monotonic counter

pacquet/crates/resolving-deps-resolver/src/node_id.rs


9. pacquet/crates/resolving-deps-resolver/src/resolved_tree.rs ✨ Enhancement +96/-16

Refactor tree to carry per-occurrence nodes and peer data

pacquet/crates/resolving-deps-resolver/src/resolved_tree.rs


10. pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs ✨ Enhancement +181/-59

Extract peer dependencies and allocate per-occurrence NodeIds

pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs


11. pacquet/crates/resolving-deps-resolver/src/dependencies_graph.rs ✨ Enhancement +114/-0

DepPath-keyed graph and peer issue types

pacquet/crates/resolving-deps-resolver/src/dependencies_graph.rs


12. pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs ✨ Enhancement +584/-0

Peer resolution algorithm with issue collection

pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs


13. pacquet/crates/resolving-deps-resolver/src/tests.rs 🧪 Tests +207/-3

Add peer resolution test cases

pacquet/crates/resolving-deps-resolver/src/tests.rs


14. pacquet/crates/resolving-deps-resolver/Cargo.toml Dependencies +2/-0

Add deps-path and node-semver dependencies

pacquet/crates/resolving-deps-resolver/Cargo.toml


15. pacquet/crates/package-manager/src/install_without_lockfile.rs ✨ Enhancement +59/-27

Wire peer resolution into install pass

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


16. pacquet/crates/package-manager/Cargo.toml Dependencies +1/-0

Add deps-path dependency

pacquet/crates/package-manager/Cargo.toml


17. Cargo.toml ⚙️ Configuration changes +1/-0

Register new deps-path crate workspace member

Cargo.toml


Grey Divider

ⓘ You are approaching your monthly quota for Qodo. Upgrade your plan

Qodo Logo

@qodo-code-review

qodo-code-review Bot commented May 20, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (2)

Context used
✅ Compliance rules (platform): 50 rules

Grey Divider


Action required

1. Peer issues logged via tracing::warn! 📘 Rule violation ≡ Correctness
Description
The peer-resolution path emits a tracing::warn! message (pacquet::install) instead of using
pnpm-compatible reporter/NDJSON logging, introducing output that can diverge from pnpm’s logging
contract. This risks breaking consumers that rely on pnpm-style NDJSON event streams and expected
channel/payload conventions.
Code

pacquet/crates/package-manager/src/install_without_lockfile.rs[R257-268]

Evidence
The checklist requires pacquet ports to mirror pnpm logging channels/mechanisms and payload
structure. The new code path logs peer issues via tracing::warn! (not via the pnpm-compatible
reporter event stream), adding a divergent emission site and message.

Rule 713458: Mirror pnpm logging behavior when porting functions to pacquet
pacquet/crates/package-manager/src/install_without_lockfile.rs[257-268]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Peer dependency issues are reported using `tracing::warn!`, which does not follow the pnpm-compatible logging mechanism expected when porting pnpm behavior to pacquet.

## Issue Context
This introduces a new logging emission site (`target: pacquet::install`) rather than a pnpm-aligned event/channel, which can cause divergence and potentially interfere with NDJSON reporter consumers.

## Fix Focus Areas
- pacquet/crates/package-manager/src/install_without_lockfile.rs[257-268]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. dep_path_to_filename_unescaped may panic 📘 Rule violation ⚙ Maintainability
Description
dep_path_to_filename_unescaped() slices trimmed.as_bytes()[1..] without guarding short inputs,
so certain inputs (e.g. empty string or a single-byte depPath) can panic. The public API docs also
don’t state any precondition or panic behavior for this case.
Code

pacquet/crates/deps-path/src/dep_path_to_filename.rs[R44-50]

Evidence
The compliance checklist requires doc comments to capture preconditions/panic conditions when
applicable. The implementation currently has an unchecked slice that can panic for short inputs, but
the docs do not describe any such precondition or panic behavior.

Rule 713450: Doc comments must describe the function contract, not its implementation
pacquet/crates/deps-path/src/dep_path_to_filename.rs[44-50]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`dep_path_to_filename_unescaped()` can panic due to unchecked slicing (`trimmed.as_bytes()[1..]`) when `trimmed.len() < 2`.

## Issue Context
This function is part of the new public depPath-to-filename conversion API. If unexpected/invalid depPath strings reach this function, the process may panic instead of safely returning a value or reporting an error.

## Fix Focus Areas
- pacquet/crates/deps-path/src/dep_path_to_filename.rs[44-60]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Dropped unresolved peer edges 🐞 Bug ≡ Correctness
Description
resolve_peers only adds peer edges when the peer NodeId already has an entry in
node_dep_paths, so peers resolved against sibling/direct packages that haven’t been visited yet
are omitted from DependenciesGraphNode.children and may also degrade to name@version peer-ids.
Code

pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[R371-378]

Evidence
walk() seeds ParentRefs with *all* direct deps, then resolves direct deps sequentially; a
subtree can resolve a peer to an unvisited sibling NodeId. Peer edges are only added if
node_dep_paths already contains that NodeId, and the installer recurses strictly via
node.children, so omitted peer edges mean missing peer installation/linking for that slot.

pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[166-175]
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[183-199]
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[371-378]
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[486-500]
pacquet/crates/package-manager/src/install_without_lockfile.rs[489-500]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`resolve_peers` may resolve a peer to a `NodeId` whose depPath hasn't been computed yet (e.g., a sibling direct dependency, because `build_importer_parents` includes all directs but `walk()` resolves them sequentially). In that case, the peer edge is skipped entirely (`if let Some(peer_dep_path)`) and `build_peer_id` can fall back to `PeerId::Pair`, producing an incomplete install graph (missing peer links) and less-specific depPaths.

### Issue Context
The installer recurses exclusively via `DependenciesGraphNode.children`, so missing peer edges means the peer package won't be installed/linked into that slot.

### Fix Focus Areas
- pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[166-199]
- pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[344-378]
- pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[486-500]

### Suggested fix approach
- When building `peer_ids` and `graph_children`, ensure each `peer_node_id` has a computed depPath before use. One practical approach:
 - Add a helper like `ensure_dep_path(peer_node_id, parent_refs, parent_chain_names, depth)` that calls `self.resolve_node(peer_node_id, parent_refs, parent_chain_names, depth)` if `node_dep_paths` is missing.
 - Use that to (a) always insert the peer edge into `graph_children` and (b) always prefer `PeerId::DepPath` when possible.
- Keep the cycle behavior (`in_progress`) as-is to avoid infinite recursion.
- Add a test where package `A` resolves a peer against sibling direct dep `B` while `B` is visited later (e.g., manifest order `{"b": ..., "a": ...}`), and assert that `A`’s graph node includes `B` in `children`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
4. Aliased child peer misfilter 🐞 Bug ≡ Correctness
Description
Internal/external peer filtering checks tree_node.children.contains_key(peer_alias), but
children is keyed by install alias while peers can be keyed by real package name (due to
dual-keyed ParentRefs), so a peer resolved to an aliased child can be incorrectly treated as
external and propagated into ancestor suffixes.
Code

pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[R298-305]

Evidence
The peer resolver records parents by both install alias and real package name, but
DependenciesTreeNode.children is built from dep.alias (install alias). Therefore checking
internal-ness via children.contains_key(peer_alias) is inconsistent and fails for npm-alias
scenarios where peer_alias is the real name but the child key is the alias.

pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[246-276]
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[290-305]
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[425-428]
pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs[212-231]
pacquet/crates/package-manifest/src/lib.rs[170-179]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`resolve_peers` tries to avoid propagating peers that are actually satisfied by one of the current node's own children, but it detects this by comparing the peer's *name key* (`peer_alias`) against `tree_node.children` keys. This breaks when a child is installed under an npm alias (child edge key is the alias, while the resolved peer key can be the real package name), causing that peer to be treated as external and incorrectly included in ancestor peer suffix propagation.

### Issue Context
This mismatch is possible because:
- Dependency keys can be npm aliases (directory name differs from registry package name).
- `ParentRefs` is intentionally dual-keyed by alias and real name.
- `DependenciesTreeNode.children` is keyed by the alias chosen during tree building.

### Fix Focus Areas
- pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[246-305]
- pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs[422-428]
- pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs[212-231]
- pacquet/crates/package-manifest/src/lib.rs[170-179]

### Suggested fix approach
- Change the “is this peer one of my children?” check to compare by `NodeId` (or by `resolved_package_id`) rather than by the alias string:
 - e.g. `if tree_node.children.values().any(|id| id == &peer_node_id) { continue; }`
 - and similarly for the `external_to_report` filter.
- Add a test using an npm-alias dependency (`"foo": "npm:bar@1.0.0"`) where another package has `peerDependencies: { "bar": "*" }` and ensure the peer is treated as internal (not propagated as external).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ You are approaching your monthly quota for Qodo. Upgrade your plan

Qodo Logo

@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: 5

🧹 Nitpick comments (3)
pacquet/crates/resolving-deps-resolver/src/dependencies_graph.rs (1)

7-30: ⚡ Quick win

Make DepPath’s inner string private.

pub struct DepPath(pub String) lets callers bypass the brand entirely, which undercuts the type-safety this wrapper is meant to add and makes future validation impossible to enforce. The existing From<String> and as_str() surface already covers normal construction and access.

Suggested change
-#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
-pub struct DepPath(pub String);
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct DepPath(String);

As per coding guidelines, "Declare a newtype wrapper for branded string types. Do not collapse the brand into a plain String or &str."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pacquet/crates/resolving-deps-resolver/src/dependencies_graph.rs` around
lines 7 - 30, The DepPath newtype currently exposes its inner String (pub struct
DepPath(pub String)), which breaks the branding; change it to a private field
(pub struct DepPath(String)) so callers cannot bypass the brand, keep the
existing impls (impl From<String> for DepPath, as_str, and Display) as-is since
they live in the same module and will still construct and access the inner
String; verify public API still uses DepPath::from(...) or as_str() for access.
pacquet/crates/resolving-deps-resolver/src/lib.rs (1)

56-59: ⚡ Quick win

Re-export ResolvePeersResult with the new root API.

resolve_peers is now exposed from the crate root, but its concrete return type is still trapped in a private module. Callers can use inference, but they can't spell the type in their own signatures from the root module.

Suggested fix
-pub use resolve_peers::{ResolvePeersOptions, resolve_peers};
+pub use resolve_peers::{ResolvePeersOptions, ResolvePeersResult, resolve_peers};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pacquet/crates/resolving-deps-resolver/src/lib.rs` around lines 56 - 59, The
root module re-exports resolve_peers and ResolvePeersOptions but not the
concrete return type ResolvePeersResult, preventing callers from naming the
type; export ResolvePeersResult from the crate root by adding it to the existing
pub use line (pub use resolve_peers::{ResolvePeersOptions, ResolvePeersResult,
resolve_peers};) and confirm that ResolvePeersResult is declared pub in the
resolve_peers module so consumers can reference it in signatures.
pacquet/crates/resolving-deps-resolver/src/tests.rs (1)

314-327: ⚡ Quick win

Assert the graph edge, not just the depPath suffix.

This test still passes if react-dom gets the right depPath string but its graph.children["react"] edge is missing. Please also assert the peer child edge itself, ideally with the manifest order reversed so the peer target is visited later.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pacquet/crates/resolving-deps-resolver/src/tests.rs` around lines 314 - 327,
The test currently only asserts the depPath string for "react-dom" but not that
the graph edge to its peer "react" exists; update the test after computing
result (from resolve_peers and ResolvePeersOptions::default()) to locate the
react-dom node (e.g. DepPath::from("react-dom@18.0.0(react@18.0.0)")) and assert
that result.graph.children[react_dom_dep_path] contains the react node (e.g.
DepPath::from("react@18.0.0")), and also reverse the manifest input order in the
test so the peer target is visited later to exercise the edge creation logic.
Ensure you still keep the existing asserts on
result.direct_dependencies_by_alias and peer_dependency_issues.
🤖 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/deps-path/src/dep_path_to_filename.rs`:
- Around line 44-50: The current logic slices trimmed.as_bytes()[1..] which will
panic for empty or single-byte inputs; before creating after_first, guard that
trimmed has length > 1 (e.g., check trimmed.len() > 1) and handle the
short/empty-case by returning trimmed.to_string() (or the appropriate fallback)
so the position search only runs on valid slices; update the code that computes
after_first and the subsequent position match (variables trimmed, after_first,
rel) to use this length check to avoid out-of-bounds slicing.

In `@pacquet/crates/deps-path/src/peer_id.rs`:
- Around line 11-14: The PeerId enum currently uses a raw String in the DepPath
variant which loses the branded type-safety; introduce a new DepPath newtype
(e.g., pub struct DepPath(String)) and replace the enum variant from
DepPath(String) to DepPath(DepPath); update any constructors, pattern matches,
and usages of PeerId::DepPath, plus impls (From/Into/AsRef/Display/Debug) as
needed to preserve behavior and ergonomics while keeping the brand encapsulated.

In `@pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs`:
- Around line 264-273: extract_children currently appends peerDependencies
unconditionally which conflicts with extract_peer_dependencies that treats names
from dependencies and optionalDependencies as "own", causing child edges to be
lost or overwritten; update extract_children (and its use of collect_deps) to
first collect both "dependencies" and "optionalDependencies" into out (or at
least ensure those names are considered "own"), then when auto_install_peers is
true either collect "peerDependencies" after that or filter peer entries by
skipping any names already present in out (so peer entries do not override
existing children in the BTreeMap by alias); refer to extract_children,
collect_deps, extract_peer_dependencies, peerDependenciesWithoutOwn, children
and BTreeMap in your change.

In `@pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs`:
- Around line 373-378: The code currently skips inserting peer edges when
self.node_dep_paths.get(peer_node_id) is None, dropping edges for peers that
point to nodes not yet walked; change the loop in resolve_peers so that if
node_dep_paths lacks peer_node_id you create a dep-path for the peer and still
insert the alias into graph_children — e.g., check
self.node_dep_paths.get(peer_node_id).cloned().or_else(||
Some(self.build_peer_id(peer_node_id))) and insert that value for peer_alias
into graph_children (referencing graph_children, child_dep_paths,
all_resolved_peers, self.node_dep_paths.get, peer_alias, peer_node_id, and
build_peer_id).
- Around line 555-565: The helper satisfies_with_prereleases currently uses
parsed_version.satisfies(&parsed_range) which excludes prereleases; update it to
call the semver prerelease-inclusive API instead (use the crate's
satisfies_with_prerelease variant) so prerelease versions like 18.0.0-rc.1 match
^18.0.0; specifically, inside satisfies_with_prereleases replace the final
parsed_version.satisfies(&parsed_range) call with the appropriate
satisfies_with_prerelease call (passing parsed_version and parsed_range) and set
include_prerelease = true so parsed_range/...satisfies_with_prerelease(...,
true) is used.

---

Nitpick comments:
In `@pacquet/crates/resolving-deps-resolver/src/dependencies_graph.rs`:
- Around line 7-30: The DepPath newtype currently exposes its inner String (pub
struct DepPath(pub String)), which breaks the branding; change it to a private
field (pub struct DepPath(String)) so callers cannot bypass the brand, keep the
existing impls (impl From<String> for DepPath, as_str, and Display) as-is since
they live in the same module and will still construct and access the inner
String; verify public API still uses DepPath::from(...) or as_str() for access.

In `@pacquet/crates/resolving-deps-resolver/src/lib.rs`:
- Around line 56-59: The root module re-exports resolve_peers and
ResolvePeersOptions but not the concrete return type ResolvePeersResult,
preventing callers from naming the type; export ResolvePeersResult from the
crate root by adding it to the existing pub use line (pub use
resolve_peers::{ResolvePeersOptions, ResolvePeersResult, resolve_peers};) and
confirm that ResolvePeersResult is declared pub in the resolve_peers module so
consumers can reference it in signatures.

In `@pacquet/crates/resolving-deps-resolver/src/tests.rs`:
- Around line 314-327: The test currently only asserts the depPath string for
"react-dom" but not that the graph edge to its peer "react" exists; update the
test after computing result (from resolve_peers and
ResolvePeersOptions::default()) to locate the react-dom node (e.g.
DepPath::from("react-dom@18.0.0(react@18.0.0)")) and assert that
result.graph.children[react_dom_dep_path] contains the react node (e.g.
DepPath::from("react@18.0.0")), and also reverse the manifest input order in the
test so the peer target is visited later to exercise the edge creation logic.
Ensure you still keep the existing asserts on
result.direct_dependencies_by_alias and peer_dependency_issues.
🪄 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: e4969e3e-694a-428b-980f-ae29e8016750

📥 Commits

Reviewing files that changed from the base of the PR and between 2061c55 and 32f22bc.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (17)
  • Cargo.toml
  • pacquet/crates/deps-path/Cargo.toml
  • pacquet/crates/deps-path/src/create_peer_dep_graph_hash.rs
  • pacquet/crates/deps-path/src/dep_path_to_filename.rs
  • pacquet/crates/deps-path/src/lib.rs
  • pacquet/crates/deps-path/src/peer_id.rs
  • pacquet/crates/deps-path/src/suffix_index.rs
  • pacquet/crates/package-manager/Cargo.toml
  • pacquet/crates/package-manager/src/install_without_lockfile.rs
  • pacquet/crates/resolving-deps-resolver/Cargo.toml
  • pacquet/crates/resolving-deps-resolver/src/dependencies_graph.rs
  • pacquet/crates/resolving-deps-resolver/src/lib.rs
  • pacquet/crates/resolving-deps-resolver/src/node_id.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs
  • pacquet/crates/resolving-deps-resolver/src/resolved_tree.rs
  • pacquet/crates/resolving-deps-resolver/src/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). (2)
  • GitHub Check: ubuntu-latest / Node.js 24 / Test
  • GitHub Check: Run benchmark on ubuntu-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/resolving-deps-resolver/src/node_id.rs
  • pacquet/crates/deps-path/src/peer_id.rs
  • pacquet/crates/deps-path/src/lib.rs
  • pacquet/crates/deps-path/src/suffix_index.rs
  • pacquet/crates/deps-path/src/dep_path_to_filename.rs
  • pacquet/crates/deps-path/src/create_peer_dep_graph_hash.rs
  • pacquet/crates/resolving-deps-resolver/src/dependencies_graph.rs
  • pacquet/crates/resolving-deps-resolver/src/lib.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs
  • pacquet/crates/resolving-deps-resolver/src/tests.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs
  • pacquet/crates/resolving-deps-resolver/src/resolved_tree.rs
  • pacquet/crates/package-manager/src/install_without_lockfile.rs
🔇 Additional comments (10)
Cargo.toml (1)

34-34: LGTM!

pacquet/crates/deps-path/Cargo.toml (1)

1-17: LGTM!

pacquet/crates/deps-path/src/create_peer_dep_graph_hash.rs (1)

1-33: LGTM!

Also applies to: 35-93

pacquet/crates/deps-path/src/dep_path_to_filename.rs (1)

25-43: LGTM!

Also applies to: 52-114

pacquet/crates/deps-path/src/lib.rs (1)

1-25: LGTM!

pacquet/crates/deps-path/src/suffix_index.rs (1)

1-81: LGTM!

Also applies to: 83-135

pacquet/crates/package-manager/Cargo.toml (1)

32-32: LGTM!

pacquet/crates/resolving-deps-resolver/Cargo.toml (1)

14-25: LGTM!

pacquet/crates/resolving-deps-resolver/src/resolved_tree.rs (1)

1-124: LGTM!

pacquet/crates/resolving-deps-resolver/src/node_id.rs (1)

1-29: LGTM!

Comment thread pacquet/crates/deps-path/src/dep_path_to_filename.rs
Comment thread pacquet/crates/deps-path/src/peer_id.rs
Comment thread pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs
Comment thread pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs Outdated
Comment thread pacquet/crates/package-manager/src/install_without_lockfile.rs
Comment thread pacquet/crates/deps-path/src/dep_path_to_filename.rs
Comment thread pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs
Comment thread pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs
zkochan added 2 commits May 20, 2026 21:39
Round of correctness fixes flagged by CodeRabbit and qodo-code-review on
#11774.

- **`dep_path_to_filename_unescaped`**: guard against empty / single-byte
  input. The previous `trimmed.as_bytes()[1..]` slice panicked when
  `trimmed.len() < 2`; the function now short-circuits to
  `trimmed.to_string()` in that case, mirroring upstream's `indexOf` with
  `fromIndex = 1` returning -1 on out-of-range scan.

- **`extract_children` / `extract_peer_dependencies` mismatch**: the
  child walker added every `peerDependencies` entry when
  `auto_install_peers` was on, but the peer-resolution stage's
  `peerDependenciesWithoutOwn` filter skips peer names also in
  `dependencies` / `optionalDependencies`. `BTreeMap` collection of the
  result by alias could silently drop the optional edge for a name that
  appeared in both. Fix: collect `optionalDependencies` into children
  too, and dedupe peer entries against own deps before appending.

- **Dropped peer edges in `graph_children`**: when a peer pointed at a
  later sibling direct dep (e.g., manifest order `{ react-dom: …,
  react: … }`), the peer's `DepPath` wasn't in `node_dep_paths` yet at
  the time the parent's `graph_children` was built, so the symlink edge
  was silently dropped. Install would then walk react-dom's slot without
  finding react. Add `pending_peer_edges` + a `patch_pending_peer_edges`
  post-pass that runs after every direct dep is walked, with regression
  test.

- **Aliased child peer misfilter**: the "is this peer one of my own
  children?" check compared the peer alias against `tree_node.children`
  keys, but `children` is keyed by install alias while peers can match
  by real name via the dual-keyed `ParentRefs`. Switch both filter
  sites to compare by `NodeId` so an npm-aliased child satisfying a
  peer is correctly classified as internal.

- **`DepPath` newtype consolidation**: `PeerId::DepPath(String)`
  collapsed the depPath brand. Move the `DepPath` newtype from
  `resolving-deps-resolver::dependencies_graph` into the lower-level
  `pacquet-deps-path` crate so the `PeerId` enum carries the branded
  type instead of `String`. `resolving-deps-resolver` re-exports
  `DepPath` from `pacquet_deps_path` to keep existing imports working.

- **Prerelease-tolerant semver match**: `node-semver`'s `Range::satisfies`
  rejects prereleases against non-prerelease comparators (`18.0.0-rc.1`
  vs `^18.0.0` returns `false`). Add a fallback in
  `satisfies_with_prereleases`: if the straight check fails and the
  candidate is a prerelease, retry against the stripped
  `MAJOR.MINOR.PATCH` base. Approximates Yarn's
  `satisfiesWithPrereleases` for the cases pnpm cares about without
  importing the full per-comparator algorithm.

The `tracing::warn!` peer-issue emission flagged by qodo and the
deeper prerelease semantics gap (per-comparator) remain documented
follow-ups; the slice's `resolve_peers.rs` module doc lists what's
intentionally not ported in this PR.
Three sites where the previous edits left non-canonical wrapping:
- `create_peer_dep_graph_hash.rs` test calls now fit one line.
- `lib.rs` re-export ordering (alphabetical).
- `tests.rs` whitespace.

CI Format check is `cargo fmt --all -- --check`; the local pre-push
hook didn't catch these because the edits landed via `Edit` without a
follow-up `cargo fmt`.
…added

`cargo fmt` collapsed the regression test's `assert_eq!` onto one line
and kept the trailing comma, which tripped the same
`perfectionist::macro_trailing_comma` rule that #11774's earlier commit
fixed elsewhere. The lint forbids a trailing comma on single-line macro
calls; rustfmt leaves it alone. Drop the comma to satisfy both.
@zkochan zkochan merged commit a1f91e1 into main May 20, 2026
25 checks passed
@zkochan zkochan deleted the peers-resolution branch May 20, 2026 20:48
zkochan added a commit that referenced this pull request May 20, 2026
…gResolutionId

The peer-resolution stage landed on main (#11774) using the old
`.id.name` / `.id.suffix` field accesses. Round-trip the opaque id
back through `PkgNameVer` at the five call sites via a small
`npm_pkg_name_ver` helper. Pulls `pacquet-lockfile` from dev-deps to
regular deps.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants