Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/actions/setup/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ runs:
steps:
- uses: goto-bus-stop/setup-zig@v2
with:
version: 0.14.0
version: 0.15.2
10 changes: 6 additions & 4 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const bunv = "bunv";
pub fn build(b: *std.Build) !void {
const min_zig_version = try std.SemanticVersion.parse("0.13.0");
if (std.SemanticVersion.order(builtin.zig_version, min_zig_version) == .lt) {
std.debug.print("Bunv requires Zig 0.13.0 or above, got {}", .{builtin.zig_version});
std.debug.print("Bunv requires Zig 0.13.0 or above, got {f}", .{builtin.zig_version});
return error.InvalidZigVersion;
}

Expand All @@ -30,9 +30,11 @@ pub fn build(b: *std.Build) !void {
fn addExe(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, version: std.SemanticVersion, comptime name: []const u8) void {
const exe = b.addExecutable(.{
.name = name,
.root_source_file = b.path("src/" ++ name ++ ".zig"),
.target = target,
.optimize = optimize,
.root_module = b.createModule(.{
.root_source_file = b.path("src/" ++ name ++ ".zig"),
.target = target,
.optimize = optimize,
}),
.version = version,
});

Expand Down
18 changes: 10 additions & 8 deletions src/bunv.zig
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
const std = @import("std");
const mem = std.mem;
const utils = @import("utils.zig");
const vm = @import("vm.zig");
const builtin = @import("builtin");

const config = @import("config");

const c = @import("colors.zig");
const utils = @import("utils.zig");
const vm = @import("vm.zig");

pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
Expand All @@ -13,7 +15,7 @@ pub fn main() !void {

const is_debug = try utils.isDebug(allocator);
if (is_debug) {
std.debug.print("Bunv Version: {}\n", .{config.version});
std.debug.print("Bunv Version: {f}\n", .{config.version});
std.debug.print("Operating System: {s}\n", .{@tagName(builtin.os.tag)});
std.debug.print("Architecture: {s}\n", .{@tagName(builtin.cpu.arch)});
}
Expand Down Expand Up @@ -51,12 +53,12 @@ pub fn main() !void {
}

fn listVersions(allocator: mem.Allocator, config_dir: []const u8) !void {
const installed_versions = try vm.getInstalledVersions(allocator, config_dir);
var installed_versions = try vm.getInstalledVersions(allocator, config_dir);
defer {
for (installed_versions.items) |item| {
allocator.free(item);
}
installed_versions.deinit();
installed_versions.deinit(allocator);
}

try printInstalledVersions(allocator, config_dir, installed_versions);
Expand All @@ -82,7 +84,7 @@ fn printInstalledVersions(allocator: mem.Allocator, config_dir: []const u8, vers
}

fn printHelp() !void {
std.debug.print("\n{s}{s}Bunv{s} - Manage installed versions of Bun {s}({}){s}\n\n", .{ c.bold, c.blue, c.reset, c.dim, config.version, c.reset });
std.debug.print("\n{s}{s}Bunv{s} - Manage installed versions of Bun {s}({f}){s}\n\n", .{ c.bold, c.blue, c.reset, c.dim, config.version, c.reset });
std.debug.print("{s}Commands:{s}\n", .{ c.bold, c.reset });
std.debug.print(" List installed Bun versions\n", .{});
std.debug.print(" {s}{s}rm{s} {s}<version>{s} Remove an installed Bun version\n", .{ c.bold, c.yellow, c.reset, c.dim, c.reset });
Expand All @@ -92,12 +94,12 @@ fn printHelp() !void {

fn removeVersion(allocator: mem.Allocator, config_dir: []const u8, version: []const u8) !void {
// Check if version is installed
const installed_versions = try vm.getInstalledVersions(allocator, config_dir);
var installed_versions = try vm.getInstalledVersions(allocator, config_dir);
defer {
for (installed_versions.items) |item| {
allocator.free(item);
}
installed_versions.deinit();
installed_versions.deinit(allocator);
}

var version_exists = false;
Expand Down
39 changes: 33 additions & 6 deletions src/utils.zig
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,34 @@ const mem = std.mem;
const fs = std.fs;
const json = std.json;
const builtin = @import("builtin");

const vm = @import("vm.zig");

pub const Cmd = enum {
bun,
bunx,
};

/// Stringify a list of strings into a formatted string representation: ["item1", "item2", "item3"]
/// Caller owns the returned memory and must free it.
pub fn stringifyStringList(allocator: mem.Allocator, list: []const []const u8) ![]const u8 {
var result: std.ArrayList(u8) = .empty;
errdefer result.deinit(allocator);

try result.append(allocator, '[');
for (list, 0..) |item, i| {
if (i > 0) {
try result.appendSlice(allocator, ", ");
}
try result.append(allocator, '"');
try result.appendSlice(allocator, item);
try result.append(allocator, '"');
}
try result.append(allocator, ']');

return result.toOwnedSlice(allocator);
}

pub fn run(allocator: mem.Allocator, cmd: Cmd) !void {
const is_debug = try isDebug(allocator);
if (is_debug) std.debug.print("Executable: {}\n", .{cmd});
Expand All @@ -30,20 +51,26 @@ pub fn run(allocator: mem.Allocator, cmd: Cmd) !void {
const bin = try fs.path.join(allocator, &[_][]const u8{ config_dir, "versions", project_version, "bin", "bun" });
defer allocator.free(bin);

var new_args = try std.ArrayList([]const u8).initCapacity(allocator, 5);
defer new_args.deinit();
var new_args: std.ArrayList([]const u8) = .empty;
defer new_args.deinit(allocator);

try new_args.append(bin);
if (cmd == .bunx) try new_args.append("x");
try new_args.append(allocator, bin);
if (cmd == .bunx) try new_args.append(allocator, "x");

var args = try std.process.argsAlloc(allocator);
defer std.process.argsFree(allocator, args);

for (args[1..]) |arg| {
try new_args.append(arg);
try new_args.append(allocator, arg);
}

if (is_debug) std.debug.print("Original args: {s}\nModified args: {s}\n---\n", .{ args, new_args.items });
if (is_debug) {
const original_str = try stringifyStringList(allocator, args);
defer allocator.free(original_str);
const modified_str = try stringifyStringList(allocator, new_args.items);
defer allocator.free(modified_str);
std.debug.print("Original args: {s}\nModified args: {s}\n---\n", .{ original_str, modified_str });
}
return runBunCmd(allocator, new_args.items);
}

Expand Down
58 changes: 34 additions & 24 deletions src/vm.zig
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ const mem = std.mem;
const fs = std.fs;
const json = std.json;
const http = std.http;
const utils = @import("utils.zig");

const c = @import("colors.zig");
const utils = @import("utils.zig");

pub fn getInstalledVersions(allocator: mem.Allocator, config_dir: []const u8) !std.ArrayList([]const u8) {
const versions_dir_path = try getVersionsDir(allocator, config_dir);
defer allocator.free(versions_dir_path);

var result = std.ArrayList([]const u8).init(allocator);
var result: std.ArrayList([]const u8) = .empty;

var versions_dir = fs.openDirAbsolute(versions_dir_path, .{ .iterate = true }) catch |err| switch (err) {
error.FileNotFound => return result,
Expand All @@ -30,7 +31,7 @@ pub fn getInstalledVersions(allocator: mem.Allocator, config_dir: []const u8) !s
defer allocator.free(bin);

if (try utils.file_exists(bin)) {
try result.append(version);
try result.append(allocator, version);
} else {
allocator.free(version);
}
Expand Down Expand Up @@ -90,13 +91,17 @@ pub fn detectProjectVersion(allocator: mem.Allocator, is_debug: bool) !?[]const
pub fn getLatestLocalVersion(allocator: mem.Allocator, is_debug: bool, config_dir: []const u8) !?[]const u8 {
if (is_debug) std.debug.print("Getting latest local version...\n", .{});

const installed_versions = try getInstalledVersions(allocator, config_dir);
if (is_debug) std.debug.print("{d} versions: {s}\n", .{ installed_versions.items.len, installed_versions.items });
var installed_versions = try getInstalledVersions(allocator, config_dir);
if (is_debug) {
const versions_str = try utils.stringifyStringList(allocator, installed_versions.items);
defer allocator.free(versions_str);
std.debug.print("{d} versions: {s}\n", .{ installed_versions.items.len, versions_str });
}
defer {
for (installed_versions.items) |item| {
allocator.free(item);
}
installed_versions.deinit();
installed_versions.deinit(allocator);
}

if (installed_versions.items.len == 0) {
Expand All @@ -120,19 +125,20 @@ pub fn getLatestRemoteVersion(allocator: mem.Allocator, is_debug: bool) ![]const
const uri = try std.Uri.parse("https://ungh.cc/repos/oven-sh/bun/releases/latest");
const buf = try allocator.alloc(u8, 1024 * 1024 * 4);
defer allocator.free(buf);
var req = try client.open(
.GET,
uri,
.{ .server_header_buffer = buf, .keep_alive = false, .extra_headers = &[_]http.Header{accept_header} },
);
var req = try client.request(.GET, uri, .{
.keep_alive = false,
.headers = .{ .accept_encoding = .omit },
.extra_headers = &[_]http.Header{accept_header},
});
defer req.deinit();

try req.send();
try req.finish();
try req.wait();
try req.sendBodiless();

var rdr = req.reader();
const body = try rdr.readAllAlloc(allocator, 1024 * 1024 * 4);
var redirect_buffer: [8 * 1024]u8 = undefined;
var response = try req.receiveHead(&redirect_buffer);
var transfer_buffer: [4 * 1024]u8 = undefined;
const reader = response.reader(&transfer_buffer);
const body = try reader.allocRemaining(allocator, .limited(1024 * 1024 * 4));
defer allocator.free(body);
if (is_debug) std.debug.print("Latest release: {s}\n", .{body});

Expand Down Expand Up @@ -238,25 +244,29 @@ fn confirmInstallation(version: []const u8) !void {
}
}

const stdout = std.io.getStdOut().writer();
var stdout_buf: [1024]u8 = undefined;
var stdout = std.fs.File.stdout().writer(&stdout_buf);

// Check if stdin is a TTY (interactive)
const is_interactive = std.io.getStdIn().isTty();
const stdin_file = std.fs.File.stdin();
const is_interactive = stdin_file.isTty();

if (is_interactive) {
const stdin = std.io.getStdIn().reader();
var stdin_buf: [1024]u8 = undefined;
var stdin = stdin_file.reader(&stdin_buf);

try stdout.print("{s}Bun v{s} is not installed. Do you want to install it? [y/N]{s} ", .{ c.yellow, version, c.reset });
var buffer: [2]u8 = undefined;
const user_input = stdin.readUntilDelimiterOrEof(&buffer, '\n') catch |err| switch (err) {
try stdout.interface.print("{s}Bun v{s} is not installed. Do you want to install it? [y/N]{s} ", .{ c.yellow, version, c.reset });
try stdout.interface.flush();
const user_input = stdin.interface.takeDelimiterExclusive('\n') catch |err| switch (err) {
error.StreamTooLong => "N",
error.EndOfStream => "N",
else => return err,
} orelse "N";
};

if (mem.eql(u8, user_input, "y")) return;
} else {
// Non-interactive mode, just display message and abort
try stdout.print("{s}Bun v{s} is not installed. Run in an interactive terminal to install or set BUNV_AUTO_INSTALL=1.{s}\n", .{ c.yellow, version, c.reset });
try stdout.interface.print("{s}Bun v{s} is not installed. Run in an interactive terminal to install or set BUNV_AUTO_INSTALL=1.{s}\n", .{ c.yellow, version, c.reset });
}

std.debug.print("Installation aborted by user\n", .{});
Expand Down
Loading