mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
878 lines
28 KiB
TypeScript
878 lines
28 KiB
TypeScript
import { spawn, spawnSync } from "bun";
|
|
import { describe, expect, it } from "bun:test";
|
|
import { bunEnv, bunExe, tempDirWithFiles } from "harness";
|
|
import { join } from "node:path";
|
|
|
|
describe("bun pm version", () => {
|
|
let i = 0;
|
|
|
|
function setupTest() {
|
|
const testDir = tempDirWithFiles(`version-${i++}`, {
|
|
"package.json": JSON.stringify(
|
|
{
|
|
name: "test-package",
|
|
version: "1.0.0",
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
});
|
|
return testDir;
|
|
}
|
|
|
|
function setupGitTest() {
|
|
const testDir = setupTest();
|
|
|
|
spawnSync({
|
|
cmd: ["git", "init"],
|
|
cwd: testDir,
|
|
env: bunEnv,
|
|
});
|
|
|
|
spawnSync({
|
|
cmd: ["git", "config", "user.name", "Test User"],
|
|
cwd: testDir,
|
|
env: bunEnv,
|
|
});
|
|
|
|
spawnSync({
|
|
cmd: ["git", "config", "user.email", "test@example.com"],
|
|
cwd: testDir,
|
|
env: bunEnv,
|
|
});
|
|
|
|
spawnSync({
|
|
cmd: ["git", "add", "package.json"],
|
|
cwd: testDir,
|
|
env: bunEnv,
|
|
});
|
|
|
|
spawnSync({
|
|
cmd: ["git", "commit", "-m", "Initial commit"],
|
|
cwd: testDir,
|
|
env: bunEnv,
|
|
});
|
|
|
|
return testDir;
|
|
}
|
|
|
|
function setupMonorepoTest() {
|
|
const testDir = tempDirWithFiles(`version-${i++}`, {
|
|
"package.json": JSON.stringify(
|
|
{
|
|
name: "monorepo-root",
|
|
version: "1.0.0",
|
|
workspaces: ["packages/*"],
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
"packages/pkg-a/package.json": JSON.stringify(
|
|
{
|
|
name: "@test/pkg-a",
|
|
version: "2.0.0",
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
"packages/pkg-b/package.json": JSON.stringify(
|
|
{
|
|
name: "@test/pkg-b",
|
|
version: "3.0.0",
|
|
dependencies: {
|
|
"@test/pkg-a": "workspace:*",
|
|
},
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
});
|
|
|
|
return testDir;
|
|
}
|
|
|
|
async function runCommand(args: string[], cwd: string, expectSuccess = true) {
|
|
const result = spawn({
|
|
cmd: args,
|
|
cwd,
|
|
env: bunEnv,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
|
|
const [output, error] = await Promise.all([new Response(result.stdout).text(), new Response(result.stderr).text()]);
|
|
|
|
const code = await result.exited;
|
|
|
|
return { output, error, code };
|
|
}
|
|
|
|
describe("help and version previews", () => {
|
|
it("should show help when no arguments provided", async () => {
|
|
const testDir = setupTest();
|
|
|
|
const { output, code } = await runCommand([bunExe(), "pm", "version"], testDir);
|
|
|
|
expect(code).toBe(0);
|
|
expect(output).toContain("bun pm version");
|
|
expect(output).toContain("Current package version: v1.0.0");
|
|
expect(output).toContain("patch");
|
|
expect(output).toContain("minor");
|
|
expect(output).toContain("major");
|
|
});
|
|
|
|
it("shows help with version previews", async () => {
|
|
const testDir1 = tempDirWithFiles(`version-${i++}`, {
|
|
"package.json": JSON.stringify({ name: "test", version: "2.5.3" }, null, 2),
|
|
});
|
|
|
|
const { output: output1, code: code1 } = await runCommand([bunExe(), "pm", "version"], testDir1);
|
|
|
|
expect(code1).toBe(0);
|
|
expect(output1).toContain("Current package version: v2.5.3");
|
|
expect(output1).toContain("patch 2.5.3 → 2.5.4");
|
|
expect(output1).toContain("minor 2.5.3 → 2.6.0");
|
|
expect(output1).toContain("major 2.5.3 → 3.0.0");
|
|
|
|
const testDir2 = tempDirWithFiles(`version-${i++}`, {
|
|
"package.json": JSON.stringify({ name: "test", version: "1.0.0-alpha.0" }, null, 2),
|
|
});
|
|
|
|
const { output: output2, code: code2 } = await runCommand([bunExe(), "pm", "version"], testDir2);
|
|
|
|
expect(code2).toBe(0);
|
|
expect(output2).toContain("prepatch");
|
|
expect(output2).toContain("preminor");
|
|
expect(output2).toContain("premajor");
|
|
expect(output2).toContain("1.0.1-alpha.0");
|
|
expect(output2).toContain("1.1.0-alpha.0");
|
|
expect(output2).toContain("2.0.0-alpha.0");
|
|
|
|
const testDir3 = tempDirWithFiles(`version-${i++}`, {
|
|
"package.json": JSON.stringify({ name: "test", version: "1.0.0" }, null, 2),
|
|
});
|
|
|
|
const { output: output3, code: code3 } = await runCommand(
|
|
[bunExe(), "pm", "version", "--preid", "beta"],
|
|
testDir3,
|
|
);
|
|
|
|
expect(code3).toBe(0);
|
|
expect(output3).toContain("prepatch");
|
|
expect(output3).toContain("preminor");
|
|
expect(output3).toContain("premajor");
|
|
expect(output3).toContain("1.0.1-beta.0");
|
|
expect(output3).toContain("1.1.0-beta.0");
|
|
expect(output3).toContain("2.0.0-beta.0");
|
|
|
|
const testDir4 = tempDirWithFiles(`version-${i++}`, {
|
|
"package.json": JSON.stringify({ name: "test" }, null, 2),
|
|
});
|
|
|
|
const { output: output4 } = await runCommand([bunExe(), "pm", "version"], testDir4);
|
|
|
|
expect(output4).not.toContain("Current package version:");
|
|
expect(output4).toContain("patch 1.0.0 → 1.0.1");
|
|
});
|
|
});
|
|
|
|
describe("basic version incrementing", () => {
|
|
it("should increment versions correctly", async () => {
|
|
const testDir = setupTest();
|
|
|
|
const { output: patchOutput, code: patchCode } = await runCommand(
|
|
[bunExe(), "pm", "version", "patch", "--no-git-tag-version"],
|
|
testDir,
|
|
);
|
|
expect(patchCode).toBe(0);
|
|
expect(patchOutput.trim()).toBe("v1.0.1");
|
|
|
|
const { output: minorOutput, code: minorCode } = await runCommand(
|
|
[bunExe(), "pm", "version", "minor", "--no-git-tag-version"],
|
|
testDir,
|
|
);
|
|
expect(minorCode).toBe(0);
|
|
expect(minorOutput.trim()).toBe("v1.1.0");
|
|
|
|
const { output: majorOutput, code: majorCode } = await runCommand(
|
|
[bunExe(), "pm", "version", "major", "--no-git-tag-version"],
|
|
testDir,
|
|
);
|
|
expect(majorCode).toBe(0);
|
|
expect(majorOutput.trim()).toBe("v2.0.0");
|
|
|
|
const packageJson = await Bun.file(`${testDir}/package.json`).json();
|
|
expect(packageJson.version).toBe("2.0.0");
|
|
});
|
|
|
|
it("should set specific version", async () => {
|
|
const testDir = setupTest();
|
|
|
|
const { output, code } = await runCommand([bunExe(), "pm", "version", "3.2.1", "--no-git-tag-version"], testDir);
|
|
|
|
expect(code).toBe(0);
|
|
expect(output.trim()).toBe("v3.2.1");
|
|
|
|
const packageJson = await Bun.file(`${testDir}/package.json`).json();
|
|
expect(packageJson.version).toBe("3.2.1");
|
|
});
|
|
|
|
it("handles empty package.json", async () => {
|
|
const testDir = tempDirWithFiles(`version-${i++}`, {
|
|
"package.json": "{}",
|
|
});
|
|
|
|
const { output, code } = await runCommand([bunExe(), "pm", "version", "patch", "--no-git-tag-version"], testDir);
|
|
|
|
expect(code).toBe(0);
|
|
expect(output.trim()).toBe("v0.0.1");
|
|
|
|
const packageJson = await Bun.file(`${testDir}/package.json`).json();
|
|
expect(packageJson.version).toBe("0.0.1");
|
|
});
|
|
});
|
|
|
|
describe("error handling", () => {
|
|
it("handles various error conditions", async () => {
|
|
const testDir2 = tempDirWithFiles(`version-${i++}`, {
|
|
"package.json": JSON.stringify({ name: "test", version: "invalid-version" }, null, 2),
|
|
});
|
|
|
|
const { error: error2, code: code2 } = await runCommand(
|
|
[bunExe(), "pm", "version", "patch", "--no-git-tag-version"],
|
|
testDir2,
|
|
false,
|
|
);
|
|
expect(error2).toContain("is not a valid semver");
|
|
expect(code2).toBe(1);
|
|
|
|
const testDir3 = setupTest();
|
|
|
|
const { error: error3, code: code3 } = await runCommand(
|
|
[bunExe(), "pm", "version", "invalid-arg", "--no-git-tag-version"],
|
|
testDir3,
|
|
false,
|
|
);
|
|
expect(error3).toContain("Invalid version argument");
|
|
expect(code3).toBe(1);
|
|
|
|
const testDir4 = setupTest();
|
|
|
|
const { error: error4, code: code4 } = await runCommand(
|
|
[bunExe(), "pm", "version", "1.0.0", "--no-git-tag-version"],
|
|
testDir4,
|
|
false,
|
|
);
|
|
expect(error4).toContain("Version not changed");
|
|
expect(code4).toBe(1);
|
|
|
|
const { output: output5, code: code5 } = await runCommand(
|
|
[bunExe(), "pm", "version", "1.0.0", "--no-git-tag-version", "--allow-same-version"],
|
|
testDir4,
|
|
);
|
|
expect(output5.trim()).toBe("v1.0.0");
|
|
expect(code5).toBe(0);
|
|
});
|
|
|
|
it("handles missing package.json like npm", async () => {
|
|
const testDir = tempDirWithFiles(`version-${i++}`, {
|
|
"README.md": "# Test project",
|
|
});
|
|
|
|
const { error, code } = await runCommand(
|
|
[bunExe(), "pm", "version", "patch", "--no-git-tag-version"],
|
|
testDir,
|
|
false,
|
|
);
|
|
expect(error).toContain("package.json");
|
|
expect(code).toBe(1);
|
|
// its an ealier check that "bun pm *" commands do so not "bun pm version" specific
|
|
// expect(error.includes("ENOENT") || error.includes("no such file")).toBe(true);
|
|
});
|
|
|
|
it("handles empty string package.json like npm", async () => {
|
|
const testDir = tempDirWithFiles(`version-${i++}`, {
|
|
"package.json": '""',
|
|
});
|
|
|
|
const { error, code } = await runCommand(
|
|
[bunExe(), "pm", "version", "patch", "--no-git-tag-version"],
|
|
testDir,
|
|
false,
|
|
);
|
|
expect(error).toContain("Failed to parse package.json");
|
|
expect(code).toBe(1);
|
|
});
|
|
|
|
it("handles malformed JSON like npm", async () => {
|
|
const testDir = tempDirWithFiles(`version-${i++}`, {
|
|
"package.json": '{ "name": "test", invalid json }',
|
|
});
|
|
|
|
const { error, code } = await runCommand(
|
|
[bunExe(), "pm", "version", "patch", "--no-git-tag-version"],
|
|
testDir,
|
|
false,
|
|
);
|
|
expect(error).toContain("Failed to parse package.json");
|
|
expect(code).toBe(1);
|
|
});
|
|
});
|
|
|
|
describe("git integration", () => {
|
|
it("creates git commits and tags by default", async () => {
|
|
const testDir1 = setupGitTest();
|
|
|
|
const {
|
|
output: output1,
|
|
code: code1,
|
|
error: stderr1,
|
|
} = await runCommand([bunExe(), "pm", "version", "patch"], testDir1);
|
|
|
|
expect(stderr1.trim()).toBe("");
|
|
expect(output1.trim()).toBe("v1.0.1");
|
|
expect(code1).toBe(0);
|
|
|
|
const { output: tagOutput } = await runCommand(["git", "tag", "-l"], testDir1);
|
|
expect(tagOutput).toContain("v1.0.1");
|
|
|
|
const { output: logOutput } = await runCommand(["git", "log", "--oneline"], testDir1);
|
|
expect(logOutput).toContain("v1.0.1");
|
|
});
|
|
|
|
it("supports custom commit messages", async () => {
|
|
const testDir2 = setupGitTest();
|
|
|
|
const {
|
|
output: output2,
|
|
error: error2,
|
|
code: code2,
|
|
} = await runCommand([bunExe(), "pm", "version", "patch", "--message", "Custom release message"], testDir2);
|
|
expect(error2).toBe("");
|
|
|
|
const { output: gitLogOutput } = await runCommand(["git", "log", "--oneline"], testDir2);
|
|
expect(gitLogOutput).toContain("Custom release message");
|
|
|
|
expect(code2).toBe(0);
|
|
expect(output2.trim()).toBe("v1.0.1");
|
|
});
|
|
|
|
it("fails when git working directory is not clean", async () => {
|
|
const testDir3 = setupGitTest();
|
|
|
|
await Bun.write(join(testDir3, "untracked.txt"), "untracked content");
|
|
|
|
const { error: error3, code: code3 } = await runCommand([bunExe(), "pm", "version", "patch"], testDir3, false);
|
|
|
|
expect(error3).toContain("Git working directory not clean");
|
|
expect(code3).toBe(1);
|
|
});
|
|
|
|
it("allows dirty working directory with --force flag", async () => {
|
|
const testDir = setupGitTest();
|
|
|
|
await Bun.write(join(testDir, "untracked.txt"), "untracked content");
|
|
|
|
const { output, code, error } = await runCommand([bunExe(), "pm", "version", "patch", "--force"], testDir);
|
|
|
|
expect(code).toBe(0);
|
|
expect(error.trim()).toBe("");
|
|
expect(output.trim()).toBe("v1.0.1");
|
|
|
|
const { output: tagOutput } = await runCommand(["git", "tag", "-l"], testDir);
|
|
expect(tagOutput).toContain("v1.0.1");
|
|
|
|
const { output: logOutput } = await runCommand(["git", "log", "--oneline"], testDir);
|
|
expect(logOutput).toContain("v1.0.1");
|
|
});
|
|
|
|
it("works without git when no repo is present", async () => {
|
|
const testDir4 = setupTest();
|
|
|
|
const { output: output4, code: code4 } = await runCommand([bunExe(), "pm", "version", "patch"], testDir4);
|
|
|
|
expect(code4).toBe(0);
|
|
expect(output4.trim()).toBe("v1.0.1");
|
|
|
|
const packageJson = await Bun.file(`${testDir4}/package.json`).json();
|
|
expect(packageJson.version).toBe("1.0.1");
|
|
});
|
|
|
|
it("respects --no-git-tag-version flag", async () => {
|
|
const testDir5 = setupGitTest();
|
|
const { output: output5, code: code5 } = await runCommand(
|
|
[bunExe(), "pm", "version", "patch", "--no-git-tag-version"],
|
|
testDir5,
|
|
);
|
|
|
|
expect(code5).toBe(0);
|
|
expect(output5.trim()).toBe("v1.0.1");
|
|
|
|
const packageJson5 = await Bun.file(`${testDir5}/package.json`).json();
|
|
expect(packageJson5.version).toBe("1.0.1");
|
|
|
|
const { output: tagOutput5 } = await runCommand(["git", "tag", "-l"], testDir5);
|
|
expect(tagOutput5.trim()).toBe("");
|
|
|
|
const { output: logOutput5 } = await runCommand(["git", "log", "--oneline"], testDir5);
|
|
expect(logOutput5).toContain("Initial commit");
|
|
expect(logOutput5).not.toContain("v1.0.1");
|
|
});
|
|
|
|
it("respects --git-tag-version=false flag", async () => {
|
|
const testDir6 = setupGitTest();
|
|
const { output: output6, code: code6 } = await runCommand(
|
|
[bunExe(), "pm", "version", "patch", "--git-tag-version=false"],
|
|
testDir6,
|
|
);
|
|
|
|
expect(code6).toBe(0);
|
|
expect(output6.trim()).toBe("v1.0.1");
|
|
|
|
const packageJson6 = await Bun.file(`${testDir6}/package.json`).json();
|
|
expect(packageJson6.version).toBe("1.0.1");
|
|
|
|
const { output: tagOutput6 } = await runCommand(["git", "tag", "-l"], testDir6);
|
|
expect(tagOutput6.trim()).toBe("");
|
|
|
|
const { output: logOutput6 } = await runCommand(["git", "log", "--oneline"], testDir6);
|
|
expect(logOutput6).toContain("Initial commit");
|
|
expect(logOutput6).not.toContain("v1.0.1");
|
|
});
|
|
|
|
it("respects --git-tag-version=true flag", async () => {
|
|
const testDir7 = setupGitTest();
|
|
const { output: output7, code: code7 } = await runCommand(
|
|
[bunExe(), "pm", "version", "patch", "--git-tag-version=true"],
|
|
testDir7,
|
|
);
|
|
|
|
expect(code7).toBe(0);
|
|
expect(output7.trim()).toBe("v1.0.1");
|
|
|
|
const packageJson7 = await Bun.file(`${testDir7}/package.json`).json();
|
|
expect(packageJson7.version).toBe("1.0.1");
|
|
|
|
const { output: tagOutput7 } = await runCommand(["git", "tag", "-l"], testDir7);
|
|
expect(tagOutput7).toContain("v1.0.1");
|
|
|
|
const { output: logOutput7 } = await runCommand(["git", "log", "--oneline"], testDir7);
|
|
expect(logOutput7).toContain("v1.0.1");
|
|
});
|
|
|
|
it("supports %s substitution in commit messages", async () => {
|
|
const testDir8 = setupGitTest();
|
|
const { output: output8, code: code8 } = await runCommand(
|
|
[bunExe(), "pm", "version", "patch", "--message", "Bump version to %s"],
|
|
testDir8,
|
|
);
|
|
|
|
expect(code8).toBe(0);
|
|
expect(output8.trim()).toBe("v1.0.1");
|
|
|
|
const { output: logOutput8 } = await runCommand(["git", "log", "--oneline", "-1"], testDir8);
|
|
expect(logOutput8).toContain("Bump version to 1.0.1");
|
|
|
|
const testDir9 = setupGitTest();
|
|
const { output: output9, code: code9 } = await runCommand(
|
|
[bunExe(), "pm", "version", "2.5.0", "-m", "Release %s with fixes"],
|
|
testDir9,
|
|
);
|
|
|
|
expect(code9).toBe(0);
|
|
expect(output9.trim()).toBe("v2.5.0");
|
|
|
|
const { output: logOutput9 } = await runCommand(["git", "log", "--oneline", "-1"], testDir9);
|
|
expect(logOutput9).toContain("Release 2.5.0 with fixes");
|
|
});
|
|
});
|
|
|
|
describe("JSON formatting preservation", () => {
|
|
it("preserves JSON formatting correctly", async () => {
|
|
const originalJson1 = `{
|
|
"name": "test",
|
|
"version": "1.0.0",
|
|
"scripts": {
|
|
"test": "echo test"
|
|
},
|
|
"dependencies": {
|
|
"lodash": "^4.17.21"
|
|
}
|
|
}`;
|
|
|
|
const testDir1 = tempDirWithFiles(`version-${i++}`, {
|
|
"package.json": originalJson1,
|
|
});
|
|
|
|
const { output: output1, code: code1 } = await runCommand(
|
|
[bunExe(), "pm", "version", "patch", "--no-git-tag-version"],
|
|
testDir1,
|
|
);
|
|
|
|
expect(code1).toBe(0);
|
|
expect(output1.trim()).toBe("v1.0.1");
|
|
|
|
const updatedJson1 = await Bun.file(`${testDir1}/package.json`).text();
|
|
|
|
expect(updatedJson1).toContain(' "version": "1.0.1"');
|
|
expect(updatedJson1).toContain('"name": "test"');
|
|
expect(updatedJson1).toContain(' "test": "echo test"');
|
|
|
|
expect(JSON.parse(updatedJson1)).toMatchObject({
|
|
name: "test",
|
|
version: "1.0.1",
|
|
scripts: {
|
|
test: "echo test",
|
|
},
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("prerelease handling", () => {
|
|
it("handles custom preid and prerelease scenarios", async () => {
|
|
const testDir1 = tempDirWithFiles(`version-${i++}`, {
|
|
"package.json": JSON.stringify({ name: "test", version: "1.0.0" }, null, 2),
|
|
});
|
|
|
|
const { output: output1, code: code1 } = await runCommand(
|
|
[bunExe(), "pm", "version", "prerelease", "--preid", "beta", "--no-git-tag-version"],
|
|
testDir1,
|
|
);
|
|
|
|
expect(code1).toBe(0);
|
|
expect(output1.trim()).toBe("v1.0.1-beta.0");
|
|
|
|
const testDir3 = tempDirWithFiles(`version-${i++}`, {
|
|
"package.json": JSON.stringify({ name: "test", version: "1.0.0" }, null, 2),
|
|
});
|
|
|
|
const { output: output3, code: code3 } = await runCommand(
|
|
[bunExe(), "pm", "version", "prerelease", "--no-git-tag-version"],
|
|
testDir3,
|
|
);
|
|
|
|
expect(code3).toBe(0);
|
|
expect(output3.trim()).toBe("v1.0.1-0");
|
|
|
|
const testDir5 = tempDirWithFiles(`version-${i++}`, {
|
|
"package.json": JSON.stringify({ name: "test", version: "1.0.0-alpha" }, null, 2),
|
|
});
|
|
|
|
const { output: output5, code: code5 } = await runCommand(
|
|
[bunExe(), "pm", "version", "prerelease", "--no-git-tag-version"],
|
|
testDir5,
|
|
);
|
|
|
|
expect(code5).toBe(0);
|
|
expect(output5.trim()).toBe("v1.0.0-alpha.1");
|
|
|
|
const testDir6 = tempDirWithFiles(`version-${i++}`, {
|
|
"package.json": JSON.stringify({ name: "test", version: "1.0.0-3" }, null, 2),
|
|
});
|
|
|
|
const { output: output6, code: code6 } = await runCommand(
|
|
[bunExe(), "pm", "version", "prerelease", "--no-git-tag-version"],
|
|
testDir6,
|
|
);
|
|
|
|
expect(code6).toBe(0);
|
|
expect(output6.trim()).toBe("v1.0.0-4");
|
|
});
|
|
|
|
it("should preserve prerelease identifiers correctly", async () => {
|
|
const scenarios = [
|
|
{
|
|
version: "1.0.3-alpha.1",
|
|
preid: "beta",
|
|
expected: {
|
|
patch: "1.0.3-alpha.1 → 1.0.4",
|
|
minor: "1.0.3-alpha.1 → 1.1.0",
|
|
major: "1.0.3-alpha.1 → 2.0.0",
|
|
prerelease: "1.0.3-alpha.1 → 1.0.3-beta.2",
|
|
prepatch: "1.0.3-alpha.1 → 1.0.4-beta.0",
|
|
preminor: "1.0.3-alpha.1 → 1.1.0-beta.0",
|
|
premajor: "1.0.3-alpha.1 → 2.0.0-beta.0",
|
|
},
|
|
},
|
|
{
|
|
version: "1.0.3-1",
|
|
preid: "abcd",
|
|
expected: {
|
|
patch: "1.0.3-1 → 1.0.4",
|
|
minor: "1.0.3-1 → 1.1.0",
|
|
major: "1.0.3-1 → 2.0.0",
|
|
prerelease: "1.0.3-1 → 1.0.3-abcd.2",
|
|
prepatch: "1.0.3-1 → 1.0.4-abcd.0",
|
|
preminor: "1.0.3-1 → 1.1.0-abcd.0",
|
|
premajor: "1.0.3-1 → 2.0.0-abcd.0",
|
|
},
|
|
},
|
|
{
|
|
version: "2.5.0-rc.3",
|
|
preid: "next",
|
|
expected: {
|
|
patch: "2.5.0-rc.3 → 2.5.1",
|
|
minor: "2.5.0-rc.3 → 2.6.0",
|
|
major: "2.5.0-rc.3 → 3.0.0",
|
|
prerelease: "2.5.0-rc.3 → 2.5.0-next.4",
|
|
prepatch: "2.5.0-rc.3 → 2.5.1-next.0",
|
|
preminor: "2.5.0-rc.3 → 2.6.0-next.0",
|
|
premajor: "2.5.0-rc.3 → 3.0.0-next.0",
|
|
},
|
|
},
|
|
{
|
|
version: "1.0.0-a",
|
|
preid: "b",
|
|
expected: {
|
|
patch: "1.0.0-a → 1.0.1",
|
|
minor: "1.0.0-a → 1.1.0",
|
|
major: "1.0.0-a → 2.0.0",
|
|
prerelease: "1.0.0-a → 1.0.0-b.1",
|
|
prepatch: "1.0.0-a → 1.0.1-b.0",
|
|
preminor: "1.0.0-a → 1.1.0-b.0",
|
|
premajor: "1.0.0-a → 2.0.0-b.0",
|
|
},
|
|
},
|
|
];
|
|
|
|
for (const scenario of scenarios) {
|
|
const testDir = tempDirWithFiles(`version-${i++}`, {
|
|
"package.json": JSON.stringify({ name: "test", version: scenario.version }, null, 2),
|
|
});
|
|
|
|
const { output, code } = await runCommand(
|
|
[bunExe(), "pm", "version", "--no-git-tag-version", `--preid=${scenario.preid}`],
|
|
testDir,
|
|
);
|
|
|
|
expect(code).toBe(0);
|
|
expect(output).toContain(`Current package version: v${scenario.version}`);
|
|
|
|
for (const [incrementType, expectedTransformation] of Object.entries(scenario.expected)) {
|
|
expect(output).toContain(`${incrementType.padEnd(10)} ${expectedTransformation}`);
|
|
}
|
|
}
|
|
|
|
const testDir2 = tempDirWithFiles(`version-${i++}`, {
|
|
"package.json": JSON.stringify({ name: "test", version: "1.0.3-alpha.1" }, null, 2),
|
|
});
|
|
|
|
const { output: output2, code: code2 } = await runCommand(
|
|
[bunExe(), "pm", "version", "--no-git-tag-version"],
|
|
testDir2,
|
|
);
|
|
|
|
expect(code2).toBe(0);
|
|
expect(output2).toContain("prerelease 1.0.3-alpha.1 → 1.0.3-alpha.2");
|
|
});
|
|
});
|
|
|
|
describe("lifecycle scripts", () => {
|
|
it("runs lifecycle scripts in correct order and handles failures", async () => {
|
|
const testDir1 = tempDirWithFiles(`version-${i++}`, {
|
|
"package.json": JSON.stringify(
|
|
{
|
|
name: "test",
|
|
version: "1.0.0",
|
|
scripts: {
|
|
preversion: "echo 'step1' >> lifecycle.log",
|
|
version: "echo 'step2' >> lifecycle.log",
|
|
postversion: "echo 'step3' >> lifecycle.log",
|
|
},
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
});
|
|
|
|
await Bun.spawn([bunExe(), "pm", "version", "patch", "--no-git-tag-version"], {
|
|
cwd: testDir1,
|
|
env: bunEnv,
|
|
stderr: "ignore",
|
|
stdout: "ignore",
|
|
}).exited;
|
|
|
|
expect(await Bun.file(join(testDir1, "lifecycle.log")).exists()).toBe(true);
|
|
const logContent = await Bun.file(join(testDir1, "lifecycle.log")).text();
|
|
expect(logContent.trim()).toBe("step1\nstep2\nstep3");
|
|
|
|
const testDir2 = tempDirWithFiles(`version-${i++}`, {
|
|
"package.json": JSON.stringify(
|
|
{
|
|
name: "test",
|
|
version: "1.0.0",
|
|
scripts: {
|
|
preversion: "echo $npm_lifecycle_event > event.log && echo $npm_lifecycle_script > script.log",
|
|
},
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
});
|
|
|
|
await Bun.spawn([bunExe(), "pm", "version", "patch", "--no-git-tag-version"], {
|
|
cwd: testDir2,
|
|
env: bunEnv,
|
|
stderr: "ignore",
|
|
stdout: "ignore",
|
|
}).exited;
|
|
|
|
expect(Bun.file(join(testDir2, "event.log")).exists()).resolves.toBe(true);
|
|
expect(Bun.file(join(testDir2, "script.log")).exists()).resolves.toBe(true);
|
|
|
|
const eventContent = await Bun.file(join(testDir2, "event.log")).text();
|
|
const scriptContent = await Bun.file(join(testDir2, "script.log")).text();
|
|
|
|
expect(eventContent.trim()).toBe("preversion");
|
|
expect(scriptContent.trim()).toContain("echo $npm_lifecycle_event");
|
|
|
|
const testDir3 = tempDirWithFiles(`version-${i++}`, {
|
|
"package.json": JSON.stringify(
|
|
{
|
|
name: "test",
|
|
version: "1.0.0",
|
|
scripts: {
|
|
preversion: "exit 1",
|
|
},
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
});
|
|
|
|
const proc = Bun.spawn([bunExe(), "pm", "version", "minor", "--no-git-tag-version"], {
|
|
cwd: testDir3,
|
|
env: bunEnv,
|
|
stderr: "pipe",
|
|
stdout: "ignore",
|
|
});
|
|
|
|
await proc.exited;
|
|
expect(proc.exitCode).toBe(1);
|
|
expect(await proc.stderr.text()).toContain('script "preversion" exited with code 1');
|
|
|
|
const packageJson = await Bun.file(join(testDir3, "package.json")).json();
|
|
expect(packageJson.version).toBe("1.0.0");
|
|
|
|
const testDir4 = tempDirWithFiles(`version-${i++}`, {
|
|
"package.json": JSON.stringify(
|
|
{
|
|
name: "test",
|
|
version: "1.0.0",
|
|
scripts: {
|
|
preversion: "mkdir -p build && echo 'built' > build/output.txt",
|
|
version: "cp build/output.txt version-output.txt",
|
|
postversion: "rm -rf build",
|
|
},
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
});
|
|
|
|
await Bun.spawn([bunExe(), "pm", "version", "patch", "--no-git-tag-version"], {
|
|
cwd: testDir4,
|
|
env: bunEnv,
|
|
stderr: "ignore",
|
|
stdout: "ignore",
|
|
}).exited;
|
|
|
|
expect(Bun.file(join(testDir4, "version-output.txt")).exists()).resolves.toBe(true);
|
|
expect(Bun.file(join(testDir4, "build")).exists()).resolves.toBe(false);
|
|
|
|
const content = await Bun.file(join(testDir4, "version-output.txt")).text();
|
|
expect(content.trim()).toBe("built");
|
|
|
|
const testDir5 = tempDirWithFiles(`version-${i++}`, {
|
|
"package.json": JSON.stringify(
|
|
{
|
|
name: "test",
|
|
version: "1.0.0",
|
|
scripts: {
|
|
preversion: "echo 'should not run' >> ignored.log",
|
|
version: "echo 'should not run' >> ignored.log",
|
|
postversion: "echo 'should not run' >> ignored.log",
|
|
},
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
});
|
|
|
|
const { output: output5, code: code5 } = await runCommand(
|
|
[bunExe(), "pm", "version", "patch", "--no-git-tag-version", "--ignore-scripts"],
|
|
testDir5,
|
|
);
|
|
|
|
expect(code5).toBe(0);
|
|
expect(output5.trim()).toBe("v1.0.1");
|
|
|
|
const packageJson5 = await Bun.file(join(testDir5, "package.json")).json();
|
|
expect(packageJson5.version).toBe("1.0.1");
|
|
|
|
expect(await Bun.file(join(testDir5, "ignored.log")).exists()).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("workspace and directory handling", () => {
|
|
it("should version workspace packages individually", async () => {
|
|
const testDir = setupMonorepoTest();
|
|
|
|
const { output: outputA, code: codeA } = await runCommand(
|
|
[bunExe(), "pm", "version", "patch", "--no-git-tag-version"],
|
|
join(testDir, "packages", "pkg-a"),
|
|
);
|
|
|
|
expect(codeA).toBe(0);
|
|
expect(outputA.trim()).toBe("v2.0.1");
|
|
|
|
const rootPackageJson = await Bun.file(`${testDir}/package.json`).json();
|
|
expect(rootPackageJson.version).toBe("1.0.0");
|
|
|
|
const pkgAJson = await Bun.file(`${testDir}/packages/pkg-a/package.json`).json();
|
|
const pkgBJson = await Bun.file(`${testDir}/packages/pkg-b/package.json`).json();
|
|
|
|
expect(pkgAJson.version).toBe("2.0.1");
|
|
expect(pkgBJson.version).toBe("3.0.0");
|
|
});
|
|
|
|
it("should work from subdirectories", async () => {
|
|
const testDir = tempDirWithFiles(`version-${i++}`, {
|
|
"package.json": JSON.stringify({ name: "test", version: "1.0.0" }, null, 2),
|
|
"src/index.js": "console.log('hello');",
|
|
});
|
|
|
|
const { output, code } = await runCommand(
|
|
[bunExe(), "pm", "version", "patch", "--no-git-tag-version"],
|
|
join(testDir, "src"),
|
|
);
|
|
|
|
expect(code).toBe(0);
|
|
expect(output.trim()).toBe("v1.0.1");
|
|
|
|
const packageJson = await Bun.file(`${testDir}/package.json`).json();
|
|
expect(packageJson.version).toBe("1.0.1");
|
|
|
|
const monorepoDir = setupMonorepoTest();
|
|
|
|
await Bun.write(join(monorepoDir, "packages", "pkg-a", "lib", "index.js"), "");
|
|
|
|
const { output: output2, code: code2 } = await runCommand(
|
|
[bunExe(), "pm", "version", "minor", "--no-git-tag-version"],
|
|
join(monorepoDir, "packages", "pkg-a", "lib"),
|
|
);
|
|
|
|
expect(code2).toBe(0);
|
|
expect(output2.trim()).toBe("v2.1.0");
|
|
|
|
const rootJson = await Bun.file(`${monorepoDir}/package.json`).json();
|
|
const pkgAJson = await Bun.file(`${monorepoDir}/packages/pkg-a/package.json`).json();
|
|
const pkgBJson = await Bun.file(`${monorepoDir}/packages/pkg-b/package.json`).json();
|
|
|
|
expect(rootJson.version).toBe("1.0.0");
|
|
expect(pkgAJson.version).toBe("2.1.0");
|
|
expect(pkgBJson.version).toBe("3.0.0");
|
|
});
|
|
});
|
|
});
|