mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
### What does this PR do? fixes #23901 ### How did you verify your code works? with a test --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1998 lines
60 KiB
TypeScript
1998 lines
60 KiB
TypeScript
import { afterAll, beforeAll, describe, expect, it } from "bun:test";
|
|
import { bunEnv, bunExe, tempDirWithFiles, VerdaccioRegistry } from "harness";
|
|
import { join } from "path";
|
|
|
|
let registry: VerdaccioRegistry;
|
|
let registryUrl: string;
|
|
|
|
beforeAll(async () => {
|
|
registry = new VerdaccioRegistry();
|
|
registryUrl = registry.registryUrl();
|
|
await registry.start();
|
|
});
|
|
|
|
afterAll(() => {
|
|
registry.stop();
|
|
});
|
|
|
|
describe("bun update --interactive", () => {
|
|
it("should handle package names of unusual lengths", async () => {
|
|
const dir = tempDirWithFiles("update-interactive-test", {
|
|
"package.json": JSON.stringify({
|
|
name: "test-project",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"a": "1.0.0",
|
|
"really-long-package-name-that-causes-formatting-issues": "1.0.0",
|
|
"@org/extremely-long-scoped-package-name-that-will-test-formatting": "1.0.0",
|
|
"short": "1.0.0",
|
|
"another-package-with-a-very-long-name-to-test-column-alignment": "1.0.0",
|
|
},
|
|
devDependencies: {
|
|
"dev-package": "1.0.0",
|
|
"super-long-dev-package-name-that-should-not-break-formatting": "1.0.0",
|
|
},
|
|
peerDependencies: {
|
|
"peer-package": "1.0.0",
|
|
"extremely-long-peer-dependency-name-for-testing-column-alignment": "1.0.0",
|
|
},
|
|
optionalDependencies: {
|
|
"optional-package": "1.0.0",
|
|
"very-long-optional-dependency-name-that-tests-formatting": "1.0.0",
|
|
},
|
|
}),
|
|
"bun.lockb": JSON.stringify({
|
|
"lockfileVersion": 3,
|
|
"packages": {
|
|
"a": {
|
|
"integrity": "sha512-fake",
|
|
"version": "1.0.0",
|
|
},
|
|
"really-long-package-name-that-causes-formatting-issues": {
|
|
"integrity": "sha512-fake",
|
|
"version": "1.0.0",
|
|
},
|
|
"@org/extremely-long-scoped-package-name-that-will-test-formatting": {
|
|
"integrity": "sha512-fake",
|
|
"version": "1.0.0",
|
|
},
|
|
"short": {
|
|
"integrity": "sha512-fake",
|
|
"version": "1.0.0",
|
|
},
|
|
"another-package-with-a-very-long-name-to-test-column-alignment": {
|
|
"integrity": "sha512-fake",
|
|
"version": "1.0.0",
|
|
},
|
|
"dev-package": {
|
|
"integrity": "sha512-fake",
|
|
"version": "1.0.0",
|
|
},
|
|
"super-long-dev-package-name-that-should-not-break-formatting": {
|
|
"integrity": "sha512-fake",
|
|
"version": "1.0.0",
|
|
},
|
|
"peer-package": {
|
|
"integrity": "sha512-fake",
|
|
"version": "1.0.0",
|
|
},
|
|
"extremely-long-peer-dependency-name-for-testing-column-alignment": {
|
|
"integrity": "sha512-fake",
|
|
"version": "1.0.0",
|
|
},
|
|
"optional-package": {
|
|
"integrity": "sha512-fake",
|
|
"version": "1.0.0",
|
|
},
|
|
"very-long-optional-dependency-name-that-tests-formatting": {
|
|
"integrity": "sha512-fake",
|
|
"version": "1.0.0",
|
|
},
|
|
},
|
|
}),
|
|
});
|
|
|
|
// Mock outdated packages by creating fake manifests
|
|
const manifestsDir = join(dir, ".bun", "manifests");
|
|
await Bun.write(
|
|
join(manifestsDir, "a.json"),
|
|
JSON.stringify({
|
|
name: "a",
|
|
"dist-tags": { latest: "2.0.0" },
|
|
versions: {
|
|
"1.0.0": { version: "1.0.0" },
|
|
"2.0.0": { version: "2.0.0" },
|
|
},
|
|
}),
|
|
);
|
|
|
|
// Test that the command doesn't crash with unusual package name lengths
|
|
const result = await Bun.spawn({
|
|
cmd: [bunExe(), "update", "--interactive", "--dry-run"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdin: "inherit",
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
|
|
const stdout = await new Response(result.stdout).text();
|
|
const stderr = await new Response(result.stderr).text();
|
|
|
|
// The command might fail due to missing manifests, but it shouldn't crash
|
|
// due to formatting issues
|
|
expect(stderr).not.toContain("panic");
|
|
expect(stderr).not.toContain("segfault");
|
|
expect(stderr).not.toContain("underflow");
|
|
expect(stderr).not.toContain("overflow");
|
|
});
|
|
|
|
it("should handle version strings of unusual lengths", async () => {
|
|
const dir = tempDirWithFiles("update-interactive-versions-test", {
|
|
"package.json": JSON.stringify({
|
|
name: "test-project",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"package-with-long-version": "1.0.0-alpha.1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20",
|
|
"package-with-short-version": "1.0.0",
|
|
"package-with-prerelease": "1.0.0-beta.1+build.1234567890.abcdef",
|
|
},
|
|
}),
|
|
"bun.lockb": JSON.stringify({
|
|
"lockfileVersion": 3,
|
|
"packages": {
|
|
"package-with-long-version": {
|
|
"integrity": "sha512-fake",
|
|
"version": "1.0.0-alpha.1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20",
|
|
},
|
|
"package-with-short-version": {
|
|
"integrity": "sha512-fake",
|
|
"version": "1.0.0",
|
|
},
|
|
"package-with-prerelease": {
|
|
"integrity": "sha512-fake",
|
|
"version": "1.0.0-beta.1+build.1234567890.abcdef",
|
|
},
|
|
},
|
|
}),
|
|
});
|
|
|
|
// Test that the command doesn't crash with unusual version string lengths
|
|
const result = await Bun.spawn({
|
|
cmd: [bunExe(), "update", "--interactive", "--dry-run"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdin: "inherit",
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
|
|
const stdout = await new Response(result.stdout).text();
|
|
const stderr = await new Response(result.stderr).text();
|
|
|
|
// The command might fail due to missing manifests, but it shouldn't crash
|
|
// due to formatting issues
|
|
expect(stderr).not.toContain("panic");
|
|
expect(stderr).not.toContain("segfault");
|
|
expect(stderr).not.toContain("underflow");
|
|
expect(stderr).not.toContain("overflow");
|
|
});
|
|
|
|
it("should truncate extremely long package names", async () => {
|
|
const extremelyLongPackageName = "a".repeat(100);
|
|
const dir = tempDirWithFiles("update-interactive-truncate-test", {
|
|
"package.json": JSON.stringify({
|
|
name: "test-project",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
[extremelyLongPackageName]: "1.0.0",
|
|
},
|
|
}),
|
|
"bun.lockb": JSON.stringify({
|
|
"lockfileVersion": 3,
|
|
"packages": {
|
|
[extremelyLongPackageName]: {
|
|
"integrity": "sha512-fake",
|
|
"version": "1.0.0",
|
|
},
|
|
},
|
|
}),
|
|
});
|
|
|
|
// Test that extremely long package names are handled gracefully
|
|
const result = await Bun.spawn({
|
|
cmd: [bunExe(), "update", "--interactive", "--dry-run"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdin: "inherit",
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
|
|
const stdout = await new Response(result.stdout).text();
|
|
const stderr = await new Response(result.stderr).text();
|
|
|
|
// The command might fail due to missing manifests, but it shouldn't crash
|
|
// due to formatting issues
|
|
expect(stderr).not.toContain("panic");
|
|
expect(stderr).not.toContain("segfault");
|
|
expect(stderr).not.toContain("underflow");
|
|
expect(stderr).not.toContain("overflow");
|
|
});
|
|
|
|
it("should show workspace column with --filter", async () => {
|
|
const dir = tempDirWithFiles("update-interactive-workspace-col-test", {
|
|
"package.json": JSON.stringify({
|
|
name: "root",
|
|
version: "1.0.0",
|
|
workspaces: ["packages/*"],
|
|
}),
|
|
"packages/pkg1/package.json": JSON.stringify({
|
|
name: "pkg1",
|
|
dependencies: {
|
|
"dep1": "1.0.0",
|
|
},
|
|
}),
|
|
"packages/pkg2/package.json": JSON.stringify({
|
|
name: "pkg2",
|
|
dependencies: {
|
|
"dep2": "1.0.0",
|
|
},
|
|
}),
|
|
});
|
|
|
|
// Test with --filter should include workspace column
|
|
const result = await Bun.spawn({
|
|
cmd: [bunExe(), "update", "--interactive", "--filter=*", "--dry-run"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdin: "inherit",
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
|
|
const stderr = await new Response(result.stderr).text();
|
|
|
|
// Should not crash with workspace column
|
|
expect(stderr).not.toContain("panic");
|
|
expect(stderr).not.toContain("segfault");
|
|
});
|
|
|
|
it("should handle catalog dependencies in interactive update", async () => {
|
|
const dir = tempDirWithFiles("update-interactive-catalog-test", {
|
|
"package.json": JSON.stringify({
|
|
name: "root",
|
|
version: "1.0.0",
|
|
catalog: {
|
|
"shared-dep": "1.0.0",
|
|
},
|
|
workspaces: ["packages/*"],
|
|
}),
|
|
"packages/pkg1/package.json": JSON.stringify({
|
|
name: "pkg1",
|
|
dependencies: {
|
|
"shared-dep": "catalog:",
|
|
},
|
|
}),
|
|
"packages/pkg2/package.json": JSON.stringify({
|
|
name: "pkg2",
|
|
dependencies: {
|
|
"shared-dep": "catalog:",
|
|
},
|
|
}),
|
|
});
|
|
|
|
// Test interactive update with catalog dependencies
|
|
const result = await Bun.spawn({
|
|
cmd: [bunExe(), "update", "--interactive", "--filter=*", "--dry-run"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdin: "inherit",
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
|
|
const stderr = await new Response(result.stderr).text();
|
|
|
|
// Should not crash with catalog dependencies
|
|
expect(stderr).not.toContain("panic");
|
|
expect(stderr).not.toContain("segfault");
|
|
expect(stderr).not.toContain("catalog: failed to resolve");
|
|
});
|
|
|
|
it("should handle mixed dependency types with various name lengths", async () => {
|
|
const dir = tempDirWithFiles("update-interactive-mixed-test", {
|
|
"package.json": JSON.stringify({
|
|
name: "test-project",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"a": "1.0.0",
|
|
"really-long-dependency-name": "1.0.0",
|
|
},
|
|
devDependencies: {
|
|
"b": "1.0.0",
|
|
"super-long-dev-dependency-name": "1.0.0",
|
|
},
|
|
peerDependencies: {
|
|
"c": "1.0.0",
|
|
"extremely-long-peer-dependency-name": "1.0.0",
|
|
},
|
|
optionalDependencies: {
|
|
"d": "1.0.0",
|
|
"very-long-optional-dependency-name": "1.0.0",
|
|
},
|
|
}),
|
|
"bun.lockb": JSON.stringify({
|
|
"lockfileVersion": 3,
|
|
"packages": {
|
|
"a": { "integrity": "sha512-fake", "version": "1.0.0" },
|
|
"really-long-dependency-name": { "integrity": "sha512-fake", "version": "1.0.0" },
|
|
"b": { "integrity": "sha512-fake", "version": "1.0.0" },
|
|
"super-long-dev-dependency-name": { "integrity": "sha512-fake", "version": "1.0.0" },
|
|
"c": { "integrity": "sha512-fake", "version": "1.0.0" },
|
|
"extremely-long-peer-dependency-name": { "integrity": "sha512-fake", "version": "1.0.0" },
|
|
"d": { "integrity": "sha512-fake", "version": "1.0.0" },
|
|
"very-long-optional-dependency-name": { "integrity": "sha512-fake", "version": "1.0.0" },
|
|
},
|
|
}),
|
|
});
|
|
|
|
// Test that mixed dependency types with various name lengths don't cause crashes
|
|
const result = await Bun.spawn({
|
|
cmd: [bunExe(), "update", "--interactive", "--dry-run"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdin: "inherit",
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
|
|
const stdout = await new Response(result.stdout).text();
|
|
const stderr = await new Response(result.stderr).text();
|
|
|
|
// The command might fail due to missing manifests, but it shouldn't crash
|
|
// due to formatting issues
|
|
expect(stderr).not.toContain("panic");
|
|
expect(stderr).not.toContain("segfault");
|
|
expect(stderr).not.toContain("underflow");
|
|
expect(stderr).not.toContain("overflow");
|
|
});
|
|
|
|
it("should update packages when 'a' (select all) is used", async () => {
|
|
const dir = tempDirWithFiles("update-interactive-select-all", {
|
|
"bunfig.toml": `[install]
|
|
cache = false
|
|
registry = "${registryUrl}"
|
|
`,
|
|
"package.json": JSON.stringify({
|
|
name: "test-project",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"no-deps": "1.0.0", // Old version
|
|
},
|
|
}),
|
|
});
|
|
|
|
// First install to get lockfile
|
|
const install = Bun.spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
expect(await install.exited).toBe(0);
|
|
|
|
// Test interactive update with 'a' to select all
|
|
const update = Bun.spawn({
|
|
cmd: [bunExe(), "update", "-i", "--latest"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdin: "pipe",
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
|
|
// Send 'a' to select all, then Enter to confirm
|
|
update.stdin.write("a\n");
|
|
update.stdin.end();
|
|
|
|
const exitCode = await update.exited;
|
|
const stdout = await new Response(update.stdout).text();
|
|
const stderr = await new Response(update.stderr).text();
|
|
const output = stdout + stderr;
|
|
|
|
if (exitCode !== 0) {
|
|
console.error("Update failed with exit code:", exitCode);
|
|
console.error("Stdout:", stdout);
|
|
console.error("Stderr:", stderr);
|
|
}
|
|
|
|
// Should complete successfully
|
|
expect(exitCode).toBe(0);
|
|
expect(stderr).not.toContain("panic");
|
|
|
|
// Check if package.json was updated
|
|
const packageJson = await Bun.file(join(dir, "package.json")).json();
|
|
// no-deps should be updated from 1.0.0 to 2.0.0
|
|
expect(packageJson.dependencies["no-deps"]).toBe("2.0.0");
|
|
|
|
// Check that the output shows the package was installed/updated
|
|
expect(output).toContain("Installing updates...");
|
|
|
|
// todo: Should show the installed package in the summary
|
|
// expect(output).toContain("installed no-deps@");
|
|
|
|
// Should save the lockfile
|
|
expect(output).toContain("Saved lockfile");
|
|
});
|
|
|
|
it("should handle workspace updates with recursive flag", async () => {
|
|
const dir = tempDirWithFiles("update-interactive-workspace-recursive", {
|
|
"bunfig.toml": `[install]
|
|
cache = false
|
|
registry = "${registryUrl}"
|
|
`,
|
|
"package.json": JSON.stringify({
|
|
name: "root",
|
|
version: "1.0.0",
|
|
private: true,
|
|
workspaces: ["packages/*"],
|
|
}),
|
|
"packages/app/package.json": JSON.stringify({
|
|
name: "@test/app",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"no-deps": "1.0.0", // Old version in workspace
|
|
},
|
|
}),
|
|
});
|
|
|
|
// First install
|
|
const install = Bun.spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
expect(await install.exited).toBe(0);
|
|
|
|
// Test interactive update with recursive flag
|
|
const update = Bun.spawn({
|
|
cmd: [bunExe(), "update", "-i", "-r", "--latest"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdin: "pipe",
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
|
|
// Select all packages
|
|
update.stdin.write("a\n");
|
|
update.stdin.end();
|
|
|
|
const exitCode = await update.exited;
|
|
const stderr = await new Response(update.stderr).text();
|
|
|
|
expect(exitCode).toBe(0);
|
|
expect(stderr).not.toContain("panic");
|
|
|
|
// Check if workspace package was updated
|
|
const appPackageJson = await Bun.file(join(dir, "packages/app/package.json")).json();
|
|
expect(appPackageJson.dependencies["no-deps"]).toBe("2.0.0");
|
|
});
|
|
|
|
it("should handle catalog updates correctly", async () => {
|
|
const dir = tempDirWithFiles("update-interactive-catalog-actual", {
|
|
"bunfig.toml": `[install]
|
|
cache = false
|
|
registry = "${registryUrl}"
|
|
`,
|
|
"package.json": JSON.stringify({
|
|
name: "root",
|
|
version: "1.0.0",
|
|
workspaces: ["packages/*"],
|
|
catalog: {
|
|
"no-deps": "1.0.0", // Old version in catalog
|
|
},
|
|
}),
|
|
"packages/app/package.json": JSON.stringify({
|
|
name: "@test/app",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"no-deps": "catalog:",
|
|
},
|
|
}),
|
|
});
|
|
|
|
// First install
|
|
await using install = Bun.spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
expect(await install.exited).toBe(0);
|
|
|
|
// Run interactive update with piped input
|
|
await using update = Bun.spawn({
|
|
cmd: [bunExe(), "update", "-i", "-r", "--latest"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdin: "pipe",
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
|
|
// Send 'a' to select all, then newline to confirm
|
|
update.stdin.write("a\n");
|
|
update.stdin.end();
|
|
|
|
const exitCode = await update.exited;
|
|
const stdout = await new Response(update.stdout).text();
|
|
const stderr = await new Response(update.stderr).text();
|
|
|
|
expect(exitCode).toBe(0);
|
|
expect(stdout + stderr).not.toContain("panic");
|
|
expect(stdout + stderr).not.toContain("catalog: failed to resolve");
|
|
|
|
// Check if catalog was updated in root package.json
|
|
const rootPackageJson = await Bun.file(join(dir, "package.json")).json();
|
|
expect(rootPackageJson.catalog["no-deps"]).toBe("2.0.0");
|
|
|
|
// App package.json should still have catalog reference
|
|
const appPackageJson = await Bun.file(join(dir, "packages/app/package.json")).json();
|
|
expect(appPackageJson.dependencies["no-deps"]).toBe("catalog:");
|
|
});
|
|
|
|
it("should work correctly when run from inside a workspace directory", async () => {
|
|
const dir = tempDirWithFiles("update-interactive-from-workspace", {
|
|
"bunfig.toml": `[install]
|
|
cache = false
|
|
registry = "${registryUrl}"
|
|
`,
|
|
"package.json": JSON.stringify({
|
|
name: "root",
|
|
version: "1.0.0",
|
|
workspaces: ["packages/*"],
|
|
}),
|
|
"packages/app1/package.json": JSON.stringify({
|
|
name: "@test/app1",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"no-deps": "1.0.0",
|
|
},
|
|
}),
|
|
"packages/app2/package.json": JSON.stringify({
|
|
name: "@test/app2",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"dep-with-tags": "1.0.0",
|
|
},
|
|
}),
|
|
});
|
|
|
|
// First install from root
|
|
await using install = Bun.spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
expect(await install.exited).toBe(0);
|
|
|
|
// Run interactive update from inside workspace
|
|
const workspaceDir = join(dir, "packages/app1");
|
|
await using update = Bun.spawn({
|
|
cmd: [bunExe(), "update", "-i", "-r", "--latest"],
|
|
cwd: workspaceDir,
|
|
env: bunEnv,
|
|
stdin: "pipe",
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
|
|
// Send 'a' to select all, then newline to confirm
|
|
update.stdin.write("a\n");
|
|
update.stdin.end();
|
|
|
|
const exitCode = await update.exited;
|
|
const output = await new Response(update.stdout).text();
|
|
const stderr = await new Response(update.stderr).text();
|
|
const combined = output + stderr;
|
|
|
|
// Should not fail with FileNotFound
|
|
expect(exitCode).toBe(0);
|
|
expect(combined).not.toContain("FileNotFound");
|
|
expect(combined).not.toContain("Failed to update");
|
|
|
|
// Check that both workspace packages were updated
|
|
const app1Json = await Bun.file(join(dir, "packages/app1/package.json")).json();
|
|
const app2Json = await Bun.file(join(dir, "packages/app2/package.json")).json();
|
|
|
|
expect(app1Json.dependencies["no-deps"]).toBe("2.0.0");
|
|
expect(app2Json.dependencies["dep-with-tags"]).toBe("3.0.0");
|
|
});
|
|
|
|
it("should handle basic interactive update with select all", async () => {
|
|
const dir = tempDirWithFiles("update-interactive-basic", {
|
|
"bunfig.toml": `[install]
|
|
cache = false
|
|
registry = "${registryUrl}"
|
|
`,
|
|
"package.json": JSON.stringify({
|
|
name: "test-project",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"no-deps": "1.0.0",
|
|
},
|
|
}),
|
|
});
|
|
|
|
// Install first
|
|
await using install = Bun.spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
expect(await install.exited).toBe(0);
|
|
|
|
// Run interactive update with piped input
|
|
await using update = Bun.spawn({
|
|
cmd: [bunExe(), "update", "-i", "--latest"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdin: "pipe",
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
|
|
// Send 'a' to select all, then newline to confirm
|
|
update.stdin.write("a\n");
|
|
update.stdin.end();
|
|
|
|
const exitCode = await update.exited;
|
|
expect(exitCode).toBe(0);
|
|
|
|
// Check if package was updated
|
|
const packageJson = await Bun.file(join(dir, "package.json")).json();
|
|
expect(packageJson.dependencies["no-deps"]).toBe("2.0.0");
|
|
});
|
|
|
|
it("should preserve version prefixes for all semver range types in catalogs", async () => {
|
|
const dir = tempDirWithFiles("update-interactive-semver-prefixes", {
|
|
"bunfig.toml": `[install]
|
|
cache = false
|
|
registry = "${registryUrl}"
|
|
`,
|
|
"package.json": JSON.stringify({
|
|
name: "root",
|
|
version: "1.0.0",
|
|
workspaces: ["packages/*"],
|
|
catalog: {
|
|
"no-deps": "^1.0.0",
|
|
"dep-with-tags": "~1.0.0",
|
|
"a-dep": ">=1.0.5",
|
|
},
|
|
}),
|
|
"packages/app/package.json": JSON.stringify({
|
|
name: "@test/app",
|
|
dependencies: {
|
|
"no-deps": "catalog:",
|
|
"dep-with-tags": "catalog:",
|
|
"a-dep": "catalog:",
|
|
},
|
|
}),
|
|
});
|
|
|
|
// Install first
|
|
await using install = Bun.spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
expect(await install.exited).toBe(0);
|
|
|
|
// Run interactive update with piped input
|
|
await using update = Bun.spawn({
|
|
cmd: [bunExe(), "update", "-i", "-r", "--latest"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdin: "pipe",
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
|
|
// Send 'a' to select all, then newline to confirm
|
|
update.stdin.write("a\n");
|
|
update.stdin.end();
|
|
|
|
const exitCode = await update.exited;
|
|
expect(exitCode).toBe(0);
|
|
|
|
// Check if prefixes were preserved
|
|
const packageJson = await Bun.file(join(dir, "package.json")).json();
|
|
|
|
// All prefixes should be preserved (versions may or may not change)
|
|
expect(packageJson.catalog["no-deps"]).toMatch(/^\^/);
|
|
expect(packageJson.catalog["dep-with-tags"]).toMatch(/^~/);
|
|
expect(packageJson.catalog["a-dep"]).toMatch(/^>=/);
|
|
});
|
|
|
|
it("should handle catalog updates in workspaces.catalogs object", async () => {
|
|
const dir = tempDirWithFiles("update-interactive-workspaces-catalogs", {
|
|
"bunfig.toml": `[install]
|
|
cache = false
|
|
registry = "${registryUrl}"
|
|
`,
|
|
"package.json": JSON.stringify({
|
|
name: "root",
|
|
version: "1.0.0",
|
|
workspaces: {
|
|
packages: ["packages/*"],
|
|
catalogs: {
|
|
"tools": {
|
|
"no-deps": "^1.0.0",
|
|
"dep-with-tags": "~1.0.0",
|
|
},
|
|
"frameworks": {
|
|
"a-dep": "^1.0.5",
|
|
"normal-dep-and-dev-dep": "^1.0.0",
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
"packages/app/package.json": JSON.stringify({
|
|
name: "@test/app",
|
|
dependencies: {
|
|
"no-deps": "catalog:tools",
|
|
"a-dep": "catalog:frameworks",
|
|
},
|
|
}),
|
|
});
|
|
|
|
// Install first
|
|
await using install = Bun.spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
expect(await install.exited).toBe(0);
|
|
|
|
// Run interactive update with piped input
|
|
await using update = Bun.spawn({
|
|
cmd: [bunExe(), "update", "-i", "-r", "--latest"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdin: "pipe",
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
|
|
// Send 'a' to select all, then newline to confirm
|
|
update.stdin.write("a\n");
|
|
update.stdin.end();
|
|
|
|
const exitCode = await update.exited;
|
|
const output = await new Response(update.stdout).text();
|
|
|
|
expect(exitCode).toBe(0);
|
|
expect(output).toContain("Installing updates...");
|
|
|
|
// Check if catalogs were updated correctly
|
|
const packageJson = await Bun.file(join(dir, "package.json")).json();
|
|
|
|
// Prefixes should be preserved
|
|
expect(packageJson.workspaces.catalogs.tools["no-deps"]).toMatch(/^\^/);
|
|
expect(packageJson.workspaces.catalogs.tools["dep-with-tags"]).toMatch(/^~/);
|
|
});
|
|
|
|
it("should handle mixed workspace and catalog dependencies", async () => {
|
|
const dir = tempDirWithFiles("update-interactive-mixed-deps", {
|
|
"bunfig.toml": `[install]
|
|
cache = false
|
|
registry = "${registryUrl}"
|
|
`,
|
|
"package.json": JSON.stringify({
|
|
name: "root",
|
|
version: "1.0.0",
|
|
workspaces: ["packages/*"],
|
|
catalog: {
|
|
"no-deps": "^1.0.0",
|
|
},
|
|
}),
|
|
"packages/lib/package.json": JSON.stringify({
|
|
name: "@test/lib",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"a-dep": "^1.0.5",
|
|
},
|
|
}),
|
|
"packages/app/package.json": JSON.stringify({
|
|
name: "@test/app",
|
|
dependencies: {
|
|
"@test/lib": "workspace:*",
|
|
"no-deps": "catalog:",
|
|
"dep-with-tags": "~1.0.0",
|
|
},
|
|
}),
|
|
});
|
|
|
|
// Install first
|
|
await using install = Bun.spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
expect(await install.exited).toBe(0);
|
|
|
|
// Run interactive update with piped input
|
|
await using update = Bun.spawn({
|
|
cmd: [bunExe(), "update", "-i", "-r", "--latest"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdin: "pipe",
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
|
|
// Send 'a' to select all, then newline to confirm
|
|
update.stdin.write("a\n");
|
|
update.stdin.end();
|
|
|
|
const exitCode = await update.exited;
|
|
expect(exitCode).toBe(0);
|
|
|
|
// Check updates were applied correctly
|
|
const appJson = await Bun.file(join(dir, "packages/app/package.json")).json();
|
|
const libJson = await Bun.file(join(dir, "packages/lib/package.json")).json();
|
|
|
|
// Workspace dependency should remain unchanged
|
|
expect(appJson.dependencies["@test/lib"]).toBe("workspace:*");
|
|
|
|
// Regular dependencies should be updated with prefix preserved
|
|
expect(appJson.dependencies["dep-with-tags"]).toMatch(/^~/);
|
|
expect(libJson.dependencies["a-dep"]).toMatch(/^\^/);
|
|
});
|
|
|
|
it("should handle selecting specific packages in interactive mode", async () => {
|
|
const dir = tempDirWithFiles("update-interactive-selective", {
|
|
"bunfig.toml": `[install]
|
|
cache = false
|
|
registry = "${registryUrl}"
|
|
`,
|
|
"package.json": JSON.stringify({
|
|
name: "test-project",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"no-deps": "1.0.0",
|
|
"dep-with-tags": "1.0.0",
|
|
"a-dep": "1.0.5",
|
|
},
|
|
}),
|
|
});
|
|
|
|
// Install first
|
|
await using install = Bun.spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
expect(await install.exited).toBe(0);
|
|
|
|
// Run interactive update that selects only first package (space toggles, arrow down, enter)
|
|
await using update = Bun.spawn({
|
|
cmd: [bunExe(), "update", "-i", "--latest"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdin: "pipe",
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
|
|
// Send keyboard navigation: space to toggle, arrow down, enter to confirm
|
|
update.stdin.write(" \u001b[B\n");
|
|
update.stdin.end();
|
|
|
|
const exitCode = await update.exited;
|
|
const output = await new Response(update.stdout).text();
|
|
|
|
expect(exitCode).toBe(0);
|
|
expect(output).toContain("Selected 1 package to update");
|
|
|
|
// Check only one package was updated
|
|
const packageJson = await Bun.file(join(dir, "package.json")).json();
|
|
|
|
// Since we toggled only the first package, check that only one was updated
|
|
// The actual package updated depends on the order, so we check that exactly one changed
|
|
let updatedCount = 0;
|
|
if (packageJson.dependencies["no-deps"] !== "1.0.0") updatedCount++;
|
|
if (packageJson.dependencies["dep-with-tags"] !== "1.0.0") updatedCount++;
|
|
if (packageJson.dependencies["a-dep"] !== "1.0.5") updatedCount++;
|
|
expect(updatedCount).toBe(1);
|
|
});
|
|
|
|
it("should handle empty catalog definitions gracefully", async () => {
|
|
const dir = tempDirWithFiles("update-interactive-empty-catalog", {
|
|
"bunfig.toml": `[install]
|
|
cache = false
|
|
registry = "${registryUrl}"
|
|
`,
|
|
"package.json": JSON.stringify({
|
|
name: "root",
|
|
version: "1.0.0",
|
|
workspaces: ["packages/*"],
|
|
catalog: {},
|
|
}),
|
|
"packages/app/package.json": JSON.stringify({
|
|
name: "@test/app",
|
|
dependencies: {
|
|
"no-deps": "^1.0.0",
|
|
},
|
|
}),
|
|
});
|
|
|
|
// Install first
|
|
await using install = Bun.spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
expect(await install.exited).toBe(0);
|
|
|
|
// Run interactive update with piped input
|
|
await using update = Bun.spawn({
|
|
cmd: [bunExe(), "update", "-i", "-r", "--latest"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdin: "pipe",
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
|
|
// Send 'a' to select all, then newline to confirm
|
|
update.stdin.write("a\n");
|
|
update.stdin.end();
|
|
|
|
const exitCode = await update.exited;
|
|
expect(exitCode).toBe(0);
|
|
|
|
// Check workspace package was updated normally
|
|
const appJson = await Bun.file(join(dir, "packages/app/package.json")).json();
|
|
expect(appJson.dependencies["no-deps"]).toBe("^2.0.0");
|
|
|
|
// Root catalog should remain empty
|
|
const rootJson = await Bun.file(join(dir, "package.json")).json();
|
|
expect(Object.keys(rootJson.catalog)).toHaveLength(0);
|
|
});
|
|
|
|
it("should handle cancellation (Ctrl+C) gracefully", async () => {
|
|
const dir = tempDirWithFiles("update-interactive-cancel", {
|
|
"bunfig.toml": `[install]
|
|
cache = false
|
|
registry = "${registryUrl}"
|
|
`,
|
|
"package.json": JSON.stringify({
|
|
name: "test-project",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"no-deps": "1.0.0",
|
|
},
|
|
}),
|
|
});
|
|
|
|
// Install first
|
|
await using install = Bun.spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
expect(await install.exited).toBe(0);
|
|
|
|
// Run interactive update and send Ctrl+C
|
|
await using update = Bun.spawn({
|
|
cmd: [bunExe(), "update", "-i", "--latest"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdin: "pipe",
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
|
|
// Send Ctrl+C to cancel
|
|
update.stdin.write("\u0003");
|
|
update.stdin.end();
|
|
|
|
const exitCode = await update.exited;
|
|
const output = await new Response(update.stdout).text();
|
|
|
|
expect(exitCode).toBe(0);
|
|
expect(output).toContain("Cancelled");
|
|
|
|
// Check package.json was not modified
|
|
const packageJson = await Bun.file(join(dir, "package.json")).json();
|
|
expect(packageJson.dependencies["no-deps"]).toBe("1.0.0");
|
|
});
|
|
|
|
it("should handle packages with pre-release versions correctly", async () => {
|
|
const dir = tempDirWithFiles("update-interactive-prerelease", {
|
|
"bunfig.toml": `[install]
|
|
cache = false
|
|
registry = "${registryUrl}"
|
|
`,
|
|
"package.json": JSON.stringify({
|
|
name: "test-project",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"no-deps": "1.0.0",
|
|
"dep-with-tags": "^1.0.0",
|
|
"a-dep": "~1.0.5",
|
|
},
|
|
}),
|
|
});
|
|
|
|
// Install first
|
|
await using install = Bun.spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
expect(await install.exited).toBe(0);
|
|
|
|
// Run interactive update with piped input
|
|
await using update = Bun.spawn({
|
|
cmd: [bunExe(), "update", "-i", "--latest"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdin: "pipe",
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
|
|
// Send 'a' to select all, then newline to confirm
|
|
update.stdin.write("a\n");
|
|
update.stdin.end();
|
|
|
|
const exitCode = await update.exited;
|
|
expect(exitCode).toBe(0);
|
|
|
|
// Check version prefixes are preserved
|
|
const packageJson = await Bun.file(join(dir, "package.json")).json();
|
|
|
|
// Prefixes should be preserved
|
|
expect(packageJson.dependencies["dep-with-tags"]).toMatch(/^\^/);
|
|
expect(packageJson.dependencies["a-dep"]).toMatch(/^~/);
|
|
});
|
|
|
|
it("should update catalog in workspaces object (not workspaces.catalogs)", async () => {
|
|
const dir = tempDirWithFiles("update-interactive-workspaces-catalog", {
|
|
"bunfig.toml": `[install]
|
|
cache = false
|
|
registry = "${registryUrl}"
|
|
`,
|
|
"package.json": JSON.stringify({
|
|
name: "root",
|
|
version: "1.0.0",
|
|
workspaces: {
|
|
packages: ["packages/*"],
|
|
catalog: {
|
|
"no-deps": "^1.0.0",
|
|
"dep-with-tags": "~1.0.0",
|
|
},
|
|
},
|
|
}),
|
|
"packages/app/package.json": JSON.stringify({
|
|
name: "@test/app",
|
|
dependencies: {
|
|
"no-deps": "catalog:",
|
|
"dep-with-tags": "catalog:",
|
|
},
|
|
}),
|
|
});
|
|
|
|
// Install first
|
|
await using install = Bun.spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
expect(await install.exited).toBe(0);
|
|
|
|
// Run interactive update with piped input
|
|
await using update = Bun.spawn({
|
|
cmd: [bunExe(), "update", "-i", "-r", "--latest"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdin: "pipe",
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
|
|
// Send 'a' to select all, then newline to confirm
|
|
update.stdin.write("a\n");
|
|
update.stdin.end();
|
|
|
|
const exitCode = await update.exited;
|
|
const output = await new Response(update.stdout).text();
|
|
|
|
expect(exitCode).toBe(0);
|
|
expect(output).toContain("Installing updates...");
|
|
|
|
// Check catalog was updated with preserved prefixes
|
|
const packageJson = await Bun.file(join(dir, "package.json")).json();
|
|
expect(packageJson.workspaces.catalog["no-deps"]).toBe("^2.0.0");
|
|
expect(packageJson.workspaces.catalog["dep-with-tags"]).toMatch(/^~/);
|
|
});
|
|
|
|
it("should handle scoped packages in catalogs correctly", async () => {
|
|
const dir = tempDirWithFiles("update-interactive-scoped-catalog", {
|
|
"bunfig.toml": `[install]
|
|
cache = false
|
|
registry = "${registryUrl}"
|
|
`,
|
|
"package.json": JSON.stringify({
|
|
name: "root",
|
|
version: "1.0.0",
|
|
workspaces: ["packages/*"],
|
|
catalog: {
|
|
"@scoped/has-bin-entry": "^1.0.0",
|
|
"no-deps": "~1.0.0",
|
|
"dep-with-tags": ">=1.0.0",
|
|
},
|
|
}),
|
|
"packages/app/package.json": JSON.stringify({
|
|
name: "@test/app",
|
|
dependencies: {
|
|
"@scoped/has-bin-entry": "catalog:",
|
|
"no-deps": "catalog:",
|
|
"dep-with-tags": "catalog:",
|
|
},
|
|
}),
|
|
});
|
|
|
|
// Install first
|
|
await using install = Bun.spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
expect(await install.exited).toBe(0);
|
|
|
|
// Run interactive update with piped input
|
|
await using update = Bun.spawn({
|
|
cmd: [bunExe(), "update", "-i", "-r", "--latest"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdin: "pipe",
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
|
|
// Send 'a' to select all, then newline to confirm
|
|
update.stdin.write("a\n");
|
|
update.stdin.end();
|
|
|
|
const exitCode = await update.exited;
|
|
expect(exitCode).toBe(0);
|
|
|
|
// Check scoped packages were updated with preserved prefixes
|
|
const packageJson = await Bun.file(join(dir, "package.json")).json();
|
|
expect(packageJson.catalog["@scoped/has-bin-entry"]).toMatch(/^\^/);
|
|
expect(packageJson.catalog["no-deps"]).toMatch(/^~/);
|
|
expect(packageJson.catalog["dep-with-tags"]).toMatch(/^>=/);
|
|
});
|
|
|
|
it("should handle catalog updates when running from root with filter", async () => {
|
|
const dir = tempDirWithFiles("update-interactive-filter-catalog", {
|
|
"bunfig.toml": `[install]
|
|
cache = false
|
|
registry = "${registryUrl}"
|
|
`,
|
|
"package.json": JSON.stringify({
|
|
name: "root",
|
|
version: "1.0.0",
|
|
workspaces: ["packages/*"],
|
|
catalog: {
|
|
"no-deps": "^1.0.0",
|
|
"dep-with-tags": "~1.0.0",
|
|
},
|
|
}),
|
|
"packages/app1/package.json": JSON.stringify({
|
|
name: "@test/app1",
|
|
dependencies: {
|
|
"no-deps": "catalog:",
|
|
},
|
|
}),
|
|
"packages/app2/package.json": JSON.stringify({
|
|
name: "@test/app2",
|
|
dependencies: {
|
|
"dep-with-tags": "catalog:",
|
|
},
|
|
}),
|
|
});
|
|
|
|
// Install first
|
|
await using install = Bun.spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
expect(await install.exited).toBe(0);
|
|
|
|
// Run interactive update with filter
|
|
await using update = Bun.spawn({
|
|
cmd: [bunExe(), "update", "-i", "--filter=@test/app2", "--latest"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdin: "pipe",
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
|
|
// Send 'a' to select all, then newline to confirm
|
|
update.stdin.write("a\n");
|
|
update.stdin.end();
|
|
|
|
const exitCode = await update.exited;
|
|
const output = await new Response(update.stdout).text();
|
|
|
|
expect(exitCode).toBe(0);
|
|
|
|
// Check catalog was updated
|
|
const packageJson = await Bun.file(join(dir, "package.json")).json();
|
|
expect(packageJson.catalog["dep-with-tags"]).toMatch(/^~/);
|
|
//todo: actually check the catalog was updated
|
|
});
|
|
|
|
it("should handle multiple catalog definitions with same package", async () => {
|
|
const dir = tempDirWithFiles("update-interactive-multi-catalog", {
|
|
"bunfig.toml": `[install]
|
|
cache = false
|
|
registry = "${registryUrl}"
|
|
`,
|
|
"package.json": JSON.stringify({
|
|
name: "root",
|
|
version: "1.0.0",
|
|
workspaces: {
|
|
packages: ["packages/*"],
|
|
catalogs: {
|
|
"dev": {
|
|
"no-deps": "^1.0.0",
|
|
},
|
|
"prod": {
|
|
"no-deps": "~1.0.0",
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
"packages/app/package.json": JSON.stringify({
|
|
name: "@test/app",
|
|
dependencies: {
|
|
"no-deps": "catalog:prod",
|
|
},
|
|
devDependencies: {
|
|
"no-deps": "catalog:dev",
|
|
},
|
|
}),
|
|
});
|
|
|
|
// Install first
|
|
await using install = Bun.spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
expect(await install.exited).toBe(0);
|
|
|
|
// Run interactive update with piped input
|
|
await using update = Bun.spawn({
|
|
cmd: [bunExe(), "update", "-i", "-r", "--latest"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdin: "pipe",
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
|
|
// Send 'a' to select all, then newline to confirm
|
|
update.stdin.write("a\n");
|
|
update.stdin.end();
|
|
|
|
const exitCode = await update.exited;
|
|
const output = await new Response(update.stdout).text();
|
|
|
|
expect(exitCode).toBe(0);
|
|
|
|
// Check both catalogs were updated with preserved prefixes
|
|
const packageJson = await Bun.file(join(dir, "package.json")).json();
|
|
expect(packageJson.workspaces.catalogs.dev["no-deps"]).toBe("^2.0.0");
|
|
expect(packageJson.workspaces.catalogs.prod["no-deps"]).toMatch(/^~/);
|
|
//todo: actually check the catalog was updated
|
|
});
|
|
|
|
it("should handle version ranges with multiple conditions", async () => {
|
|
const dir = tempDirWithFiles("update-interactive-complex-ranges", {
|
|
"bunfig.toml": `[install]
|
|
cache = false
|
|
registry = "${registryUrl}"
|
|
`,
|
|
"package.json": JSON.stringify({
|
|
name: "root",
|
|
version: "1.0.0",
|
|
workspaces: ["packages/*"],
|
|
catalog: {
|
|
"no-deps": "^1.0.0 || ^2.0.0",
|
|
"dep-with-tags": ">=1.0.0 <3.0.0",
|
|
},
|
|
}),
|
|
"packages/app/package.json": JSON.stringify({
|
|
name: "@test/app",
|
|
dependencies: {
|
|
"no-deps": "catalog:",
|
|
"dep-with-tags": "catalog:",
|
|
},
|
|
}),
|
|
});
|
|
|
|
// Install first
|
|
await using install = Bun.spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
expect(await install.exited).toBe(0);
|
|
|
|
// Run interactive update with piped input
|
|
await using update = Bun.spawn({
|
|
cmd: [bunExe(), "update", "-i", "-r", "--latest"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdin: "pipe",
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
|
|
// Send 'a' to select all, then newline to confirm
|
|
update.stdin.write("a\n");
|
|
update.stdin.end();
|
|
|
|
const exitCode = await update.exited;
|
|
expect(exitCode).toBe(0);
|
|
|
|
// Check complex ranges are handled (they might be simplified)
|
|
const packageJson = await Bun.file(join(dir, "package.json")).json();
|
|
// Complex ranges might be simplified to latest version
|
|
expect(packageJson.catalog["no-deps"]).toBeDefined();
|
|
expect(packageJson.catalog["dep-with-tags"]).toBeDefined();
|
|
});
|
|
|
|
it("should handle dry-run mode correctly", async () => {
|
|
const dir = tempDirWithFiles("update-interactive-dry-run", {
|
|
"bunfig.toml": `[install]
|
|
cache = false
|
|
registry = "${registryUrl}"
|
|
`,
|
|
"package.json": JSON.stringify({
|
|
name: "test-project",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"no-deps": "1.0.0",
|
|
"dep-with-tags": "1.0.0",
|
|
},
|
|
}),
|
|
});
|
|
|
|
// Install first
|
|
await using install = Bun.spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
expect(await install.exited).toBe(0);
|
|
|
|
// Run interactive update with dry-run
|
|
await using update = Bun.spawn({
|
|
cmd: [bunExe(), "update", "-i", "--latest", "--dry-run"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdin: "pipe",
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
|
|
// Send 'a' to select all, then newline to confirm
|
|
update.stdin.write("a\n");
|
|
update.stdin.end();
|
|
|
|
const exitCode = await update.exited;
|
|
const output = await new Response(update.stdout).text();
|
|
|
|
expect(exitCode).toBe(0);
|
|
expect(output).toContain("Selected");
|
|
|
|
// Check packages were NOT updated (dry-run)
|
|
const packageJson = await Bun.file(join(dir, "package.json")).json();
|
|
expect(packageJson.dependencies["no-deps"]).toBe("1.0.0");
|
|
expect(packageJson.dependencies["dep-with-tags"]).toBe("1.0.0");
|
|
});
|
|
|
|
it("should handle keyboard navigation correctly", async () => {
|
|
const dir = tempDirWithFiles("update-interactive-navigation", {
|
|
"bunfig.toml": `[install]
|
|
cache = false
|
|
registry = "${registryUrl}"
|
|
`,
|
|
"package.json": JSON.stringify({
|
|
name: "test-project",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"no-deps": "1.0.0",
|
|
"dep-with-tags": "1.0.0",
|
|
"a-dep": "1.0.5",
|
|
},
|
|
}),
|
|
});
|
|
|
|
// Install first
|
|
await using install = Bun.spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
expect(await install.exited).toBe(0);
|
|
|
|
// Run interactive update with keyboard navigation:
|
|
// - n (select none)
|
|
// - i (invert selection)
|
|
// - Enter (confirm)
|
|
await using update = Bun.spawn({
|
|
cmd: [bunExe(), "update", "-i", "--latest"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdin: "pipe",
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
|
|
// Send keyboard navigation commands
|
|
update.stdin.write("ni\n");
|
|
update.stdin.end();
|
|
|
|
const exitCode = await update.exited;
|
|
const output = await new Response(update.stdout).text();
|
|
|
|
expect(exitCode).toBe(0);
|
|
expect(output).toContain("Selected 3 packages to update");
|
|
});
|
|
|
|
// Comprehensive tests from separate file
|
|
it("comprehensive interactive update test with all scenarios", async () => {
|
|
const dir = tempDirWithFiles("update-interactive-comprehensive", {
|
|
"bunfig.toml": `[install]
|
|
cache = false
|
|
registry = "${registryUrl}"
|
|
`,
|
|
// Root package.json with catalog definitions and dependencies
|
|
"package.json": JSON.stringify({
|
|
name: "root-project",
|
|
version: "1.0.0",
|
|
private: true,
|
|
workspaces: ["packages/*"],
|
|
// Catalog with old versions that can be updated
|
|
catalog: {
|
|
"no-deps": "^1.0.0",
|
|
"dep-with-tags": "~1.0.0",
|
|
},
|
|
// Some root dependencies
|
|
dependencies: {
|
|
"a-dep": "^1.0.5",
|
|
},
|
|
devDependencies: {
|
|
"normal-dep-and-dev-dep": "^1.0.0",
|
|
},
|
|
}),
|
|
// Workspace 1: Uses catalog references and has its own dependencies
|
|
"packages/app1/package.json": JSON.stringify({
|
|
name: "@test/app1",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"no-deps": "catalog:", // References catalog
|
|
"dep-with-tags": "catalog:", // References catalog
|
|
"a-dep": "^1.0.5", // Regular dependency (same as root)
|
|
},
|
|
devDependencies: {
|
|
"normal-dep-and-dev-dep": "^1.0.0", // Dev dependency
|
|
},
|
|
}),
|
|
// Workspace 2: Different dependencies to test workspace-specific updates
|
|
"packages/app2/package.json": JSON.stringify({
|
|
name: "@test/app2",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"no-deps": "catalog:", // References catalog
|
|
"a-dep": "^1.0.5", // Regular dependency
|
|
},
|
|
devDependencies: {
|
|
"dep-with-tags": "^1.0.0", // Different from catalog - should update independently
|
|
},
|
|
}),
|
|
});
|
|
|
|
// First install to establish the lockfile
|
|
await using install = Bun.spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
|
|
const installExitCode = await install.exited;
|
|
if (installExitCode !== 0) {
|
|
const stderr = await new Response(install.stderr).text();
|
|
console.error("Install failed:", stderr);
|
|
}
|
|
expect(installExitCode).toBe(0);
|
|
|
|
// Run interactive update and select all packages
|
|
await using update = Bun.spawn({
|
|
cmd: [bunExe(), "update", "-i", "-r", "--latest"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdin: "pipe",
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
|
|
// Send 'a' to select all, then newline to confirm
|
|
update.stdin.write("a\n");
|
|
update.stdin.end();
|
|
|
|
const updateExitCode = await update.exited;
|
|
const stdout = await new Response(update.stdout).text();
|
|
const stderr = await new Response(update.stderr).text();
|
|
const combined = stdout + stderr;
|
|
|
|
// Should complete successfully
|
|
expect(updateExitCode).toBe(0);
|
|
expect(combined).not.toContain("panic");
|
|
expect(combined).not.toContain("FileNotFound");
|
|
expect(combined).not.toContain("Failed to update");
|
|
|
|
// Verify catalog definitions were updated in root package.json
|
|
const rootPackageJson = await Bun.file(join(dir, "package.json")).json();
|
|
|
|
// Catalog should be updated while preserving prefixes
|
|
expect(rootPackageJson.catalog["no-deps"]).toBe("^2.0.0");
|
|
expect(rootPackageJson.catalog["dep-with-tags"]).toMatch(/^~/);
|
|
|
|
// Root dependencies should be updated
|
|
expect(rootPackageJson.dependencies["a-dep"]).toMatch(/^\^/);
|
|
expect(rootPackageJson.devDependencies["normal-dep-and-dev-dep"]).toMatch(/^\^/);
|
|
|
|
// App1 should have catalog references preserved but regular deps updated
|
|
const app1Json = await Bun.file(join(dir, "packages/app1/package.json")).json();
|
|
expect(app1Json.dependencies["no-deps"]).toBe("catalog:"); // Catalog ref preserved
|
|
expect(app1Json.dependencies["dep-with-tags"]).toBe("catalog:"); // Catalog ref preserved
|
|
expect(app1Json.dependencies["a-dep"]).toMatch(/^\^/); // Regular dep updated
|
|
expect(app1Json.devDependencies["normal-dep-and-dev-dep"]).toMatch(/^\^/); // Dev dep updated
|
|
|
|
// App2 should have catalog references preserved and independent deps updated
|
|
const app2Json = await Bun.file(join(dir, "packages/app2/package.json")).json();
|
|
expect(app2Json.dependencies["no-deps"]).toBe("catalog:"); // Catalog ref preserved
|
|
expect(app2Json.dependencies["a-dep"]).toMatch(/^\^/); // Regular dep updated
|
|
expect(app2Json.devDependencies["dep-with-tags"]).toMatch(/^\^/); // Independent dep updated
|
|
|
|
// Verify lockfile exists and is valid
|
|
console.log("Checking lockfile...");
|
|
const lockfilePath = join(dir, "bun.lock");
|
|
const lockfileExists = await Bun.file(lockfilePath).exists();
|
|
expect(lockfileExists).toBe(true);
|
|
|
|
// Run bun install again to verify no changes are needed
|
|
await using verifyInstall = Bun.spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
|
|
const verifyExitCode = await verifyInstall.exited;
|
|
const verifyStdout = await new Response(verifyInstall.stdout).text();
|
|
const verifyStderr = await new Response(verifyInstall.stderr).text();
|
|
const verifyCombined = verifyStdout + verifyStderr;
|
|
|
|
expect(verifyExitCode).toBe(0);
|
|
|
|
// Should indicate no changes are needed - just check that no new packages are being installed
|
|
expect(verifyCombined).not.toContain("Installing");
|
|
// "Saved lockfile" is fine even when no changes, so don't check for it
|
|
});
|
|
|
|
it("interactive update with workspace filters", async () => {
|
|
const dir = tempDirWithFiles("update-interactive-filter", {
|
|
"bunfig.toml": `[install]
|
|
cache = false
|
|
registry = "${registryUrl}"
|
|
`,
|
|
"package.json": JSON.stringify({
|
|
name: "root",
|
|
version: "1.0.0",
|
|
private: true,
|
|
workspaces: ["packages/*"],
|
|
catalog: {
|
|
"no-deps": "^1.0.0",
|
|
},
|
|
}),
|
|
"packages/frontend/package.json": JSON.stringify({
|
|
name: "@test/frontend",
|
|
dependencies: {
|
|
"no-deps": "catalog:",
|
|
"a-dep": "^1.0.5",
|
|
},
|
|
}),
|
|
"packages/backend/package.json": JSON.stringify({
|
|
name: "@test/backend",
|
|
dependencies: {
|
|
"dep-with-tags": "^1.0.0",
|
|
},
|
|
}),
|
|
});
|
|
|
|
// Install first
|
|
await using install = Bun.spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
expect(await install.exited).toBe(0);
|
|
|
|
// Update only frontend workspace
|
|
await using update = Bun.spawn({
|
|
cmd: [bunExe(), "update", "-i", "--filter=@test/frontend", "--latest"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdin: "pipe",
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
|
|
update.stdin.write("a\n");
|
|
update.stdin.end();
|
|
|
|
const exitCode = await update.exited;
|
|
expect(exitCode).toBe(0);
|
|
|
|
// Verify catalog was updated (even with filter)
|
|
const rootJson = await Bun.file(join(dir, "package.json")).json();
|
|
expect(rootJson.catalog["no-deps"]).toBe("^2.0.0");
|
|
|
|
// Verify frontend was updated
|
|
const frontendJson = await Bun.file(join(dir, "packages/frontend/package.json")).json();
|
|
expect(frontendJson.dependencies["a-dep"]).toMatch(/^\^/);
|
|
|
|
// Verify backend was not updated (should still be old version)
|
|
const backendJson = await Bun.file(join(dir, "packages/backend/package.json")).json();
|
|
expect(backendJson.dependencies["dep-with-tags"]).toBe("^1.0.0");
|
|
});
|
|
|
|
it("interactive update with workspaces.catalogs structure", async () => {
|
|
const dir = tempDirWithFiles("update-interactive-workspaces-catalogs", {
|
|
"bunfig.toml": `[install]
|
|
cache = false
|
|
registry = "${registryUrl}"
|
|
`,
|
|
"package.json": JSON.stringify({
|
|
name: "root",
|
|
version: "1.0.0",
|
|
workspaces: {
|
|
packages: ["packages/*"],
|
|
catalogs: {
|
|
"shared": {
|
|
"no-deps": "^1.0.0",
|
|
"dep-with-tags": "~1.0.0",
|
|
},
|
|
"tools": {
|
|
"a-dep": ">=1.0.5",
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
"packages/app/package.json": JSON.stringify({
|
|
name: "@test/app",
|
|
dependencies: {
|
|
"no-deps": "catalog:shared",
|
|
"dep-with-tags": "catalog:shared",
|
|
"a-dep": "catalog:tools",
|
|
},
|
|
}),
|
|
});
|
|
|
|
// Install first
|
|
await using install = Bun.spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
expect(await install.exited).toBe(0);
|
|
|
|
// Run interactive update
|
|
await using update = Bun.spawn({
|
|
cmd: [bunExe(), "update", "-i", "-r", "--latest"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdin: "pipe",
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
|
|
update.stdin.write("a\n");
|
|
update.stdin.end();
|
|
|
|
const exitCode = await update.exited;
|
|
const output = await new Response(update.stdout).text();
|
|
|
|
expect(exitCode).toBe(0);
|
|
expect(output).toContain("Installing updates..."); // Should show install message
|
|
|
|
// Verify workspaces.catalogs were updated with preserved prefixes AND new versions
|
|
const packageJson = await Bun.file(join(dir, "package.json")).json();
|
|
|
|
// Check that versions actually changed from original static values
|
|
expect(packageJson.workspaces.catalogs.shared["no-deps"]).not.toBe("^1.0.0"); // Should be newer
|
|
expect(packageJson.workspaces.catalogs.shared["dep-with-tags"]).not.toBe("~1.0.0"); // Should be newer
|
|
|
|
// For a-dep, check if it changed or at least verify it has the right prefix
|
|
// (Some versions might not change if already satisfied)
|
|
const aDep = packageJson.workspaces.catalogs.tools["a-dep"];
|
|
if (aDep !== ">=1.0.5") {
|
|
// Version changed - verify it starts with >=
|
|
expect(aDep).toMatch(/^>=/);
|
|
} else {
|
|
// Version didn't change - that's ok if the constraint was already satisfied
|
|
expect(aDep).toBe(">=1.0.5");
|
|
}
|
|
|
|
// Check that prefixes are preserved
|
|
expect(packageJson.workspaces.catalogs.shared["no-deps"]).toMatch(/^\^/);
|
|
expect(packageJson.workspaces.catalogs.shared["dep-with-tags"]).toMatch(/^~/);
|
|
expect(packageJson.workspaces.catalogs.tools["a-dep"]).toMatch(/^>=/);
|
|
|
|
// App package should still have catalog references (unchanged)
|
|
const appJson = await Bun.file(join(dir, "packages/app/package.json")).json();
|
|
expect(appJson.dependencies["no-deps"]).toBe("catalog:shared");
|
|
expect(appJson.dependencies["dep-with-tags"]).toBe("catalog:shared");
|
|
expect(appJson.dependencies["a-dep"]).toBe("catalog:tools");
|
|
});
|
|
|
|
it("interactive update dry run mode", async () => {
|
|
const dir = tempDirWithFiles("update-interactive-dry-run", {
|
|
"bunfig.toml": `[install]
|
|
cache = false
|
|
registry = "${registryUrl}"
|
|
`,
|
|
"package.json": JSON.stringify({
|
|
name: "test-project",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"no-deps": "1.0.0",
|
|
"dep-with-tags": "1.0.0",
|
|
},
|
|
}),
|
|
});
|
|
|
|
// Install first
|
|
await using install = Bun.spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
expect(await install.exited).toBe(0);
|
|
|
|
// Store original package.json content
|
|
const originalContent = await Bun.file(join(dir, "package.json")).text();
|
|
|
|
// Run interactive update with dry-run
|
|
await using update = Bun.spawn({
|
|
cmd: [bunExe(), "update", "-i", "--latest", "--dry-run"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdin: "pipe",
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
|
|
update.stdin.write("a\n");
|
|
update.stdin.end();
|
|
|
|
const exitCode = await update.exited;
|
|
const output = await new Response(update.stdout).text();
|
|
|
|
expect(exitCode).toBe(0);
|
|
expect(output).toContain("Dry run");
|
|
|
|
// Verify package.json was NOT modified
|
|
const afterContent = await Bun.file(join(dir, "package.json")).text();
|
|
expect(afterContent).toBe(originalContent);
|
|
|
|
// Parse and verify versions are still old
|
|
const packageJson = await Bun.file(join(dir, "package.json")).json();
|
|
expect(packageJson.dependencies["no-deps"]).toBe("1.0.0");
|
|
expect(packageJson.dependencies["dep-with-tags"]).toBe("1.0.0");
|
|
});
|
|
|
|
it("should preserve npm: alias prefix when updating packages", async () => {
|
|
const dir = tempDirWithFiles("update-interactive-npm-alias", {
|
|
"bunfig.toml": `[install]
|
|
cache = false
|
|
registry = "${registryUrl}"
|
|
`,
|
|
"package.json": JSON.stringify({
|
|
name: "test-project",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"my-alias": "npm:no-deps@1.0.0",
|
|
"@my/alias": "npm:@types/no-deps@^1.0.0",
|
|
},
|
|
}),
|
|
});
|
|
|
|
await using install = Bun.spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
expect(await install.exited).toBe(0);
|
|
|
|
await using update = Bun.spawn({
|
|
cmd: [bunExe(), "update", "-i", "--latest"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdin: "pipe",
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
|
|
update.stdin.write("a\n");
|
|
update.stdin.end();
|
|
|
|
const exitCode = await update.exited;
|
|
expect(exitCode).toBe(0);
|
|
|
|
const packageJson = await Bun.file(join(dir, "package.json")).json();
|
|
expect(packageJson.dependencies["my-alias"]).toBe("npm:no-deps@2.0.0");
|
|
expect(packageJson.dependencies["@my/alias"]).toBe("npm:@types/no-deps@^2.0.0");
|
|
});
|
|
|
|
it("interactive update with mixed dependency types", async () => {
|
|
const dir = tempDirWithFiles("update-interactive-mixed", {
|
|
"bunfig.toml": `[install]
|
|
cache = false
|
|
registry = "${registryUrl}"
|
|
`,
|
|
"package.json": JSON.stringify({
|
|
name: "test-project",
|
|
version: "1.0.0",
|
|
workspaces: ["packages/*"],
|
|
catalog: {
|
|
"a-dep": "^1.0.5",
|
|
},
|
|
dependencies: {
|
|
"no-deps": "^1.0.0",
|
|
},
|
|
devDependencies: {
|
|
"dep-with-tags": "~1.0.0",
|
|
},
|
|
peerDependencies: {
|
|
"a-dep": ">=1.0.5",
|
|
},
|
|
optionalDependencies: {
|
|
"normal-dep-and-dev-dep": "^1.0.0",
|
|
},
|
|
}),
|
|
"packages/workspace1/package.json": JSON.stringify({
|
|
name: "@test/workspace1",
|
|
dependencies: {
|
|
"a-dep": "catalog:",
|
|
"@test/workspace2": "workspace:*",
|
|
},
|
|
devDependencies: {
|
|
"no-deps": "^1.0.0",
|
|
},
|
|
}),
|
|
"packages/workspace2/package.json": JSON.stringify({
|
|
name: "@test/workspace2",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"a-dep": "catalog:",
|
|
},
|
|
}),
|
|
});
|
|
|
|
// Install first
|
|
await using install = Bun.spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
expect(await install.exited).toBe(0);
|
|
|
|
// Run interactive update
|
|
await using update = Bun.spawn({
|
|
cmd: [bunExe(), "update", "-i", "-r", "--latest"],
|
|
cwd: dir,
|
|
env: bunEnv,
|
|
stdin: "pipe",
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
|
|
update.stdin.write("a\n");
|
|
update.stdin.end();
|
|
|
|
const exitCode = await update.exited;
|
|
expect(exitCode).toBe(0);
|
|
|
|
// Verify all dependency types were handled correctly
|
|
const rootJson = await Bun.file(join(dir, "package.json")).json();
|
|
expect(rootJson.catalog["a-dep"]).toMatch(/^\^/); // Catalog updated
|
|
expect(rootJson.dependencies["no-deps"]).toMatch(/^\^/); // Regular dep updated
|
|
expect(rootJson.devDependencies["dep-with-tags"]).toMatch(/^~/); // Dev dep updated with prefix preserved
|
|
expect(rootJson.peerDependencies["a-dep"]).toMatch(/^>=/); // Peer dep updated with prefix preserved
|
|
expect(rootJson.optionalDependencies["normal-dep-and-dev-dep"]).toMatch(/^\^/); // Optional dep updated
|
|
|
|
// Verify workspace dependencies
|
|
const ws1Json = await Bun.file(join(dir, "packages/workspace1/package.json")).json();
|
|
expect(ws1Json.dependencies["a-dep"]).toBe("catalog:"); // Catalog ref preserved
|
|
expect(ws1Json.dependencies["@test/workspace2"]).toBe("workspace:*"); // Workspace ref preserved
|
|
expect(ws1Json.devDependencies["no-deps"]).toMatch(/^\^/); // Regular dep updated
|
|
|
|
const ws2Json = await Bun.file(join(dir, "packages/workspace2/package.json")).json();
|
|
expect(ws2Json.dependencies["a-dep"]).toBe("catalog:"); // Catalog ref preserved
|
|
});
|
|
});
|