Merge pull request #16 from jetzig-framework/cli-update

Add update command to CLI tool
This commit is contained in:
bobf 2024-03-10 10:49:36 +00:00 committed by GitHub
commit ea1962fcb8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 165 additions and 81 deletions

View File

@ -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 },
),
};
}
}

View File

@ -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",

72
cli/commands/update.zig Normal file
View File

@ -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.
\\
, .{});
}

View File

@ -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",
},
);
}