diff --git a/cli/cli.zig b/cli/cli.zig index 61b9384..e6853b0 100644 --- a/cli/cli.zig +++ b/cli/cli.zig @@ -1,6 +1,7 @@ const std = @import("std"); const args = @import("args"); const init = @import("commands/init.zig"); +const update = @import("commands/update.zig"); const generate = @import("commands/generate.zig"); const Options = struct { @@ -14,6 +15,7 @@ const Options = struct { .usage_summary = "[COMMAND]", .option_docs = .{ .init = "Initialize a new project", + .update = "Update current project to latest version of Jetzig", .generate = "Generate scaffolding", .help = "Print help and exit", }, @@ -22,6 +24,7 @@ const Options = struct { const Verb = union(enum) { init: init.Options, + update: update.Options, generate: generate.Options, g: generate.Options, }; @@ -52,6 +55,7 @@ pub fn main() !void { \\Commands: \\ \\ init Initialize a new project. + \\ update Update current project to latest version of Jetzig. \\ generate Generate scaffolding. \\ \\ Pass --help to any command for more information, e.g. `jetzig init --help` @@ -77,6 +81,13 @@ fn run(allocator: std.mem.Allocator, options: args.ParseArgsResult(Options, Verb options.positionals, .{ .help = options.options.help }, ), + .update => |opts| update.run( + allocator, + opts, + writer, + options.positionals, + .{ .help = options.options.help }, + ), }; } } diff --git a/cli/commands/init.zig b/cli/commands/init.zig index 4244088..87ace36 100644 --- a/cli/commands/init.zig +++ b/cli/commands/init.zig @@ -47,7 +47,7 @@ pub fn run( install_path = arg; } - const github_url = try githubUrl(allocator); + const github_url = try util.githubUrl(allocator); defer allocator.free(github_url); if (other_options.help) { @@ -87,10 +87,10 @@ pub fn run( install_dir = try std.fs.cwd().makeOpenPath(input_install_path, .{}); } - const real_path = try install_dir.realpathAlloc(allocator, "."); - defer allocator.free(real_path); + const realpath = try install_dir.realpathAlloc(allocator, "."); + defer allocator.free(realpath); - const output = try std.fmt.allocPrint(allocator, "Creating new project in {s}\n\n", .{real_path}); + const output = try std.fmt.allocPrint(allocator, "Creating new project in {s}\n\n", .{realpath}); defer allocator.free(output); try writer.writeAll(output); @@ -184,7 +184,7 @@ pub fn run( null, ); - try runCommand(allocator, real_path, &[_][]const u8{ + try util.runCommand(allocator, realpath, &[_][]const u8{ "zig", "fetch", "--save", @@ -208,38 +208,7 @@ pub fn run( \\And then browse to http://localhost:8080/ \\ \\ - , .{real_path}); -} - -fn runCommand(allocator: std.mem.Allocator, install_path: []const u8, argv: []const []const u8) !void { - const result = try std.process.Child.run(.{ .allocator = allocator, .argv = argv, .cwd = install_path }); - defer allocator.free(result.stdout); - defer allocator.free(result.stderr); - - const command = try std.mem.join(allocator, " ", argv); - defer allocator.free(command); - - std.debug.print("[exec] {s}", .{command}); - - if (result.term.Exited != 0) { - util.printFailure(); - std.debug.print( - \\ - \\Error running command: {s} - \\ - \\[stdout]: - \\ - \\{s} - \\ - \\[stderr]: - \\ - \\{s} - \\ - , .{ command, result.stdout, result.stderr }); - return error.JetzigCommandError; - } else { - util.printSuccess(); - } + , .{realpath}); } const Replace = struct { @@ -308,47 +277,6 @@ fn writeSourceFile(install_dir: std.fs.Dir, path: []const u8, content: []const u } } -// Generate a full GitHub URL for passing to `zig fetch`. -fn githubUrl(allocator: std.mem.Allocator) ![]const u8 { - var client = std.http.Client{ .allocator = allocator }; - defer client.deinit(); - - const url = "https://api.github.com/repos/jetzig-framework/jetzig/branches/main"; - const extra_headers = &[_]std.http.Header{.{ .name = "X-GitHub-Api-Version", .value = "2022-11-28" }}; - - var response_storage = std.ArrayList(u8).init(allocator); - defer response_storage.deinit(); - - const fetch_result = try client.fetch(.{ - .location = .{ .url = url }, - .extra_headers = extra_headers, - .response_storage = .{ .dynamic = &response_storage }, - }); - - if (fetch_result.status != .ok) { - std.debug.print("Error fetching from GitHub: {s}\n", .{url}); - return error.JetzigCommandError; - } - - const parsed_response = try std.json.parseFromSlice( - struct { commit: struct { sha: []const u8 } }, - allocator, - response_storage.items, - .{ .ignore_unknown_fields = true }, - ); - defer parsed_response.deinit(); - - return try std.mem.concat( - allocator, - u8, - &[_][]const u8{ - "https://github.com/jetzig-framework/jetzig/archive/", - parsed_response.value.commit.sha, - ".tar.gz", - }, - ); -} - // Prompt a user for input and return the result. Accepts an optional default value. fn promptInput( allocator: std.mem.Allocator, @@ -384,19 +312,19 @@ fn promptInput( // Initialize a new Git repository when setting up a new project (optional). fn gitSetup(allocator: std.mem.Allocator, install_dir: *std.fs.Dir) !void { - try runCommand(allocator, install_dir, &[_][]const u8{ + try util.runCommand(allocator, install_dir, &[_][]const u8{ "git", "init", ".", }); - try runCommand(allocator, install_dir, &[_][]const u8{ + try util.runCommand(allocator, install_dir, &[_][]const u8{ "git", "add", ".", }); - try runCommand(allocator, install_dir, &[_][]const u8{ + try util.runCommand(allocator, install_dir, &[_][]const u8{ "git", "commit", "-m", diff --git a/cli/commands/update.zig b/cli/commands/update.zig new file mode 100644 index 0000000..b57106e --- /dev/null +++ b/cli/commands/update.zig @@ -0,0 +1,72 @@ +const std = @import("std"); +const args = @import("args"); +const util = @import("../util.zig"); + +/// Command line options for the `update` command. +pub const Options = struct { + pub const meta = .{ + .usage_summary = "[NAME=jetzig]", + .full_text = + \\Updates the current project to the latest version of Jetzig. + \\ + \\Optionally pass a positional argument to save the dependency to `build.zig.zon` with an + \\alternative name. + \\ + \\Equivalent to running `zig fetch --save=jetzig https://github.com/jetzig-framework/jetzig/archive/.tar.gz` + \\ + \\Example: + \\ + \\ jetzig update + \\ jetzig update web + , + .option_docs = .{ + .path = "Set the output path relative to the current directory (default: current directory)", + }, + }; +}; + +/// Run the `jetzig update` command. +pub fn run( + allocator: std.mem.Allocator, + options: Options, + writer: anytype, + positionals: [][]const u8, + other_options: struct { help: bool }, +) !void { + _ = options; + if (other_options.help) { + try args.printHelp(Options, "jetzig update", writer); + return; + } + + if (positionals.len > 1) { + std.debug.print("Expected at most 1 positional argument, found {}\n", .{positionals.len}); + return error.JetzigCommandError; + } + + const name = if (positionals.len > 0) positionals[0] else "jetzig"; + + const github_url = try util.githubUrl(allocator); + defer allocator.free(github_url); + + const save_arg = try std.mem.concat(allocator, u8, &[_][]const u8{ "--save=", name }); + defer allocator.free(save_arg); + + var cwd = try util.detectJetzigProjectDir(); + defer cwd.close(); + + const realpath = try std.fs.realpathAlloc(allocator, "."); + defer allocator.free(realpath); + + try util.runCommand(allocator, realpath, &[_][]const u8{ + "zig", + "fetch", + save_arg, + github_url, + }); + + std.debug.print( + \\Update complete. + \\ + , .{}); +} diff --git a/cli/util.zig b/cli/util.zig index 449b90e..2350911 100644 --- a/cli/util.zig +++ b/cli/util.zig @@ -94,3 +94,76 @@ pub fn isCamelCase(input: []const u8) bool { return true; } + +/// Runs a command as a child process and verifies successful exit code. +pub fn runCommand(allocator: std.mem.Allocator, install_path: []const u8, argv: []const []const u8) !void { + const result = try std.process.Child.run(.{ .allocator = allocator, .argv = argv, .cwd = install_path }); + defer allocator.free(result.stdout); + defer allocator.free(result.stderr); + + const command = try std.mem.join(allocator, " ", argv); + defer allocator.free(command); + + std.debug.print("[exec] {s}", .{command}); + + if (result.term.Exited != 0) { + printFailure(); + std.debug.print( + \\ + \\Error running command: {s} + \\ + \\[stdout]: + \\ + \\{s} + \\ + \\[stderr]: + \\ + \\{s} + \\ + , .{ command, result.stdout, result.stderr }); + return error.JetzigCommandError; + } else { + printSuccess(); + } +} + +/// Generate a full GitHub URL for passing to `zig fetch`. +pub fn githubUrl(allocator: std.mem.Allocator) ![]const u8 { + var client = std.http.Client{ .allocator = allocator }; + defer client.deinit(); + + const url = "https://api.github.com/repos/jetzig-framework/jetzig/branches/main"; + const extra_headers = &[_]std.http.Header{.{ .name = "X-GitHub-Api-Version", .value = "2022-11-28" }}; + + var response_storage = std.ArrayList(u8).init(allocator); + defer response_storage.deinit(); + + const fetch_result = try client.fetch(.{ + .location = .{ .url = url }, + .extra_headers = extra_headers, + .response_storage = .{ .dynamic = &response_storage }, + }); + + if (fetch_result.status != .ok) { + std.debug.print("Error fetching from GitHub: {s}\n", .{url}); + return error.JetzigCommandError; + } + + const parsed_response = try std.json.parseFromSlice( + struct { commit: struct { sha: []const u8 } }, + allocator, + response_storage.items, + .{ .ignore_unknown_fields = true }, + ); + defer parsed_response.deinit(); + + return try std.mem.concat( + allocator, + u8, + &[_][]const u8{ + "https://github.com/jetzig-framework/jetzig/archive/", + parsed_response.value.commit.sha, + ".tar.gz", + }, + ); +}