build: unify the gate so root make check covers the backend (closes #16)
All checks were successful
check / check (push) Successful in 42s

Root `make check` only ever ran the frontend, so the "main is always
green" policy was satisfied vacuously: the Go backend could be entirely
broken and the root gate stayed green.

- The backend moves onto scripts-to-rule-them-all. Its test, lint, fmt,
  fmt-check, build, run and clean implementations now live in
  `backend/script/`, and `backend/Makefile` is thin shims. The backend
  is its own project (own module, README, LICENSE, linter config,
  Dockerfile stage), and `Dockerfile.backend` only copies `backend/`
  into its builder, so its scripts have to live under `backend/`.
- The root `script/test`, `script/lint`, `script/fmt` and
  `script/fmt-check` now run the frontend step and then the matching
  `backend/script/*` step, so `script/check` — and therefore the
  pre-commit hook — gates both halves. The frontend-only steps moved
  into `script/frontend-*` so nothing is duplicated.
- `script/frontend-check` is the frontend half of the gate, exposed as
  the `check-frontend` target, for the frontend Dockerfile: its build
  stage is a node image with no Go toolchain. The backend half is gated
  by `Dockerfile.backend`, and `script/cibuild` builds both images, so
  the two Dockerfiles together still gate the whole repo. The
  `check-backend` target is the mirror of it.
- `script/cibuild` builds both images through one `build_image` helper,
  and the Gitea workflow's only build step is `script/cibuild`; the raw
  `docker build -f Dockerfile.backend .` is gone from the workflow.
  `script/docker` likewise builds and tags both images.
- `backend/Makefile`'s `hooks` target is removed. It wrote the same
  `.git/hooks/pre-commit` as `script/install-precommit`, so the two
  clobbered each other and the developer silently ended up gating on
  only one half of the repo. `script/install-precommit` is now the only
  installer, and the hook it writes runs the repo-wide `script/check`.
- `backend/Makefile`'s `docker` target is removed too: the backend image
  builds from the repo root with a root-level Dockerfile, so it belongs
  to the root `script/docker` and `script/cibuild` rather than to a
  backend script that would have to reach outside `backend/`.
- `backend/script/lint` verifies that `.golangci.yml` still matches its
  pinned sha256 before running the linter. Offline hash comparison, no
  network.

READMEs at the root and in `backend/` document every script, and
`TODO.md` records the change.
This commit is contained in:
clawbot
2026-08-09 05:52:40 +00:00
parent fbfe1df349
commit a6a744b45f
27 changed files with 387 additions and 76 deletions

View File

@@ -1,6 +1,7 @@
#!/bin/sh
# script/check: run all checks (test, lint, fmt-check). Our own
# extension to scripts-to-rule-them-all. Must not modify any files.
# script/check: run all checks (test, lint, fmt-check) across the whole
# repo, frontend and backend. Our own extension to
# scripts-to-rule-them-all. Must not modify any files.
set -eu
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"

View File

@@ -1,13 +1,24 @@
#!/bin/sh
# script/cibuild: run the CI build. The Dockerfile runs make check, so
# a successful build implies all checks pass.
# script/cibuild: run the CI build. It builds every image in the repo:
# the frontend image from Dockerfile and the backend image from
# Dockerfile.backend. Each Dockerfile runs its half of make check as a
# build step, so a successful cibuild implies the whole repo is green.
# This is the only build step the Gitea workflow runs.
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
# build_image <dockerfile>: build one image from the repo root context.
# Every docker build CI performs goes through here, so build-wide flags
# only ever have to be added in one place.
build_image() {
timeout 300 docker build -f "$1" .
}
main() {
cd "$ROOT"
docker build .
build_image Dockerfile
build_image Dockerfile.backend
}
main "$@"

View File

@@ -1,6 +1,7 @@
#!/bin/sh
# script/docker: build the Docker image tagged with the project name.
# The tag comes from script/projectname.
# script/docker: build the repo's Docker images, tagged from
# script/projectname: the frontend image as <name> and the backend image
# as <name>-server. Both build from the repo root as their context.
set -eu
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
@@ -8,7 +9,9 @@ ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)"
main() {
cd "$ROOT"
timeout 300 docker build -t "$("$SCRIPT_DIR/projectname")" .
name="$("$SCRIPT_DIR/projectname")"
timeout 300 docker build -t "$name" -f Dockerfile .
timeout 300 docker build -t "$name-server" -f Dockerfile.backend .
}
main "$@"

View File

@@ -1,12 +1,14 @@
#!/bin/sh
# script/fmt: format all files (writes).
# script/fmt: format the whole repo (writes): prettier over everything
# it understands, then gofmt over the Go backend.
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() {
cd "$ROOT"
yarn prettier --write .
"$ROOT/script/frontend-fmt"
"$ROOT/backend/script/fmt"
}
main "$@"

View File

@@ -1,13 +1,14 @@
#!/bin/sh
# script/fmt-check: check formatting (read-only). Same scope as
# script/fmt, but fails instead of writing.
# script/fmt-check: check formatting across the whole repo (read-only).
# Same scope as script/fmt, but fails instead of writing.
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() {
cd "$ROOT"
yarn prettier --check .
"$ROOT/script/frontend-fmt-check"
"$ROOT/backend/script/fmt-check"
}
main "$@"

17
script/frontend-check Executable file
View File

@@ -0,0 +1,17 @@
#!/bin/sh
# script/frontend-check: run the frontend half of the checks only (test,
# lint, fmt-check). This exists for the frontend Dockerfile, whose build
# stage is a node image with no Go toolchain; the backend half is gated
# by Dockerfile.backend. Everywhere else, use script/check, which covers
# the whole repo. Must not modify any files.
set -eu
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
main() {
"$SCRIPT_DIR/frontend-test"
"$SCRIPT_DIR/frontend-lint"
"$SCRIPT_DIR/frontend-fmt-check"
}
main "$@"

14
script/frontend-fmt Executable file
View File

@@ -0,0 +1,14 @@
#!/bin/sh
# script/frontend-fmt: format the frontend and every other file prettier
# understands, repo-wide (writes). backend/ is in .prettierignore; Go
# sources are formatted by backend/script/fmt.
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() {
cd "$ROOT"
yarn prettier --write .
}
main "$@"

13
script/frontend-fmt-check Executable file
View File

@@ -0,0 +1,13 @@
#!/bin/sh
# script/frontend-fmt-check: check prettier formatting (read-only). Same
# scope as script/frontend-fmt, but fails instead of writing.
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() {
cd "$ROOT"
yarn prettier --check .
}
main "$@"

12
script/frontend-lint Executable file
View File

@@ -0,0 +1,12 @@
#!/bin/sh
# script/frontend-lint: run the frontend linter (prettier in check mode).
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() {
cd "$ROOT"
yarn prettier --check .
}
main "$@"

14
script/frontend-test Executable file
View File

@@ -0,0 +1,14 @@
#!/bin/sh
# script/frontend-test: run the frontend test suite. The frontend has no
# unit tests; the production build serves as the test (fails on broken
# code).
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() {
cd "$ROOT"
timeout 30 yarn build
}
main "$@"

View File

@@ -1,12 +1,14 @@
#!/bin/sh
# script/lint: run the linter (prettier in check mode).
# script/lint: lint the whole repo: prettier over everything it
# understands, then golangci-lint over the Go backend.
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() {
cd "$ROOT"
yarn prettier --check .
"$ROOT/script/frontend-lint"
"$ROOT/backend/script/lint"
}
main "$@"

View File

@@ -1,13 +1,14 @@
#!/bin/sh
# script/test: run the test suite. This repo has no unit tests; the
# production build serves as the test (fails on broken code).
# script/test: run the test suite for the whole repo: the frontend at
# the repo root, then the Go backend in backend/.
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() {
cd "$ROOT"
timeout 30 yarn build
"$ROOT/script/frontend-test"
"$ROOT/backend/script/test"
}
main "$@"