Files
bun.sh/test/cli/install/migration/pnpm-lock-migration.test.ts
Michael H ba20670da3 implement pnpm migration (#22262)
### What does this PR do?

fixes #7157, fixes #14662

migrates pnpm-workspace.yaml data to package.json & converts
pnpm-lock.yml to bun.lock

---

### How did you verify your code works?

manually, tests and real world examples

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
2025-09-27 00:45:29 -07:00

405 lines
11 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import fs from "fs";
import { bunEnv, bunExe, tempDirWithFiles } from "harness";
import { join } from "path";
describe("pnpm-lock.yaml migration", () => {
test("simple pnpm lockfile migration produces correct bun.lock", async () => {
const tempDir = tempDirWithFiles("pnpm-migrate-simple", {
"package.json": JSON.stringify(
{
name: "simple-pnpm-test",
version: "1.0.0",
dependencies: {
"is-number": "^7.0.0",
"left-pad": "^1.3.0",
},
},
null,
2,
),
"pnpm-lock.yaml": `lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
dependencies:
is-number:
specifier: ^7.0.0
version: 7.0.0
left-pad:
specifier: ^1.3.0
version: 1.3.0
packages:
is-number@7.0.0:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
left-pad@1.3.0:
resolution: {integrity: sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==}
deprecated: use String.prototype.padStart()
snapshots:
is-number@7.0.0: {}
left-pad@1.3.0: {}
`,
});
// Run bun pm migrate
await using proc = Bun.spawn({
cmd: [bunExe(), "pm", "migrate"],
cwd: tempDir,
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
if (exitCode !== 0) {
console.log("stdout:", stdout);
console.log("stderr:", stderr);
}
expect(exitCode).toBe(0);
// Check migration message in stderr
expect(stderr).toContain("migrated lockfile from pnpm-lock.yaml");
// Check that bun.lock was created
expect(fs.existsSync(join(tempDir, "bun.lock"))).toBe(true);
// Read and snapshot the migrated lockfile
const bunLockContent = fs.readFileSync(join(tempDir, "bun.lock"), "utf8");
expect(bunLockContent).toMatchSnapshot("simple-pnpm-migration");
// Verify install works with migrated lockfile
await using installProc = Bun.spawn({
cmd: [bunExe(), "install", "--frozen-lockfile"],
cwd: tempDir,
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [installStdout, installStderr, installExitCode] = await Promise.all([
installProc.stdout.text(),
installProc.stderr.text(),
installProc.exited,
]);
if (installExitCode !== 0) {
console.log("Install stdout:", installStdout);
console.log("Install stderr:", installStderr);
console.log("Lockfile content:", bunLockContent);
}
expect(installExitCode).toBe(0);
// Verify packages were installed
expect(fs.existsSync(join(tempDir, "node_modules/is-number"))).toBe(true);
expect(fs.existsSync(join(tempDir, "node_modules/left-pad"))).toBe(true);
});
test("pnpm workspace lockfile migration", async () => {
const tempDir = tempDirWithFiles("pnpm-migrate-workspace", {
"package.json": JSON.stringify(
{
name: "monorepo-root",
version: "1.0.0",
private: true,
workspaces: ["packages/*", "apps/*"],
},
null,
2,
),
"pnpm-workspace.yaml": `packages:
- 'packages/*'
- 'apps/*'
`,
"packages/ui/package.json": JSON.stringify(
{
name: "@repo/ui",
version: "1.0.0",
dependencies: {
react: "^18.2.0",
},
},
null,
2,
),
"packages/utils/package.json": JSON.stringify(
{
name: "@repo/utils",
version: "1.0.0",
dependencies: {
lodash: "^4.17.21",
},
},
null,
2,
),
"apps/web/package.json": JSON.stringify(
{
name: "@repo/web",
version: "1.0.0",
dependencies: {
"@repo/ui": "workspace:*",
"@repo/utils": "workspace:*",
next: "^14.0.0",
},
},
null,
2,
),
"pnpm-lock.yaml": `lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
dependencies: {}
apps/web:
dependencies:
'@repo/ui':
specifier: workspace:*
version: link:../../packages/ui
'@repo/utils':
specifier: workspace:*
version: link:../../packages/utils
next:
specifier: ^14.0.0
version: 14.0.4
packages/ui:
dependencies:
react:
specifier: ^18.2.0
version: 18.2.0
packages/utils:
dependencies:
lodash:
specifier: ^4.17.21
version: 4.17.21
packages:
react@18.2.0:
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
engines: {node: '>=0.10.0'}
lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
next@14.0.4:
resolution: {integrity: sha512-qbwypnM7327SadwFtxXnQdGiKpkuhaRLE2uq62/nRul9cj9KhQ5LhHmlziTNqUidZotw/Q1I9OjirBROdUJNgA==}
engines: {node: '>=18.17.0'}
hasBin: true
loose-envify@1.4.0:
resolution: {}
hasBin: true
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
snapshots:
react@18.2.0:
dependencies:
loose-envify: 1.4.0
lodash@4.17.21: {}
next@14.0.4:
dependencies:
react: 18.2.0
loose-envify@1.4.0:
dependencies:
js-tokens: 4.0.0
js-tokens@4.0.0: {}
`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "pm", "migrate"],
cwd: tempDir,
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
if (exitCode !== 0) {
console.log("stdout:", stdout);
console.log("stderr:", stderr);
}
expect(exitCode).toBe(0);
// Check migration message in stderr
expect(stderr).toContain("migrated lockfile from pnpm-lock.yaml");
expect(fs.existsSync(join(tempDir, "bun.lock"))).toBe(true);
const bunLockContent = fs.readFileSync(join(tempDir, "bun.lock"), "utf8");
expect(bunLockContent).toMatchSnapshot("workspace-pnpm-migration");
const packageJson = JSON.parse(fs.readFileSync(join(tempDir, "package.json"), "utf8"));
expect(packageJson).toMatchSnapshot("workspace-pnpm-migration-package-json");
});
test("pnpm with npm protocol aliases", async () => {
const tempDir = tempDirWithFiles("pnpm-migrate-npm-aliases", {
"package.json": JSON.stringify(
{
name: "alias-test",
dependencies: {
"my-react": "npm:react@^17.0.0",
"my-lodash": "npm:lodash@latest",
},
},
null,
2,
),
"pnpm-lock.yaml": `lockfileVersion: '9.0'
importers:
.:
dependencies:
my-react:
specifier: npm:react@^17.0.0
version: react@17.0.2
my-lodash:
specifier: npm:lodash@latest
version: lodash@4.17.21
packages:
react@17.0.2:
resolution: {integrity: sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==}
engines: {node: '>=0.10.0'}
lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
loose-envify@1.4.0:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/tNVBQAZ8HW+WqwP25nGsjKeMZk13HGBF7YbJSi1KyeKwGAteWUa/ZKPUKAZNiIrUqZg==}
hasBin: true
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnkjVqfO3E+1Q45hXf64UF+6eWwJJCTNJN7q7vfVQqPJZsB/1/vb9TuT9e2vYfqvnMqGCDJ5x6+WUJA==}
snapshots:
react@17.0.2:
dependencies:
loose-envify: 1.4.0
object-assign: 4.1.1
lodash@4.17.21: {}
loose-envify@1.4.0:
dependencies:
js-tokens: 4.0.0
js-tokens@4.0.0: {}
object-assign@4.1.1: {}
`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "pm", "migrate"],
cwd: tempDir,
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
if (exitCode !== 0) {
console.log("stdout:", stdout);
console.log("stderr:", stderr);
}
expect(exitCode).toBe(0);
// Check migration message in stderr
expect(stderr).toContain("migrated lockfile from pnpm-lock.yaml");
expect(fs.existsSync(join(tempDir, "bun.lock"))).toBe(true);
const bunLockContent = fs.readFileSync(join(tempDir, "bun.lock"), "utf8");
expect(bunLockContent).toMatchSnapshot("npm-aliases-pnpm-migration");
});
test("handles different pnpm lockfile versions", async () => {
// Test version 8
const v8Dir = tempDirWithFiles("pnpm-v8", {
"package.json": JSON.stringify({ name: "v8-test", dependencies: { "lodash": "^4.17.21" } }),
"pnpm-lock.yaml": `lockfileVersion: '8.0'
importers:
.:
dependencies:
lodash:
specifier: ^4.17.21
version: 4.17.21
packages:
lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
snapshots:
lodash@4.17.21: {}`,
});
await using v8Proc = Bun.spawn({
cmd: [bunExe(), "pm", "migrate"],
cwd: v8Dir,
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const v8ExitCode = await v8Proc.exited;
expect(v8ExitCode).toBe(0);
expect(fs.existsSync(join(v8Dir, "bun.lock"))).toBe(true);
});
test("handles missing pnpm-lock.yaml gracefully", async () => {
const tempDir = tempDirWithFiles("pnpm-migrate-missing", {
"package.json": JSON.stringify({
name: "test",
version: "1.0.0",
}),
});
await using proc = Bun.spawn({
cmd: [bunExe(), "pm", "migrate"],
cwd: tempDir,
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
// Should return an error when no lockfile is found
expect(exitCode).toBe(1);
expect(stderr).toContain("could not find any other lockfile");
expect(stderr).not.toContain("migrated lockfile from pnpm-lock.yaml");
});
});