Add comprehensive real-world Yarn Berry migration test

Test simulates a realistic Next.js monorepo with:
- workspace:^ dependencies between workspace packages
- Optional peer dependencies (like @opentelemetry/api for Next.js)
- Platform-specific binaries with conditions (darwin-arm64, linux-x64)
- Multiple bin entries (tsc, tsserver, next, loose-envify)
- Nested dependencies and peer dependencies
- Both migration and bun ci (frozen lockfile install)

Verifies the complete workflow works end-to-end without lockfile changes or resolution errors.
This commit is contained in:
RiskyMH
2025-10-13 20:31:47 +11:00
parent 1e3dbef951
commit 71eae86041
2 changed files with 316 additions and 1 deletions

View File

@@ -120,3 +120,68 @@ exports[`Yarn Berry migration v6 format fallback with os/cpu arrays 1`] = `
}
"
`;
exports[`Yarn Berry migration real-world monorepo with Next.js, workspace:^ deps, optional peers, and platform-specific bins 1`] = `
"{
"lockfileVersion": 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=="],
}
}
"
`;

View File

@@ -45,7 +45,7 @@ describe("Yarn Berry migration", () => {
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(exitCode).toBe(0);
const lockContents = await Bun.file(join(String(dir), "bun.lock")).text();
expect(lockContents).toContain("fsevents");
expect(lockContents).toContain("@esbuild/darwin-arm64");
@@ -291,4 +291,254 @@ describe("Yarn Berry migration", () => {
const lockContents = await Bun.file(join(String(dir), "bun.lock")).text();
expect(lockContents).toMatchSnapshot();
});
test("real-world monorepo with Next.js, workspace:^ deps, optional peers, and platform-specific bins", async () => {
using dir = tempDir("yarn-berry-nextjs-monorepo", {
"package.json": JSON.stringify({
name: "nextjs-monorepo",
private: true,
workspaces: ["packages/*", "apps/*"],
devDependencies: {
typescript: "^5.0.0",
},
}),
"yarn.lock": `__metadata:
version: 8
cacheKey: 10c0
"@next/env@npm:14.1.0":
version: 14.1.0
resolution: "@next/env@npm:14.1.0"
checksum: 10/abc123
languageName: node
linkType: hard
"@next/swc-darwin-arm64@npm:14.1.0":
version: 14.1.0
resolution: "@next/swc-darwin-arm64@npm:14.1.0"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
"@next/swc-linux-x64-gnu@npm:14.1.0":
version: 14.1.0
resolution: "@next/swc-linux-x64-gnu@npm:14.1.0"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
"@ui/shared@workspace:^, @ui/shared@workspace:packages/shared":
version: 0.0.0-use.local
resolution: "@ui/shared@workspace:packages/shared"
dependencies:
react: "npm:^18.2.0"
languageName: unknown
linkType: soft
"js-tokens@npm:^4.0.0":
version: 4.0.0
resolution: "js-tokens@npm:4.0.0"
checksum: 10/abc456
languageName: node
linkType: hard
"loose-envify@npm:^1.1.0":
version: 1.4.0
resolution: "loose-envify@npm:1.4.0"
dependencies:
js-tokens: "npm:^4.0.0"
bin:
loose-envify: cli.js
checksum: 10/def789
languageName: node
linkType: hard
"next@npm:14.1.0":
version: 14.1.0
resolution: "next@npm:14.1.0"
dependencies:
"@next/env": "npm:14.1.0"
"@next/swc-darwin-arm64": "npm:14.1.0"
"@next/swc-linux-x64-gnu": "npm:14.1.0"
busboy: "npm:1.6.0"
styled-jsx: "npm:5.1.1"
peerDependencies:
"@opentelemetry/api": ^1.1.0
react: ^18.2.0
react-dom: ^18.2.0
sass: ^1.3.0
peerDependenciesMeta:
"@opentelemetry/api":
optional: true
sass:
optional: true
bin:
next: dist/bin/next
checksum: 10/ghi012
languageName: node
linkType: hard
"nextjs-app@workspace:apps/web":
version: 0.0.0-use.local
resolution: "nextjs-app@workspace:apps/web"
dependencies:
"@ui/shared": "workspace:^"
next: "npm:14.1.0"
react: "npm:^18.2.0"
react-dom: "npm:^18.2.0"
languageName: unknown
linkType: soft
"nextjs-monorepo@workspace:.":
version: 0.0.0-use.local
resolution: "nextjs-monorepo@workspace:."
dependencies:
typescript: "npm:^5.0.0"
languageName: unknown
linkType: soft
"busboy@npm:1.6.0":
version: 1.6.0
resolution: "busboy@npm:1.6.0"
dependencies:
streamsearch: "npm:^1.1.0"
checksum: 10/jkl345
languageName: node
linkType: hard
"react@npm:^18.2.0":
version: 18.2.0
resolution: "react@npm:18.2.0"
dependencies:
loose-envify: "npm:^1.1.0"
checksum: 10/mno678
languageName: node
linkType: hard
"react-dom@npm:^18.2.0":
version: 18.2.0
resolution: "react-dom@npm:18.2.0"
dependencies:
loose-envify: "npm:^1.1.0"
react: "npm:^18.2.0"
scheduler: "npm:^0.23.0"
peerDependencies:
react: ^18.2.0
checksum: 10/pqr901
languageName: node
linkType: hard
"scheduler@npm:^0.23.0":
version: 0.23.0
resolution: "scheduler@npm:0.23.0"
dependencies:
loose-envify: "npm:^1.1.0"
checksum: 10/stu234
languageName: node
linkType: hard
"streamsearch@npm:^1.1.0":
version: 1.1.0
resolution: "streamsearch@npm:1.1.0"
checksum: 10/vwx567
languageName: node
linkType: hard
"styled-jsx@npm:5.1.1":
version: 5.1.1
resolution: "styled-jsx@npm:5.1.1"
dependencies:
client-only: "npm:0.0.1"
peerDependencies:
react: "*"
checksum: 10/yza890
languageName: node
linkType: hard
"client-only@npm:0.0.1":
version: 0.0.1
resolution: "client-only@npm:0.0.1"
checksum: 10/bcd123
languageName: node
linkType: hard
"typescript@npm:^5.0.0":
version: 5.3.3
resolution: "typescript@npm:5.3.3"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: 10/efg456
languageName: node
linkType: hard
`,
});
// Create workspace packages
mkdirSync(join(String(dir), "packages", "shared"), { recursive: true });
writeFileSync(
join(String(dir), "packages", "shared", "package.json"),
JSON.stringify({
name: "@ui/shared",
version: "1.0.0",
dependencies: {
react: "^18.2.0",
},
}),
);
mkdirSync(join(String(dir), "apps", "web"), { recursive: true });
writeFileSync(
join(String(dir), "apps", "web", "package.json"),
JSON.stringify({
name: "nextjs-app",
version: "1.0.0",
dependencies: {
"@ui/shared": "workspace:^",
next: "14.1.0",
react: "^18.2.0",
"react-dom": "^18.2.0",
},
}),
);
// Test migration
await using procMigrate = Bun.spawn({
cmd: [bunExe(), "pm", "migrate"],
cwd: String(dir),
env: bunEnv,
stderr: "pipe",
stdout: "pipe",
});
const [stdoutMigrate, stderrMigrate, exitCodeMigrate] = await Promise.all([
procMigrate.stdout.text(),
procMigrate.stderr.text(),
procMigrate.exited,
]);
expect(exitCodeMigrate).toBe(0);
const lockContents = await Bun.file(join(String(dir), "bun.lock")).text();
expect(lockContents).toMatchSnapshot();
// Test that bun install --frozen-lockfile works (bun ci)
await using procInstall = Bun.spawn({
cmd: [bunExe(), "install", "--frozen-lockfile"],
cwd: String(dir),
env: bunEnv,
stderr: "pipe",
stdout: "pipe",
});
const [stdoutInstall, stderrInstall, exitCodeInstall] = await Promise.all([
procInstall.stdout.text(),
procInstall.stderr.text(),
procInstall.exited,
]);
expect(exitCodeInstall).toBe(0);
expect(stderrInstall).not.toContain("lockfile had changes");
expect(stderrInstall).not.toContain("failed to resolve");
expect(stderrInstall).not.toContain("failed to parse");
});
});