name: CI on: pull_request: push: branches: - main workflow_dispatch: concurrency: group: ci-${{ github.ref }} cancel-in-progress: true env: GOPROXY: https://proxy.golang.org,direct GOPRIVATE: github.com/Silo-Server/* GONOSUMDB: github.com/Silo-Server/* # Pinned so a lint gate cannot change its mind between two runs of the same # commit. Built from source below rather than downloaded: a released binary # refuses to run against a Go version newer than the one it was built with, # and go.mod tracks Go closely enough that this repo is regularly ahead. GOLANGCI_LINT_VERSION: v2.12.2 # The default token is read-write. Nothing here needs to write, and a token # that cannot push is one fewer thing a compromised dependency can reach. permissions: contents: read jobs: go: name: Go runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v5 with: # golangci-lint needs the merge base to tell this branch's lines from # the ones it inherited. fetch-depth: 0 persist-credentials: false # github.com/h2non/bimg binds libvips through cgo and pkg-config, so # nothing under ./... compiles without the headers. The Dockerfile # installs the same package in its build stage. - name: Install libvips run: | sudo apt-get update sudo apt-get install -y --no-install-recommends libvips-dev - name: Set up Go uses: actions/setup-go@v6 with: go-version-file: go.mod cache: true # cmd/silo embeds the built frontend, so nothing under ./... compiles # without web/dist. The Go jobs never serve it, so a placeholder is # enough; the Docker workflow builds the real bundle. - name: Stub the embedded frontend bundle run: make embed-stub - name: Build run: go build ./... - name: gofmt run: | unformatted="$(gofmt -l .)" if [ -n "$unformatted" ]; then echo "::error::gofmt is required on:" echo "$unformatted" exit 1 fi - name: Vet run: go vet ./... # Scoped to the lines this branch touched. The repo does not pass a full # golangci-lint run today — there are a few hundred pre-existing findings, # which is why the Go half of `make lint` has never been enforced — and # blocking every PR on a cleanup nobody has done would just get the gate # removed again. New and changed lines have to be clean, so the count only # falls from here. - name: Install golangci-lint run: go install "github.com/golangci/golangci-lint/v2/cmd/golangci-lint@${GOLANGCI_LINT_VERSION}" - name: Lint changed lines env: # A PR carries its target branch; a push to main compares against # main's own history, which leaves the merge base at HEAD and lints # nothing new. Read through the environment rather than interpolated # into the script. BASE_REF: ${{ github.base_ref || github.event.repository.default_branch }} run: | git fetch --no-tags origin "+refs/heads/${BASE_REF}:refs/remotes/origin/${BASE_REF}" golangci-lint run --new-from-merge-base="origin/${BASE_REF}" ./... # A manifest change that does not regenerate leaves every client reading # stale keys, which the contract exists to prevent. - name: Verify settings bindings are current run: make verify-settings-bindings # The server defines the playback protocol and the clients prove # conformance against these fixtures. A contract change that does not # regenerate leaves every client testing against a stale body, so the # drift surfaces as a playback bug on three platforms rather than as a # failure here. - name: Verify playback protocol fixtures are current run: make verify-playback-fixtures # Runs the settings-contract gate among everything else: the embedded # manifest must parse, satisfy its own schema, hold every structural # invariant, and agree with the live settings registry on keys and # defaults. Without this job those tests exist but never run. - name: Test run: make test-go web: name: Web runs-on: ubuntu-latest defaults: run: working-directory: web steps: - name: Checkout uses: actions/checkout@v5 with: persist-credentials: false # The pnpm version comes from web/package.json's packageManager field — # there is no package.json at the repo root, and `defaults.run` does not # apply to an action's own inputs. - name: Set up pnpm uses: pnpm/action-setup@v4 with: package_json_file: web/package.json - name: Set up Node uses: actions/setup-node@v5 with: node-version: 22 cache: pnpm cache-dependency-path: web/pnpm-lock.yaml - name: Install run: pnpm install --frozen-lockfile - name: Lint run: pnpm run lint - name: Format check run: pnpm run format:check - name: Typecheck and build run: pnpm run build # Includes the appearance-cache ownership tests, which are the regression # guard for cross-account leaks in the localStorage warm start. - name: Test working-directory: . run: make test-web # The generated web binding is compared after prettier, so this half of # the bindings check lives here rather than in the Go job, which has no # pnpm. It needs Go to run the generator. - name: Set up Go uses: actions/setup-go@v6 with: go-version-file: go.mod cache: true - name: Verify the generated web settings binding is current working-directory: . run: make verify-settings-bindings-web docs: name: Docs hygiene runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v5 with: persist-credentials: false - name: Verify no local paths leaked into committed docs run: make verify-local-paths