diff --git a/.gitea/workflows/gates.yml b/.gitea/workflows/gates.yml index b1727d4..49d4d4a 100644 --- a/.gitea/workflows/gates.yml +++ b/.gitea/workflows/gates.yml @@ -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 diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 5fe58dc..48d5a04 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -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 diff --git a/.gitignore b/.gitignore index aed4d0c..949846b 100644 --- a/.gitignore +++ b/.gitignore @@ -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/ diff --git a/AGENTS.md b/AGENTS.md index ea25562..fa4c5cc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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. diff --git a/PLAN.md b/PLAN.md index 1a67b15..331871c 100644 --- a/PLAN.md +++ b/PLAN.md @@ -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/ diff --git a/README.md b/README.md index e59c8f7..bfccc9c 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/web/.prettierignore b/admin/.prettierignore similarity index 100% rename from web/.prettierignore rename to admin/.prettierignore diff --git a/web/dist-placeholder/favicon.svg b/admin/dist-placeholder/favicon.svg similarity index 100% rename from web/dist-placeholder/favicon.svg rename to admin/dist-placeholder/favicon.svg diff --git a/web/dist-placeholder/index.html b/admin/dist-placeholder/index.html similarity index 100% rename from web/dist-placeholder/index.html rename to admin/dist-placeholder/index.html diff --git a/web/index.html b/admin/index.html similarity index 100% rename from web/index.html rename to admin/index.html diff --git a/web/package-lock.json b/admin/package-lock.json similarity index 99% rename from web/package-lock.json rename to admin/package-lock.json index 42a6214..b70a727 100644 --- a/web/package-lock.json +++ b/admin/package-lock.json @@ -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", diff --git a/web/package.json b/admin/package.json similarity index 98% rename from web/package.json rename to admin/package.json index aeb405d..4f12e3e 100644 --- a/web/package.json +++ b/admin/package.json @@ -1,5 +1,5 @@ { - "name": "nxdns-web", + "name": "nxdns-admin", "private": true, "version": "0.0.0", "type": "module", diff --git a/web/public/favicon.svg b/admin/public/favicon.svg similarity index 100% rename from web/public/favicon.svg rename to admin/public/favicon.svg diff --git a/web/scripts/assert-bundled-packages.mjs b/admin/scripts/assert-bundled-packages.mjs similarity index 87% rename from web/scripts/assert-bundled-packages.mjs rename to admin/scripts/assert-bundled-packages.mjs index 63436f8..5457580 100644 --- a/web/scripts/assert-bundled-packages.mjs +++ b/admin/scripts/assert-bundled-packages.mjs @@ -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`); diff --git a/web/scripts/assert-css-layers.mjs b/admin/scripts/assert-css-layers.mjs similarity index 98% rename from web/scripts/assert-css-layers.mjs rename to admin/scripts/assert-css-layers.mjs index 5ab165c..7b2331d 100644 --- a/web/scripts/assert-css-layers.mjs +++ b/admin/scripts/assert-css-layers.mjs @@ -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( diff --git a/web/scripts/bundledPackages.mjs b/admin/scripts/bundledPackages.mjs similarity index 97% rename from web/scripts/bundledPackages.mjs rename to admin/scripts/bundledPackages.mjs index 321207f..2b5f3e4 100644 --- a/web/scripts/bundledPackages.mjs +++ b/admin/scripts/bundledPackages.mjs @@ -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//` or `node_modules/@//`. Only diff --git a/web/scripts/bundledPackages.test.mjs b/admin/scripts/bundledPackages.test.mjs similarity index 94% rename from web/scripts/bundledPackages.test.mjs rename to admin/scripts/bundledPackages.test.mjs index 4d9cb06..8a6ceba 100644 --- a/web/scripts/bundledPackages.test.mjs +++ b/admin/scripts/bundledPackages.test.mjs @@ -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([]); }); }); diff --git a/web/scripts/stamp-dist.mjs b/admin/scripts/stamp-dist.mjs similarity index 85% rename from web/scripts/stamp-dist.mjs rename to admin/scripts/stamp-dist.mjs index 783a3ca..45243f3 100644 --- a/web/scripts/stamp-dist.mjs +++ b/admin/scripts/stamp-dist.mjs @@ -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. diff --git a/web/src/auth/LoginPage.test.tsx b/admin/src/auth/LoginPage.test.tsx similarity index 100% rename from web/src/auth/LoginPage.test.tsx rename to admin/src/auth/LoginPage.test.tsx diff --git a/web/src/auth/LoginPage.tsx b/admin/src/auth/LoginPage.tsx similarity index 100% rename from web/src/auth/LoginPage.tsx rename to admin/src/auth/LoginPage.tsx diff --git a/web/src/auth/store.test.tsx b/admin/src/auth/store.test.tsx similarity index 100% rename from web/src/auth/store.test.tsx rename to admin/src/auth/store.test.tsx diff --git a/web/src/auth/store.tsx b/admin/src/auth/store.tsx similarity index 100% rename from web/src/auth/store.tsx rename to admin/src/auth/store.tsx diff --git a/web/src/features/blocklists/BlocklistForm.test.tsx b/admin/src/features/blocklists/BlocklistForm.test.tsx similarity index 100% rename from web/src/features/blocklists/BlocklistForm.test.tsx rename to admin/src/features/blocklists/BlocklistForm.test.tsx diff --git a/web/src/features/blocklists/BlocklistForm.tsx b/admin/src/features/blocklists/BlocklistForm.tsx similarity index 100% rename from web/src/features/blocklists/BlocklistForm.tsx rename to admin/src/features/blocklists/BlocklistForm.tsx diff --git a/web/src/features/blocklists/BlocklistsPage.test.tsx b/admin/src/features/blocklists/BlocklistsPage.test.tsx similarity index 100% rename from web/src/features/blocklists/BlocklistsPage.test.tsx rename to admin/src/features/blocklists/BlocklistsPage.test.tsx diff --git a/web/src/features/blocklists/BlocklistsPage.tsx b/admin/src/features/blocklists/BlocklistsPage.tsx similarity index 100% rename from web/src/features/blocklists/BlocklistsPage.tsx rename to admin/src/features/blocklists/BlocklistsPage.tsx diff --git a/web/src/features/blocklists/SourceStatusSection.tsx b/admin/src/features/blocklists/SourceStatusSection.tsx similarity index 100% rename from web/src/features/blocklists/SourceStatusSection.tsx rename to admin/src/features/blocklists/SourceStatusSection.tsx diff --git a/web/src/features/blocklists/refreshStore.ts b/admin/src/features/blocklists/refreshStore.ts similarity index 100% rename from web/src/features/blocklists/refreshStore.ts rename to admin/src/features/blocklists/refreshStore.ts diff --git a/web/src/features/clients/ClientEditDialog.tsx b/admin/src/features/clients/ClientEditDialog.tsx similarity index 100% rename from web/src/features/clients/ClientEditDialog.tsx rename to admin/src/features/clients/ClientEditDialog.tsx diff --git a/web/src/features/clients/ClientsPage.test.tsx b/admin/src/features/clients/ClientsPage.test.tsx similarity index 100% rename from web/src/features/clients/ClientsPage.test.tsx rename to admin/src/features/clients/ClientsPage.test.tsx diff --git a/web/src/features/clients/ClientsPage.tsx b/admin/src/features/clients/ClientsPage.tsx similarity index 100% rename from web/src/features/clients/ClientsPage.tsx rename to admin/src/features/clients/ClientsPage.tsx diff --git a/web/src/features/clients/PrefixesEditor.tsx b/admin/src/features/clients/PrefixesEditor.tsx similarity index 100% rename from web/src/features/clients/PrefixesEditor.tsx rename to admin/src/features/clients/PrefixesEditor.tsx diff --git a/web/src/features/clients/prefixEditor.test.ts b/admin/src/features/clients/prefixEditor.test.ts similarity index 100% rename from web/src/features/clients/prefixEditor.test.ts rename to admin/src/features/clients/prefixEditor.test.ts diff --git a/web/src/features/clients/prefixEditor.ts b/admin/src/features/clients/prefixEditor.ts similarity index 100% rename from web/src/features/clients/prefixEditor.ts rename to admin/src/features/clients/prefixEditor.ts diff --git a/web/src/features/dashboard/DashboardPage.test.tsx b/admin/src/features/dashboard/DashboardPage.test.tsx similarity index 100% rename from web/src/features/dashboard/DashboardPage.test.tsx rename to admin/src/features/dashboard/DashboardPage.test.tsx diff --git a/web/src/features/dashboard/DashboardPage.tsx b/admin/src/features/dashboard/DashboardPage.tsx similarity index 100% rename from web/src/features/dashboard/DashboardPage.tsx rename to admin/src/features/dashboard/DashboardPage.tsx diff --git a/web/src/features/dashboard/DiskCard.tsx b/admin/src/features/dashboard/DiskCard.tsx similarity index 100% rename from web/src/features/dashboard/DiskCard.tsx rename to admin/src/features/dashboard/DiskCard.tsx diff --git a/web/src/features/dashboard/HealthBanners.tsx b/admin/src/features/dashboard/HealthBanners.tsx similarity index 100% rename from web/src/features/dashboard/HealthBanners.tsx rename to admin/src/features/dashboard/HealthBanners.tsx diff --git a/web/src/features/dashboard/StatCards.tsx b/admin/src/features/dashboard/StatCards.tsx similarity index 100% rename from web/src/features/dashboard/StatCards.tsx rename to admin/src/features/dashboard/StatCards.tsx diff --git a/web/src/features/dashboard/TimeseriesChart.tsx b/admin/src/features/dashboard/TimeseriesChart.tsx similarity index 100% rename from web/src/features/dashboard/TimeseriesChart.tsx rename to admin/src/features/dashboard/TimeseriesChart.tsx diff --git a/web/src/features/dashboard/UpstreamHealthTable.tsx b/admin/src/features/dashboard/UpstreamHealthTable.tsx similarity index 100% rename from web/src/features/dashboard/UpstreamHealthTable.tsx rename to admin/src/features/dashboard/UpstreamHealthTable.tsx diff --git a/web/src/features/dashboard/chartLayout.test.ts b/admin/src/features/dashboard/chartLayout.test.ts similarity index 100% rename from web/src/features/dashboard/chartLayout.test.ts rename to admin/src/features/dashboard/chartLayout.test.ts diff --git a/web/src/features/dashboard/chartLayout.ts b/admin/src/features/dashboard/chartLayout.ts similarity index 100% rename from web/src/features/dashboard/chartLayout.ts rename to admin/src/features/dashboard/chartLayout.ts diff --git a/web/src/features/groups/GroupSourcesEditor.tsx b/admin/src/features/groups/GroupSourcesEditor.tsx similarity index 100% rename from web/src/features/groups/GroupSourcesEditor.tsx rename to admin/src/features/groups/GroupSourcesEditor.tsx diff --git a/web/src/features/groups/GroupsPage.test.tsx b/admin/src/features/groups/GroupsPage.test.tsx similarity index 100% rename from web/src/features/groups/GroupsPage.test.tsx rename to admin/src/features/groups/GroupsPage.test.tsx diff --git a/web/src/features/groups/GroupsPage.tsx b/admin/src/features/groups/GroupsPage.tsx similarity index 100% rename from web/src/features/groups/GroupsPage.tsx rename to admin/src/features/groups/GroupsPage.tsx diff --git a/web/src/features/groups/sourceSet.test.ts b/admin/src/features/groups/sourceSet.test.ts similarity index 100% rename from web/src/features/groups/sourceSet.test.ts rename to admin/src/features/groups/sourceSet.test.ts diff --git a/web/src/features/groups/sourceSet.ts b/admin/src/features/groups/sourceSet.ts similarity index 100% rename from web/src/features/groups/sourceSet.ts rename to admin/src/features/groups/sourceSet.ts diff --git a/web/src/features/live/LiveLogPage.test.tsx b/admin/src/features/live/LiveLogPage.test.tsx similarity index 100% rename from web/src/features/live/LiveLogPage.test.tsx rename to admin/src/features/live/LiveLogPage.test.tsx diff --git a/web/src/features/live/LiveLogPage.tsx b/admin/src/features/live/LiveLogPage.tsx similarity index 100% rename from web/src/features/live/LiveLogPage.tsx rename to admin/src/features/live/LiveLogPage.tsx diff --git a/web/src/features/live/fakeEventSource.ts b/admin/src/features/live/fakeEventSource.ts similarity index 100% rename from web/src/features/live/fakeEventSource.ts rename to admin/src/features/live/fakeEventSource.ts diff --git a/web/src/features/live/ringBuffer.test.ts b/admin/src/features/live/ringBuffer.test.ts similarity index 100% rename from web/src/features/live/ringBuffer.test.ts rename to admin/src/features/live/ringBuffer.test.ts diff --git a/web/src/features/live/ringBuffer.ts b/admin/src/features/live/ringBuffer.ts similarity index 100% rename from web/src/features/live/ringBuffer.ts rename to admin/src/features/live/ringBuffer.ts diff --git a/web/src/features/live/useLiveQueries.test.tsx b/admin/src/features/live/useLiveQueries.test.tsx similarity index 100% rename from web/src/features/live/useLiveQueries.test.tsx rename to admin/src/features/live/useLiveQueries.test.tsx diff --git a/web/src/features/live/useLiveQueries.ts b/admin/src/features/live/useLiveQueries.ts similarity index 100% rename from web/src/features/live/useLiveQueries.ts rename to admin/src/features/live/useLiveQueries.ts diff --git a/web/src/features/local/LocalDnsPage.test.tsx b/admin/src/features/local/LocalDnsPage.test.tsx similarity index 100% rename from web/src/features/local/LocalDnsPage.test.tsx rename to admin/src/features/local/LocalDnsPage.test.tsx diff --git a/web/src/features/local/LocalDnsPage.tsx b/admin/src/features/local/LocalDnsPage.tsx similarity index 100% rename from web/src/features/local/LocalDnsPage.tsx rename to admin/src/features/local/LocalDnsPage.tsx diff --git a/web/src/features/local/RecordsTab.tsx b/admin/src/features/local/RecordsTab.tsx similarity index 100% rename from web/src/features/local/RecordsTab.tsx rename to admin/src/features/local/RecordsTab.tsx diff --git a/web/src/features/local/ZonesTab.tsx b/admin/src/features/local/ZonesTab.tsx similarity index 100% rename from web/src/features/local/ZonesTab.tsx rename to admin/src/features/local/ZonesTab.tsx diff --git a/web/src/features/lookup/LookupPage.test.tsx b/admin/src/features/lookup/LookupPage.test.tsx similarity index 100% rename from web/src/features/lookup/LookupPage.test.tsx rename to admin/src/features/lookup/LookupPage.test.tsx diff --git a/web/src/features/lookup/LookupPage.tsx b/admin/src/features/lookup/LookupPage.tsx similarity index 100% rename from web/src/features/lookup/LookupPage.tsx rename to admin/src/features/lookup/LookupPage.tsx diff --git a/web/src/features/pause/PauseWidget.test.tsx b/admin/src/features/pause/PauseWidget.test.tsx similarity index 100% rename from web/src/features/pause/PauseWidget.test.tsx rename to admin/src/features/pause/PauseWidget.test.tsx diff --git a/web/src/features/pause/PauseWidget.tsx b/admin/src/features/pause/PauseWidget.tsx similarity index 100% rename from web/src/features/pause/PauseWidget.tsx rename to admin/src/features/pause/PauseWidget.tsx diff --git a/web/src/features/queries/QueryLogPage.test.tsx b/admin/src/features/queries/QueryLogPage.test.tsx similarity index 100% rename from web/src/features/queries/QueryLogPage.test.tsx rename to admin/src/features/queries/QueryLogPage.test.tsx diff --git a/web/src/features/queries/QueryLogPage.tsx b/admin/src/features/queries/QueryLogPage.tsx similarity index 100% rename from web/src/features/queries/QueryLogPage.tsx rename to admin/src/features/queries/QueryLogPage.tsx diff --git a/web/src/features/queries/qtype.test.ts b/admin/src/features/queries/qtype.test.ts similarity index 100% rename from web/src/features/queries/qtype.test.ts rename to admin/src/features/queries/qtype.test.ts diff --git a/web/src/features/queries/qtype.ts b/admin/src/features/queries/qtype.ts similarity index 100% rename from web/src/features/queries/qtype.ts rename to admin/src/features/queries/qtype.ts diff --git a/web/src/features/rules/RulesPage.test.tsx b/admin/src/features/rules/RulesPage.test.tsx similarity index 100% rename from web/src/features/rules/RulesPage.test.tsx rename to admin/src/features/rules/RulesPage.test.tsx diff --git a/web/src/features/rules/RulesPage.tsx b/admin/src/features/rules/RulesPage.tsx similarity index 100% rename from web/src/features/rules/RulesPage.tsx rename to admin/src/features/rules/RulesPage.tsx diff --git a/web/src/features/settings/ReadOnlyConfigBanner.tsx b/admin/src/features/settings/ReadOnlyConfigBanner.tsx similarity index 100% rename from web/src/features/settings/ReadOnlyConfigBanner.tsx rename to admin/src/features/settings/ReadOnlyConfigBanner.tsx diff --git a/web/src/features/settings/RestartBanner.test.tsx b/admin/src/features/settings/RestartBanner.test.tsx similarity index 100% rename from web/src/features/settings/RestartBanner.test.tsx rename to admin/src/features/settings/RestartBanner.test.tsx diff --git a/web/src/features/settings/RestartBanner.tsx b/admin/src/features/settings/RestartBanner.tsx similarity index 100% rename from web/src/features/settings/RestartBanner.tsx rename to admin/src/features/settings/RestartBanner.tsx diff --git a/web/src/features/settings/SettingsPage.test.tsx b/admin/src/features/settings/SettingsPage.test.tsx similarity index 100% rename from web/src/features/settings/SettingsPage.test.tsx rename to admin/src/features/settings/SettingsPage.test.tsx diff --git a/web/src/features/settings/SettingsPage.tsx b/admin/src/features/settings/SettingsPage.tsx similarity index 100% rename from web/src/features/settings/SettingsPage.tsx rename to admin/src/features/settings/SettingsPage.tsx diff --git a/web/src/features/settings/authority.test.tsx b/admin/src/features/settings/authority.test.tsx similarity index 100% rename from web/src/features/settings/authority.test.tsx rename to admin/src/features/settings/authority.test.tsx diff --git a/web/src/features/settings/authority.ts b/admin/src/features/settings/authority.ts similarity index 100% rename from web/src/features/settings/authority.ts rename to admin/src/features/settings/authority.ts diff --git a/web/src/features/settings/restartBanner.ts b/admin/src/features/settings/restartBanner.ts similarity index 100% rename from web/src/features/settings/restartBanner.ts rename to admin/src/features/settings/restartBanner.ts diff --git a/web/src/features/upstreams/UpstreamForm.tsx b/admin/src/features/upstreams/UpstreamForm.tsx similarity index 100% rename from web/src/features/upstreams/UpstreamForm.tsx rename to admin/src/features/upstreams/UpstreamForm.tsx diff --git a/web/src/features/upstreams/UpstreamsPage.test.tsx b/admin/src/features/upstreams/UpstreamsPage.test.tsx similarity index 100% rename from web/src/features/upstreams/UpstreamsPage.test.tsx rename to admin/src/features/upstreams/UpstreamsPage.test.tsx diff --git a/web/src/features/upstreams/UpstreamsPage.tsx b/admin/src/features/upstreams/UpstreamsPage.tsx similarity index 100% rename from web/src/features/upstreams/UpstreamsPage.tsx rename to admin/src/features/upstreams/UpstreamsPage.tsx diff --git a/web/src/lib/InlineError.test.tsx b/admin/src/lib/InlineError.test.tsx similarity index 100% rename from web/src/lib/InlineError.test.tsx rename to admin/src/lib/InlineError.test.tsx diff --git a/web/src/lib/InlineError.tsx b/admin/src/lib/InlineError.tsx similarity index 100% rename from web/src/lib/InlineError.tsx rename to admin/src/lib/InlineError.tsx diff --git a/web/src/lib/api.test.ts b/admin/src/lib/api.test.ts similarity index 100% rename from web/src/lib/api.test.ts rename to admin/src/lib/api.test.ts diff --git a/web/src/lib/api.ts b/admin/src/lib/api.ts similarity index 100% rename from web/src/lib/api.ts rename to admin/src/lib/api.ts diff --git a/web/src/lib/contractSamples.gen.ts b/admin/src/lib/contractSamples.gen.ts similarity index 99% rename from web/src/lib/contractSamples.gen.ts rename to admin/src/lib/contractSamples.gen.ts index c756745..fdf2fec 100644 --- a/web/src/lib/contractSamples.gen.ts +++ b/admin/src/lib/contractSamples.gen.ts @@ -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, diff --git a/web/src/lib/contract_samples.zig b/admin/src/lib/contract_samples.zig similarity index 87% rename from web/src/lib/contract_samples.zig rename to admin/src/lib/contract_samples.zig index ad350f1..99a691e 100644 --- a/web/src/lib/contract_samples.zig +++ b/admin/src/lib/contract_samples.zig @@ -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"; diff --git a/web/src/lib/defaultGroup.ts b/admin/src/lib/defaultGroup.ts similarity index 100% rename from web/src/lib/defaultGroup.ts rename to admin/src/lib/defaultGroup.ts diff --git a/web/src/lib/format.test.ts b/admin/src/lib/format.test.ts similarity index 100% rename from web/src/lib/format.test.ts rename to admin/src/lib/format.test.ts diff --git a/web/src/lib/format.ts b/admin/src/lib/format.ts similarity index 100% rename from web/src/lib/format.ts rename to admin/src/lib/format.ts diff --git a/web/src/lib/queries.ts b/admin/src/lib/queries.ts similarity index 100% rename from web/src/lib/queries.ts rename to admin/src/lib/queries.ts diff --git a/web/src/lib/queryClient.ts b/admin/src/lib/queryClient.ts similarity index 100% rename from web/src/lib/queryClient.ts rename to admin/src/lib/queryClient.ts diff --git a/web/src/lib/settingsDiff.test.ts b/admin/src/lib/settingsDiff.test.ts similarity index 100% rename from web/src/lib/settingsDiff.test.ts rename to admin/src/lib/settingsDiff.test.ts diff --git a/web/src/lib/settingsDiff.ts b/admin/src/lib/settingsDiff.ts similarity index 100% rename from web/src/lib/settingsDiff.ts rename to admin/src/lib/settingsDiff.ts diff --git a/web/src/lib/types.ts b/admin/src/lib/types.ts similarity index 100% rename from web/src/lib/types.ts rename to admin/src/lib/types.ts diff --git a/web/src/main.tsx b/admin/src/main.tsx similarity index 100% rename from web/src/main.tsx rename to admin/src/main.tsx diff --git a/web/src/routes.tsx b/admin/src/routes.tsx similarity index 100% rename from web/src/routes.tsx rename to admin/src/routes.tsx diff --git a/web/src/shell/AppShell.test.tsx b/admin/src/shell/AppShell.test.tsx similarity index 100% rename from web/src/shell/AppShell.test.tsx rename to admin/src/shell/AppShell.test.tsx diff --git a/web/src/shell/AppShell.tsx b/admin/src/shell/AppShell.tsx similarity index 100% rename from web/src/shell/AppShell.tsx rename to admin/src/shell/AppShell.tsx diff --git a/web/src/styles.css b/admin/src/styles.css similarity index 98% rename from web/src/styles.css rename to admin/src/styles.css index d78cf98..8cb7b37 100644 --- a/web/src/styles.css +++ b/admin/src/styles.css @@ -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. diff --git a/web/src/ui/ConfirmDialog.tsx b/admin/src/ui/ConfirmDialog.tsx similarity index 100% rename from web/src/ui/ConfirmDialog.tsx rename to admin/src/ui/ConfirmDialog.tsx diff --git a/web/src/ui/Dialog.tsx b/admin/src/ui/Dialog.tsx similarity index 100% rename from web/src/ui/Dialog.tsx rename to admin/src/ui/Dialog.tsx diff --git a/web/src/ui/Select.tsx b/admin/src/ui/Select.tsx similarity index 100% rename from web/src/ui/Select.tsx rename to admin/src/ui/Select.tsx diff --git a/web/src/ui/Tabs.tsx b/admin/src/ui/Tabs.tsx similarity index 100% rename from web/src/ui/Tabs.tsx rename to admin/src/ui/Tabs.tsx diff --git a/web/src/ui/styles.test.tsx b/admin/src/ui/styles.test.tsx similarity index 100% rename from web/src/ui/styles.test.tsx rename to admin/src/ui/styles.test.tsx diff --git a/web/src/ui/styles.ts b/admin/src/ui/styles.ts similarity index 100% rename from web/src/ui/styles.ts rename to admin/src/ui/styles.ts diff --git a/web/src/ui/tokens.stylex.ts b/admin/src/ui/tokens.stylex.ts similarity index 100% rename from web/src/ui/tokens.stylex.ts rename to admin/src/ui/tokens.stylex.ts diff --git a/web/src/ui/useCrudForm.test.tsx b/admin/src/ui/useCrudForm.test.tsx similarity index 100% rename from web/src/ui/useCrudForm.test.tsx rename to admin/src/ui/useCrudForm.test.tsx diff --git a/web/src/ui/useCrudForm.ts b/admin/src/ui/useCrudForm.ts similarity index 100% rename from web/src/ui/useCrudForm.ts rename to admin/src/ui/useCrudForm.ts diff --git a/web/tsconfig.app.json b/admin/tsconfig.app.json similarity index 100% rename from web/tsconfig.app.json rename to admin/tsconfig.app.json diff --git a/web/tsconfig.json b/admin/tsconfig.json similarity index 100% rename from web/tsconfig.json rename to admin/tsconfig.json diff --git a/web/tsconfig.node.json b/admin/tsconfig.node.json similarity index 100% rename from web/tsconfig.node.json rename to admin/tsconfig.node.json diff --git a/web/vite.config.ts b/admin/vite.config.ts similarity index 100% rename from web/vite.config.ts rename to admin/vite.config.ts diff --git a/web/vitest.setup.ts b/admin/vitest.setup.ts similarity index 100% rename from web/vitest.setup.ts rename to admin/vitest.setup.ts diff --git a/build.zig b/build.zig index b05a307..84801db 100644 --- a/build.zig +++ b/build.zig @@ -32,7 +32,7 @@ pub fn build(b: *std.Build) void { // Milestone-17 ruling 5. The embedded golden carries no source-tree path, // so regeneration names the destination explicitly: // zig build test -Dintegration \ - // -Dcontract-samples-out="$PWD/web/src/lib/contractSamples.gen.ts" + // -Dcontract-samples-out="$PWD/admin/src/lib/contractSamples.gen.ts" const contract_samples_out = b.option( []const u8, "contract-samples-out", @@ -44,34 +44,34 @@ pub fn build(b: *std.Build) void { const version_option = b.option([]const u8, "version-string", "Version reported by `nxdns version` (required by `dist`)"); const version_string = version_option orelse "0.1.0-dev"; const git_commit = b.option([]const u8, "git-commit", "Git commit reported by `nxdns version`") orelse "unknown"; - const web_dist = b.option( + const admin_dist = b.option( []const u8, - "web-dist", + "admin-dist", "Built web UI directory to embed (default: the placeholder page). " ++ - "Only the exact value `web/dist` gets the freshness check; " ++ + "Only the exact value `admin/dist` gets the freshness check; " ++ "the placeholder and any other path skip it.", - ) orelse "web/dist-placeholder"; + ) orelse "admin/dist-placeholder"; // `b.path` panics on absolute paths, and a CI artifact directory is one. - const web_dist_path: std.Build.LazyPath = if (std.fs.path.isAbsolute(web_dist)) - .{ .cwd_relative = web_dist } + const admin_dist_path: std.Build.LazyPath = if (std.fs.path.isAbsolute(admin_dist)) + .{ .cwd_relative = admin_dist } else - b.path(web_dist); + b.path(admin_dist); - // A stale `web/dist` shipped a crashing settings page once (milestone-15 + // A stale `admin/dist` shipped a crashing settings page once (milestone-15 // ruling 5). The stamp is checked only for the real dist tree: the // placeholder has no sources to be stale against, and an explicit path is a // CI artifact that was built elsewhere. - const web_dist_check: ?*std.Build.Step = if (std.mem.eql(u8, web_dist, "web/dist")) check: { + const admin_dist_check: ?*std.Build.Step = if (std.mem.eql(u8, admin_dist, "admin/dist")) check: { const run_check = b.addSystemCommand(&.{"node"}); // The script path goes through `b.path` so it resolves against the // build root: `zig build` run from any other directory would not find // a cwd-relative one. - run_check.addFileArg(b.path("web/scripts/stamp-dist.mjs")); + run_check.addFileArg(b.path("admin/scripts/stamp-dist.mjs")); run_check.addArg("--check"); break :check &run_check.step; } else null; - const web_assets = webAssetsIndex(b, web_dist_path, web_dist_check); + const admin_assets = adminAssetsIndex(b, admin_dist_path, admin_dist_check); const options = b.addOptions(); options.addOption(bool, "integration", integration); @@ -81,7 +81,7 @@ pub fn build(b: *std.Build) void { options.addOption([]const u8, "zig_version_string", builtin.zig_version_string); options.addOption([]const u8, "contract_samples_out", contract_samples_out); - const exe = addExecutable(b, target, optimize, options, web_assets); + const exe = addExecutable(b, target, optimize, options, admin_assets); b.installArtifact(exe); const run = b.addRunArtifact(exe); @@ -125,7 +125,7 @@ pub fn build(b: *std.Build) void { // the reference is the 0.16.0 source lines above. See AGENTS.md. const licenses_files = licensesFilesRoot(b); - const tests = addTestSuite(b, target, optimize, options, web_assets, licenses_files); + const tests = addTestSuite(b, target, optimize, options, admin_assets, licenses_files); const test_step = b.step("test", "Run the test suite"); test_step.dependOn(&b.addRunArtifact(tests).step); @@ -220,7 +220,7 @@ pub fn build(b: *std.Build) void { const aarch64_target = b.resolveTargetQuery( std.Target.Query.parse(.{ .arch_os_abi = aarch64_triple }) catch unreachable, ); - const aarch64_tests = addTestSuite(b, aarch64_target, optimize, options, web_assets, licenses_files); + const aarch64_tests = addTestSuite(b, aarch64_target, optimize, options, admin_assets, licenses_files); aarch64_tests.linkage = .static; const aarch64_run = b.addRunArtifact(aarch64_tests); aarch64_run.skip_foreign_checks = true; @@ -269,11 +269,11 @@ pub fn build(b: *std.Build) void { }); test_step.dependOn(&b.addRunArtifact(container_check_tests).step); - addDist(b, options, web_assets, .{ + addDist(b, options, admin_assets, .{ .version = version_option, .version_string = version_string, .git_commit = git_commit, - .web_dist = web_dist, + .admin_dist = admin_dist, }); } @@ -281,10 +281,10 @@ pub fn build(b: *std.Build) void { const max_binary_bytes = 15_728_640; const max_asset_free_binary_bytes = 10_485_760; -/// The default of `-Dweb-dist`. A release built without the flag would ship the +/// The default of `-Dadmin-dist`. A release built without the flag would ship the /// placeholder admin page, so `dist` refuses it (milestone-14 ruling 4). There /// is deliberately no override. -const placeholder_web_dist = "web/dist-placeholder"; +const placeholder_admin_dist = "admin/dist-placeholder"; /// Repository files that go into the tarball verbatim. `nxdns.conf` is /// `sysusers.conf` under the name it is installed with, so nothing renames a @@ -306,7 +306,7 @@ const DistOptions = struct { version: ?[]const u8, version_string: []const u8, git_commit: []const u8, - web_dist: []const u8, + admin_dist: []const u8, }; /// `dist` builds everything releasable; `verify-dist` asserts the result. @@ -316,7 +316,7 @@ const DistOptions = struct { fn addDist( b: *std.Build, options: *std.Build.Step.Options, - web_assets: std.Build.LazyPath, + admin_assets: std.Build.LazyPath, dist_options: DistOptions, ) void { const dist_step = b.step("dist", "Build the release tarballs, checksums and staged payloads"); @@ -363,7 +363,7 @@ fn addDist( // directory (milestone-14 ruling 5). Not the placeholder: ruling 4 makes // that unbuildable, and re-admitting it for one size check through a back // door would defeat the point of the refusal. - const empty_web_assets = webAssetsIndex(b, b.addWriteFiles().getDirectory(), null); + const empty_admin_assets = adminAssetsIndex(b, b.addWriteFiles().getDirectory(), null); for (cross_targets) |triple| { const query = std.Target.Query.parse(.{ .arch_os_abi = triple }) catch |err| { @@ -376,7 +376,7 @@ fn addDist( // not change shape because a flag was forgotten. `.strip` is // `std.Build.Module.strip`, which emits `-fstrip` (Module.zig:545), so // no objcopy and no binutils-aarch64-linux-gnu on the runner. - const exe = addExecutable(b, target, .ReleaseSafe, options, web_assets); + const exe = addExecutable(b, target, .ReleaseSafe, options, admin_assets); exe.linkage = .static; exe.root_module.strip = true; @@ -449,7 +449,7 @@ fn addDist( sums_run.addArg(b.fmt("{s}.tar.gz", .{name})); sums_run.addFileArg(tarball); - const asset_free_exe = addExecutable(b, target, .ReleaseSafe, options, empty_web_assets); + const asset_free_exe = addExecutable(b, target, .ReleaseSafe, options, empty_admin_assets); asset_free_exe.linkage = .static; asset_free_exe.root_module.strip = true; @@ -477,10 +477,10 @@ fn distPreflight(b: *std.Build, dist_options: DistOptions) ?[]const u8 { "(the release tag without its leading `v`); it has no default here.") catch @panic("OOM"); } - if (std.mem.eql(u8, b.pathFromRoot(dist_options.web_dist), b.pathFromRoot(placeholder_web_dist))) { - problems.append(b.allocator, "-Dweb-dist resolves to " ++ placeholder_web_dist ++ + if (std.mem.eql(u8, b.pathFromRoot(dist_options.admin_dist), b.pathFromRoot(placeholder_admin_dist))) { + problems.append(b.allocator, "-Dadmin-dist resolves to " ++ placeholder_admin_dist ++ ", which a release must never ship. Build the real admin UI " ++ - "(cd web && npm ci && npm run build) and pass -Dweb-dist=web/dist.") catch @panic("OOM"); + "(cd admin && npm ci && npm run build) and pass -Dadmin-dist=admin/dist.") catch @panic("OOM"); } for ([_][]const u8{ @@ -576,7 +576,7 @@ fn addTestSuite( target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, options: *std.Build.Step.Options, - web_assets: std.Build.LazyPath, + admin_assets: std.Build.LazyPath, licenses_files: std.Build.LazyPath, ) *std.Build.Step.Compile { const tests = b.addTest(.{ @@ -601,9 +601,9 @@ fn addTestSuite( // An anonymous-import root must be Zig, so the committed TypeScript golden // is reached through a one-decl wrapper beside it. tests.root_module.addAnonymousImport("contract_samples", .{ - .root_source_file = b.path("web/src/lib/contract_samples.zig"), + .root_source_file = b.path("admin/src/lib/contract_samples.zig"), }); - tests.root_module.addAnonymousImport("web_assets", .{ .root_source_file = web_assets }); + tests.root_module.addAnonymousImport("admin_assets", .{ .root_source_file = admin_assets }); tests.root_module.addAnonymousImport("licenses_files", .{ .root_source_file = licenses_files }); return tests; } @@ -614,7 +614,7 @@ fn addTestSuite( /// /// A module cannot embed anything above its own root directory, so the trees /// are merged into one WriteFiles directory: `licenses/` at the root, plus -/// copies of `build.zig.zon`, `web/package-lock.json` and the image Dockerfile +/// copies of `build.zig.zon`, `admin/package-lock.json` and the image Dockerfile /// beside it. The module root is the copy of `licenses/licenses.zig`, which is /// why that file's `@embedFile` paths name files that do not sit beside it in /// the repository. @@ -622,7 +622,7 @@ fn licensesFilesRoot(b: *std.Build) std.Build.LazyPath { const stage = b.addWriteFiles(); _ = stage.addCopyDirectory(b.path("licenses"), ".", .{}); _ = stage.addCopyFile(b.path("build.zig.zon"), "build.zig.zon"); - _ = stage.addCopyFile(b.path("web/package-lock.json"), "package-lock.json"); + _ = stage.addCopyFile(b.path("admin/package-lock.json"), "package-lock.json"); _ = stage.addCopyFile(b.path(dockerfile_path), "Dockerfile"); return stage.getDirectory().path(b, "licenses.zig"); } @@ -679,7 +679,7 @@ fn addExecutable( target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, options: *std.Build.Step.Options, - web_assets: std.Build.LazyPath, + admin_assets: std.Build.LazyPath, ) *std.Build.Step.Compile { const exe = b.addExecutable(.{ .name = "nxdns", @@ -691,7 +691,7 @@ fn addExecutable( }), }); exe.root_module.addOptions("build_options", options); - exe.root_module.addAnonymousImport("web_assets", .{ .root_source_file = web_assets }); + exe.root_module.addAnonymousImport("admin_assets", .{ .root_source_file = admin_assets }); exe.root_module.linkLibrary(sqliteLibrary(b, target, optimize)); exe.root_module.linkLibrary(mbedtlsLibrary(b, target, optimize)); exe.root_module.addCSourceFile(.{ .file = b.path("src/platform/mbedtls_shim.c") }); @@ -702,13 +702,13 @@ fn addExecutable( /// The embedded web UI (milestone-8 ruling 24): the dist directory plus the /// generated `assets.zig` index and build-time gzip siblings, merged into one /// WriteFiles directory so every `@embedFile` path resolves inside the module -/// root. Returns the index file, the root of the `web_assets` module. +/// root. Returns the index file, the root of the `admin_assets` module. /// /// The dist is staged through its own WriteFiles step before it reaches the /// tool because a Run step hashes only the resolved path string of a directory /// argument, not its contents; the staged copy lives at a content-hashed path, /// so editing an asset re-runs the tool instead of replaying a stale cache. -fn webAssetsIndex( +fn adminAssetsIndex( b: *std.Build, dist: std.Build.LazyPath, freshness_check: ?*std.Build.Step, @@ -718,9 +718,9 @@ fn webAssetsIndex( const staged = stage.addCopyDirectory(dist, ".", .{}); const tool = b.addExecutable(.{ - .name = "gen_web_assets", + .name = "gen_admin_assets", .root_module = b.createModule(.{ - .root_source_file = b.path("tools/gen_web_assets.zig"), + .root_source_file = b.path("tools/gen_admin_assets.zig"), .target = b.graph.host, .optimize = .ReleaseSafe, }), @@ -728,7 +728,7 @@ fn webAssetsIndex( const run = b.addRunArtifact(tool); run.addDirectoryArg(staged); - const generated = run.addOutputDirectoryArg("web_assets"); + const generated = run.addOutputDirectoryArg("admin_assets"); const merged = b.addWriteFiles(); _ = merged.addCopyDirectory(staged, ".", .{}); diff --git a/deploy/docker/Dockerfile b/deploy/docker/Dockerfile index 7eb9008..8cecdcb 100644 --- a/deploy/docker/Dockerfile +++ b/deploy/docker/Dockerfile @@ -1,8 +1,8 @@ # The binary is NOT compiled here. Build it first, from the repository root: # -# (cd web && npm ci && npm run build) +# (cd admin && npm ci && npm run build) # zig build dist -Dversion-string= -Dgit-commit= \ -# -Dweb-dist=web/dist -Doptimize=ReleaseSafe +# -Dadmin-dist=admin/dist -Doptimize=ReleaseSafe # # then build the image with the repository root as context: # diff --git a/docs/explanation/architecture.md b/docs/explanation/architecture.md index e4b3f64..892c3a3 100644 --- a/docs/explanation/architecture.md +++ b/docs/explanation/architecture.md @@ -32,7 +32,7 @@ Directories: | `src/web/` | The admin HTTP layer: `server.zig` (listener), `router.zig`/`routes.zig`, one file per resource under `handlers/`, `auth.zig` (sessions), `sse.zig` (live query fanout), `static.zig` (embedded SPA), `metrics.zig` (Prometheus), `openapi.zig` (served contract), `api_limiter.zig`, `http_util.zig`. | | `src/platform/` | OS and TLS edges: IP address values, the `std.log` sink (`logging.zig`), `statfs.zig` (free-space query via libc), client TLS over `std.crypto.tls` (`tls_client.zig`), server TLS over vendored Mbed TLS (`tls_server.zig`). | -The SPA source lives in `web/` at the repo root; the build embeds its `dist/` output as the `web_assets` module (`-Dweb-dist`). +The SPA source lives in `admin/` at the repo root; the build embeds its `dist/` output as the `admin_assets` module (`-Dadmin-dist`). ``` main.zig ── cli.zig ── app.zig (composition root) @@ -123,7 +123,7 @@ The split exists so that the churn of the second database can never endanger the `web/server.zig` runs one `std.http.Server` per connection over its own accept loop, with a fixed set of pre-allocated connection slots, optionally behind TLS. Over capacity it answers 503 rather than queueing without bound — the admin UI is not the product, and it must not be able to starve DNS. -The SPA is embedded at build time: `static.zig` serves the `web_assets` module — bytes, content type, strong ETag, and a pre-compressed `.gz` sibling where it paid off — via a linear scan with no filesystem access at runtime. (The one exception is `nxdns run --web-dev DIR`, which serves from disk with no cache headers, for developing the SPA against a running server.) `GET /api/queries/live` is server-sent events over chunked transfer, fed by the same `QuerySink` the logger reads. Routing is a flat table (`routes.zig`) matched linearly; a few dozen routes do not justify a trie. The OpenAPI YAML is hand-written, embedded and served at `GET /api/openapi.yaml`, kept honest by tests that assert every served route appears in it. +The SPA is embedded at build time: `static.zig` serves the `admin_assets` module — bytes, content type, strong ETag, and a pre-compressed `.gz` sibling where it paid off — via a linear scan with no filesystem access at runtime. (The one exception is `nxdns run --admin-dev DIR`, which serves from disk with no cache headers, for developing the SPA against a running server.) `GET /api/queries/live` is server-sent events over chunked transfer, fed by the same `QuerySink` the logger reads. Routing is a flat table (`routes.zig`) matched linearly; a few dozen routes do not justify a trie. The OpenAPI YAML is hand-written, embedded and served at `GET /api/openapi.yaml`, kept honest by tests that assert every served route appears in it. Authentication (`web/auth.zig`): the operator's password is verified against an argon2id PHC string (`web.password_hash`; the plaintext is hashed on import and never stored). A successful login mints a 256-bit token carried in a cookie; the in-memory session table holds only SHA-256 digests of tokens, compared in constant time, capped at 32 sessions with LRU eviction. Nothing is persisted, so a restart logs everyone out — for a household LAN that is a feature, not a gap. Unauthenticated by design: the monitoring endpoints (health, version, metrics), the served OpenAPI contract, login itself, and the static SPA assets, which the router hands to the SPA fallback before any auth check. Everything else requires the cookie, and the API has its own token-bucket rate limiter. See [how-to/set-up-admin-authentication.md](../how-to/set-up-admin-authentication.md). diff --git a/docs/explanation/configuration-model.md b/docs/explanation/configuration-model.md index f8e8b16..630ecea 100644 --- a/docs/explanation/configuration-model.md +++ b/docs/explanation/configuration-model.md @@ -116,4 +116,4 @@ Export's canonical form is therefore `password = null` beside the stored `passwo ## What is not configuration -Storage paths are process arguments, not configuration fields: `--data-dir`, `--config`, `--web-dev`. They cannot live in the file, because the file is found by way of them — a path that told you where to find the thing that told you the path would be circular. They are also the settings a supervisor (systemd, Docker) owns rather than the operator's policy about DNS. See [reference/files-and-directories.md](../reference/files-and-directories.md). +Storage paths are process arguments, not configuration fields: `--data-dir`, `--config`, `--admin-dev`. They cannot live in the file, because the file is found by way of them — a path that told you where to find the thing that told you the path would be circular. They are also the settings a supervisor (systemd, Docker) owns rather than the operator's policy about DNS. See [reference/files-and-directories.md](../reference/files-and-directories.md). diff --git a/docs/explanation/performance-and-testing.md b/docs/explanation/performance-and-testing.md index 119d752..0bcc89f 100644 --- a/docs/explanation/performance-and-testing.md +++ b/docs/explanation/performance-and-testing.md @@ -28,7 +28,7 @@ So the bench defaults to informational, and `--assert` — which exits non-zero CI does gate on the one performance property that *is* deterministic: binary size. The `package` job builds the release artifacts and `zig build verify-dist` asserts both §18 budgets against them. Size is a function of the input, not of the runner's mood, so it is exactly the kind of thing a shared runner can measure honestly. -The budgets are asserted as exact byte counts, and the asset-free budget gets its own build against a generated empty assets directory rather than against `web/dist-placeholder`. The placeholder is not buildable by `dist` at all — that is the guard against a release shipping a stub admin page — and letting it back in through a size check would have defeated the guard for the sake of one number. +The budgets are asserted as exact byte counts, and the asset-free budget gets its own build against a generated empty assets directory rather than against `admin/dist-placeholder`. The placeholder is not buildable by `dist` at all — that is the guard against a release shipping a stub admin page — and letting it back in through a size check would have defeated the guard for the sake of one number. ## What the test suite is @@ -60,7 +60,7 @@ The suite is hermetic by design. That is the right default: it is fast, it is de The reason no test caught it is precise and instructive. The copy length is zero whenever the reader has nothing buffered, and a zero-length `@memcpy` is fine. Bytes only accumulate in the reader's own buffer when a read comes back short of filling the destination and the loop goes round again — that is, when the body arrives in more than one stream call. The loopback fixture answers every request with one small in-memory body that lands in a single read, and the one over-size test never streams a byte, because the fetcher refuses an oversized `content-length` on the response head. Every test in the suite was on the zero-length-memcpy side of the branch. The first real download — a multi-megabyte list over TLS across the WAN, arriving in many TCP segments — was on the other side, and took the process down. The fix (commit 35f2324) streams the body straight into the caller's writer, so the reader's buffer is never a destination slice, and it came with four regression tests that put a fully-buffered reader into exactly the state the old code could not survive. -**A stale embedded SPA bundle shipped a settings page that crashed on load, while 121 web tests passed.** `web/dist/` is gitignored and `-Dweb-dist=web/dist` embeds whatever bytes are sitting in that directory. The frontend tests ran against the sources, in jsdom, and were green; the binary carried an older build. The tests were testing something the artifact did not contain. +**A stale embedded SPA bundle shipped a settings page that crashed on load, while 121 web tests passed.** `admin/dist/` is gitignored and `-Dadmin-dist=admin/dist` embeds whatever bytes are sitting in that directory. The frontend tests ran against the sources, in jsdom, and were green; the binary carried an older build. The tests were testing something the artifact did not contain. Note what these two have in common. Neither was a logic bug that a better unit test would have caught. One lived in the seam between the pure core and its one I/O edge; the other lived in the seam between two build systems. Hermetic tests are constructed to exclude exactly those seams — that is what makes them hermetic. @@ -73,7 +73,7 @@ The response is not to make CI non-deterministic. It is to require that the real Two honest gaps remain, stated so nobody has to rediscover them: - Nothing in the suite drives a multi-read HTTP body through the fetcher end to end. The regression tests cover `pumpBody` directly over a pre-buffered reader; the loopback fixture still sends one small body per connection. -- There is no freshness check on `web/dist`. CI cannot embed a stale bundle, because the jobs that pass `-Dweb-dist` rebuild the frontend immediately beforehand. A local build can, and will do it without a warning. +- There is no freshness check on `admin/dist`. CI cannot embed a stale bundle, because the jobs that pass `-Dadmin-dist` rebuild the frontend immediately beforehand. A local build can, and will do it without a warning. ## What a signed release does not prove either diff --git a/docs/how-to/install-with-docker.md b/docs/how-to/install-with-docker.md index 17c9006..77d7c2b 100644 --- a/docs/how-to/install-with-docker.md +++ b/docs/how-to/install-with-docker.md @@ -20,7 +20,7 @@ For what each configuration field means, see [the configuration reference](../re > **Not re-run for the file-mode revision.** The compose file now ships > `command: ["run", "--config=/etc/nxdns/config.zon"]`, and no container was > started against that command on this host: staging a release image needs -> `zig build dist`, which refuses to run while `web/dist` is stale, and the web +> `zig build dist`, which refuses to run while `admin/dist` is stale, and the web > bundle was being rebuilt by other work in the same tree at the time. What was > checked instead is `docker compose -f deploy/docker/compose.yaml config`, > which resolves the file without contacting a registry and prints the `command` @@ -218,16 +218,16 @@ docker pull --platform linux/arm64 git.mial.net/mokhtar/nxdns:$VERSION The Dockerfile does not compile anything. It assembles a filesystem around binaries you build first, so build the admin interface and the release artifacts from the repository root: ```sh -(cd web && npm ci && npm run build) +(cd admin && npm ci && npm run build) 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 + -Dadmin-dist=admin/dist -Doptimize=ReleaseSafe DOCKER_BUILDKIT=1 docker build -t nxdns -f deploy/docker/Dockerfile . ``` Take the version from `build.zig.zon` rather than inventing one: `verify-dist` asserts the two agree, so a made-up string builds but fails verification. BuildKit is required — the Dockerfile pins its builder stage to `$BUILDPLATFORM`, which the classic builder does not define. -Build `web/dist` every time, before the binaries. A stale bundle is embedded silently and ships an admin interface that does not match its API — which is why `dist` refuses to build against the `web/dist-placeholder` default at all. +Build `admin/dist` every time, before the binaries. A stale bundle is embedded silently and ships an admin interface that does not match its API — which is why `dist` refuses to build against the `admin/dist-placeholder` default at all. The context has to be the repository root, because the Dockerfile copies `zig-out/dist/bin` and `zig-out/dist/stage`. The result is a `scratch` image holding the binary, a CA bundle, `/LICENSE`, `/THIRD-PARTY-NOTICES` and two empty directories. diff --git a/docs/how-to/install-with-systemd.md b/docs/how-to/install-with-systemd.md index a45e93c..9d460de 100644 --- a/docs/how-to/install-with-systemd.md +++ b/docs/how-to/install-with-systemd.md @@ -375,13 +375,13 @@ You do not need this to install nxdns, and it gets you a binary nobody has signe Requires Zig 0.16.0 and Node.js. From the repository root: ```sh -(cd web && npm ci && npm run build) +(cd admin && npm ci && npm run build) 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 + -Dadmin-dist=admin/dist -Doptimize=ReleaseSafe ``` -The first command builds the admin interface into `web/dist`; the last one embeds that directory in the binary. Build the interface every time, before the binary: a stale `web/dist` ships an admin UI that does not match the API it talks to. `dist` refuses to run against the `web/dist-placeholder` default for exactly that reason, so there is no way to skip it by accident. +The first command builds the admin interface into `admin/dist`; the last one embeds that directory in the binary. Build the interface every time, before the binary: a stale `admin/dist` ships an admin UI that does not match the API it talks to. `dist` refuses to run against the `admin/dist-placeholder` default for exactly that reason, so there is no way to skip it by accident. `-Dversion-string` is required and has no default. It is what `nxdns version` prints. Take it from `build.zig.zon` rather than inventing one: `verify-dist` asserts that the version under build equals `.version` there, so a made-up string like `0.0.0-local` builds but then fails verification. `-Dgit-commit` is what distinguishes your build from the published one of the same version. @@ -398,7 +398,7 @@ Check the result the same way the release pipeline does: ```sh zig build verify-dist -Dversion-string="$VERSION" -Dgit-commit="$(git rev-parse HEAD)" \ - -Dweb-dist=web/dist -Doptimize=ReleaseSafe + -Dadmin-dist=admin/dist -Doptimize=ReleaseSafe ``` `verify-dist` extracts each archive and asserts the ELF is static and within the size budget, that the layout and file modes are exactly what step 1 lists, and that `nxdns version` prints what was built. It exits non-zero on any failure. diff --git a/docs/how-to/upgrade.md b/docs/how-to/upgrade.md index ccf3147..6ed8bfd 100644 --- a/docs/how-to/upgrade.md +++ b/docs/how-to/upgrade.md @@ -336,13 +336,13 @@ Stop the server first either way. `import` rewrites configuration underneath a p If you are running something you built rather than a release, step 2 is a build instead of a download: ```sh -(cd web && npm ci && npm run build) +(cd admin && npm ci && npm run build) 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 + -Dadmin-dist=admin/dist -Doptimize=ReleaseSafe ``` -Rebuild `web/dist` before the binary on every upgrade. The admin interface is embedded at build time, and an old bundle against a new API is a broken settings page. `dist` refuses the `web/dist-placeholder` default outright, so the only way to ship a stale bundle is to leave an old `web/dist` in place. +Rebuild `admin/dist` before the binary on every upgrade. The admin interface is embedded at build time, and an old bundle against a new API is a broken settings page. `dist` refuses the `admin/dist-placeholder` default outright, so the only way to ship a stale bundle is to leave an old `admin/dist` in place. The staged payload for each target is under `zig-out/dist/stage/nxdns--/`, and step 3 continues from there with that path in place of the extracted one. The version string has to equal `.version` in `build.zig.zon` — `verify-dist` asserts it, so a made-up one builds and then fails verification. What tells your build apart from the published release of the same version is `-Dgit-commit`, which `nxdns version` prints beside the version. diff --git a/docs/how-to/verify-a-release.md b/docs/how-to/verify-a-release.md index 49a5eae..01768bb 100644 --- a/docs/how-to/verify-a-release.md +++ b/docs/how-to/verify-a-release.md @@ -275,9 +275,9 @@ git clone https://git.mial.net/mokhtar/nxdns cd nxdns git checkout "v$VERSION" git verify-tag "v$VERSION" -(cd web && npm ci && npm run build) +(cd admin && npm ci && npm run build) zig build dist -Dversion-string="$VERSION" -Dgit-commit="$(git rev-parse HEAD)" \ - -Dweb-dist=web/dist -Doptimize=ReleaseSafe + -Dadmin-dist=admin/dist -Doptimize=ReleaseSafe sha256sum zig-out/dist/nxdns-"$VERSION"-*.tar.gz ``` diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 394c221..abf9945 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -6,7 +6,7 @@ nxdns [options] Six subcommands: `run`, `check`, `export`, `import`, `version`, `help`. Source of truth: `src/cli.zig`. -Every flag takes both spellings, `--flag value` and `--flag=value`. An attached value that is empty (`--config=`) is a missing value, not an empty path. `--allow-delete` is boolean and takes no value at all, so `--allow-delete=1` is not a spelling of any flag this program has. A flag is rejected by the subcommand that has no use for it: `--web-dev` outside `run` is an unknown flag, not a no-op. +Every flag takes both spellings, `--flag value` and `--flag=value`. An attached value that is empty (`--config=`) is a missing value, not an empty path. `--allow-delete` is boolean and takes no value at all, so `--allow-delete=1` is not a spelling of any flag this program has. A flag is rejected by the subcommand that has no use for it: `--admin-dev` outside `run` is an unknown flag, not a no-op. ## `run` @@ -16,7 +16,7 @@ Serves DNS until SIGINT or SIGTERM. | --- | --- | | `--data-dir DIR` | Data directory (default `/var/lib/nxdns`). Created at mode 0700 if missing. | | `--config FILE` | Make FILE the sole source of configuration and reconcile the database onto it at every start. No default: without this flag the database is the configuration and no file is read. | -| `--web-dev DIR` | Serve the web interface from DIR instead of the embedded assets, with no cache headers. Development only. | +| `--admin-dev DIR` | Serve the web interface from DIR instead of the embedded assets, with no cache headers. Development only. | ### Which authority the invocation selects diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index bf22dfa..164524e 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -24,7 +24,7 @@ Storage paths are process arguments, not configuration: | --- | --- | --- | | `--data-dir DIR` | `/var/lib/nxdns` | Holds `config.db` and `querylog.db`; see [files and directories](files-and-directories.md). | | `--config FILE` | — | On `run`, makes FILE the sole source of configuration; on `check`, grades FILE instead of the database. No default: without it the database is the configuration. | -| `--web-dev DIR` | — | `run` only; serves the web interface from a directory instead of the embedded assets. | +| `--admin-dev DIR` | — | `run` only; serves the web interface from a directory instead of the embedded assets. | ## Scalar sections diff --git a/docs/tutorial/first-run.md b/docs/tutorial/first-run.md index 0598bb1..40b83bd 100644 --- a/docs/tutorial/first-run.md +++ b/docs/tutorial/first-run.md @@ -4,7 +4,7 @@ This page takes you from a checkout of the repository to a running nxdns that an Follow the steps in order. Each one says what it did. -Every command below was executed on x86_64 Linux with Zig 0.16.0, Node.js 24.14.1, dig 9.20.26 and curl 8.21.0. Steps 2, 4 to 11, 13 and 14 were re-run end to end for this revision, and the transcripts are that run's output with the tutorial directory substituted. Two things were not re-run: the browser page in step 12 — its endpoints were exercised, the page itself was not opened — and the `npm` build in step 1, whose `web/dist` was already on disk and is the one the binary under test embeds. The ZON block at the end of step 14 was checked with `nxdns check --config` rather than started. +Every command below was executed on x86_64 Linux with Zig 0.16.0, Node.js 24.14.1, dig 9.20.26 and curl 8.21.0. Steps 2, 4 to 11, 13 and 14 were re-run end to end for this revision, and the transcripts are that run's output with the tutorial directory substituted. Two things were not re-run: the browser page in step 12 — its endpoints were exercised, the page itself was not opened — and the `npm` build in step 1, whose `admin/dist` was already on disk and is the one the binary under test embeds. The ZON block at the end of step 14 was checked with `nxdns check --config` rather than started. ## What you need @@ -18,18 +18,18 @@ Build commands run from the repository root. The server and the queries use `~/n ## 1. Build the web interface ```sh -cd web && npm ci && npm run build && cd .. +cd admin && npm ci && npm run build && cd .. ``` -This produces `web/dist`. Do not skip it. A plain `zig build` embeds `web/dist-placeholder`, a one-page status stub, and you would reach step 12 and find no admin interface there. +This produces `admin/dist`. Do not skip it. A plain `zig build` embeds `admin/dist-placeholder`, a one-page status stub, and you would reach step 12 and find no admin interface there. ## 2. Build nxdns ```sh -zig build -Dweb-dist=web/dist +zig build -Dadmin-dist=admin/dist ``` -The binary is `zig-out/bin/nxdns`, with `web/dist` embedded in it. SQLite and mbedTLS are vendored and built by this command, so the first build takes a while; later builds are cached. +The binary is `zig-out/bin/nxdns`, with `admin/dist` embedded in it. SQLite and mbedTLS are vendored and built by this command, so the first build takes a while; later builds are cached. ## 3. Write a configuration file diff --git a/licenses/dependency-identity.txt b/licenses/dependency-identity.txt index 5cb5787..7bee1ea 100644 --- a/licenses/dependency-identity.txt +++ b/licenses/dependency-identity.txt @@ -1,6 +1,6 @@ The identity of the dependency sets that decide what licenses/inventory.zon has to cover. This file is a snapshot, not a source: src/licenses_drift_test.zig -recomputes each section from build.zig.zon and web/package-lock.json and fails +recomputes each section from build.zig.zon and admin/package-lock.json and fails when it disagrees with what is recorded here. Prose before the first section header is ignored by the guard. @@ -10,11 +10,11 @@ shipped bytes first, then record it. The runtime closure is every lockfile package not marked `dev`. That is a superset of what the bundler emits — the guard cannot run rollup — so packages -tree-shaken out of web/dist appear here too; the inventory explains each of them +tree-shaken out of admin/dist appear here too; the inventory explains each of them instead of carrying its licence. The bundled-packages section is the one the Zig guard cannot compute: it is the -set of npm packages that actually contribute a module to web/dist, read off the +set of npm packages that actually contribute a module to admin/dist, read off the sourcemap `sources` lists of a `vite build --sourcemap`. Only the frontend gate can produce it, so gates.yml recomputes it there and fails on a difference. It is what closes the hole the runtime-closure section leaves open — a package @@ -28,9 +28,9 @@ digest both change the line, which forces somebody to re-read what the new base ships before the inventory can claim it. The generators section is the other direction: packages the lockfile marks `dev` -whose own output nevertheless ends up in web/dist, and so in the binary. Its +whose own output nevertheless ends up in admin/dist, and so in the binary. Its membership is a judgement, not a query — it was settled by reading a -`vite build --sourcemap` of web/, both the sourcemap `sources` lists and the +`vite build --sourcemap` of admin/, both the sourcemap `sources` lists and the regions of each chunk no sourcemap segment maps back to a source file. The versions here are read from the lockfile like any other section. @@ -80,7 +80,7 @@ vite 8.1.5 MIT [container base image] alpine:3.22@sha256:14358309a308569c32bdc37e2e0e9694be33a9d99e68afb0f5ff33cc1f695dce -[npm packages bundled into web/dist] +[npm packages bundled into admin/dist] @internationalized/string @stylexjs/stylex @tanstack/history diff --git a/licenses/inventory.zon b/licenses/inventory.zon index 10914f4..2a47e6a 100644 --- a/licenses/inventory.zon +++ b/licenses/inventory.zon @@ -25,7 +25,7 @@ // against licenses/dependency-identity.txt, so a version bump that touches only // the identity file fails. // -// The web entries are the packages whose code is actually in web/dist, read off +// The web entries are the packages whose code is actually in admin/dist, read off // a `vite build --sourcemap`: the sourcemap `sources` lists give the packages // whose modules were bundled, and the regions of each chunk that *no* sourcemap // segment maps back to a source file give the code the toolchain injected on @@ -45,7 +45,7 @@ // build of a component calling stylex.props shows no trace of any of the four, // so they are dead entries of the published manifest rather than code with a // job here. css-mediaquery is the one that matters: it is BSD, and it reaching -// web/dist would force a licence decision this project has not made. +// admin/dist would force a licence decision this project has not made. // // Seven more joined that list with React Aria, and the sourcemap build is again // what settled which: @internationalized/date, @internationalized/number, @@ -71,7 +71,7 @@ // a `sources` list — no guard would raise it, which is why it is written down // here. // -// Of the remaining devDependencies, none puts a byte in web/dist: @vitejs/ +// Of the remaining devDependencies, none puts a byte in admin/dist: @vitejs/ // plugin-react and typescript only transform our own sources (react-refresh is // dev-server only, and tslib is optional and unused), lightningcss only // minifies, and @testing-library/*, jsdom, vitest, oxlint and prettier @@ -146,7 +146,7 @@ .{ .component = "styleq", .version = "styleq 0.2.1", - .note = "The class-name merger StyleX calls at runtime. Never a bundled package in its own right: @stylexjs/stylex vendors its source into lib/es/stylex.mjs, so it reaches web/dist without appearing in any sourcemap `sources` list. Separate entry from StyleX: same MIT text, different copyright line.", + .note = "The class-name merger StyleX calls at runtime. Never a bundled package in its own right: @stylexjs/stylex vendors its source into lib/es/stylex.mjs, so it reaches admin/dist without appearing in any sourcemap `sources` list. Separate entry from StyleX: same MIT text, different copyright line.", .file = "styleq-mit.txt", }, .{ @@ -158,13 +158,13 @@ .{ .component = "Vite", .version = "vite 8.1.5", - .note = "A devDependency whose own runtime source ships: the preload helper and the modulepreload polyfill are injected verbatim into web/dist/assets/index-*.js, which is embedded in the binary.", + .note = "A devDependency whose own runtime source ships: the preload helper and the modulepreload polyfill are injected verbatim into admin/dist/assets/index-*.js, which is embedded in the binary.", .file = "vite-mit.txt", }, .{ .component = "Rolldown", .version = "rolldown 1.1.5", - .note = "A devDependency whose own runtime source ships: the bundler Vite 8 runs prepends its CommonJS interop helpers verbatim to web/dist/assets/styles-*.js, which is embedded in the binary.", + .note = "A devDependency whose own runtime source ships: the bundler Vite 8 runs prepends its CommonJS interop helpers verbatim to admin/dist/assets/styles-*.js, which is embedded in the binary.", .file = "rolldown-mit.txt", }, .{ diff --git a/licenses/licenses.zig b/licenses/licenses.zig index 8dd2a0e..97baa66 100644 --- a/licenses/licenses.zig +++ b/licenses/licenses.zig @@ -2,7 +2,7 @@ //! `@embedFile` paths resolve relative to this file, and a module cannot embed //! anything above its own root directory, so `build.zig` stages a directory //! holding a copy of `licenses/` alongside copies of `build.zig.zon` and -//! `web/package-lock.json`, and roots the import at the copy of this file. +//! `admin/package-lock.json`, and roots the import at the copy of this file. //! That is why the two decls below name files that do not sit beside this one //! in the repository. //! @@ -24,7 +24,7 @@ pub const dependency_identity_txt = @embedFile("dependency-identity.txt"); /// Repository root `build.zig.zon`, the identity of the Zig dependency set. pub const build_zig_zon = @embedFile("build.zig.zon"); -/// `web/package-lock.json`, the identity of the npm closure. +/// `admin/package-lock.json`, the identity of the npm closure. pub const package_lock_json = @embedFile("package-lock.json"); /// `deploy/docker/Dockerfile`, the identity of the base image. The image is a diff --git a/licenses/rolldown-mit.txt b/licenses/rolldown-mit.txt index 87d9f4f..3958042 100644 --- a/licenses/rolldown-mit.txt +++ b/licenses/rolldown-mit.txt @@ -2,7 +2,7 @@ Rolldown is the bundler Vite 8 runs, and it prepends its own CommonJS interop helpers — the `__commonJS`, `__toESM` and `__copyProps` closures built out of Object.create, Object.defineProperty and Object.getOwnPropertyNames — to every chunk that pulls in a CommonJS module. In this build they are the first 554 -bytes of web/dist/assets/classes-*.js, which is embedded in every nxdns binary. +bytes of admin/dist/assets/classes-*.js, which is embedded in every nxdns binary. Like Vite's preload helper, they are a region of the chunk that no sourcemap segment maps back to a source file. diff --git a/licenses/vite-mit.txt b/licenses/vite-mit.txt index 684b350..550f69c 100644 --- a/licenses/vite-mit.txt +++ b/licenses/vite-mit.txt @@ -1,6 +1,6 @@ Vite emits its own runtime source verbatim into the bundle that `vite build` -writes to web/dist, and web/dist is embedded in every nxdns binary. Two pieces -of Vite's code ship in web/dist/assets/index-*.js: +writes to admin/dist, and admin/dist is embedded in every nxdns binary. Two pieces +of Vite's code ship in admin/dist/assets/index-*.js: - the `__vite__mapDeps` table and the `__vitePreload` helper that dynamic imports are rewritten to call (vite/preload-helper), and diff --git a/src/app.zig b/src/app.zig index 9633507..d46f40b 100644 --- a/src/app.zig +++ b/src/app.zig @@ -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, diff --git a/src/cli.zig b/src/cli.zig index 1e39a67..86f8889 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -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 `: serve the web +/// `admin_dev` is milestone-8 ruling 24's `--admin-dev `: 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" { diff --git a/src/licenses_drift_test.zig b/src/licenses_drift_test.zig index 4c8849d..0e8db4b 100644 --- a/src/licenses_drift_test.zig +++ b/src/licenses_drift_test.zig @@ -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 diff --git a/src/web/server.zig b/src/web/server.zig index 884d758..7f6e579 100644 --- a/src/web/server.zig +++ b/src/web/server.zig @@ -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. diff --git a/src/web/static.zig b/src/web/static.zig index 5b3f50b..64ef7da 100644 --- a/src/web/static.zig +++ b/src/web/static.zig @@ -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 //! `.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 `, wired by the CLI) serves from disk +//! Dev mode (`nxdns run --admin-dev `, 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 }{ diff --git a/tools/gen_web_assets.zig b/tools/gen_admin_assets.zig similarity index 97% rename from tools/gen_web_assets.zig rename to tools/gen_admin_assets.zig index 914d22f..ced73a7 100644 --- a/tools/gen_web_assets.zig +++ b/tools/gen_admin_assets.zig @@ -1,6 +1,6 @@ //! Build-time web asset indexer (milestone-8 ruling 24). //! -//! `gen_web_assets ` reads a built web dist directory and +//! `gen_admin_assets ` reads a built web dist directory and //! writes into ``: //! //! - `.gz` next to each asset worth compressing, unless the dist already @@ -40,7 +40,7 @@ pub fn main(init: std.process.Init) !void { const arena = init.arena.allocator(); const io = init.io; const args = try init.minimal.args.toSlice(arena); - if (args.len != 3) std.process.fatal("usage: gen_web_assets ", .{}); + if (args.len != 3) std.process.fatal("usage: gen_admin_assets ", .{}); var dist = std.Io.Dir.cwd().openDir(io, args[1], .{ .iterate = true }) catch |err| { std.process.fatal("cannot open dist directory '{s}': {t}", .{ args[1], err }); @@ -207,7 +207,7 @@ fn renderIndex(arena: Allocator, assets: []const Asset) ![]const u8 { var sink: std.Io.Writer.Allocating = try .initCapacity(arena, 4096); const w = &sink.writer; try w.writeAll( - \\//! Generated by tools/gen_web_assets.zig. Do not edit. + \\//! Generated by tools/gen_admin_assets.zig. Do not edit. \\ \\pub const File = struct { \\ /// Request path, `/`-prefixed.