mirror of
https://github.com/tiennm99/noitu.git
synced 2026-10-11 03:13:45 +00:00
HEALTHCHECK via noitu-server -healthcheck; the root LICENSE ships next to NOTICE and the CI image check requires it; .claude and .env files stay out of the build context. CI uses go-version stable, runs govulncheck and npm audit, checks out without persisted credentials, and proto.yml moves off the archived buf-setup-action. dependabot.yml is dropped; the audit steps are the dependency signal. deployment.md gains the Coolify/Traefik recipe, the stop-grace rule, the hello deadline and per-address room budget, and the suppressed-log counter.
211 lines
6.7 KiB
YAML
211 lines
6.7 KiB
YAML
# Tests and packaging.
|
|
#
|
|
# Nothing here downloads the upstream wordlist except the release image
|
|
# build. Everything else plays against the small database derived from
|
|
# the checked-in word sample, which goes through the same builder the real one
|
|
# does.
|
|
#
|
|
# The wire contract has its own workflow: see proto.yml.
|
|
name: ci
|
|
|
|
on:
|
|
push:
|
|
branches: [main, dev]
|
|
pull_request:
|
|
release:
|
|
types: [published]
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
go:
|
|
name: Go
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
persist-credentials: false
|
|
- uses: actions/setup-go@v5
|
|
with:
|
|
go-version: stable
|
|
cache-dependency-path: server/go.sum
|
|
|
|
- name: Vet
|
|
run: go vet ./...
|
|
working-directory: server
|
|
|
|
# gofmt -l lists files that are not gofmt-clean; -d would only show the
|
|
# diff. Non-empty output is the failure signal, so it decides the exit
|
|
# code itself rather than leaning on the emptiness of a report nobody
|
|
# reads.
|
|
- name: Format check
|
|
run: |
|
|
unformatted="$(gofmt -l .)"
|
|
if [ -n "$unformatted" ]; then
|
|
echo "not gofmt-clean:"
|
|
echo "$unformatted"
|
|
exit 1
|
|
fi
|
|
working-directory: server
|
|
|
|
# Default linters only: this is a signal every PR has to pass, not a
|
|
# style debate, so nothing beyond golangci-lint's own defaults is
|
|
# enabled here.
|
|
- name: Lint
|
|
uses: golangci/golangci-lint-action@v9
|
|
with:
|
|
working-directory: server
|
|
|
|
# The module's go directive is only the minimum. CI runs the newest
|
|
# toolchain, the way the image's golang:1 does, so vet and race results
|
|
# come from the compiler that ships. govulncheck reads the call graph
|
|
# against the current advisory database.
|
|
- name: Vulnerability scan
|
|
run: go run golang.org/x/vuln/cmd/govulncheck@latest ./...
|
|
working-directory: server
|
|
|
|
# -race because the whole transport layer is goroutines and timers, and a
|
|
# data race there is exactly the kind of defect that passes without it.
|
|
- name: Test
|
|
run: go test ./... -race
|
|
working-directory: server
|
|
|
|
web:
|
|
name: Frontend
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
persist-credentials: false
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 24
|
|
cache: npm
|
|
cache-dependency-path: web/package-lock.json
|
|
|
|
- run: npm ci
|
|
working-directory: web
|
|
|
|
- name: Type check
|
|
run: npm run check
|
|
working-directory: web
|
|
|
|
- name: Lint
|
|
run: npm run lint
|
|
working-directory: web
|
|
|
|
# Runtime dependencies only: the frontend ships as static files, so a
|
|
# dev-tool advisory cannot reach production.
|
|
- name: Audit
|
|
run: npm audit --omit=dev --audit-level=high
|
|
working-directory: web
|
|
|
|
# npm test builds first, so this also proves the bundle compiles and
|
|
# carries no wordlist.
|
|
- name: Test
|
|
run: npm test
|
|
working-directory: web
|
|
|
|
e2e:
|
|
name: End to end
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
persist-credentials: false
|
|
- uses: actions/setup-go@v5
|
|
with:
|
|
go-version: stable
|
|
cache-dependency-path: server/go.sum
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 24
|
|
cache: npm
|
|
cache-dependency-path: web/package-lock.json
|
|
|
|
- name: Build the fixture dictionary
|
|
run: go run ./cmd/build-dictionary --words ../testdata/fixture-words.txt --out ../data/fixture.db --min-words 150
|
|
working-directory: server
|
|
|
|
- run: npm ci
|
|
working-directory: web
|
|
|
|
- name: Install the browser
|
|
run: npx playwright install --with-deps chromium
|
|
working-directory: web
|
|
|
|
- name: Run the suite
|
|
run: npm run test:e2e
|
|
working-directory: web
|
|
|
|
# test-results holds the traces a failure leaves behind, which is what is
|
|
# actually worth downloading; the HTML report is not generated here.
|
|
- uses: actions/upload-artifact@v4
|
|
if: failure()
|
|
with:
|
|
name: playwright-traces
|
|
path: web/test-results/
|
|
retention-days: 7
|
|
|
|
image:
|
|
name: Container image
|
|
runs-on: ubuntu-latest
|
|
# e2e too: the moment this job gains a publish step, an image built past a
|
|
# red browser suite is an image nobody meant to ship.
|
|
needs: [go, web, e2e]
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
persist-credentials: false
|
|
|
|
# On a release the image is built from the real upstream release, which
|
|
# is the only job in this file that downloads it. Every other run builds
|
|
# the same Dockerfile against the fixture word list, so a broken image is
|
|
# caught on the pull request rather than at release time.
|
|
- name: Build
|
|
run: |
|
|
if [ "${{ github.event_name }}" = "release" ]; then
|
|
docker build -t noitu:ci .
|
|
else
|
|
docker build --build-arg FIXTURE_DICT=1 -t noitu:ci .
|
|
fi
|
|
|
|
# CC BY-SA 4.0 applies to the derived wordlist wherever it is
|
|
# distributed, and an image is distribution. This is the assertion that
|
|
# the obligation actually shipped.
|
|
- name: The licence travels with the data
|
|
run: |
|
|
set -eu
|
|
docker create --name check noitu:ci
|
|
docker export check | tar -t > files.txt
|
|
docker rm check
|
|
|
|
for required in app/LICENSE app/data/LICENSE app/data/ATTRIBUTION.md app/NOTICE app/data/noitu.db; do
|
|
grep -qx "$required" files.txt || { echo "missing from the image: $required"; exit 1; }
|
|
done
|
|
|
|
# The upstream dump, compressed or not, must never reach the final
|
|
# image.
|
|
if grep -Eq '\.(bz2|xml)$' files.txt; then
|
|
echo "the upstream dump leaked into the image"
|
|
exit 1
|
|
fi
|
|
|
|
- name: It serves a game
|
|
run: |
|
|
set -eu
|
|
docker run -d --name noitu -p 8080:8080 noitu:ci
|
|
for _ in $(seq 1 30); do
|
|
if curl -fsS http://localhost:8080/healthz >/dev/null 2>&1; then break; fi
|
|
sleep 1
|
|
done
|
|
curl -fsS http://localhost:8080/healthz
|
|
# The image has no curl, so the HEALTHCHECK command is the binary
|
|
# itself; run it the way the container runtime does.
|
|
docker exec noitu /app/noitu-server -healthcheck
|
|
# A deep link is a client route, so the binary has to answer it with
|
|
# the app shell rather than a 404.
|
|
curl -fsS -o /dev/null -w '%{http_code}\n' 'http://localhost:8080/play?difficulty=2' | grep -qx 200
|
|
docker rm -f noitu
|