refactor(pacquet/cli): split cli_args.rs#12690
Conversation
The single cli_args.rs file had grown to ~1500 lines and mixed several unrelated concerns, making it a frequent source of merge conflicts. Split it into focused submodules under cli_args/, leaving cli_args.rs as a thin module index: - cli_command.rs: the CliArgs and CliCommand argument types - dispatch.rs: the impl CliArgs command-dispatch logic - pipelines.rs: the install/deploy/dedupe/prune reporter-generic pipelines - package_manager.rs: packageManager / devEngines parsing helpers - reporter.rs: ReporterType and its emit/configure helpers No behavior change; the move is mechanical. Subcommand modules that adding a new command still touches (the enum and the dispatch match) now live in their own files, so unrelated changes to pipelines or package-manager parsing no longer conflict with them. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WLA4Nx1wDMQCaGzhcbXKVV
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughThe CLI argument surface was split into dedicated modules for command definitions, reporter setup, package-manager sync detection, pipelines, and dispatch handlers. The main run path now uses shared context, completion handling, install fast-path execution, and per-command install/query/script routing. ChangesCLI module split and orchestration
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
✨ Finishing Touches📝 Generate docstrings
🧪 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 |
Micro-Benchmark ResultsLinux |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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/cli_command.rs`:
- Around line 108-109: Restore the install shortcut by ensuring
CliCommand::Install has the same alias/argv handling for "i" as before, so pnpm
i maps to install again. Update the CliCommand::Install variant in
cli_command.rs or the command parsing/rewrite logic that feeds it, and add or
adjust a test covering the pnpm i invocation so the shortcut behavior stays
locked in.
In `@pacquet/crates/cli/src/cli_args/package_manager.rs`:
- Around line 106-116: The packageManager parser in parse_package_manager
currently uses split_once('@'), which incorrectly breaks scoped packages like
`@pnpm/exe`@10.0.0 and prevents pnpm-matching behavior. Update
parse_package_manager to treat the last '@' as the version separator while
preserving the existing reference normalization logic (including the ':' guard
and '+' version trimming), so scoped names are parsed into the correct name and
optional reference.
In `@pacquet/crates/cli/src/cli_args/pipelines.rs`:
- Around line 196-213: The prune boundary check in pipelines::Config validation
is only lexical, so a relative escape like modules_dir pointing through ".." can
still bypass the workspace guard. Update the containment check around
cfg.modules_dir and config_root to first resolve/normalize the prune target
against the workspace root (or canonicalize both paths) before using
starts_with, so the final comparison in the prune pipeline reflects the real
filesystem location. Keep the safety behavior aligned with the existing prune
validation and the WorkspaceSettings::apply_to flow.
🪄 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: cf15d503-53e1-463f-a966-431db773ddf8
📒 Files selected for processing (7)
pacquet/crates/cli/src/cli_args.rspacquet/crates/cli/src/cli_args/cli_command.rspacquet/crates/cli/src/cli_args/dispatch.rspacquet/crates/cli/src/cli_args/package_manager.rspacquet/crates/cli/src/cli_args/pipelines.rspacquet/crates/cli/src/cli_args/reporter.rspacquet/crates/cli/src/cli_args/tests.rs
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #12690 +/- ##
==========================================
+ Coverage 86.92% 86.96% +0.04%
==========================================
Files 376 382 +6
Lines 57967 58161 +194
==========================================
+ Hits 50386 50579 +193
- Misses 7581 7582 +1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
The dylint `perfectionist::import-granularity` lint (style = "crate") requires all imports sharing a crate root to be merged into a single `use` with nested braces. The split introduced several files with one `use super::x::Y;` per line, which the lint rejects. Collapse the top-level imports in the new cli_args submodules accordingly. This only affects module-level imports; function-body `use` statements are left as-is (the lint does not flag them).
Integrated-Benchmark Report (Linux)Commit: 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": 4.447792795700001,
"stddev": 0.18275526009310783,
"median": 4.4216461875,
"user": 3.1564263200000005,
"system": 2.69355482,
"min": 4.2338434845,
"max": 4.7911472985,
"times": [
4.3387218275,
4.7037978015,
4.5060775225,
4.7911472985,
4.4837955705,
4.4156382415,
4.3253193685,
4.2338434845,
4.4276541335,
4.2519327085
]
},
{
"command": "pacquet@main",
"mean": 4.3573895379,
"stddev": 0.09534042186825586,
"median": 4.3450163565,
"user": 3.11221572,
"system": 2.68461512,
"min": 4.2308315875,
"max": 4.5113799225,
"times": [
4.3160126475,
4.2308315875,
4.4701038745,
4.5113799225,
4.4423555445,
4.2865208735,
4.2847877455,
4.3931828335,
4.2647002845,
4.3740200655
]
},
{
"command": "pnpr@HEAD",
"mean": 2.7723649518,
"stddev": 0.13926223788651496,
"median": 2.7459913030000003,
"user": 2.15608792,
"system": 2.29063022,
"min": 2.6339354155,
"max": 3.0777243005,
"times": [
2.8145266815000003,
2.7914335975,
3.0777243005,
2.6653991425,
2.9197551855,
2.6339354155,
2.7717508895000003,
2.6441885155000002,
2.6847040735,
2.7202317165
]
},
{
"command": "pnpr@main",
"mean": 2.8265720696,
"stddev": 0.13549471201373717,
"median": 2.819267559,
"user": 2.1518121199999998,
"system": 2.2817162200000003,
"min": 2.6605615465000003,
"max": 3.0340871155,
"times": [
3.0041397425,
2.7087355215,
2.6605615465000003,
2.8736339375,
2.8104075265,
2.6973736265,
2.7018305445,
3.0340871155,
2.8281275915000004,
2.9468235435
]
}
]
}Scenario: Isolated linker: fresh restore, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 0.66172196354,
"stddev": 0.059068447446870356,
"median": 0.62269658134,
"user": 0.31228500000000003,
"system": 0.9968055999999998,
"min": 0.60609254134,
"max": 0.73707992234,
"times": [
0.73707992234,
0.60609254134,
0.72451361534,
0.62453763034,
0.62085553234,
0.61578014234,
0.72983615134,
0.72875969234,
0.61434805034,
0.61541635734
]
},
{
"command": "pacquet@main",
"mean": 0.6887148209399998,
"stddev": 0.10662663409789597,
"median": 0.6243599773399999,
"user": 0.3102204,
"system": 1.0107624,
"min": 0.61410262434,
"max": 0.94536169334,
"times": [
0.62709467434,
0.74462745434,
0.74544247534,
0.73095496334,
0.62162528034,
0.61410262434,
0.62098911734,
0.61647735534,
0.62047257134,
0.94536169334
]
},
{
"command": "pnpr@HEAD",
"mean": 0.6746956512400001,
"stddev": 0.048602157268984675,
"median": 0.66214945534,
"user": 0.3194484,
"system": 1.0508707999999998,
"min": 0.64664181334,
"max": 0.81101163534,
"times": [
0.81101163534,
0.66986963234,
0.67129434934,
0.64664181334,
0.6554904903400001,
0.66221325034,
0.66208566034,
0.65577282334,
0.64803563934,
0.66454121834
]
},
{
"command": "pnpr@main",
"mean": 0.73674742634,
"stddev": 0.08288742324011034,
"median": 0.7213200578400001,
"user": 0.313863,
"system": 1.0716912,
"min": 0.65863031434,
"max": 0.8866616003400001,
"times": [
0.8866616003400001,
0.82566390534,
0.67314482034,
0.66166310434,
0.76949529534,
0.78337292134,
0.66652793534,
0.78033507834,
0.66197928834,
0.65863031434
]
}
]
}Scenario: Isolated linker: fresh install, cold cache + cold store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 4.372174682159999,
"stddev": 0.03621498662470635,
"median": 4.373118291259999,
"user": 3.0117563999999994,
"system": 2.57807866,
"min": 4.32097334876,
"max": 4.42067719076,
"times": [
4.42067719076,
4.4057405337599995,
4.369264525759999,
4.38537686676,
4.333578419759999,
4.4153380877599995,
4.37697205676,
4.32607851576,
4.32097334876,
4.367747275759999
]
},
{
"command": "pacquet@main",
"mean": 4.36711845886,
"stddev": 0.04774616200077435,
"median": 4.35254588676,
"user": 3.0169043999999996,
"system": 2.5885437599999994,
"min": 4.32497560476,
"max": 4.490834952759999,
"times": [
4.490834952759999,
4.3359203507599995,
4.34684501576,
4.3677389797599995,
4.32497560476,
4.37698405176,
4.3553096257599995,
4.33449009576,
4.38830376376,
4.34978214776
]
},
{
"command": "pnpr@HEAD",
"mean": 3.19014051926,
"stddev": 0.28640823314099706,
"median": 3.15395868476,
"user": 2.3306801999999998,
"system": 2.50195616,
"min": 2.83335520876,
"max": 3.64535346876,
"times": [
2.9028590287600005,
3.0310783327600004,
2.93856126376,
3.4106970977600004,
2.99284047176,
3.64535346876,
3.52902485276,
3.27683903676,
2.83335520876,
3.34079643076
]
},
{
"command": "pnpr@main",
"mean": 3.0107072500600003,
"stddev": 0.07808220919743038,
"median": 3.01484328026,
"user": 2.2531264,
"system": 2.4638980599999996,
"min": 2.8526531747600004,
"max": 3.11598033476,
"times": [
2.98396406776,
3.06202461576,
2.95517767176,
3.03958954376,
2.99009701676,
2.8526531747600004,
3.11598033476,
2.9577104237600005,
3.0695993027600004,
3.08027634876
]
}
]
}Scenario: Isolated linker: fresh install, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 1.2173982998800001,
"stddev": 0.046434694261724765,
"median": 1.2084169721800002,
"user": 1.0478673400000003,
"system": 1.3428523599999997,
"min": 1.1808719541800001,
"max": 1.34356061718,
"times": [
1.1894855561800002,
1.19495789418,
1.2058517911800002,
1.34356061718,
1.1918730691800001,
1.22258994818,
1.21993519618,
1.1808719541800001,
1.2138748191800002,
1.21098215318
]
},
{
"command": "pacquet@main",
"mean": 1.2360448218800002,
"stddev": 0.05567251701808611,
"median": 1.21891279518,
"user": 1.05147264,
"system": 1.35857646,
"min": 1.18936041118,
"max": 1.39003698518,
"times": [
1.21351817618,
1.21603725718,
1.39003698518,
1.2406332561800002,
1.2187585871800002,
1.2182780531800002,
1.2305520111800001,
1.2242064781800002,
1.18936041118,
1.2190670031800002
]
},
{
"command": "pnpr@HEAD",
"mean": 1.29377457118,
"stddev": 0.08683528164826192,
"median": 1.26880941868,
"user": 0.47109724000000003,
"system": 1.23323196,
"min": 1.24321835618,
"max": 1.53892552218,
"times": [
1.2769735611800002,
1.26401759118,
1.24321835618,
1.53892552218,
1.2851129671800001,
1.26241796418,
1.26965755218,
1.26910280918,
1.26851602818,
1.25980336018
]
},
{
"command": "pnpr@main",
"mean": 1.3027630579799998,
"stddev": 0.08178019920853473,
"median": 1.2762448276800002,
"user": 0.4703504399999999,
"system": 1.23528336,
"min": 1.2674990511800002,
"max": 1.53404241918,
"times": [
1.2674990511800002,
1.2759295021800001,
1.27034117818,
1.53404241918,
1.29655454018,
1.28443685418,
1.2676618161800002,
1.2765601531800002,
1.28428594918,
1.27031911618
]
}
]
}Scenario: Isolated linker: fresh install, cold cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 2.73257122908,
"stddev": 0.09612468063140177,
"median": 2.69897953078,
"user": 1.42091592,
"system": 1.5559568,
"min": 2.67205262178,
"max": 2.98468350878,
"times": [
2.67205262178,
2.70893233178,
2.71415048678,
2.68902672978,
2.68236379778,
2.70973631378,
2.80181139178,
2.68798497878,
2.98468350878,
2.6749701297799997
]
},
{
"command": "pacquet@main",
"mean": 2.7883534810799993,
"stddev": 0.10582113157462848,
"median": 2.75880803828,
"user": 1.41869622,
"system": 1.5706883999999999,
"min": 2.68343498078,
"max": 3.04116522278,
"times": [
2.75327429478,
2.74231415778,
2.68741887178,
2.7643417817800002,
2.83474692478,
2.71252260278,
2.68343498078,
2.83395655878,
3.04116522278,
2.8303594147799997
]
},
{
"command": "pnpr@HEAD",
"mean": 1.26957674558,
"stddev": 0.013369905847460993,
"median": 1.2712781187799997,
"user": 0.47410682,
"system": 1.2271338999999999,
"min": 1.2364300317799999,
"max": 1.28345058678,
"times": [
1.2692539087799999,
1.28345058678,
1.2761994857799999,
1.2678293327799999,
1.2733023287799998,
1.26560749278,
1.27790496378,
1.2642467817799998,
1.2364300317799999,
1.28154254278
]
},
{
"command": "pnpr@main",
"mean": 1.29202259678,
"stddev": 0.09646948115966539,
"median": 1.2574064647799998,
"user": 0.48208912000000004,
"system": 1.2066268,
"min": 1.23469731678,
"max": 1.55800953978,
"times": [
1.2787816967799999,
1.24474720078,
1.2710480517799998,
1.55800953978,
1.31971029578,
1.26201427878,
1.25279865078,
1.23469731678,
1.2488660757799999,
1.24955286078
]
}
]
} |
|
| Branch | pr/12690 |
| 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,372.17 ms(-8.11%)Baseline: 4,758.04 ms | 5,709.65 ms (76.58%) |
| isolated-linker.fresh-install.cold-cache.hot-store | 📈 view plot 🚷 view threshold | 2,732.57 ms(-10.41%)Baseline: 3,050.23 ms | 3,660.28 ms (74.65%) |
| isolated-linker.fresh-install.hot-cache.hot-store | 📈 view plot 🚷 view threshold | 1,217.40 ms(-10.39%)Baseline: 1,358.57 ms | 1,630.28 ms (74.67%) |
| isolated-linker.fresh-restore.cold-cache.cold-store | 📈 view plot 🚷 view threshold | 4,447.79 ms(-7.43%)Baseline: 4,805.00 ms | 5,766.00 ms (77.14%) |
| isolated-linker.fresh-restore.hot-cache.hot-store | 📈 view plot 🚷 view threshold | 661.72 ms(+0.91%)Baseline: 655.72 ms | 786.87 ms (84.10%) |
|
| Branch | pr/12690 |
| 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 | 3,190.14 ms |
| isolated-linker.fresh-install.cold-cache.hot-store | 📈 view plot | 1,269.58 ms |
| isolated-linker.fresh-install.hot-cache.hot-store | 📈 view plot | 1,293.77 ms |
| isolated-linker.fresh-restore.cold-cache.cold-store | 📈 view plot | 2,772.36 ms |
| isolated-linker.fresh-restore.hot-cache.hot-store | 📈 view plot | 674.70 ms |
`dispatch.rs` was dominated by one ~600-line `match` over every `CliCommand` variant. Extract a `RunCtx` context (the canonicalized dir, the manifest path, the reporter, the `--recursive` flag, and the lazily loaded `config` / `state` closures as `&dyn Fn`) and move each command's body into one of three handler modules grouped by what the command does: - dispatch_install.rs: commands that mutate the install graph (add, update, remove, install, deploy, dedupe, prune, fetch, import, link, unlink, rebuild, patch*, dlx, create, runtime, approve-builds) - dispatch_query.rs: read-only queries (outdated, audit, list, why, whoami, dist-tag, ping, pack, root, config, pack-app, docs, store, cache, cat-file, cat-index, ignored-builds, find-hash) - dispatch_script.rs: package.json script / lifecycle commands (init, test, run, exec, start, stop, restart, set-script) `dispatch.rs` keeps only `RunCtx`, the three `CliArgs` methods, and a thin `route` match that wires each variant to its handler. No behavior change. The `config` / `state` closures are shared with each handler as `&dyn Fn(...) + Sync`; the `+ Sync` is required so the async handler futures that capture them stay `Send`, and it is mirrored onto `ApproveBuildsArgs::prepare`, which takes the same closures.
…ger parsing
Two pnpm-parity fixes surfaced by review of the cli_args split (both
pre-existing on the original cli_args.rs, not refactor regressions):
- Expose `i` as a visible alias of `install`, matching pnpm's
`commandNames = ['install', 'i']`, so `pacquet i` dispatches to install.
- Parse the `packageManager` field the way pnpm does: split on the first
`@`, except skip a leading scope `@` and split on the next one. The
previous `split_once('@')` mis-parsed scoped names like
`@scope/pnpm@1.0.0` into an empty name. The first `@` (not the last) is
used deliberately so a reference that is a URL containing `@` (e.g.
credentials) stays intact — mirroring pnpm's parsePackageManager.
Adds tests for the `i` alias and for parse_package_manager (unscoped,
scoped, `+`-hash, missing separator, and URL-with-`@` cases).
Brings in 3 commits from main: the cli_args.rs split (#12690, CLI-only), plus the bytes and tower-http dependency-version bumps. Only Cargo.lock conflicted; resolved by taking main's lockfile and re-adding this branch's network-web-auth subtree (crossterm, qrcode, open) via cargo. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWejcxTU8n144a1KK4nRj8
Summary
pacquet/crates/cli/src/cli_args.rshad grown to ~1500 lines and mixedseveral unrelated concerns in one file, which made it a frequent source of
merge conflicts. This PR splits it into focused submodules under
cli_args/, leavingcli_args.rsas a thin module index, and then furtherbreaks up the command-dispatch
match.1. Split
cli_args.rsinto focused submodulesThe
pub mod <subcommand>;declarations stay incli_args.rs(they definethe module paths for the existing
cli_args/<cmd>.rsfiles), but everythingelse moves out:
cli_args.rsCliArgsre-exportcli_args/cli_command.rsCliArgsstruct andCliCommandenumcli_args/dispatch.rsimpl CliArgs— the install fast-path, completion handling, and theroutedispatchercli_args/pipelines.rscli_args/package_manager.rspackageManager/devEnginesparsingcli_args/reporter.rsReporterTypeand itsemit/configurehelpers2. Split the dispatch
matchdispatch.rsnow holds aRunCtxcontext (the canonicalized dir, themanifest path, the reporter, the
--recursiveflag, and the lazily-loadedconfig/stateclosures) and a thinroutematch that wires each variantto a handler. The per-command bodies move into three modules grouped by what
the command does:
cli_args/dispatch_install.rscli_args/dispatch_query.rscli_args/dispatch_script.rspackage.jsonscript / lifecycle: init, test, run, exec, start, stop, restart, set-scriptdispatch.rsdrops from ~800 lines to ~280; thematchitself is now a~50-line router.
3. Two pnpm-parity fixes surfaced by review
Both were pre-existing in the original
cli_args.rs(not refactorregressions), found while reviewing the split:
iinstall alias — pnpm exposescommandNames = ['install', 'i'];pacquet lacked it. Added
#[clap(visible_alias = "i")]sopacquet idispatches to install.
packageManagerparsing —parse_package_managernow mirrorspnpm's parser (split on the first
@, skipping a leading scope@),instead of
split_once('@')which mis-parsed@scope/pnpm@1.0.0. Thefirst
@(not the last) is deliberate so a URL reference containing@stays intact.
Notes
substantive edits there are visibility widenings (to
pub(crate)/pub(super)), the import collapsing required by theperfectionist::import-granularitydylint lint (oneuseper crate root),and one signature change:
ApproveBuildsArgs::preparenow takes itsconfig/stateclosures as&(dyn Fn(...) + Sync)so the async handlerfutures that capture those closures stay
Send.pacquet-cli:cargo check,cargo clippy -- --deny warnings, andcargo fmt --checkare clean, and the lib tests(356, incl. the two new parity tests) pass. Import granularity was verified
with
rustfmt --config imports_granularity=Crate(and the CI Dylint job isgreen).
Squash Commit Body
Checklist
pacquet/port, or the description notes what still needs porting.The refactor is pacquet-internal (nothing to port). The two parity
fixes align pacquet with the existing TypeScript pnpm behavior, so no
TS-side change is needed.
The refactor is a mechanical move; the existing
cli_argstests movedwith the code. Added
install_command_parses_i_aliasandparse_package_manager_handles_unscoped_scoped_and_url_referencesfor thetwo parity fixes.
Written by an agent (Claude Code, Claude Opus 4.8).
🤖 Generated with Claude Code
https://claude.ai/code/session_01WLA4Nx1wDMQCaGzhcbXKVV
Summary by CodeRabbit
New Features
Bug Fixes