fix: keep the path of git-hosted tarball resolutions in the lockfile#12344
Conversation
Code Review by Qodo🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)
Great, no issues found!Qodo reviewed your code and found no material issues that require reviewPrevious review resultsReview updated until commit bd9b738 Results up to commit 7cbf4a8
Great, no issues found!Qodo reviewed your code and found no material issues that require reviewResults up to commit f06ef86
Great, no issues found!Qodo reviewed your code and found no material issues that require reviewResults up to commit a10a313
Great, no issues found!Qodo reviewed your code and found no material issues that require reviewResults up to commit 16ce549
Great, no issues found!Qodo reviewed your code and found no material issues that require reviewResults up to commit 04f6937
Great, no issues found!Qodo reviewed your code and found no material issues that require reviewResults up to commit 4a04657
Great, no issues found!Qodo reviewed your code and found no material issues that require review |
|
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:
📝 WalkthroughWalkthroughCentralizes tarball-URL preservation in ChangesGit Tarball Path Preservation
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 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 |
PR Summary by QodoFix lockfile to preserve WalkthroughsDescription• Preserve resolution.path when serializing git-hosted tarball resolutions to the lockfile. • Prevent frozen installs from unpacking repo root instead of the requested subdirectory. • Add regression tests for both lockfileIncludeTarballUrl branches. Diagramgraph TD
A["Install flow"] --> B["Resolution (tarball)"] --> C["toLockfileResolution"] --> D[("pnpm-lock.yaml")]
D --> E["Git tarball fetcher"] --> F["preparePackage (extract)"]
High-Level AssessmentThe following are alternative approaches to this PR: 1. Preserve original resolution object when adding integrity
2. Add an integration test that installs a git subdir dependency end-to-end
Recommendation: The chosen approach is best: keep the existing lockfile canonicalization behavior, but centralize the kept-URL construction and explicitly carry File ChangesBug fix (1)
Tests (1)
Documentation (1)
|
4a04657 to
04f6937
Compare
|
Code review by qodo was updated up to the latest commit 04f6937 |
…file_form Adds regression tests guarding that to_lockfile_form keeps the TarballResolution.path of git-hosted subdirectory tarballs in both the default and include_tarball_url branches, mirroring the TypeScript toLockfileResolution tests added for pnpm#12304.
|
Code review by qodo was updated up to the latest commit 16ce549 |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #12344 +/- ##
==========================================
+ Coverage 88.18% 88.19% +0.01%
==========================================
Files 296 296
Lines 37944 37951 +7
==========================================
+ Hits 33460 33471 +11
+ Misses 4484 4480 -4 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Code review by qodo was updated up to the latest commit a10a313 |
|
Code review by qodo was updated up to the latest commit f06ef86 |
…L return Invert the branching so the canonical-registry case is the early return and the kept-URL resolution (integrity + tarball + optional gitHosted/path) is the single fall-through. This removes the keepTarballUrl closure and the preservingGitHosted helper; the URL-reconstruction check moves into isCanonicalRegistryTarballUrl. No behavior change.
f06ef86 to
7cbf4a8
Compare
|
Code review by qodo was updated up to the latest commit 7cbf4a8 |
…form Apply the same inversion as the TypeScript toLockfileResolution: the canonical-registry case becomes the early return and the kept-URL resolution is the single fall-through, removing the keep_url closure. The URL-match check moves into is_canonical_registry_tarball_url. No behavior change; the three to_lockfile_form tests still pass.
|
Code review by qodo was updated up to the latest commit bd9b738 |
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.05213986552,
"stddev": 0.0785391947215144,
"median": 4.07175035712,
"user": 4.00579764,
"system": 3.40077332,
"min": 3.91400591562,
"max": 4.14948833662,
"times": [
4.13156883162,
4.094147277619999,
3.95258684862,
4.01684275262,
4.06106767162,
4.11715334962,
4.00210462862,
4.08243304262,
3.91400591562,
4.14948833662
]
},
{
"command": "pacquet@main",
"mean": 4.11116962642,
"stddev": 0.14686412525651948,
"median": 4.03437754712,
"user": 3.97370504,
"system": 3.3674522199999997,
"min": 3.93765541162,
"max": 4.29498322062,
"times": [
4.29498322062,
4.268943966619999,
4.29372907262,
3.93765541162,
4.03337953162,
4.253488690619999,
4.02331834162,
4.002837899619999,
3.96798456662,
4.03537556262
]
},
{
"command": "pnpr@HEAD",
"mean": 1.99640305362,
"stddev": 0.118612108443758,
"median": 1.95433983462,
"user": 2.70988834,
"system": 2.85293662,
"min": 1.91352036062,
"max": 2.3069068606200003,
"times": [
1.96804228562,
2.02764267462,
1.91846729162,
1.91352036062,
1.9406373836200002,
2.3069068606200003,
1.93412991862,
1.9771872876199998,
1.9254674576200002,
2.05202901562
]
},
{
"command": "pnpr@main",
"mean": 2.1350770102200003,
"stddev": 0.14072499599289862,
"median": 2.11283413762,
"user": 2.67010064,
"system": 2.8584072199999997,
"min": 1.96733200762,
"max": 2.36761638262,
"times": [
2.02788545362,
2.36761638262,
2.19084702662,
1.96733200762,
2.03482124862,
2.19264573562,
2.26638974562,
2.0237106056200003,
2.00126241862,
2.2782594776200003
]
}
]
}Scenario: Isolated linker: fresh restore, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 0.6129616412400002,
"stddev": 0.012286329315236821,
"median": 0.61209962344,
"user": 0.36055792000000003,
"system": 1.27908312,
"min": 0.5937686884400001,
"max": 0.63970497144,
"times": [
0.6234416224400001,
0.6051056334400001,
0.60509062944,
0.60778743444,
0.6146957674400001,
0.63970497144,
0.5937686884400001,
0.6110846484400001,
0.6158224184400001,
0.61311459844
]
},
{
"command": "pacquet@main",
"mean": 0.63560200854,
"stddev": 0.01610810145627245,
"median": 0.6316561919400001,
"user": 0.37808592,
"system": 1.30603072,
"min": 0.6181911284400001,
"max": 0.66704277044,
"times": [
0.62528562744,
0.62510377244,
0.6181911284400001,
0.6380267564400001,
0.6392586574400001,
0.6395246694400001,
0.6574463154400001,
0.6237889454400001,
0.66704277044,
0.6223514424400001
]
},
{
"command": "pnpr@HEAD",
"mean": 0.6653052152400001,
"stddev": 0.018959929906848052,
"median": 0.6640394014400001,
"user": 0.36534802,
"system": 1.32592572,
"min": 0.6435638154400001,
"max": 0.7027513234400001,
"times": [
0.6486917294400001,
0.6446776724400001,
0.6765931794400001,
0.6818106114400001,
0.6435638154400001,
0.65242319244,
0.66578105744,
0.7027513234400001,
0.6744618254400001,
0.6622977454400001
]
},
{
"command": "pnpr@main",
"mean": 0.6767036136400002,
"stddev": 0.020139430244034445,
"median": 0.67348565894,
"user": 0.38984102,
"system": 1.3255887200000003,
"min": 0.6517290184400001,
"max": 0.7061821454400001,
"times": [
0.7021705424400001,
0.6517290184400001,
0.6711727914400001,
0.6978795604400001,
0.6616887824400001,
0.6841315154400001,
0.6635981674400001,
0.67579852644,
0.7061821454400001,
0.6526850864400001
]
}
]
}Scenario: Isolated linker: fresh install, cold cache + cold store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 4.086195172559999,
"stddev": 0.07720287402635818,
"median": 4.07629366466,
"user": 3.7264787600000004,
"system": 3.2535927,
"min": 3.9845192586600002,
"max": 4.1998557736599995,
"times": [
3.9845192586600002,
4.03243880466,
4.08835269866,
4.06423463066,
4.1609010156599995,
4.03552064166,
3.99129985666,
4.1998557736599995,
4.12912111166,
4.17570793366
]
},
{
"command": "pacquet@main",
"mean": 4.13483446386,
"stddev": 0.09897322488124125,
"median": 4.12275247266,
"user": 3.78675846,
"system": 3.2655794,
"min": 4.002919008659999,
"max": 4.2636077306599995,
"times": [
4.2636077306599995,
4.25813028466,
4.23166740966,
4.002919008659999,
4.041949315659999,
4.18368984666,
4.1828037156599995,
4.06270122966,
4.06269280366,
4.05818329366
]
},
{
"command": "pnpr@HEAD",
"mean": 2.24522761946,
"stddev": 0.13501396740979438,
"median": 2.29314626566,
"user": 2.53255906,
"system": 2.7586581000000003,
"min": 2.02650421566,
"max": 2.4202772506600003,
"times": [
2.06188309066,
2.08980985466,
2.31843153466,
2.28743850766,
2.31046657466,
2.28353426066,
2.02650421566,
2.35507688166,
2.4202772506600003,
2.29885402366
]
},
{
"command": "pnpr@main",
"mean": 2.2227252522599996,
"stddev": 0.11704715947647382,
"median": 2.24877710266,
"user": 2.51233666,
"system": 2.804182,
"min": 2.05522545466,
"max": 2.41248348966,
"times": [
2.26427734866,
2.41248348966,
2.23960031566,
2.35349458766,
2.25795388966,
2.1897659536600003,
2.2710238936600002,
2.05522545466,
2.11554516466,
2.06788242466
]
}
]
}Scenario: Isolated linker: fresh install, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 1.2156292371399997,
"stddev": 0.02101844585969297,
"median": 1.20561789924,
"user": 1.1959720199999997,
"system": 1.66536222,
"min": 1.19542181374,
"max": 1.2502084147399999,
"times": [
1.23735635774,
1.20594335174,
1.24794700774,
1.19707455574,
1.20529244674,
1.19542181374,
1.2086262637399998,
1.20479329974,
1.20362885974,
1.2502084147399999
]
},
{
"command": "pacquet@main",
"mean": 1.2557947184399998,
"stddev": 0.09250681264506788,
"median": 1.22723726724,
"user": 1.2325027199999998,
"system": 1.7078360199999998,
"min": 1.2144865677399999,
"max": 1.51815945774,
"times": [
1.2144865677399999,
1.23538813974,
1.2346868597399998,
1.51815945774,
1.2197643487399998,
1.2283732087399999,
1.22610132574,
1.22084510774,
1.22178575774,
1.23835641074
]
},
{
"command": "pnpr@HEAD",
"mean": 0.6362998080400001,
"stddev": 0.014225014935037417,
"median": 0.63314515524,
"user": 0.31593782,
"system": 1.2476881199999998,
"min": 0.6175687577400001,
"max": 0.66705936574,
"times": [
0.6277746297400001,
0.6175687577400001,
0.62291383974,
0.64474007174,
0.66705936574,
0.62979920674,
0.64161674674,
0.6364911037400001,
0.64550240674,
0.6295319517400001
]
},
{
"command": "pnpr@main",
"mean": 0.63947599894,
"stddev": 0.013467262649056025,
"median": 0.63392616924,
"user": 0.33478582000000007,
"system": 1.26068242,
"min": 0.6257257177400001,
"max": 0.66957977374,
"times": [
0.64735350374,
0.6289637237400001,
0.6330877567400001,
0.66957977374,
0.63168759974,
0.65343685074,
0.6370727247400001,
0.63334185874,
0.6345104797400001,
0.6257257177400001
]
}
]
}Scenario: Isolated linker: fresh install, cold cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 2.9031241892400006,
"stddev": 0.04946516856036864,
"median": 2.89088735714,
"user": 1.6377351399999998,
"system": 1.9658668000000001,
"min": 2.8554925406400002,
"max": 3.02188654964,
"times": [
2.89820538364,
2.92327823864,
2.93922910264,
2.89874784464,
2.8554925406400002,
2.88356933064,
2.88008986064,
2.85726733564,
3.02188654964,
2.8734757056399998
]
},
{
"command": "pacquet@main",
"mean": 2.90850666924,
"stddev": 0.030296709546101395,
"median": 2.90781671964,
"user": 1.6915931400000002,
"system": 1.9674521999999999,
"min": 2.87266316764,
"max": 2.96686942464,
"times": [
2.88329969164,
2.88119302764,
2.9113823976399997,
2.93444590064,
2.87959300264,
2.87266316764,
2.92006135964,
2.93130767864,
2.96686942464,
2.9042510416400003
]
},
{
"command": "pnpr@HEAD",
"mean": 0.6264605051400001,
"stddev": 0.010077070703272269,
"median": 0.62185758664,
"user": 0.30399144,
"system": 1.2420826999999999,
"min": 0.6176750136400001,
"max": 0.64571807964,
"times": [
0.6404109296400001,
0.64571807964,
0.6207637046400001,
0.6186818096400001,
0.62078815064,
0.6236045816400001,
0.61911622064,
0.6176750136400001,
0.6229270226400001,
0.6349195386400001
]
},
{
"command": "pnpr@main",
"mean": 0.6604159278400001,
"stddev": 0.07579103364254736,
"median": 0.6409355116400002,
"user": 0.33992714,
"system": 1.2500078999999997,
"min": 0.6232368586400001,
"max": 0.8748104226400001,
"times": [
0.6232368586400001,
0.6373992586400001,
0.6444574646400001,
0.6379809876400001,
0.6439940606400001,
0.6262938936400001,
0.6452352276400001,
0.6268610686400001,
0.8748104226400001,
0.6438900356400001
]
}
]
} |
|
| Branch | pr/12344 |
| 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,086.20 ms(-22.99%)Baseline: 5,306.40 ms | 6,367.68 ms (64.17%) |
| isolated-linker.fresh-install.cold-cache.hot-store | 📈 view plot 🚷 view threshold | 2,903.12 ms(-11.64%)Baseline: 3,285.61 ms | 3,942.73 ms (73.63%) |
| isolated-linker.fresh-install.hot-cache.hot-store | 📈 view plot 🚷 view threshold | 1,215.63 ms(+0.88%)Baseline: 1,205.07 ms | 1,446.08 ms (84.06%) |
| isolated-linker.fresh-restore.cold-cache.cold-store | 📈 view plot 🚷 view threshold | 4,052.14 ms(-30.27%)Baseline: 5,811.11 ms | 6,973.33 ms (58.11%) |
| isolated-linker.fresh-restore.hot-cache.hot-store | 📈 view plot 🚷 view threshold | 612.96 ms(-6.91%)Baseline: 658.49 ms | 790.19 ms (77.57%) |
|
| Branch | pr/12344 |
| 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,245.23 ms |
| isolated-linker.fresh-install.cold-cache.hot-store | 📈 view plot | 626.46 ms |
| isolated-linker.fresh-install.hot-cache.hot-store | 📈 view plot | 636.30 ms |
| isolated-linker.fresh-restore.cold-cache.cold-store | 📈 view plot | 1,996.40 ms |
| isolated-linker.fresh-restore.hot-cache.hot-store | 📈 view plot | 665.31 ms |
|
Congrats on merging your first pull request! 🎉🎉🎉 |
Closes #12304
When a git dependency points to a subdirectory of a repository (
github:user/repo#commit&path:/sub/dir), the lockfile resolution loses itspathfield on any install that actually downloads the tarball. Installs served from the store keep it, which is why the field disappears "randomly and frequently" — it depends on store state, not on the project.Root cause
Since #11481 (pin integrity of git-hosted tarballs in the lockfile), a real download attaches the computed integrity to the resolution object (
installing/package-requester/src/packageRequester.ts, theif (fetchedResult.integrity && ...)block). With integrity present,toLockfileResolutionno longer passes the resolution through unchanged — it rebuilds the kept-URL form as{ integrity, tarball, gitHosted }, droppingpath. Without a download there is no integrity, the early!resolution['integrity']return fires, and the resolution (includingpath) is written as-is.The consequence is worse than a broken lockfile diff:
pathis what the git-hosted tarball fetcher passes topreparePackageto extract the subdirectory. A frozen install from the damaged lockfile silently unpacks the repository root instead of the requested package.The Rust port already preserves the field —
LockfileResolution::to_lockfile_formclonespathinto its kept-URL branch (pacquet/crates/lockfile/src/resolution.rs) — so this aligns the TS original with it.Reproduction (pnpm 11.2.2, also on latest)
{ "dependencies": { "simple-react-app": "github:RexSkz/test-git-subfolder-fetch#beta&path:/packages/simple-react-app" } }Cold store (tarball downloaded):
pathgone. Same project, warm store (no download):pathkept. A--frozen-lockfileinstall from the cold-store lockfile on a fresh store then installs the monorepo root (monorepo-example,private: true) under the dependency's name, with no error.Fix
toLockfileResolutionbuilds its kept-URL form in one place (mirroring the Rust port'skeep_url()) and carriespaththrough. Regression tests cover both the default and thelockfileIncludeTarballUrl: truebranches.Summary by CodeRabbit
Bug Fixes
Documentation
Tests