rename web/ to admin/, along with the web-named build and cli identifiers

This commit is contained in:
2026-08-16 00:17:58 +02:00
parent 5b3d1cd65c
commit 1e97c80f6b
136 changed files with 196 additions and 196 deletions
+4 -4
View File
@@ -658,10 +658,10 @@ fn serve(r: cli.Runner, args: cli.RunArgs) !u8 {
.querylog_db = if (web_querylog_db) |*database| database else null,
.version = version.string,
.started_unix = std.Io.Clock.real.now(io).toSeconds(),
// Ruling 24: `--web-dev` serves from disk with no cache headers;
// Ruling 24: `--admin-dev` serves from disk with no cache headers;
// otherwise the embedded assets answer every non-/api miss.
.fallback = if (args.web_dev != null) serveWebDev else static.fallback,
.dev_dir = args.web_dev orelse "",
.fallback = if (args.admin_dev != null) serveAdminDev else static.fallback,
.dev_dir = args.admin_dev orelse "",
.reload_fn = reloadManager,
};
@@ -799,7 +799,7 @@ fn reloadManager(state: *web_server.WebState, io: std.Io) anyerror!void {
/// Dev-mode asset serving (ruling 24): straight from disk, no cache headers,
/// so an edit shows up on the next reload.
fn serveWebDev(
fn serveAdminDev(
state: *web_server.WebState,
io: std.Io,
request: *http_util.Request,
+17 -17
View File
@@ -69,12 +69,12 @@ pub const ImportArgs = struct { paths: Paths = .{}, file: []const u8, allow_dele
/// makes that file the sole declarative source of truth: it is read, validated
/// and reconciled into the database on every start.
///
/// `web_dev` is milestone-8 ruling 24's `--web-dev <dir>`: serve the web
/// `admin_dev` is milestone-8 ruling 24's `--admin-dev <dir>`: serve the web
/// interface from that directory instead of the embedded assets.
pub const RunArgs = struct {
paths: Paths = .{},
config: ?[]const u8 = null,
web_dev: ?[]const u8 = null,
admin_dev: ?[]const u8 = null,
};
pub const Command = union(enum) {
@@ -179,7 +179,7 @@ fn flagValue(flag: Flag, argv: []const []const u8, i: *usize) ParseError![]const
return argv[i.*];
}
/// `run` takes `check`'s two flags plus `--web-dev`, which only a process that
/// `run` takes `check`'s two flags plus `--admin-dev`, which only a process that
/// serves has any use for; `check` deliberately rejects it.
fn parseRunArgs(argv: []const []const u8) ParseError!RunArgs {
var args: RunArgs = .{};
@@ -190,8 +190,8 @@ fn parseRunArgs(argv: []const []const u8) ParseError!RunArgs {
args.paths.data_dir = try flagValue(flag, argv, &i);
} else if (eql(flag.name, "config")) {
args.config = try flagValue(flag, argv, &i);
} else if (eql(flag.name, "web-dev")) {
args.web_dev = try flagValue(flag, argv, &i);
} else if (eql(flag.name, "admin-dev")) {
args.admin_dev = try flagValue(flag, argv, &i);
} else return error.UnknownFlag;
}
return args;
@@ -396,7 +396,7 @@ const usage_text =
\\ check: grade FILE instead of the database
\\ --out FILE write the export to FILE instead of stdout
\\ --allow-delete let import apply a file whose diff deletes rows
\\ --web-dev DIR run only: serve the web interface from DIR instead of
\\ --admin-dev DIR run only: serve the web interface from DIR instead of
\\ the embedded assets
\\
;
@@ -1030,7 +1030,7 @@ test "run without --config selects database authority" {
const command = try parseArgs(&.{"run"});
try testing.expectEqualStrings("/var/lib/nxdns", command.run.paths.data_dir);
try testing.expectEqual(@as(?[]const u8, null), command.run.config);
try testing.expectEqual(@as(?[]const u8, null), command.run.web_dev);
try testing.expectEqual(@as(?[]const u8, null), command.run.admin_dev);
}
test "run --config selects file authority and the path lands in the run args" {
@@ -1042,19 +1042,19 @@ test "run --config selects file authority and the path lands in the run args" {
try testing.expectEqualStrings("/tmp/c.zon", command.run.config.?);
}
test "parseArgs accepts run with --web-dev in both spellings" {
const attached = try parseArgs(&.{ "run", "--web-dev=web/dist" });
try testing.expectEqualStrings("web/dist", attached.run.web_dev.?);
test "parseArgs accepts run with --admin-dev in both spellings" {
const attached = try parseArgs(&.{ "run", "--admin-dev=admin/dist" });
try testing.expectEqualStrings("admin/dist", attached.run.admin_dev.?);
const separate = try parseArgs(&.{ "run", "--web-dev", "web/dist" });
try testing.expectEqualStrings("web/dist", separate.run.web_dev.?);
const separate = try parseArgs(&.{ "run", "--admin-dev", "admin/dist" });
try testing.expectEqualStrings("admin/dist", separate.run.admin_dev.?);
}
test "parseArgs rejects --web-dev without a value and outside run" {
try testing.expectError(error.MissingValue, parseArgs(&.{ "run", "--web-dev" }));
try testing.expectError(error.MissingValue, parseArgs(&.{ "run", "--web-dev=" }));
try testing.expectError(error.UnknownFlag, parseArgs(&.{ "check", "--web-dev", "web/dist" }));
try testing.expectError(error.UnknownFlag, parseArgs(&.{ "export", "--web-dev", "web/dist" }));
test "parseArgs rejects --admin-dev without a value and outside run" {
try testing.expectError(error.MissingValue, parseArgs(&.{ "run", "--admin-dev" }));
try testing.expectError(error.MissingValue, parseArgs(&.{ "run", "--admin-dev=" }));
try testing.expectError(error.UnknownFlag, parseArgs(&.{ "check", "--admin-dev", "admin/dist" }));
try testing.expectError(error.UnknownFlag, parseArgs(&.{ "export", "--admin-dev", "admin/dist" }));
}
test "parseArgs accepts --data-dir with and without an equals sign" {
+13 -13
View File
@@ -22,9 +22,9 @@
//! the assembled notices — says which artifacts the file speaks for.
//!
//! One question is out of reach here and lives in `gates.yml` instead: which
//! packages actually contribute a module to `web/dist`. Answering it means
//! packages actually contribute a module to `admin/dist`. Answering it means
//! running the bundler. The frontend gate does that, compares the result with
//! the `[npm packages bundled into web/dist]` section, and this file checks the
//! the `[npm packages bundled into admin/dist]` section, and this file checks the
//! inventory against that same section.
//!
//! Everything is embedded at compile time through the `licenses_files` module:
@@ -35,27 +35,27 @@ const builtin = @import("builtin");
const licenses = @import("licenses_files");
/// The npm packages that are `devDependencies` in the lockfile but whose own
/// output ends up in `web/dist`, and so in the binary. None of them runs in the
/// output ends up in `admin/dist`, and so in the binary. None of them runs in the
/// browser; their emitted bytes do.
///
/// The set was settled by building `web/` with `--sourcemap` and reading two
/// The set was settled by building `admin/` with `--sourcemap` and reading two
/// things off the result: the sourcemap `sources` lists, which name the
/// packages whose modules were bundled, and the regions of each chunk that no
/// sourcemap segment maps back to a source file, which are the bytes the
/// toolchain injected on its own. Tailwind is here for its generated
/// stylesheet; vite for `vite/preload-helper` and `vite/modulepreload-polyfill`;
/// rolldown for the CommonJS interop helpers it prepends to any chunk pulling in
/// a CommonJS module. Every other devDependency left `web/dist` untouched.
/// a CommonJS module. Every other devDependency left `admin/dist` untouched.
/// Anything added here must also be inventoried.
const npm_generators = [_][]const u8{ "rolldown", "vite" };
/// Packages of the recorded runtime closure that put no byte in `web/dist`, so
/// Packages of the recorded runtime closure that put no byte in `admin/dist`, so
/// the inventory names them in a note rather than carrying their licence. The
/// note has to exist: each name below must still appear in
/// `licenses/inventory.zon`.
///
/// This list is a claim about the build, and the build is what settles it: a
/// name here that turns up in the recorded `web/dist` bundle fails, so a
/// name here that turns up in the recorded `admin/dist` bundle fails, so a
/// package that starts shipping cannot stay on this list quietly.
const npm_not_shipped = [_][]const u8{
"cookie-es",
@@ -135,7 +135,7 @@ const ZigDependencyVersion = struct {
/// on 2026-08-12, and `licenses/react-aria-apache-2.0.txt` carries the text
/// Section 4 asks for.
///
/// Packages that put no byte in `web/dist` are here too. They redistribute
/// Packages that put no byte in `admin/dist` are here too. They redistribute
/// nothing today, so their licence carries no obligation today — but "not
/// shipped" is a claim about the build that a future build can falsify, and
/// recording the licence now means the answer is already reviewed when it does.
@@ -197,7 +197,7 @@ const zig_section = "[build.zig.zon dependencies]";
const npm_section = "[npm runtime closure]";
const generator_section = "[npm build-time generators whose output ships]";
const base_image_section = "[container base image]";
const bundled_section = "[npm packages bundled into web/dist]";
const bundled_section = "[npm packages bundled into admin/dist]";
/// The shape `tools/dist_stage.zig` parses when it assembles
/// THIRD-PARTY-NOTICES. Parsing `licenses/inventory.zon` into it here is itself
@@ -619,7 +619,7 @@ test "the licence texts that must be reproduced in full are unmodified" {
// the recorded list honest against the build; this keeps the inventory honest
// against the recorded list. Neither works alone: the gate cannot say what the
// inventory covers, and this test cannot run rollup.
test "every package bundled into web/dist is inventoried" {
test "every package bundled into admin/dist is inventoried" {
const gpa = std.testing.allocator;
const entries = try parseInventory(gpa);
defer std.zon.parse.free(gpa, entries);
@@ -632,7 +632,7 @@ test "every package bundled into web/dist is inventoried" {
if (isNotShipped(name)) {
std.debug.print(
"npm package '{s}' is in the recorded web/dist bundle but npm_not_shipped still" ++
"npm package '{s}' is in the recorded admin/dist bundle but npm_not_shipped still" ++
" claims it ships nothing. It does now: inventory it and take it out of that" ++
" list.\n",
.{name},
@@ -642,7 +642,7 @@ test "every package bundled into web/dist is inventoried" {
if (!inventoryNamesPackage(entries, name)) {
std.debug.print(
"npm package '{s}' is bundled into web/dist but no licenses/inventory.zon entry" ++
"npm package '{s}' is bundled into admin/dist but no licenses/inventory.zon entry" ++
" names it\n",
.{name},
);
@@ -1078,7 +1078,7 @@ fn skipWhitespace(text: []const u8, from: usize) usize {
const NpmSet = enum { runtime, generators };
/// `name version licence` for every package in `web/package-lock.json` that is
/// `name version licence` for every package in `admin/package-lock.json` that is
/// not marked `dev`, or for the build-time generators, sorted by name. The
/// non-dev set is a superset of what the bundler emits — it cannot run rollup —
/// so a new runtime dependency always trips the guard even when tree-shaking
+1 -1
View File
@@ -180,7 +180,7 @@ pub const WebState = struct {
querylog_db: ?*db.Db = null,
version: []const u8 = "",
/// The `--web-dev` asset directory, read by the dev-mode fallback. Empty
/// The `--admin-dev` asset directory, read by the dev-mode fallback. Empty
/// whenever that fallback is not wired.
dev_dir: []const u8 = "",
/// Unix seconds at process start, for uptime.
+10 -10
View File
@@ -1,7 +1,7 @@
//! Static asset serving (milestone-8 ruling 24).
//!
//! Production serves from `web_assets`, the module the build generates from
//! `-Dweb-dist`: bytes, content type and a strong ETag per file, plus a
//! Production serves from `admin_assets`, the module the build generates from
//! `-Dadmin-dist`: bytes, content type and a strong ETag per file, plus a
//! `<name>.gz` sibling entry where compressing at build time paid off. Serving
//! is a linear scan over a handful of immutable entries — no allocation, no
//! clock, no disk.
@@ -18,12 +18,12 @@
//! ETag so a `304` is always judged against the representation that would be
//! served.
//!
//! Dev mode (`nxdns run --web-dev <dir>`, wired by the CLI) serves from disk
//! Dev mode (`nxdns run --admin-dev <dir>`, wired by the CLI) serves from disk
//! with no cache headers, so a UI developer sees an edit on reload.
const std = @import("std");
const assets = @import("web_assets");
const assets = @import("admin_assets");
const http_util = @import("http_util.zig");
const server = @import("server.zig");
@@ -37,7 +37,7 @@ pub const embedded: []const File = assets.files;
pub const index_path = "/index.html";
/// A disk asset a dev-mode request may read. Matches the embed limit in
/// tools/gen_web_assets.zig.
/// tools/gen_admin_assets.zig.
pub const max_disk_asset_bytes = 64 * 1024 * 1024;
pub const Selection = struct {
@@ -193,7 +193,7 @@ pub fn diskRelativePath(buf: []u8, segments: []const []const u8) ?[]const u8 {
return writer.buffered();
}
/// Dev-mode disk serving for `--web-dev` (ruling 24). No cache headers: the
/// Dev-mode disk serving for `--admin-dev` (ruling 24). No cache headers: the
/// point of the flag is that an edit shows up on the next reload. The CLI
/// wiring (W9) closes over the directory and passes it here.
pub fn serveFromDisk(
@@ -209,8 +209,8 @@ pub fn serveFromDisk(
return http_util.respondError(request, .not_found, "not found");
var dir = std.Io.Dir.cwd().openDir(io, root, .{}) catch |err| {
log.warn("web-dev directory '{s}' is unreadable: {t}", .{ root, err });
return http_util.respondError(request, .internal_server_error, "web-dev directory unavailable");
log.warn("admin-dev directory '{s}' is unreadable: {t}", .{ root, err });
return http_util.respondError(request, .internal_server_error, "admin-dev directory unavailable");
};
defer dir.close(io);
@@ -233,7 +233,7 @@ fn readDiskFile(
return dir.readFileAlloc(io, sub_path, request.arena, .limited(max_disk_asset_bytes)) catch |err| {
switch (err) {
error.FileNotFound, error.IsDir => {},
else => log.warn("web-dev read of '{s}' failed: {t}", .{ sub_path, err }),
else => log.warn("admin-dev read of '{s}' failed: {t}", .{ sub_path, err }),
}
return null;
};
@@ -255,7 +255,7 @@ fn resolvesUnderRoot(dir: std.Io.Dir, io: std.Io, sub_path: []const u8) bool {
}
/// Extension → MIME type for dev-mode disk serving. The embedded entries carry
/// the same mapping, stamped by tools/gen_web_assets.zig; a test below keeps
/// the same mapping, stamped by tools/gen_admin_assets.zig; a test below keeps
/// the two from drifting.
pub fn contentType(path: []const u8) []const u8 {
const map = [_]struct { ext: []const u8, mime: []const u8 }{