mirror of
https://github.com/jetzig-framework/jetzig.git
synced 2025-05-14 14:06:08 +00:00
170 lines
5.3 KiB
Zig
170 lines
5.3 KiB
Zig
const std = @import("std");
|
|
|
|
/// Decode a base64 string, used for parsing out build artifacts generated by the CLI program's
|
|
/// build.zig which are stored in the executable as a module.
|
|
pub fn base64Decode(allocator: std.mem.Allocator, input: []const u8) ![]const u8 {
|
|
const decoder = std.base64.Base64Decoder.init(
|
|
std.base64.url_safe_no_pad.alphabet_chars,
|
|
std.base64.url_safe_no_pad.pad_char,
|
|
);
|
|
const size = try decoder.calcSizeForSlice(input);
|
|
const ptr = try allocator.alloc(u8, size);
|
|
try decoder.decode(ptr, input);
|
|
return ptr;
|
|
}
|
|
|
|
/// Print a success confirmation.
|
|
pub fn printSuccess() void {
|
|
std.debug.print(" ✅\n", .{});
|
|
}
|
|
|
|
/// Print a failure confirmation.
|
|
pub fn printFailure() void {
|
|
std.debug.print(" ❌\n", .{});
|
|
}
|
|
|
|
/// Verifies that cwd is the root of a Jetzig project
|
|
pub fn detectJetzigProjectDir() !std.fs.Dir {
|
|
var dir = try std.fs.cwd().openDir(".", .{});
|
|
const max_parent_dirs: usize = 100; // Prevent symlink loops or other weird stuff.
|
|
|
|
for (0..max_parent_dirs) |_| {
|
|
if (try isPath(dir, "build.zig", .file) and try isPath(dir, "src/app/views", .dir)) return dir;
|
|
|
|
dir = dir.openDir("..", .{}) catch |err| {
|
|
switch (err) {
|
|
error.FileNotFound, error.NotDir => {
|
|
std.debug.print(
|
|
"Encountered unexpected detecting Jetzig project directory: {s}\n",
|
|
.{@errorName(err)},
|
|
);
|
|
return error.JetzigCommandError;
|
|
},
|
|
else => return err,
|
|
}
|
|
};
|
|
continue;
|
|
}
|
|
|
|
std.debug.print(
|
|
\\Exceeded maximum parent directory depth.
|
|
\\Unable to detect Jetzig project directory.
|
|
\\
|
|
,
|
|
.{},
|
|
);
|
|
return error.JetzigCommandError;
|
|
}
|
|
|
|
fn isPath(dir: std.fs.Dir, sub_path: []const u8, path_type: enum { file, dir }) !bool {
|
|
switch (path_type) {
|
|
.file => {
|
|
_ = dir.statFile(sub_path) catch |err| {
|
|
switch (err) {
|
|
error.FileNotFound => return false,
|
|
else => return err,
|
|
}
|
|
};
|
|
return true;
|
|
},
|
|
.dir => {
|
|
var test_dir = dir.openDir(sub_path, .{}) catch |err| {
|
|
switch (err) {
|
|
error.FileNotFound, error.NotDir => return false,
|
|
else => return err,
|
|
}
|
|
};
|
|
test_dir.close();
|
|
return true;
|
|
},
|
|
}
|
|
}
|
|
|
|
// Strip leading and trailing whitespace from a u8 slice.
|
|
pub fn strip(input: []const u8) []const u8 {
|
|
return std.mem.trim(u8, input, &std.ascii.whitespace);
|
|
}
|
|
|
|
/// Attempts to confirm if a string input is in CamelCase. False if the first character is
|
|
/// not alphabetic lower-case or if the input contains underscores.
|
|
pub fn isCamelCase(input: []const u8) bool {
|
|
if (input.len == 0) return false;
|
|
if (!std.mem.containsAtLeast(u8, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 1, &[_]u8{input[0]})) return false;
|
|
if (std.mem.containsAtLeast(u8, input, 1, "_")) return false;
|
|
|
|
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",
|
|
},
|
|
);
|
|
}
|