mirror of
https://github.com/jetzig-framework/jetzig.git
synced 2025-05-14 22:16:08 +00:00
Implement jetzig server
command
Runs a development server and auto-reloads when changes are detected to files. Note that a "change" is just an mtime update, Zig does a more intelligent change detection so avoids unnecessary recompiles, so the server reloads very quickly.
This commit is contained in:
parent
4d7bd71324
commit
e980782316
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@ zig-out/
|
|||||||
zig-cache/
|
zig-cache/
|
||||||
*.core
|
*.core
|
||||||
static/
|
static/
|
||||||
|
.jetzig
|
||||||
|
@ -86,6 +86,14 @@ pub fn jetzigInit(b: *std.Build, exe: *std.Build.Step.Compile, options: JetzigIn
|
|||||||
exe.root_module.addImport("jetzig", jetzig_module);
|
exe.root_module.addImport("jetzig", jetzig_module);
|
||||||
exe.root_module.addImport("zmpl", zmpl_module);
|
exe.root_module.addImport("zmpl", zmpl_module);
|
||||||
|
|
||||||
|
if (b.option(bool, "jetzig_runner", "Used internally by `jetzig server` command.")) |jetzig_runner| {
|
||||||
|
if (jetzig_runner) {
|
||||||
|
const file = try std.fs.cwd().createFile(".jetzig", .{ .truncate = true });
|
||||||
|
defer file.close();
|
||||||
|
try file.writeAll(exe.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var generate_routes = try GenerateRoutes.init(b.allocator, "src/app/views");
|
var generate_routes = try GenerateRoutes.init(b.allocator, "src/app/views");
|
||||||
try generate_routes.generateRoutes();
|
try generate_routes.generateRoutes();
|
||||||
const write_files = b.addWriteFiles();
|
const write_files = b.addWriteFiles();
|
||||||
|
12
cli/cli.zig
12
cli/cli.zig
@ -3,6 +3,7 @@ const args = @import("args");
|
|||||||
const init = @import("commands/init.zig");
|
const init = @import("commands/init.zig");
|
||||||
const update = @import("commands/update.zig");
|
const update = @import("commands/update.zig");
|
||||||
const generate = @import("commands/generate.zig");
|
const generate = @import("commands/generate.zig");
|
||||||
|
const server = @import("commands/server.zig");
|
||||||
|
|
||||||
const Options = struct {
|
const Options = struct {
|
||||||
help: bool = false,
|
help: bool = false,
|
||||||
@ -17,6 +18,7 @@ const Options = struct {
|
|||||||
.init = "Initialize a new project",
|
.init = "Initialize a new project",
|
||||||
.update = "Update current project to latest version of Jetzig",
|
.update = "Update current project to latest version of Jetzig",
|
||||||
.generate = "Generate scaffolding",
|
.generate = "Generate scaffolding",
|
||||||
|
.server = "Run a development server",
|
||||||
.help = "Print help and exit",
|
.help = "Print help and exit",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -26,7 +28,9 @@ const Verb = union(enum) {
|
|||||||
init: init.Options,
|
init: init.Options,
|
||||||
update: update.Options,
|
update: update.Options,
|
||||||
generate: generate.Options,
|
generate: generate.Options,
|
||||||
|
server: server.Options,
|
||||||
g: generate.Options,
|
g: generate.Options,
|
||||||
|
s: server.Options,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Main entrypoint for `jetzig` executable. Parses command line args and generates a new
|
/// Main entrypoint for `jetzig` executable. Parses command line args and generates a new
|
||||||
@ -57,6 +61,7 @@ pub fn main() !void {
|
|||||||
\\ init Initialize a new project.
|
\\ init Initialize a new project.
|
||||||
\\ update Update current project to latest version of Jetzig.
|
\\ update Update current project to latest version of Jetzig.
|
||||||
\\ generate Generate scaffolding.
|
\\ generate Generate scaffolding.
|
||||||
|
\\ server Run a development server.
|
||||||
\\
|
\\
|
||||||
\\ Pass --help to any command for more information, e.g. `jetzig init --help`
|
\\ Pass --help to any command for more information, e.g. `jetzig init --help`
|
||||||
\\
|
\\
|
||||||
@ -88,6 +93,13 @@ fn run(allocator: std.mem.Allocator, options: args.ParseArgsResult(Options, Verb
|
|||||||
options.positionals,
|
options.positionals,
|
||||||
.{ .help = options.options.help },
|
.{ .help = options.options.help },
|
||||||
),
|
),
|
||||||
|
.s, .server => |opts| server.run(
|
||||||
|
allocator,
|
||||||
|
opts,
|
||||||
|
writer,
|
||||||
|
options.positionals,
|
||||||
|
.{ .help = options.options.help },
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
166
cli/commands/server.zig
Normal file
166
cli/commands/server.zig
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const args = @import("args");
|
||||||
|
const util = @import("../util.zig");
|
||||||
|
|
||||||
|
pub const watch_changes_pause_duration = 1 * 1000 * 1000 * 1000;
|
||||||
|
|
||||||
|
/// Command line options for the `update` command.
|
||||||
|
pub const Options = struct {
|
||||||
|
reload: bool = true,
|
||||||
|
|
||||||
|
pub const meta = .{
|
||||||
|
.full_text =
|
||||||
|
\\Launches a development server.
|
||||||
|
\\
|
||||||
|
\\The development server reloads when files in `src/` are updated.
|
||||||
|
\\
|
||||||
|
\\To disable this behaviour, pass `--reload=false`
|
||||||
|
\\
|
||||||
|
\\Example:
|
||||||
|
\\
|
||||||
|
\\ jetzig server
|
||||||
|
\\ jetzig server --reload=false
|
||||||
|
,
|
||||||
|
.option_docs = .{
|
||||||
|
.reload = "Enable or disable automatic reload on update (default: true)",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Run the `jetzig server` command.
|
||||||
|
pub fn run(
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
options: Options,
|
||||||
|
writer: anytype,
|
||||||
|
positionals: [][]const u8,
|
||||||
|
other_options: struct { help: bool },
|
||||||
|
) !void {
|
||||||
|
if (other_options.help) {
|
||||||
|
try args.printHelp(Options, "jetzig server", writer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (positionals.len > 0) {
|
||||||
|
std.debug.print("The `server` command does not accept positional arguments.", .{});
|
||||||
|
return error.JetzigCommandError;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cwd = try util.detectJetzigProjectDir();
|
||||||
|
defer cwd.close();
|
||||||
|
|
||||||
|
const realpath = try std.fs.realpathAlloc(allocator, ".");
|
||||||
|
defer allocator.free(realpath);
|
||||||
|
|
||||||
|
var mtime = try totalMtime(allocator, cwd, "src");
|
||||||
|
|
||||||
|
std.debug.print(
|
||||||
|
"Launching development server. [reload:{s}]\n",
|
||||||
|
.{
|
||||||
|
if (options.reload) "enabled" else "disabled",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
try util.runCommand(
|
||||||
|
allocator,
|
||||||
|
realpath,
|
||||||
|
&[_][]const u8{ "zig", "build", "-Djetzig_runner=true", "install" },
|
||||||
|
);
|
||||||
|
|
||||||
|
const exe_path = try locateExecutable(allocator, cwd);
|
||||||
|
if (exe_path == null) {
|
||||||
|
std.debug.print("Unable to locate compiled executable. Exiting.\n", .{});
|
||||||
|
std.os.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const argv = &[_][]const u8{exe_path.?};
|
||||||
|
defer allocator.free(exe_path.?);
|
||||||
|
|
||||||
|
var process = std.process.Child.init(argv, allocator);
|
||||||
|
process.stdin_behavior = .Inherit;
|
||||||
|
process.stdout_behavior = .Inherit;
|
||||||
|
process.stderr_behavior = .Inherit;
|
||||||
|
process.cwd = realpath;
|
||||||
|
|
||||||
|
var stdout_buf = std.ArrayList(u8).init(allocator);
|
||||||
|
defer stdout_buf.deinit();
|
||||||
|
|
||||||
|
var stderr_buf = std.ArrayList(u8).init(allocator);
|
||||||
|
defer stderr_buf.deinit();
|
||||||
|
|
||||||
|
try process.spawn();
|
||||||
|
|
||||||
|
if (!options.reload) {
|
||||||
|
const term = try process.wait();
|
||||||
|
std.os.exit(term.Exited);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (process.term) |_| {
|
||||||
|
_ = try process.wait();
|
||||||
|
std.debug.print("Server exited, restarting...\n", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
std.time.sleep(watch_changes_pause_duration);
|
||||||
|
|
||||||
|
const new_mtime = try totalMtime(allocator, cwd, "src");
|
||||||
|
|
||||||
|
if (new_mtime > mtime) {
|
||||||
|
std.debug.print("Changes detected, restarting server...\n", .{});
|
||||||
|
_ = try process.kill();
|
||||||
|
mtime = new_mtime;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn totalMtime(allocator: std.mem.Allocator, cwd: std.fs.Dir, sub_path: []const u8) !i128 {
|
||||||
|
var dir = try cwd.openDir(sub_path, .{ .iterate = true });
|
||||||
|
defer dir.close();
|
||||||
|
|
||||||
|
var walker = try dir.walk(allocator);
|
||||||
|
defer walker.deinit();
|
||||||
|
|
||||||
|
var sum: i128 = 0;
|
||||||
|
|
||||||
|
while (try walker.next()) |entry| {
|
||||||
|
if (entry.kind != .file) continue;
|
||||||
|
const extension = std.fs.path.extension(entry.path);
|
||||||
|
|
||||||
|
if (std.mem.eql(u8, extension, ".zig") or std.mem.eql(u8, extension, ".zmpl")) {
|
||||||
|
const stat = try dir.statFile(entry.path);
|
||||||
|
sum += stat.mtime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn locateExecutable(allocator: std.mem.Allocator, dir: std.fs.Dir) !?[]const u8 {
|
||||||
|
const file = dir.openFile(".jetzig", .{}) catch |err| {
|
||||||
|
switch (err) {
|
||||||
|
error.FileNotFound => return null,
|
||||||
|
else => return err,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const content = try file.readToEndAlloc(allocator, 1024);
|
||||||
|
defer allocator.free(content);
|
||||||
|
|
||||||
|
const exe_name = util.strip(content);
|
||||||
|
|
||||||
|
// XXX: Will fail if user sets a custom install path.
|
||||||
|
var bin_dir = try dir.openDir("zig-out/bin", .{ .iterate = true });
|
||||||
|
defer bin_dir.close();
|
||||||
|
|
||||||
|
var walker = try bin_dir.walk(allocator);
|
||||||
|
defer walker.deinit();
|
||||||
|
|
||||||
|
while (try walker.next()) |entry| {
|
||||||
|
if (entry.kind == .file and std.mem.eql(u8, entry.path, exe_name)) {
|
||||||
|
return try bin_dir.realpathAlloc(allocator, entry.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user