From 71eae860412df42ca071dd24bf56667adc9bc6bd Mon Sep 17 00:00:00 2001 From: RiskyMH Date: Mon, 13 Oct 2025 20:31:47 +1100 Subject: [PATCH] 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. --- .../__snapshots__/yarn-berry.test.ts.snap | 65 +++++ test/cli/install/migration/yarn-berry.test.ts | 252 +++++++++++++++++- 2 files changed, 316 insertions(+), 1 deletion(-) diff --git a/test/cli/install/migration/__snapshots__/yarn-berry.test.ts.snap b/test/cli/install/migration/__snapshots__/yarn-berry.test.ts.snap index 38c8222d76..8105eddc2b 100644 --- a/test/cli/install/migration/__snapshots__/yarn-berry.test.ts.snap +++ b/test/cli/install/migration/__snapshots__/yarn-berry.test.ts.snap @@ -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=="], + } +} +" +`; diff --git a/test/cli/install/migration/yarn-berry.test.ts b/test/cli/install/migration/yarn-berry.test.ts index c62abcb83a..c37aa7321e 100644 --- a/test/cli/install/migration/yarn-berry.test.ts +++ b/test/cli/install/migration/yarn-berry.test.ts @@ -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"); + }); });