Skip to content

Pnpm lockfile migration#283

Merged
arcanis merged 13 commits intomainfrom
mael/pnpm-lockfile
Apr 19, 2026
Merged

Pnpm lockfile migration#283
arcanis merged 13 commits intomainfrom
mael/pnpm-lockfile

Conversation

@arcanis
Copy link
Copy Markdown
Member

@arcanis arcanis commented Apr 19, 2026

This PR adds a way to migrate a repository from pnpm to Yarn. I have an older repository I need to migrate so this will be handy.


Note

Medium Risk
Changes how lockfiles are synthesized from pnpm list output (depth handling, path resolution, optional dependency filtering), which can affect dependency resolution during migrations. Adds new e2e coverage and updates CI sandbox tooling, so failures may surface in build/test pipelines.

Overview
Improves pnpm migration lockfile generation by dynamically choosing pnpm list depth based on pnpm version, normalizing relative package paths to node_modules, and incorporating optionalDependencies only when actually installed.

Updates the e2e sandbox image to install pnpm, adds an e2e pnpm-migration.sh test that compares resolved versions between pnpm and yarn with a mismatch threshold, and expands .gitignore to exclude local .claude/.superset directories.

Reviewed by Cursor Bugbot for commit ed5ae36. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread .claude/settings.local.json Outdated
= manifest.dependencies.into_iter().map(|(n, r)| (n, r, false))
.chain(manifest.optional_dependencies.into_iter().map(|(n, r)| (n, r, true)));

for (name, range, is_optional) in all_dependencies {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional deps missing from pnpm output parsing

Medium Severity

The new code reads optionalDependencies from each package's package.json manifest and then looks them up via entry.dependencies.get(&name). However, PnpmListDependency only deserializes the dependencies key from pnpm's JSON output. Since pnpm's list --json outputs optionalDependencies as a separate top-level key (evidenced by its --no-optional and --prod filter flags), optional dependencies of workspace packages will never be found in entry.dependencies, and the new handling code will silently skip all of them.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1d9b669. Configure here.

@arcanis arcanis merged commit 1e0dd05 into main Apr 19, 2026
11 of 12 checks passed
@arcanis arcanis deleted the mael/pnpm-lockfile branch April 19, 2026 21:03
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ed5ae36. Configure here.

# Capture the versions pnpm resolved (normalized to name@version)
pnpm ls -r --json --depth=Infinity \
| jq -r '.. | objects | select(.version? and .from?) | "\(.from)@\(.version)"' \
| sort -u > "${TEMP_DIR}/pnpm-versions.txt"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test version extraction likely broken for transitive dependencies

Medium Severity

The jq filter builds version strings using \(.from)@\(.version), but the from field in pnpm's JSON output includes the version range for transitive dependencies (e.g., "from": "graceful-fs@^4.2.0"). This produces garbled entries like graceful-fs@^4.2.0@4.2.6 instead of graceful-fs@4.2.6, causing false mismatches with the yarn side. With jest having 100+ transitive dependencies, this likely exceeds the 5-mismatch threshold, making the test always fail — or if from was removed in recent pnpm, the file would be empty and the test would pass vacuously without verifying anything.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ed5ae36. Configure here.

@github-actions
Copy link
Copy Markdown

⏱️ Benchmark Results

gatsby install-full-cold

Metric Base Head Difference
Mean 2.456s 2.460s +0.14% ⚠️
Median 2.447s 2.455s +0.32% ⚠️
Min 2.402s 2.410s
Max 2.651s 2.620s
Std Dev 0.041s 0.038s
📊 Raw benchmark data (gatsby install-full-cold)

Base times: 2.651s, 2.489s, 2.431s, 2.456s, 2.465s, 2.429s, 2.444s, 2.439s, 2.446s, 2.437s, 2.458s, 2.463s, 2.447s, 2.442s, 2.437s, 2.442s, 2.459s, 2.402s, 2.473s, 2.486s, 2.443s, 2.458s, 2.431s, 2.448s, 2.438s, 2.469s, 2.438s, 2.463s, 2.454s, 2.448s

Head times: 2.620s, 2.410s, 2.490s, 2.428s, 2.419s, 2.436s, 2.457s, 2.443s, 2.435s, 2.457s, 2.467s, 2.438s, 2.487s, 2.453s, 2.469s, 2.425s, 2.443s, 2.444s, 2.454s, 2.466s, 2.487s, 2.460s, 2.416s, 2.466s, 2.471s, 2.466s, 2.484s, 2.511s, 2.441s, 2.447s


gatsby install-cache-and-lock (warm, with lockfile)

Metric Base Head Difference
Mean 0.362s 0.368s +1.63% ⚠️
Median 0.361s 0.365s +1.18% ⚠️
Min 0.348s 0.359s
Max 0.388s 0.405s
Std Dev 0.007s 0.009s
📊 Raw benchmark data (gatsby install-cache-and-lock (warm, with lockfile))

Base times: 0.362s, 0.355s, 0.365s, 0.360s, 0.365s, 0.365s, 0.362s, 0.363s, 0.358s, 0.365s, 0.360s, 0.360s, 0.363s, 0.360s, 0.367s, 0.357s, 0.367s, 0.355s, 0.365s, 0.359s, 0.358s, 0.368s, 0.363s, 0.370s, 0.388s, 0.359s, 0.348s, 0.352s, 0.358s, 0.356s

Head times: 0.363s, 0.361s, 0.361s, 0.361s, 0.381s, 0.359s, 0.372s, 0.369s, 0.405s, 0.362s, 0.362s, 0.370s, 0.361s, 0.372s, 0.360s, 0.363s, 0.362s, 0.367s, 0.374s, 0.366s, 0.375s, 0.362s, 0.368s, 0.362s, 0.370s, 0.364s, 0.367s, 0.371s, 0.365s, 0.374s

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant