Skip to content

feat(pacquet): port catalogs (types, protocol-parser, resolver, config)#11787

Merged
zkochan merged 5 commits into
mainfrom
catalogs
May 21, 2026
Merged

feat(pacquet): port catalogs (types, protocol-parser, resolver, config)#11787
zkochan merged 5 commits into
mainfrom
catalogs

Conversation

@zkochan

@zkochan zkochan commented May 20, 2026

Copy link
Copy Markdown
Member

Summary

Ports the four upstream catalogs/* packages to pacquet:

  • pacquet-catalogs-typesCatalog / Catalogs map aliases plus the DEFAULT_CATALOG_NAME constant.
  • pacquet-catalogs-protocol-parserparse_catalog_protocol; folds the catalog: shorthand into "default" to match upstream.
  • pacquet-catalogs-resolverresolve_from_catalog with the four upstream PnpmError codes:
    • ERR_PNPM_CATALOG_ENTRY_NOT_FOUND_FOR_SPEC
    • ERR_PNPM_CATALOG_ENTRY_INVALID_RECURSIVE_DEFINITION
    • ERR_PNPM_CATALOG_ENTRY_INVALID_WORKSPACE_SPEC
    • ERR_PNPM_CATALOG_ENTRY_INVALID_SPEC
  • pacquet-catalogs-configget_catalogs_from_workspace_manifest plus the ERR_PNPM_INVALID_CATALOGS_CONFIGURATION mutual-exclusion check between catalog: and catalogs.default.

pacquet-workspace's WorkspaceManifest now actually deserializes the catalog and catalogs fields — previously they were silently dropped at parse time.

Tests are 1:1 ports of the upstream Jest suites:

Notes

  • The upstream matchCatalogResolveResult visitor is intentionally not ported. Rust's exhaustive match on CatalogResolutionResult gives the same exhaustiveness guarantee for free.
  • The resolver is not yet wired into the install path. Pacquet's resolver chain doesn't have a catalog-protocol seam yet, and installing/deps-installer (the upstream caller) hasn't been ported. The crates are ready for that next step.

Test plan

  • cargo nextest run -p pacquet-catalogs-types -p pacquet-catalogs-protocol-parser -p pacquet-catalogs-resolver -p pacquet-catalogs-config -p pacquet-workspace
  • just check
  • just lint
  • just test (full 1736-test suite, registry mock running)
  • cargo fmt

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

Summary by CodeRabbit

  • New Features

    • Workspace catalog support for defining reusable catalog entries and a well-known default catalog
    • Support for the catalog: protocol so direct dependencies can reference workspace catalogs
    • Installer and resolver now consume workspace catalogs during dependency resolution
  • Bug Fixes / Validation

    • Better validation and explicit diagnostics for catalog misconfigurations
  • Tests

    • New unit and end-to-end tests covering catalog parsing, protocol parsing, resolution, and installer behavior

Review Change Stack

@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented May 20, 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

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: 20bedce6-a878-446b-85ca-771bd1eee7d8

📥 Commits

Reviewing files that changed from the base of the PR and between 444fe23 and b90fd24.

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

📄 CodeRabbit inference engine (pacquet/AGENTS.md)

pacquet/**/*.rs: When porting a function that 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/cli/tests/install.rs
pacquet/**/{tests,test}/**/*.rs

📄 CodeRabbit inference engine (pacquet/AGENTS.md)

pacquet/**/{tests,test}/**/*.rs: Prefer real fixtures; reach for the dependency-injection seam only when they can't cover the branch. Most happy paths and error paths should be tested with tempfile::TempDir, the mocked registry, or an integration test that spawns the actual binary.
Use the DI seam — a capability trait on the Host provider, threaded as Sys: <Bounds> — only for branches a real fixture can't reach portably: filesystem error kinds, deterministic time, shared process-global state, or external-service happy paths.
Log before non-assert_eq! assertions, dbg! complex structures, skip logging for simple scalar assert_eq!. Follow the test-logging guidance in CODE_STYLE_GUIDE.md.
Tests must not be tolerant of missing build/runtime environment by silently returning early when a tool isn't found. If the test needs a tool, let it panic when the tool is absent.
Prefer #[cfg_attr(target_os = "windows", ignore = "...")] (or #[cfg(unix)]) over a runtime probe-and-skip helper for platform-locked tools.
Port relevant tests from pnpm when porting behavior. Matching test coverage is the easiest way to prove behavioral parity.
When porting behavior from pnpm, consult plans/TEST_PORTING.md before adding ported tests. Use allow_known_failure! at the not-yet-implemented boundary and update checkboxes as items land.

Files:

  • pacquet/crates/cli/tests/install.rs
🧠 Learnings (2)
📚 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/cli/tests/install.rs
📚 Learning: 2026-05-20T23:07:58.444Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11784
File: pacquet/crates/resolving-deps-resolver/src/hoist_peers.rs:120-133
Timestamp: 2026-05-20T23:07:58.444Z
Learning: When reviewing code in this pacquet Rust port, follow the upstream pnpm compatibility rule: only match pnpm’s behavior exactly. Do not propose review changes that intentionally deviate from pnpm’s documented/observed behavior, even if pnpm appears buggy. If you identify a real bug in pnpm behavior, the review should prioritize fixing it upstream in pnpm first, and avoid implementing a pnpm-behavior workaround here unless the same fix has already landed upstream.

Applied to files:

  • pacquet/crates/cli/tests/install.rs
🔇 Additional comments (1)
pacquet/crates/cli/tests/install.rs (1)

435-438: LGTM!


📝 Walkthrough

Walkthrough

Adds pnpm-style workspace catalog support: new catalog type crate, protocol parser, config normalizer, resolver crate, workspace manifest fields, install/resolver plumbing, and unit + end-to-end tests validating resolution and misconfiguration errors.

Changes

Catalog resolution support

Layer / File(s) Summary
Catalog type definitions and workspace setup
pacquet/crates/catalogs-types/Cargo.toml, pacquet/crates/catalogs-types/src/lib.rs, Cargo.toml, pacquet/crates/workspace/Cargo.toml
DEFAULT_CATALOG_NAME, Catalog, and Catalogs types added; workspace dependencies updated to include catalog crates.
Catalog protocol parsing
pacquet/crates/catalogs-protocol-parser/Cargo.toml, pacquet/crates/catalogs-protocol-parser/src/lib.rs, pacquet/crates/catalogs-protocol-parser/src/tests.rs
parse_catalog_protocol extracts/normalizes catalog: specifiers and maps empty remainders to the default catalog; unit tests added.
Workspace manifest catalog fields
pacquet/crates/workspace/src/manifest.rs, pacquet/crates/workspace/src/manifest/tests.rs
WorkspaceManifest gains optional catalog and catalogs serde fields; tests verify parsing of both top-level and named catalog shapes.
Catalog configuration normalization
pacquet/crates/catalogs-config/Cargo.toml, pacquet/crates/catalogs-config/src/lib.rs, pacquet/crates/catalogs-config/src/tests.rs
get_catalogs_from_workspace_manifest flattens catalog and catalogs into a single Catalogs map and enforces that default is defined at most once via InvalidCatalogsConfigurationError; tests cover merging and error cases.
Catalog resolution engine
pacquet/crates/catalogs-resolver/Cargo.toml, pacquet/crates/catalogs-resolver/src/lib.rs, pacquet/crates/catalogs-resolver/src/tests.rs
resolve_from_catalog looks up aliased entries, rejects recursive or forbidden (workspace:, file:, link:) specs, and returns structured Found/Misconfiguration/Unused outcomes; tests mirror pnpm cases.
Install pipeline catalog integration
pacquet/crates/package-manager/Cargo.toml, pacquet/crates/package-manager/src/install.rs, pacquet/crates/package-manager/src/install_without_lockfile.rs
Install reads workspace manifest, parses catalogs, adds InstallError variants for manifest/config failures, and threads catalogs into no-lockfile install flow.
Dependency resolver catalog application
pacquet/crates/resolving-deps-resolver/Cargo.toml, pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs, pacquet/crates/resolving-deps-resolver/src/resolve_importer.rs, pacquet/crates/resolving-deps-resolver/src/resolve_importer/tests.rs
ResolveImporterOptions gains catalogs; direct dependency specifiers are rewritten via resolve_catalog_specifiers before tree resolution; resolver surfaces catalog misconfiguration errors and tests validate rewrites and error messages.
End-to-end install tests
pacquet/crates/cli/tests/install.rs
Integration tests: one verifies catalog: specifier is resolved and installed from workspace catalog; another verifies missing catalog entry surfaces upstream error text.

Possibly related PRs

  • pnpm/pnpm#11760: Builds on the resolver pipeline and interacts with dependency-tree resolution foundations used here.
  • pnpm/pnpm#11665: Modifies the installer pipeline and Install::run, touching similar install flow code.

🎯 4 (Complex) | ⏱️ ~60 minutes

"🐰
Four crates sprout where manifests lie,
I nibble specs and watch them fly.
Parse, map, resolve — a tidy hop,
Catalogs land and installations stop.
Hooray, the garden's set to shop!"

🚥 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 clearly and specifically describes the main change: porting four catalog-related crates (types, protocol-parser, resolver, config) into the pacquet project, which aligns with the comprehensive scope of the PR.
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 catalogs

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.

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

🤖 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/catalogs-resolver/src/lib.rs`:
- Around line 156-167: The code currently treats any string before a ':' as a
protocol by using protocol_of_lookup =
catalog_lookup.split(':').next().unwrap_or(""), which incorrectly flags bare
values like "workspace" or "file" as protocols; change the detection to require
the explicit prefix form (e.g., "workspace:", "link:", "file:") by replacing the
split-based check with starts_with checks on catalog_lookup (e.g.,
catalog_lookup.starts_with("workspace:"), catalog_lookup.starts_with("link:") ||
catalog_lookup.starts_with("file:")) and update the branches that return
CatalogResolutionResult::Misconfiguration (CatalogResolutionMisconfiguration /
CatalogResolutionError::EntryInvalidWorkspaceSpec) and the matches!(...) branch
so they only run when the string actually has the protocol prefix.
🪄 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: e4723fb5-7ab4-4cb6-9237-d78e5660d024

📥 Commits

Reviewing files that changed from the base of the PR and between a8a8cbc and adb6df5.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (15)
  • Cargo.toml
  • pacquet/crates/catalogs-config/Cargo.toml
  • pacquet/crates/catalogs-config/src/lib.rs
  • pacquet/crates/catalogs-config/src/tests.rs
  • pacquet/crates/catalogs-protocol-parser/Cargo.toml
  • pacquet/crates/catalogs-protocol-parser/src/lib.rs
  • pacquet/crates/catalogs-protocol-parser/src/tests.rs
  • pacquet/crates/catalogs-resolver/Cargo.toml
  • pacquet/crates/catalogs-resolver/src/lib.rs
  • pacquet/crates/catalogs-resolver/src/tests.rs
  • pacquet/crates/catalogs-types/Cargo.toml
  • pacquet/crates/catalogs-types/src/lib.rs
  • pacquet/crates/workspace/Cargo.toml
  • pacquet/crates/workspace/src/manifest.rs
  • pacquet/crates/workspace/src/manifest/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: Code Coverage
  • 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/catalogs-protocol-parser/src/tests.rs
  • pacquet/crates/catalogs-config/src/tests.rs
  • pacquet/crates/catalogs-types/src/lib.rs
  • pacquet/crates/catalogs-resolver/src/tests.rs
  • pacquet/crates/catalogs-protocol-parser/src/lib.rs
  • pacquet/crates/workspace/src/manifest/tests.rs
  • pacquet/crates/workspace/src/manifest.rs
  • pacquet/crates/catalogs-resolver/src/lib.rs
  • pacquet/crates/catalogs-config/src/lib.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/catalogs-protocol-parser/src/tests.rs
  • pacquet/crates/catalogs-config/src/tests.rs
  • pacquet/crates/catalogs-types/src/lib.rs
  • pacquet/crates/catalogs-resolver/src/tests.rs
  • pacquet/crates/catalogs-protocol-parser/src/lib.rs
  • pacquet/crates/workspace/src/manifest/tests.rs
  • pacquet/crates/workspace/src/manifest.rs
  • pacquet/crates/catalogs-resolver/src/lib.rs
  • pacquet/crates/catalogs-config/src/lib.rs
🔇 Additional comments (14)
pacquet/crates/catalogs-types/Cargo.toml (1)

1-17: LGTM!

pacquet/crates/catalogs-types/src/lib.rs (1)

12-30: LGTM!

Cargo.toml (1)

16-19: LGTM!

pacquet/crates/workspace/Cargo.toml (1)

14-14: LGTM!

pacquet/crates/catalogs-protocol-parser/Cargo.toml (1)

1-18: LGTM!

pacquet/crates/catalogs-protocol-parser/src/lib.rs (1)

9-24: LGTM!

Also applies to: 26-27

pacquet/crates/catalogs-protocol-parser/src/tests.rs (1)

4-25: LGTM!

pacquet/crates/workspace/src/manifest.rs (1)

26-27: LGTM!

Also applies to: 61-70

pacquet/crates/workspace/src/manifest/tests.rs (1)

5-5: LGTM!

Also applies to: 69-102

pacquet/crates/catalogs-config/Cargo.toml (1)

1-21: LGTM!

pacquet/crates/catalogs-config/src/lib.rs (1)

1-82: LGTM!

pacquet/crates/catalogs-config/src/tests.rs (1)

1-68: LGTM!

pacquet/crates/catalogs-resolver/Cargo.toml (1)

1-21: LGTM!

pacquet/crates/catalogs-resolver/src/tests.rs (1)

1-187: LGTM!

Comment thread pacquet/crates/catalogs-resolver/src/lib.rs
@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.01      7.8±0.42ms   553.2 KB/sec    1.00      7.7±0.11ms   561.5 KB/sec

@codecov-commenter

codecov-commenter commented May 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 87.28%. Comparing base (df990fd) to head (b90fd24).

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #11787      +/-   ##
==========================================
+ Coverage   87.20%   87.28%   +0.08%     
==========================================
  Files         190      193       +3     
  Lines       22525    22636     +111     
==========================================
+ Hits        19642    19758     +116     
+ Misses       2883     2878       -5     

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

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

@github-actions

Copy link
Copy Markdown
Contributor

Integrated-Benchmark Report (Linux)

Scenario: Frozen Lockfile

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 2.486 ± 0.093 2.365 2.625 1.01 ± 0.05
pacquet@main 2.453 ± 0.077 2.394 2.643 1.00
pnpm 4.895 ± 0.032 4.850 4.959 2.00 ± 0.06
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 2.4859350611199997,
      "stddev": 0.09283392618464369,
      "median": 2.49261433302,
      "user": 2.74078248,
      "system": 3.7682987799999994,
      "min": 2.3650666575200003,
      "max": 2.6246971665200003,
      "times": [
        2.6246971665200003,
        2.40831743852,
        2.39636764352,
        2.61340181652,
        2.39480132152,
        2.3650666575200003,
        2.48837943452,
        2.5383850325200004,
        2.53308486852,
        2.49684923152
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 2.45273394212,
      "stddev": 0.07701080737153443,
      "median": 2.42379126452,
      "user": 2.7797429800000004,
      "system": 3.7432415800000003,
      "min": 2.3939300235200003,
      "max": 2.6432428965200003,
      "times": [
        2.6432428965200003,
        2.39890137352,
        2.4846596505200003,
        2.3939300235200003,
        2.4597739305200004,
        2.40335039652,
        2.39640543452,
        2.49949318652,
        2.43338918952,
        2.41419333952
      ]
    },
    {
      "command": "pnpm",
      "mean": 4.8950438069199995,
      "stddev": 0.031929295098169705,
      "median": 4.8970558230200005,
      "user": 8.305616079999998,
      "system": 4.27526368,
      "min": 4.85028775552,
      "max": 4.95913795452,
      "times": [
        4.89788594052,
        4.87186001152,
        4.86666162752,
        4.95913795452,
        4.90541144152,
        4.87029306452,
        4.92284622252,
        4.89622570552,
        4.909828345519999,
        4.85028775552
      ]
    }
  ]
}

Scenario: Frozen Lockfile (Hot Cache)

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 687.5 ± 43.1 668.9 809.8 1.00
pacquet@main 712.3 ± 66.4 672.9 893.0 1.04 ± 0.12
pnpm 2694.0 ± 102.0 2515.3 2882.2 3.92 ± 0.29
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 0.6874626044000001,
      "stddev": 0.04312440265635045,
      "median": 0.6748557775000001,
      "user": 0.37214291999999993,
      "system": 1.5690749400000001,
      "min": 0.6689246075,
      "max": 0.8098015325000001,
      "times": [
        0.8098015325000001,
        0.6724887755000001,
        0.6699208805000001,
        0.6762909355000001,
        0.6758231895000001,
        0.6707976545000001,
        0.6689246075,
        0.6738883655000001,
        0.6765413845000001,
        0.6801487185000001
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 0.7122716305000001,
      "stddev": 0.06640839286212849,
      "median": 0.6887535145000001,
      "user": 0.38859732,
      "system": 1.58013774,
      "min": 0.6728854885000001,
      "max": 0.8930219035000001,
      "times": [
        0.7413084255000001,
        0.6728854885000001,
        0.6838209995000001,
        0.8930219035000001,
        0.6988824615000001,
        0.6808291675000001,
        0.6816090465000001,
        0.6779234125000001,
        0.6936860295000001,
        0.6987493705000001
      ]
    },
    {
      "command": "pnpm",
      "mean": 2.6940487583,
      "stddev": 0.10203990429441027,
      "median": 2.7014742160000003,
      "user": 3.2648885200000004,
      "system": 2.28780054,
      "min": 2.5152544865,
      "max": 2.8822053075000005,
      "times": [
        2.6979711595,
        2.5587714555,
        2.7523891865000003,
        2.7377413005,
        2.7049772725000003,
        2.6634356575,
        2.5152544865,
        2.7347106845000004,
        2.8822053075000005,
        2.6930310725
      ]
    }
  ]
}

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
pacquet/crates/cli/tests/install.rs (1)

385-389: ⚡ Quick win

Assert the pnpm error code in addition to the message.

This test validates the message text but not the contract error code (ERR_PNPM_CATALOG_ENTRY_NOT_FOUND_FOR_SPEC), so a code regression could pass unnoticed.

Proposed assertion addition
     assert!(
         flattened.contains(
             "Nocatalogentry'`@pnpm.e2e/hello-world-js-bin-parent`'wasfoundforcatalog'default'.",
         ),
         "stderr did not mention the missing-catalog-entry error: {stderr}",
     );
+    assert!(
+        stderr.contains("ERR_PNPM_CATALOG_ENTRY_NOT_FOUND_FOR_SPEC"),
+        "stderr did not include the expected pnpm error code: {stderr}",
+    );
🤖 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/cli/tests/install.rs` around lines 385 - 389, The current
assertion in the test checks the human-readable message but not the pnpm error
code; update the test in install.rs (near the assert that inspects flattened and
stderr) to also assert that the stderr (or flattened) contains the literal error
code "ERR_PNPM_CATALOG_ENTRY_NOT_FOUND_FOR_SPEC" so the contract is verified;
add a second assert using the same stderr/flattened variable (or extend the
existing assertion) to fail if the error code string is not present alongside
the message.
🤖 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/cli/tests/install.rs`:
- Around line 385-389: The current assertion in the test checks the
human-readable message but not the pnpm error code; update the test in
install.rs (near the assert that inspects flattened and stderr) to also assert
that the stderr (or flattened) contains the literal error code
"ERR_PNPM_CATALOG_ENTRY_NOT_FOUND_FOR_SPEC" so the contract is verified; add a
second assert using the same stderr/flattened variable (or extend the existing
assertion) to fail if the error code string is not present alongside the
message.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 86d140d6-a73b-4c23-bb2b-b517a21b761b

📥 Commits

Reviewing files that changed from the base of the PR and between e1a1e1a and 441eda8.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • pacquet/crates/cli/tests/install.rs
  • pacquet/crates/package-manager/Cargo.toml
  • pacquet/crates/package-manager/src/install.rs
  • pacquet/crates/package-manager/src/install_without_lockfile.rs
  • pacquet/crates/resolving-deps-resolver/Cargo.toml
  • pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs
  • pacquet/crates/resolving-deps-resolver/src/tests.rs
✅ Files skipped from review due to trivial changes (1)
  • pacquet/crates/resolving-deps-resolver/Cargo.toml
📜 Review details
🧰 Additional context used
📓 Path-based instructions (2)
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_without_lockfile.rs
  • pacquet/crates/cli/tests/install.rs
  • pacquet/crates/package-manager/src/install.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs
  • pacquet/crates/resolving-deps-resolver/src/tests.rs
pacquet/**/{tests,test}/**/*.rs

📄 CodeRabbit inference engine (pacquet/AGENTS.md)

pacquet/**/{tests,test}/**/*.rs: Prefer real fixtures; reach for the dependency-injection seam only when they can't cover the branch. Most happy paths and error paths should be tested with tempfile::TempDir, the mocked registry, or an integration test that spawns the actual binary.
Use the DI seam — a capability trait on the Host provider, threaded as Sys: <Bounds> — only for branches a real fixture can't reach portably: filesystem error kinds, deterministic time, shared process-global state, or external-service happy paths.
Log before non-assert_eq! assertions, dbg! complex structures, skip logging for simple scalar assert_eq!. Follow the test-logging guidance in CODE_STYLE_GUIDE.md.
Tests must not be tolerant of missing build/runtime environment by silently returning early when a tool isn't found. If the test needs a tool, let it panic when the tool is absent.
Prefer #[cfg_attr(target_os = "windows", ignore = "...")] (or #[cfg(unix)]) over a runtime probe-and-skip helper for platform-locked tools.
Port relevant tests from pnpm when porting behavior. Matching test coverage is the easiest way to prove behavioral parity.
When porting behavior from pnpm, consult plans/TEST_PORTING.md before adding ported tests. Use allow_known_failure! at the not-yet-implemented boundary and update checkboxes as items land.

Files:

  • pacquet/crates/cli/tests/install.rs
🧠 Learnings (2)
📚 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_without_lockfile.rs
  • pacquet/crates/cli/tests/install.rs
  • pacquet/crates/package-manager/src/install.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs
  • pacquet/crates/resolving-deps-resolver/src/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/package-manager/src/install_without_lockfile.rs
  • pacquet/crates/cli/tests/install.rs
  • pacquet/crates/package-manager/src/install.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs
  • pacquet/crates/resolving-deps-resolver/src/tests.rs
🔇 Additional comments (6)
pacquet/crates/package-manager/Cargo.toml (1)

14-15: LGTM!

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

10-10: LGTM!

Also applies to: 79-81, 152-152, 243-243

pacquet/crates/cli/tests/install.rs (1)

314-353: LGTM!

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

10-12: LGTM!

Also applies to: 172-182, 256-273, 551-551

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

7-12: LGTM!

Also applies to: 43-48, 68-73, 117-117, 269-296

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

3-3: LGTM!

Also applies to: 120-120, 188-188, 219-219, 232-301, 307-307, 335-335, 386-386, 435-435, 487-487, 547-547

zkochan added 4 commits May 21, 2026 01:47
Adds four new crates mirroring upstream's `catalogs/*` packages:

- pacquet-catalogs-types: `Catalog`/`Catalogs` map aliases plus
  `DEFAULT_CATALOG_NAME`.
- pacquet-catalogs-protocol-parser: `parse_catalog_protocol`; folds
  `catalog:` shorthand into `"default"` to match upstream.
- pacquet-catalogs-resolver: `resolve_from_catalog` with the four
  upstream `PnpmError` codes (`CATALOG_ENTRY_NOT_FOUND_FOR_SPEC`,
  `CATALOG_ENTRY_INVALID_RECURSIVE_DEFINITION`,
  `CATALOG_ENTRY_INVALID_WORKSPACE_SPEC`,
  `CATALOG_ENTRY_INVALID_SPEC`). Rust callers `match` on the result
  enum directly instead of porting the TS-ergonomic
  `matchCatalogResolveResult` visitor.
- pacquet-catalogs-config: `get_catalogs_from_workspace_manifest` plus
  the `INVALID_CATALOGS_CONFIGURATION` mutual-exclusion check.

`pacquet-workspace`'s `WorkspaceManifest` now actually deserializes
the `catalog` and `catalogs` fields (previously dropped). The
resolver is not yet wired into the install path — `deps-installer`
hasn't been ported — but the crates are ready for that next step.

Tests are 1:1 ports of the upstream Jest suites.
- catalogs-resolver: drop the intra-doc-link form on the
  `pacquet_resolving_resolver_base::WantedDependency` reference; the
  crate isn't a dependency, so rustdoc failed to resolve it under
  `-D rustdoc::broken-intra-doc-links`.
- catalogs-resolver, catalogs-config: reorder the `CatalogResolutionError`
  / `InvalidCatalogsConfigurationError` derive lists to
  `Debug, Display, Error, Diagnostic, Clone, PartialEq, Eq` so they
  match the `prefix_then_alphabetical` rule that the CI-only
  Perfectionist dylint enforces. `just ready` doesn't surface this lint
  locally.
Wires the catalogs port into the install path:

- `install.rs`: read `pnpm-workspace.yaml` after `find_workspace_dir`
  and normalize via `get_catalogs_from_workspace_manifest` into a
  `Catalogs` map. Adds `InstallError::{ReadWorkspaceManifest,
  InvalidCatalogsConfiguration}` so the upstream
  `ERR_PNPM_INVALID_CATALOGS_CONFIGURATION` propagates verbatim.
- `install_without_lockfile.rs`: threads the `Catalogs` map into
  `ResolveDependencyTreeOptions`. (Frozen-lockfile catalog handling
  needs a lockfile-snapshot pass and is a separate slice.)
- `resolve_dependency_tree`: replaces direct (importer-level)
  `catalog:` bare specifiers with the catalog's recorded version
  before the resolver chain dispatches. Catalog resolution does NOT
  run on transitive deps, matching upstream's importer-only scope.
  Misconfigured entries surface as `CatalogMisconfiguration` with
  the upstream `ERR_PNPM_CATALOG_ENTRY_*` code instead of leaking
  through to `SPEC_NOT_SUPPORTED_BY_ANY_RESOLVER`.

Tests:

- deps-resolver: two new unit tests prove direct-dep rewriting and
  the misconfiguration code path.
- cli (e2e): `install_resolves_catalog_protocol` runs the binary
  against a workspace with a `catalog:` entry and checks the
  virtual-store layout; `install_surfaces_catalog_misconfiguration`
  asserts the upstream message is surfaced when the catalog has no
  matching alias.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
pacquet/crates/catalogs-config/src/lib.rs (1)

41-43: ⚡ Quick win

Add an explicit unit test for the None manifest branch.

The early-return path (None → empty Catalogs) is part of the public behavior and appears to be the remaining uncovered line; a focused test will lock this contract and prevent regressions.

🤖 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/catalogs-config/src/lib.rs` around lines 41 - 43, Add a unit
test that exercises the early-return branch where workspace_manifest is None to
assert that the function returns an empty Catalogs (i.e., Catalogs::new()). In
your test module for lib.rs, construct the minimal inputs or test harness so
workspace_manifest evaluates to None, call the public entrypoint that contains
the let Some(manifest) = workspace_manifest else { return Ok(Catalogs::new()); }
logic, and assert the returned value equals Catalogs::new() (or matches expected
empty state). Name the test clearly (e.g.,
returns_empty_catalogs_when_manifest_none) and keep it focused only on the None
branch to lock the public contract.
🤖 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/catalogs-config/src/lib.rs`:
- Around line 41-43: Add a unit test that exercises the early-return branch
where workspace_manifest is None to assert that the function returns an empty
Catalogs (i.e., Catalogs::new()). In your test module for lib.rs, construct the
minimal inputs or test harness so workspace_manifest evaluates to None, call the
public entrypoint that contains the let Some(manifest) = workspace_manifest else
{ return Ok(Catalogs::new()); } logic, and assert the returned value equals
Catalogs::new() (or matches expected empty state). Name the test clearly (e.g.,
returns_empty_catalogs_when_manifest_none) and keep it focused only on the None
branch to lock the public contract.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7830f019-63d7-4bb9-88d3-0faaf30cb8d6

📥 Commits

Reviewing files that changed from the base of the PR and between 441eda8 and 444fe23.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (23)
  • Cargo.toml
  • pacquet/crates/catalogs-config/Cargo.toml
  • pacquet/crates/catalogs-config/src/lib.rs
  • pacquet/crates/catalogs-config/src/tests.rs
  • pacquet/crates/catalogs-protocol-parser/Cargo.toml
  • pacquet/crates/catalogs-protocol-parser/src/lib.rs
  • pacquet/crates/catalogs-protocol-parser/src/tests.rs
  • pacquet/crates/catalogs-resolver/Cargo.toml
  • pacquet/crates/catalogs-resolver/src/lib.rs
  • pacquet/crates/catalogs-resolver/src/tests.rs
  • pacquet/crates/catalogs-types/Cargo.toml
  • pacquet/crates/catalogs-types/src/lib.rs
  • pacquet/crates/cli/tests/install.rs
  • pacquet/crates/package-manager/Cargo.toml
  • pacquet/crates/package-manager/src/install.rs
  • pacquet/crates/package-manager/src/install_without_lockfile.rs
  • pacquet/crates/resolving-deps-resolver/Cargo.toml
  • pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_importer.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_importer/tests.rs
  • pacquet/crates/workspace/Cargo.toml
  • pacquet/crates/workspace/src/manifest.rs
  • pacquet/crates/workspace/src/manifest/tests.rs
✅ Files skipped from review due to trivial changes (3)
  • pacquet/crates/catalogs-config/Cargo.toml
  • pacquet/crates/catalogs-protocol-parser/Cargo.toml
  • pacquet/crates/catalogs-types/Cargo.toml
📜 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: Run benchmark on ubuntu-latest
  • GitHub Check: Code Coverage
  • GitHub Check: Lint and Test (windows-latest)
  • GitHub Check: Lint and Test (macos-latest)
  • GitHub Check: Lint and Test (ubuntu-latest)
  • GitHub Check: Compile & Lint
🧰 Additional context used
📓 Path-based instructions (2)
pacquet/**/*.rs

📄 CodeRabbit inference engine (pacquet/AGENTS.md)

pacquet/**/*.rs: When porting a function that 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/workspace/src/manifest/tests.rs
  • pacquet/crates/cli/tests/install.rs
  • pacquet/crates/catalogs-types/src/lib.rs
  • pacquet/crates/catalogs-config/src/lib.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs
  • pacquet/crates/catalogs-config/src/tests.rs
  • pacquet/crates/catalogs-protocol-parser/src/lib.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_importer.rs
  • pacquet/crates/package-manager/src/install_without_lockfile.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_importer/tests.rs
  • pacquet/crates/catalogs-protocol-parser/src/tests.rs
  • pacquet/crates/catalogs-resolver/src/lib.rs
  • pacquet/crates/catalogs-resolver/src/tests.rs
  • pacquet/crates/package-manager/src/install.rs
  • pacquet/crates/workspace/src/manifest.rs
pacquet/**/{tests,test}/**/*.rs

📄 CodeRabbit inference engine (pacquet/AGENTS.md)

pacquet/**/{tests,test}/**/*.rs: Prefer real fixtures; reach for the dependency-injection seam only when they can't cover the branch. Most happy paths and error paths should be tested with tempfile::TempDir, the mocked registry, or an integration test that spawns the actual binary.
Use the DI seam — a capability trait on the Host provider, threaded as Sys: <Bounds> — only for branches a real fixture can't reach portably: filesystem error kinds, deterministic time, shared process-global state, or external-service happy paths.
Log before non-assert_eq! assertions, dbg! complex structures, skip logging for simple scalar assert_eq!. Follow the test-logging guidance in CODE_STYLE_GUIDE.md.
Tests must not be tolerant of missing build/runtime environment by silently returning early when a tool isn't found. If the test needs a tool, let it panic when the tool is absent.
Prefer #[cfg_attr(target_os = "windows", ignore = "...")] (or #[cfg(unix)]) over a runtime probe-and-skip helper for platform-locked tools.
Port relevant tests from pnpm when porting behavior. Matching test coverage is the easiest way to prove behavioral parity.
When porting behavior from pnpm, consult plans/TEST_PORTING.md before adding ported tests. Use allow_known_failure! at the not-yet-implemented boundary and update checkboxes as items land.

Files:

  • pacquet/crates/cli/tests/install.rs
🧠 Learnings (2)
📚 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/workspace/src/manifest/tests.rs
  • pacquet/crates/cli/tests/install.rs
  • pacquet/crates/catalogs-types/src/lib.rs
  • pacquet/crates/catalogs-config/src/lib.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs
  • pacquet/crates/catalogs-config/src/tests.rs
  • pacquet/crates/catalogs-protocol-parser/src/lib.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_importer.rs
  • pacquet/crates/package-manager/src/install_without_lockfile.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_importer/tests.rs
  • pacquet/crates/catalogs-protocol-parser/src/tests.rs
  • pacquet/crates/catalogs-resolver/src/lib.rs
  • pacquet/crates/catalogs-resolver/src/tests.rs
  • pacquet/crates/package-manager/src/install.rs
  • pacquet/crates/workspace/src/manifest.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/workspace/src/manifest/tests.rs
  • pacquet/crates/cli/tests/install.rs
  • pacquet/crates/catalogs-types/src/lib.rs
  • pacquet/crates/catalogs-config/src/lib.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs
  • pacquet/crates/catalogs-config/src/tests.rs
  • pacquet/crates/catalogs-protocol-parser/src/lib.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_importer.rs
  • pacquet/crates/package-manager/src/install_without_lockfile.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_importer/tests.rs
  • pacquet/crates/catalogs-protocol-parser/src/tests.rs
  • pacquet/crates/catalogs-resolver/src/lib.rs
  • pacquet/crates/catalogs-resolver/src/tests.rs
  • pacquet/crates/package-manager/src/install.rs
  • pacquet/crates/workspace/src/manifest.rs
🔇 Additional comments (20)
pacquet/crates/catalogs-types/src/lib.rs (1)

1-30: LGTM!

Cargo.toml (1)

16-19: LGTM!

Also applies to: 23-26, 35-35

pacquet/crates/workspace/Cargo.toml (1)

14-14: LGTM!

pacquet/crates/catalogs-protocol-parser/src/lib.rs (1)

1-27: LGTM!

pacquet/crates/catalogs-protocol-parser/src/tests.rs (1)

1-25: LGTM!

pacquet/crates/workspace/src/manifest.rs (1)

26-26: LGTM!

Also applies to: 61-70

pacquet/crates/workspace/src/manifest/tests.rs (1)

5-5: LGTM!

Also applies to: 69-101

pacquet/crates/catalogs-config/src/lib.rs (1)

1-40: LGTM!

Also applies to: 45-81

pacquet/crates/catalogs-config/src/tests.rs (1)

1-68: LGTM!

pacquet/crates/catalogs-resolver/Cargo.toml (1)

1-21: LGTM!

pacquet/crates/catalogs-resolver/src/lib.rs (1)

1-184: LGTM!

pacquet/crates/catalogs-resolver/src/tests.rs (1)

1-187: LGTM!

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

14-15: LGTM!

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

10-12: LGTM!

Also applies to: 172-182, 256-273, 551-552

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

10-11: LGTM!

Also applies to: 21-34, 89-92, 113-133, 177-178, 205-210, 214-276, 301-329, 342-343, 378-382, 511-513, 630-657

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

14-15: LGTM!

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

7-12: LGTM!

Also applies to: 57-63, 92-99, 101-204, 222-222, 288-288, 321-348, 364-373

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

47-84: LGTM!

Also applies to: 94-105, 109-226, 243-344

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

85-94: LGTM!

Also applies to: 96-827

pacquet/crates/cli/tests/install.rs (1)

314-447: LGTM!

Perfectionist's `single_letter_closure_param` lint (CI-only via
Dylint) flagged `|c|` in the box-drawing-strip filter.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants