Skip to content

feat(pacquet/cli): --filter, --filter-prod (initial)#12000

Merged
zkochan merged 11 commits into
mainfrom
claude/gifted-einstein-vuowL
May 29, 2026
Merged

feat(pacquet/cli): --filter, --filter-prod (initial)#12000
zkochan merged 11 commits into
mainfrom
claude/gifted-einstein-vuowL

Conversation

@KSXGitHub

@KSXGitHub KSXGitHub commented May 27, 2026

Copy link
Copy Markdown
Contributor

Ports pnpm's @pnpm/workspace.projects-graph and @pnpm/workspace.projects-filter to Rust and adds the --filter / -F and --filter-prod CLI flags.

What

  • pacquet-workspace-projects-graphcreate_projects_graph builds the inter-project dependency graph (edges from workspace:, semver, and local-path specifiers).
  • pacquet-workspace-projects-filterparse_project_selector (name globs, ... dependents/dependencies, ^, !, {dir}, [since]) and filter_workspace_projects (selectors → selected projects).
  • CLI: --filter / -F and --filter-prod parse into Config::filter / Config::filter_prod, mirroring pnpm's CLI-only config.

Scope (initial)

Selector parsing and project selection only. As with --recursive, install still materializes every workspace importer in one shared pass, so narrowing the install to the selected projects is a follow-up — the selectors are stored but not yet consumed by install. The [since] changed-packages selector parses but returns FilterError::UnsupportedDiffSelector (git-diff selection not ported yet).

Tests

Ports the upstream parseProjectSelector, filterWorkspaceProjects, and create_projects_graph suites (diff-based cases stubbed as known_failures); plans/TEST_PORTING.md updated.


Written by an agent (Claude Code).

Summary by CodeRabbit

  • New Features

    • Workspace project filtering via global flags (--filter / --filter-prod, -F) to target subsets of workspace projects.
    • Project-graph-based resolution to follow dependencies/dependents during filtering.
  • Utilities

    • Selector parsing and directory glob matching for flexible filter expressions, including prod-only traversal.
  • Tests

    • Extensive unit test coverage for selector parsing, globbing, graph building, and filtering behaviors.
  • Documentation

    • New docs section describing workspace filtering support and porting status.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 27, 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: 6dc49f93-6826-4026-bf22-8238ec63a830

📥 Commits

Reviewing files that changed from the base of the PR and between d35b7a8 and cefd4cf.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (21)
  • Cargo.toml
  • pacquet/crates/cli/src/cli_args.rs
  • pacquet/crates/cli/src/cli_args/tests.rs
  • pacquet/crates/config/src/lib.rs
  • pacquet/crates/package-manager/src/install_package_from_registry/tests.rs
  • pacquet/crates/workspace-projects-filter/Cargo.toml
  • pacquet/crates/workspace-projects-filter/src/filter.rs
  • pacquet/crates/workspace-projects-filter/src/filter/tests.rs
  • pacquet/crates/workspace-projects-filter/src/glob.rs
  • pacquet/crates/workspace-projects-filter/src/glob/tests.rs
  • pacquet/crates/workspace-projects-filter/src/lib.rs
  • pacquet/crates/workspace-projects-filter/src/parse_project_selector.rs
  • pacquet/crates/workspace-projects-filter/src/parse_project_selector/tests.rs
  • pacquet/crates/workspace-projects-filter/src/path_util.rs
  • pacquet/crates/workspace-projects-graph/Cargo.toml
  • pacquet/crates/workspace-projects-graph/src/base_project.rs
  • pacquet/crates/workspace-projects-graph/src/create_projects_graph.rs
  • pacquet/crates/workspace-projects-graph/src/create_projects_graph/tests.rs
  • pacquet/crates/workspace-projects-graph/src/graph.rs
  • pacquet/crates/workspace-projects-graph/src/lib.rs
  • pacquet/plans/TEST_PORTING.md
✅ Files skipped from review due to trivial changes (3)
  • pacquet/crates/workspace-projects-graph/Cargo.toml
  • pacquet/crates/workspace-projects-filter/Cargo.toml
  • pacquet/plans/TEST_PORTING.md
🚧 Files skipped from review as they are similar to previous changes (16)
  • pacquet/crates/cli/src/cli_args/tests.rs
  • pacquet/crates/package-manager/src/install_package_from_registry/tests.rs
  • pacquet/crates/config/src/lib.rs
  • pacquet/crates/workspace-projects-graph/src/graph.rs
  • pacquet/crates/cli/src/cli_args.rs
  • pacquet/crates/workspace-projects-filter/src/path_util.rs
  • pacquet/crates/workspace-projects-graph/src/base_project.rs
  • Cargo.toml
  • pacquet/crates/workspace-projects-filter/src/parse_project_selector.rs
  • pacquet/crates/workspace-projects-graph/src/lib.rs
  • pacquet/crates/workspace-projects-filter/src/glob.rs
  • pacquet/crates/workspace-projects-filter/src/parse_project_selector/tests.rs
  • pacquet/crates/workspace-projects-graph/src/create_projects_graph/tests.rs
  • pacquet/crates/workspace-projects-filter/src/lib.rs
  • pacquet/crates/workspace-projects-filter/src/filter.rs
  • pacquet/crates/workspace-projects-graph/src/create_projects_graph.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). (6)
  • GitHub Check: Code Coverage
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Lint and Test (macos-latest)
  • GitHub Check: Analyze (javascript)
  • GitHub Check: Compile & Lint
🧰 Additional context used
📓 Path-based instructions (1)
pacquet/crates/**/*.rs

📄 CodeRabbit inference engine (pacquet/AGENTS.md)

pacquet/crates/**/*.rs: Log emissions must match pnpm: mirror the call site, payload, and ordering so @pnpm/cli.default-reporter parses pacquet's NDJSON the same way it parses pnpm's
Match upstream serde behavior for branded types crossing JSON/YAML/INI boundaries: use #[serde(try_from = "String")] for deserialization and #[serde(into = "String")] for serialization
Derive simple conversions with derive_more crate; use #[derive(derive_more::From)] and #[derive(derive_more::Into)] for mechanical one-liner conversions
Do not use star imports inside module bodies; write explicit imports like use super::{Foo, bar} instead of use super::*;, except for external-crate preludes and root-of-module re-exports
Use doc comments (///, //!) to document the contract including preconditions, postconditions, panics, and the reason the function exists, not as re-narration of the body
Use // SAFETY:, // TODO:, and similar prefixes to signal hidden invariants or known follow-ups that cannot be recovered from the code alone
Warnings are errors in clippy checks; do not silence them with #[allow(...)] unless there is a specific, justified reason
User-facing errors go through miette via the pacquet-diagnostics crate; match pnpm's error codes and messages where pnpm defines them
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

Files:

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

Applied to files:

  • pacquet/crates/workspace-projects-filter/src/glob/tests.rs
  • pacquet/crates/workspace-projects-filter/src/filter/tests.rs
📚 Learning: 2026-05-22T00:08:44.646Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11837
File: pacquet/crates/resolving-npm-resolver/src/pick_package.rs:33-51
Timestamp: 2026-05-22T00:08:44.646Z
Learning: In the pnpm/pnpm repo’s pacquet Rust crates, do not flag Unicode ellipsis characters (U+2026, `…`) in Rust doc comments (`///` / `/** */`) as a lint violation. The pacquet crate’s `dylint.toml` only enables `perfectionist::derive_ordering`, and the Dylint `unicode-ellipsis` rule is not enabled for this project—so `…` in doc comments is an intentional, repo-consistent style.

Applied to files:

  • pacquet/crates/workspace-projects-filter/src/glob/tests.rs
  • pacquet/crates/workspace-projects-filter/src/filter/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/workspace-projects-filter/src/glob/tests.rs
  • pacquet/crates/workspace-projects-filter/src/filter/tests.rs
🔇 Additional comments (4)
pacquet/crates/workspace-projects-filter/src/glob/tests.rs (1)

1-57: LGTM!

pacquet/crates/workspace-projects-filter/src/filter/tests.rs (3)

1-101: LGTM!


103-402: LGTM!


404-575: LGTM!


📝 Walkthrough

Walkthrough

Adds global --filter / --filter-prod flags and two crates. pacquet-workspace-projects-graph builds an ordered workspace dependency graph; pacquet-workspace-projects-filter parses selectors, matches by name/dir/glob, and filters via dependency/dependent traversal. CLI, config, manifests, and unit tests are included.

Changes

Workspace Project Filtering Feature

Layer / File(s) Summary
CLI flags and config propagation
Cargo.toml, pacquet/crates/cli/src/cli_args.rs, pacquet/crates/cli/src/cli_args/tests.rs, pacquet/crates/config/src/lib.rs, pacquet/crates/package-manager/src/install_package_from_registry/tests.rs
Global --filter/-F and --filter-prod flags are parsed via clap and threaded into Config during initialization; tests verify default, repeatable, and global-flag precedence.
Crate manifests & entrypoints
pacquet/crates/workspace-projects-filter/Cargo.toml, pacquet/crates/workspace-projects-filter/src/lib.rs, pacquet/crates/workspace-projects-graph/Cargo.toml, pacquet/crates/workspace-projects-graph/src/lib.rs
New crate manifests register workspace-projects-filter and workspace-projects-graph in the workspace; lib entrypoints re-export filtering and graph APIs.
Workspace graph data structures & traits
pacquet/crates/workspace-projects-graph/src/graph.rs, pacquet/crates/workspace-projects-graph/src/base_project.rs
ProjectGraphNode<Pkg> and ProjectGraph<Pkg> model the workspace graph; BaseProject/GraphProject traits define required project metadata and merged-deps API.
Workspace graph construction
pacquet/crates/workspace-projects-graph/src/create_projects_graph.rs, pacquet/crates/workspace-projects-graph/src/create_projects_graph/tests.rs
create_projects_graph() snapshots projects, classifies specs (workspace:, path-like, semver), resolves edges to sibling projects, emits unmatched records, and returns an insertion-ordered graph; tests cover resolution modes and edge cases.
Filter selector string parsing
pacquet/crates/workspace-projects-filter/src/parse_project_selector.rs, pacquet/crates/workspace-projects-filter/src/parse_project_selector/tests.rs
parse_project_selector() converts raw selectors to ProjectSelector objects (exclude !, ..., ^, {}, []), falls back to path or name patterns on malformed input, and is exercised by unit tests covering normal and edge cases.
Glob pattern matching & path utilities
pacquet/crates/workspace-projects-filter/src/glob.rs, pacquet/crates/workspace-projects-filter/src/glob/tests.rs, pacquet/crates/workspace-projects-filter/src/path_util.rs
is_match() supports * (single-segment) and ** (multi-segment) matching with normalization and backtracking; lexical_join() provides prefix-relative join semantics; tests validate matching behaviors.
Project filtering by selectors and graph traversal
pacquet/crates/workspace-projects-filter/src/filter.rs, pacquet/crates/workspace-projects-filter/src/filter/tests.rs
filter_workspace_projects() partitions include/exclude selectors, resolves entry points (name + parent-dir), traverses deps/dependents with deduplication, supports prod-only traversal via separate graph builds, and returns selected projects with unmatched selectors; comprehensive tests and known-failure stubs included.

Sequence Diagram(s)

sequenceDiagram
  participant CLI as CLI Parser
  participant CliArgs
  participant Config
  participant FilterProj as filter_projects
  participant Parser as parse_project_selector
  participant GraphCtor as create_projects_graph
  participant FilterEngine as filter_workspace_projects
  CLI->>CliArgs: parse --filter / --filter-prod
  CliArgs->>Config: copy filter vectors into Config
  Config->>FilterProj: pass filters
  FilterProj->>Parser: parse raw selector strings
  FilterProj->>GraphCtor: build ProjectGraph(s)
  FilterProj->>FilterEngine: apply parsed selectors to graph
  FilterEngine->>FilterEngine: resolve entry points, traverse deps/dependents
  FilterEngine->>FilterProj: return selected projects + unmatched
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • zkochan

Poem

🐰 A rabbit parses filters with a tiny hop,

globs and graphs in tidy rows they prop,
names and paths and prod-only dreams aligned,
tests keep watch so matches are well-defined,
Pacquet picks packages, one hop at a time.

🚥 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 summarizes the main addition: new CLI flags --filter and --filter-prod with their initial implementation in the pacquet CLI layer.
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 claude/gifted-einstein-vuowL

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.

@github-actions

github-actions Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

Micro-Benchmark Results

Linux

group                          main                                   pr
-----                          ----                                   --
tarball/download_dependency    1.01      7.8±0.31ms   554.7 KB/sec    1.00      7.7±0.24ms   561.8 KB/sec

@codecov-commenter

codecov-commenter commented May 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.38%. Comparing base (49e6074) to head (cefd4cf).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #12000      +/-   ##
==========================================
+ Coverage   89.20%   89.38%   +0.17%     
==========================================
  Files         238      243       +5     
  Lines       32003    32536     +533     
==========================================
+ Hits        28549    29081     +532     
- Misses       3454     3455       +1     

☔ 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 27, 2026

Copy link
Copy Markdown
Contributor

Integrated-Benchmark Report (Linux)

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

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 2.108 ± 0.063 2.051 2.252 1.03 ± 0.04
pacquet@main 2.056 ± 0.045 2.003 2.132 1.00
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 2.1083576487999998,
      "stddev": 0.06279294754721261,
      "median": 2.0885676146999996,
      "user": 2.69096814,
      "system": 3.28864934,
      "min": 2.0510612566999997,
      "max": 2.2523143857,
      "times": [
        2.1056892997,
        2.1160715287,
        2.1039139827,
        2.1795793967,
        2.0691338767,
        2.0510612566999997,
        2.2523143857,
        2.0640837857,
        2.0732212466999997,
        2.0685077286999998
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 2.0558177831999997,
      "stddev": 0.045117370820884,
      "median": 2.0481068077,
      "user": 2.75835704,
      "system": 3.2951159399999996,
      "min": 2.0027051647,
      "max": 2.1320898777,
      "times": [
        2.0027051647,
        2.0617345067,
        2.0128604337,
        2.0924476007,
        2.0559846947,
        2.0137764427,
        2.1320898777,
        2.0293193126999998,
        2.0402289206999997,
        2.1170308777
      ]
    }
  ]
}

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

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 682.0 ± 55.4 645.8 832.7 1.00 ± 0.08
pacquet@main 681.1 ± 15.8 662.2 708.8 1.00
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 0.6819614637,
      "stddev": 0.05537632821572339,
      "median": 0.6634553345,
      "user": 0.36889879999999997,
      "system": 1.34143434,
      "min": 0.645791674,
      "max": 0.8326578820000001,
      "times": [
        0.8326578820000001,
        0.7027885690000001,
        0.675922019,
        0.6734715290000001,
        0.645791674,
        0.6614897160000001,
        0.6535233570000001,
        0.655741942,
        0.652806996,
        0.665420953
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 0.6810759167,
      "stddev": 0.01579819562584968,
      "median": 0.677368535,
      "user": 0.3867216,
      "system": 1.3588216399999997,
      "min": 0.662231639,
      "max": 0.7088218100000001,
      "times": [
        0.7088218100000001,
        0.682150397,
        0.6725866730000001,
        0.6643783900000001,
        0.6946824340000001,
        0.68548622,
        0.6702981870000001,
        0.6706264100000001,
        0.662231639,
        0.6994970070000001
      ]
    }
  ]
}

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

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 2.438 ± 0.018 2.402 2.457 1.00
pacquet@main 2.470 ± 0.018 2.441 2.490 1.01 ± 0.01
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 2.4381137769,
      "stddev": 0.01834309003458254,
      "median": 2.4422587578,
      "user": 3.9658804400000003,
      "system": 3.1250128999999998,
      "min": 2.4016433392999996,
      "max": 2.4573872393,
      "times": [
        2.4274090462999998,
        2.4016433392999996,
        2.4573872393,
        2.4463186153,
        2.4381989002999997,
        2.4568592213,
        2.4302209243,
        2.4517748213,
        2.4519787173,
        2.4193469443
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 2.4698891925999993,
      "stddev": 0.018483365955591857,
      "median": 2.4739184852999996,
      "user": 4.042252840000001,
      "system": 3.1252991999999997,
      "min": 2.4414476442999997,
      "max": 2.4904683113,
      "times": [
        2.4707651622999998,
        2.4770718083,
        2.4473728343,
        2.4849243833,
        2.4828737042999998,
        2.4662512983,
        2.4897501823,
        2.4414476442999997,
        2.4479665972999998,
        2.4904683113
      ]
    }
  ]
}

Scenario: Isolated linker: fresh install, hot cache + hot store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 1.689 ± 0.020 1.660 1.720 1.00
pacquet@main 1.693 ± 0.033 1.637 1.766 1.00 ± 0.02
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 1.6885599412399999,
      "stddev": 0.019978289402574765,
      "median": 1.68921864994,
      "user": 1.8765917399999996,
      "system": 1.89500986,
      "min": 1.66017798744,
      "max": 1.7196604714400001,
      "times": [
        1.66017798744,
        1.68608479344,
        1.67668377744,
        1.69481988244,
        1.70162223344,
        1.7152923164399998,
        1.67072847444,
        1.66817696944,
        1.7196604714400001,
        1.6923525064399998
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 1.69283327474,
      "stddev": 0.03259580752576191,
      "median": 1.68900997944,
      "user": 1.9035820400000003,
      "system": 1.9264911599999999,
      "min": 1.63660581244,
      "max": 1.76551919644,
      "times": [
        1.68773787344,
        1.6880788514399998,
        1.76551919644,
        1.69728121844,
        1.71005452744,
        1.63660581244,
        1.66653921744,
        1.68895629044,
        1.69849609144,
        1.68906366844
      ]
    }
  ]
}

@github-actions

github-actions Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

🐰 Bencher Report

Branchpr/12000
Testbedpacquet
Click to view all benchmark results
BenchmarkLatencyBenchmark Result
milliseconds (ms)
(Result Δ%)
Upper Boundary
milliseconds (ms)
(Limit %)
isolated-linker.fresh-install.cold-cache.cold-store📈 view plot
🚷 view threshold
2,438.11 ms
(+2.32%)Baseline: 2,382.88 ms
2,859.45 ms
(85.26%)
isolated-linker.fresh-install.hot-cache.hot-store📈 view plot
🚷 view threshold
1,688.56 ms
(+12.86%)Baseline: 1,496.14 ms
1,795.37 ms
(94.05%)
isolated-linker.fresh-restore.cold-cache.cold-store📈 view plot
🚷 view threshold
2,108.36 ms
(-0.83%)Baseline: 2,126.10 ms
2,551.32 ms
(82.64%)
isolated-linker.fresh-restore.hot-cache.hot-store📈 view plot
🚷 view threshold
681.96 ms
(-1.58%)Baseline: 692.88 ms
831.45 ms
(82.02%)
🐰 View full continuous benchmarking report in Bencher

Comment thread pacquet/crates/config/src/lib.rs Outdated
Comment thread pacquet/crates/config/src/lib.rs Outdated
@KSXGitHub KSXGitHub marked this pull request as ready for review May 29, 2026 08:57
@KSXGitHub KSXGitHub requested a review from zkochan as a code owner May 29, 2026 08:57
Copilot AI review requested due to automatic review settings May 29, 2026 08:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds initial Rust ports of pnpm’s workspace project graph + filtering logic and wires new CLI flags into pacquet config, so selectors can be parsed/stored (selection is implemented in the new crates but not yet consumed by install).

Changes:

  • Introduces pacquet-workspace-projects-graph (dependency edge graph between workspace projects).
  • Introduces pacquet-workspace-projects-filter (selector parsing + project selection, with [since] diff selectors rejected as unsupported for now).
  • Adds CLI flags --filter / -F and --filter-prod, threading them into pacquet_config::Config.

Reviewed changes

Copilot reviewed 21 out of 22 changed files in this pull request and generated no comments.

Show a summary per file
File Description
pacquet/plans/TEST_PORTING.md Documents port status for workspace filtering/graph logic and known-failure stubs.
pacquet/crates/workspace-projects-graph/Cargo.toml New crate manifest for the Rust projects graph port.
pacquet/crates/workspace-projects-graph/src/lib.rs Public API + module wiring for the graph crate.
pacquet/crates/workspace-projects-graph/src/base_project.rs Defines BaseProject / GraphProject traits used by graph construction.
pacquet/crates/workspace-projects-graph/src/graph.rs Defines ProjectGraph / ProjectGraphNode types and ordering contract.
pacquet/crates/workspace-projects-graph/src/create_projects_graph.rs Implements create_projects_graph and edge resolution logic.
pacquet/crates/workspace-projects-graph/src/create_projects_graph/tests.rs Unit tests covering workspace spec/range/path edge resolution behavior.
pacquet/crates/workspace-projects-filter/Cargo.toml New crate manifest for the Rust projects filter port.
pacquet/crates/workspace-projects-filter/src/lib.rs Public API for selector parsing and filtering entry points.
pacquet/crates/workspace-projects-filter/src/path_util.rs Adds lexical_join helper for directory selectors.
pacquet/crates/workspace-projects-filter/src/parse_project_selector.rs Implements selector parsing (name/dir/…/^/!/diff).
pacquet/crates/workspace-projects-filter/src/parse_project_selector/tests.rs Ports upstream selector parsing fixtures.
pacquet/crates/workspace-projects-filter/src/filter.rs Implements workspace selection (include/exclude, deps/dependents walking).
pacquet/crates/workspace-projects-filter/src/filter/tests.rs Ports upstream filtering fixtures (+ known failures for [since]).
pacquet/crates/workspace-projects-filter/src/glob.rs Implements minimal */** directory glob matcher for glob dir filtering.
pacquet/crates/workspace-projects-filter/src/glob/tests.rs Tests glob behavior used by directory filtering.
pacquet/crates/config/src/lib.rs Adds CLI-only config fields filter and filter_prod.
pacquet/crates/cli/src/cli_args.rs Adds --filter/-F and --filter-prod flags and threads them into Config.
pacquet/crates/cli/src/cli_args/tests.rs Adds parsing tests for new filter flags.
pacquet/crates/package-manager/src/install_package_from_registry/tests.rs Updates config test helper to include new fields.
Cargo.toml Adds new crates to workspace dependencies.
Cargo.lock Records new workspace crates and deps.
Comments suppressed due to low confidence (8)

pacquet/crates/workspace-projects-filter/src/glob.rs:21

  • In is_match, only the pattern is normalized to forward slashes. On Windows, PathBuf::to_string_lossy() typically yields \ separators for project root dirs, so glob dir filtering will fail to match even when the logical paths line up. Normalize the candidate string too (and keep the normalization buffer alive while splitting).

This issue also appears on line 13 of the same file.

pub fn is_match(candidate: &str, pattern: &str) -> bool {
    let pattern = pattern.replace('\\', "/");
    let pattern = pattern.strip_suffix('/').unwrap_or(&pattern);
    let candidate = candidate.strip_suffix('/').unwrap_or(candidate);

    let pattern_segments: Vec<&str> = pattern.split('/').collect();
    let candidate_segments: Vec<&str> = candidate.split('/').collect();
    match_segments(&pattern_segments, &candidate_segments)
}

pacquet/crates/workspace-projects-filter/src/parse_project_selector.rs:36

  • Doc comment has a duplicated word: "directory directory-selectors".

This issue also appears on line 35 of the same file.

/// Parse one raw `--filter` selector string against `prefix` (the
/// directory directory-selectors resolve relative to).

pacquet/crates/workspace-projects-graph/src/create_projects_graph.rs:204

  • resolve_directory only does an exact lexical-normalized lookup. Upstream createProjectsGraph has an additional slow-path scan to handle case mismatches on case-insensitive filesystems (so link:../B can still match a workspace project rooted at ../b). Without a similar fallback, inter-project edges may be missed on Windows/macOS when dependency path casing differs from the actual directory casing.

This issue also appears on line 198 of the same file.

/// Resolve a local-path dependency to a sibling by directory: join the
/// path onto the importer's root, normalize, and look it up in the
/// by-directory index.
fn resolve_directory(importer: usize, path: &str, lookups: &Lookups) -> Option<PathBuf> {
    let resolved = lexical_normalize(&lookups.node_keys[importer].join(path));
    lookups.by_dir.get(&resolved).map(|&index| lookups.node_keys[index].clone())
}

pacquet/crates/workspace-projects-filter/src/filter.rs:143

  • filter_graph clones adjacency lists on every traversal (node.dependencies.clone() and graph.get(id).cloned()), which can become a noticeable overhead for large workspaces because each visited node allocates and copies its edge list. Consider changing pick_subgraph/adj to borrow adjacency slices (e.g., Fn(&Path) -> Option<&[PathBuf]>) so traversal can walk without cloning.

This issue also appears on line 137 of the same file.

    let forward = |id: &Path| projects_graph.get(id).map(|node| node.dependencies.clone());
    let reversed_graph = selectors
        .iter()
        .any(|selector| selector.include_dependents)
        .then(|| reverse_graph(projects_graph));
    let reverse = |id: &Path| reversed_graph.as_ref().and_then(|graph| graph.get(id).cloned());

pacquet/crates/workspace-projects-filter/src/glob.rs:21

  • In is_match, only the pattern is normalized to forward slashes. On Windows, PathBuf::to_string_lossy() typically yields \ separators for project root dirs, so glob dir filtering will fail to match even when the logical paths line up. Normalize the candidate string too (and keep the normalization buffer alive while splitting).

This issue also appears on line 13 of the same file.

pub fn is_match(candidate: &str, pattern: &str) -> bool {
    let pattern = pattern.replace('\\', "/");
    let pattern = pattern.strip_suffix('/').unwrap_or(&pattern);
    let candidate = candidate.strip_suffix('/').unwrap_or(candidate);

    let pattern_segments: Vec<&str> = pattern.split('/').collect();
    let candidate_segments: Vec<&str> = candidate.split('/').collect();
    match_segments(&pattern_segments, &candidate_segments)
}

pacquet/crates/workspace-projects-filter/src/parse_project_selector.rs:36

  • Doc comment has a duplicated word: "directory directory-selectors".

This issue also appears on line 35 of the same file.

/// Parse one raw `--filter` selector string against `prefix` (the
/// directory directory-selectors resolve relative to).

pacquet/crates/workspace-projects-graph/src/create_projects_graph.rs:204

  • resolve_directory only does an exact lexical-normalized lookup. Upstream createProjectsGraph has an additional slow-path scan to handle case mismatches on case-insensitive filesystems (so link:../B can still match a workspace project rooted at ../b). Without a similar fallback, inter-project edges may be missed on Windows/macOS when dependency path casing differs from the actual directory casing.

This issue also appears on line 198 of the same file.

/// Resolve a local-path dependency to a sibling by directory: join the
/// path onto the importer's root, normalize, and look it up in the
/// by-directory index.
fn resolve_directory(importer: usize, path: &str, lookups: &Lookups) -> Option<PathBuf> {
    let resolved = lexical_normalize(&lookups.node_keys[importer].join(path));
    lookups.by_dir.get(&resolved).map(|&index| lookups.node_keys[index].clone())
}

pacquet/crates/workspace-projects-filter/src/filter.rs:143

  • filter_graph clones adjacency lists on every traversal (node.dependencies.clone() and graph.get(id).cloned()), which can become a noticeable overhead for large workspaces because each visited node allocates and copies its edge list. Consider changing pick_subgraph/adj to borrow adjacency slices (e.g., Fn(&Path) -> Option<&[PathBuf]>) so traversal can walk without cloning.

This issue also appears on line 137 of the same file.

    let forward = |id: &Path| projects_graph.get(id).map(|node| node.dependencies.clone());
    let reversed_graph = selectors
        .iter()
        .any(|selector| selector.include_dependents)
        .then(|| reverse_graph(projects_graph));
    let reverse = |id: &Path| reversed_graph.as_ref().and_then(|graph| graph.get(id).cloned());


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@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

🧹 Nitpick comments (3)
pacquet/plans/TEST_PORTING.md (2)

266-266: ⚡ Quick win

Clarify the directional reference to hoist stubs.

The phrase "the two known_failures hoist stubs below stay" is confusing because the referenced hoist stubs are in the "Hoisting" section (lines 123-124), which appears above this new section in the file, not below.

📝 Suggested clarification
-selected projects is still a follow-up (the install fan-out is
-unfiltered, so the two `known_failures` hoist stubs below stay).
+selected projects is still a follow-up (the install fan-out is
+unfiltered, so the two `known_failures` hoist stubs in the "Hoisting"
+section remain blocked).
🤖 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/plans/TEST_PORTING.md` at line 266, The sentence referencing "the two
`known_failures` hoist stubs below stay" is misleading because the Hoisting
section (which contains those `known_failures` stubs) is above this paragraph;
update the sentence in TEST_PORTING.md to explicitly reference the Hoisting
section or change "below" to "above" (e.g., "the two `known_failures` hoist
stubs in the 'Hoisting' section remain") so readers can locate the
`known_failures` hoist stubs unambiguously.

300-302: 💤 Low value

Make stubbed test documentation consistent.

The first stubbed test includes the test name in parentheses (git_diff_selection_unimplemented), but the next two only say "Stubbed." For consistency and discoverability, either include the stub name for all three or omit it for all three.

📝 Suggested fix for consistency

If the stub names differ, include them:

-- [x] `TypeScript repo: workspace/projects-filter/test/index.ts:348` `select changed packages`. Stubbed (`git_diff_selection_unimplemented`).
-- [x] `TypeScript repo: workspace/projects-filter/test/index.ts:480` `select changed packages when operating under a git worktree`. Stubbed.
-- [x] `TypeScript repo: workspace/projects-filter/test/index.ts:553` `selection should fail when diffing to a branch that does not exist`. Stubbed.
++ [x] `TypeScript repo: workspace/projects-filter/test/index.ts:348` `select changed packages`. Stubbed (`git_diff_selection_unimplemented`).
++ [x] `TypeScript repo: workspace/projects-filter/test/index.ts:480` `select changed packages when operating under a git worktree`. Stubbed (`git_diff_selection_unimplemented`).
++ [x] `TypeScript repo: workspace/projects-filter/test/index.ts:553` `selection should fail when diffing to a branch that does not exist`. Stubbed (`git_diff_selection_unimplemented`).

Or, if they all use the same stub and the rejection path is already documented on line 296, omit the parenthetical entirely:

-- [x] `TypeScript repo: workspace/projects-filter/test/index.ts:348` `select changed packages`. Stubbed (`git_diff_selection_unimplemented`).
++ [x] `TypeScript repo: workspace/projects-filter/test/index.ts:348` `select changed packages`. Stubbed.
🤖 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/plans/TEST_PORTING.md` around lines 300 - 302, The three checklist
items referencing tests "select changed packages"
(workspace/projects-filter/test/index.ts:348), "select changed packages when
operating under a git worktree" (index.ts:480), and "selection should fail when
diffing to a branch that does not exist" (index.ts:553) are inconsistent: the
first shows the stub name (git_diff_selection_unimplemented) in parentheses
while the other two just say "Stubbed." Make them consistent by either adding
the stub name "(git_diff_selection_unimplemented)" to the two latter entries or
removing the parenthetical from the first entry so all three simply read
"Stubbed."; update the three checklist lines accordingly and ensure the exact
stub identifier git_diff_selection_unimplemented is used if you choose to
include names.
pacquet/crates/cli/src/cli_args.rs (1)

150-151: 💤 Low value

Consider moving instead of cloning.

filter and filter_prod are owned values (moved out at line 112) and are only used once here. Direct assignment would avoid the clone:

-                    cfg.filter.clone_from(&filter);
-                    cfg.filter_prod.clone_from(&filter_prod);
+                    cfg.filter = filter;
+                    cfg.filter_prod = filter_prod;
🤖 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/src/cli_args.rs` around lines 150 - 151, The two lines use
clone_from on owned values; replace these clones by moving the owned values into
the config fields instead of cloning—i.e., stop calling
cfg.filter.clone_from(&filter) and cfg.filter_prod.clone_from(&filter_prod) and
assign the owned filter and filter_prod into cfg.filter and cfg.filter_prod
respectively so the ownership is moved rather than cloned.
🤖 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/workspace-projects-filter/src/path_util.rs`:
- Around line 4-12: lexical_join currently uses Path::join which replaces the
prefix when rel is absolute; to match Node/pnpm path.join semantics (where an
absolute rel is concatenated), change lexical_join to detect when rel is an
absolute-looking string and strip leading root separators (e.g. '/' and '\\')
before joining so prefix.join(<stripped rel>) is used, then pass the result to
lexical_normalize; update logic in the lexical_join function (and any helper
that parses rel) to trim leading separators rather than relying on Path::join to
retain prefix.

---

Nitpick comments:
In `@pacquet/crates/cli/src/cli_args.rs`:
- Around line 150-151: The two lines use clone_from on owned values; replace
these clones by moving the owned values into the config fields instead of
cloning—i.e., stop calling cfg.filter.clone_from(&filter) and
cfg.filter_prod.clone_from(&filter_prod) and assign the owned filter and
filter_prod into cfg.filter and cfg.filter_prod respectively so the ownership is
moved rather than cloned.

In `@pacquet/plans/TEST_PORTING.md`:
- Line 266: The sentence referencing "the two `known_failures` hoist stubs below
stay" is misleading because the Hoisting section (which contains those
`known_failures` stubs) is above this paragraph; update the sentence in
TEST_PORTING.md to explicitly reference the Hoisting section or change "below"
to "above" (e.g., "the two `known_failures` hoist stubs in the 'Hoisting'
section remain") so readers can locate the `known_failures` hoist stubs
unambiguously.
- Around line 300-302: The three checklist items referencing tests "select
changed packages" (workspace/projects-filter/test/index.ts:348), "select changed
packages when operating under a git worktree" (index.ts:480), and "selection
should fail when diffing to a branch that does not exist" (index.ts:553) are
inconsistent: the first shows the stub name (git_diff_selection_unimplemented)
in parentheses while the other two just say "Stubbed." Make them consistent by
either adding the stub name "(git_diff_selection_unimplemented)" to the two
latter entries or removing the parenthetical from the first entry so all three
simply read "Stubbed."; update the three checklist lines accordingly and ensure
the exact stub identifier git_diff_selection_unimplemented is used if you choose
to include names.
🪄 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: 413a711d-7a28-4f80-ba71-bff28fc21d85

📥 Commits

Reviewing files that changed from the base of the PR and between 72d997c and 7aa7798.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (21)
  • Cargo.toml
  • pacquet/crates/cli/src/cli_args.rs
  • pacquet/crates/cli/src/cli_args/tests.rs
  • pacquet/crates/config/src/lib.rs
  • pacquet/crates/package-manager/src/install_package_from_registry/tests.rs
  • pacquet/crates/workspace-projects-filter/Cargo.toml
  • pacquet/crates/workspace-projects-filter/src/filter.rs
  • pacquet/crates/workspace-projects-filter/src/filter/tests.rs
  • pacquet/crates/workspace-projects-filter/src/glob.rs
  • pacquet/crates/workspace-projects-filter/src/glob/tests.rs
  • pacquet/crates/workspace-projects-filter/src/lib.rs
  • pacquet/crates/workspace-projects-filter/src/parse_project_selector.rs
  • pacquet/crates/workspace-projects-filter/src/parse_project_selector/tests.rs
  • pacquet/crates/workspace-projects-filter/src/path_util.rs
  • pacquet/crates/workspace-projects-graph/Cargo.toml
  • pacquet/crates/workspace-projects-graph/src/base_project.rs
  • pacquet/crates/workspace-projects-graph/src/create_projects_graph.rs
  • pacquet/crates/workspace-projects-graph/src/create_projects_graph/tests.rs
  • pacquet/crates/workspace-projects-graph/src/graph.rs
  • pacquet/crates/workspace-projects-graph/src/lib.rs
  • pacquet/plans/TEST_PORTING.md
📜 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: Lint and Test (ubuntu-latest)
  • GitHub Check: Lint and Test (macos-latest)
  • GitHub Check: Lint and Test (windows-latest)
  • GitHub Check: Code Coverage
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Compile & Lint
🧰 Additional context used
📓 Path-based instructions (1)
pacquet/**/*.rs

📄 CodeRabbit inference engine (pacquet/AGENTS.md)

pacquet/**/*.rs: Warnings are errors (--deny warnings in lint). Do not silence them with #[allow(...)] unless there is a specific, justified reason.
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: https://rust-lang.github.io/api-guidelines/naming.html
No star imports inside module bodies. Write use super::{Foo, bar} instead of use super::*;, and the same for any other glob whose target is a module you control. Two forms stay allowed: external-crate preludes such as use rayon::prelude::*; and root-of-module re-exports such as pub use submodule::*; in a lib.rs.
Doc comments (///, //!) document the contract: preconditions, postconditions, panics, the reason the function exists. They are not a re-narration of the body.
Do not restate at call sites what the callee's doc comment already says. If /// on the function says 'no-op when …', the caller should not repeat that. Update the doc once; let every call site benefit.
Tests are documentation. Do not duplicate them in prose. If a behavioral scenario, edge case, failure mode, or worked example is already captured by a test, do not also narrate it in the doc comment on the implementation. The same applies in reverse: a test's own doc comment should not re-explain what the asserts already say, only the why if it is not obvious.
Use // SAFETY:, // TODO:, and similar prefixes to signal hidden invariants or known follow-ups that a reader cannot recover from the code alone.
When editing existing code, do not break a method chain (including pipe-trait .pipe(...) chains) into intermediate let bindings unless you can justify the rewrite. Valid justifications include a chain that fails to compile after your...

Files:

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

Applied to files:

  • pacquet/crates/workspace-projects-filter/src/path_util.rs
  • pacquet/crates/cli/src/cli_args.rs
  • pacquet/crates/workspace-projects-graph/src/graph.rs
  • pacquet/crates/package-manager/src/install_package_from_registry/tests.rs
  • pacquet/crates/config/src/lib.rs
  • pacquet/crates/workspace-projects-graph/src/lib.rs
  • pacquet/crates/workspace-projects-graph/src/base_project.rs
  • pacquet/crates/workspace-projects-filter/src/lib.rs
  • pacquet/crates/cli/src/cli_args/tests.rs
  • pacquet/crates/workspace-projects-filter/src/parse_project_selector/tests.rs
  • pacquet/crates/workspace-projects-filter/src/parse_project_selector.rs
  • pacquet/crates/workspace-projects-graph/src/create_projects_graph.rs
  • pacquet/crates/workspace-projects-filter/src/glob/tests.rs
  • pacquet/crates/workspace-projects-filter/src/glob.rs
  • pacquet/crates/workspace-projects-filter/src/filter.rs
  • pacquet/crates/workspace-projects-filter/src/filter/tests.rs
  • pacquet/crates/workspace-projects-graph/src/create_projects_graph/tests.rs
📚 Learning: 2026-05-22T00:08:44.646Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11837
File: pacquet/crates/resolving-npm-resolver/src/pick_package.rs:33-51
Timestamp: 2026-05-22T00:08:44.646Z
Learning: In the pnpm/pnpm repo’s pacquet Rust crates, do not flag Unicode ellipsis characters (U+2026, `…`) in Rust doc comments (`///` / `/** */`) as a lint violation. The pacquet crate’s `dylint.toml` only enables `perfectionist::derive_ordering`, and the Dylint `unicode-ellipsis` rule is not enabled for this project—so `…` in doc comments is an intentional, repo-consistent style.

Applied to files:

  • pacquet/crates/workspace-projects-filter/src/path_util.rs
  • pacquet/crates/cli/src/cli_args.rs
  • pacquet/crates/workspace-projects-graph/src/graph.rs
  • pacquet/crates/package-manager/src/install_package_from_registry/tests.rs
  • pacquet/crates/config/src/lib.rs
  • pacquet/crates/workspace-projects-graph/src/lib.rs
  • pacquet/crates/workspace-projects-graph/src/base_project.rs
  • pacquet/crates/workspace-projects-filter/src/lib.rs
  • pacquet/crates/cli/src/cli_args/tests.rs
  • pacquet/crates/workspace-projects-filter/src/parse_project_selector/tests.rs
  • pacquet/crates/workspace-projects-filter/src/parse_project_selector.rs
  • pacquet/crates/workspace-projects-graph/src/create_projects_graph.rs
  • pacquet/crates/workspace-projects-filter/src/glob/tests.rs
  • pacquet/crates/workspace-projects-filter/src/glob.rs
  • pacquet/crates/workspace-projects-filter/src/filter.rs
  • pacquet/crates/workspace-projects-filter/src/filter/tests.rs
  • pacquet/crates/workspace-projects-graph/src/create_projects_graph/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/workspace-projects-filter/src/path_util.rs
  • pacquet/crates/cli/src/cli_args.rs
  • pacquet/crates/workspace-projects-graph/src/graph.rs
  • pacquet/crates/package-manager/src/install_package_from_registry/tests.rs
  • pacquet/crates/config/src/lib.rs
  • pacquet/crates/workspace-projects-graph/src/lib.rs
  • pacquet/crates/workspace-projects-graph/src/base_project.rs
  • pacquet/crates/workspace-projects-filter/src/lib.rs
  • pacquet/crates/cli/src/cli_args/tests.rs
  • pacquet/crates/workspace-projects-filter/src/parse_project_selector/tests.rs
  • pacquet/crates/workspace-projects-filter/src/parse_project_selector.rs
  • pacquet/crates/workspace-projects-graph/src/create_projects_graph.rs
  • pacquet/crates/workspace-projects-filter/src/glob/tests.rs
  • pacquet/crates/workspace-projects-filter/src/glob.rs
  • pacquet/crates/workspace-projects-filter/src/filter.rs
  • pacquet/crates/workspace-projects-filter/src/filter/tests.rs
  • pacquet/crates/workspace-projects-graph/src/create_projects_graph/tests.rs
🔇 Additional comments (25)
pacquet/crates/workspace-projects-graph/Cargo.toml (1)

1-22: LGTM!

pacquet/crates/workspace-projects-graph/src/base_project.rs (1)

1-31: LGTM!

pacquet/crates/workspace-projects-graph/src/graph.rs (1)

1-25: LGTM!

pacquet/crates/workspace-projects-graph/src/lib.rs (1)

17-26: LGTM!

pacquet/crates/workspace-projects-filter/Cargo.toml (1)

1-26: LGTM!

pacquet/crates/workspace-projects-graph/src/create_projects_graph.rs (2)

12-136: LGTM!


161-298: LGTM!

pacquet/crates/workspace-projects-graph/src/create_projects_graph/tests.rs (1)

8-253: LGTM!

Cargo.toml (1)

63-64: LGTM!

pacquet/crates/cli/src/cli_args.rs (2)

47-67: LGTM!


112-112: LGTM!

pacquet/crates/cli/src/cli_args/tests.rs (1)

27-64: LGTM!

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

819-832: LGTM!

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

73-74: LGTM!

pacquet/crates/workspace-projects-filter/src/parse_project_selector.rs (2)

11-98: LGTM!


103-204: LGTM!

pacquet/crates/workspace-projects-filter/src/parse_project_selector/tests.rs (1)

18-258: LGTM!

pacquet/crates/workspace-projects-filter/src/glob.rs (1)

13-68: LGTM!

pacquet/crates/workspace-projects-filter/src/glob/tests.rs (1)

3-48: LGTM!

pacquet/crates/workspace-projects-filter/src/filter.rs (4)

86-114: LGTM!


123-239: LGTM!


244-305: LGTM!


312-395: LGTM!

pacquet/crates/workspace-projects-filter/src/filter/tests.rs (1)

12-575: LGTM!

pacquet/crates/workspace-projects-filter/src/lib.rs (1)

20-32: LGTM!

Comment thread pacquet/crates/workspace-projects-filter/src/path_util.rs
Copilot AI review requested due to automatic review settings May 29, 2026 10:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 21 out of 22 changed files in this pull request and generated 4 comments.

Comment thread pacquet/crates/workspace-projects-filter/src/glob.rs
Comment thread pacquet/crates/cli/src/cli_args.rs
Comment thread pacquet/crates/workspace-projects-filter/src/filter.rs Outdated
claude and others added 11 commits May 29, 2026 15:42
…ject filtering

Port pnpm's `@pnpm/workspace.projects-graph` and
`@pnpm/workspace.projects-filter` as the new
`pacquet-workspace-projects-graph` and `pacquet-workspace-projects-filter`
crates, and expose the `--filter` / `--filter-prod` CLI flags (stored into
`Config::filter` / `Config::filter_prod`).

- `parse_project_selector` parses selector strings (name glob, `...`
  dependents/dependencies, `^` exclude-self, `!` exclude, `{dir}`, `[since]`).
- `create_projects_graph` computes inter-project edges via `workspace:`,
  semver version/range, and local-path resolution.
- `filter_workspace_projects` resolves selectors against the graph;
  `filter_projects` builds the graph(s) and applies a `WorkspaceFilter` list.

The `[since]` changed-packages selector parses but is rejected with
`FilterError::UnsupportedDiffSelector` (git-diff project selection is not
ported). As with `--recursive`, the install still materializes every
workspace importer in one shared pass, so narrowing the install to the
selected projects remains a follow-up; the `known_failures` hoist stubs for
`--filter` selected-projects installs are unchanged.

Ports the upstream `parseProjectSelector` and `filterWorkspaceProjects` tests;
the diff-based cases are stubbed as `known_failures`. Updates
plans/TEST_PORTING.md.

https://claude.ai/code/session_01BsrsKRRp5ntwqnTB7YREes
`parse_project_selector` is both a module and a re-exported function at
the crate root, so the bare intra-doc link was ambiguous and failed
`cargo doc` under `-D warnings`. Link to the function with `()`.

https://claude.ai/code/session_01BsrsKRRp5ntwqnTB7YREes
…ph parity

Review-driven fixes for parity with upstream `@pnpm/workspace.projects-filter`
and `projects-graph`:

- `parse_project_selector`: the hand-rolled selector matcher now backtracks
  like the upstream regex `^([^.][^{}[\]]*)?(\{[^}]+\})?(\[[^\]]+\])?$`. The
  name group's first char may be a brace/bracket, so `!{foo` is an exclude
  selector (was previously parsed as include, inverting selection) and
  `{[master]` keeps its `[master]` diff. Added cases.
- `create_projects_graph`: a path-style `workspace:` token (`workspace:../foo`)
  now resolves by directory rather than failing version resolution and
  emitting a spurious `unmatched` entry, matching upstream's
  `workspacePrefToNpm` + `npa.resolve` path.
- `glob::segment_match`: replaced the greedy single-pass match with a
  backtracking wildcard match so multiple `*` in one path segment
  (`{packages/a*-*}`) match correctly.
- Deduplicated `lexical_normalize`: it now lives in
  `pacquet-workspace-projects-graph` and the filter crate reuses it.

https://claude.ai/code/session_01BsrsKRRp5ntwqnTB7YREes
…rustdoc CI

- Rename closure params off single letters (`|p|` → `|project|`,
  `|&v|` → `|&version|`) to satisfy `perfectionist::single_letter_closure_param`.
- Use raw strings for the `.\` / `..\` path prefixes
  (`perfectionist::prefer_raw_string`) and add the missing `matches!`
  trailing comma (`perfectionist::macro_trailing_comma`).
- Disambiguate every `create_projects_graph` intra-doc link with `()`
  (module and re-exported function share the name), fixing
  `rustdoc::broken_intra_doc_links` under nightly.
- Rename the `unparseable_braces…` test to `unparsable_…` (typos).

https://claude.ai/code/session_01BsrsKRRp5ntwqnTB7YREes
…gs; add filter-prod coverage

- Reorder derives to `prefix_then_alphabetical` (Default before Clone)
  for `perfectionist::derive_ordering`.
- Replace U+2026 `…` with ASCII `...` in comments
  (`perfectionist::unicode_ellipsis_in_comments`).
- Add coverage the cloud review flagged as missing: the `--filter-prod`
  production-only graph (dev edges dropped), the prod-then-all union
  order, and `unmatched_filters` for a path selector that matches
  nothing.
- Document the clap global-flag limitation: `--filter` occurrences only
  merge within one side of the subcommand boundary.

Verified locally with `cargo dylint --all` and `cargo doc -D warnings`.

https://claude.ai/code/session_01BsrsKRRp5ntwqnTB7YREes
…verage

Add unit tests for every reachable line CodeCov flagged uncovered, and
delete the one unreachable branch:

- parser: empty input (`...`), empty brace group (`{}`), dot-prefixed
  name (`.foo`).
- filter: selector combining a directory and a name pattern, a selector
  with no name/dir/diff (`UnsupportedSelector`), and the `is_subdir`
  contract (incomparable / descendant / equal paths).
- glob: trailing `*` after an exact prefix.
- graph: `./`-segment collapse in a path spec, and the Windows
  drive-prefix predicate.
- Remove the unreachable `candidates.is_empty()` guard in
  `resolve_by_name_version` (`by_name` never holds empty vecs; `?`
  already covers absence).

All pure in-memory logic, so plain unit tests with constructed inputs
fit — no integration, filesystem fixtures, or DI seam needed. Verified
with `cargo dylint --all` and `cargo doc -D warnings`.

https://claude.ai/code/session_01BsrsKRRp5ntwqnTB7YREes
…normalize

The graph crate carried its own copy of `lexical_normalize`, duplicating
`pacquet-fs`'s already fully-tested helper. For every input the graph
passes (absolute project roots and absolute-base joins) the two behave
identically — they differ only on unanchored leading `..`, which never
reaches this code. Drop the duplicate and re-export the `pacquet-fs` one,
removing the lone uncovered `Component::CurDir` branch in the process
(the shared helper already covers it).

https://claude.ai/code/session_0144D5iK3jHxrWENJFq6m8KF
`pick_subgraph` skips a node it has already inserted via the
`walked.contains` guard, the branch that keeps the walk terminating on
diamonds and cycles. None of the existing fixtures reach a node by two
paths, so add a diamond graph (`top` -> {`left`, `right`} -> `shared`)
whose `...` dependency walk visits `shared` twice, exercising the guard.

https://claude.ai/code/session_0144D5iK3jHxrWENJFq6m8KF
Drop the second paragraph narrating the current install behavior
("still materializes every importer ... follow-up ... stored for
parity"). That described a transient state that goes stale the moment
pacquet consumes the filter during install. Keep only the stable
contract: what the field holds, that it mirrors pnpm's CLI-only
`filter` array, and that only the CLI layer populates it.

https://claude.ai/code/session_0144D5iK3jHxrWENJFq6m8KF
…y selectors like path.join

Upstream's `parseProjectSelector` resolves directory selectors with
`path.join(prefix, rel)`, which concatenates even when `rel` is absolute
(`path.join('/ws', '/pkg')` -> `/ws/pkg`). Rust's `Path::join` instead
drops `prefix` for an absolute `rel`, so `lexical_join` resolved a `{/pkg}`
selector to `/pkg` rather than `/ws/pkg`, diverging from pnpm.

Strip leading separators from `rel` before joining so an absolute
selector extends the prefix, and correct the doc comment, which described
the wrong (Rust) semantics. Add a parse test covering `{/pkg}`.
…rators in dir globs

Address PR review comments:

- glob: normalize backslashes to `/` in the candidate path as well as the
  pattern, so a Windows `ProjectRootDir` rendered with backslashes by
  `PathBuf::to_string_lossy()` still matches a glob dir selector. Matches
  micromatch's effective separator handling.
- filter: include the offending selector in `UnsupportedSelector`,
  mirroring upstream's `Unsupported project selector: ${JSON.stringify(selector)}`.
- cli: add a regression test locking the documented clap mixed-placement
  limitation for the global `-F` flag.
- parse_project_selector: fix a doc typo ("directory directory-selectors").
@zkochan zkochan force-pushed the claude/gifted-einstein-vuowL branch from d35b7a8 to cefd4cf Compare May 29, 2026 13:53
@zkochan zkochan merged commit faf8484 into main May 29, 2026
28 checks passed
@zkochan zkochan deleted the claude/gifted-einstein-vuowL branch May 29, 2026 14:18
KSXGitHub pushed a commit that referenced this pull request Jun 1, 2026
…ral doc

- Re-pad the [dependencies] table so every `=` aligns to the widest
  key (pacquet-resolving-parse-wanted-dependency, added by this PR).
  The merge from origin/main introduced a new dep that broke the
  uniform column alignment taplo enforces.
- Update exec.rs's deferral doc: the workspace-projects-filter /
  workspace-projects-graph crates and the global --filter/--recursive
  flags landed on main (#11959, #12000), so the selection layer now
  exists; only the recursive runner is still missing.

https://claude.ai/code/session_01PPwhryEFfN4iyZkVsjy2Hf
KSXGitHub pushed a commit that referenced this pull request Jun 1, 2026
The perfectionist 0.0.0-rc.17 upgrade on main flags bare `#NNN`
references as ambiguous (could be issue / PR / colour / etc.). Replace
the two #11959 / #12000 references with explicit pull-request URLs,
matching pacquet's existing doc-comment convention.

https://claude.ai/code/session_01PPwhryEFfN4iyZkVsjy2Hf
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants