build: unify the gate so root make check covers the backend (closes #16)
check / check (push) Successful in 11s
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:
@@ -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
@@ -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
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
|
||||||
|
|||||||
Executable
+21
@@ -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 "$@"
|
||||||
Executable
+12
@@ -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 "$@"
|
||||||
Executable
+12
@@ -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 "$@"
|
||||||
Executable
+18
@@ -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 "$@"
|
||||||
Executable
+32
@@ -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 "$@"
|
||||||
Executable
+13
@@ -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 "$@"
|
||||||
Executable
+12
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 "$@"
|
||||||
|
|||||||
Executable
+18
@@ -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 "$@"
|
||||||
Executable
+14
@@ -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 "$@"
|
||||||
Executable
+13
@@ -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 "$@"
|
||||||
Executable
+12
@@ -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 "$@"
|
||||||
Executable
+14
@@ -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 "$@"
|
||||||
@@ -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
@@ -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
@@ -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 "$@"
|
||||||
|
|||||||
Reference in New Issue
Block a user