followup for bun pm version (#20799)

This commit is contained in:
Michael H
2025-07-08 04:21:36 +10:00
committed by GitHub
parent dacb75dc1f
commit 0399ae0ee9
3 changed files with 888 additions and 800 deletions

View File

@@ -175,13 +175,14 @@ Increment:
Options:
--no-git-tag-version Skip git operations
--allow-same-version Prevents throwing error if version is the same
--message=<val>, -m Custom commit message
--preid=<val> Prerelease identifier
--message=<val>, -m Custom commit message, use %s for version substitution
--preid=<val> Prerelease identifier (i.e beta → 1.0.1-beta.0)
--force, -f Bypass dirty git history check
Examples:
$ bun pm version patch
$ bun pm version 1.2.3 --no-git-tag-version
$ bun pm version prerelease --preid beta
$ bun pm version prerelease --preid beta --message "Release beta: %s"
```
To bump the version in `package.json`:

View File

@@ -11,6 +11,7 @@ const logger = bun.logger;
const JSON = bun.JSON;
const RunCommand = bun.RunCommand;
const Environment = bun.Environment;
const JSPrinter = bun.js_printer;
pub const PmVersionCommand = struct {
const VersionType = enum {
@@ -59,30 +60,45 @@ pub const PmVersionCommand = struct {
defer ctx.allocator.free(package_json_contents);
const package_json_source = logger.Source.initPathString(package_json_path, package_json_contents);
const json = JSON.parsePackageJSONUTF8(&package_json_source, ctx.log, ctx.allocator) catch |err| {
const json_result = JSON.parsePackageJSONUTF8WithOpts(
&package_json_source,
ctx.log,
ctx.allocator,
.{
.is_json = true,
.allow_comments = true,
.allow_trailing_commas = true,
.guess_indentation = true,
},
) catch |err| {
Output.errGeneric("Failed to parse package.json: {s}", .{@errorName(err)});
Global.exit(1);
};
const scripts = json.asProperty("scripts");
var json = json_result.root;
if (json.data != .e_object) {
Output.errGeneric("Failed to parse package.json: root must be an object", .{});
Global.exit(1);
}
const scripts = if (pm.options.do.run_scripts) json.asProperty("scripts") else null;
const scripts_obj = if (scripts) |s| if (s.expr.data == .e_object) s.expr else null else null;
if (pm.options.do.run_scripts) {
if (scripts_obj) |s| {
if (s.get("preversion")) |script| {
if (script.asString(ctx.allocator)) |script_command| {
try RunCommand.runPackageScriptForeground(
ctx,
ctx.allocator,
script_command,
"preversion",
package_json_dir,
pm.env,
&.{},
pm.options.log_level == .silent,
ctx.debug.use_system_shell,
);
}
if (scripts_obj) |s| {
if (s.get("preversion")) |script| {
if (script.asString(ctx.allocator)) |script_command| {
try RunCommand.runPackageScriptForeground(
ctx,
ctx.allocator,
script_command,
"preversion",
package_json_dir,
pm.env,
&.{},
pm.options.log_level == .silent,
ctx.debug.use_system_shell,
);
}
}
}
@@ -96,49 +112,63 @@ pub const PmVersionCommand = struct {
else => {},
}
}
Output.errGeneric("No version field found in package.json", .{});
Global.exit(1);
break :brk_version null;
};
const new_version_str = try calculateNewVersion(ctx.allocator, current_version, version_type, new_version, pm.options.preid, package_json_dir);
const new_version_str = try calculateNewVersion(ctx.allocator, current_version orelse "0.0.0", version_type, new_version, pm.options.preid, package_json_dir);
defer ctx.allocator.free(new_version_str);
if (!pm.options.allow_same_version and strings.eql(current_version, new_version_str)) {
Output.errGeneric("Version not changed", .{});
Global.exit(1);
if (current_version) |version| {
if (!pm.options.allow_same_version and strings.eql(version, new_version_str)) {
Output.errGeneric("Version not changed", .{});
Global.exit(1);
}
}
{
const updated_contents = try updateVersionString(ctx.allocator, package_json_contents, current_version, new_version_str);
defer ctx.allocator.free(updated_contents);
try json.data.e_object.putString(ctx.allocator, "version", new_version_str);
const file = std.fs.cwd().openFile(package_json_path, .{ .mode = .write_only }) catch |err| {
Output.errGeneric("Failed to open package.json for writing: {s}", .{@errorName(err)});
var buffer_writer = JSPrinter.BufferWriter.init(ctx.allocator);
buffer_writer.append_newline = package_json_contents.len > 0 and package_json_contents[package_json_contents.len - 1] == '\n';
var package_json_writer = JSPrinter.BufferPrinter.init(buffer_writer);
_ = JSPrinter.printJSON(
@TypeOf(&package_json_writer),
&package_json_writer,
json,
&package_json_source,
.{
.indent = json_result.indentation,
.mangled_props = null,
},
) catch |err| {
Output.errGeneric("Failed to save package.json: {s}", .{@errorName(err)});
Global.exit(1);
};
defer file.close();
try file.seekTo(0);
try file.setEndPos(0);
try file.writeAll(updated_contents);
std.fs.cwd().writeFile(.{
.sub_path = package_json_path,
.data = package_json_writer.ctx.writtenWithoutTrailingZero(),
}) catch |err| {
Output.errGeneric("Failed to write package.json: {s}", .{@errorName(err)});
Global.exit(1);
};
}
if (pm.options.do.run_scripts) {
if (scripts_obj) |s| {
if (s.get("version")) |script| {
if (script.asString(ctx.allocator)) |script_command| {
try RunCommand.runPackageScriptForeground(
ctx,
ctx.allocator,
script_command,
"version",
package_json_dir,
pm.env,
&.{},
pm.options.log_level == .silent,
ctx.debug.use_system_shell,
);
}
if (scripts_obj) |s| {
if (s.get("version")) |script| {
if (script.asString(ctx.allocator)) |script_command| {
try RunCommand.runPackageScriptForeground(
ctx,
ctx.allocator,
script_command,
"version",
package_json_dir,
pm.env,
&.{},
pm.options.log_level == .silent,
ctx.debug.use_system_shell,
);
}
}
}
@@ -147,22 +177,20 @@ pub const PmVersionCommand = struct {
try gitCommitAndTag(ctx.allocator, new_version_str, pm.options.message, package_json_dir);
}
if (pm.options.do.run_scripts) {
if (scripts_obj) |s| {
if (s.get("postversion")) |script| {
if (script.asString(ctx.allocator)) |script_command| {
try RunCommand.runPackageScriptForeground(
ctx,
ctx.allocator,
script_command,
"postversion",
package_json_dir,
pm.env,
&.{},
pm.options.log_level == .silent,
ctx.debug.use_system_shell,
);
}
if (scripts_obj) |s| {
if (s.get("postversion")) |script| {
if (script.asString(ctx.allocator)) |script_command| {
try RunCommand.runPackageScriptForeground(
ctx,
ctx.allocator,
script_command,
"postversion",
package_json_dir,
pm.env,
&.{},
pm.options.log_level == .silent,
ctx.debug.use_system_shell,
);
}
}
}
@@ -201,7 +229,7 @@ pub const PmVersionCommand = struct {
return;
}
if (!try isGitClean(cwd) and !pm.options.force) {
if (!pm.options.force and !try isGitClean(cwd)) {
Output.errGeneric("Git working directory not clean.", .{});
Global.exit(1);
}
@@ -256,6 +284,15 @@ pub const PmVersionCommand = struct {
Output.prettyln("Current package version: <green>v{s}<r>", .{version});
}
const patch_version = try calculateNewVersion(ctx.allocator, current_version, .patch, null, pm.options.preid, cwd);
const minor_version = try calculateNewVersion(ctx.allocator, current_version, .minor, null, pm.options.preid, cwd);
const major_version = try calculateNewVersion(ctx.allocator, current_version, .major, null, pm.options.preid, cwd);
const prerelease_version = try calculateNewVersion(ctx.allocator, current_version, .prerelease, null, pm.options.preid, cwd);
defer ctx.allocator.free(patch_version);
defer ctx.allocator.free(minor_version);
defer ctx.allocator.free(major_version);
defer ctx.allocator.free(prerelease_version);
const increment_help_text =
\\
\\<b>Increment<r>:
@@ -266,13 +303,20 @@ pub const PmVersionCommand = struct {
\\
;
Output.pretty(increment_help_text, .{
current_version, try calculateNewVersion(ctx.allocator, current_version, .patch, null, pm.options.preid, cwd),
current_version, try calculateNewVersion(ctx.allocator, current_version, .minor, null, pm.options.preid, cwd),
current_version, try calculateNewVersion(ctx.allocator, current_version, .major, null, pm.options.preid, cwd),
current_version, try calculateNewVersion(ctx.allocator, current_version, .prerelease, null, pm.options.preid, cwd),
current_version, patch_version,
current_version, minor_version,
current_version, major_version,
current_version, prerelease_version,
});
if (strings.indexOfChar(current_version, '-') != null or pm.options.preid.len > 0) {
const prepatch_version = try calculateNewVersion(ctx.allocator, current_version, .prepatch, null, pm.options.preid, cwd);
const preminor_version = try calculateNewVersion(ctx.allocator, current_version, .preminor, null, pm.options.preid, cwd);
const premajor_version = try calculateNewVersion(ctx.allocator, current_version, .premajor, null, pm.options.preid, cwd);
defer ctx.allocator.free(prepatch_version);
defer ctx.allocator.free(preminor_version);
defer ctx.allocator.free(premajor_version);
const prerelease_help_text =
\\ <cyan>prepatch<r> <d>{s} → {s}<r>
\\ <cyan>preminor<r> <d>{s} → {s}<r>
@@ -280,12 +324,15 @@ pub const PmVersionCommand = struct {
\\
;
Output.pretty(prerelease_help_text, .{
current_version, try calculateNewVersion(ctx.allocator, current_version, .prepatch, null, pm.options.preid, cwd),
current_version, try calculateNewVersion(ctx.allocator, current_version, .preminor, null, pm.options.preid, cwd),
current_version, try calculateNewVersion(ctx.allocator, current_version, .premajor, null, pm.options.preid, cwd),
current_version, prepatch_version,
current_version, preminor_version,
current_version, premajor_version,
});
}
const beta_prerelease_version = try calculateNewVersion(ctx.allocator, current_version, .prerelease, null, "beta", cwd);
defer ctx.allocator.free(beta_prerelease_version);
const set_specific_version_help_text =
\\ <cyan>from-git<r> <d>Use version from latest git tag<r>
\\ <blue>1.2.3<r> <d>Set specific version<r>
@@ -293,78 +340,22 @@ pub const PmVersionCommand = struct {
\\<b>Options<r>:
\\ <cyan>--no-git-tag-version<r> <d>Skip git operations<r>
\\ <cyan>--allow-same-version<r> <d>Prevents throwing error if version is the same<r>
\\ <cyan>--message<d>=\<val\><r>, <cyan>-m<r> <d>Custom commit message<r>
\\ <cyan>--preid<d>=\<val\><r> <d>Prerelease identifier<r>
\\ <cyan>--message<d>=\<val\><r>, <cyan>-m<r> <d>Custom commit message, use %s for version substitution<r>
\\ <cyan>--preid<d>=\<val\><r> <d>Prerelease identifier (i.e beta → {s})<r>
\\ <cyan>--force<r>, <cyan>-f<r> <d>Bypass dirty git history check<r>
\\
\\<b>Examples<r>:
\\ <d>$<r> <b><green>bun pm version<r> <cyan>patch<r>
\\ <d>$<r> <b><green>bun pm version<r> <blue>1.2.3<r> <cyan>--no-git-tag-version<r>
\\ <d>$<r> <b><green>bun pm version<r> <cyan>prerelease<r> <cyan>--preid<r> <blue>beta<r>
\\ <d>$<r> <b><green>bun pm version<r> <cyan>prerelease<r> <cyan>--preid<r> <blue>beta<r> <cyan>--message<r> <blue>"Release beta: %s"<r>
\\
\\More info: <magenta>https://bun.sh/docs/cli/pm#version<r>
\\
;
Output.pretty(set_specific_version_help_text, .{});
Output.pretty(set_specific_version_help_text, .{beta_prerelease_version});
Output.flush();
}
fn updateVersionString(allocator: std.mem.Allocator, contents: []const u8, old_version: []const u8, new_version: []const u8) ![]const u8 {
const version_key = "\"version\"";
var search_start: usize = 0;
while (std.mem.indexOfPos(u8, contents, search_start, version_key)) |key_pos| {
var colon_pos = key_pos + version_key.len;
while (colon_pos < contents.len and (contents[colon_pos] == ' ' or contents[colon_pos] == '\t')) {
colon_pos += 1;
}
if (colon_pos >= contents.len or contents[colon_pos] != ':') {
search_start = key_pos + 1;
continue;
}
colon_pos += 1;
while (colon_pos < contents.len and (contents[colon_pos] == ' ' or contents[colon_pos] == '\t')) {
colon_pos += 1;
}
if (colon_pos >= contents.len or contents[colon_pos] != '"') {
search_start = key_pos + 1;
continue;
}
const value_start = colon_pos + 1;
var value_end = value_start;
while (value_end < contents.len and contents[value_end] != '"') {
if (contents[value_end] == '\\' and value_end + 1 < contents.len) {
value_end += 2;
} else {
value_end += 1;
}
}
if (value_end >= contents.len) {
search_start = key_pos + 1;
continue;
}
const current_value = contents[value_start..value_end];
if (strings.eql(current_value, old_version)) {
var result = std.ArrayList(u8).init(allocator);
try result.appendSlice(contents[0..value_start]);
try result.appendSlice(new_version);
try result.appendSlice(contents[value_end..]);
return result.toOwnedSlice();
}
search_start = value_end + 1;
}
Output.errGeneric("Version not found in package.json", .{});
Global.exit(1);
}
fn calculateNewVersion(allocator: std.mem.Allocator, current_str: []const u8, version_type: VersionType, specific_version: ?[]const u8, preid: []const u8, cwd: []const u8) bun.OOM![]const u8 {
if (version_type == .specific) {
return try allocator.dupe(u8, specific_version.?);
@@ -487,12 +478,15 @@ pub const PmVersionCommand = struct {
.windows = if (Environment.isWindows) .{
.loop = bun.JSC.EventLoopHandle.init(bun.JSC.MiniEventLoop.initGlobal(null)),
},
}) catch return false;
}) catch |err| {
Output.errGeneric("Failed to spawn git process: {s}", .{@errorName(err)});
Global.exit(1);
};
switch (proc) {
.err => |err| {
Output.err(err, "Failed to spawn git process", .{});
return false;
Global.exit(1);
},
.result => |result| {
return result.isOK() and result.stdout.items.len == 0;
@@ -566,24 +560,27 @@ pub const PmVersionCommand = struct {
},
}) catch |err| {
Output.errGeneric("Git add failed: {s}", .{@errorName(err)});
return;
Global.exit(1);
};
switch (stage_proc) {
.err => |err| {
Output.err(err, "Git add failed unexpectedly", .{});
return;
Global.exit(1);
},
.result => |result| {
if (!result.isOK()) {
Output.errGeneric("Git add failed with exit code {d}", .{result.status.exited.code});
return;
Global.exit(1);
}
},
}
const commit_message = custom_message orelse try std.fmt.allocPrint(allocator, "v{s}", .{version});
defer if (custom_message == null) allocator.free(commit_message);
const commit_message = if (custom_message) |msg|
try std.mem.replaceOwned(u8, allocator, msg, "%s", version)
else
try std.fmt.allocPrint(allocator, "v{s}", .{version});
defer allocator.free(commit_message);
const commit_proc = bun.spawnSync(&.{
.argv = &.{ git_path, "commit", "-m", commit_message },
@@ -597,18 +594,18 @@ pub const PmVersionCommand = struct {
},
}) catch |err| {
Output.errGeneric("Git commit failed: {s}", .{@errorName(err)});
return;
Global.exit(1);
};
switch (commit_proc) {
.err => |err| {
Output.err(err, "Git commit failed unexpectedly", .{});
return;
Global.exit(1);
},
.result => |result| {
if (!result.isOK()) {
Output.errGeneric("Git commit failed", .{});
return;
Global.exit(1);
}
},
}
@@ -628,18 +625,18 @@ pub const PmVersionCommand = struct {
},
}) catch |err| {
Output.errGeneric("Git tag failed: {s}", .{@errorName(err)});
return;
Global.exit(1);
};
switch (tag_proc) {
.err => |err| {
Output.err(err, "Git tag failed unexpectedly", .{});
return;
Global.exit(1);
},
.result => |result| {
if (!result.isOK()) {
Output.errGeneric("Git tag failed", .{});
return;
Global.exit(1);
}
},
}

File diff suppressed because it is too large Load Diff