ee8d4fd85e
- deploy/README.md documents the services, how to run it locally and in CI, and every variable: required (the four :? ones + ≥1 bot token) and optional with defaults, marked secret-vs-variable and with the TEST_/PROD_ Gitea mapping; plus the fixed internal wiring and the host-side setup. - ci.yaml maps the remaining POSTGRES_DB/USER, DICT_VERSION and LOG_LEVEL (unset renders empty -> the compose ":-" defaults apply), so every documented var is per-contour overridable. - .env.example points at the README for the full reference.
209 lines
7.3 KiB
YAML
209 lines
7.3 KiB
YAML
name: CI
|
|
|
|
# Single gated pipeline for the test contour (Stage 16). Gitea cannot express
|
|
# cross-workflow `needs`, so the full test suite and the auto test-deploy live in
|
|
# one workflow.
|
|
#
|
|
# Branch model (CLAUDE.md): feature branches are cut from `development`; a commit
|
|
# to a feature branch triggers nothing. The pipeline runs on a PR into
|
|
# `development` or `master` (the full test suite — the merge gate) and on a push
|
|
# to `development` (after a merge). The deploy job runs only for `development`
|
|
# (PR or merge), so a PR into `master` is test-only; the prod deploy is a manual
|
|
# workflow (Stage 17).
|
|
#
|
|
# Console output is kept plain (NO_COLOR + `docker compose --ansi never` +
|
|
# `--progress plain`) so the Gitea logs stay readable.
|
|
|
|
on:
|
|
pull_request:
|
|
branches: [development, master]
|
|
push:
|
|
branches: [development]
|
|
|
|
jobs:
|
|
unit:
|
|
runs-on: ubuntu-latest
|
|
defaults:
|
|
run:
|
|
shell: bash
|
|
env:
|
|
# The engine consumes the published scrabble-solver module from this Gitea;
|
|
# GOPRIVATE makes go fetch it directly (skipping the public proxy/checksum DB).
|
|
GOPRIVATE: gitea.iliadenisov.ru/*
|
|
DICT_VERSION: v1.0.0
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Fetch dictionary DAWGs
|
|
run: |
|
|
mkdir -p "${GITHUB_WORKSPACE}/dawg"
|
|
curl -fsSL -o /tmp/dawg.tar.gz "https://gitea.iliadenisov.ru/developer/scrabble-dictionary/releases/download/${DICT_VERSION}/scrabble-dawg-${DICT_VERSION}.tar.gz"
|
|
tar xzf /tmp/dawg.tar.gz -C "${GITHUB_WORKSPACE}/dawg"
|
|
ls -la "${GITHUB_WORKSPACE}/dawg"
|
|
|
|
- name: Set up Go
|
|
uses: actions/setup-go@v5
|
|
with:
|
|
go-version-file: go.work
|
|
cache: true
|
|
|
|
- name: gofmt
|
|
run: |
|
|
unformatted="$(gofmt -l .)"
|
|
if [ -n "$unformatted" ]; then
|
|
echo "gofmt needed on:"; echo "$unformatted"; exit 1
|
|
fi
|
|
|
|
- name: vet
|
|
run: go vet ./backend/... ./pkg/... ./gateway/... ./platform/telegram/...
|
|
|
|
- name: build
|
|
run: go build ./backend/... ./pkg/... ./gateway/... ./platform/telegram/...
|
|
|
|
- name: test
|
|
env:
|
|
BACKEND_DICT_DIR: ${{ github.workspace }}/dawg
|
|
run: go test -count=1 ./backend/... ./pkg/... ./gateway/... ./platform/telegram/...
|
|
|
|
integration:
|
|
runs-on: ubuntu-latest
|
|
defaults:
|
|
run:
|
|
shell: bash
|
|
env:
|
|
# Ryuk (testcontainers' reaper) does not start cleanly on every runner; the
|
|
# suite's TestMain terminates its own container, so disable it.
|
|
TESTCONTAINERS_RYUK_DISABLED: "true"
|
|
GOPRIVATE: gitea.iliadenisov.ru/*
|
|
DICT_VERSION: v1.0.0
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Fetch dictionary DAWGs
|
|
run: |
|
|
mkdir -p "${GITHUB_WORKSPACE}/dawg"
|
|
curl -fsSL -o /tmp/dawg.tar.gz "https://gitea.iliadenisov.ru/developer/scrabble-dictionary/releases/download/${DICT_VERSION}/scrabble-dawg-${DICT_VERSION}.tar.gz"
|
|
tar xzf /tmp/dawg.tar.gz -C "${GITHUB_WORKSPACE}/dawg"
|
|
ls -la "${GITHUB_WORKSPACE}/dawg"
|
|
|
|
- name: Set up Go
|
|
uses: actions/setup-go@v5
|
|
with:
|
|
go-version-file: go.work
|
|
cache: true
|
|
|
|
- name: Integration tests
|
|
# -count=1 disables the cache; -p=1 -parallel=1 keeps the container-backed
|
|
# tests serial; the 15-minute timeout bounds a stuck container pull.
|
|
env:
|
|
BACKEND_DICT_DIR: ${{ github.workspace }}/dawg
|
|
run: go test -tags=integration -count=1 -p=1 -parallel=1 -timeout=15m ./backend/...
|
|
|
|
ui:
|
|
runs-on: ubuntu-latest
|
|
defaults:
|
|
run:
|
|
shell: bash
|
|
working-directory: ui
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Set up Node
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 22
|
|
|
|
- name: Install pnpm
|
|
run: npm install -g pnpm@11.0.9
|
|
|
|
- name: Install deps
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
- name: Type-check
|
|
run: pnpm run check
|
|
|
|
- name: Unit tests
|
|
run: pnpm run test:unit
|
|
|
|
- name: Build
|
|
run: pnpm run build
|
|
|
|
- name: Bundle-size budget
|
|
run: node scripts/bundle-size.mjs
|
|
|
|
- name: Install Playwright browsers
|
|
run: pnpm exec playwright install chromium webkit
|
|
timeout-minutes: 5
|
|
|
|
- name: E2E smoke (mock)
|
|
run: pnpm run test:e2e
|
|
timeout-minutes: 5
|
|
|
|
deploy:
|
|
# Auto test-deploy on a PR into development and on the push that merges it.
|
|
# A PR into master is test-only (this job is skipped); prod deploy is manual.
|
|
needs: [unit, integration, ui]
|
|
if: ${{ (github.event_name == 'push' && github.ref == 'refs/heads/development') || (github.event_name == 'pull_request' && github.base_ref == 'development') }}
|
|
runs-on: ubuntu-latest
|
|
defaults:
|
|
run:
|
|
shell: bash
|
|
env:
|
|
NO_COLOR: "1"
|
|
DOCKER_CLI_HINTS: "false"
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Build and (re)deploy the test contour
|
|
working-directory: deploy
|
|
env:
|
|
# Sensitive values -> secrets; non-sensitive -> variables. The compose
|
|
# interpolates these unprefixed names (see deploy/.env.example).
|
|
POSTGRES_PASSWORD: ${{ secrets.TEST_POSTGRES_PASSWORD }}
|
|
AWG_CONF: ${{ secrets.TEST_AWG_CONF }}
|
|
GM_BASICAUTH_HASH: ${{ secrets.TEST_GM_BASICAUTH_HASH }}
|
|
GRAFANA_ADMIN_PASSWORD: ${{ secrets.TEST_GRAFANA_ADMIN_PASSWORD }}
|
|
TELEGRAM_BOT_TOKEN_EN: ${{ secrets.TEST_TELEGRAM_BOT_TOKEN_EN }}
|
|
TELEGRAM_BOT_TOKEN_RU: ${{ secrets.TEST_TELEGRAM_BOT_TOKEN_RU }}
|
|
GM_BASICAUTH_USER: ${{ vars.TEST_GM_BASICAUTH_USER }}
|
|
GRAFANA_ROOT_URL: ${{ vars.TEST_GRAFANA_ROOT_URL }}
|
|
CADDY_SITE_ADDRESS: ${{ vars.TEST_CADDY_SITE_ADDRESS }}
|
|
TELEGRAM_MINIAPP_URL: ${{ vars.TEST_TELEGRAM_MINIAPP_URL }}
|
|
TELEGRAM_GAME_CHANNEL_ID_EN: ${{ vars.TEST_TELEGRAM_GAME_CHANNEL_ID_EN }}
|
|
TELEGRAM_GAME_CHANNEL_ID_RU: ${{ vars.TEST_TELEGRAM_GAME_CHANNEL_ID_RU }}
|
|
TELEGRAM_TEST_ENV: ${{ vars.TEST_TELEGRAM_TEST_ENV }}
|
|
VITE_TELEGRAM_BOT_ID: ${{ vars.TEST_VITE_TELEGRAM_BOT_ID }}
|
|
VITE_TELEGRAM_LINK: ${{ vars.TEST_VITE_TELEGRAM_LINK }}
|
|
VITE_GATEWAY_URL: ${{ vars.TEST_VITE_GATEWAY_URL }}
|
|
GATEWAY_DEFAULT_SUPPORTED_LANGUAGES: ${{ vars.TEST_GATEWAY_DEFAULT_SUPPORTED_LANGUAGES }}
|
|
# Unset vars render empty -> the compose ":-" defaults apply.
|
|
POSTGRES_DB: ${{ vars.TEST_POSTGRES_DB }}
|
|
POSTGRES_USER: ${{ vars.TEST_POSTGRES_USER }}
|
|
DICT_VERSION: ${{ vars.TEST_DICT_VERSION }}
|
|
LOG_LEVEL: ${{ vars.TEST_LOG_LEVEL }}
|
|
run: |
|
|
docker compose --ansi never build --progress plain
|
|
docker compose --ansi never up -d --remove-orphans
|
|
|
|
- name: Probe the gateway through caddy
|
|
run: |
|
|
set -u
|
|
for i in $(seq 1 20); do
|
|
if docker run --rm --network edge alpine:3.20 wget -q -T 5 -O /dev/null http://scrabble/; then
|
|
echo "healthy: GET http://scrabble/"
|
|
exit 0
|
|
fi
|
|
sleep 3
|
|
done
|
|
echo "probe failed; recent gateway logs:"
|
|
docker logs --tail 50 scrabble-gateway || true
|
|
exit 1
|
|
|
|
- name: Prune dangling images
|
|
if: always()
|
|
run: docker image prune -f
|