Compare commits

...

2 Commits

Author SHA1 Message Date
autofix-ci[bot]
0c36b94382 [autofix.ci] apply automated fixes 2025-07-20 06:08:14 +00:00
Claude Bot
ef2ac4123e fix: ensure bunx always points to current bun version on Windows and POSIX
Fixes issue where bunx hardlinks/symlinks could become stale after bun upgrades,
causing bunx to execute old versions of bun.

Changes:
- Windows: Force recreation of bunx.exe hardlink during completions install
- POSIX: Check and update existing bunx symlinks to point to current bun executable
- Upgrade: Improve error reporting when completions/bunx update fails
- Use bun.sys instead of std.posix for consistency with codebase patterns

The fix ensures that both PATH and same-directory bunx links are updated
during the completions install process, which runs during installation
and auto-updates.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-20 05:51:20 +00:00
2 changed files with 87 additions and 33 deletions

View File

@@ -21,45 +21,86 @@ pub const InstallCompletionsCommand = struct {
fn installBunxSymlinkPosix(cwd: []const u8) !void {
var buf: bun.PathBuffer = undefined;
// don't install it if it's already there
if (bun.which(&buf, bun.getenvZ("PATH") orelse cwd, cwd, bunx_name) != null)
return;
// first try installing the symlink into the same directory as the bun executable
const exe = try bun.selfExePath();
var target_buf: bun.PathBuffer = undefined;
var target = std.fmt.bufPrint(&target_buf, "{s}/" ++ bunx_name, .{std.fs.path.dirname(exe).?}) catch unreachable;
std.posix.symlink(exe, target) catch {
outer: {
// Check and update bunx symlinks in both PATH and same directory as bun executable
var found_correct_symlink = false;
// Check if bunx exists in PATH and update if stale
if (bun.which(&buf, bun.getenvZ("PATH") orelse cwd, cwd, bunx_name)) |existing_bunx_path| {
var link_target_buf: bun.PathBuffer = undefined;
switch (bun.sys.readlink(existing_bunx_path, &link_target_buf)) {
.result => |link_target| {
if (strings.eql(link_target, exe)) {
found_correct_symlink = true;
} else {
// Remove stale symlink so we can create a new one
_ = bun.sys.unlink(existing_bunx_path);
}
},
.err => {
// If readlink fails, remove the existing file
_ = bun.sys.unlink(existing_bunx_path);
},
}
}
// Check if bunx exists in the same directory as bun executable and update if stale
var target = std.fmt.bufPrintZ(&target_buf, "{s}/" ++ bunx_name, .{std.fs.path.dirname(exe).?}) catch unreachable;
var same_dir_is_correct = false;
{
var link_target_buf: bun.PathBuffer = undefined;
switch (bun.sys.readlink(target, &link_target_buf)) {
.result => |link_target| {
if (strings.eql(link_target, exe)) {
same_dir_is_correct = true;
} else {
// Remove stale symlink so we can create a new one
_ = bun.sys.unlink(target);
}
},
.err => {
// If readlink fails (file doesn't exist or isn't a symlink), we'll create one
},
}
}
// If both symlinks are correct, no need to create new ones
if (found_correct_symlink and same_dir_is_correct) {
return;
}
// First try installing the symlink into the same directory as the bun executable
switch (bun.sys.symlink(exe, target)) {
.result => {},
.err => {
if (bun.getenvZ("BUN_INSTALL")) |install_dir| {
target = std.fmt.bufPrint(&target_buf, "{s}/bin/" ++ bunx_name, .{install_dir}) catch unreachable;
std.posix.symlink(exe, target) catch break :outer;
return;
target = std.fmt.bufPrintZ(&target_buf, "{s}/bin/" ++ bunx_name, .{install_dir}) catch unreachable;
switch (bun.sys.symlink(exe, target)) {
.result => return,
.err => {},
}
}
}
// if that fails, try $HOME/.bun/bin
outer: {
// if that fails, try $HOME/.bun/bin
if (bun.getenvZ(bun.DotEnv.home_env)) |home_dir| {
target = std.fmt.bufPrint(&target_buf, "{s}/.bun/bin/" ++ bunx_name, .{home_dir}) catch unreachable;
std.posix.symlink(exe, target) catch break :outer;
return;
target = std.fmt.bufPrintZ(&target_buf, "{s}/.bun/bin/" ++ bunx_name, .{home_dir}) catch unreachable;
switch (bun.sys.symlink(exe, target)) {
.result => return,
.err => {},
}
}
}
// if that fails, try $HOME/.local/bin
outer: {
// if that fails, try $HOME/.local/bin
if (bun.getenvZ(bun.DotEnv.home_env)) |home_dir| {
target = std.fmt.bufPrint(&target_buf, "{s}/.local/bin/" ++ bunx_name, .{home_dir}) catch unreachable;
std.posix.symlink(exe, target) catch break :outer;
return;
target = std.fmt.bufPrintZ(&target_buf, "{s}/.local/bin/" ++ bunx_name, .{home_dir}) catch unreachable;
_ = bun.sys.symlink(exe, target);
}
}
// otherwise...give up?
};
// otherwise...give up?
},
}
}
fn installBunxSymlinkWindows(_: []const u8) !void {
@@ -71,6 +112,7 @@ pub const InstallCompletionsCommand = struct {
var bunx_path_buf: bun.WPathBuffer = undefined;
// Always delete both .cmd and .exe versions first to ensure clean state
std.os.windows.DeleteFile(try bun.strings.concatBufT(u16, &bunx_path_buf, .{
&bun.windows.nt_object_prefix,
image_dirname,
@@ -85,14 +127,15 @@ pub const InstallCompletionsCommand = struct {
const bunx_path = bunx_path_with_z[0 .. bunx_path_with_z.len - 1 :0];
std.os.windows.DeleteFile(bunx_path, .{ .dir = null }) catch {};
// Force recreation of hardlink to ensure it points to current bun.exe
if (bun.windows.CreateHardLinkW(bunx_path, image_path, null) == 0) {
// if hard link fails, use a cmd script
// if hard link fails, use a cmd script that dynamically finds bun.exe
const script = "@%~dp0bun.exe x %*\n";
const bunx_cmd_with_z = try bun.strings.concatBufT(u16, &bunx_path_buf, .{
&bun.windows.nt_object_prefix,
image_dirname,
comptime bun.strings.literal(u16, bunx_name ++ ".exe\x00"),
comptime bun.strings.literal(u16, bunx_name ++ ".cmd\x00"),
});
const bunx_cmd = bunx_cmd_with_z[0 .. bunx_cmd_with_z.len - 1 :0];
// TODO: fix this zig bug, it is one line change to a few functions.

View File

@@ -841,7 +841,7 @@ pub const UpgradeCommand = struct {
};
}
// Ensure completions are up to date.
// Ensure completions are up to date, including bunx symlink/hardlink
{
var completions_argv = [_]string{
target_filename,
@@ -851,13 +851,24 @@ pub const UpgradeCommand = struct {
env_loader.map.put("IS_BUN_AUTO_UPDATE", "true") catch bun.outOfMemory();
var std_map = try env_loader.map.stdEnvMap(ctx.allocator);
defer std_map.deinit();
_ = std.process.Child.run(.{
if (std.process.Child.run(.{
.allocator = ctx.allocator,
.argv = &completions_argv,
.cwd = target_dirname,
.max_output_bytes = 4096,
.env_map = std_map.get(),
}) catch {};
})) |result| {
if (result.term.Exited != 0) {
Output.prettyErrorln("<r><yellow>warn<r>: Completions update exited with code {d}", .{result.term.Exited});
if (result.stderr.len > 0) {
Output.prettyErrorln("stderr: {s}", .{result.stderr});
}
Output.note("You may need to run 'bun completions' manually to update bunx", .{});
}
} else |err| {
Output.prettyErrorln("<r><yellow>warn<r>: Failed to update completions and bunx: {s}", .{@errorName(err)});
Output.note("You may need to run 'bun completions' manually to update bunx", .{});
}
}
Output.printStartEnd(ctx.start_time, std.time.nanoTimestamp());