build: unify the gate so root make check covers the backend (closes #16)
check / check (push) Successful in 11s

Root make check, and with it the pre-commit hook, now gates the Go
backend too. The backend's Makefile targets are shims over
backend/script/*; script/cibuild builds both images and is the
workflow's only build step. Root make test runs both halves within one
30-second timeout.

Root make lint runs golangci-lint only in Docker, by building the lint
stage of Dockerfile.backend without the cache; the .golangci.yml drift
check moved into backend/script/lint. script/bootstrap installs no
linter: it reuses a Go at least as new as backend/go.mod asks for,
otherwise installs the pinned, hash-verified release, linked into
~/.local/bin without replacing anything it did not create. With VERSION
unset or empty, the backend version falls back to git describe inside a
git checkout, then to dev.

Model: opus-5-5
This commit was merged in pull request #38.
This commit is contained in:
2026-09-29 01:22:08 +02:00
parent 45d2ad21bc
commit de4e86c433
28 changed files with 460 additions and 106 deletions
+1 -1
View File
@@ -6,5 +6,5 @@ jobs:
steps: steps:
# actions/checkout v4.2.2, 2026-02-22 # actions/checkout v4.2.2, 2026-02-22
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
# script/cibuild builds both images.
- run: script/cibuild - run: script/cibuild
- run: docker build -f Dockerfile.backend .
+6 -4
View File
@@ -5,10 +5,12 @@ COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile RUN yarn install --frozen-lockfile
RUN apk add --no-cache git make RUN apk add --no-cache git make
COPY . . COPY . .
# make check runs script/check (test + lint + fmt-check); its test step # make frontend-check is the frontend half of make check (test + lint +
# is the production yarn build, so this both produces dist/ and gates the # fmt-check); its test step is the production yarn build, so this both
# image on lint/fmt-check/test regressions, not merely a broken build. # produces dist/ and gates the image on lint/fmt-check/test regressions.
RUN make check # This node stage has neither Go nor Docker for the other half, which
# Dockerfile.backend gates; script/cibuild builds both images.
RUN make frontend-check
# nginx:stable-alpine as of 2026-02-22 # nginx:stable-alpine as of 2026-02-22
FROM nginx@sha256:15e96e59aa3b0aada3a121296e3bce117721f42d88f5f64217ef4b18f458c6ab FROM nginx@sha256:15e96e59aa3b0aada3a121296e3bce117721f42d88f5f64217ef4b18f458c6ab
+4 -5
View File
@@ -30,12 +30,11 @@ COPY backend/ .
RUN make test RUN make test
# The build is driven through the Makefile so there is exactly one # make build is a shim around backend/script/build, the one definition
# definition of the build command; it expands to # of the build command:
# CGO_ENABLED=0 go build -trimpath -ldflags "-s -w -X main.Version=... -X main.Buildarch=..." # CGO_ENABLED=0 go build -trimpath -ldflags "-s -w -X main.Version=... -X main.Buildarch=..."
# VERSION is handed over in the environment rather than as a make # That script reads VERSION from the environment, so it is handed over
# variable so it still reaches the build if the target is ever turned # there rather than as a make variable.
# into a shim around a script.
ARG VERSION=dev ARG VERSION=dev
RUN VERSION="${VERSION}" make build RUN VERSION="${VERSION}" make build
+8 -2
View File
@@ -1,9 +1,10 @@
.PHONY: bootstrap setup dev test lint fmt fmt-check check \ .PHONY: bootstrap setup dev test lint fmt fmt-check check frontend-check \
frontend-viewport-test docker hooks frontend-viewport-test docker hooks
# Standard targets are thin shims; the implementations live in script/ # Standard targets are thin shims; the implementations live in script/
# per the scripts-to-rule-them-all pattern (see the Entrypoints section # per the scripts-to-rule-them-all pattern (see the Entrypoints section
# of README.md). # of README.md). test, lint, fmt, fmt-check and check cover the whole
# repo: the frontend here and the Go backend in backend/.
bootstrap: bootstrap:
@script/bootstrap @script/bootstrap
@@ -29,6 +30,11 @@ fmt-check:
check: check:
@script/check @script/check
# The frontend half of check, for Dockerfile's node build stage, which
# has neither Go nor Docker. Use check everywhere else.
frontend-check:
@script/frontend-check
# Responsive-layout verification in a containerised browser. Kept out of # Responsive-layout verification in a containerised browser. Kept out of
# check: it needs Docker and takes minutes, where make test has to stay # check: it needs Docker and takes minutes, where make test has to stay
# under 20 seconds. # under 20 seconds.
+26 -10
View File
@@ -31,25 +31,41 @@ docker run -p 8080:8080 netwatch
This repository adheres to the This repository adheres to the
[Scripts to Rule Them All](https://github.com/github/scripts-to-rule-them-all) [Scripts to Rule Them All](https://github.com/github/scripts-to-rule-them-all)
standard: normalized scripts in `script/` are the entrypoints for the standard: normalized scripts in `script/` are the entrypoints for the
development workflow, and the Makefile targets are thin shims that call them. We development workflow, and the Makefile targets are thin shims that call them.
provide: The Go backend in `backend/` has its own `script/` directory and shim Makefile
(see [backend/README.md](backend/README.md)). The root scripts cover both
halves, so the root `make check` fails if either one is broken. We provide:
- `script/bootstrap` — install all dependencies (pinned node via nvm if needed, - `script/bootstrap` — install all dependencies (pinned node via nvm if needed,
yarn via corepack, `yarn install --frozen-lockfile`) yarn via corepack, `yarn install --frozen-lockfile`, the pinned Go unless one
at least as new as `backend/go.mod` asks for is installed, and the Go
modules), linking what it installs itself into `~/.local/bin`, which has to be
on `PATH`. It installs no Go linter and not Docker: `make lint` runs the
linter in Docker
- `script/setup` — make a fresh clone ready for development: bootstrap plus the - `script/setup` — make a fresh clone ready for development: bootstrap plus the
git pre-commit hook git pre-commit hook
- `script/projectname` — print the project name (used for the Docker image tag) - `script/projectname` — print the project name (used for the Docker image tags)
- `script/test` — run the production build as the test (no unit tests yet) - `script/test` — run `script/frontend-test`, then the backend's Go tests, both
- `script/lint` — run prettier in check mode within one 30-second timeout
- `script/fmt` — format all files (writes) - `script/lint` — run `script/frontend-lint`, then golangci-lint in Docker, by
- `script/fmt-check` — check formatting (read-only) building the lint stage of `Dockerfile.backend` without the cache
- `script/fmt` — format all files (writes): prettier, then gofmt over `backend/`
- `script/fmt-check` — check formatting (read-only): prettier, then gofmt
- `script/check` — run test, lint, and fmt-check - `script/check` — run test, lint, and fmt-check
- `script/frontend-test` — run the production build as the frontend's test (no
unit tests yet)
- `script/frontend-lint` — run prettier in check mode
- `script/frontend-fmt` — format everything prettier understands (writes)
- `script/frontend-fmt-check` — check prettier formatting (read-only)
- `script/frontend-check` — the frontend half of `script/check`, for
`Dockerfile`, whose node build stage has neither Go nor Docker
- `script/frontend-viewport-test` — responsive-layout verification of the built - `script/frontend-viewport-test` — responsive-layout verification of the built
frontend in a containerised headless Chrome (see frontend in a containerised headless Chrome (see
[test/viewport/README.md](test/viewport/README.md)). Not part of [test/viewport/README.md](test/viewport/README.md)). Not part of
`script/check`: it needs Docker and takes minutes. `script/check`: it needs Docker and takes minutes.
- `script/docker` — build the Docker image tagged via `script/projectname` - `script/docker` — build both images, tagged via `script/projectname`:
- `script/cibuild` — CI entrypoint: plain `docker build .` `netwatch` from `Dockerfile` and `netwatch-server` from `Dockerfile.backend`
- `script/cibuild` — CI entrypoint: builds both images
- `script/precommit` — run by the git pre-commit hook; runs `script/check` - `script/precommit` — run by the git pre-commit hook; runs `script/check`
- `script/install-precommit` — install the git pre-commit hook - `script/install-precommit` — install the git pre-commit hook
+12
View File
@@ -23,6 +23,18 @@ latest run passes.
# Completed Steps # Completed Steps
- 2026-09-28: unified the gate (issue #16): the root `make check` covers the Go
backend as well as the frontend, and the pre-commit hook with it; the backend
moved onto scripts-to-rule-them-all (`backend/script/*`, `backend/Makefile` as
shims, its duplicate hook installer removed); `script/cibuild` builds both
images and is the workflow's only build step. The root `make lint` runs
golangci-lint only in Docker, by building the lint stage of
`Dockerfile.backend` without the cache. `script/bootstrap` installs the pinned
Go unless the installed one is at least what `backend/go.mod` asks for, links
what it installs into `~/.local/bin` without replacing anything it did not
create, and installs no linter. Root `make test` runs both halves within one
30-second timeout. When `VERSION` is unset or empty, the backend binary's
version falls back to `git describe` inside a git checkout, then to `dev`
- 2026-09-28: frontend reporting client (issue #53): a `Reporter` class posts - 2026-09-28: frontend reporting client (issue #53): a `Reporter` class posts
collected samples to `/api/v1/reports` every `reportInterval` (default 60s) as collected samples to `/api/v1/reports` every `reportInterval` (default 60s) as
a per-host delta, with the report-building step a pure exported function of a per-host delta, with the report-building step a pure exported function of
+14 -53
View File
@@ -1,69 +1,30 @@
# VERSION is overridable (the Dockerfile passes its ARG VERSION in) and # Thin shims; the implementations live in backend/script/ (see the
# degrades to "dev" when git is unavailable or there is no .git — the # Entrypoints section of README.md). There is no check, hooks or docker
# build must not depend on the repository history being in the build # target here: the root Makefile's check covers this directory, its
# context. # hooks target installs the repo's only pre-commit hook, and its docker
VERSION ?= $(shell { git describe --always --dirty; } 2>/dev/null || echo dev) # target builds this image, whose build context is the repo root.
BUILDARCH := $(shell uname -m)
BINARY := netwatch-server
GOLDFLAGS += -s -w .PHONY: all build test lint fmt fmt-check run clean
GOLDFLAGS += -X main.Version=$(VERSION)
GOLDFLAGS += -X main.Buildarch=$(BUILDARCH)
# macOS ships shasum rather than sha256sum.
SHA256SUM := $(shell command -v sha256sum >/dev/null 2>&1 && echo sha256sum || echo shasum -a 256)
# .golangci.yml is standardized org-wide and must never be edited here
# (REPO_POLICIES.md). Its last silent drift replaced the v2 schema with
# v1 keys, which left every threshold in the file inert while the build
# stayed green. The lint target therefore asserts the file still matches
# the canonical copy byte for byte. The check is a local hash comparison:
# no network, no remote schema, nothing unpinned in the build path.
GOLANGCI_CONFIG_SHA256 := 021cc83f4e6fc7c31b95b34b846723dfcf20b66b7baeea1dc40406e643346bcb
.PHONY: all build test lint fmt fmt-check check docker hooks run clean
all: build all: build
build: build:
CGO_ENABLED=0 go build -trimpath -ldflags "$(GOLDFLAGS)" \ @script/build
-o ./$(BINARY) ./cmd/netwatch-server/
test: test:
timeout 30 go test ./... @script/test
lint: lint:
@actual=$$($(SHA256SUM) .golangci.yml | cut -d' ' -f1); \ @script/lint
if [ "$$actual" != "$(GOLANGCI_CONFIG_SHA256)" ]; then \
echo ".golangci.yml has drifted from the org standard."; \
echo " expected $(GOLANGCI_CONFIG_SHA256)"; \
echo " actual $$actual"; \
echo "Restore it verbatim from sneak/prompts; do not edit it."; \
exit 1; \
fi
golangci-lint run ./...
fmt: fmt:
go fmt ./... @script/fmt
fmt-check: fmt-check:
@test -z "$$(gofmt -l .)" || \ @script/fmt-check
(echo "Files not formatted:"; gofmt -l .; exit 1)
check: test lint fmt-check run:
@script/run
docker:
timeout 300 docker build -t netwatch-server -f ../Dockerfile.backend ..
hooks:
@printf '#!/bin/sh\ncd backend && make check\n' > \
$$(git rev-parse --show-toplevel)/.git/hooks/pre-commit
@chmod +x \
$$(git rev-parse --show-toplevel)/.git/hooks/pre-commit
@echo "Pre-commit hook installed"
run: build
./$(BINARY)
clean: clean:
rm -f ./$(BINARY) @script/clean
+35 -3
View File
@@ -4,18 +4,50 @@ SPA and persists them as zstd-compressed JSONL files on disk.
## Getting Started ## Getting Started
From this directory:
```bash ```bash
# Build and run locally # Build and run locally
make run make run
```
# Run tests, lint, and format check From the repo root, which is also the build context of `Dockerfile.backend`:
```bash
# Run tests, lint, and format check over the frontend and this backend
make check make check
# Docker # Build both images, including netwatch-server
docker build -t netwatch-server . make docker
docker run -p 8080:8080 netwatch-server docker run -p 8080:8080 netwatch-server
``` ```
## Entrypoints
This directory follows the same
[Scripts to Rule Them All](https://github.com/github/scripts-to-rule-them-all)
pattern as the repo root: the targets in `backend/Makefile` are thin shims over
`backend/script/`. `Dockerfile.backend` runs them, and the root scripts call
`test`, `fmt` and `fmt-check`:
- `script/build` — compile the static `netwatch-server` binary with its version
and architecture stamped in. The version is `VERSION` from the environment;
when that is unset or empty, it falls back to `git describe` inside a git
checkout, then to `dev`
- `script/test` — run the Go tests under a 30-second timeout
- `script/lint` — check `.golangci.yml` against its pinned sha256, then run
golangci-lint. It runs inside the golangci-lint image of the lint stage of
`Dockerfile.backend`; from a checkout, run `make lint` at the repo root, which
builds that stage
- `script/fmt` — format the Go sources (writes)
- `script/fmt-check` — check Go formatting (read-only)
- `script/run` — build and run the server locally
- `script/clean` — remove build artifacts
There is no `check`, `hooks` or `docker` target here: the root `make check`
covers this directory, the root `make hooks` installs the repo's only pre-commit
hook, and the root `make docker` builds this image.
## Rationale ## Rationale
The NetWatch frontend collects latency measurements from the browser but has no The NetWatch frontend collects latency measurements from the browser but has no
+21
View File
@@ -0,0 +1,21 @@
#!/bin/sh
# script/build: compile the static netwatch-server binary into the
# backend project root, with its version and architecture stamped in.
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() {
cd "$ROOT"
# VERSION comes from the environment (Dockerfile.backend passes its
# ARG VERSION in). Unset or empty, it is git describe, or "dev" where
# there is no git or no repository history.
version="${VERSION:-$(git describe --always --dirty 2>/dev/null || echo dev)}"
CGO_ENABLED=0 go build -trimpath \
-ldflags "-s -w -X main.Version=$version -X main.Buildarch=$(uname -m)" \
-o netwatch-server ./cmd/netwatch-server/
}
main "$@"
+12
View File
@@ -0,0 +1,12 @@
#!/bin/sh
# script/clean: remove build artifacts.
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() {
cd "$ROOT"
rm -f netwatch-server
}
main "$@"
+12
View File
@@ -0,0 +1,12 @@
#!/bin/sh
# script/fmt: format the Go sources (writes).
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() {
cd "$ROOT"
go fmt ./...
}
main "$@"
+18
View File
@@ -0,0 +1,18 @@
#!/bin/sh
# script/fmt-check: check Go formatting (read-only). Same scope as
# script/fmt, but fails instead of writing.
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() {
cd "$ROOT"
unformatted="$(gofmt -l .)"
if [ -n "$unformatted" ]; then
echo "Files not formatted:" >&2
echo "$unformatted" >&2
exit 1
fi
}
main "$@"
+32
View File
@@ -0,0 +1,32 @@
#!/bin/sh
# script/lint: run golangci-lint over the backend. This runs inside the
# lint stage of Dockerfile.backend, whose digest-pinned golangci-lint
# image provides the linter; nothing installs golangci-lint on the host.
# From a checkout, run `make lint` at the repo root, which builds that
# stage.
#
# .golangci.yml is standardized org-wide and must never be edited here
# (REPO_POLICIES.md). Its last silent drift replaced the v2 schema with
# v1 keys, which left every threshold in the file inert while the build
# stayed green. So the file is first checked against the canonical
# copy's sha256: a local comparison, no network, nothing unpinned.
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
GOLANGCI_CONFIG_SHA256="021cc83f4e6fc7c31b95b34b846723dfcf20b66b7baeea1dc40406e643346bcb"
main() {
cd "$ROOT"
actual="$(sha256sum .golangci.yml | cut -d' ' -f1)"
if [ "$actual" != "$GOLANGCI_CONFIG_SHA256" ]; then
echo ".golangci.yml has drifted from the org standard." >&2
echo " expected $GOLANGCI_CONFIG_SHA256" >&2
echo " actual $actual" >&2
echo "Restore it verbatim from sneak/prompts; do not edit it." >&2
exit 1
fi
golangci-lint run ./...
}
main "$@"
+13
View File
@@ -0,0 +1,13 @@
#!/bin/sh
# script/run: build and run netwatch-server locally.
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() {
cd "$ROOT"
"$ROOT/script/build"
exec ./netwatch-server "$@"
}
main "$@"
+12
View File
@@ -0,0 +1,12 @@
#!/bin/sh
# script/test: run the backend test suite.
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() {
cd "$ROOT"
timeout 30 go test ./...
}
main "$@"
+126 -11
View File
@@ -5,7 +5,18 @@
# or apk (detected in that order); assumes nothing is present. Node is # or apk (detected in that order); assumes nothing is present. Node is
# used directly if installed; otherwise it is installed at a pinned # used directly if installed; otherwise it is installed at a pinned
# version via nvm (installing nvm itself first, from a hash-verified # version via nvm (installing nvm itself first, from a hash-verified
# release archive, never curl | sh). # 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.
#
# 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.
set -eu set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
@@ -16,6 +27,12 @@ NVM_VERSION="0.40.3"
# sha256 of https://github.com/nvm-sh/nvm/archive/refs/tags/v0.40.3.tar.gz # sha256 of https://github.com/nvm-sh/nvm/archive/refs/tags/v0.40.3.tar.gz
NVM_SHA256="5f4d6aaa04a177dc93c985e31dbc411ab6b8c6e1e21d8015dbc1372625fcd1d0" NVM_SHA256="5f4d6aaa04a177dc93c985e31dbc411ab6b8c6e1e21d8015dbc1372625fcd1d0"
YARN_VERSION="1.22.22" YARN_VERSION="1.22.22"
# The Go inside the golang:1.25-alpine image Dockerfile.backend builds
# with, 2026-08-09. The archive hashes are in ensure_go.
GO_VERSION="1.25.7"
BIN_DIR="$HOME/.local/bin"
TOOLCHAIN="$HOME/.local/share/$("$ROOT/script/projectname")/toolchain"
PKGMGR="" PKGMGR=""
SUDO="" SUDO=""
@@ -79,6 +96,26 @@ verify_sha256() {
fi fi
} }
# link_bin <target> <name>: make an installed tool reachable as
# $BIN_DIR/<name>. Only a symlink this script made, one pointing into
# $TOOLCHAIN or ~/.nvm, is ever replaced; if anything else is already
# there, bootstrap stops.
link_bin() {
link="$BIN_DIR/$2"
if [ -L "$link" ] || [ -e "$link" ]; then
case "$(readlink "$link" || true)" in
"$TOOLCHAIN"/* | "$HOME"/.nvm/*) ;;
*)
echo "bootstrap: $link was not created by this script;" >&2
echo " remove or rename it, then re-run bootstrap" >&2
exit 1
;;
esac
fi
mkdir -p "$BIN_DIR"
ln -sf "$1" "$link"
}
# nvm is a bash script; run a command in a bash with nvm loaded # nvm is a bash script; run a command in a bash with nvm loaded
nvm_sh() { nvm_sh() {
bash -c ". \"\$HOME/.nvm/nvm.sh\" && $*" bash -c ". \"\$HOME/.nvm/nvm.sh\" && $*"
@@ -103,39 +140,117 @@ ensure_node() {
if ! missing node; then return 0; fi if ! missing node; then return 0; fi
ensure_nvm ensure_nvm
nvm_sh "nvm install $NODE_VERSION" nvm_sh "nvm install $NODE_VERSION"
link_bin "$HOME/.nvm/versions/node/v$NODE_VERSION/bin/node" node
} }
# ensure_yarn: corepack writes its shims (pnpm and yarnpkg as well as
# yarn) into $TOOLCHAIN rather than next to itself, and the npm fallback
# installs there too; only yarn is linked.
ensure_yarn() { ensure_yarn() {
if ! missing yarn; then return 0; fi if ! missing yarn; then return 0; fi
shims="$TOOLCHAIN/corepack-shims"
mkdir -p "$shims"
if ! missing corepack; then if ! missing corepack; then
corepack enable corepack enable --install-directory "$shims"
corepack prepare "yarn@$YARN_VERSION" --activate corepack prepare "yarn@$YARN_VERSION" --activate
elif [ -s "$HOME/.nvm/nvm.sh" ]; then elif [ -s "$HOME/.nvm/nvm.sh" ]; then
nvm_sh "nvm use $NODE_VERSION >/dev/null && corepack enable && \ nvm_sh "nvm use $NODE_VERSION >/dev/null && \
corepack enable --install-directory \"$shims\" && \
corepack prepare yarn@$YARN_VERSION --activate" corepack prepare yarn@$YARN_VERSION --activate"
else else
npm install -g "yarn@$YARN_VERSION" npm install -g --prefix "$TOOLCHAIN/npm-global" "yarn@$YARN_VERSION"
shims="$TOOLCHAIN/npm-global/bin"
fi fi
link_bin "$shims/yarn" yarn
} }
install_js_deps() { # go_ok: the go on PATH has its gofmt beside it (a Go release ships the
if missing yarn && [ -s "$HOME/.nvm/nvm.sh" ]; then # two together) and is at least the version backend/go.mod asks for.
nvm_sh "nvm use $NODE_VERSION >/dev/null && cd \"$ROOT\" && \ # GOTOOLCHAIN=local makes an older go fail here instead of fetching a
yarn install --frozen-lockfile" # newer toolchain for itself.
else go_ok() {
yarn install --frozen-lockfile 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)
}
# 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.
ensure_go() {
if go_ok; then 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
case "$(uname -s)-$(uname -m)" in
Linux-x86_64)
plat="linux-amd64"
sha="12e6d6a191091ae27dc31f6efc630e3a3b8ba409baf3573d955b196fdf086005"
;;
Linux-aarch64)
plat="linux-arm64"
sha="ba611a53534135a81067240eff9508cd7e256c560edd5d8c2fef54f083c07129"
;;
Darwin-x86_64)
plat="darwin-amd64"
sha="bf5050a2152f4053837b886e8d9640c829dbacbc3370f913351eb0904cb706f5"
;;
Darwin-arm64)
plat="darwin-arm64"
sha="ff18369ffad05c57d5bed888b660b31385f3c913670a83ef557cdfd98ea9ae1b"
;;
*)
echo "bootstrap: no pinned Go release for this platform" >&2
exit 1
;;
esac
if missing curl; then pkg_install curl curl curl curl; fi
mkdir -p "$TOOLCHAIN"
curl -fsSL -o "$go_dir.tar.gz" \
"https://go.dev/dl/go$GO_VERSION.$plat.tar.gz"
verify_sha256 "$go_dir.tar.gz" "$sha"
# Unpacked beside its final place and then moved there, so an
# interrupted run never leaves a partial Go that looks complete.
rm -rf "$go_dir.partial"
mkdir "$go_dir.partial"
tar -xzf "$go_dir.tar.gz" -C "$go_dir.partial" --strip-components=1
rm -rf "$go_dir" "$go_dir.tar.gz"
mv "$go_dir.partial" "$go_dir"
fi fi
link_bin "$go_dir/bin/go" go
link_bin "$go_dir/bin/gofmt" gofmt
} }
main() { main() {
cd "$ROOT" cd "$ROOT"
# Tools linked on an earlier run count as installed, and tools linked
# on this run are found by the steps after it.
path_hint=""
case ":$PATH:" in
*":$BIN_DIR:"*) ;;
*) path_hint=yes ;;
esac
PATH="$BIN_DIR:$PATH"
if missing make; then pkg_install gnumake make make make; fi if missing make; then pkg_install gnumake make make make; fi
if missing git; then pkg_install git git git git; fi if missing git; then pkg_install git git git git; fi
ensure_node ensure_node
ensure_yarn ensure_yarn
install_js_deps yarn install --frozen-lockfile
ensure_go
(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 Go linter" >&2
fi
if [ -n "$path_hint" ] && [ -d "$BIN_DIR" ]; then
echo "bootstrap: add $BIN_DIR to the front of your PATH, e.g." >&2
echo " export PATH=\"\$HOME/.local/bin:\$PATH\"" >&2
fi
echo "bootstrap complete" echo "bootstrap complete"
} }
+7 -3
View File
@@ -1,13 +1,17 @@
#!/bin/sh #!/bin/sh
# script/cibuild: run the CI build. The Dockerfile runs make check, so # script/cibuild: run the CI build. It builds both images: the frontend
# a successful build implies all checks pass. # from Dockerfile and the backend from Dockerfile.backend. Each runs its
# half of the checks as build steps, so a successful cibuild implies the
# whole repo is green. This is the only build step the Gitea workflow
# runs.
set -eu set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() { main() {
cd "$ROOT" cd "$ROOT"
docker build . timeout 300 docker build .
timeout 300 docker build -f Dockerfile.backend .
} }
main "$@" main "$@"
+6 -3
View File
@@ -1,6 +1,7 @@
#!/bin/sh #!/bin/sh
# script/docker: build the Docker image tagged with the project name. # script/docker: build both Docker images, tagged with the project name
# The tag comes from script/projectname. # from script/projectname: the frontend as <name>, from Dockerfile, and
# the backend as <name>-server, from Dockerfile.backend.
set -eu set -eu
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
@@ -8,7 +9,9 @@ ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)"
main() { main() {
cd "$ROOT" cd "$ROOT"
timeout 300 docker build -t "$("$SCRIPT_DIR/projectname")" . name="$("$SCRIPT_DIR/projectname")"
timeout 300 docker build -t "$name" .
timeout 300 docker build -t "$name-server" -f Dockerfile.backend .
} }
main "$@" main "$@"
+4 -2
View File
@@ -1,12 +1,14 @@
#!/bin/sh #!/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 set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() { main() {
cd "$ROOT" cd "$ROOT"
yarn prettier --write . "$ROOT/script/frontend-fmt"
"$ROOT/backend/script/fmt"
} }
main "$@" main "$@"
+4 -3
View File
@@ -1,13 +1,14 @@
#!/bin/sh #!/bin/sh
# script/fmt-check: check formatting (read-only). Same scope as # script/fmt-check: check formatting across the whole repo (read-only).
# script/fmt, but fails instead of writing. # Same scope as script/fmt, but fails instead of writing.
set -eu set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() { main() {
cd "$ROOT" cd "$ROOT"
yarn prettier --check . "$ROOT/script/frontend-fmt-check"
"$ROOT/backend/script/fmt-check"
} }
main "$@" main "$@"
+18
View File
@@ -0,0 +1,18 @@
#!/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 neither Go nor Docker; 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
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() {
cd "$ROOT"
"$ROOT/script/frontend-test"
"$ROOT/script/frontend-lint"
"$ROOT/script/frontend-fmt-check"
}
main "$@"
+14
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
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
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
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 "$@"
+1 -1
View File
@@ -45,7 +45,7 @@ main() {
cd "$ROOT" cd "$ROOT"
# Test what ships: the production build, not a dev server. # Test what ships: the production build, not a dev server.
"$ROOT/script/test" "$ROOT/script/frontend-test"
if [ ! -f "$ROOT/dist/index.html" ]; then if [ ! -f "$ROOT/dist/index.html" ]; then
echo "frontend-viewport-test: dist/index.html missing after build" >&2 echo "frontend-viewport-test: dist/index.html missing after build" >&2
exit 1 exit 1
+11 -2
View File
@@ -1,12 +1,21 @@
#!/bin/sh #!/bin/sh
# script/lint: run the linter (prettier in check mode). # script/lint: lint the whole repo: prettier over the frontend, then the
# Go linter over backend/.
#
# The Go linter runs only in Docker: this builds the lint stage of
# Dockerfile.backend, the digest-pinned golangci-lint image, which runs
# the backend's fmt-check and lint targets. --no-cache makes the linter
# really run every time rather than reuse an earlier result, and the
# stage is built for its checks alone, so no image is kept.
set -eu set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() { main() {
cd "$ROOT" cd "$ROOT"
yarn prettier --check . "$ROOT/script/frontend-lint"
timeout 300 docker build --no-cache --target lint \
--output type=cacheonly -f Dockerfile.backend .
} }
main "$@" main "$@"
+4 -3
View File
@@ -1,13 +1,14 @@
#!/bin/sh #!/bin/sh
# script/test: run the test suite. This repo has no unit tests; the # script/test: run the test suite for the whole repo: the frontend at
# production build serves as the test (fails on broken code). # the repo root, then the Go backend in backend/. Both halves together
# get 30 seconds; each also keeps its own limit for the Dockerfiles.
set -eu set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() { main() {
cd "$ROOT" cd "$ROOT"
timeout 30 yarn build timeout 30 sh -c 'script/frontend-test && backend/script/test'
} }
main "$@" main "$@"