From 16f8353f3aae3d48b20127f187d05bb0adce7b55 Mon Sep 17 00:00:00 2001 From: mac Date: Mon, 20 Jul 2026 17:02:13 +0800 Subject: [PATCH 1/2] ci: add frontend and backend workflows --- .github/workflows/backend-ci.yml | 79 +++++++++++++++++++++++++++++++ .github/workflows/frontend-ci.yml | 62 ++++++++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 .github/workflows/backend-ci.yml create mode 100644 .github/workflows/frontend-ci.yml diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml new file mode 100644 index 0000000..b5d02a8 --- /dev/null +++ b/.github/workflows/backend-ci.yml @@ -0,0 +1,79 @@ +name: Backend CI + +on: + pull_request: + paths: + - "backend/**" + - ".github/workflows/backend-ci.yml" + push: + branches: + - main + paths: + - "backend/**" + - ".github/workflows/backend-ci.yml" + +permissions: + contents: read + +concurrency: + group: backend-ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + check: + name: Check, test, and build + runs-on: ubuntu-latest + defaults: + run: + working-directory: backend + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Detect backend project + id: backend + working-directory: . + run: | + if [[ -f backend/pyproject.toml && -f backend/uv.lock ]]; then + echo "exists=true" >> "$GITHUB_OUTPUT" + else + echo "exists=false" >> "$GITHUB_OUTPUT" + fi + + - name: Set up Python + if: steps.backend.outputs.exists == 'true' + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Set up uv + if: steps.backend.outputs.exists == 'true' + uses: astral-sh/setup-uv@v6 + with: + enable-cache: true + cache-dependency-glob: backend/uv.lock + + - name: Install dependencies + if: steps.backend.outputs.exists == 'true' + run: uv sync --locked --all-groups + + - name: Lint + if: steps.backend.outputs.exists == 'true' + run: uv run ruff check . + + - name: Check formatting + if: steps.backend.outputs.exists == 'true' + run: uv run ruff format --check . + + - name: Type check + if: steps.backend.outputs.exists == 'true' + run: uv run mypy + + - name: Test + if: steps.backend.outputs.exists == 'true' + run: uv run pytest + + - name: Build package + if: steps.backend.outputs.exists == 'true' + run: uv build diff --git a/.github/workflows/frontend-ci.yml b/.github/workflows/frontend-ci.yml new file mode 100644 index 0000000..cb27b1e --- /dev/null +++ b/.github/workflows/frontend-ci.yml @@ -0,0 +1,62 @@ +name: Frontend CI + +on: + pull_request: + paths: + - "frontend/**" + - ".github/workflows/frontend-ci.yml" + push: + branches: + - main + paths: + - "frontend/**" + - ".github/workflows/frontend-ci.yml" + +permissions: + contents: read + +concurrency: + group: frontend-ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + check: + name: Check and build + runs-on: ubuntu-latest + defaults: + run: + working-directory: frontend + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Detect frontend project + id: frontend + working-directory: . + run: | + if [[ -f frontend/package-lock.json ]]; then + echo "exists=true" >> "$GITHUB_OUTPUT" + else + echo "exists=false" >> "$GITHUB_OUTPUT" + fi + + - name: Set up Node.js + if: steps.frontend.outputs.exists == 'true' + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + cache-dependency-path: frontend/package-lock.json + + - name: Install dependencies + if: steps.frontend.outputs.exists == 'true' + run: npm ci + + - name: Check code quality + if: steps.frontend.outputs.exists == 'true' + run: npm run check + + - name: Build Android bundle + if: steps.frontend.outputs.exists == 'true' + run: npx expo export --platform android --output-dir dist From aa8eb17269acbc7172e91ee4da54c834ae705f15 Mon Sep 17 00:00:00 2001 From: mac Date: Tue, 21 Jul 2026 14:51:18 +0800 Subject: [PATCH 2/2] ci: consolidate frontend and backend workflows --- .github/workflows/backend-ci.yml | 79 ------------------ .github/workflows/ci.yml | 129 ++++++++++++++++++++++++++++++ .github/workflows/frontend-ci.yml | 62 -------------- 3 files changed, 129 insertions(+), 141 deletions(-) delete mode 100644 .github/workflows/backend-ci.yml create mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/frontend-ci.yml diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml deleted file mode 100644 index b5d02a8..0000000 --- a/.github/workflows/backend-ci.yml +++ /dev/null @@ -1,79 +0,0 @@ -name: Backend CI - -on: - pull_request: - paths: - - "backend/**" - - ".github/workflows/backend-ci.yml" - push: - branches: - - main - paths: - - "backend/**" - - ".github/workflows/backend-ci.yml" - -permissions: - contents: read - -concurrency: - group: backend-ci-${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - check: - name: Check, test, and build - runs-on: ubuntu-latest - defaults: - run: - working-directory: backend - - steps: - - name: Check out repository - uses: actions/checkout@v4 - - - name: Detect backend project - id: backend - working-directory: . - run: | - if [[ -f backend/pyproject.toml && -f backend/uv.lock ]]; then - echo "exists=true" >> "$GITHUB_OUTPUT" - else - echo "exists=false" >> "$GITHUB_OUTPUT" - fi - - - name: Set up Python - if: steps.backend.outputs.exists == 'true' - uses: actions/setup-python@v5 - with: - python-version: "3.11" - - - name: Set up uv - if: steps.backend.outputs.exists == 'true' - uses: astral-sh/setup-uv@v6 - with: - enable-cache: true - cache-dependency-glob: backend/uv.lock - - - name: Install dependencies - if: steps.backend.outputs.exists == 'true' - run: uv sync --locked --all-groups - - - name: Lint - if: steps.backend.outputs.exists == 'true' - run: uv run ruff check . - - - name: Check formatting - if: steps.backend.outputs.exists == 'true' - run: uv run ruff format --check . - - - name: Type check - if: steps.backend.outputs.exists == 'true' - run: uv run mypy - - - name: Test - if: steps.backend.outputs.exists == 'true' - run: uv run pytest - - - name: Build package - if: steps.backend.outputs.exists == 'true' - run: uv build diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d1c7c80 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,129 @@ +name: CI + +on: + pull_request: + push: + branches: [main] + +permissions: + contents: read + +jobs: + detect: + name: Detect project layout + runs-on: ubuntu-latest + outputs: + backend: ${{ steps.projects.outputs.backend }} + frontend: ${{ steps.projects.outputs.frontend }} + compose: ${{ steps.projects.outputs.compose }} + steps: + - uses: actions/checkout@v4 + - id: projects + shell: bash + run: | + backend=false + frontend=false + compose=false + + if [[ -f backend/pyproject.toml && -f backend/uv.lock ]]; then + backend=true + fi + if [[ -f frontend/package-lock.json ]]; then + frontend=true + fi + if [[ -f docker-compose.yml ]]; then + compose=true + fi + + echo "backend=$backend" >> "$GITHUB_OUTPUT" + echo "frontend=$frontend" >> "$GITHUB_OUTPUT" + echo "compose=$compose" >> "$GITHUB_OUTPUT" + + backend: + name: Backend checks + needs: detect + if: needs.detect.outputs.backend == 'true' + runs-on: ubuntu-latest + defaults: + run: + working-directory: backend + services: + postgres: + image: postgres:16.4-alpine + env: + POSTGRES_DB: timeapp + POSTGRES_USER: timeapp + POSTGRES_PASSWORD: timeapp + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -U timeapp -d timeapp" + --health-interval 5s + --health-timeout 5s + --health-retries 10 + env: + TIMEAPP_DATABASE_URL: postgresql+psycopg://timeapp:timeapp@127.0.0.1:5432/timeapp + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.11.15 + - uses: astral-sh/setup-uv@v6 + with: + version: 0.11.28 + enable-cache: true + cache-dependency-glob: backend/uv.lock + - run: uv sync --locked --all-groups + - run: uv run ruff check . + - run: uv run ruff format --check . + - run: uv run mypy + - run: uv run pytest + - run: uv run alembic upgrade head + - run: uv run alembic check + - run: uv build + + frontend: + name: Frontend checks + needs: detect + if: needs.detect.outputs.frontend == 'true' + runs-on: ubuntu-latest + defaults: + run: + working-directory: frontend + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20.20.2 + cache: npm + cache-dependency-path: frontend/package-lock.json + - run: npm ci + - run: npm run check + - run: npm audit --audit-level=moderate + - run: npx --yes expo-doctor@1.20.1 + - run: npx expo export --platform android --output-dir dist + + containers: + name: Container validation + needs: detect + if: needs.detect.outputs.backend == 'true' && needs.detect.outputs.compose == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: docker compose --env-file .env.example config + - run: docker compose --env-file .env.example up --build --detach + - run: | + for attempt in {1..30}; do + if curl --fail --silent http://127.0.0.1:8000/api/v1/health; then + break + fi + if [[ "$attempt" -eq 30 ]]; then + exit 1 + fi + sleep 2 + done + - run: | + # Assert DB is at migration head (do not hardcode a revision id). + docker compose --env-file .env.example exec -T api alembic current | grep -Eq '\(head\)' + - if: always() + run: docker compose --env-file .env.example down --volumes diff --git a/.github/workflows/frontend-ci.yml b/.github/workflows/frontend-ci.yml deleted file mode 100644 index cb27b1e..0000000 --- a/.github/workflows/frontend-ci.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: Frontend CI - -on: - pull_request: - paths: - - "frontend/**" - - ".github/workflows/frontend-ci.yml" - push: - branches: - - main - paths: - - "frontend/**" - - ".github/workflows/frontend-ci.yml" - -permissions: - contents: read - -concurrency: - group: frontend-ci-${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - check: - name: Check and build - runs-on: ubuntu-latest - defaults: - run: - working-directory: frontend - - steps: - - name: Check out repository - uses: actions/checkout@v4 - - - name: Detect frontend project - id: frontend - working-directory: . - run: | - if [[ -f frontend/package-lock.json ]]; then - echo "exists=true" >> "$GITHUB_OUTPUT" - else - echo "exists=false" >> "$GITHUB_OUTPUT" - fi - - - name: Set up Node.js - if: steps.frontend.outputs.exists == 'true' - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: npm - cache-dependency-path: frontend/package-lock.json - - - name: Install dependencies - if: steps.frontend.outputs.exists == 'true' - run: npm ci - - - name: Check code quality - if: steps.frontend.outputs.exists == 'true' - run: npm run check - - - name: Build Android bundle - if: steps.frontend.outputs.exists == 'true' - run: npx expo export --platform android --output-dir dist