feat(pacquet-cli): implement pkg command#12795
Conversation
📝 WalkthroughWalkthroughThis PR adds a new Changespkg subcommand
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
Suggested labels: 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
AI Description
Diagram
High-Level Assessment
Files changed (6)
|
Code Review by Qodo
1.
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #12795 +/- ##
==========================================
- Coverage 85.02% 84.85% -0.17%
==========================================
Files 415 416 +1
Lines 65438 65895 +457
==========================================
+ Hits 55638 55916 +278
- Misses 9800 9979 +179 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Code review by qodo was updated up to the latest commit 50b900f |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
pacquet/crates/cli/src/cli_args/pkg/tests.rs (1)
61-97: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDuplicate multi-key
gettests.
test_get_multiple_keysandtest_get_multiple_keys_returns_json_objectexercise the same manifest and the same behavior (selectingname/version) with only a trivial construction difference. Consider consolidating into one test.🤖 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/pkg/tests.rs` around lines 61 - 97, There are duplicate coverage cases in the pkg CLI tests: test_get_multiple_keys and test_get_multiple_keys_returns_json_object both verify the same multi-key get behavior on the same manifest. Consolidate them into a single test in the pkg tests module, keeping one assertion path that exercises get_output with name/version and validates the JSON object result.pacquet/crates/cli/src/cli_args/pkg.rs (1)
235-262: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winProperty path parsed twice per
setcall.
check_unsafe_key_in_pathparsespathinternally, thenset_object_value_by_property_pathimmediately re-parses the samepatha few lines later. Pass the already-parsedsegmentsinto the unsafe-key check instead of re-deriving them.♻️ Proposed fix: parse once, validate on the parsed segments
-fn check_unsafe_key_in_path(key: &str) -> Result<(), PkgError> { - let segments = parse_property_path(key).map_err(PkgError::InvalidPropertyPath)?; - for segment in &segments { +fn check_unsafe_key_in_segments(segments: &[Segment]) -> Result<(), PkgError> { + for segment in segments { if let Segment::Key(k) = segment && UNSAFE_KEYS.contains(&k.as_str()) { return Err(PkgError::UnsafeKey { key: k.clone() }); } } Ok(()) } @@ - check_unsafe_key_in_path(path)?; - let segments = parse_property_path(path) - .map_err(|err| miette::Report::new(PkgError::InvalidPropertyPath(err)))?; + let segments = parse_property_path(path) + .map_err(|err| miette::Report::new(PkgError::InvalidPropertyPath(err)))?; + check_unsafe_key_in_segments(&segments)?;As per path instructions (REVIEW_GUIDE.md): "Performance is secondary; avoid extra parsing/serialization beyond what's needed per command."
🤖 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/pkg.rs` around lines 235 - 262, The set path flow in set_object_value_by_property_path is parsing the same property path twice, once in check_unsafe_key_in_path and again immediately afterward. Change the validation helper to accept the already-parsed segments (or move the unsafe-key check into set_object_value_by_property_path after parse_property_path) so the path is parsed only once per set call. Keep the existing unsafe-key behavior by reusing the parsed Segment values instead of re-deriving them.Source: Path instructions
🤖 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/pkg.rs`:
- Around line 264-295: The path-setting logic in the segment-walk loop always
creates missing intermediate keys as objects, so `Segment::Key` must look ahead
to the next segment and choose an object or array container accordingly; update
the container creation in the `current` traversal so documented paths like
`keywords[0]` can initialize `keywords` as an array instead of failing later in
`Segment::Index`. Also harden both `arr.resize(index + 1, Value::Null)` branches
by validating/capping `idx` before converting to `usize` and before resizing, so
a malformed `Segment::Index` cannot force an unbounded allocation or overflow.
- Around line 183-195: The pkg_delete flow currently allows unsafe property
paths that the rest of the pkg contract rejects. Update pkg_delete in the pkg.rs
CLI logic to validate each key before calling
delete_object_value_by_property_path, rejecting __proto__, constructor, and
prototype the same way pkg set and pnpm’s TS pkg delete do. Keep the validation
close to pkg_delete so malformed paths fail before PackageManifest::from_path,
manifest.value_mut, or manifest.save are used.
In `@pacquet/crates/cli/src/cli_args/pkg/tests.rs`:
- Around line 197-251: Add a regression test in test_set_* coverage for unsafe
path rejection in set_object_value_by_property_path, since it already calls
check_unsafe_key_in_path but has no assertion for blocked prototype-polluting
keys. Extend the tests around set_object_value_by_property_path to verify paths
like __proto__.polluted or constructor.prototype are rejected with an error and
do not mutate the JSON value. Use the existing test helpers and function name to
locate the behavior being validated.
---
Nitpick comments:
In `@pacquet/crates/cli/src/cli_args/pkg.rs`:
- Around line 235-262: The set path flow in set_object_value_by_property_path is
parsing the same property path twice, once in check_unsafe_key_in_path and again
immediately afterward. Change the validation helper to accept the already-parsed
segments (or move the unsafe-key check into set_object_value_by_property_path
after parse_property_path) so the path is parsed only once per set call. Keep
the existing unsafe-key behavior by reusing the parsed Segment values instead of
re-deriving them.
In `@pacquet/crates/cli/src/cli_args/pkg/tests.rs`:
- Around line 61-97: There are duplicate coverage cases in the pkg CLI tests:
test_get_multiple_keys and test_get_multiple_keys_returns_json_object both
verify the same multi-key get behavior on the same manifest. Consolidate them
into a single test in the pkg tests module, keeping one assertion path that
exercises get_output with name/version and validates the JSON object result.
🪄 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: b17bfb93-f9b9-4d74-917d-e47f22056852
📒 Files selected for processing (6)
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/dispatch_script.rspacquet/crates/cli/src/cli_args/pkg.rspacquet/crates/cli/src/cli_args/pkg/tests.rs
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": 3.7822814852199995,
"stddev": 0.1465037300023765,
"median": 3.73859849402,
"user": 3.61716898,
"system": 3.3692105399999996,
"min": 3.6283688205200004,
"max": 4.11394171152,
"times": [
3.8789691815200005,
3.7896692185200003,
3.8962060175200004,
3.71051575152,
4.11394171152,
3.6599044865200003,
3.6680426765200003,
3.74592039952,
3.6283688205200004,
3.73127658852
]
},
{
"command": "pacquet@main",
"mean": 3.83467366922,
"stddev": 0.12698112196217864,
"median": 3.82583213452,
"user": 3.5941966799999996,
"system": 3.37493854,
"min": 3.6391551245200002,
"max": 3.99292523652,
"times": [
3.99292523652,
3.6391551245200002,
3.7628192455200002,
3.95438312552,
3.85547116252,
3.9674158805200004,
3.6721049815200004,
3.79619310652,
3.9417612035200005,
3.7645076255200003
]
},
{
"command": "pnpr@HEAD",
"mean": 2.1510186833200002,
"stddev": 0.1617546995725202,
"median": 2.0837725555200004,
"user": 2.65824178,
"system": 2.9610634400000007,
"min": 1.97681448052,
"max": 2.38885254452,
"times": [
2.32639567052,
2.38885254452,
2.0404234865200004,
2.1062530185200004,
1.97681448052,
2.03881053552,
2.20702050552,
1.9848563085200002,
2.3794681905200004,
2.06129209252
]
},
{
"command": "pnpr@main",
"mean": 2.1391052853200003,
"stddev": 0.1327565687008611,
"median": 2.12565060952,
"user": 2.6471240799999998,
"system": 2.9606953399999996,
"min": 1.9968216575200002,
"max": 2.3968276525200003,
"times": [
2.05872698852,
2.3968276525200003,
2.25308706852,
2.2114306315200003,
2.03109800152,
2.03957625652,
2.0040287355200004,
1.9968216575200002,
2.19257423052,
2.2068816305200003
]
}
]
}Scenario: Isolated linker: fresh restore, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 0.6326642331600001,
"stddev": 0.017424694617822766,
"median": 0.6288632548599999,
"user": 0.37429094,
"system": 1.3070935599999998,
"min": 0.61519017786,
"max": 0.67111311986,
"times": [
0.67111311986,
0.61810539086,
0.64621837086,
0.61519017786,
0.6272696558599999,
0.63090293086,
0.61866500186,
0.62182173286,
0.6304568538599999,
0.64689909686
]
},
{
"command": "pacquet@main",
"mean": 0.6335322267600001,
"stddev": 0.016926000677317223,
"median": 0.62725412986,
"user": 0.36612954,
"system": 1.32496706,
"min": 0.61236112186,
"max": 0.65951354286,
"times": [
0.6281737898599999,
0.65143540986,
0.65951354286,
0.64819055386,
0.61236112186,
0.61400021786,
0.62051799486,
0.6487663648599999,
0.62602880186,
0.62633446986
]
},
{
"command": "pnpr@HEAD",
"mean": 0.7078798737600001,
"stddev": 0.059177850478771506,
"median": 0.69652407736,
"user": 0.38917594,
"system": 1.3479778599999999,
"min": 0.65900208386,
"max": 0.87059778386,
"times": [
0.69770829386,
0.70178732486,
0.65900208386,
0.70973450086,
0.69283878786,
0.69533986086,
0.69987278786,
0.67930960086,
0.67260771286,
0.87059778386
]
},
{
"command": "pnpr@main",
"mean": 0.70504978086,
"stddev": 0.04677482978680625,
"median": 0.69509456986,
"user": 0.38641593999999996,
"system": 1.3452670599999998,
"min": 0.66753435986,
"max": 0.83295132586,
"times": [
0.71063682186,
0.69857645486,
0.69121933286,
0.70013101186,
0.69333887886,
0.66753435986,
0.68797879486,
0.69685026086,
0.83295132586,
0.67128056686
]
}
]
}Scenario: Isolated linker: fresh install, cold cache + cold store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 4.226236957959999,
"stddev": 0.052328648082538946,
"median": 4.218877710360001,
"user": 3.7108414599999997,
"system": 3.3525622399999997,
"min": 4.163743168860001,
"max": 4.35978788286,
"times": [
4.35978788286,
4.196814397860001,
4.163743168860001,
4.2196437728600005,
4.2183541598600005,
4.219401260860001,
4.210946513860001,
4.22054136786,
4.1981187138600005,
4.25501834086
]
},
{
"command": "pacquet@main",
"mean": 4.200449624759999,
"stddev": 0.043531247136894664,
"median": 4.19126164786,
"user": 3.7174738599999997,
"system": 3.3462843399999995,
"min": 4.149298685860001,
"max": 4.271531293860001,
"times": [
4.271531293860001,
4.25474260286,
4.22331555586,
4.15391340886,
4.165614569860001,
4.149298685860001,
4.18629872286,
4.23579601486,
4.19622457286,
4.167760819860001
]
},
{
"command": "pnpr@HEAD",
"mean": 2.14806017886,
"stddev": 0.13479926327956876,
"median": 2.07437102386,
"user": 2.4929604599999995,
"system": 2.8863801400000004,
"min": 2.02491004786,
"max": 2.37818828486,
"times": [
2.37818828486,
2.05386565386,
2.07615642186,
2.03130367686,
2.27511163186,
2.02491004786,
2.15393281686,
2.34665163886,
2.06789598986,
2.07258562586
]
},
{
"command": "pnpr@main",
"mean": 2.2080459401599994,
"stddev": 0.15106285770322717,
"median": 2.19542316036,
"user": 2.4911481599999994,
"system": 2.88722274,
"min": 2.00485598886,
"max": 2.46457664086,
"times": [
2.46457664086,
2.04544341886,
2.28328167986,
2.36969669286,
2.00485598886,
2.32372371286,
2.06855704986,
2.12947789686,
2.17638819786,
2.21445812286
]
}
]
}Scenario: Isolated linker: fresh install, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 1.3277827386399998,
"stddev": 0.012846721020045873,
"median": 1.3284332378400001,
"user": 1.29864444,
"system": 1.6741275599999998,
"min": 1.30775116284,
"max": 1.35296053384,
"times": [
1.32982401484,
1.32808098184,
1.35296053384,
1.32878549384,
1.30775116284,
1.32899975084,
1.34126562784,
1.31619335284,
1.31650392284,
1.32746254484
]
},
{
"command": "pacquet@main",
"mean": 1.3747937182399996,
"stddev": 0.09360022243894765,
"median": 1.33922946134,
"user": 1.2966214399999998,
"system": 1.7092517599999997,
"min": 1.32521995284,
"max": 1.63617743084,
"times": [
1.32993483284,
1.63617743084,
1.3646941718399999,
1.33761227384,
1.3557000638399999,
1.33511635584,
1.32521995284,
1.38502317784,
1.33901061684,
1.33944830584
]
},
{
"command": "pnpr@HEAD",
"mean": 0.6834283736400002,
"stddev": 0.09173915770470631,
"median": 0.65372793934,
"user": 0.3328369400000001,
"system": 1.30236746,
"min": 0.63708253984,
"max": 0.93999297584,
"times": [
0.64155840284,
0.63745188984,
0.63708253984,
0.64219169784,
0.93999297584,
0.6819803008399999,
0.66550562084,
0.68106442984,
0.66117069884,
0.64628517984
]
},
{
"command": "pnpr@main",
"mean": 0.65237269454,
"stddev": 0.0163178186114579,
"median": 0.64867194884,
"user": 0.34458854000000005,
"system": 1.2695949599999998,
"min": 0.63990027984,
"max": 0.69702958784,
"times": [
0.65124589684,
0.65152255584,
0.64807637984,
0.64750147484,
0.64489695784,
0.64073077684,
0.63990027984,
0.69702958784,
0.65355551784,
0.64926751784
]
}
]
}Scenario: Isolated linker: fresh install, cold cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 2.9907900125199998,
"stddev": 0.03829000651422907,
"median": 2.97795824552,
"user": 1.7541768599999998,
"system": 1.93806812,
"min": 2.96389283252,
"max": 3.0869080205199997,
"times": [
2.98197440852,
2.9781280845199998,
2.96799320852,
3.02442815752,
2.9777884065199998,
2.99352306052,
2.96664233552,
3.0869080205199997,
2.96389283252,
2.96662161052
]
},
{
"command": "pacquet@main",
"mean": 3.0030864203199994,
"stddev": 0.046053123169286676,
"median": 2.99620000352,
"user": 1.7453888599999998,
"system": 1.95140442,
"min": 2.9585215305199997,
"max": 3.10879179952,
"times": [
3.00528080052,
3.03909251252,
2.9815383125199997,
3.0233125835199997,
2.9618685015199997,
2.98883494052,
2.9585215305199997,
3.0035650665199998,
3.10879179952,
2.96005815552
]
},
{
"command": "pnpr@HEAD",
"mean": 0.65701131712,
"stddev": 0.013240598837840187,
"median": 0.65301090802,
"user": 0.33562285999999997,
"system": 1.2875543200000001,
"min": 0.64013179952,
"max": 0.67843321752,
"times": [
0.65641257052,
0.65444451052,
0.65157730552,
0.66867067552,
0.6487772605200001,
0.64013179952,
0.67687405052,
0.67843321752,
0.65007435552,
0.64471742552
]
},
{
"command": "pnpr@main",
"mean": 0.6457571263199999,
"stddev": 0.007871988511410839,
"median": 0.6495935820200001,
"user": 0.33609866,
"system": 1.28012502,
"min": 0.63395620752,
"max": 0.65507719952,
"times": [
0.65507719952,
0.6536868865200001,
0.64978487052,
0.63724186352,
0.6507289765200001,
0.63568608252,
0.63395620752,
0.64940229352,
0.65049905352,
0.64150782952
]
}
]
}Scenario: Isolated linker: fresh resolve, hot cache, offline
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 0.64754421134,
"stddev": 0.03459582219913087,
"median": 0.63654121044,
"user": 0.60754028,
"system": 0.15320823999999997,
"min": 0.62133498144,
"max": 0.72167481544,
"times": [
0.72167481544,
0.69848001744,
0.63497844744,
0.62209906244,
0.62133498144,
0.62327374144,
0.63810397344,
0.62596359944,
0.64866752744,
0.64086594744
]
},
{
"command": "pacquet@main",
"mean": 0.6308395543399999,
"stddev": 0.007938993698063751,
"median": 0.6292289494400001,
"user": 0.60432448,
"system": 0.14627593999999997,
"min": 0.6208765754400001,
"max": 0.6474362824400001,
"times": [
0.6474362824400001,
0.6351504114400001,
0.6248671114400001,
0.6208765754400001,
0.63525639944,
0.62638972944,
0.63206816944,
0.6363001394400001,
0.62515969644,
0.62489102844
]
},
{
"command": "pnpr@HEAD",
"mean": 0.10400979355538462,
"stddev": 0.0018455080076893147,
"median": 0.10303675244,
"user": 0.026164664615384605,
"system": 0.018517001538461534,
"min": 0.10268257644,
"max": 0.10778921744,
"times": [
0.10620845744,
0.10332345644,
0.10729560344,
0.10298878644000001,
0.10300469644,
0.10287146844,
0.10311314944000001,
0.10316735044,
0.10268257644,
0.10303905344,
0.10303445144000001,
0.10279179044,
0.10269783444000001,
0.10268572144,
0.10302491844,
0.10299729644000001,
0.10278863644000001,
0.10289686644,
0.10361819944,
0.10778921744,
0.10760859244000001,
0.10280060144,
0.10344051344,
0.10714681444,
0.10379600044000001,
0.10744257944
]
},
{
"command": "pnpr@main",
"mean": 0.10289890686307693,
"stddev": 0.00040127155251048374,
"median": 0.10281092894,
"user": 0.02525643384615385,
"system": 0.01881427076923077,
"min": 0.10223388844,
"max": 0.10376214444000001,
"times": [
0.10280246344,
0.10257540544,
0.10335683144,
0.10232265644,
0.10327834244,
0.10353436344,
0.10257881244,
0.10299778744,
0.10238720844,
0.10268656744,
0.10306184544000001,
0.10295813344,
0.10263456044000001,
0.10358093044000001,
0.10281939444,
0.10271848544,
0.10318032244,
0.10223388844,
0.10334669044,
0.10269612444000001,
0.10376214444000001,
0.10239658244000001,
0.10301839444000001,
0.10285648144000001,
0.10279191044000001,
0.10279525144
]
}
]
}Scenario: Isolated linker: fresh restore, cold cache + cold store + cold pnpr
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 6.58964831414,
"stddev": 0.05855499873471422,
"median": 6.5991376847400005,
"user": 3.7816249400000004,
"system": 3.6489552599999997,
"min": 6.486919798240001,
"max": 6.69188391824,
"times": [
6.62979877624,
6.54053035424,
6.69188391824,
6.62797118724,
6.54429163324,
6.61836432624,
6.586405042240001,
6.558447778240001,
6.61187032724,
6.486919798240001
]
},
{
"command": "pacquet@main",
"mean": 6.67473878034,
"stddev": 0.12285833438362853,
"median": 6.653653737740001,
"user": 3.8004678399999996,
"system": 3.66689856,
"min": 6.541597788240001,
"max": 6.836735641240001,
"times": [
6.832616248240001,
6.686250059240001,
6.723347671240001,
6.543075255240001,
6.56374269624,
6.62105741624,
6.575595893240001,
6.82336913424,
6.836735641240001,
6.541597788240001
]
},
{
"command": "pnpr@HEAD",
"mean": 4.96569435334,
"stddev": 0.12149370176196984,
"median": 4.94938025574,
"user": 2.7346137400000003,
"system": 3.20623236,
"min": 4.8468692972400005,
"max": 5.277461429240001,
"times": [
4.955007737240001,
4.949984365240001,
4.8720097112400005,
4.87901123724,
5.277461429240001,
4.94877614624,
4.993908534240001,
4.8468692972400005,
5.0104956432400005,
4.92341943224
]
},
{
"command": "pnpr@main",
"mean": 4.957016987840001,
"stddev": 0.15717088015578462,
"median": 4.900393241740001,
"user": 2.75709154,
"system": 3.1891815599999993,
"min": 4.808712063240001,
"max": 5.26598802324,
"times": [
5.26598802324,
4.81797559924,
5.070101575240001,
5.1599495632400005,
4.870838758240001,
4.9513580602400005,
4.892588491240001,
4.808712063240001,
4.908197992240001,
4.82445975224
]
}
]
} |
|
| Branch | pr/12795 |
| 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,148.06 ms |
| isolated-linker.fresh-install.cold-cache.hot-store | 📈 view plot | 657.01 ms |
| isolated-linker.fresh-install.hot-cache.hot-store | 📈 view plot | 683.43 ms |
| isolated-linker.fresh-resolve.hot-cache.offline | 📈 view plot | 104.01 ms |
| isolated-linker.fresh-restore.cold-cache.cold-store | 📈 view plot | 2,151.02 ms |
| isolated-linker.fresh-restore.cold-cache.cold-store.cold-pnpr | 📈 view plot | 4,965.69 ms |
| isolated-linker.fresh-restore.hot-cache.hot-store | 📈 view plot | 707.88 ms |
- Validate f64 indices against MAX_ARRAY_INDEX cap - Implement look-ahead container creation to match TS behavior - Correct Object vs Array handling for Index segments - Reject empty property paths in set/get/delete - Block unsafe keys (__proto__, constructor.prototype) in delete path - Return Result<Option<Value>> from get_output, omit missing keys - Use is_some_and instead of map_or(false, ...) per clippy - Add trailing commas for dylint Closes pnpm#12795
| } else { | ||
| let replacement = if needs_array { | ||
| let mut arr = Vec::with_capacity(index.saturating_add(1)); | ||
| arr.resize(index.saturating_add(1), Value::Null); | ||
| Value::Array(arr) | ||
| } else { | ||
| let mut map = Map::new(); | ||
| map.insert(idx_to_string(*idx), Value::Null); | ||
| Value::Object(map) | ||
| }; | ||
| *current = replacement; | ||
| } |
There was a problem hiding this comment.
2. Index traversal loses position 🐞 Bug ≡ Correctness
In set_object_value_by_property_path, when an intermediate Segment::Index is applied while current is neither an object nor array, the code replaces current with a container but does not descend into the indexed slot. Subsequent segments then apply at the wrong level, producing an incorrect JSON structure for paths that start with (or encounter) an index while the current value is scalar.
Agent Prompt
### Issue description
In `set_object_value_by_property_path`, the `Segment::Index` traversal has a branch where `current` is neither object nor array. That branch sets `*current = replacement` but never updates `current` to the newly created `arr[index]` (array case) or `obj[key]` (object case). Any remaining segments are then applied to the wrong node.
### Issue Context
This breaks nested sets for some valid property paths when the JSON root (or an intermediate value reached via index traversal) is scalar and must be replaced to continue.
### Fix Focus Areas
- pacquet/crates/cli/src/cli_args/pkg.rs[292-360]
### Suggested approach
After `*current = replacement;`:
- If `needs_array` is true: ensure the array has length `index + 1` and set `current = &mut arr[index]`.
- Else: ensure the object contains `key` and set `current = obj.get_mut(&key).unwrap()`.
Add a unit test that demonstrates the bug, e.g. starting with a scalar root and running a path like `[0].a` or similar, and verifying the nested structure is created under key/index `0` rather than at the root.
ⓘ 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 007bdfd |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
pacquet/crates/cli/src/cli_args/pkg/tests.rs (1)
272-298: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winAdd a regression test for
setwith an index segment on an existing object/array.Current
settests only cover creating containers from scratch ({}) or replacing a scalar. None assert what happens when the target key already holds a valid container of the "other" type — exactly the data-loss case flagged inpkg.rs(Lines 295-345). A test like the following would pin the intended parity behavior and fail against the current overwrite logic:#[test] fn test_set_index_on_existing_object_preserves_object() { let mut value = json!({ "scripts": { "test": "echo test" } }); set_object_value_by_property_path(&mut value, "scripts[0]", json!("x")).unwrap(); // Expected (consistent with get/delete): index-as-string-key on the object, // existing entries retained — confirm against pnpm TS. assert_eq!(value["scripts"]["test"], "echo test"); }🤖 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/pkg/tests.rs` around lines 272 - 298, Add a regression test in the `set_object_value_by_property_path` test module for the case where an index segment is applied to an existing container of the other type, not just `{}` or scalars. Use a test alongside `test_set_creates_array_from_scratch`, `test_set_creates_array_intermediate`, and `test_set_replaces_scalar_with_object/array` to verify that setting a path like `scripts[0]` on an existing object preserves existing object data instead of overwriting it. This should lock in the intended `set` behavior for mixed object/array paths and catch the data-loss case in `pkg.rs`.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@pacquet/crates/cli/src/cli_args/pkg/tests.rs`:
- Around line 272-298: Add a regression test in the
`set_object_value_by_property_path` test module for the case where an index
segment is applied to an existing container of the other type, not just `{}` or
scalars. Use a test alongside `test_set_creates_array_from_scratch`,
`test_set_creates_array_intermediate`, and
`test_set_replaces_scalar_with_object/array` to verify that setting a path like
`scripts[0]` on an existing object preserves existing object data instead of
overwriting it. This should lock in the intended `set` behavior for mixed
object/array paths and catch the data-loss case in `pkg.rs`.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 3ac51955-bf47-4462-83c0-cd70aaa46e19
📒 Files selected for processing (2)
pacquet/crates/cli/src/cli_args/pkg.rspacquet/crates/cli/src/cli_args/pkg/tests.rs
|
Code review by qodo was updated up to the latest commit 007bdfd |
Perfectionist lint rejects trailing commas on single-line macro invocations.
|
Code review by qodo was updated up to the latest commit 3795988 |
|
Code review by qodo was updated up to the latest commit 3795988 |
Support -r/--recursive flag for pkg get/set/delete/fix, matching the TS reference. Recursive get returns a JSON object keyed by package name. Set/delete/fix run sequentially on each selected workspace project.
|
Code review by qodo was updated up to the latest commit 24f7d71 |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
pacquet/crates/cli/src/cli_args/dispatch_script.rs (1)
31-38: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winInconsistent error-propagation pattern vs. sibling
run()dispatcher.Here, errors from
args.run_recursive(...)/args.run(...)are captured intoresultand deferred into thefuture::ready(result)payload, so the function itself always returnsOk(future). The adjacentrun()dispatcher (lines 49-56) instead uses?to propagate errors immediately, only ever wrappingOk(())in the ready future. Functionally the error should still surface once the future is polled/awaited, but if any code path treats "dispatch function returnedErr" differently from "future resolved toErr" (e.g., spinner/telemetry/early-exit logic before polling), this divergence could produce different observable behavior forpkgvs. other commands.Consider aligning
pkgwith the establishedrun()pattern for consistency, unless the deferred-error behavior is intentional:♻️ Align with existing dispatcher pattern
pub(super) fn pkg<'a>(ctx: &RunCtx<'a>, args: PkgArgs) -> miette::Result<CommandFuture<'a>> { - let result = if ctx.recursive { - args.run_recursive((ctx.config)()?, ctx.dir) - } else { - args.run(ctx.manifest_path) - }; - Ok(Box::pin(std::future::ready(result))) + if ctx.recursive { + args.run_recursive((ctx.config)()?, ctx.dir)?; + } else { + args.run(ctx.manifest_path)?; + } + Ok(Box::pin(std::future::ready(Ok(())))) }Could you confirm whether the caller of these
dispatch_*functions handles a top-levelErrthe same way as a future that resolves toErr? If so, this is purely cosmetic; if not, this is a functional discrepancy.🤖 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/dispatch_script.rs` around lines 31 - 38, The pkg dispatcher currently wraps both args.run_recursive(...) and args.run(...) errors inside future::ready(result), unlike the sibling run() dispatcher that uses ? before boxing. Update pkg in dispatch_script.rs to match the run() error-propagation pattern by propagating the dispatch-time error immediately and only returning a ready future for the successful path, using the pkg function and its args.run_recursive/args.run branches as the reference points.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@pacquet/crates/cli/src/cli_args/dispatch_script.rs`:
- Around line 31-38: The pkg dispatcher currently wraps both
args.run_recursive(...) and args.run(...) errors inside future::ready(result),
unlike the sibling run() dispatcher that uses ? before boxing. Update pkg in
dispatch_script.rs to match the run() error-propagation pattern by propagating
the dispatch-time error immediately and only returning a ready future for the
successful path, using the pkg function and its args.run_recursive/args.run
branches as the reference points.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 470a2d3a-dece-47a4-8a58-c1c0e9d78a1f
📒 Files selected for processing (2)
pacquet/crates/cli/src/cli_args/dispatch_script.rspacquet/crates/cli/src/cli_args/pkg.rs
🚧 Files skipped from review as they are similar to previous changes (1)
- pacquet/crates/cli/src/cli_args/pkg.rs
|
Code review by qodo was updated up to the latest commit 24f7d71 |
The pkg command (#12795) added a CliCommand::Pkg variant but did not extend the exhaustive match in command_name, breaking every Rust CI job with a non-exhaustive-patterns compile error.
Brings in the pkg command (#12795), recursive project listing (#12848), the Pkg command_name match fix (#12850), and the github-actions dependency bump (#12785). No conflicts. main's changes are the pkg/list CLI commands and CI workflows, disjoint from the login/auth-commands work; the shared command-registry files (cli_args, cli_command, dispatch, dispatch_query, switch_cli_version) merged cleanly with both the login and the pkg/list entries.
Summary
Implement the
pnpm pkgcommand in Rust for the pacquet CLI, ported from the TypeScript implementation atpnpm/pkg-manifest/commands/src/pkg.ts. The command managespackage.jsonthrough four subcommands:get,set,delete, andfix. This implementation provides native Rust subcommand dispatch via clap instead of the TypeScript approach of parsing positional arguments, while preserving identical observable behavior.Related to: #11633.
Squash Commit Body
Checklist
pacquet/port, or the description notes what still needs porting.pnpm changeset) if this PR changes any publishedpackage. Keep it short and written for pnpm users — it becomes a release note.
Summary by CodeRabbit
pkgCLI command to view, update, delete, and fixpackage.json.fixnormalization by removing fields that don’t match expected shapes.