From a1d162febf0af7b904ac29f981f0bd6dcf65523f Mon Sep 17 00:00:00 2001 From: AskaEth Date: Sat, 24 Oct 2026 19:42:58 +0800 Subject: [PATCH 1/2] fix(webui): commit dist placeholder so go:embed compiles in clean checkouts internal/webui/webui.go uses //go:embed all:dist, which fails to compile when the directory holds zero files. dist/ is gitignored (it is npm build output), so every fresh clone or worktree could not run go build ./... until the frontend had been built once. Track an empty internal/webui/dist/.gitkeep so the embed always has one matching file. The all: prefix is what makes a dotfile count. Real build output (index.html, assets/**) stays ignored and is never committed. The placeholder is forced into the index because .gitignore's dist/ rule excludes the parent directory with no way to re-include a child. --- internal/webui/dist/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 internal/webui/dist/.gitkeep diff --git a/internal/webui/dist/.gitkeep b/internal/webui/dist/.gitkeep new file mode 100644 index 0000000..e69de29 From ec6c98d26a4f1c87576864f9be7822457552205e Mon Sep 17 00:00:00 2001 From: AskaEth Date: Sat, 24 Oct 2026 19:44:34 +0800 Subject: [PATCH 2/2] fix(web): keep the webui dist placeholder across frontend builds copy-dist.mjs rm -rf'd internal/webui/dist before copying the SPA in, so every npm run build deleted the committed .gitkeep and a fresh checkout went back to failing go:embed. Restore the placeholder after the copy, and also on a failed copy (an empty dist/ still compiles; a missing dir does not), so the wipe only ever clears build output. --- frontend/web/copy-dist.mjs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/frontend/web/copy-dist.mjs b/frontend/web/copy-dist.mjs index 033fe1e..a6ff2ef 100644 --- a/frontend/web/copy-dist.mjs +++ b/frontend/web/copy-dist.mjs @@ -2,14 +2,27 @@ // the Go package can //go:embed it into the single Cyrene binary. It runs as the // last step of `npm run build`. The source dist is the canonical build output, // so it also serves as the external deploy-override directory. -import { cp, rm } from "node:fs/promises"; -import { dirname, resolve } from "node:path"; +// +// The destination is wiped first so assets from an older build cannot linger in +// the binary. internal/webui/dist/.gitkeep is committed because //go:embed +// all:dist fails to compile when the directory holds no files at all — that is +// what makes `go build ./...` work in a clean checkout. The wipe may clear build +// output but never the placeholder, so it is restored on every run, including a +// failed copy: a bare placeholder still compiles, a missing one breaks the build. +import { cp, mkdir, rm, writeFile } from "node:fs/promises"; +import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; const here = dirname(fileURLToPath(import.meta.url)); const src = resolve(here, "dist"); const dest = resolve(here, "..", "..", "internal", "webui", "dist"); +const placeholder = ".gitkeep"; await rm(dest, { recursive: true, force: true }); -await cp(src, dest, { recursive: true }); +try { + await cp(src, dest, { recursive: true }); +} finally { + await mkdir(dest, { recursive: true }); + await writeFile(join(dest, placeholder), ""); +} console.log(`webui: copied ${src} -> ${dest}`);