Re-vendor the shared files from sneak/prompts at dd4027b (closes #113)
check / check (push) Waiting to run

The shared files are the sneak/prompts copies at dd4027b, with this
repository's own entries after them. make lint and make test each build
one Dockerfile phase without the cache, both covering the frontend
through a node stage; the builder stage waits on both and takes its
version from git describe unless VERSION is given. golangci-lint moves
to v2.14.0 with the new .golangci.yml; one test spells X-Request-ID as
canonicalheader asks. prettier formats only JavaScript, CSS, HTML and
Markdown, so .golangci.yml stays as fetched. script/fmt and
script/fmt-check put ~/.local/bin on PATH, which the shared workflow no
longer does. script/bootstrap keeps a Go only if it is exactly
GO_VERSION, and re-checks the go on PATH after installing.

Model: opus-5-5
This commit is contained in:
2026-10-07 10:09:29 +00:00
parent 186f932eb8
commit 86ad75952a
28 changed files with 727 additions and 361 deletions
+31 -15
View File
@@ -6,17 +6,16 @@
# used directly if it is at least NODE_MIN_VERSION; otherwise it is
# installed at a pinned version via nvm (installing nvm itself first,
# from a hash-verified release archive, never curl | sh). Go, with its
# gofmt, is used directly if it is at least the version backend/go.mod
# asks for; otherwise the pinned Go release is installed from its
# hash-verified archive.
# gofmt, is used directly only if it is exactly GO_VERSION; otherwise
# that release is installed from its hash-verified archive.
#
# What this script installs outside the system package manager lives
# under $HOME and is linked into ~/.local/bin, where make and the git
# hook find it once that directory is on PATH. Nothing in ~/.local/bin
# that this script did not create is ever replaced.
#
# golangci-lint is not installed: make lint runs it in Docker, which
# this script does not install either.
# golangci-lint is not installed: make lint runs it in Docker, as make
# test runs the tests, and this script does not install Docker either.
#
# Unlike the org model: Go and gcc for backend/, a newer node for eslint.
set -eu
@@ -186,20 +185,30 @@ ensure_yarn() {
}
# go_ok: the go on PATH has its gofmt beside it (a Go release ships the
# two together) and is at least the version backend/go.mod asks for.
# GOTOOLCHAIN=local makes an older go fail here instead of fetching a
# newer toolchain for itself.
# two together) and go version reports exactly GO_VERSION, compared over
# the whole version, not a prefix of it. A go that fails or prints
# anything else does not pass. GOTOOLCHAIN=local makes go report itself
# rather than a toolchain it would fetch.
go_ok() {
if missing go; then return 1; fi
[ -x "$(dirname "$(command -v go)")/gofmt" ] || return 1
(cd "$ROOT/backend" && GOTOOLCHAIN=local go list -m >/dev/null 2>&1)
version="$(GOTOOLCHAIN=local go version 2>/dev/null)" || return 1
case "$version" in
"go version go$GO_VERSION "*) return 0 ;;
*) return 1 ;;
esac
}
# ensure_go: unless go_ok, install GO_VERSION and link its go and gofmt.
# They are linked on every run that needs them, so a deleted link is put
# back, and the archive is unpacked again if either binary is missing.
# Afterwards go is looked up through PATH again and must pass go_ok, so
# another go that hides the link stops bootstrap.
ensure_go() {
if go_ok; then return 0; fi
if go_ok; then
echo "bootstrap: using $(GOTOOLCHAIN=local go version)"
return 0
fi
go_dir="$TOOLCHAIN/go-$GO_VERSION"
if [ ! -x "$go_dir/bin/go" ] || [ ! -x "$go_dir/bin/gofmt" ]; then
# sha256 of each archive, from https://go.dev/dl/?mode=json
@@ -240,6 +249,13 @@ ensure_go() {
fi
link_bin "$go_dir/bin/go" go
link_bin "$go_dir/bin/gofmt" gofmt
hash -r
if ! go_ok; then
echo "bootstrap: after installing go $GO_VERSION in $go_dir," >&2
echo " the go on PATH, $(command -v go), is not it or has no gofmt" >&2
exit 1
fi
echo "bootstrap: installed $(GOTOOLCHAIN=local go version)"
}
main() {
@@ -256,9 +272,9 @@ main() {
if missing make; then pkg_install gnumake make make make; fi
if missing git; then pkg_install git git git git; fi
# The race detector in make test needs cgo, which Go turns on only
# when it finds its C compiler, gcc on Linux. apt and apk ship the C
# library headers apart from gcc.
# The race detector in backend/'s make test needs cgo, which Go turns
# on only when it finds its C compiler, gcc on Linux. apt and apk
# ship the C library headers apart from gcc.
if missing gcc; then
pkg_install gcc "gcc libc6-dev" gcc "gcc musl-dev"
fi
@@ -271,8 +287,8 @@ main() {
(cd "$ROOT/backend" && go mod download)
if missing docker; then
echo "bootstrap: docker not found; make lint, and so make check" >&2
echo " and the pre-commit hook, need it to run the linters" >&2
echo "bootstrap: docker not found; make test and make lint, and so" >&2
echo " make check and the pre-commit hook, need it" >&2
fi
if [ -n "$path_hint" ] && [ -d "$BIN_DIR" ]; then
echo "bootstrap: add $BIN_DIR to the front of your PATH, e.g." >&2
+3 -1
View File
@@ -1,6 +1,8 @@
#!/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.
# extension to scripts-to-rule-them-all. test and lint are Docker
# phases; fmt-check is native, because a formatter writes the working
# tree. Must not modify any files.
set -eu
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
+5 -2
View File
@@ -1,6 +1,6 @@
#!/bin/sh
# script/fmt: format the whole repo (writes): prettier over everything
# it understands, then gofmt over the Go backend.
# script/fmt: format the whole repo (writes): prettier over the
# JavaScript, CSS, HTML and Markdown, then gofmt over the Go backend.
# The org model formats only markdown; this repo also has JS and Go.
set -eu
@@ -8,6 +8,9 @@ ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() {
cd "$ROOT"
# script/bootstrap links the node, yarn and gofmt it installs into
# ~/.local/bin, which the shell that called it may not have on PATH.
PATH="$HOME/.local/bin:$PATH"
"$ROOT/script/frontend-fmt"
"$ROOT/backend/script/fmt"
}
+3
View File
@@ -8,6 +8,9 @@ ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() {
cd "$ROOT"
# script/bootstrap links the node, yarn and gofmt it installs into
# ~/.local/bin, which the shell that called it may not have on PATH.
PATH="$HOME/.local/bin:$PATH"
"$ROOT/script/frontend-fmt-check"
"$ROOT/backend/script/fmt-check"
}
-18
View File
@@ -1,18 +0,0 @@
#!/bin/sh
# script/frontend-check: run the frontend tests and format check only.
# This exists for the frontend stage of Dockerfile, a node image with
# neither Go nor Docker; the Dockerfile's frontend-lint stage runs the
# frontend linter, and its lint and builder stages gate the backend.
# Everywhere else, use script/check, which covers the whole repo. Must
# not modify any files.
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() {
cd "$ROOT"
"$ROOT/script/frontend-test"
"$ROOT/script/frontend-fmt-check"
}
main "$@"
+5 -3
View File
@@ -1,6 +1,8 @@
#!/bin/sh
# script/frontend-fmt: format the frontend and every other file prettier
# understands, repo-wide (writes), the markdown in backend/ included.
# script/frontend-fmt: format the JavaScript, CSS, HTML and Markdown,
# repo-wide (writes), the markdown in backend/ included. Those are the
# languages REPO_POLICIES.md gives prettier; the YAML is left alone, as
# backend/.golangci.yml must stay the org standard byte for byte.
# Prettier does not read Go; backend/script/fmt formats the Go sources.
set -eu
@@ -8,7 +10,7 @@ ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() {
cd "$ROOT"
yarn prettier --write .
yarn prettier --write '**/*.{js,css,html,md}'
}
main "$@"
+1 -1
View File
@@ -7,7 +7,7 @@ ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() {
cd "$ROOT"
yarn prettier --check .
yarn prettier --check '**/*.{js,css,html,md}'
}
main "$@"
-15
View File
@@ -1,15 +0,0 @@
#!/bin/sh
# script/frontend-lint: run eslint over the frontend. This runs inside
# the frontend-lint stage of Dockerfile, the digest-pinned node image
# with the packages from yarn.lock. From a checkout, run `make lint`,
# which builds that stage.
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() {
cd "$ROOT"
yarn eslint .
}
main "$@"
+6 -5
View File
@@ -1,7 +1,8 @@
#!/bin/sh
# script/frontend-test: run the frontend test suite: the unit tests in
# test/unit/, through the test script in package.json, then the
# production build, which fails on broken code. The tests print a dot
# script/frontend-test: run the frontend test suite on the host: the
# unit tests in test/unit/, through the test script in package.json,
# then the production build, which fails on broken code. make test runs
# the same in the frontend stage of Dockerfile. The tests print a dot
# each; if any fails, they run again with every test listed, and the
# script fails even if that run passes. NODE_OPTIONS chooses the
# reporter because yarn adds its arguments after the test files, where
@@ -12,9 +13,9 @@ ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() {
cd "$ROOT"
NODE_OPTIONS=--test-reporter=dot timeout 30 yarn --silent run test || {
NODE_OPTIONS=--test-reporter=dot timeout 90 yarn --silent run test || {
echo "--- Rerunning with every test listed for details ---"
NODE_OPTIONS=--test-reporter=spec timeout 30 yarn --silent run test
NODE_OPTIONS=--test-reporter=spec timeout 90 yarn --silent run test
exit 1
}
timeout 30 yarn build
+13 -13
View File
@@ -1,23 +1,23 @@
#!/bin/sh
# script/lint: lint the whole repo: eslint over the frontend, then the Go
# linter over backend/.
# script/lint: run the linter. Linting is a phase of the Dockerfile and
# this builds that phase alone; the linter is never installed or run on
# a developer host, where a shared result cache and a host-global lock
# make its answer untrustworthy.
#
# No linter runs on the host: this builds the frontend-lint and lint
# stages of Dockerfile, the digest-pinned node and golangci-lint images.
# The first runs eslint; the second runs the backend's fmt-check and
# lint targets. --no-cache makes each linter really run every time
# rather than reuse an earlier result, and each stage is built for its
# checks alone, so no image is kept.
# The phase is not the last stage in the file, so it is built only when
# --target names it. --no-cache because a cached lint layer is a lint
# that did not run. The tag makes each build replace the previous image
# instead of leaving a dangling one behind.
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)"
main() {
cd "$ROOT"
timeout 300 docker build --no-cache --target frontend-lint \
--output type=cacheonly .
timeout 300 docker build --no-cache --target lint \
--output type=cacheonly .
docker build --no-cache \
--target lint \
-t "$("$SCRIPT_DIR/projectname")-lint" .
}
main "$@"
+10 -8
View File
@@ -1,17 +1,19 @@
#!/bin/sh
# script/test: run the test suite for the whole repo: the frontend at
# the repo root, then the Go backend in backend/. Each half has its own
# 30-second limit, and there is none around both: from a cold Go build
# cache, compiling the backend's tests with the race detector can take
# 30 seconds on its own, and Go's -timeout leaves the compile out.
# script/test: run the test suite. Testing is a phase of the Dockerfile
# and this builds that phase alone, on the same terms as script/lint:
# --target because a phase that is not the last stage is built only when
# named, --no-cache because a cached test layer is a test that did not
# run, and a tag so each build replaces the previous image.
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)"
main() {
cd "$ROOT"
script/frontend-test
backend/script/test
docker build --no-cache \
--target test \
-t "$("$SCRIPT_DIR/projectname")-test" .
}
main "$@"