fix(update): handle mixed direct and transitive selectors#12105
Conversation
Review Summary by QodoFix recursive updates with mixed direct and transitive selectors
WalkthroughsDescription• Fix recursive transitive updates when mixing direct and transitive selectors • Apply update depth to existing dependencies when update patterns present • Add regression test for mixed selector scenarios Diagramflowchart LR
A["Update command with mixed selectors"] --> B["Check updateMatching presence"]
B --> C["Apply updateDepth to existing deps"]
C --> D["Transitive deps updated correctly"]
File Changes1. installing/commands/test/update/update.ts
|
There was a problem hiding this comment.
Pull request overview
Fixes pnpm update behavior when a command mixes transitive update patterns (via updateMatching) with at least one direct dependency selector, which previously caused some transitive matches to be skipped during recursive updates.
Changes:
- Ensure existing (non-selected) dependencies get
updateDepthapplied wheneverproject.updateMatchingis present, even if dependency selectors are used. - Add a regression test covering mixed transitive + direct selector update args.
- Add a patch changeset for
@pnpm/installing.deps-resolverandpnpm.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| installing/deps-resolver/src/toResolveImporter.ts | Applies update depth to existing deps whenever updateMatching is active, fixing skipped transitive updates when selectors are mixed. |
| installing/commands/test/update/update.ts | Adds regression coverage for mixed transitive package-name updates plus a direct dependency selector. |
| .changeset/fix-recursive-update-mixed-selectors.md | Records patch release notes for the behavior fix. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@coderabbitai full review |
✅ Action performedFull review finished. |
|
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 (1)
✅ Files skipped from review due to trivial changes (1)
📝 WalkthroughWalkthroughRemoves the ChangesTransitive Update Depth Fix
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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 |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@installing/deps-resolver/src/toResolveImporter.ts`:
- Around line 80-82: The recursive-update depth-handling fix in the ternary
expression within toResolveImporter.ts that sets updateDepth to -1 must be
mirrored in pacquet's update.rs file to maintain parity. You must either apply
the equivalent depth-handling logic for recursive dependency selectors in
pacquet's update flow, or explicitly open a follow-up issue documenting which
specific parts of pacquet's update.rs require this fix and tracking it for
future work. Update the changeset if needed to reflect that pacquet changes are
pending or planned separately.
🪄 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: d9e1e436-52ec-44e9-b025-f57711bda6ea
📒 Files selected for processing (3)
.changeset/fix-recursive-update-mixed-selectors.mdinstalling/commands/test/update/update.tsinstalling/deps-resolver/src/toResolveImporter.ts
The pnpm regression test passed with and without the fix: the fixture's `latest` dist-tag made a fresh install of `^100.0.0` already resolve to 100.1.0, so the assertion was trivially true. Pin the transitive dep-of-pkg-with-1-dep to 100.0.0 before install so the test genuinely fails without the fix and passes with it. Add pacquet parity regression tests for the same mixed direct/transitive selector scenario (exact-name and glob forms). pacquet has no equivalent source change to make — its `update` matches every bare-name/glob selector against direct deps and locked snapshot names in one pass, so a direct selector never gates the transitive one — but the behavior is guarded by tests to lock in pnpm#12103 parity.
e436bd8 to
1c517d2
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #12105 +/- ##
==========================================
+ Coverage 88.08% 88.12% +0.04%
==========================================
Files 310 310
Lines 41863 41863
==========================================
+ Hits 36874 36892 +18
+ Misses 4989 4971 -18 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
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": 4.23800797256,
"stddev": 0.16305775855810642,
"median": 4.19744091236,
"user": 4.0163353399999995,
"system": 3.4363481399999998,
"min": 3.98560362536,
"max": 4.51092723636,
"times": [
4.12265007636,
3.98560362536,
4.18965503336,
4.16299295436,
4.51092723636,
4.08767783436,
4.35397095236,
4.34915363436,
4.20522679136,
4.41222158736
]
},
{
"command": "pacquet@main",
"mean": 4.28042688316,
"stddev": 0.11228652306196493,
"median": 4.2707805608600005,
"user": 4.076846639999999,
"system": 3.48111524,
"min": 4.1330640363599995,
"max": 4.46026532236,
"times": [
4.31273803736,
4.17386685736,
4.22882308436,
4.16225063836,
4.37039365036,
4.39367901136,
4.46026532236,
4.21591267736,
4.1330640363599995,
4.35327551636
]
},
{
"command": "pnpr@HEAD",
"mean": 2.1630883027600003,
"stddev": 0.1602823793421426,
"median": 2.15865763686,
"user": 2.7258523400000003,
"system": 2.93629594,
"min": 1.97976278936,
"max": 2.39315581936,
"times": [
2.10771797036,
2.20959730336,
1.97976278936,
2.32926977836,
1.9977962633600002,
2.31259130736,
1.9948393963600002,
2.28278478636,
2.39315581936,
2.02336761336
]
},
{
"command": "pnpr@main",
"mean": 2.14068684266,
"stddev": 0.09054036512534056,
"median": 2.10254157286,
"user": 2.7560455400000006,
"system": 2.96087064,
"min": 2.03415653436,
"max": 2.29837598836,
"times": [
2.11702531136,
2.22538009436,
2.05538504436,
2.29837598836,
2.23587733236,
2.07773361536,
2.07939103536,
2.03415653436,
2.08805783436,
2.19548563636
]
}
]
}Scenario: Isolated linker: fresh restore, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 0.6660764374799999,
"stddev": 0.01244197015236164,
"median": 0.66247816528,
"user": 0.39148644000000005,
"system": 1.36386274,
"min": 0.65071622328,
"max": 0.68344748828,
"times": [
0.65071622328,
0.68344748828,
0.66274277728,
0.6542449312799999,
0.68315859328,
0.65778442428,
0.68262295128,
0.65871023128,
0.66512320128,
0.66221355328
]
},
{
"command": "pacquet@main",
"mean": 0.6501841343800001,
"stddev": 0.026468799562158512,
"median": 0.63966413378,
"user": 0.39287294,
"system": 1.3300769399999999,
"min": 0.63177368928,
"max": 0.71008214428,
"times": [
0.63410054128,
0.64996391028,
0.63970959528,
0.63366653028,
0.64030775128,
0.63600437528,
0.63177368928,
0.63961867228,
0.71008214428,
0.68661413428
]
},
{
"command": "pnpr@HEAD",
"mean": 0.7001298877800001,
"stddev": 0.014459856841055429,
"median": 0.70229330628,
"user": 0.39716584000000005,
"system": 1.3741323399999998,
"min": 0.6755770652799999,
"max": 0.72336842128,
"times": [
0.72336842128,
0.70169533128,
0.70489244828,
0.6755770652799999,
0.68773311728,
0.69411822028,
0.71713858428,
0.68677558628,
0.70289128128,
0.70710882228
]
},
{
"command": "pnpr@main",
"mean": 0.72138622338,
"stddev": 0.08632778338468858,
"median": 0.69536631478,
"user": 0.39771123999999997,
"system": 1.37238574,
"min": 0.67732335028,
"max": 0.96493477828,
"times": [
0.67732335028,
0.70827812228,
0.70991000228,
0.68249710728,
0.70712822928,
0.69562942928,
0.68413111328,
0.6951032002799999,
0.68892690128,
0.96493477828
]
}
]
}Scenario: Isolated linker: fresh install, cold cache + cold store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 4.3180261819000005,
"stddev": 0.04474187146046556,
"median": 4.3207350275000005,
"user": 3.90186134,
"system": 3.3342726199999992,
"min": 4.2582804625,
"max": 4.3787499595,
"times": [
4.3787499595,
4.2582804625,
4.2889789975,
4.2697539195,
4.2930148025,
4.3504292275,
4.3532019325,
4.348455252500001,
4.3629493825,
4.2764478825
]
},
{
"command": "pacquet@main",
"mean": 4.3595529759,
"stddev": 0.06866152008181975,
"median": 4.389542281500001,
"user": 3.9831047400000004,
"system": 3.37138222,
"min": 4.2603806115000005,
"max": 4.4322498265000005,
"times": [
4.3077162695,
4.2603806115000005,
4.2638083265,
4.3013665135000005,
4.3762892055,
4.4027953575000005,
4.4322498265000005,
4.4177418265,
4.4202857265,
4.412896095500001
]
},
{
"command": "pnpr@HEAD",
"mean": 2.1714269060999998,
"stddev": 0.10061876944509626,
"median": 2.1559153364999997,
"user": 2.5760903399999995,
"system": 2.87139372,
"min": 2.0509690155,
"max": 2.3795682015,
"times": [
2.1075894925,
2.0983551095,
2.0760377825,
2.2413614545000002,
2.3795682015,
2.2005176445,
2.1925397335,
2.2480396875,
2.0509690155,
2.1192909395
]
},
{
"command": "pnpr@main",
"mean": 2.2723553273,
"stddev": 0.1574269624696403,
"median": 2.2937295129999997,
"user": 2.57933584,
"system": 2.87228292,
"min": 2.0567198465,
"max": 2.5373417395,
"times": [
2.4211752815,
2.3401401845,
2.1721453455,
2.2877244345,
2.3827663935,
2.0603581215,
2.5373417395,
2.2997345915,
2.0567198465,
2.1654473345
]
}
]
}Scenario: Isolated linker: fresh install, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 1.4226861790400003,
"stddev": 0.01595274481348487,
"median": 1.4213368542400002,
"user": 1.41132866,
"system": 1.7261297599999998,
"min": 1.3961074287400002,
"max": 1.4467048067400001,
"times": [
1.4411840017400002,
1.4166150537400002,
1.4316420247400001,
1.4467048067400001,
1.42605865474,
1.4120256567400002,
1.4345923577400002,
1.4127468867400002,
1.40918491874,
1.3961074287400002
]
},
{
"command": "pacquet@main",
"mean": 1.39600750604,
"stddev": 0.03475260264459405,
"median": 1.40146775774,
"user": 1.3776443600000001,
"system": 1.73430696,
"min": 1.35407799674,
"max": 1.45578023074,
"times": [
1.45578023074,
1.4150242827400001,
1.40073773774,
1.40219777774,
1.42788469874,
1.41715889174,
1.3757021447400002,
1.35407799674,
1.35609306274,
1.35541823674
]
},
{
"command": "pnpr@HEAD",
"mean": 0.65627861934,
"stddev": 0.02634540166646343,
"median": 0.64746936974,
"user": 0.32675186,
"system": 1.27948016,
"min": 0.6343707497400001,
"max": 0.7183614607400001,
"times": [
0.64653193074,
0.64144255174,
0.6874961967400001,
0.64440347874,
0.6343707497400001,
0.63705070774,
0.7183614607400001,
0.64924235974,
0.64840680874,
0.65547994874
]
},
{
"command": "pnpr@main",
"mean": 0.69011934284,
"stddev": 0.05441912174511755,
"median": 0.67626686524,
"user": 0.33348856,
"system": 1.30215846,
"min": 0.6609191547400001,
"max": 0.84341047674,
"times": [
0.68197888074,
0.67961555774,
0.66152894574,
0.68142824974,
0.68000752174,
0.6609191547400001,
0.66979583074,
0.67291817274,
0.66959063774,
0.84341047674
]
}
]
}Scenario: Isolated linker: fresh install, cold cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 3.02310723282,
"stddev": 0.019501791954577744,
"median": 3.02324974322,
"user": 1.7936514199999998,
"system": 1.9740987999999997,
"min": 2.9969320227200003,
"max": 3.05500728772,
"times": [
3.0306530457200003,
3.0024214167200003,
3.01536041972,
2.9969320227200003,
3.02583600772,
3.02066347872,
3.00173855472,
3.05500728772,
3.04614721772,
3.0363128767200003
]
},
{
"command": "pacquet@main",
"mean": 3.02337936702,
"stddev": 0.03009806952657256,
"median": 3.02703913772,
"user": 1.80710682,
"system": 1.9733197999999998,
"min": 2.9819797397200003,
"max": 3.06132981372,
"times": [
3.0278297487200003,
3.03035740372,
3.01722654472,
2.9902204777200003,
2.98446198972,
2.9819797397200003,
3.06132981372,
3.05680762272,
3.0573318027200003,
3.0262485267200003
]
},
{
"command": "pnpr@HEAD",
"mean": 0.67523756922,
"stddev": 0.014379551410715426,
"median": 0.67167992422,
"user": 0.35035701999999996,
"system": 1.3272961,
"min": 0.66046095372,
"max": 0.71152358372,
"times": [
0.66380741972,
0.6772262437200001,
0.66957749972,
0.66046095372,
0.66645805972,
0.71152358372,
0.6721057397200001,
0.6775806687200001,
0.68238141472,
0.67125410872
]
},
{
"command": "pnpr@main",
"mean": 0.6615865138200001,
"stddev": 0.013107970458177978,
"median": 0.66195390122,
"user": 0.34420032,
"system": 1.2905337999999997,
"min": 0.6424290117200001,
"max": 0.68713128172,
"times": [
0.6658547707200001,
0.64419037572,
0.6424290117200001,
0.65383001072,
0.6605909887200001,
0.6621955967200001,
0.66171220572,
0.66474983572,
0.68713128172,
0.6731810607200001
]
}
]
} |
|
| Branch | pr/12105 |
| 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,318.03 ms(+3.33%)Baseline: 4,178.67 ms | 5,014.41 ms (86.11%) |
| isolated-linker.fresh-install.cold-cache.hot-store | 📈 view plot 🚷 view threshold | 3,023.11 ms(+1.02%)Baseline: 2,992.57 ms | 3,591.08 ms (84.18%) |
| isolated-linker.fresh-install.hot-cache.hot-store | 📈 view plot 🚷 view threshold | 1,422.69 ms(+8.51%)Baseline: 1,311.15 ms | 1,573.38 ms (90.42%) |
| isolated-linker.fresh-restore.cold-cache.cold-store | 📈 view plot 🚷 view threshold | 4,238.01 ms(+3.93%)Baseline: 4,077.75 ms | 4,893.31 ms (86.61%) |
| isolated-linker.fresh-restore.hot-cache.hot-store | 📈 view plot 🚷 view threshold | 666.08 ms(+8.56%)Baseline: 613.54 ms | 736.25 ms (90.47%) |
|
| Branch | pr/12105 |
| 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,171.43 ms |
| isolated-linker.fresh-install.cold-cache.hot-store | 📈 view plot | 675.24 ms |
| isolated-linker.fresh-install.hot-cache.hot-store | 📈 view plot | 656.28 ms |
| isolated-linker.fresh-restore.cold-cache.cold-store | 📈 view plot | 2,163.09 ms |
| isolated-linker.fresh-restore.hot-cache.hot-store | 📈 view plot | 700.13 ms |
Summary
Fixes recursive transitive updates when the update command mixes a transitive dependency pattern with a direct dependency selector.
For example,
pnpm up -r "@babel/core" uuidnow updates matching transitive@babel/coredependencies even whenuuidis a direct dependency selector.Fixes #12103.
Root Cause
noDependencySelectorswas computed globally. If any importer had a direct selector, existing transitive dependencies were assignedupdateDepth: -1, soupdateMatchingcould match the package name but still fail to request an update.Changes
defaultUpdateDepthto existing deps wheneverproject.updateMatchingis present.@pnpm/installing.deps-resolverandpnpm.Validation
pnpm --filter @pnpm/installing.deps-resolver run compilepnpm --filter @pnpm/installing.commands test test/update/update.ts -t "mixed with a direct"~/fathom/frontend/fathom-frontend-bench1with local pnpm bundle:pnpm up -r "@babel/core" uuidnow adds@babel/core@7.29.7@babel/core@7.29.7Push hooks also passed TypeScript build, pnpm compile, spellcheck, metadata lint, eslint, cargo fmt/doc, and taplo format. Existing skipped-test warnings were reported;
cargo-dylintwas not installed and was skipped by the hook.Written by an agent (opencode, gpt-5.5).
Summary by CodeRabbit
Bug Fixes
Tests