mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 02:48:50 +00:00
### 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>
1006 lines
28 KiB
TypeScript
1006 lines
28 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 Migration Complete Test Suite", () => {
|
|
test("comprehensive PNPM migration with all edge cases", async () => {
|
|
// ===== SECTION 1: Basic Dependencies =====
|
|
const basicTest = tempDirWithFiles("pnpm-basic", {
|
|
"package.json": JSON.stringify({
|
|
name: "basic-test",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"lodash": "^4.17.21",
|
|
"react": "^18.2.0",
|
|
},
|
|
devDependencies: {
|
|
"typescript": "^5.3.3",
|
|
},
|
|
}),
|
|
"pnpm-lock.yaml": `lockfileVersion: '9.0'
|
|
|
|
importers:
|
|
.:
|
|
dependencies:
|
|
lodash:
|
|
specifier: ^4.17.21
|
|
version: 4.17.21
|
|
react:
|
|
specifier: ^18.2.0
|
|
version: 18.2.0
|
|
devDependencies:
|
|
typescript:
|
|
specifier: ^5.3.3
|
|
version: 5.3.3
|
|
|
|
packages:
|
|
lodash@4.17.21:
|
|
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
|
|
|
react@18.2.0:
|
|
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
|
|
dependencies:
|
|
loose-envify: 1.4.0
|
|
|
|
typescript@5.3.3:
|
|
resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==}
|
|
engines: {node: '>=14.17'}
|
|
hasBin: true
|
|
|
|
loose-envify@1.4.0:
|
|
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
|
hasBin: true
|
|
dependencies:
|
|
js-tokens: 4.0.0
|
|
|
|
js-tokens@4.0.0:
|
|
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
|
|
|
snapshots:
|
|
lodash@4.17.21: {}
|
|
|
|
react@18.2.0:
|
|
dependencies:
|
|
loose-envify: 1.4.0
|
|
|
|
typescript@5.3.3: {}
|
|
|
|
loose-envify@1.4.0:
|
|
dependencies:
|
|
js-tokens: 4.0.0
|
|
|
|
js-tokens@4.0.0: {}`,
|
|
});
|
|
|
|
const basicProc = Bun.spawn({
|
|
cmd: [bunExe(), "pm", "migrate"],
|
|
cwd: basicTest,
|
|
env: bunEnv,
|
|
stderr: "pipe",
|
|
});
|
|
|
|
const [basicStderr, basicExitCode] = await Promise.all([basicProc.stderr.text(), basicProc.exited]);
|
|
|
|
expect(basicExitCode).toBe(0);
|
|
expect(basicStderr).toContain("migrated lockfile from pnpm-lock.yaml");
|
|
|
|
const basicLockfile = fs.readFileSync(join(basicTest, "bun.lock"), "utf8");
|
|
expect(basicLockfile).toContain('"lodash": "^4.17.21"');
|
|
expect(basicLockfile).toContain('"react": "^18.2.0"');
|
|
expect(basicLockfile).toContain('"typescript": "^5.3.3"');
|
|
expect(basicLockfile).toMatchSnapshot("basic-dependencies");
|
|
|
|
// ===== SECTION 2: Canary Versions =====
|
|
const canaryTest = tempDirWithFiles("pnpm-canary", {
|
|
"package.json": JSON.stringify({
|
|
name: "canary-test",
|
|
dependencies: {
|
|
"react": "19.2.0-canary-a96a0f39-20250815",
|
|
"react-dom": "19.2.0-canary-a96a0f39-20250815",
|
|
"scheduler": "0.27.0-canary-a96a0f39-20250815",
|
|
},
|
|
}),
|
|
"pnpm-lock.yaml": `lockfileVersion: '9.0'
|
|
|
|
importers:
|
|
.:
|
|
dependencies:
|
|
react:
|
|
specifier: 19.2.0-canary-a96a0f39-20250815
|
|
version: 19.2.0-canary-a96a0f39-20250815
|
|
react-dom:
|
|
specifier: 19.2.0-canary-a96a0f39-20250815
|
|
version: 19.2.0-canary-a96a0f39-20250815
|
|
scheduler:
|
|
specifier: 0.27.0-canary-a96a0f39-20250815
|
|
version: 0.27.0-canary-a96a0f39-20250815
|
|
|
|
packages:
|
|
react@19.2.0-canary-a96a0f39-20250815:
|
|
resolution: {integrity: sha512-reactcanary==}
|
|
|
|
react-dom@19.2.0-canary-a96a0f39-20250815:
|
|
resolution: {integrity: sha512-reactdomcanary==}
|
|
dependencies:
|
|
scheduler: 0.27.0-canary-a96a0f39-20250815
|
|
|
|
scheduler@0.27.0-canary-a96a0f39-20250815:
|
|
resolution: {integrity: sha512-schedulercanary==}
|
|
|
|
snapshots:
|
|
react@19.2.0-canary-a96a0f39-20250815: {}
|
|
|
|
react-dom@19.2.0-canary-a96a0f39-20250815:
|
|
dependencies:
|
|
scheduler: 0.27.0-canary-a96a0f39-20250815
|
|
|
|
scheduler@0.27.0-canary-a96a0f39-20250815: {}`,
|
|
});
|
|
|
|
const canaryProc = Bun.spawn({
|
|
cmd: [bunExe(), "pm", "migrate"],
|
|
cwd: canaryTest,
|
|
env: bunEnv,
|
|
stderr: "pipe",
|
|
});
|
|
|
|
const [canaryStderr, canaryExitCode] = await Promise.all([canaryProc.stderr.text(), canaryProc.exited]);
|
|
|
|
expect(canaryExitCode).toBe(0);
|
|
const canaryLockfile = fs.readFileSync(join(canaryTest, "bun.lock"), "utf8");
|
|
|
|
// Verify canary versions are preserved exactly
|
|
expect(canaryLockfile).toContain('"react@19.2.0-canary-a96a0f39-20250815"');
|
|
expect(canaryLockfile).toContain('"scheduler@0.27.0-canary-a96a0f39-20250815"');
|
|
expect(canaryLockfile).not.toContain("canary-a96a0f39-20250815-"); // No corruption
|
|
expect(canaryLockfile).toMatchSnapshot("canary-versions");
|
|
|
|
// ===== SECTION 3: Complex Monorepo with Workspaces =====
|
|
const monorepoTest = tempDirWithFiles("pnpm-monorepo", {
|
|
"package.json": JSON.stringify({
|
|
name: "monorepo-root",
|
|
private: true,
|
|
workspaces: ["packages/*", "apps/*"],
|
|
dependencies: {
|
|
"@workspace/shared": "workspace:*",
|
|
"@workspace/utils": "workspace:^",
|
|
},
|
|
}),
|
|
"packages/shared/package.json": JSON.stringify({ name: "shared" }),
|
|
"packages/utils/package.json": JSON.stringify({ name: "utils" }),
|
|
"apps/web/package.json": JSON.stringify({ name: "web" }),
|
|
"pnpm-lock.yaml": `lockfileVersion: '9.0'
|
|
|
|
importers:
|
|
.:
|
|
dependencies:
|
|
'@workspace/shared':
|
|
specifier: workspace:*
|
|
version: link:packages/shared
|
|
'@workspace/utils':
|
|
specifier: workspace:^
|
|
version: link:packages/utils
|
|
|
|
packages/shared:
|
|
dependencies:
|
|
lodash:
|
|
specifier: ^4.17.21
|
|
version: 4.17.21
|
|
'@workspace/utils':
|
|
specifier: workspace:*
|
|
version: link:../utils
|
|
|
|
packages/utils:
|
|
dependencies:
|
|
axios:
|
|
specifier: ^1.6.0
|
|
version: 1.6.7
|
|
|
|
apps/web:
|
|
dependencies:
|
|
'@workspace/shared':
|
|
specifier: workspace:*
|
|
version: link:../../packages/shared
|
|
react:
|
|
specifier: ^18.2.0
|
|
version: 18.2.0
|
|
|
|
packages:
|
|
lodash@4.17.21:
|
|
resolution: {integrity: sha512-lodash==}
|
|
|
|
axios@1.6.7:
|
|
resolution: {integrity: sha512-axios==}
|
|
|
|
react@18.2.0:
|
|
resolution: {integrity: sha512-react==}
|
|
|
|
snapshots:
|
|
lodash@4.17.21: {}
|
|
axios@1.6.7: {}
|
|
react@18.2.0: {}`,
|
|
});
|
|
|
|
const monorepoProc = Bun.spawn({
|
|
cmd: [bunExe(), "pm", "migrate"],
|
|
cwd: monorepoTest,
|
|
env: bunEnv,
|
|
stderr: "pipe",
|
|
});
|
|
|
|
const [monorepoStderr, monorepoExitCode] = await Promise.all([monorepoProc.stderr.text(), monorepoProc.exited]);
|
|
|
|
expect(monorepoExitCode).toBe(0);
|
|
const monorepoLockfile = fs.readFileSync(join(monorepoTest, "bun.lock"), "utf8");
|
|
|
|
// Verify workspaces are created
|
|
expect(monorepoLockfile).toContain('"packages/shared"');
|
|
expect(monorepoLockfile).toContain('"packages/utils"');
|
|
expect(monorepoLockfile).toContain('"apps/web"');
|
|
expect(monorepoLockfile).toContain('"@workspace/shared": "workspace:*"');
|
|
expect(monorepoLockfile).toMatchSnapshot("monorepo-workspaces");
|
|
|
|
// ===== SECTION 4: Patches and Overrides =====
|
|
const patchesTest = tempDirWithFiles("pnpm-patches", {
|
|
"patches/lodash@4.17.21.patch": `diff --git a/lib/application.js b/lib/application.js
|
|
index 1234567..abcdefg 100644
|
|
--- a/lib/application.js
|
|
+++ b/lib/application.js
|
|
@@ -123,7 +123,7 @@ app.defaultConfiguration = function defaultConfiguration() {
|
|
this.set('subdomain offset', 2);
|
|
this.set('trust proxy', false);
|
|
|
|
- // trust proxy inherit back-compat
|
|
+ // trust proxy inherit back-compat - PATCHED
|
|
Object.defineProperty(this.settings, trustProxyDefaultSymbol, {
|
|
configurable: true,
|
|
value: true`,
|
|
"package.json": JSON.stringify({
|
|
name: "patches-test",
|
|
dependencies: {
|
|
"lodash": "^4.17.21",
|
|
},
|
|
pnpm: {
|
|
patchedDependencies: {
|
|
"lodash@4.17.21": "patches/lodash@4.17.21.patch",
|
|
},
|
|
overrides: {
|
|
"axios": "1.6.0",
|
|
},
|
|
},
|
|
}),
|
|
"pnpm-lock.yaml": `lockfileVersion: '9.0'
|
|
|
|
settings:
|
|
autoInstallPeers: true
|
|
|
|
patchedDependencies:
|
|
lodash@4.17.21:
|
|
path: patches/lodash@4.17.21.patch
|
|
hash: abc123
|
|
|
|
overrides:
|
|
axios: 1.6.0
|
|
|
|
importers:
|
|
.:
|
|
dependencies:
|
|
lodash:
|
|
specifier: ^4.17.21
|
|
version: 4.17.21(patch_hash=abc123)
|
|
|
|
packages:
|
|
lodash@4.17.21:
|
|
resolution: {integrity: sha512-lodash==}
|
|
patched: true
|
|
|
|
snapshots:
|
|
lodash@4.17.21(patch_hash=abc123): {}`,
|
|
});
|
|
|
|
const patchesProc = Bun.spawn({
|
|
cmd: [bunExe(), "install", "--lockfile-only"],
|
|
cwd: patchesTest,
|
|
env: bunEnv,
|
|
stderr: "pipe",
|
|
});
|
|
|
|
const [patchesStderr, patchesExitCode] = await Promise.all([patchesProc.stderr.text(), patchesProc.exited]);
|
|
|
|
expect(patchesExitCode).toBe(0);
|
|
const patchesLockfile = fs.readFileSync(join(patchesTest, "bun.lock"), "utf8");
|
|
|
|
expect(patchesLockfile).toContain('"patchedDependencies"');
|
|
expect(patchesLockfile).toContain('"lodash@4.17.21": "patches/lodash@4.17.21.patch"');
|
|
expect(patchesLockfile).toContain('"overrides"');
|
|
expect(patchesLockfile).toContain('"axios": "1.6.0"');
|
|
expect(patchesLockfile).toMatchSnapshot("patches-overrides");
|
|
|
|
// ===== SECTION 5: File and Link Dependencies =====
|
|
const fileLinksTest = tempDirWithFiles("pnpm-file-links", {
|
|
"shared/config/package.json": JSON.stringify({ name: "hi" }),
|
|
"local-pkg/package.json": JSON.stringify({ name: "hi2" }),
|
|
"package.json": JSON.stringify({
|
|
name: "file-links-test",
|
|
dependencies: {
|
|
"local-pkg": "file:./local-pkg",
|
|
"config": "file:./shared/config",
|
|
},
|
|
}),
|
|
"pnpm-lock.yaml": `lockfileVersion: '9.0'
|
|
|
|
settings:
|
|
autoInstallPeers: true
|
|
excludeLinksFromLockfile: false
|
|
|
|
importers:
|
|
|
|
.:
|
|
dependencies:
|
|
config:
|
|
specifier: file:./shared/config
|
|
version: hi2@file:shared/config
|
|
local-pkg:
|
|
specifier: file:./local-pkg
|
|
version: hi@file:local-pkg
|
|
|
|
packages:
|
|
|
|
hi2@file:shared/config:
|
|
resolution: {directory: shared/config, type: directory}
|
|
|
|
hi@file:local-pkg:
|
|
resolution: {directory: local-pkg, type: directory}
|
|
|
|
snapshots:
|
|
|
|
hi2@file:shared/config: {}
|
|
|
|
hi@file:local-pkg: {}
|
|
`,
|
|
});
|
|
|
|
const fileLinksProc = Bun.spawn({
|
|
cmd: [bunExe(), "pm", "migrate"],
|
|
cwd: fileLinksTest,
|
|
env: bunEnv,
|
|
stderr: "pipe",
|
|
});
|
|
|
|
const [fileLinksStderr, fileLinksExitCode] = await Promise.all([fileLinksProc.stderr.text(), fileLinksProc.exited]);
|
|
|
|
expect(fileLinksExitCode).toBe(0);
|
|
const fileLinksLockfile = fs.readFileSync(join(fileLinksTest, "bun.lock"), "utf8");
|
|
|
|
expect(fileLinksLockfile).toContain('"local-pkg": "file:./local-pkg"');
|
|
expect(fileLinksLockfile).toContain('"config": "file:./shared/config"');
|
|
expect(fileLinksLockfile).toMatchSnapshot("file-link-deps");
|
|
|
|
// ===== SECTION 6: Custom Registries =====
|
|
const registriesTest = tempDirWithFiles("pnpm-registries", {
|
|
"package.json": JSON.stringify({
|
|
name: "registries-test",
|
|
dependencies: {
|
|
"@company/private-pkg": "^1.0.0",
|
|
"lodash": "^4.17.21",
|
|
},
|
|
}),
|
|
"pnpm-lock.yaml": `lockfileVersion: '9.0'
|
|
|
|
importers:
|
|
.:
|
|
dependencies:
|
|
'@company/private-pkg':
|
|
specifier: ^1.0.0
|
|
version: 1.0.5(registry=https://npm.company.com/)
|
|
lodash:
|
|
specifier: ^4.17.21
|
|
version: 4.17.21
|
|
|
|
packages:
|
|
'@company/private-pkg@1.0.5':
|
|
resolution: {integrity: sha512-private==, registry: https://npm.company.com/, tarball: https://npm.company.com/@company/private-pkg/-/private-pkg-1.0.5.tgz}
|
|
|
|
lodash@4.17.21:
|
|
resolution: {integrity: sha512-lodash==}
|
|
|
|
snapshots:
|
|
'@company/private-pkg@1.0.5(registry=https://npm.company.com/)': {}
|
|
lodash@4.17.21: {}`,
|
|
});
|
|
|
|
const registriesProc = Bun.spawn({
|
|
cmd: [bunExe(), "pm", "migrate"],
|
|
cwd: registriesTest,
|
|
env: bunEnv,
|
|
stderr: "pipe",
|
|
});
|
|
|
|
const [registriesStderr, registriesExitCode] = await Promise.all([
|
|
registriesProc.stderr.text(),
|
|
registriesProc.exited,
|
|
]);
|
|
|
|
expect(registriesExitCode).toBe(0);
|
|
const registriesLockfile = fs.readFileSync(join(registriesTest, "bun.lock"), "utf8");
|
|
|
|
expect(registriesLockfile).toContain('"@company/private-pkg": "^1.0.0"');
|
|
// Registry URLs are stored in the package entries
|
|
expect(registriesLockfile).toContain('"@company/private-pkg"');
|
|
expect(registriesLockfile).toMatchSnapshot("custom-registries");
|
|
|
|
// ===== SECTION 7: Peer Dependencies =====
|
|
const peerDepsTest = tempDirWithFiles("pnpm-peer-deps", {
|
|
"package.json": JSON.stringify({
|
|
name: "peer-deps-test",
|
|
dependencies: {
|
|
"react": "^18.2.0",
|
|
"react-dom": "^18.2.0",
|
|
"@mui/material": "^5.15.0",
|
|
},
|
|
}),
|
|
"pnpm-lock.yaml": `lockfileVersion: '9.0'
|
|
|
|
settings:
|
|
autoInstallPeers: false
|
|
|
|
importers:
|
|
.:
|
|
dependencies:
|
|
react:
|
|
specifier: ^18.2.0
|
|
version: 18.2.0
|
|
react-dom:
|
|
specifier: ^18.2.0
|
|
version: 18.2.0(react@18.2.0)
|
|
'@mui/material':
|
|
specifier: ^5.15.0
|
|
version: 5.15.0(@emotion/react@11.11.3)(@emotion/styled@11.11.0)(react-dom@18.2.0)(react@18.2.0)
|
|
|
|
packages:
|
|
react@18.2.0:
|
|
resolution: {integrity: sha512-react==}
|
|
|
|
react-dom@18.2.0:
|
|
resolution: {integrity: sha512-reactdom==}
|
|
peerDependencies:
|
|
react: ^18.2.0
|
|
|
|
'@mui/material@5.15.0':
|
|
resolution: {integrity: sha512-mui==}
|
|
peerDependencies:
|
|
'@emotion/react': ^11.5.0
|
|
'@emotion/styled': ^11.3.0
|
|
react: ^17.0.0 || ^18.0.0
|
|
react-dom: ^17.0.0 || ^18.0.0
|
|
peerDependenciesMeta:
|
|
'@emotion/react':
|
|
optional: true
|
|
'@emotion/styled':
|
|
optional: true
|
|
|
|
'@emotion/react@11.11.3':
|
|
resolution: {integrity: sha512-emotion-react==}
|
|
peerDependencies:
|
|
react: '>=16.8.0'
|
|
|
|
'@emotion/styled@11.11.0':
|
|
resolution: {integrity: sha512-emotion-styled==}
|
|
peerDependencies:
|
|
'@emotion/react': ^11.0.0
|
|
react: '>=16.8.0'
|
|
|
|
snapshots:
|
|
react@18.2.0: {}
|
|
|
|
react-dom@18.2.0(react@18.2.0):
|
|
dependencies:
|
|
react: 18.2.0
|
|
|
|
'@mui/material@5.15.0(@emotion/react@11.11.3)(@emotion/styled@11.11.0)(react-dom@18.2.0)(react@18.2.0)':
|
|
dependencies:
|
|
react: 18.2.0
|
|
react-dom: 18.2.0(react@18.2.0)
|
|
optionalDependencies:
|
|
'@emotion/react': 11.11.3(react@18.2.0)
|
|
'@emotion/styled': 11.11.0(@emotion/react@11.11.3)(react@18.2.0)
|
|
|
|
'@emotion/react@11.11.3(react@18.2.0)':
|
|
dependencies:
|
|
react: 18.2.0
|
|
|
|
'@emotion/styled@11.11.0(@emotion/react@11.11.3)(react@18.2.0)':
|
|
dependencies:
|
|
'@emotion/react': 11.11.3(react@18.2.0)
|
|
react: 18.2.0`,
|
|
});
|
|
|
|
const peerDepsProc = Bun.spawn({
|
|
cmd: [bunExe(), "pm", "migrate"],
|
|
cwd: peerDepsTest,
|
|
env: bunEnv,
|
|
stderr: "pipe",
|
|
});
|
|
|
|
const [peerDepsStderr, peerDepsExitCode] = await Promise.all([peerDepsProc.stderr.text(), peerDepsProc.exited]);
|
|
|
|
expect(peerDepsExitCode).toBe(0);
|
|
const peerDepsLockfile = fs.readFileSync(join(peerDepsTest, "bun.lock"), "utf8");
|
|
|
|
expect(peerDepsLockfile).toContain('"@mui/material": "^5.15.0"');
|
|
expect(peerDepsLockfile).toContain('"react": "^18.2.0"');
|
|
expect(peerDepsLockfile).toContain('"react-dom": "^18.2.0"');
|
|
expect(peerDepsLockfile).toMatchSnapshot("peer-dependencies");
|
|
|
|
// ===== SECTION 9: Duplicate Packages =====
|
|
const duplicatesTest = tempDirWithFiles("pnpm-duplicates", {
|
|
"package.json": JSON.stringify({
|
|
name: "duplicates-test",
|
|
dependencies: {
|
|
"package-a": "^1.0.0",
|
|
"package-b": "^1.0.0",
|
|
"my-lodash": "npm:lodash@^4.17.20",
|
|
"lodash": "^4.17.21",
|
|
},
|
|
}),
|
|
"pnpm-lock.yaml": `lockfileVersion: '9.0'
|
|
|
|
importers:
|
|
.:
|
|
dependencies:
|
|
package-a:
|
|
specifier: ^1.0.0
|
|
version: 1.0.0
|
|
package-b:
|
|
specifier: ^1.0.0
|
|
version: 1.0.0
|
|
my-lodash:
|
|
specifier: npm:lodash@^4.17.20
|
|
version: lodash@4.17.20
|
|
lodash:
|
|
specifier: ^4.17.21
|
|
version: 4.17.21
|
|
|
|
packages:
|
|
package-a@1.0.0:
|
|
resolution: {integrity: sha512-packageA==}
|
|
dependencies:
|
|
shared-dep: 2.0.0
|
|
|
|
package-b@1.0.0:
|
|
resolution: {integrity: sha512-packageB==}
|
|
dependencies:
|
|
shared-dep: 3.0.0
|
|
|
|
shared-dep@2.0.0:
|
|
resolution: {integrity: sha512-shared2==}
|
|
|
|
shared-dep@3.0.0:
|
|
resolution: {integrity: sha512-shared3==}
|
|
|
|
lodash@4.17.20:
|
|
resolution: {integrity: sha512-lodash20==}
|
|
|
|
lodash@4.17.21:
|
|
resolution: {integrity: sha512-lodash21==}
|
|
|
|
snapshots:
|
|
package-a@1.0.0:
|
|
dependencies:
|
|
shared-dep: 2.0.0
|
|
|
|
package-b@1.0.0:
|
|
dependencies:
|
|
shared-dep: 3.0.0
|
|
|
|
shared-dep@2.0.0: {}
|
|
shared-dep@3.0.0: {}
|
|
lodash@4.17.20: {}
|
|
lodash@4.17.21: {}`,
|
|
});
|
|
|
|
const duplicatesProc = Bun.spawn({
|
|
cmd: [bunExe(), "pm", "migrate"],
|
|
cwd: duplicatesTest,
|
|
env: bunEnv,
|
|
stderr: "pipe",
|
|
});
|
|
|
|
const [duplicatesStderr, duplicatesExitCode] = await Promise.all([
|
|
duplicatesProc.stderr.text(),
|
|
duplicatesProc.exited,
|
|
]);
|
|
|
|
expect(duplicatesExitCode).toBe(0);
|
|
const duplicatesLockfile = fs.readFileSync(join(duplicatesTest, "bun.lock"), "utf8");
|
|
|
|
// Both versions of shared-dep should exist
|
|
expect(duplicatesLockfile).toContain('"shared-dep@2.0.0"');
|
|
expect(duplicatesLockfile).toContain('"shared-dep@3.0.0"');
|
|
// Aliased package
|
|
expect(duplicatesLockfile).toContain('"my-lodash": "npm:lodash@^4.17.20"');
|
|
expect(duplicatesLockfile).toContain('"lodash": "^4.17.21"');
|
|
expect(duplicatesLockfile).toMatchSnapshot("duplicate-packages");
|
|
|
|
// ===== SECTION 10: Catalogs =====
|
|
const catalogsTest = tempDirWithFiles("pnpm-catalogs", {
|
|
"pnpm-workspace.yaml": `packages: []
|
|
|
|
catalog:
|
|
react: 18.2.0
|
|
react-dom: 18.2.0
|
|
|
|
catalogs:
|
|
tools:
|
|
lodash: 4.17.21
|
|
eslint: 8.56.0`,
|
|
|
|
"package.json": JSON.stringify({
|
|
name: "catalogs-test",
|
|
dependencies: {
|
|
"react": "catalog:",
|
|
"lodash": "catalog:tools",
|
|
},
|
|
}),
|
|
"pnpm-lock.yaml": `lockfileVersion: '9.0'
|
|
|
|
settings:
|
|
autoInstallPeers: true
|
|
excludeLinksFromLockfile: false
|
|
|
|
catalogs:
|
|
default:
|
|
react:
|
|
specifier: 18.2.0
|
|
version: 18.2.0
|
|
tools:
|
|
lodash:
|
|
specifier: 4.17.21
|
|
version: 4.17.21
|
|
|
|
importers:
|
|
|
|
.:
|
|
dependencies:
|
|
lodash:
|
|
specifier: catalog:tools
|
|
version: 4.17.21
|
|
react:
|
|
specifier: 'catalog:'
|
|
version: 18.2.0
|
|
|
|
packages:
|
|
|
|
js-tokens@4.0.0:
|
|
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
|
|
|
lodash@4.17.21:
|
|
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
|
|
|
loose-envify@1.4.0:
|
|
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
|
hasBin: true
|
|
|
|
react@18.2.0:
|
|
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
|
|
engines: {node: '>=0.10.0'}
|
|
|
|
snapshots:
|
|
|
|
js-tokens@4.0.0: {}
|
|
|
|
lodash@4.17.21: {}
|
|
|
|
loose-envify@1.4.0:
|
|
dependencies:
|
|
js-tokens: 4.0.0
|
|
|
|
react@18.2.0:
|
|
dependencies:
|
|
loose-envify: 1.4.0
|
|
`,
|
|
});
|
|
|
|
const catalogsProc = Bun.spawn({
|
|
cmd: [bunExe(), "pm", "migrate"],
|
|
cwd: catalogsTest,
|
|
env: bunEnv,
|
|
stderr: "pipe",
|
|
});
|
|
|
|
const [catalogsStderr, catalogsExitCode] = await Promise.all([catalogsProc.stderr.text(), catalogsProc.exited]);
|
|
|
|
expect(catalogsExitCode).toBe(0);
|
|
const catalogsLockfile = fs.readFileSync(join(catalogsTest, "bun.lock"), "utf8");
|
|
|
|
// Catalogs are resolved to actual versions during migration
|
|
expect(catalogsLockfile).toContain('"react": "18.2.0"');
|
|
expect(catalogsLockfile).toContain('"lodash": "4.17.21"');
|
|
// The actual packages should be in the lockfile
|
|
expect(catalogsLockfile).toContain('"react@18.2.0"');
|
|
expect(catalogsLockfile).toContain('"lodash@4.17.21"');
|
|
expect(catalogsLockfile).toMatchSnapshot("catalogs");
|
|
|
|
// ===== SECTION 12: Integrity Hashes =====
|
|
const integrityTest = tempDirWithFiles("pnpm-integrity", {
|
|
"package.json": JSON.stringify({
|
|
name: "integrity-test",
|
|
dependencies: {
|
|
"express": "^4.18.2",
|
|
"axios": "^1.6.0",
|
|
},
|
|
}),
|
|
"pnpm-lock.yaml": `lockfileVersion: '9.0'
|
|
|
|
importers:
|
|
.:
|
|
dependencies:
|
|
express:
|
|
specifier: ^4.18.2
|
|
version: 4.18.2
|
|
axios:
|
|
specifier: ^1.6.0
|
|
version: 1.6.7
|
|
|
|
packages:
|
|
express@4.18.2:
|
|
resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==}
|
|
engines: {node: '>= 0.10.0'}
|
|
|
|
axios@1.6.7:
|
|
resolution: {integrity: sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==}
|
|
|
|
snapshots:
|
|
express@4.18.2: {}
|
|
axios@1.6.7: {}`,
|
|
});
|
|
|
|
const integrityProc = Bun.spawn({
|
|
cmd: [bunExe(), "pm", "migrate"],
|
|
cwd: integrityTest,
|
|
env: bunEnv,
|
|
stderr: "pipe",
|
|
});
|
|
|
|
const [integrityStderr, integrityExitCode] = await Promise.all([integrityProc.stderr.text(), integrityProc.exited]);
|
|
|
|
expect(integrityExitCode).toBe(0);
|
|
const integrityLockfile = fs.readFileSync(join(integrityTest, "bun.lock"), "utf8");
|
|
|
|
// Check integrity hashes are preserved
|
|
expect(integrityLockfile).toContain(
|
|
"sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
|
|
);
|
|
expect(integrityLockfile).toContain(
|
|
"sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==",
|
|
);
|
|
expect(integrityLockfile).toMatchSnapshot("integrity-hashes");
|
|
|
|
// ===== SECTION 13: Version Zero Bug Test =====
|
|
const versionZeroTest = tempDirWithFiles("pnpm-version-zero", {
|
|
"package.json": JSON.stringify({
|
|
name: "version-zero-test",
|
|
dependencies: {
|
|
"package-with-zero": "0.0.0",
|
|
},
|
|
}),
|
|
"pnpm-lock.yaml": `lockfileVersion: '9.0'
|
|
|
|
importers:
|
|
.:
|
|
dependencies:
|
|
package-with-zero:
|
|
specifier: 0.0.0
|
|
version: 0.0.0
|
|
|
|
packages:
|
|
package-with-zero@0.0.0:
|
|
resolution: {integrity: sha512-zero==}
|
|
|
|
snapshots:
|
|
package-with-zero@0.0.0: {}`,
|
|
});
|
|
|
|
const versionZeroProc = Bun.spawn({
|
|
cmd: [bunExe(), "pm", "migrate"],
|
|
cwd: versionZeroTest,
|
|
env: bunEnv,
|
|
stderr: "pipe",
|
|
});
|
|
|
|
const [versionZeroStderr, versionZeroExitCode] = await Promise.all([
|
|
versionZeroProc.stderr.text(),
|
|
versionZeroProc.exited,
|
|
]);
|
|
|
|
expect(versionZeroExitCode).toBe(0);
|
|
const versionZeroLockfile = fs.readFileSync(join(versionZeroTest, "bun.lock"), "utf8");
|
|
|
|
expect(versionZeroLockfile).toContain('"package-with-zero": "0.0.0"');
|
|
expect(versionZeroLockfile).toContain('"package-with-zero@0.0.0"');
|
|
expect(versionZeroLockfile).toMatchSnapshot("version-zero");
|
|
|
|
// ===== SECTION 14: Mixed Dependency Types =====
|
|
const mixedDepsTest = tempDirWithFiles("pnpm-mixed-deps", {
|
|
"package.json": JSON.stringify({
|
|
name: "mixed-deps-test",
|
|
dependencies: {
|
|
"react": "^18.2.0",
|
|
"typescript": "^4.0.0",
|
|
},
|
|
devDependencies: {
|
|
"typescript": "^5.3.3",
|
|
"eslint": "^8.56.0",
|
|
},
|
|
optionalDependencies: {
|
|
"fsevents": "^2.3.3",
|
|
},
|
|
peerDependencies: {
|
|
"react": ">=16.0.0",
|
|
},
|
|
}),
|
|
"pnpm-lock.yaml": `lockfileVersion: '9.0'
|
|
|
|
importers:
|
|
.:
|
|
dependencies:
|
|
react:
|
|
specifier: ^18.2.0
|
|
version: 18.2.0
|
|
typescript:
|
|
specifier: ^4.0.0
|
|
version: 4.9.5
|
|
devDependencies:
|
|
typescript:
|
|
specifier: ^5.3.3
|
|
version: 5.3.3
|
|
eslint:
|
|
specifier: ^8.56.0
|
|
version: 8.56.0
|
|
optionalDependencies:
|
|
fsevents:
|
|
specifier: ^2.3.3
|
|
version: 2.3.3
|
|
|
|
packages:
|
|
react@18.2.0:
|
|
resolution: {integrity: sha512-react==}
|
|
|
|
typescript@4.9.5:
|
|
resolution: {integrity: sha512-ts4==}
|
|
|
|
typescript@5.3.3:
|
|
resolution: {integrity: sha512-ts5==}
|
|
|
|
eslint@8.56.0:
|
|
resolution: {integrity: sha512-eslint==}
|
|
|
|
fsevents@2.3.3:
|
|
resolution: {integrity: sha512-fsevents==}
|
|
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
|
os: [darwin]
|
|
requiresBuild: true
|
|
optional: true
|
|
|
|
snapshots:
|
|
react@18.2.0: {}
|
|
typescript@4.9.5: {}
|
|
typescript@5.3.3: {}
|
|
eslint@8.56.0: {}
|
|
fsevents@2.3.3:
|
|
optional: true`,
|
|
});
|
|
|
|
const mixedDepsProc = Bun.spawn({
|
|
cmd: [bunExe(), "pm", "migrate"],
|
|
cwd: mixedDepsTest,
|
|
env: bunEnv,
|
|
stderr: "pipe",
|
|
});
|
|
|
|
const [mixedDepsStderr, mixedDepsExitCode] = await Promise.all([mixedDepsProc.stderr.text(), mixedDepsProc.exited]);
|
|
|
|
expect(mixedDepsExitCode).toBe(0);
|
|
const mixedDepsLockfile = fs.readFileSync(join(mixedDepsTest, "bun.lock"), "utf8");
|
|
|
|
// Dependencies version should win
|
|
expect(mixedDepsLockfile).toContain('"typescript": "^4.0.0"');
|
|
// But devDeps-only packages should be there
|
|
expect(mixedDepsLockfile).toContain('"eslint": "^8.56.0"');
|
|
expect(mixedDepsLockfile).toContain('"fsevents"');
|
|
expect(mixedDepsLockfile).toMatchSnapshot("mixed-dependency-types");
|
|
|
|
// ===== SECTION 15: Circular Workspace Dependencies =====
|
|
const circularTest = tempDirWithFiles("pnpm-circular", {
|
|
"package.json": JSON.stringify({
|
|
name: "circular-test",
|
|
workspaces: ["packages/*"],
|
|
dependencies: {
|
|
"@workspace/pkg1": "workspace:*",
|
|
},
|
|
}),
|
|
"packages/pkg1/package.json": JSON.stringify({
|
|
"name": "@workspace/pkg1",
|
|
"version": "1.0.0",
|
|
"main": "index.js",
|
|
"dependencies": {
|
|
"@workspace/pkg2": "workspace:*",
|
|
"lodash": "^4.17.21",
|
|
},
|
|
}),
|
|
"packages/pkg2/package.json": JSON.stringify({
|
|
"name": "@workspace/pkg2",
|
|
"version": "1.0.0",
|
|
"main": "index.js",
|
|
"dependencies": {
|
|
"@workspace/pkg3": "workspace:*",
|
|
},
|
|
}),
|
|
"packages/pkg3/package.json": JSON.stringify({
|
|
"name": "@workspace/pkg3",
|
|
"version": "1.0.0",
|
|
"main": "index.js",
|
|
"dependencies": {
|
|
"@workspace/pkg1": "workspace:*",
|
|
},
|
|
}),
|
|
"pnpm-lock.yaml": `lockfileVersion: '9.0'
|
|
|
|
importers:
|
|
.:
|
|
dependencies:
|
|
'@workspace/pkg1':
|
|
specifier: workspace:*
|
|
version: link:packages/pkg1
|
|
|
|
packages/pkg1:
|
|
dependencies:
|
|
'@workspace/pkg2':
|
|
specifier: workspace:*
|
|
version: link:../pkg2
|
|
lodash:
|
|
specifier: ^4.17.21
|
|
version: 4.17.21
|
|
|
|
packages/pkg2:
|
|
dependencies:
|
|
'@workspace/pkg3':
|
|
specifier: workspace:*
|
|
version: link:../pkg3
|
|
|
|
packages/pkg3:
|
|
dependencies:
|
|
'@workspace/pkg1':
|
|
specifier: workspace:*
|
|
version: link:../pkg1
|
|
|
|
packages:
|
|
lodash@4.17.21:
|
|
resolution: {integrity: sha512-lodash==}
|
|
|
|
snapshots:
|
|
lodash@4.17.21: {}`,
|
|
});
|
|
|
|
const circularProc = Bun.spawn({
|
|
cmd: [bunExe(), "pm", "migrate"],
|
|
cwd: circularTest,
|
|
env: bunEnv,
|
|
stderr: "pipe",
|
|
});
|
|
|
|
const [circularStderr, circularExitCode] = await Promise.all([circularProc.stderr.text(), circularProc.exited]);
|
|
|
|
expect(circularExitCode).toBe(0);
|
|
const circularLockfile = fs.readFileSync(join(circularTest, "bun.lock"), "utf8");
|
|
|
|
// All workspaces should be created despite circular dependencies
|
|
expect(circularLockfile).toContain('"packages/pkg1"');
|
|
expect(circularLockfile).toContain('"packages/pkg2"');
|
|
expect(circularLockfile).toContain('"packages/pkg3"');
|
|
expect(circularLockfile).toContain('"@workspace/pkg1": "workspace:*"');
|
|
expect(circularLockfile).toMatchSnapshot("circular-workspaces");
|
|
});
|
|
});
|