Skip to content

Commit

Permalink
Merge pull request #16 from jetzig-framework/cli-update
Browse files Browse the repository at this point in the history
Add update command to CLI tool
  • Loading branch information
bobf authored Mar 10, 2024
2 parents 670fe23 + bec5f9c commit ea1962f
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 81 deletions.
11 changes: 11 additions & 0 deletions cli/cli.zig
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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",
},
Expand All @@ -22,6 +24,7 @@ const Options = struct {

const Verb = union(enum) {
init: init.Options,
update: update.Options,
generate: generate.Options,
g: generate.Options,
};
Expand Down Expand Up @@ -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`
Expand All @@ -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 },
),
};
}
}
90 changes: 9 additions & 81 deletions cli/commands/init.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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",
Expand All @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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",
Expand Down
72 changes: 72 additions & 0 deletions cli/commands/update.zig
Original file line number Diff line number Diff line change
@@ -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/<latest-commit>.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.
\\
, .{});
}
73 changes: 73 additions & 0 deletions cli/util.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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",
},
);
}

0 comments on commit ea1962f

Please sign in to comment.