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 # No postgres service container — SQLite (docs/DECISIONS.md D15) needs no server to talk to; # the test suite creates its own temp file (see tests/conftest.py). This also makes the job # meaningfully faster: no service container to start and health-check before tests can run. env: VELODROME_ENVIRONMENT: 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 # No postgres service container — see the api job's comment; same reasoning. env: VELODROME_DATABASE_URL: sqlite+aiosqlite:///./velodrome-ci-migrations.db 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