name: CI on: push: branches: [main] pull_request: workflow_dispatch: concurrency: group: ci-${{ gitea.ref }} cancel-in-progress: true jobs: meta: name: Repo hygiene runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Branch name follows convention if: gitea.event_name == 'pull_request' run: | BRANCH="${{ gitea.head_ref }}" echo "Branch: $BRANCH" if echo "$BRANCH" | grep -Eq '^(feat|fix|refactor|test|docs|chore|ci)/[a-z0-9._-]+$'; then echo "OK" else echo "::error::Branch '$BRANCH' does not match /." echo "Valid types: feat fix refactor test docs chore ci" exit 1 fi - name: No secrets committed run: | # Deliberately narrow: high-signal patterns only, so this never cries wolf. PATTERN='BEGIN (RSA|OPENSSH|EC|PGP) PRIVATE KEY|xoxb-[0-9A-Za-z-]{10,}|AKIA[0-9A-Z]{16}' if git grep -nIE "$PATTERN" -- . ':!.gitea/workflows/*' ; then echo "::error::Possible secret committed (see matches above)." exit 1 fi echo "No secret patterns found." - name: No ride data committed run: | if git ls-files | grep -E '\.(fit|gpx|tcx)$' | grep -v '^apps/api/tests/fixtures/fit/'; then echo "::error::Ride files belong in the blob store, not git." echo "Only apps/api/tests/fixtures/fit/ may contain .fit files (golden fixtures)." exit 1 fi echo "Clean." api: name: API (lint, types, tests) runs-on: ubuntu-latest services: postgres: image: postgis/postgis:16-3.4 env: POSTGRES_USER: velodrome POSTGRES_PASSWORD: velodrome POSTGRES_DB: velodrome_test options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 10 env: DATABASE_URL: postgresql+asyncpg://velodrome:velodrome@postgres:5432/velodrome_test steps: - uses: actions/checkout@v4 - name: Is there API code yet? id: guard run: | if [ -n "$(find apps/api -name '*.py' -not -path '*/.*' 2>/dev/null | head -1)" ]; then echo "present=true" >> "$GITHUB_OUTPUT" else echo "present=false" >> "$GITHUB_OUTPUT" echo "No Python sources under apps/api yet — skipping." fi - uses: actions/setup-python@v5 if: steps.guard.outputs.present == 'true' with: python-version: '3.12' - name: Cache uv if: steps.guard.outputs.present == 'true' uses: actions/cache@v4 with: path: ~/.cache/uv key: uv-${{ runner.os }}-${{ hashFiles('apps/api/pyproject.toml') }} restore-keys: uv-${{ runner.os }}- - name: Install if: steps.guard.outputs.present == 'true' working-directory: apps/api run: | curl -LsSf https://astral.sh/uv/install.sh | sh export PATH="$HOME/.local/bin:$PATH" uv sync --all-extras - name: Lint (ruff) if: steps.guard.outputs.present == 'true' working-directory: apps/api run: | export PATH="$HOME/.local/bin:$PATH" uv run ruff check . uv run ruff format --check . - name: Types (mypy --strict) if: steps.guard.outputs.present == 'true' working-directory: apps/api run: | export PATH="$HOME/.local/bin:$PATH" uv run mypy --strict velodrome - name: Tests if: steps.guard.outputs.present == 'true' working-directory: apps/api run: | export PATH="$HOME/.local/bin:$PATH" uv run pytest -q web: name: Web (lint, typecheck, build) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Is there web code yet? id: guard run: | if [ -f apps/web/package.json ]; then echo "present=true" >> "$GITHUB_OUTPUT" else echo "present=false" >> "$GITHUB_OUTPUT" echo "No apps/web/package.json yet — skipping." fi - uses: actions/setup-node@v4 if: steps.guard.outputs.present == 'true' with: node-version: '22' - name: Install, lint, build if: steps.guard.outputs.present == 'true' working-directory: apps/web run: | corepack enable pnpm install --frozen-lockfile pnpm run lint pnpm run check pnpm run build migrations: name: Migrations reversible runs-on: ubuntu-latest services: postgres: image: postgis/postgis:16-3.4 env: POSTGRES_USER: velodrome POSTGRES_PASSWORD: velodrome POSTGRES_DB: velodrome_mig options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 10 env: DATABASE_URL: postgresql+asyncpg://velodrome:velodrome@postgres:5432/velodrome_mig steps: - uses: actions/checkout@v4 - name: Are there migrations yet? id: guard run: | if [ -d apps/api/alembic/versions ] && [ -n "$(ls -A apps/api/alembic/versions/*.py 2>/dev/null)" ]; then echo "present=true" >> "$GITHUB_OUTPUT" else echo "present=false" >> "$GITHUB_OUTPUT" echo "No migrations yet — skipping." fi - uses: actions/setup-python@v5 if: steps.guard.outputs.present == 'true' with: python-version: '3.12' - name: upgrade -> downgrade -> upgrade, and check for model drift if: steps.guard.outputs.present == 'true' working-directory: apps/api run: | curl -LsSf https://astral.sh/uv/install.sh | sh export PATH="$HOME/.local/bin:$PATH" uv sync --all-extras uv run alembic upgrade head uv run alembic downgrade -1 uv run alembic upgrade head # Fails if the models have drifted from the migrations. uv run alembic check