feat(cli): add pacquet why command#12497
Conversation
Code Review by Qodo
1. Memoization breaks traversal
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a Changespacquet why subcommand
Sequence DiagramsequenceDiagram
participant User
participant CLI as CliArgs::run
participant WhyArgs as WhyArgs::run
participant Lockfile as State::lockfile
participant Builder as build_dependents_tree
participant Renderer as render_tree
User->>CLI: pacquet why <pattern> [--depth N]
CLI->>WhyArgs: dispatch Why(args) with state
WhyArgs->>Lockfile: load lockfile or return error if missing
WhyArgs->>Builder: build reverse adjacency from lockfile snapshots and importer deps
Builder->>Builder: match packages against names and aliases
Builder-->>WhyArgs: sorted WhyResult entries with DependentNode trees
WhyArgs->>Renderer: render_tree(results, max_depth)
Renderer-->>WhyArgs: formatted Unicode tree string with bold/dim styling
WhyArgs-->>User: write to stdout
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested reviewers
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
PR Summary by QodoAdd Description
Diagram
High-Level Assessment
Files changed (6)
|
cb1538f to
8848697
Compare
There was a problem hiding this comment.
Pull request overview
Adds a new pacquet why command (Rust CLI) to display reverse dependency trees for matched packages, and adjusts pnpm’s hoisted symlink handling to correctly replace stale hoisted links when switching to the global virtual store (GVS).
Changes:
- Add
pacquet whycommand implementation with--depthsupport plus unit/integration tests. - Wire the new
Whysubcommand into the pacquet CLI command router. - Fix pnpm hoisted symlink replacement when migrating from non-GVS to GVS, and add an e2e test covering the scenario.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
pnpm/test/install/globalVirtualStore.ts |
Adds an e2e regression test asserting stale hoisted symlinks are replaced when enabling GVS. |
installing/linking/hoist/src/index.ts |
Allows overwriting hoisted symlinks that point into pnpm-internal .pnpm locations during relinking (migration to GVS). |
pacquet/crates/cli/src/cli_args.rs |
Registers the new why module and exposes pacquet why as a CLI subcommand. |
pacquet/crates/cli/src/cli_args/why.rs |
Implements pacquet why: lockfile read, reverse graph traversal, and tree rendering. |
pacquet/crates/cli/src/cli_args/why/tests.rs |
Adds unit tests for tree rendering and small helpers. |
pacquet/crates/cli/tests/why.rs |
Adds integration tests for pacquet why behavior and error cases. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /// Exclude peer dependencies from the output. | ||
| #[clap(long)] | ||
| pub exclude_peers: bool, |
There was a problem hiding this comment.
Fixed: removed --exclude-peers entirely since it was a no-op.
| let matched = matcher.matches(&name); | ||
|
|
||
| if !matched { | ||
| let alias_matched = importer_edges | ||
| .iter() | ||
| .any(|(alias, target)| target.as_ref() == Some(key) && matcher.matches(alias)); | ||
| if !alias_matched { | ||
| continue; | ||
| } | ||
| } |
There was a problem hiding this comment.
Fixed: alias matching now checks all incoming edges in reverse_map (all parents), not just importer edges.
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 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/cli/src/cli_args.rs`:
- Around line 304-308: The CliCommand::Why variant is calling state(false) which
disables lockfile requirement, but the why command needs lockfile data to
function properly. Change the state(false) call in the CliCommand::Why branch to
state(true) to ensure the lockfile is required and loaded before the why command
executes.
In `@pacquet/crates/cli/src/cli_args/why.rs`:
- Around line 42-45: The exclude_peers boolean flag is declared in the struct
but is never actually consulted when building or rendering the dependency tree
output. To fix this, locate where the tree is constructed and rendered in the
why command implementation, and add logic to filter out peer dependencies from
the tree when the exclude_peers flag is set to true. Ensure the implementation
matches pnpm's behavior for the --exclude-peers flag to maintain consistency
with the coding guidelines.
- Around line 93-95: The forward_edges HashMap uses nondeterministic iteration
order, causing tree output ordering to vary between runs. Replace the HashMap
type with an ordered map structure (such as BTreeMap or IndexMap) for the
forward_edges declaration to ensure consistent, deterministic output ordering
when the map is iterated at line 144 and throughout the walk_reverse function
and related logic. This ensures the tree output matches pnpm's behavior
consistently across runs.
- Around line 182-236: The walk_reverse function performs unbounded recursion
over the dependency graph without any depth limit, creating a denial-of-service
vulnerability when processing malicious lockfiles with extremely deep dependency
chains. Add a maximum depth parameter to the walk_reverse function signature and
check it at the beginning of the recursive calls, returning early with a
DependentNode containing limited information if the maximum depth is exceeded.
This ensures that even with untrusted lockfile input, the traversal cannot cause
a stack overflow regardless of nesting depth.
In `@pacquet/crates/cli/tests/why.rs`:
- Around line 68-71: The transitive test in the why.rs file is not properly
testing transitive dependency traversal. Currently, the write_manifest call
includes both PKG and DEP as direct dependencies, which means DEP is installed
directly rather than only accessible transitively through PKG. To fix this,
modify the manifest format string in the write_manifest call to only include PKG
as a direct dependency, removing DEP from the direct dependencies. This will
ensure that pacquet why DEP must traverse back through PKG to locate DEP,
properly validating the transitive reverse traversal logic rather than just
finding a direct dependency.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: b96e3688-b7a0-4815-a0ee-e6ae9f1184b4
📒 Files selected for processing (6)
installing/linking/hoist/src/index.tspacquet/crates/cli/src/cli_args.rspacquet/crates/cli/src/cli_args/why.rspacquet/crates/cli/src/cli_args/why/tests.rspacquet/crates/cli/tests/why.rspnpm/test/install/globalVirtualStore.ts
There was a problem hiding this comment.
♻️ Duplicate comments (1)
pacquet/crates/cli/src/cli_args.rs (1)
304-308:⚠️ Potential issue | 🔴 Critical | ⚡ Quick win
whyrequires lockfile data but usesstate(false).The
whycommand builds a reverse dependency graph from lockfile snapshots and cannot function without it (seeWhyArgs::runat why.rs:48-83, which errors when lockfile isNone). Usingstate(false)means whenlockfile=falseis set in config, the lockfile won't be loaded even ifpnpm-lock.yamlexists on disk, and the command will fail with "No lockfile" error.🔧 Proposed fix
- // `why` is a read-only query like `outdated`: it prints a - // reverse dependency tree to stdout and never installs. CliCommand::Why(args) => { - args.run(state(false)?).await?; + args.run(state(true)?).await?; }🤖 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 304 - 308, The `why` command requires lockfile data to build a reverse dependency graph, but it is currently calling `state(false)` which prevents the lockfile from being loaded. In the CliCommand::Why handler, change the `state(false)` call to `state(true)` to ensure the lockfile is loaded from disk before the `args.run()` method executes, since WhyArgs::run will error if the lockfile is None.
🤖 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.
Duplicate comments:
In `@pacquet/crates/cli/src/cli_args.rs`:
- Around line 304-308: The `why` command requires lockfile data to build a
reverse dependency graph, but it is currently calling `state(false)` which
prevents the lockfile from being loaded. In the CliCommand::Why handler, change
the `state(false)` call to `state(true)` to ensure the lockfile is loaded from
disk before the `args.run()` method executes, since WhyArgs::run will error if
the lockfile is None.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 7b10e5c3-77dd-4751-9c2e-57a0723b3e1f
📒 Files selected for processing (4)
pacquet/crates/cli/src/cli_args.rspacquet/crates/cli/src/cli_args/why.rspacquet/crates/cli/src/cli_args/why/tests.rspacquet/crates/cli/tests/why.rs
🚧 Files skipped from review as they are similar to previous changes (3)
- pacquet/crates/cli/src/cli_args/why/tests.rs
- pacquet/crates/cli/tests/why.rs
- pacquet/crates/cli/src/cli_args/why.rs
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #12497 +/- ##
==========================================
- Coverage 88.18% 88.14% -0.04%
==========================================
Files 313 314 +1
Lines 43074 43402 +328
==========================================
+ Hits 37983 38255 +272
- Misses 5091 5147 +56 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Code review by qodo was updated up to the latest commit 8848697 |
8848697 to
0a78616
Compare
0a78616 to
39c7ac7
Compare
| for edges in reverse_map.values_mut() { | ||
| edges.sort_by(|a, b| a.0.to_string().cmp(&b.0.to_string())); | ||
| } |
There was a problem hiding this comment.
2. Costly unstable edge sorting 🐞 Bug ➹ Performance
Reverse-edge lists are sorted using to_string() inside the comparator, causing repeated heap allocations during sort and unnecessary CPU on large lockfiles. Sorting only by parent key also leaves ordering undefined when multiple edges share the same parent (notably the root importer), making CLI output ordering nondeterministic.
Agent Prompt
### Issue description
`reverse_map` edge lists are sorted with a comparator that allocates (`to_string()`) on every comparison and does not fully order ties (only parent key).
### Issue Context
This runs on every `pacquet why` invocation and can become expensive for large lockfiles with many reverse edges. Also, leaving ties unordered leads to unstable output ordering.
### Fix Focus Areas
- pacquet/crates/cli/src/cli_args/why.rs[145-147]
- pacquet/crates/cli/src/cli_args/why.rs[113-143]
### Suggested fix
- Replace the comparator with a cached-key sort and include the alias in the key so ties are deterministic, e.g.:
- `edges.sort_by_cached_key(|(parent, alias)| (parent.to_string(), alias.clone()));`
- If you want to avoid allocating `alias.clone()` too, sort by cached parent string and then a borrowed alias via `sort_by` on `(&cached_parent, &alias)` (or store alias as `PkgName`/interned string).
- Add/adjust a test to assert stable ordering for multiple direct dependents from the root importer.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| fn walk_reverse( | ||
| node_key: &PkgNameVerPeer, | ||
| reverse_map: &HashMap<PkgNameVerPeer, Vec<(PkgNameVerPeer, String)>>, | ||
| visited: &mut HashSet<PkgNameVerPeer>, | ||
| depth: usize, | ||
| ) -> Vec<DependentNode> { | ||
| if depth >= MAX_REVERSE_WALK_DEPTH { | ||
| return vec![]; | ||
| } | ||
|
|
There was a problem hiding this comment.
3. Traversal capped at 64 🐞 Bug ≡ Correctness
walk_reverse hard-stops traversal at depth 64 regardless of --depth, so deep dependency chains can be silently truncated even when the user expects a full tree. This yields incomplete results because the cap happens during tree construction, not just rendering.
Agent Prompt
### Issue description
Reverse traversal is capped by a hard-coded constant (`MAX_REVERSE_WALK_DEPTH`) in `walk_reverse`, independent of the user-facing `--depth` rendering limit. This can truncate the dependency ancestry silently.
### Issue Context
`--depth` currently controls rendering (`render_dependents`) but cannot recover nodes omitted during traversal.
### Fix Focus Areas
- pacquet/crates/cli/src/cli_args/why.rs[79-82]
- pacquet/crates/cli/src/cli_args/why.rs[174-183]
- pacquet/crates/cli/src/cli_args/why.rs[255-266]
### Suggested fix
- Pass a traversal limit into `walk_reverse` (e.g., `max_traversal_depth`) derived from `--depth` when provided (often `depth + 1` depending on your depth semantics), and only fall back to a safety cap when `--depth` is not set.
- If you keep a hard safety cap, emit an explicit marker (or warning) when truncation occurs so users know results are incomplete.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
|
Code review by qodo was updated up to the latest commit 39c7ac7 |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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/cli/src/cli_args/why.rs`:
- Around line 57-60: The error message in the miette::miette! macro call with
code "ERR_PNPM_OUTDATED_NO_LOCKFILE" currently instructs users to run "pacquet
install" but should reference "pnpm install" instead to maintain consistency
with pnpm's exact error messaging. Update the error message string to replace
"pacquet install" with "pnpm install" while keeping the rest of the message
intact.
- Line 197: The `dep_field` is hardcoded to `None` in the tree building logic,
but the rendering infrastructure with `dep_field_name` function and output
formatting exists to display dependency type labels like "(dependencies)" and
"(devDependencies)". Populate `dep_field` with the appropriate dependency group
information during tree construction instead of leaving it as `None`. In the
root importer iteration block, preserve the group information when creating
dependency entries. For non-root packages, leverage the snapshot structure to
determine which dependency group each edge belongs to and set `dep_field` to
that group value accordingly.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: e74ee892-e065-4784-b5ef-3437f887d0bf
📒 Files selected for processing (4)
pacquet/crates/cli/src/cli_args.rspacquet/crates/cli/src/cli_args/why.rspacquet/crates/cli/src/cli_args/why/tests.rspacquet/crates/cli/tests/why.rs
✅ Files skipped from review due to trivial changes (1)
- pacquet/crates/cli/src/cli_args/why/tests.rs
🚧 Files skipped from review as they are similar to previous changes (2)
- pacquet/crates/cli/src/cli_args.rs
- pacquet/crates/cli/tests/why.rs
Integrated-Benchmark Report (Linux)Each scenario reports direct installs and pnpr installs. Bencher consumes pacquet@HEAD and pnpr@HEAD. Scenario: Isolated linker: fresh restore, cold cache + cold store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 3.90601317808,
"stddev": 0.5519630330972025,
"median": 3.7449200425800004,
"user": 3.4300219399999996,
"system": 3.3571336,
"min": 3.53975628458,
"max": 5.422025408580001,
"times": [
5.422025408580001,
3.81092147858,
3.95275730758,
3.96518155458,
3.6812976065800003,
3.74761142058,
3.7422286645800003,
3.6444061435800004,
3.53975628458,
3.5539459115800005
]
},
{
"command": "pacquet@main",
"mean": 3.686008630780001,
"stddev": 0.11934380243895698,
"median": 3.6372999540800004,
"user": 3.46965594,
"system": 3.3395083,
"min": 3.5536837625800004,
"max": 3.92149655458,
"times": [
3.63197739858,
3.80580432158,
3.5974468295800004,
3.60308602058,
3.6426225095800002,
3.8034911105800004,
3.70018330058,
3.6002944995800004,
3.5536837625800004,
3.92149655458
]
},
{
"command": "pnpr@HEAD",
"mean": 2.1669327656800004,
"stddev": 0.1411854218126093,
"median": 2.1505965750800002,
"user": 2.57933024,
"system": 2.9685159999999997,
"min": 2.01690830158,
"max": 2.40522815658,
"times": [
2.1506230415800003,
2.40522815658,
2.33011652258,
2.32791802458,
2.1605621695800004,
2.04748156058,
2.02454006658,
2.0553797045800004,
2.15057010858,
2.01690830158
]
},
{
"command": "pnpr@main",
"mean": 2.0923244716800005,
"stddev": 0.07778512613246513,
"median": 2.09529506258,
"user": 2.63218104,
"system": 2.965376,
"min": 1.9728758345800002,
"max": 2.2614813605800004,
"times": [
2.1039285475800003,
2.03079442858,
2.2614813605800004,
2.1502486275800003,
2.09219754258,
2.0983925825800003,
1.9728758345800002,
2.11238383458,
2.06377977758,
2.03716218058
]
}
]
}Scenario: Isolated linker: fresh restore, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 0.62516206176,
"stddev": 0.015123900250576116,
"median": 0.61927949716,
"user": 0.39094338000000006,
"system": 1.30320912,
"min": 0.6093896186600001,
"max": 0.65550085166,
"times": [
0.61665586666,
0.6168138606600001,
0.65550085166,
0.62024253366,
0.6355841706600001,
0.6257007316600001,
0.64347989366,
0.6093896186600001,
0.61831646066,
0.6099366296600001
]
},
{
"command": "pacquet@main",
"mean": 0.6149052149600001,
"stddev": 0.007230716940288946,
"median": 0.6136772081600002,
"user": 0.36649017999999994,
"system": 1.3049994200000001,
"min": 0.60593633566,
"max": 0.6316838596600001,
"times": [
0.61596850366,
0.6316838596600001,
0.6124806996600001,
0.6145217806600001,
0.60593633566,
0.6097006956600001,
0.6141066486600001,
0.6098001646600001,
0.6132477676600001,
0.6216056936600001
]
},
{
"command": "pnpr@HEAD",
"mean": 0.6776758283600002,
"stddev": 0.013079349627300438,
"median": 0.68244541666,
"user": 0.39758528000000004,
"system": 1.3368870200000003,
"min": 0.6515636556600001,
"max": 0.6902701456600001,
"times": [
0.6880441606600001,
0.6566891086600001,
0.6759932326600001,
0.6902701456600001,
0.68129552766,
0.6804157996600001,
0.68517873366,
0.68371261366,
0.6515636556600001,
0.68359530566
]
},
{
"command": "pnpr@main",
"mean": 0.69271474746,
"stddev": 0.03435438044356578,
"median": 0.68527122916,
"user": 0.38046598,
"system": 1.3400888200000003,
"min": 0.65518316466,
"max": 0.7632964076600001,
"times": [
0.65518316466,
0.6858926026600001,
0.68464985566,
0.6556615396600001,
0.6799560306600001,
0.7632964076600001,
0.74207883866,
0.6916640476600001,
0.6824407086600001,
0.68632427866
]
}
]
}Scenario: Isolated linker: fresh install, cold cache + cold store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 4.1037329093,
"stddev": 0.028738226089050953,
"median": 4.1048069316,
"user": 3.61206562,
"system": 3.32113474,
"min": 4.0524101531,
"max": 4.1391101501,
"times": [
4.1391101501,
4.1057074941,
4.1271148631,
4.1039063691,
4.1382964631,
4.0836006981,
4.0772285321,
4.0524101531,
4.0863864911,
4.1235678791
]
},
{
"command": "pacquet@main",
"mean": 4.1264874451999995,
"stddev": 0.03727071642052666,
"median": 4.1222457756,
"user": 3.6413353199999996,
"system": 3.3099778399999997,
"min": 4.0850420481,
"max": 4.1799494981,
"times": [
4.1799494981,
4.0850420481,
4.1190751291,
4.1254164221,
4.0874938861,
4.1027992291,
4.1290324441,
4.1791005271,
4.1672495721,
4.0897156961
]
},
{
"command": "pnpr@HEAD",
"mean": 2.1578740324000005,
"stddev": 0.1251495708233763,
"median": 2.1351161580999998,
"user": 2.45039572,
"system": 2.8896050399999997,
"min": 2.0207927451,
"max": 2.3847101871,
"times": [
2.3543469341,
2.0289145581,
2.1215409561,
2.1550256321,
2.0207927451,
2.1939972551,
2.1347391471,
2.1354931691,
2.3847101871,
2.0491797401
]
},
{
"command": "pnpr@main",
"mean": 2.213468534,
"stddev": 0.1372052814970433,
"median": 2.1758914286,
"user": 2.4827419199999996,
"system": 2.85948804,
"min": 2.0742688811,
"max": 2.4237144921000002,
"times": [
2.0775522281,
2.3888005311000002,
2.0939139871,
2.1554919761,
2.0742688811,
2.2030730661,
2.1302200091,
2.4237144921000002,
2.3913592881,
2.1962908811
]
}
]
}Scenario: Isolated linker: fresh install, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 1.2974532715600002,
"stddev": 0.017624133914807587,
"median": 1.29377813226,
"user": 1.31993782,
"system": 1.7025693,
"min": 1.27262385126,
"max": 1.3252899762600001,
"times": [
1.27262385126,
1.3252899762600001,
1.29376962626,
1.2998637782600002,
1.2836808942600002,
1.29378663826,
1.28455420226,
1.3240995472600001,
1.3107463212600001,
1.28611788026
]
},
{
"command": "pacquet@main",
"mean": 1.2931988168600002,
"stddev": 0.0503322508887862,
"median": 1.27625442076,
"user": 1.2616462199999998,
"system": 1.6978040999999997,
"min": 1.25670708926,
"max": 1.4245824122600002,
"times": [
1.2881720032600001,
1.30638529226,
1.4245824122600002,
1.31581101826,
1.28101885626,
1.25670708926,
1.2714899852600001,
1.2626153392600001,
1.25672563026,
1.26848054226
]
},
{
"command": "pnpr@HEAD",
"mean": 0.64423451126,
"stddev": 0.012961301154503403,
"median": 0.64056695526,
"user": 0.34005012,
"system": 1.2620757,
"min": 0.62555257426,
"max": 0.67054417626,
"times": [
0.63437149026,
0.62555257426,
0.64154164526,
0.67054417626,
0.63959226526,
0.65452224526,
0.63757826126,
0.65297755226,
0.63527771426,
0.65038718826
]
},
{
"command": "pnpr@main",
"mean": 0.64252325906,
"stddev": 0.0076658660261874605,
"median": 0.64500077976,
"user": 0.33227702000000003,
"system": 1.2800753,
"min": 0.62582990826,
"max": 0.64894942726,
"times": [
0.62582990826,
0.64695385026,
0.64835901926,
0.64297308326,
0.63157141526,
0.64722029526,
0.64512781726,
0.64487374226,
0.64894942726,
0.64337403226
]
}
]
}Scenario: Isolated linker: fresh install, cold cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 2.97486675742,
"stddev": 0.04607518935014748,
"median": 2.96693781312,
"user": 1.77156988,
"system": 1.94652292,
"min": 2.90896467412,
"max": 3.0512176961199997,
"times": [
3.0085536661199996,
2.92798424312,
3.02235342512,
2.9775190071199997,
2.95335800812,
2.95635661912,
2.90896467412,
3.0512176961199997,
3.0065815801199998,
2.93577865512
]
},
{
"command": "pacquet@main",
"mean": 2.9077934362199995,
"stddev": 0.03255845087278677,
"median": 2.90137729262,
"user": 1.6910249800000003,
"system": 1.9341402199999997,
"min": 2.87763407312,
"max": 2.99284797112,
"times": [
2.91553106312,
2.88973399012,
2.87763407312,
2.91857427312,
2.88518798912,
2.89840168912,
2.90435408312,
2.89131633412,
2.99284797112,
2.90435289612
]
},
{
"command": "pnpr@HEAD",
"mean": 0.64687430722,
"stddev": 0.010947610420697435,
"median": 0.6448605326200001,
"user": 0.34366868,
"system": 1.2850783199999998,
"min": 0.6329135401200001,
"max": 0.6687815051200001,
"times": [
0.6409157441200001,
0.6436499961200001,
0.6687815051200001,
0.64507052612,
0.65879928212,
0.6329135401200001,
0.64465053912,
0.6532052391200001,
0.6339346261200001,
0.64682207412
]
},
{
"command": "pnpr@main",
"mean": 0.64737526542,
"stddev": 0.03980069492573665,
"median": 0.6349486631200001,
"user": 0.32640048000000005,
"system": 1.26903312,
"min": 0.6294656711200001,
"max": 0.7599163681200001,
"times": [
0.64222336412,
0.6335293901200001,
0.63061965312,
0.63699884512,
0.6294656711200001,
0.6306199831200001,
0.6363679361200001,
0.63215918512,
0.7599163681200001,
0.64185225812
]
}
]
} |
|
| Branch | pr/12497 |
| Testbed | pacquet |
Click to view all benchmark results
| Benchmark | Latency | Benchmark Result milliseconds (ms) (Result Δ%) | Upper Boundary milliseconds (ms) (Limit %) |
|---|---|---|---|
| isolated-linker.fresh-install.cold-cache.cold-store | 📈 view plot 🚷 view threshold | 4,103.73 ms(-2.48%)Baseline: 4,207.89 ms | 5,049.47 ms (81.27%) |
| isolated-linker.fresh-install.cold-cache.hot-store | 📈 view plot 🚷 view threshold | 2,974.87 ms(-0.71%)Baseline: 2,996.01 ms | 3,595.21 ms (82.75%) |
| isolated-linker.fresh-install.hot-cache.hot-store | 📈 view plot 🚷 view threshold | 1,297.45 ms(-1.85%)Baseline: 1,321.92 ms | 1,586.30 ms (81.79%) |
| isolated-linker.fresh-restore.cold-cache.cold-store | 📈 view plot 🚷 view threshold | 3,906.01 ms(-5.65%)Baseline: 4,139.95 ms | 4,967.94 ms (78.62%) |
| isolated-linker.fresh-restore.hot-cache.hot-store | 📈 view plot 🚷 view threshold | 625.16 ms(+1.99%)Baseline: 612.97 ms | 735.57 ms (84.99%) |
|
| Branch | pr/12497 |
| Testbed | pnpr |
⚠️ WARNING: No Threshold found!Without a Threshold, no Alerts will ever be generated.
Click here to create a new Threshold
For more information, see the Threshold documentation.
To only post results if a Threshold exists, set the--ci-only-thresholdsflag.
Click to view all benchmark results
| Benchmark | Latency | milliseconds (ms) |
|---|---|---|
| isolated-linker.fresh-install.cold-cache.cold-store | 📈 view plot | 2,157.87 ms |
| isolated-linker.fresh-install.cold-cache.hot-store | 📈 view plot | 646.87 ms |
| isolated-linker.fresh-install.hot-cache.hot-store | 📈 view plot | 644.23 ms |
| isolated-linker.fresh-restore.cold-cache.cold-store | 📈 view plot | 2,166.93 ms |
| isolated-linker.fresh-restore.hot-cache.hot-store | 📈 view plot | 677.68 ms |
|
Code review by qodo was updated up to the latest commit a5bc188 |
…in pacquet why - Share a memo cache keyed by (ReverseKey, depth) across all root walks to avoid recomputing identical subtrees for overlapping matches - Separate matching scan from tree building so all roots are collected before any expensive walks begin - Support pruned lockfiles (missing packages:) by building node keys from snapshots instead of packages when the latter is absent - Make WalkCtx.packages optional so display_version falls back to key.suffix.version() when metadata is unavailable Refs: #12497
|
Code review by qodo was updated up to the latest commit 677e77d |
APFS coarse mtime granularity can cause the second install to see a stale manifest. Add a 100ms sleep after the manifest write to ensure the filesystem is visible to the subprocess. Refs: #12497
|
Code review by qodo was updated up to the latest commit b0083e1 |
Summary
Adds
pacquet whycommand that shows which packages depend on a given package, porting pnpm'swhycommand to the Rustpacquet/stack.The command reads the lockfile, builds a forward dependency graph, inverts it, and walks the reverse edges to display a tree of dependents up to the root project. Supports glob patterns (
@scope/*,!exclude) and--depthto limit display depth.Squash Commit Body
Checklist
pacquet/port, or the description notes what still needs porting.why. This PR ports it to pacquet. No TypeScript changes needed.pnpm changeset) if this PR changes any published package.Summary by CodeRabbit
New Features
pacquet whysubcommand that displays reverse dependency trees for specified packages, including support for alias/name matching.Bug Fixes