Introduce yarn.lock -> bun.lock{b} migrator (#16166)

### What does this PR do?

fixes #6409

This PR implements `bun install` automatic migration from yarn.lock
files to bun.lock, preserving versions exactly. The migration happens
automatically when:

1. A project has a `yarn.lock` file
2. No `bun.lock` or `bun.lockb` file exists  
3. User runs `bun install`

### Current Status:  Complete and Working

The yarn.lock migration feature is **fully functional and
comprehensively tested**. All dependency types are supported:

-  Regular npm dependencies (`package@^1.0.0`)
-  Git dependencies (`git+https://github.com/user/repo.git`,
`github:user/repo`)
-  NPM alias dependencies (`alias@npm:package@version`)
-  File dependencies (`file:./path`)
-  Remote tarball URLs (`https://registry.npmjs.org/package.tgz`)
-  Local tarball files (`file:package.tgz`)

### Test Results

```bash
$ bun bd test test/cli/install/migration/yarn-lock-migration.test.ts
 4 pass, 0 fail
- yarn-lock-mkdirp (basic npm dependency)
- yarn-lock-mkdirp-no-resolved (npm dependency without resolved field)
- yarn-lock-mkdirp-file-dep (file dependency)
- yarn-stuff (all complex dependency types: git, npm aliases, file, remote tarballs)
```

### How did you verify your code works?

1. **Comprehensive test suite**: Added 4 test cases covering all
dependency types
2. **Version preservation**: Verified that package versions are
preserved exactly during migration
3. **Real-world scenarios**: Tested with complex yarn.lock files
containing git deps, npm aliases, file deps, and remote tarballs
4. **Migration logging**: Confirms migration with log message `[X.XXms]
migrated lockfile from yarn.lock`

### Key Implementation Details

- **Core parser**: `src/install/yarn.zig` handles all yarn.lock parsing
and dependency type resolution
- **Integration**: Migration is built into existing lockfile loading
infrastructure
- **Performance**: Migration typically completes in ~1ms for most
projects
- **Compatibility**: Preserves exact dependency versions and resolution
behavior

The implementation correctly handles edge cases like npm aliases, git
dependencies with commits, file dependencies with transitive deps, and
remote tarballs.

---------

Co-authored-by: Jarred-Sumner <709451+Jarred-Sumner@users.noreply.github.com>
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: RiskyMH <git@riskymh.dev>
Co-authored-by: RiskyMH <56214343+RiskyMH@users.noreply.github.com>
This commit is contained in:
Jarred Sumner
2025-07-29 13:07:47 -07:00
committed by GitHub
parent 245abb92fb
commit 003d13ec27
25 changed files with 14347 additions and 1 deletions

View File

@@ -168,5 +168,5 @@
"WebKit/WebInspectorUI": true, "WebKit/WebInspectorUI": true,
}, },
"git.detectSubmodules": false, "git.detectSubmodules": false,
// "bun.test.customScript": "./build/debug/bun-debug test" "bun.test.customScript": "./build/debug/bun-debug test"
} }

View File

@@ -634,6 +634,7 @@ src/install/resolvers/folder_resolver.zig
src/install/versioned_url.zig src/install/versioned_url.zig
src/install/windows-shim/BinLinkingShim.zig src/install/windows-shim/BinLinkingShim.zig
src/install/windows-shim/bun_shim_impl.zig src/install/windows-shim/bun_shim_impl.zig
src/install/yarn.zig
src/interchange.zig src/interchange.zig
src/interchange/json.zig src/interchange/json.zig
src/interchange/toml.zig src/interchange/toml.zig

View File

@@ -52,6 +52,38 @@ pub fn detectAndLoadOtherLockfile(
return migrate_result; return migrate_result;
} }
yarn: {
var timer = std.time.Timer.start() catch unreachable;
const lockfile = File.openat(dir, "yarn.lock", bun.O.RDONLY, 0).unwrap() catch break :yarn;
defer lockfile.close();
const data = lockfile.readToEnd(allocator).unwrap() catch break :yarn;
const migrate_result = @import("./yarn.zig").migrateYarnLockfile(this, manager, allocator, log, data, dir) catch |err| {
if (Environment.isDebug) {
bun.handleErrorReturnTrace(err, @errorReturnTrace());
Output.prettyErrorln("Error: {s}", .{@errorName(err)});
log.print(Output.errorWriter()) catch {};
Output.prettyErrorln("Invalid yarn.lock\nIn a release build, this would ignore and do a fresh install.\nAborting", .{});
Global.exit(1);
}
return LoadResult{ .err = .{
.step = .migrating,
.value = err,
.lockfile_path = "yarn.lock",
.format = .binary,
} };
};
if (migrate_result == .ok) {
Output.printElapsed(@as(f64, @floatFromInt(timer.read())) / std.time.ns_per_ms);
Output.prettyError(" ", .{});
Output.prettyErrorln("<d>migrated lockfile from <r><green>yarn.lock<r>", .{});
Output.flush();
}
return migrate_result;
}
return LoadResult{ .not_found = {} }; return LoadResult{ .not_found = {} };
} }

1718
src/install/yarn.zig Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
# we do need it the files here
!yarn.lock

View File

@@ -0,0 +1,157 @@
{
"name": "yarn",
"installationMethod": "unknown",
"version": "1.23.0-0",
"license": "BSD-2-Clause",
"preferGlobal": true,
"description": "📦🐈 Fast, reliable, and secure dependency management.",
"dependencies": {
"@zkochan/cmd-shim": "^3.1.0",
"babel-runtime": "^6.26.0",
"bytes": "^3.0.0",
"camelcase": "^4.0.0",
"chalk": "^2.1.0",
"cli-table3": "^0.4.0",
"commander": "^2.9.0",
"death": "^1.0.0",
"debug": "^3.0.0",
"deep-equal": "^1.0.1",
"detect-indent": "^5.0.0",
"dnscache": "^1.0.1",
"glob": "^7.1.1",
"gunzip-maybe": "^1.4.0",
"hash-for-dep": "^1.2.3",
"imports-loader": "^0.8.0",
"ini": "^1.3.4",
"inquirer": "^6.2.0",
"invariant": "^2.2.0",
"is-builtin-module": "^2.0.0",
"is-ci": "^1.0.10",
"is-webpack-bundle": "^1.0.0",
"js-yaml": "^3.13.1",
"leven": "^2.0.0",
"loud-rejection": "^1.2.0",
"micromatch": "^2.3.11",
"mkdirp": "^0.5.1",
"node-emoji": "^1.6.1",
"normalize-url": "^2.0.0",
"npm-logical-tree": "^1.2.1",
"object-path": "^0.11.2",
"proper-lockfile": "^2.0.0",
"puka": "^1.0.0",
"read": "^1.0.7",
"request": "^2.87.0",
"request-capture-har": "^1.2.2",
"rimraf": "^2.5.0",
"semver": "^5.1.0",
"ssri": "^5.3.0",
"strip-ansi": "^4.0.0",
"strip-bom": "^3.0.0",
"tar-fs": "^1.16.0",
"tar-stream": "^1.6.1",
"uuid": "^3.0.1",
"v8-compile-cache": "^2.0.0",
"validate-npm-package-license": "^3.0.4",
"yn": "^2.0.0"
},
"devDependencies": {
"babel-core": "^6.26.0",
"babel-eslint": "^7.2.3",
"babel-loader": "^6.2.5",
"babel-plugin-array-includes": "^2.0.3",
"babel-plugin-inline-import": "^3.0.0",
"babel-plugin-transform-builtin-extend": "^1.1.2",
"babel-plugin-transform-inline-imports-commonjs": "^1.0.0",
"babel-plugin-transform-runtime": "^6.4.3",
"babel-preset-env": "^1.6.0",
"babel-preset-flow": "^6.23.0",
"babel-preset-stage-0": "^6.0.0",
"babylon": "^6.5.0",
"commitizen": "^2.9.6",
"cz-conventional-changelog": "^2.0.0",
"eslint": "^4.3.0",
"eslint-config-fb-strict": "^22.0.0",
"eslint-plugin-babel": "^5.0.0",
"eslint-plugin-flowtype": "^2.35.0",
"eslint-plugin-jasmine": "^2.6.2",
"eslint-plugin-jest": "^21.0.0",
"eslint-plugin-jsx-a11y": "^6.0.2",
"eslint-plugin-prefer-object-spread": "^1.2.1",
"eslint-plugin-prettier": "^2.1.2",
"eslint-plugin-react": "^7.1.0",
"eslint-plugin-relay": "^0.0.28",
"eslint-plugin-yarn-internal": "file:scripts/eslint-rules",
"execa": "^0.11.0",
"fancy-log": "^1.3.2",
"flow-bin": "^0.66.0",
"git-release-notes": "^3.0.0",
"gulp": "^4.0.0",
"gulp-babel": "^7.0.0",
"gulp-if": "^2.0.1",
"gulp-newer": "^1.0.0",
"gulp-plumber": "^1.0.1",
"gulp-sourcemaps": "^2.2.0",
"jest": "^22.4.4",
"jsinspect": "^0.12.6",
"minimatch": "^3.0.4",
"mock-stdin": "^0.3.0",
"prettier": "^1.5.2",
"string-replace-loader": "^2.1.1",
"temp": "^0.8.3",
"webpack": "^2.1.0-beta.25",
"yargs": "^6.3.0"
},
"resolutions": {
"sshpk": "^1.14.2"
},
"engines": {
"node": ">=4.0.0"
},
"repository": "yarnpkg/yarn",
"bin": {
"yarn": "./bin/yarn.js",
"yarnpkg": "./bin/yarn.js"
},
"scripts": {
"build": "gulp build",
"build-bundle": "node ./scripts/build-webpack.js",
"build-chocolatey": "powershell ./scripts/build-chocolatey.ps1",
"build-deb": "./scripts/build-deb.sh",
"build-dist": "bash ./scripts/build-dist.sh",
"build-win-installer": "scripts\\build-windows-installer.bat",
"changelog": "git-release-notes $(git describe --tags --abbrev=0 $(git describe --tags --abbrev=0)^)..$(git describe --tags --abbrev=0) scripts/changelog.md",
"dupe-check": "yarn jsinspect ./src",
"lint": "eslint . && flow check",
"pkg-tests": "yarn --cwd packages/pkg-tests jest yarn.test.js",
"prettier": "eslint src __tests__ --fix",
"release-branch": "./scripts/release-branch.sh",
"test": "yarn lint && yarn test-only",
"test-only": "node --max_old_space_size=4096 node_modules/jest/bin/jest.js --verbose",
"test-only-debug": "node --inspect-brk --max_old_space_size=4096 node_modules/jest/bin/jest.js --runInBand --verbose",
"test-coverage": "node --max_old_space_size=4096 node_modules/jest/bin/jest.js --coverage --verbose",
"watch": "gulp watch",
"commit": "git-cz"
},
"jest": {
"collectCoverageFrom": [
"src/**/*.js"
],
"testEnvironment": "node",
"modulePathIgnorePatterns": [
"__tests__/fixtures/",
"packages/pkg-tests/pkg-tests-fixtures",
"dist/"
],
"testPathIgnorePatterns": [
"__tests__/(fixtures|__mocks__)/",
"updates/",
"_(temp|mock|install|init|helpers).js$",
"packages/pkg-tests"
]
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
bun.*

View File

@@ -0,0 +1,44 @@
{
"name": "mkdirp",
"description": "Recursively mkdir, like `mkdir -p`",
"version": "1.0.2",
"main": "index.js",
"keywords": [
"mkdir",
"directory",
"make dir",
"make",
"dir",
"recursive",
"native"
],
"repository": {
"type": "git",
"url": "https://github.com/isaacs/node-mkdirp.git"
},
"scripts": {
"test": "tap",
"snap": "tap",
"preversion": "npm test",
"postversion": "npm publish",
"postpublish": "git push origin --follow-tags"
},
"tap": {
"check-coverage": true,
"coverage-map": "map.js"
},
"devDependencies": {
"require-inject": "^1.4.4",
"tap": "^14.10.6"
},
"bin": "bin/cmd.js",
"license": "MIT",
"engines": {
"node": ">=10"
},
"files": [
"bin",
"lib",
"index.js"
]
}

View File

@@ -0,0 +1,5 @@
{
"dependencies": {
"mkdirp": "file:mkdirp"
}
}

View File

@@ -0,0 +1,6 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"mkdirp@file:mkdirp":
version "1.0.2"

View File

@@ -0,0 +1 @@
bun.*

View File

@@ -0,0 +1,5 @@
{
"dependencies": {
"mkdirp": "^1.0.2"
}
}

View File

@@ -0,0 +1,8 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
mkdirp@^1.0.2:
version "1.0.4"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==

View File

@@ -0,0 +1 @@
bun.*

View File

@@ -0,0 +1,5 @@
{
"dependencies": {
"mkdirp": "^1.0.2"
}
}

View File

@@ -0,0 +1,8 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
mkdirp@^1.0.2:
version "1.0.4"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==

View File

@@ -0,0 +1 @@
bun.*

View File

@@ -0,0 +1 @@
{"name":"abbrev","version":"1.1.1"}

View File

@@ -0,0 +1,4 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1

View File

@@ -0,0 +1,15 @@
{
"name": "a",
"version": "1.2.3",
"dependencies": {
"abbrev": "^1.1.1",
"full-git-url": "git+https://github.com/isaacs/abbrev-js.git",
"ghshort": "github:isaacs/abbrev-js",
"old": "npm:abbrev@1.0.x",
"pinned": "npm:abbrev@1.1.1",
"reg": "npm:abbrev@^1.1.1",
"remote": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"symlink": "file:./abbrev-link-target",
"tarball": "file:abbrev-1.1.1.tgz"
}
}

View File

@@ -0,0 +1,42 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
abbrev@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
"full-git-url@git+https://github.com/isaacs/abbrev-js.git":
version "3.0.1"
resolved "git+https://github.com/isaacs/abbrev-js.git#3f9802e56ff878761a338e43ecacbfed39d2181d"
"ghshort@github:isaacs/abbrev-js":
version "3.0.1"
resolved "https://codeload.github.com/isaacs/abbrev-js/tar.gz/3f9802e56ff878761a338e43ecacbfed39d2181d"
"old@npm:abbrev@1.0.x":
version "1.0.9"
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135"
integrity sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q==
"pinned@npm:abbrev@1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
"reg@npm:abbrev@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
"remote@https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz":
version "1.1.1"
resolved "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
"symlink@file:./abbrev-link-target":
version "1.1.1"
"tarball@file:abbrev-1.1.1.tgz":
version "1.1.1"
resolved "file:abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"