Compare commits

...

1 Commits

Author SHA1 Message Date
RiskyMH
2cf9f7595b add Yarn Berry (v2+) lockfile migration support 2025-12-01 01:37:30 +11:00
9 changed files with 3767 additions and 1740 deletions

View File

@@ -87,6 +87,7 @@ pub const Features = struct {
pub var unsupported_uv_function: usize = 0;
pub var exited: usize = 0;
pub var yarn_migration: usize = 0;
pub var yarn_berry_migration: usize = 0;
pub var pnpm_migration: usize = 0;
pub var yaml_parse: usize = 0;
pub var cpu_profile: usize = 0;

View File

@@ -114,7 +114,7 @@ pub const LoadResult = union(enum) {
ok: struct {
lockfile: *Lockfile,
loaded_from_binary_lockfile: bool,
migrated: enum { none, npm, yarn, pnpm } = .none,
migrated: enum { none, npm, yarn, yarn_berry, pnpm } = .none,
serializer_result: Serializer.SerializerLoadResult,
format: LockfileFormat,
},
@@ -208,6 +208,7 @@ pub const LoadResult = union(enum) {
.pnpm => .{ .v1, true },
.npm => .{ .v0, true },
.yarn => .{ .v0, true },
.yarn_berry => .{ .v0, true },
},
};
}
@@ -977,7 +978,16 @@ const PendingResolution = struct {
const PendingResolutions = std.array_list.Managed(PendingResolution);
pub fn fetchNecessaryPackageMetadataAfterYarnOrPnpmMigration(this: *Lockfile, manager: *PackageManager, comptime update_os_cpu: bool) OOM!void {
pub const MigrationType = enum {
// pnpm needs to get dependencies from npm registry (it stores them under resolved without semver)
pnpm,
// yarn classic also needs to get os/cpu data from npm registry
yarn_classic,
// yarn berry only needs to get integrity data from npm registry
yarn_berry,
};
pub fn fetchNecessaryPackageMetadataAfterYarnOrPnpmMigration(this: *Lockfile, manager: *PackageManager, comptime migration_type: MigrationType) OOM!void {
manager.populateManifestCache(.all) catch return;
const pkgs = this.packages.slice();
@@ -986,28 +996,28 @@ pub fn fetchNecessaryPackageMetadataAfterYarnOrPnpmMigration(this: *Lockfile, ma
const pkg_name_hashes = pkgs.items(.name_hash);
const pkg_resolutions = pkgs.items(.resolution);
const pkg_bins = pkgs.items(.bin);
const pkg_metas = if (update_os_cpu) pkgs.items(.meta) else undefined;
const pkg_metas = pkgs.items(.meta);
if (update_os_cpu) {
for (pkg_names, pkg_name_hashes, pkg_resolutions, pkg_bins, pkg_metas) |pkg_name, pkg_name_hash, pkg_res, *pkg_bin, *pkg_meta| {
switch (pkg_res.tag) {
.npm => {
const manifest = manager.manifests.byNameHash(
manager,
manager.scopeForPackageName(pkg_name.slice(this.buffers.string_bytes.items)),
pkg_name_hash,
.load_from_memory_fallback_to_disk,
false,
) orelse {
continue;
};
for (pkg_names, pkg_name_hashes, pkg_resolutions, pkg_bins, pkg_metas) |pkg_name, pkg_name_hash, pkg_res, *pkg_bin, *pkg_meta| {
switch (pkg_res.tag) {
.npm => {
const manifest = manager.manifests.byNameHash(
manager,
manager.scopeForPackageName(pkg_name.slice(this.buffers.string_bytes.items)),
pkg_name_hash,
.load_from_memory_fallback_to_disk,
false,
) orelse continue;
const pkg = manifest.findByVersion(pkg_res.value.npm.version) orelse {
continue;
};
const pkg = manifest.findByVersion(pkg_res.value.npm.version) orelse continue;
var builder = manager.lockfile.stringBuilder();
var builder = manager.lockfile.stringBuilder();
if (migration_type == .yarn_berry) {
if (pkg_meta.integrity.tag == .unknown) {
pkg_meta.integrity = pkg.package.integrity;
}
} else {
var bin_extern_strings_count: u32 = 0;
bin_extern_strings_count += pkg.package.bin.count(manifest.string_buf, manifest.extern_strings_bin_entries, @TypeOf(&builder), &builder);
@@ -1022,53 +1032,17 @@ pub fn fetchNecessaryPackageMetadataAfterYarnOrPnpmMigration(this: *Lockfile, ma
pkg_bin.* = pkg.package.bin.clone(manifest.string_buf, manifest.extern_strings_bin_entries, extern_strings_list.items, extern_strings, @TypeOf(&builder), &builder);
// Update os/cpu metadata if not already set
if (pkg_meta.os == .all) {
pkg_meta.os = pkg.package.os;
if (migration_type == .yarn_classic) {
if (pkg_meta.os == .all) {
pkg_meta.os = pkg.package.os;
}
if (pkg_meta.arch == .all) {
pkg_meta.arch = pkg.package.cpu;
}
}
if (pkg_meta.arch == .all) {
pkg_meta.arch = pkg.package.cpu;
}
},
else => {},
}
}
} else {
for (pkg_names, pkg_name_hashes, pkg_resolutions, pkg_bins) |pkg_name, pkg_name_hash, pkg_res, *pkg_bin| {
switch (pkg_res.tag) {
.npm => {
const manifest = manager.manifests.byNameHash(
manager,
manager.scopeForPackageName(pkg_name.slice(this.buffers.string_bytes.items)),
pkg_name_hash,
.load_from_memory_fallback_to_disk,
false,
) orelse {
continue;
};
const pkg = manifest.findByVersion(pkg_res.value.npm.version) orelse {
continue;
};
var builder = manager.lockfile.stringBuilder();
var bin_extern_strings_count: u32 = 0;
bin_extern_strings_count += pkg.package.bin.count(manifest.string_buf, manifest.extern_strings_bin_entries, @TypeOf(&builder), &builder);
try builder.allocate();
defer builder.clamp();
var extern_strings_list = &manager.lockfile.buffers.extern_strings;
try extern_strings_list.ensureUnusedCapacity(manager.lockfile.allocator, bin_extern_strings_count);
extern_strings_list.items.len += bin_extern_strings_count;
const extern_strings = extern_strings_list.items[extern_strings_list.items.len - bin_extern_strings_count ..];
pkg_bin.* = pkg.package.bin.clone(manifest.string_buf, manifest.extern_strings_bin_entries, extern_strings_list.items, extern_strings, @TypeOf(&builder), &builder);
},
else => {},
}
}
},
else => {},
}
}
}

View File

@@ -824,7 +824,7 @@ pub fn migratePnpmLockfile(
try lockfile.resolve(log);
try lockfile.fetchNecessaryPackageMetadataAfterYarnOrPnpmMigration(manager, false);
try lockfile.fetchNecessaryPackageMetadataAfterYarnOrPnpmMigration(manager, .pnpm);
try updatePackageJsonAfterMigration(allocator, manager, log, dir, found_patches);

View File

@@ -100,6 +100,372 @@ pub fn ResolutionType(comptime SemverIntType: type) type {
};
}
const FromYarnBerryLockfileError = OOM || error{InvalidYarnBerryLockfile};
/// Parse Yarn Berry resolution: "package@protocol:details" or "package@protocol:details::metadata"
pub fn fromYarnBerryLockfile(res_str: []const u8, string_buf: *String.Buf) FromYarnBerryLockfileError!Resolution {
const at_idx = strings.lastIndexOfChar(res_str, '@') orelse return error.InvalidYarnBerryLockfile;
var protocol_part = res_str[at_idx + 1 ..];
// Extract __archiveUrl from ::metadata if present (custom registry URLs)
var archive_url: ?[]const u8 = null;
if (strings.indexOf(protocol_part, "::__archiveUrl=")) |archive_idx| {
const encoded_url = protocol_part[archive_idx + "::__archiveUrl=".len ..];
protocol_part = protocol_part[0..archive_idx];
archive_url = encoded_url;
} else if (strings.indexOf(protocol_part, "::")) |idx| {
protocol_part = protocol_part[0..idx];
}
// npm:version
if (strings.withoutPrefixIfPossibleComptime(protocol_part, "npm:")) |version_str| {
const version_literal = try string_buf.append(version_str);
const parsed = Semver.Version.parse(version_literal.sliced(string_buf.bytes.items));
if (!parsed.valid or parsed.version.major == null or parsed.version.minor == null or parsed.version.patch == null) {
return error.InvalidYarnBerryLockfile;
}
// Use custom registry URL if present
var url: String = .{};
if (archive_url) |encoded| {
var decoded_buf: [2048]u8 = undefined;
var decoded_stream = std.io.fixedBufferStream(&decoded_buf);
const decoded_len = PercentEncoding.decode(@TypeOf(decoded_stream).Writer, decoded_stream.writer(), encoded) catch 0;
if (decoded_len > 0) {
url = try string_buf.append(decoded_buf[0..decoded_len]);
}
}
return This.init(.{ .npm = .{ .version = parsed.version.min(), .url = url } });
}
// link:path -> symlink
if (strings.withoutPrefixIfPossibleComptime(protocol_part, "link:")) |path| {
return This.init(.{ .symlink = try string_buf.append(path) });
}
// file:path -> folder
if (strings.withoutPrefixIfPossibleComptime(protocol_part, "file:")) |path| {
return This.init(.{ .folder = try string_buf.append(path) });
}
// portal:path -> folder
if (strings.withoutPrefixIfPossibleComptime(protocol_part, "portal:")) |path| {
return This.init(.{ .folder = try string_buf.append(path) });
}
// github:owner/repo#ref
if (strings.withoutPrefixIfPossibleComptime(protocol_part, "github:")) |github_spec| {
return This.init(.{ .github = try Repository.parseAppendGithub(github_spec, string_buf) });
}
// git:url or git+protocol:url
if (strings.hasPrefixComptime(protocol_part, "git:") or strings.hasPrefixComptime(protocol_part, "git+")) {
return This.init(.{ .git = try Repository.parseAppendGit(protocol_part, string_buf) });
}
// https://.../*.git#... -> git resolution (github, gitlab, bitbucket, etc.)
if (strings.hasPrefixComptime(protocol_part, "https:") or strings.hasPrefixComptime(protocol_part, "http:")) {
if (strings.indexOf(protocol_part, ".git")) |git_idx| {
const after_git = protocol_part[git_idx + ".git".len ..];
const committish = strings.withoutPrefixIfPossibleComptime(after_git, "#commit=") orelse
strings.withoutPrefixIfPossibleComptime(after_git, "#") orelse "";
if (committish.len > 0) {
const repo_url = protocol_part[0 .. git_idx + ".git".len];
// GitHub: convert to github:owner/repo#commit format
if (strings.withoutPrefixIfPossibleComptime(repo_url, "https://github.com/")) |path| {
if (strings.indexOfChar(path, '/')) |slash_idx| {
const owner = path[0..slash_idx];
const repo = path[slash_idx + 1 .. path.len - ".git".len];
const short_commit = shortCommit(committish);
var resolved_buf: [256]u8 = undefined;
const resolved_str = std.fmt.bufPrint(&resolved_buf, "{s}-{s}-{s}", .{ owner, repo, short_commit }) catch "";
return This.init(.{ .github = .{
.owner = try string_buf.append(owner),
.repo = try string_buf.append(repo),
.committish = try string_buf.append(committish),
.resolved = if (resolved_str.len > 0) try string_buf.append(resolved_str) else .{},
} });
}
}
// Non-GitHub (gitlab, bitbucket, etc.): git+url#commit format
return This.init(.{ .git = .{
.repo = try string_buf.append(repo_url),
.committish = try string_buf.append(committish),
.resolved = try string_buf.append(committish),
} });
}
}
return This.init(.{ .remote_tarball = try string_buf.append(protocol_part) });
}
// Fallback: bare version without protocol (Yarn 2.x lockfiles v4/v5)
// e.g. "lodash@4.17.21" instead of "lodash@npm:4.17.21"
const version_literal = try string_buf.append(protocol_part);
const parsed = Semver.Version.parse(version_literal.sliced(string_buf.bytes.items));
if (parsed.valid and parsed.version.major != null and parsed.version.minor != null and parsed.version.patch != null) {
return This.init(.{ .npm = .{ .version = parsed.version.min(), .url = .{} } });
}
return error.InvalidYarnBerryLockfile;
}
fn extractPackageName(spec: []const u8) []const u8 {
const at_idx = if (strings.hasPrefixComptime(spec, "@"))
strings.indexOfChar(spec[1..], '@')
else
strings.indexOfChar(spec, '@');
if (at_idx) |idx| {
if (strings.hasPrefixComptime(spec, "@")) {
return spec[0 .. idx + 1];
}
return spec[0..idx];
}
return spec;
}
fn shortCommit(committish: []const u8) []const u8 {
return if (committish.len > 7) committish[0..7] else committish;
}
fn appendShortCommit(committish: []const u8, buf: *String.Buf) !String {
return try buf.append(shortCommit(committish));
}
fn isDefaultRegistry(url: []const u8) bool {
return strings.containsComptime(url, "registry.yarnpkg.com") or
strings.containsComptime(url, "registry.npmjs.org");
}
pub fn fromYarnV1Spec(
first_spec: []const u8,
resolved: []const u8,
version: []const u8,
string_buf: *String.Buf,
) FromYarnBerryLockfileError!struct { Resolution, String, u64 } {
const is_github_spec = strings.containsComptime(first_spec, "@github:");
const is_git_spec = strings.containsComptime(first_spec, "@git+");
const is_tarball_url_spec = strings.containsComptime(first_spec, "@http");
const real_name = blk: {
if (strings.containsComptime(first_spec, "@npm:")) {
if (strings.hasPrefixComptime(first_spec, "@")) {
if (strings.indexOfChar(first_spec, '@')) |first_at| {
const after_first_at = first_spec[first_at + 1 ..];
if (strings.indexOfChar(after_first_at, '@')) |second_at_in_substr| {
const second_at = first_at + 1 + second_at_in_substr;
if (strings.hasPrefixComptime(first_spec[second_at..], "@npm:")) {
const real_spec = first_spec[second_at + "@npm:".len ..];
const real_pkg_name = extractPackageName(real_spec);
break :blk real_pkg_name;
}
}
}
} else {
if (strings.indexOfChar(first_spec, '@')) |at_pos| {
const after_at = first_spec[at_pos + 1 ..];
if (strings.hasPrefixComptime(after_at, "npm:")) {
const real_spec = first_spec[at_pos + "@npm:".len ..];
const real_pkg_name = extractPackageName(real_spec);
break :blk real_pkg_name;
}
}
}
}
break :blk extractPackageName(first_spec);
};
var real_name_hash = String.Builder.stringHash(real_name);
var real_name_string = try string_buf.appendWithHash(real_name, real_name_hash);
var res: Resolution = undefined;
if (is_github_spec) {
const at_github_idx = strings.indexOf(first_spec, "@github:") orelse {
return error.InvalidYarnBerryLockfile;
};
const github_spec = first_spec[at_github_idx + "@github:".len ..];
var repo = try Repository.parseAppendGithub(github_spec, string_buf);
if (repo.committish.isEmpty() and resolved.len > 0) {
if (Resolution.fromPnpmLockfile(resolved, string_buf)) |resolved_res| {
if (resolved_res.tag == .github) {
repo.committish = resolved_res.value.github.committish;
}
} else |_| {}
}
if (repo.committish.len() > 0) {
const committish = repo.committish.slice(string_buf.bytes.items);
repo.committish = try appendShortCommit(committish, string_buf);
}
res = .init(.{ .github = repo });
const alias_name = first_spec[0..at_github_idx];
real_name_hash = String.Builder.stringHash(alias_name);
real_name_string = try string_buf.appendWithHash(alias_name, real_name_hash);
} else if (is_git_spec) {
const at_git_idx = strings.indexOf(first_spec, "@git+") orelse {
return error.InvalidYarnBerryLockfile;
};
const git_spec = first_spec[at_git_idx + 1 ..];
if (strings.containsComptime(git_spec, "github.com")) {
const github_com_idx = strings.indexOf(git_spec, "github.com/") orelse {
return error.InvalidYarnBerryLockfile;
};
var path = git_spec[github_com_idx + "github.com/".len ..];
if (strings.hasPrefixComptime(path, "git+")) {
path = path["git+".len..];
}
var hash_idx: usize = 0;
var slash_idx: usize = 0;
for (path, 0..) |c, i| {
switch (c) {
'/' => slash_idx = i,
'#' => {
hash_idx = i;
break;
},
else => {},
}
}
const owner = path[0..slash_idx];
var repo_part = if (hash_idx == 0) path[slash_idx + 1 ..] else path[slash_idx + 1 .. hash_idx];
if (strings.hasSuffixComptime(repo_part, ".git")) {
repo_part = repo_part[0 .. repo_part.len - 4];
}
var repo_result: Repository = .{
.owner = try string_buf.append(owner),
.repo = try string_buf.append(repo_part),
};
if (hash_idx != 0) {
const committish = path[hash_idx + 1 ..];
repo_result.committish = try appendShortCommit(committish, string_buf);
} else if (resolved.len > 0) {
if (strings.indexOfChar(resolved, '#')) |resolved_hash_idx| {
const committish = resolved[resolved_hash_idx + 1 ..];
repo_result.committish = try appendShortCommit(committish, string_buf);
}
}
res = .init(.{ .github = repo_result });
const alias_name = first_spec[0..at_git_idx];
real_name_hash = String.Builder.stringHash(alias_name);
real_name_string = try string_buf.appendWithHash(alias_name, real_name_hash);
} else {
const repo = try Repository.parseAppendGit(git_spec, string_buf);
res = .init(.{ .git = repo });
const alias_name = first_spec[0..at_git_idx];
real_name_hash = String.Builder.stringHash(alias_name);
real_name_string = try string_buf.appendWithHash(alias_name, real_name_hash);
}
} else if (is_tarball_url_spec) {
const at_http_idx = strings.indexOf(first_spec, "@http") orelse {
return error.InvalidYarnBerryLockfile;
};
const url_after_at = first_spec[at_http_idx + 1 ..];
if (strings.indexOf(url_after_at, "/-/")) |dash_slash_idx| {
const before_dash_slash = url_after_at[0..dash_slash_idx];
if (strings.lastIndexOfChar(before_dash_slash, '/')) |last_slash| {
const real_pkg_name_from_url = before_dash_slash[last_slash + 1 ..];
real_name_hash = String.Builder.stringHash(real_pkg_name_from_url);
real_name_string = try string_buf.appendWithHash(real_pkg_name_from_url, real_name_hash);
} else {
const real_pkg_name_from_spec = first_spec[0..at_http_idx];
real_name_hash = String.Builder.stringHash(real_pkg_name_from_spec);
real_name_string = try string_buf.appendWithHash(real_pkg_name_from_spec, real_name_hash);
}
} else {
const real_pkg_name_from_spec = first_spec[0..at_http_idx];
real_name_hash = String.Builder.stringHash(real_pkg_name_from_spec);
real_name_string = try string_buf.appendWithHash(real_pkg_name_from_spec, real_name_hash);
}
const version_str = try string_buf.append(version);
const parsed = Semver.Version.parse(version_str.sliced(string_buf.bytes.items));
if (!parsed.valid or parsed.version.major == null or parsed.version.minor == null or parsed.version.patch == null) {
return error.InvalidYarnBerryLockfile;
}
res = .init(.{ .npm = .{
.version = parsed.version.min(),
.url = try string_buf.append(url_after_at),
} });
} else if (resolved.len == 0) {
if (strings.containsComptime(first_spec, "@file:")) {
const at_file_idx = strings.indexOf(first_spec, "@file:") orelse {
return error.InvalidYarnBerryLockfile;
};
const path = first_spec[at_file_idx + "@file:".len ..];
if (strings.hasSuffixComptime(path, ".tgz") or strings.hasSuffixComptime(path, ".tar.gz")) {
res = .init(.{ .local_tarball = try string_buf.append(path) });
} else {
res = .init(.{ .folder = try string_buf.append(path) });
}
} else {
return error.InvalidYarnBerryLockfile;
}
} else if (isDefaultRegistry(resolved) and
(strings.hasPrefixComptime(resolved, "https://") or
strings.hasPrefixComptime(resolved, "http://")))
{
const version_str = try string_buf.append(version);
const parsed = Semver.Version.parse(version_str.sliced(string_buf.bytes.items));
if (!parsed.valid) {
return error.InvalidYarnBerryLockfile;
}
res = .init(.{ .npm = .{
.version = parsed.version.min(),
.url = .{},
} });
} else {
res = Resolution.fromPnpmLockfile(resolved, string_buf) catch {
return error.InvalidYarnBerryLockfile;
};
switch (res.tag) {
.github => {
var repo = res.value.github;
if (repo.committish.len() > 0) {
const committish = repo.committish.slice(string_buf.bytes.items);
repo.committish = try appendShortCommit(committish, string_buf);
res = .init(.{ .github = repo });
}
const repo_name = repo.repo.slice(string_buf.bytes.items);
real_name_hash = String.Builder.stringHash(repo_name);
real_name_string = repo.repo;
},
.git => {
const repo = res.value.git;
var repo_name = repo.repo.slice(string_buf.bytes.items);
if (strings.hasSuffixComptime(repo_name, ".git")) {
repo_name = repo_name[0 .. repo_name.len - 4];
}
if (strings.lastIndexOfChar(repo_name, '/')) |slash| {
repo_name = repo_name[slash + 1 ..];
}
real_name_hash = String.Builder.stringHash(repo_name);
real_name_string = try string_buf.appendWithHash(repo_name, real_name_hash);
},
else => {},
}
}
return .{ res, real_name_string, real_name_hash };
}
const FromPnpmLockfileError = OOM || error{InvalidPnpmLockfile};
pub fn fromPnpmLockfile(res_str: []const u8, string_buf: *String.Buf) FromPnpmLockfileError!Resolution {
@@ -540,6 +906,7 @@ pub fn ResolutionType(comptime SemverIntType: type) type {
const string = []const u8;
const std = @import("std");
const PercentEncoding = @import("../url.zig").PercentEncoding;
const Repository = @import("./repository.zig").Repository;
const VersionedURLType = @import("./versioned_url.zig").VersionedURLType;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,413 @@
// Bun Snapshot v1, https://bun.sh/docs/test/snapshots
exports[`Yarn Berry migration simple package with conditions (v8 format) 1`] = `
"{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "test-conditions",
"dependencies": {
"@esbuild/darwin-arm64": "^0.21.5",
"fsevents": "^2.3.2",
},
},
},
"packages": {
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.21.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ=="],
"fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],
}
}
"
`;
exports[`Yarn Berry migration optional peer dependencies 1`] = `
"{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "test-peer-meta",
"dependencies": {
"react": "^18.0.0",
},
},
},
"packages": {
"loose-envify": ["loose-envify@1.4.0", "", { "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"], "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
"react": ["react@18.2.0", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="],
}
}
"
`;
exports[`Yarn Berry migration optional dependencies via dependenciesMeta 1`] = `
"{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "test-deps-meta",
"dependencies": {
"sharp": "^0.32.0",
},
},
},
"packages": {
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.32.6", "", { "os": "darwin", "cpu": "arm64" }, ""],
"@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.32.6", "", { "os": "linux", "cpu": "x64" }, ""],
"sharp": ["sharp@0.32.6", "", { "dependencies": { "@img/sharp-darwin-arm64": "0.32.6", "@img/sharp-linux-x64": "0.32.6" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.32.6", "@img/sharp-linux-x64": "0.32.6" } }, "sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w=="],
}
}
"
`;
exports[`Yarn Berry migration bin definitions 1`] = `
"{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "test-bins",
"dependencies": {
"typescript": "^5.9.2",
},
},
},
"packages": {
"typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="],
}
}
"
`;
exports[`Yarn Berry migration workspace:* protocol 1`] = `
"{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "test-workspaces",
"dependencies": {
"lib-a": "workspace:*",
},
},
"packages/lib-a": {
"name": "lib-a",
"version": "1.0.0",
},
},
"packages": {
"lib-a": ["lib-a@workspace:packages/lib-a"],
}
}
"
`;
exports[`Yarn Berry migration v4 format (Yarn 2.x) with bare versions 1`] = `
"{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "test-v4",
"dependencies": {
"lodash": "^4.17.21",
},
},
},
"packages": {
"lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
}
}
"
`;
exports[`Yarn Berry migration v6 format fallback with os/cpu arrays 1`] = `
"{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "test-v6",
"dependencies": {
"fsevents": "^2.3.2",
},
},
},
"packages": {
"fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],
}
}
"
`;
exports[`Yarn Berry migration real-world monorepo with Next.js, workspace:^ deps, optional peers, and platform-specific bins 1`] = `
"{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "nextjs-monorepo",
"devDependencies": {
"typescript": "^5.0.0",
},
},
"apps/web": {
"name": "nextjs-app",
"version": "1.0.0",
"dependencies": {
"@ui/shared": "workspace:^",
"next": "14.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
},
},
"packages/shared": {
"name": "@ui/shared",
"version": "1.0.0",
"dependencies": {
"react": "^18.2.0",
},
},
},
"packages": {
"@next/env": ["@next/env@14.1.0", "", {}, "sha512-Py8zIo+02ht82brwwhTg36iogzFqGLPXlRGKQw5s+qP/kMNc4MAyDeEwBKDijk6zTIbegEgu8Qy7C1LboslQAw=="],
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@14.1.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-nUDn7TOGcIeyQni6lZHfzNoo9S0euXnu0jhsbMOmMJUBfgsnESdjN97kM7cBqQxZa8L/bM9om/S5/1dzCrW6wQ=="],
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@14.1.0", "", { "os": "linux", "cpu": "x64" }, "sha512-zJ2pnoFYB1F4vmEVlb/eSe+VH679zT1VdXlZKX+pE66grOgjmKJHKacf82g/sWE4MQ4Rk2FMBCRnX+l6/TVYzQ=="],
"@ui/shared": ["@ui/shared@workspace:packages/shared"],
"busboy": ["busboy@1.6.0", "", { "dependencies": { "streamsearch": "^1.1.0" } }, "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA=="],
"client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="],
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
"next": ["next@14.1.0", "", { "dependencies": { "@next/env": "14.1.0", "@next/swc-darwin-arm64": "14.1.0", "@next/swc-linux-x64-gnu": "14.1.0", "busboy": "1.6.0", "styled-jsx": "5.1.1" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-wlzrsbfeSU48YQBjZhDzOwhWhGsy+uQycR8bHAOt1LY1bn3zZEcDyHQOEoN3aWzQ8LHCAJ1nqrWCc9XF2+O45Q=="],
"nextjs-app": ["nextjs-app@workspace:apps/web"],
"react": ["react@18.2.0", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="],
"react-dom": ["react-dom@18.2.0", "", { "dependencies": { "loose-envify": "^1.1.0", "react": "^18.2.0", "scheduler": "^0.23.0" }, "peerDependencies": { "react": "^18.2.0" } }, "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g=="],
"scheduler": ["scheduler@0.23.0", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw=="],
"streamsearch": ["streamsearch@1.1.0", "", {}, "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg=="],
"styled-jsx": ["styled-jsx@5.1.1", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": "*" } }, "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw=="],
"typescript": ["typescript@5.3.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw=="],
}
}
"
`;
exports[`Yarn Berry migration deeply nested workspace dependencies with multiple conflicting versions 1`] = `
"{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "complex-deps-monorepo",
},
"packages/pkg-a": {
"name": "pkg-a",
"version": "1.0.0",
"dependencies": {
"lodash": "^4.17.20",
"react": "^18.0.0",
},
},
"packages/pkg-b": {
"name": "pkg-b",
"version": "1.0.0",
"dependencies": {
"lodash": "^4.17.21",
"pkg-a": "workspace:^",
"react": "^17.0.0",
},
},
"packages/pkg-c": {
"name": "pkg-c",
"version": "1.0.0",
"dependencies": {
"pkg-a": "workspace:^",
"pkg-b": "workspace:^",
"react": "^18.2.0",
},
},
"packages/pkg-d": {
"name": "pkg-d",
"version": "1.0.0",
"dependencies": {
"lodash": "^4.17.19",
"pkg-c": "workspace:^",
"react": "^16.14.0",
},
},
"packages/pkg-e": {
"name": "pkg-e",
"version": "1.0.0",
"dependencies": {
"pkg-a": "workspace:^",
"pkg-d": "workspace:^",
"react": "^18.0.0",
},
},
},
"packages": {
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
"pkg-a": ["pkg-a@workspace:packages/pkg-a"],
"pkg-b": ["pkg-b@workspace:packages/pkg-b"],
"pkg-c": ["pkg-c@workspace:packages/pkg-c"],
"pkg-d": ["pkg-d@workspace:packages/pkg-d"],
"pkg-e": ["pkg-e@workspace:packages/pkg-e"],
"prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
"react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="],
"react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
"pkg-b/react": ["react@17.0.2", "", { "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" } }, "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA=="],
"pkg-d/react": ["react@16.14.0", "", { "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", "prop-types": "^15.6.2" } }, "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g=="],
}
}
"
`;
exports[`Yarn Berry migration git and GitHub dependencies: git-deps-berry 1`] = `
"{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "git-deps-test",
"dependencies": {
"git-pkg": "git+https://github.com/example/repo.git#v1.0.0",
"github-pkg": "github:user/repo#main",
},
},
},
"packages": {
"git-pkg": ["git-pkg@github:example/repo#abc123d", {}, "example-repo-abc123d"],
"github-pkg": ["github-pkg@github:user/repo#deadbee", { "dependencies": { "is-number": "^7.0.0" } }, "user-repo-deadbee"],
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
}
}
"
`;
exports[`Yarn Berry migration non-GitHub git dependencies (gitlab, bitbucket): gitlab-deps-berry 1`] = `
"{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "gitlab-deps-test",
"dependencies": {
"gitlab-pkg": "git+https://gitlab.com/nickvision/tube.git#v1.0.0",
},
},
},
"packages": {
"gitlab-pkg": ["gitlab-pkg@git+https://gitlab.com/nickvision/tube.git#abc123def456", {}, "abc123def456"],
}
}
"
`;
exports[`Yarn Berry migration npm aliases in Berry format: npm-aliases-berry 1`] = `
"{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "npm-alias-test",
"dependencies": {
"my-lodash": "npm:lodash@^4.17.0",
"old-react": "npm:react@17.0.2",
},
},
},
"packages": {
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
"my-lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
"old-react": ["react@17.0.2", "", { "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" } }, "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA=="],
}
}
"
`;
exports[`Yarn Berry migration custom registry URLs: custom-registry-berry 1`] = `
"{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "custom-registry-test",
"dependencies": {
"private-pkg": "^1.0.0",
},
},
},
"packages": {
"private-pkg": ["private-pkg@1.0.0", "https://my-private-registry.com/private-pkg/-/private-pkg-1.0.0.tgz", {}, ""],
}
}
"
`;
exports[`Yarn Berry migration multiple resolutions for same package (version aliasing): multi-resolution-berry 1`] = `
"{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "multi-res-test",
"dependencies": {
"is-number": "^7.0.0",
"is-odd": "^3.0.0",
},
},
},
"packages": {
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
"is-odd": ["is-odd@3.0.1", "", { "dependencies": { "is-number": "^6.0.0" } }, "sha512-CQpnWPrDwmP1+SMHXZhtLtJv90yiyVfluGsX5iNCVkrhQtU3TQHsUWPG9wkdk9Lgd5yNpAg9jQEo90CBaXgWMA=="],
"is-odd/is-number": ["is-number@6.0.0", "", {}, "sha512-Wu1VHeILBK8KAWJUAiSZQX94GmOE45Rg6/538fKwiloUu21KncEkYGPqob2oSZ5mUT73vLGrHQjKw3KMPwfDzg=="],
}
}
"
`;

View File

@@ -228,9 +228,34 @@ exports[`yarn.lock migration basic yarn.lock with workspace dependencies: worksp
"lodash": "^4.17.21",
},
},
"packages/a": {
"name": "@workspace/a",
"version": "1.0.0",
"dependencies": {
"@workspace/b": "workspace:*",
"is-number": "^7.0.0",
},
},
"packages/b": {
"name": "@workspace/b",
"version": "1.0.0",
"dependencies": {
"is-odd": "^3.0.1",
},
},
},
"packages": {
"@workspace/a": ["@workspace/a@workspace:packages/a"],
"@workspace/b": ["@workspace/b@workspace:packages/b"],
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
"is-odd": ["is-odd@3.0.1", "", { "dependencies": { "is-number": "^6.0.0" } }, "sha512-CQpnWPrDwmP1+SMHXZhtLtJv90yiyVfluGsX5iNCVkrhQtU3TQHsUWPG9wkdk9Lgd5yNpAg9jQEo90CBaXgWMA=="],
"lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
"is-odd/is-number": ["is-number@6.0.0", "", {}, "sha512-Wu1VHeILBK8KAWJUAiSZQX94GmOE45Rg6/538fKwiloUu21KncEkYGPqob2oSZ5mUT73vLGrHQjKw3KMPwfDzg=="],
}
}
"
@@ -272,15 +297,15 @@ exports[`yarn.lock migration basic migration with realistic complex yarn.lock: c
"": {
"name": "complex-app",
"dependencies": {
"@babel/core": "^7.20.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"@babel/core": "^7.20.0",
"webpack": "^5.75.0",
},
"devDependencies": {
"@types/react": "^18.0.0",
"typescript": "^4.9.0",
"eslint": "^8.0.0",
"typescript": "^4.9.0",
},
"optionalDependencies": {
"fsevents": "^2.3.2",
@@ -2932,7 +2957,7 @@ exports[`bun pm migrate for existing yarn.lock yarn-cli-repo: yarn-cli-repo 1`]
"class-utils/define-property/is-descriptor/is-data-descriptor/kind-of": ["kind-of@3.2.2", "", { "dependencies": { "is-buffer": "^1.1.5" } }, "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ="],
"commitizen/inquirer/cli-cursor/restore-cursor/onetime": ["onetime@1.1.0", "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789", {}, "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k="],
"commitizen/inquirer/cli-cursor/restore-cursor/onetime": ["onetime@1.1.0", "", {}, "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k="],
"eslint/inquirer/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="],
@@ -3141,9 +3166,9 @@ exports[`bun pm migrate for existing yarn.lock yarn-stuff: yarn-stuff 1`] = `
"packages": {
"abbrev": ["abbrev@1.1.1", "", {}, "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="],
"full-git-url": ["abbrev-js@github:isaacs/abbrev-js#3f9802e", {}, ""],
"full-git-url": ["full-git-url@github:isaacs/abbrev-js#3f9802e", {}, ""],
"ghshort": ["ghshort@3.0.1", "https://codeload.github.com/isaacs/abbrev-js/tar.gz/3f9802e56ff878761a338e43ecacbfed39d2181d", {}, ""],
"ghshort": ["ghshort@github:isaacs/abbrev-js#3f9802e", {}, ""],
"old": ["abbrev@1.0.9", "", {}, "sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q=="],
@@ -3151,11 +3176,11 @@ exports[`bun pm migrate for existing yarn.lock yarn-stuff: yarn-stuff 1`] = `
"reg": ["abbrev@1.1.1", "", {}, "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="],
"remote": ["abbrev@https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8", {}],
"remote": ["abbrev@1.1.1", "", {}, "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="],
"symlink": ["symlink@file:./abbrev-link-target", {}],
"tarball": ["tarball@abbrev-1.1.1.tgz", {}],
"tarball": ["tarball@file:abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8", {}],
}
}
"
@@ -3183,8 +3208,8 @@ exports[`bun pm migrate for existing yarn.lock yarn.lock with packages that have
"": {
"name": "os-cpu-test",
"dependencies": {
"fsevents": "^2.3.2",
"esbuild": "^0.17.0",
"fsevents": "^2.3.2",
},
},
},
@@ -3206,3 +3231,135 @@ exports[`bun pm migrate for existing yarn.lock yarn.lock with packages that have
}
"
`;
exports[`bun pm migrate for existing yarn.lock yarn.lock with custom registry tarball URLs: custom-registry-yarn-migration 1`] = `
"{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "custom-registry-test",
"dependencies": {
"another-pkg": "https://packages.example.com/tarballs/another-pkg-2.1.0.tgz",
"my-package": "https://my-custom-registry.com/my-package/-/my-package-1.0.0.tgz",
},
},
},
"packages": {
"another-pkg": ["another-pkg@2.1.0", "https://packages.example.com/tarballs/another-pkg-2.1.0.tgz", {}, "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="],
"my-package": ["my-package@1.0.0", "https://my-custom-registry.com/my-package/-/my-package-1.0.0.tgz", {}, ""],
}
}
"
`;
exports[`bun pm migrate for existing yarn.lock all yarn v1 quirks in one test: yarn-comprehensive 1`] = `
"{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "yarn-quirks-test",
"dependencies": {
"@babel/core": "^7.20.0",
"@prisma/engines": "4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81",
"is-number": "^7.0.0",
"my-lodash": "npm:lodash@4.17.21",
},
"devDependencies": {
"lodash": "^3.10.0",
},
"optionalDependencies": {
"fsevents": "^2.3.2",
},
},
"packages/app": {
"name": "@workspace/app",
"version": "1.0.0",
"dependencies": {
"@workspace/lib": "workspace:*",
"is-number": "~7.0.0",
"lodash": "^4.17.0",
},
},
"packages/lib": {
"name": "@workspace/lib",
"version": "1.0.0",
"dependencies": {
"is-odd": "^3.0.0",
},
},
},
"packages": {
"@babel/core": ["@babel/core@7.20.12", "", { "dependencies": { "@babel/types": "^7.20.7" } }, "sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg=="],
"@babel/types": ["@babel/types@7.20.7", "", {}, "sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg=="],
"@prisma/engines": ["@prisma/engines@4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81", "", {}, "sha512-q617EUWfRIDTriWADZ4YiWRZXCa/WuhNgLTVd+HqWLffjMSPzyM5uOWoauX91wvQClSKZU4pzI4JJLQ9Kl62Qg=="],
"@workspace/app": ["@workspace/app@workspace:packages/app"],
"@workspace/lib": ["@workspace/lib@workspace:packages/lib"],
"fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
"is-odd": ["is-odd@3.0.1", "", { "dependencies": { "is-number": "^6.0.0" } }, "sha512-CQpnWPrDwmP1+SMHXZhtLtJv90yiyVfluGsX5iNCVkrhQtU3TQHsUWPG9wkdk9Lgd5yNpAg9jQEo90CBaXgWMA=="],
"lodash": ["lodash@3.10.1", "", {}, "sha512-9mDDwqVIma6OZX79ZlDACZl8sBm0TEnkf99zV3iMA4GzkSTbUYwRa7cmPR8CKkkl+OuU/fGTM48FNhQ5GnLsXw=="],
"my-lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
"@workspace/app/lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
"is-odd/is-number": ["is-number@6.0.0", "", {}, "sha512-Wu1VHeILBK8KAWJUAiSZQX94GmOE45Rg6/538fKwiloUu21KncEkYGPqob2oSZ5mUT73vLGrHQjKw3KMPwfDzg=="],
}
}
"
`;
exports[`bun pm migrate for existing yarn.lock parser handles indentation correctly: yarn-indentation 1`] = `
"{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "indent-test",
"dependencies": {
"test-pkg": "^1.0.0",
},
},
},
"packages": {
"dep-a": ["dep-a@1.0.0", "", {}, ""],
"dep-b": ["dep-b@2.0.0", "", {}, ""],
"test-pkg": ["test-pkg@1.0.0", "", { "dependencies": { "dep-a": "^1.0.0", "dep-b": "^2.0.0" } }, ""],
}
}
"
`;
exports[`bun pm migrate for existing yarn.lock handles optionalDependencies correctly: yarn-optional-deps 1`] = `
"{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "optional-test",
"dependencies": {
"pkg-a": "^1.0.0",
},
},
},
"packages": {
"optional-dep": ["optional-dep@1.0.0", "", {}, "sha512-testopt2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="],
"pkg-a": ["pkg-a@1.0.0", "", { "optionalDependencies": { "optional-dep": "^1.0.0" } }, "sha512-testoptionalAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="],
}
}
"
`;

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,11 @@
import { describe, expect, test } from "bun:test";
import fs from "fs";
import { bunEnv, bunExe, tempDirWithFiles } from "harness";
import { bunEnv, bunExe, tempDir } from "harness";
import { join } from "path";
describe("yarn.lock migration basic", () => {
test("simple yarn.lock migration produces correct bun.lock", async () => {
const tempDir = tempDirWithFiles("yarn-migration-simple", {
using dir = tempDir("yarn-migration-simple", {
"package.json": JSON.stringify(
{
name: "simple-test",
@@ -31,7 +31,7 @@ is-number@^7.0.0:
// Run bun pm migrate
const migrateResult = await Bun.spawn({
cmd: [bunExe(), "pm", "migrate", "-f"],
cwd: tempDir,
cwd: String(dir),
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
@@ -45,14 +45,14 @@ is-number@^7.0.0:
]);
expect(exitCode).toBe(0);
expect(fs.existsSync(join(tempDir, "bun.lock"))).toBe(true);
expect(fs.existsSync(join(String(dir), "bun.lock"))).toBe(true);
const bunLockContent = fs.readFileSync(join(tempDir, "bun.lock"), "utf8");
const bunLockContent = fs.readFileSync(join(String(dir), "bun.lock"), "utf8");
expect(bunLockContent).toMatchSnapshot("simple-yarn-migration");
});
test("yarn.lock with packages containing long build tags", async () => {
const tempDir = tempDirWithFiles("yarn-migration-build-tags", {
using dir = tempDir("yarn-migration-build-tags", {
"package.json": JSON.stringify(
{
name: "build-tags-test",
@@ -128,7 +128,7 @@ to-fast-properties@^2.0.0:
// Run bun pm migrate
const migrateResult = await Bun.spawn({
cmd: [bunExe(), "pm", "migrate", "-f"],
cwd: tempDir,
cwd: String(dir),
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
@@ -142,9 +142,9 @@ to-fast-properties@^2.0.0:
]);
expect(exitCode).toBe(0);
expect(fs.existsSync(join(tempDir, "bun.lock"))).toBe(true);
expect(fs.existsSync(join(String(dir), "bun.lock"))).toBe(true);
const bunLockContent = fs.readFileSync(join(tempDir, "bun.lock"), "utf8");
const bunLockContent = fs.readFileSync(join(String(dir), "bun.lock"), "utf8");
// Verify that long build tags are preserved correctly
expect(bunLockContent).toContain("4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81");
@@ -159,7 +159,7 @@ to-fast-properties@^2.0.0:
// Install should work after migration
const installResult = await Bun.spawn({
cmd: [bunExe(), "install"],
cwd: tempDir,
cwd: String(dir),
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
@@ -171,7 +171,7 @@ to-fast-properties@^2.0.0:
});
test("yarn.lock with extremely long build tags (regression test)", async () => {
const tempDir = tempDirWithFiles("yarn-migration-extreme-build-tags", {
using dir = tempDir("yarn-migration-extreme-build-tags", {
"package.json": JSON.stringify(
{
name: "extreme-build-tags-test",
@@ -198,7 +198,7 @@ test-package@1.0.0-alpha.beta.gamma.delta.epsilon.zeta.eta.theta.iota.kappa.lamb
// Run bun pm migrate
const migrateResult = await Bun.spawn({
cmd: [bunExe(), "pm", "migrate", "-f"],
cwd: tempDir,
cwd: String(dir),
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
@@ -208,7 +208,7 @@ test-package@1.0.0-alpha.beta.gamma.delta.epsilon.zeta.eta.theta.iota.kappa.lamb
const exitCode = await migrateResult.exited;
expect(exitCode).toBe(0);
const bunLockPath = join(tempDir, "bun.lock");
const bunLockPath = join(String(dir), "bun.lock");
expect(fs.existsSync(bunLockPath)).toBe(true);
const bunLockContent = fs.readFileSync(bunLockPath, "utf8");
@@ -226,7 +226,7 @@ test-package@1.0.0-alpha.beta.gamma.delta.epsilon.zeta.eta.theta.iota.kappa.lamb
});
test("complex yarn.lock with multiple dependencies and versions", async () => {
const tempDir = tempDirWithFiles("yarn-migration-complex", {
using dir = tempDir("yarn-migration-complex", {
"package.json": JSON.stringify(
{
name: "complex-test",
@@ -714,7 +714,7 @@ vary@~1.1.2:
// Run bun pm migrate
const migrateResult = await Bun.spawn({
cmd: [bunExe(), "pm", "migrate", "-f"],
cwd: tempDir,
cwd: String(dir),
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
@@ -728,14 +728,14 @@ vary@~1.1.2:
]);
expect(exitCode).toBe(0);
expect(fs.existsSync(join(tempDir, "bun.lock"))).toBe(true);
expect(fs.existsSync(join(String(dir), "bun.lock"))).toBe(true);
const bunLockContent = fs.readFileSync(join(tempDir, "bun.lock"), "utf8");
const bunLockContent = fs.readFileSync(join(String(dir), "bun.lock"), "utf8");
expect(bunLockContent).toMatchSnapshot("complex-yarn-migration");
});
test("yarn.lock with npm aliases", async () => {
const tempDir = tempDirWithFiles("yarn-migration-aliases", {
using dir = tempDir("yarn-migration-aliases", {
"package.json": JSON.stringify(
{
name: "alias-test",
@@ -786,7 +786,7 @@ undici-types@~5.26.4:
// Run bun pm migrate
const migrateResult = await Bun.spawn({
cmd: [bunExe(), "pm", "migrate", "-f"],
cwd: tempDir,
cwd: String(dir),
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
@@ -800,9 +800,9 @@ undici-types@~5.26.4:
]);
expect(exitCode).toBe(0);
expect(fs.existsSync(join(tempDir, "bun.lock"))).toBe(true);
expect(fs.existsSync(join(String(dir), "bun.lock"))).toBe(true);
const bunLockContent = fs.readFileSync(join(tempDir, "bun.lock"), "utf8");
const bunLockContent = fs.readFileSync(join(String(dir), "bun.lock"), "utf8");
expect(bunLockContent).toMatchSnapshot("aliases-yarn-migration");
// Verify that npm aliases are handled correctly
@@ -811,7 +811,7 @@ undici-types@~5.26.4:
});
test("yarn.lock with resolutions", async () => {
const tempDir = tempDirWithFiles("yarn-migration-resolutions", {
using dir = tempDir("yarn-migration-resolutions", {
"package.json": JSON.stringify(
{
name: "resolutions-test",
@@ -876,7 +876,7 @@ webpack@^5.89.0:
// Run bun pm migrate
const migrateResult = await Bun.spawn({
cmd: [bunExe(), "pm", "migrate", "-f"],
cwd: tempDir,
cwd: String(dir),
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
@@ -890,9 +890,9 @@ webpack@^5.89.0:
]);
expect(exitCode).toBe(0);
expect(fs.existsSync(join(tempDir, "bun.lock"))).toBe(true);
expect(fs.existsSync(join(String(dir), "bun.lock"))).toBe(true);
const bunLockContent = fs.readFileSync(join(tempDir, "bun.lock"), "utf8");
const bunLockContent = fs.readFileSync(join(String(dir), "bun.lock"), "utf8");
expect(bunLockContent).toMatchSnapshot("resolutions-yarn-migration");
// Verify resolutions are handled
@@ -900,7 +900,7 @@ webpack@^5.89.0:
});
test("yarn.lock with workspace dependencies", async () => {
const tempDir = tempDirWithFiles("yarn-migration-workspace", {
using dir = tempDir("yarn-migration-workspace", {
"package.json": JSON.stringify(
{
name: "workspace-root",
@@ -974,7 +974,7 @@ lodash@^4.17.21:
// Run bun pm migrate
const migrateResult = await Bun.spawn({
cmd: [bunExe(), "pm", "migrate", "-f"],
cwd: tempDir,
cwd: String(dir),
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
@@ -988,9 +988,9 @@ lodash@^4.17.21:
]);
expect(exitCode).toBe(0);
expect(fs.existsSync(join(tempDir, "bun.lock"))).toBe(true);
expect(fs.existsSync(join(String(dir), "bun.lock"))).toBe(true);
const bunLockContent = fs.readFileSync(join(tempDir, "bun.lock"), "utf8");
const bunLockContent = fs.readFileSync(join(String(dir), "bun.lock"), "utf8");
expect(bunLockContent).toMatchSnapshot("workspace-yarn-migration");
// TODO: Workspace dependencies are not yet supported in yarn migration
@@ -998,7 +998,7 @@ lodash@^4.17.21:
});
test("yarn.lock with scoped packages and parent/child relationships", async () => {
const tempDir = tempDirWithFiles("yarn-migration-scoped", {
using dir = tempDir("yarn-migration-scoped", {
"package.json": JSON.stringify(
{
name: "scoped-test",
@@ -1089,7 +1089,7 @@ babel-loader/chalk@^2.4.2:
// Run bun pm migrate
const migrateResult = await Bun.spawn({
cmd: [bunExe(), "pm", "migrate", "-f"],
cwd: tempDir,
cwd: String(dir),
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
@@ -1103,9 +1103,9 @@ babel-loader/chalk@^2.4.2:
]);
expect(exitCode).toBe(0);
expect(fs.existsSync(join(tempDir, "bun.lock"))).toBe(true);
expect(fs.existsSync(join(String(dir), "bun.lock"))).toBe(true);
const bunLockContent = fs.readFileSync(join(tempDir, "bun.lock"), "utf8");
const bunLockContent = fs.readFileSync(join(String(dir), "bun.lock"), "utf8");
expect(bunLockContent).toMatchSnapshot("scoped-yarn-migration");
// Verify scoped packages are at the bottom
@@ -1124,7 +1124,7 @@ babel-loader/chalk@^2.4.2:
test("migration with realistic complex yarn.lock", async () => {
// Create a realistic yarn.lock with various edge cases
const tempDir = tempDirWithFiles("yarn-migration-complex-realistic", {
using dir = tempDir("yarn-migration-complex-realistic", {
"package.json": JSON.stringify(
{
name: "complex-app",
@@ -1314,7 +1314,7 @@ webpack@^5.75.0:
// Run bun pm migrate
const migrateResult = await Bun.spawn({
cmd: [bunExe(), "pm", "migrate", "-f"],
cwd: tempDir,
cwd: String(dir),
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
@@ -1323,9 +1323,9 @@ webpack@^5.75.0:
const exitCode = await migrateResult.exited;
expect(exitCode).toBe(0);
expect(fs.existsSync(join(tempDir, "bun.lock"))).toBe(true);
expect(fs.existsSync(join(String(dir), "bun.lock"))).toBe(true);
const bunLockContent = fs.readFileSync(join(tempDir, "bun.lock"), "utf8");
const bunLockContent = fs.readFileSync(join(String(dir), "bun.lock"), "utf8");
expect(bunLockContent).toMatchSnapshot("complex-realistic-yarn-migration");
// Verify key features are migrated
@@ -1352,14 +1352,14 @@ describe("bun pm migrate for existing yarn.lock", () => {
const packageJsonContent = await Bun.file(join(import.meta.dir, "yarn", folder, "package.json")).text();
const yarnLockContent = await Bun.file(join(import.meta.dir, "yarn", folder, "yarn.lock")).text();
const tempDir = tempDirWithFiles("yarn-lock-migration-", {
using dir = tempDir("yarn-lock-migration-", {
"package.json": packageJsonContent,
"yarn.lock": yarnLockContent,
});
const migrateResult = Bun.spawn({
cmd: [bunExe(), "pm", "migrate", "-f"],
cwd: tempDir,
cwd: String(dir),
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
@@ -1367,14 +1367,14 @@ describe("bun pm migrate for existing yarn.lock", () => {
});
expect(migrateResult.exited).resolves.toBe(0);
expect(Bun.file(join(tempDir, "bun.lock")).exists()).resolves.toBe(true);
expect(Bun.file(join(String(dir), "bun.lock")).exists()).resolves.toBe(true);
const bunLockContent = await Bun.file(join(tempDir, "bun.lock")).text();
const bunLockContent = await Bun.file(join(String(dir), "bun.lock")).text();
expect(bunLockContent).toMatchSnapshot(folder);
});
test("yarn.lock with packages that have os/cpu requirements", async () => {
const tempDir = tempDirWithFiles("yarn-migration-os-cpu", {
using dir = tempDir("yarn-migration-os-cpu", {
"package.json": JSON.stringify(
{
name: "os-cpu-test",
@@ -1454,7 +1454,7 @@ fsevents@^2.3.2:
// Run bun pm migrate
const migrateResult = await Bun.spawn({
cmd: [bunExe(), "pm", "migrate", "-f"],
cwd: tempDir,
cwd: String(dir),
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
@@ -1468,9 +1468,9 @@ fsevents@^2.3.2:
]);
expect(exitCode).toBe(0);
expect(fs.existsSync(join(tempDir, "bun.lock"))).toBe(true);
expect(fs.existsSync(join(String(dir), "bun.lock"))).toBe(true);
const bunLockContent = fs.readFileSync(join(tempDir, "bun.lock"), "utf8");
const bunLockContent = fs.readFileSync(join(String(dir), "bun.lock"), "utf8");
expect(bunLockContent).toMatchSnapshot("os-cpu-yarn-migration");
// Verify that the lockfile contains the expected os/cpu metadata by checking the snapshot
@@ -1479,4 +1479,328 @@ fsevents@^2.3.2:
expect(bunLockContent).toContain("@esbuild/linux-arm64");
expect(bunLockContent).toContain("@esbuild/darwin-arm64");
});
test("yarn.lock with custom registry tarball URLs", async () => {
using dir = tempDir("yarn-migration-custom-registry", {
"package.json": JSON.stringify(
{
name: "custom-registry-test",
version: "1.0.0",
dependencies: {
"my-package": "https://my-custom-registry.com/my-package/-/my-package-1.0.0.tgz",
"another-pkg": "https://packages.example.com/tarballs/another-pkg-2.1.0.tgz",
},
},
null,
2,
),
"yarn.lock": `# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"another-pkg@https://packages.example.com/tarballs/another-pkg-2.1.0.tgz":
version "2.1.0"
resolved "https://packages.example.com/tarballs/another-pkg-2.1.0.tgz#abc123def456"
integrity sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
"my-package@https://my-custom-registry.com/my-package/-/my-package-1.0.0.tgz":
version "1.0.0"
resolved "https://my-custom-registry.com/my-package/-/my-package-1.0.0.tgz#deadbeef"
integrity sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=
`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "pm", "migrate", "-f"],
cwd: String(dir),
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).not.toContain("error");
expect(exitCode).toBe(0);
const bunLockContent = await Bun.file(join(String(dir), "bun.lock")).text();
expect(bunLockContent).toMatchSnapshot("custom-registry-yarn-migration");
// Verify custom registry URLs are preserved as tarball resolutions
expect(bunLockContent).toContain("my-package");
expect(bunLockContent).toContain("another-pkg");
expect(bunLockContent).toContain("my-custom-registry.com");
expect(bunLockContent).toContain("packages.example.com");
});
test("all yarn v1 quirks in one test", async () => {
using dir = tempDir("yarn-comprehensive", {
"package.json": JSON.stringify(
{
name: "yarn-quirks-test",
version: "1.0.0",
private: true,
workspaces: ["packages/*"],
dependencies: {
// Multi-spec consolidation: multiple ranges -> same version
"is-number": "^7.0.0",
// npm alias
"my-lodash": "npm:lodash@4.17.21",
// Scoped package
"@babel/core": "^7.20.0",
// Long build tag
"@prisma/engines": "4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81",
},
devDependencies: {
// Multiple versions of same package
lodash: "^3.10.0",
},
optionalDependencies: {
// Platform-specific (but yarn doesn't store os/cpu)
fsevents: "^2.3.2",
},
},
null,
2,
),
"packages/app/package.json": JSON.stringify(
{
name: "@workspace/app",
version: "1.0.0",
dependencies: {
// Workspace dependency
"@workspace/lib": "workspace:*",
// Creates multi-spec entry with root
"is-number": "~7.0.0",
// Different version than root
lodash: "^4.17.0",
},
},
null,
2,
),
"packages/lib/package.json": JSON.stringify(
{
name: "@workspace/lib",
version: "1.0.0",
dependencies: {
// External dep
"is-odd": "^3.0.0",
},
},
null,
2,
),
"yarn.lock": `# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@babel/core@^7.20.0":
version "7.20.12"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.20.12.tgz#7f12f7fe01cfcc5c4f37fa6e09a6e7ac0736b5e9"
integrity sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==
dependencies:
"@babel/types" "^7.20.7"
"@babel/types@^7.20.7":
version "7.20.7"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.7.tgz#54ec75e252318423fc07fb644dc6a58a64c09b7f"
integrity sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==
"@prisma/engines@4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81":
version "4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81"
resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81.tgz#5512069ca14c44af7f38e7c39d9a169480e63a33"
integrity sha512-q617EUWfRIDTriWADZ4YiWRZXCa/WuhNgLTVd+HqWLffjMSPzyM5uOWoauX91wvQClSKZU4pzI4JJLQ9Kl62Qg==
"@workspace/lib@workspace:*, @workspace/lib@workspace:packages/lib":
version "0.0.0-use.local"
resolved "file:packages/lib"
dependencies:
is-odd "^3.0.0"
fsevents@^2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
"is-number@^6.0.0":
version "6.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-6.0.0.tgz#e6d15ad31fc262887d1846d1c6c84c9b3b0b5982"
integrity sha512-Wu1VHeILBK8KAWJUAiSZQX94GmOE45Rg6/538fKwiloUu21KncEkYGPqob2oSZ5mUT73vLGrHQjKw3KMPwfDzg==
"is-number@^7.0.0, is-number@~7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
is-odd@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/is-odd/-/is-odd-3.0.1.tgz#65101baf63c59f7b5c3a429d0a4e3d8ca7914559"
integrity sha512-CQpnWPrDwmP1+SMHXZhtLtJv90yiyVfluGsX5iNCVkrhQtU3TQHsUWPG9wkdk9Lgd5yNpAg9jQEo90CBaXgWMA==
dependencies:
is-number "^6.0.0"
lodash@^3.10.0:
version "3.10.1"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
integrity sha512-9mDDwqVIma6OZX79ZlDACZl8sBm0TEnkf99zV3iMA4GzkSTbUYwRa7cmPR8CKkkl+OuU/fGTM48FNhQ5GnLsXw==
"lodash@^4.17.0, lodash@4.17.21":
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
my-lodash@npm:lodash@4.17.21:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "pm", "migrate", "-f"],
cwd: String(dir),
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).not.toContain("error");
expect(exitCode).toBe(0);
const bunLockContent = await Bun.file(join(String(dir), "bun.lock")).text();
expect(bunLockContent).toMatchSnapshot("yarn-comprehensive");
// Multi-spec consolidation: both specs resolve to same package
expect(bunLockContent).toContain("is-number@7.0.0");
// npm alias: should create entry for alias pointing to real package
expect(bunLockContent).toContain("my-lodash");
expect(bunLockContent).toContain("lodash@4.17.21");
// Multiple versions: both lodash 3 and 4 should exist
expect(bunLockContent).toContain("lodash@3.10.1");
// Long build tag preserved
expect(bunLockContent).toContain("4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81");
// Scoped packages
expect(bunLockContent).toContain("@babel/core");
expect(bunLockContent).toContain("@babel/types");
// Workspace dependencies
expect(bunLockContent).toContain("@workspace/app");
expect(bunLockContent).toContain("@workspace/lib");
// Integrity hashes preserved
expect(bunLockContent).toMatch(/sha512-/);
});
test("parser handles indentation correctly", async () => {
// Test that the parser correctly handles the YAML-like indentation
using dir = tempDir("yarn-indentation", {
"package.json": JSON.stringify(
{
name: "indent-test",
version: "1.0.0",
dependencies: {
"test-pkg": "^1.0.0",
},
},
null,
2,
),
"yarn.lock": `# yarn lockfile v1
test-pkg@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/test-pkg/-/test-pkg-1.0.0.tgz#abc123"
integrity sha512-test123==
dependencies:
dep-a "^1.0.0"
dep-b "^2.0.0"
dep-a@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/dep-a/-/dep-a-1.0.0.tgz#def456"
integrity sha512-testa==
dep-b@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/dep-b/-/dep-b-2.0.0.tgz#ghi789"
integrity sha512-testb==
`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "pm", "migrate", "-f"],
cwd: String(dir),
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).not.toContain("error");
expect(exitCode).toBe(0);
const bunLockContent = await Bun.file(join(String(dir), "bun.lock")).text();
expect(bunLockContent).toMatchSnapshot("yarn-indentation");
expect(bunLockContent).toContain("test-pkg");
expect(bunLockContent).toContain("dep-a");
expect(bunLockContent).toContain("dep-b");
});
test("handles optionalDependencies correctly", async () => {
using dir = tempDir("yarn-optional", {
"package.json": JSON.stringify(
{
name: "optional-test",
version: "1.0.0",
dependencies: {
"pkg-a": "^1.0.0",
},
},
null,
2,
),
"yarn.lock": `# yarn lockfile v1
pkg-a@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/pkg-a/-/pkg-a-1.0.0.tgz#abc"
integrity sha512-testoptional==
optionalDependencies:
optional-dep "^1.0.0"
optional-dep@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/optional-dep/-/optional-dep-1.0.0.tgz#def"
integrity sha512-testopt2==
`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "pm", "migrate", "-f"],
cwd: String(dir),
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).not.toContain("error");
expect(exitCode).toBe(0);
const bunLockContent = await Bun.file(join(String(dir), "bun.lock")).text();
expect(bunLockContent).toMatchSnapshot("yarn-optional-deps");
expect(bunLockContent).toContain("pkg-a");
expect(bunLockContent).toContain("optional-dep");
// Should have optionalDependencies field in metadata
expect(bunLockContent).toMatch(/optionalDependencies/);
});
});