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
+22 -22
View File
@@ -119,39 +119,39 @@ jobs:
with:
node-version: ${{ env.NODE_VERSION }}
cache: npm
cache-dependency-path: web/package-lock.json
cache-dependency-path: admin/package-lock.json
- name: Install dependencies
working-directory: web
working-directory: admin
run: npm ci
- name: Check formatting
working-directory: web
working-directory: admin
run: npm run format:check
- name: Lint
working-directory: web
working-directory: admin
run: npm run lint
- name: Typecheck
working-directory: web
working-directory: admin
run: npm run typecheck
- name: Run tests
working-directory: web
working-directory: admin
run: npm test
- name: Build
working-directory: web
working-directory: admin
run: npm run build
# The licence inventory has to cover every package whose bytes ship, and
# the lockfile does not answer that question: it lists what could be
# reached, not what rollup kept. The bundle is what this reads. The logic
# lives in web/scripts/, unit-tested by `npm test`, so it runs on a laptop
# lives in admin/scripts/, unit-tested by `npm test`, so it runs on a laptop
# exactly as it runs here (milestone-14 deviation 24).
- name: Assert the packages bundled into web/dist are the recorded ones
working-directory: web
- name: Assert the packages bundled into admin/dist are the recorded ones
working-directory: admin
run: npm run assert-bundled
# The package and container jobs consume this bundle instead of building
@@ -164,16 +164,16 @@ jobs:
#
# A later move to v4 has to add `include-hidden-files: true` here.
# `npm run build` writes the freshness stamp to the hidden file
# web/dist/.src-hash (milestone-15 ruling 5), and v4.4.0 and later drop
# admin/dist/.src-hash (milestone-15 ruling 5), and v4.4.0 and later drop
# dotfiles by default. It is inactive today — v3 keeps them, and the
# package job's target path skips the stamp check regardless — but a move
# to v4 that also pointed the download back at web/dist would fail with
# "web/dist is stale".
# to v4 that also pointed the download back at admin/dist would fail with
# "admin/dist is stale".
- name: Upload the built web UI
uses: actions/upload-artifact@c24449f33cd45d4826c6702db7e49f7cdb9b551d # v3.2.1-node20
with:
name: web-dist
path: web/dist
name: admin-dist
path: admin/dist
if-no-files-found: error
package:
@@ -201,22 +201,22 @@ jobs:
- name: Create the fetch temp dir zig assumes
run: mkdir -p "${ZIG_GLOBAL_CACHE_DIR:?}/tmp"
# `dist` refuses web/dist-placeholder (ruling 4), so a real bundle has to
# `dist` refuses admin/dist-placeholder (ruling 4), so a real bundle has to
# exist before the packaging gate runs. It arrives from the frontend job,
# already formatted, linted, typechecked, tested and licence-checked.
#
# The target is deliberately not `web/dist`: build.zig runs the freshness
# The target is deliberately not `admin/dist`: build.zig runs the freshness
# stamp check for that exact path and no other (milestone-15 ruling 5),
# and the check shells out to `node`. Here it would buy nothing — the
# stamp hashes the web/ sources, not the bundle, so against a checkout of
# stamp hashes the admin/ sources, not the bundle, so against a checkout of
# the same commit that built the bundle it can only agree. An explicit
# path is the case build.zig documents for a bundle built elsewhere, and
# taking it keeps node out of this job entirely.
- name: Download the web UI built by the frontend job
uses: actions/download-artifact@ad191675b41f6a5b46da9a048cb6893812da158b # v3.1.0-node20
with:
name: web-dist
path: web-dist-ci
name: admin-dist
path: admin-dist-ci
# Ruling 2: build.zig.zon is the only place besides the tag that carries
# the version, and ruling 5 makes verify-dist assert the two agree. The
@@ -239,7 +239,7 @@ jobs:
zig build dist \
-Dversion-string="$CI_VERSION" \
-Dgit-commit="$GITHUB_SHA" \
-Dweb-dist=web-dist-ci \
-Dadmin-dist=admin-dist-ci \
-Doptimize=ReleaseSafe
# verify-dist owns every assert the CI shell used to make: ELF static
@@ -255,7 +255,7 @@ jobs:
zig build verify-dist \
-Dversion-string="$CI_VERSION" \
-Dgit-commit="$GITHUB_SHA" \
-Dweb-dist=web-dist-ci \
-Dadmin-dist=admin-dist-ci \
-Doptimize=ReleaseSafe
# deploy/docker/Dockerfile copies both of these trees and nothing else
+4 -4
View File
@@ -269,10 +269,10 @@ jobs:
with:
node-version: ${{ env.NODE_VERSION }}
cache: npm
cache-dependency-path: web/package-lock.json
cache-dependency-path: admin/package-lock.json
- name: Build the web UI
working-directory: web
working-directory: admin
run: |
npm ci
npm run build
@@ -283,7 +283,7 @@ jobs:
zig build dist
-Dversion-string="$VERSION"
-Dgit-commit="$TAG_COMMIT"
-Dweb-dist=web/dist
-Dadmin-dist=admin/dist
-Doptimize=ReleaseSafe
- name: Verify the release artifacts
@@ -291,7 +291,7 @@ jobs:
zig build verify-dist
-Dversion-string="$VERSION"
-Dgit-commit="$TAG_COMMIT"
-Dweb-dist=web/dist
-Dadmin-dist=admin/dist
-Doptimize=ReleaseSafe
# Step 9. Extracted and validated before anything is pushed anywhere, so
+4 -4
View File
@@ -1,7 +1,7 @@
.zig-cache/
zig-out/
zig-pkg/
web/node_modules/
web/dist/
web/dist-sourcemap/
web-dist-ci/
admin/node_modules/
admin/dist/
admin/dist-sourcemap/
admin-dist-ci/
+3 -3
View File
@@ -34,10 +34,10 @@ One trap: running a cached test binary by hand with `--listen=-` aborts with `in
## Regenerating the contract samples
`web/src/lib/contractSamples.gen.ts` is a committed golden of canonicalized API responses, byte-compared against the live server by a `-Dintegration` test and type-checked by `tsc`. After a deliberate API contract change, regenerate it with:
`admin/src/lib/contractSamples.gen.ts` is a committed golden of canonicalized API responses, byte-compared against the live server by a `-Dintegration` test and type-checked by `tsc`. After a deliberate API contract change, regenerate it with:
```
zig build test -Dintegration -Dcontract-samples-out="$PWD/web/src/lib/contractSamples.gen.ts"
zig build test -Dintegration -Dcontract-samples-out="$PWD/admin/src/lib/contractSamples.gen.ts"
```
then update `web/src/lib/types.ts` to match and commit both. Never edit the generated file by hand.
then update `admin/src/lib/types.ts` to match and commit both. Never edit the generated file by hand.
+1 -1
View File
@@ -242,7 +242,7 @@ src/
rules.zig local.zig lookup.zig pause.zig settings.zig
upstream_health.zig certs.zig health.zig version.zig
web/ # Vite + React + TS + StyleX + React Aria + TanStack
admin/ # Vite + React + TS + StyleX + React Aria + TanStack
vendor/ # sqlite3 amalgamation, mbedtls (pinned)
docs/ # tutorial/ how-to/ reference/ explanation/ (Diátaxis)
tests/ # dns/ integration/ fuzz/
+5 -5
View File
@@ -60,20 +60,20 @@ Full install instructions, including the systemd path and the Pi 5 recipe, are i
Requires [Zig 0.16.0](https://ziglang.org/download/) and Node.js 24 (for the web UI). C dependencies (SQLite, mbedTLS) are vendored and built by `zig build`.
```sh
(cd web && npm ci && npm run build) # web UI -> web/dist
zig build -Dweb-dist=web/dist # native binary -> zig-out/bin/nxdns
(cd admin && npm ci && npm run build) # web UI -> admin/dist
zig build -Dadmin-dist=admin/dist # native binary -> zig-out/bin/nxdns
zig build test --summary all # unit tests
```
The release artifacts come out of the same build graph, so the whole release build runs on a laptop exactly as it runs on the CI runner:
```sh
(cd web && npm ci && npm run build) # required: dist refuses the placeholder
(cd admin && npm ci && npm run build) # required: dist refuses the placeholder
VERSION=$(sed -n 's/^[[:space:]]*\.version[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' build.zig.zon)
zig build dist -Dversion-string="$VERSION" -Dgit-commit=$(git rev-parse HEAD) \
-Dweb-dist=web/dist -Doptimize=ReleaseSafe # tarballs -> zig-out/dist/
-Dadmin-dist=admin/dist -Doptimize=ReleaseSafe # tarballs -> zig-out/dist/
zig build verify-dist -Dversion-string="$VERSION" -Dgit-commit=$(git rev-parse HEAD) \
-Dweb-dist=web/dist -Doptimize=ReleaseSafe # the release checks
-Dadmin-dist=admin/dist -Doptimize=ReleaseSafe # the release checks
```
The version comes from `build.zig.zon` because `verify-dist` asserts the two agree; a tag sets both.

Before

Width:  |  Height:  |  Size: 303 B

After

Width:  |  Height:  |  Size: 303 B

View File
+2 -2
View File
@@ -1,11 +1,11 @@
{
"name": "nxdns-web",
"name": "nxdns-admin",
"version": "0.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "nxdns-web",
"name": "nxdns-admin",
"version": "0.0.0",
"dependencies": {
"@stylexjs/stylex": "0.19.0",
+1 -1
View File
@@ -1,5 +1,5 @@
{
"name": "nxdns-web",
"name": "nxdns-admin",
"private": true,
"version": "0.0.0",
"type": "module",

Before

Width:  |  Height:  |  Size: 262 B

After

Width:  |  Height:  |  Size: 262 B

@@ -1,11 +1,11 @@
#!/usr/bin/env node
// The set of npm packages whose bytes reach web/dist must be exactly the set
// The set of npm packages whose bytes reach admin/dist must be exactly the set
// recorded in licenses/dependency-identity.txt (milestone-14 ruling 3).
//
// The shipped build carries no sourcemaps, so this makes a second build with
// them into its own directory: the `sources` list of each chunk names the
// modules that went into it, and the artifact `npm run build` produced stays
// untouched. Runs from web/ as `npm run assert-bundled`, on a laptop exactly as
// untouched. Runs from admin/ as `npm run assert-bundled`, on a laptop exactly as
// on the runner.
import { execFileSync } from "node:child_process";
@@ -85,10 +85,10 @@ try {
const recorded = recordedPackages(identity);
if (recorded === null) {
fail("assert-bundled: licenses/dependency-identity.txt has no '[npm packages bundled into web/dist]' section");
fail("assert-bundled: licenses/dependency-identity.txt has no '[npm packages bundled into admin/dist]' section");
}
if (recorded.length === 0) {
fail("assert-bundled: the '[npm packages bundled into web/dist]' section is empty");
fail("assert-bundled: the '[npm packages bundled into admin/dist]' section is empty");
}
const { added, removed } = comparePackages(recorded, bundled);
@@ -96,12 +96,12 @@ if (added.length !== 0 || removed.length !== 0) {
process.stderr.write(`${formatDiff(recorded, bundled)}\n\n`);
fail(
[
"the set of npm packages in web/dist has changed (-recorded +current).",
"the set of npm packages in admin/dist has changed (-recorded +current).",
"Work out what the change means for licenses/inventory.zon first, then record",
"the new list in that section of licenses/dependency-identity.txt.",
].join("\n"),
);
}
process.stdout.write(`web/dist bundles exactly the ${bundled.length} recorded packages:\n`);
process.stdout.write(`admin/dist bundles exactly the ${bundled.length} recorded packages:\n`);
for (const name of bundled) process.stdout.write(`${name}\n`);
@@ -14,7 +14,7 @@
// brace inside a string blinds the stripper; and with several stylesheets it
// judges each alone, not their load order in the document.
//
// This check runs from web/ as part of `npm run build`.
// This check runs from admin/ as part of `npm run build`.
import { readdirSync, readFileSync } from "node:fs";
import { dirname, join } from "node:path";
@@ -110,7 +110,7 @@ for (const sheet of sheets) {
}
if (failed) {
console.error("Wrap it in a layer declared before StyleX's, as web/src/styles.css does.");
console.error("Wrap it in a layer declared before StyleX's, as admin/src/styles.css does.");
process.exit(1);
}
console.log(
@@ -8,7 +8,7 @@
// lockfile, no version and no dependency set changes — only the bundle does. So
// the bundle is what this reads.
const sectionHeading = "[npm packages bundled into web/dist]";
const sectionHeading = "[npm packages bundled into admin/dist]";
// A sourcemap `sources` entry for a dependency ends in
// `node_modules/<name>/<file>` or `node_modules/@<scope>/<name>/<file>`. Only
@@ -48,7 +48,7 @@ describe("recordedPackages", () => {
"[some earlier section]",
"ignored",
"",
"[npm packages bundled into web/dist]",
"[npm packages bundled into admin/dist]",
"react",
"@tanstack/react-query",
"",
@@ -64,7 +64,7 @@ describe("recordedPackages", () => {
it("distinguishes a missing section from an empty one", () => {
expect(recordedPackages("[other]\nx\n")).toBeNull();
expect(recordedPackages("[npm packages bundled into web/dist]\n\n[next]\n")).toEqual([]);
expect(recordedPackages("[npm packages bundled into admin/dist]\n\n[next]\n")).toEqual([]);
});
});
@@ -1,6 +1,6 @@
#!/usr/bin/env node
// Freshness stamp for web/dist (milestone-15 ruling 5). A stale dist has
// already shipped a crashing settings page once. Write mode runs from web/ as
// Freshness stamp for admin/dist (milestone-15 ruling 5). A stale dist has
// already shipped a crashing settings page once. Write mode runs from admin/ as
// part of `npm run build`; check mode runs from the repository root as a
// build.zig system command. Every path resolves from this file's own location
// so both working directories hash the same set.
@@ -13,7 +13,7 @@ import { fileURLToPath } from "node:url";
const webRoot = dirname(dirname(fileURLToPath(import.meta.url)));
const distDir = join(webRoot, "dist");
const stampFile = join(distDir, ".src-hash");
const stampRelative = "web/dist/.src-hash";
const stampRelative = "admin/dist/.src-hash";
const inputDirs = ["src", "public"];
const inputFiles = [
@@ -26,7 +26,7 @@ const inputFiles = [
"tsconfig.node.json",
];
const staleMessage = "web/dist is stale: rebuild the frontend (npm run build)";
const staleMessage = "admin/dist is stale: rebuild the frontend (npm run build)";
function fail(message) {
process.stderr.write(`${message}\n`);
@@ -39,7 +39,7 @@ function walk(relativeDir) {
try {
entries = readdirSync(absolute, { withFileTypes: true });
} catch (err) {
fail(`stamp-dist: cannot read web/${relativeDir}: ${err.message}`);
fail(`stamp-dist: cannot read admin/${relativeDir}: ${err.message}`);
}
const found = [];
for (const entry of entries) {
@@ -57,9 +57,9 @@ function inputSet() {
const paths = [...inputFiles, ...inputDirs.flatMap(walk)];
for (const path of inputFiles) {
try {
if (!statSync(join(webRoot, path)).isFile()) fail(`stamp-dist: web/${path} is not a file`);
if (!statSync(join(webRoot, path)).isFile()) fail(`stamp-dist: admin/${path} is not a file`);
} catch (err) {
fail(`stamp-dist: cannot stat web/${path}: ${err.message}`);
fail(`stamp-dist: cannot stat admin/${path}: ${err.message}`);
}
}
// Sorted by path so the digest does not depend on directory order.
@@ -9,7 +9,7 @@
// declare, and a string outside a literal union.
//
// Regenerate with:
// zig build test -Dintegration -Dcontract-samples-out="$PWD/web/src/lib/contractSamples.gen.ts"
// zig build test -Dintegration -Dcontract-samples-out="$PWD/admin/src/lib/contractSamples.gen.ts"
import type {
Blocklist,
@@ -6,4 +6,4 @@
pub const bytes = @embedFile("contractSamples.gen.ts");
/// Repo-relative path, so a failing assertion names the file to regenerate.
pub const path = "web/src/lib/contractSamples.gen.ts";
pub const path = "admin/src/lib/contractSamples.gen.ts";
+1 -1
View File
@@ -4,7 +4,7 @@
* those rules are written against.
*
* The reset is not optional and not cosmetic. Tailwind's preflight used to
* supply it, so every StyleX style in `web/src` is written assuming border-box
* supply it, so every StyleX style in `admin/src` is written assuming border-box
* sizing, no default margins, unstyled lists and form controls that inherit
* their font. Deleting this block does not restore browser defaults it
* silently changes the meaning of every size and spacing value in the app.

Some files were not shown because too many files have changed in this diff Show More