Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 102 additions & 37 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,69 +1,134 @@
name: CI

on:
# ready_for_review is not in the default type set, and without it a draft PR
# turned ready would never start a run.
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
push:
branches: [main]
workflow_dispatch:

permissions:
contents: read

# Cancel superseded runs on the same PR/branch to save CI minutes.
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
cancel-in-progress: true

env:
PYTHON_VERSION: "3.11.15"
UV_VERSION: "0.11.28"
NODE_VERSION: "20.20.2"

jobs:
ci:
name: Minimal CI
backend:
name: Backend (lint, types, tests)
# Skip while the PR is a draft; the ready_for_review event starts the real run.
# Repeated per job because there is no workflow-level `if`. The event_name guard
# comes first so push and workflow_dispatch runs are unaffected.
if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
runs-on: ubuntu-latest
timeout-minutes: 15
defaults:
run:
working-directory: backend
steps:
- uses: actions/checkout@v4

- id: detect
shell: bash
run: |
backend=false
frontend=false
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}

- uses: astral-sh/setup-uv@v6
with:
version: ${{ env.UV_VERSION }}
enable-cache: true
cache-dependency-glob: backend/uv.lock

if [[ -f backend/pyproject.toml && -f backend/uv.lock ]]; then
backend=true
fi
if [[ -f frontend/package-lock.json ]]; then
frontend=true
fi
# Single source of truth: mirrors the local scripts/check.sh gate
# (uv lock --check, ruff check, ruff format --check, mypy, pytest+coverage, alembic heads).
- name: Quality gate
run: bash scripts/check.sh

echo "backend=$backend" >> "$GITHUB_OUTPUT"
echo "frontend=$frontend" >> "$GITHUB_OUTPUT"
- name: Upload coverage report
if: always()
uses: actions/upload-artifact@v4
with:
name: backend-coverage
path: backend/coverage.xml
if-no-files-found: ignore

migrations:
name: Backend (migrations)
if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
runs-on: ubuntu-latest
timeout-minutes: 10
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:
TIMEFLOW_DATABASE_URL: postgresql+psycopg://timeapp:timeapp@127.0.0.1:5432/timeapp
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
if: steps.detect.outputs.backend == 'true'
with:
python-version: 3.11.15
python-version: ${{ env.PYTHON_VERSION }}

- uses: astral-sh/setup-uv@v6
if: steps.detect.outputs.backend == 'true'
with:
version: 0.11.28
version: ${{ env.UV_VERSION }}
enable-cache: true
cache-dependency-glob: backend/uv.lock

- name: Backend checks
if: steps.detect.outputs.backend == 'true'
working-directory: backend
shell: bash
run: |
uv sync --locked --all-groups
uv run ruff check .
uv run pytest
- name: Install dependencies
run: uv sync --locked --all-groups

# Validate the migration chain against a real Postgres, both directions.
- name: Apply migrations (upgrade head)
run: uv run alembic upgrade head

- name: Verify downgrade path (downgrade base)
run: uv run alembic downgrade base

frontend:
name: Frontend (lint, types, build)
if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
runs-on: ubuntu-latest
timeout-minutes: 15
defaults:
run:
working-directory: frontend
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
if: steps.detect.outputs.frontend == 'true'
with:
node-version: 20.20.2
node-version: ${{ env.NODE_VERSION }}
cache: npm
cache-dependency-path: frontend/package-lock.json

- name: Frontend checks
if: steps.detect.outputs.frontend == 'true'
working-directory: frontend
shell: bash
run: |
npm ci
npm run check
npx expo export --platform android --output-dir dist
- name: Install dependencies
run: npm ci

- name: Checks (lint, format, typecheck)
run: npm run check

- name: Export build
run: npx expo export --platform android --output-dir dist
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ __pycache__/
.mypy_cache/
.pytest_cache/
.ruff_cache/
.coverage
coverage.xml
dist/
htmlcov/
9 changes: 9 additions & 0 deletions backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ dev = [
"httpx2>=2,<3",
"mypy>=1.19,<2",
"pytest>=9,<10",
"pytest-cov>=7,<8",
"ruff>=0.15,<1",
]

Expand All @@ -32,6 +33,14 @@ packages = ["src/timeflow"]
testpaths = ["tests"]
addopts = ["-q"]

[tool.coverage.run]
source = ["timeflow"]
branch = true

[tool.coverage.report]
show_missing = true
skip_covered = false

[tool.ruff]
target-version = "py311"
line-length = 100
Expand Down
2 changes: 1 addition & 1 deletion backend/scripts/check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ uv lock --check
uv run ruff check .
uv run ruff format --check .
uv run mypy
uv run pytest
uv run pytest --cov --cov-report=term-missing --cov-report=xml --cov-fail-under=80
uv run alembic heads
63 changes: 63 additions & 0 deletions backend/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading