1+ # .github/workflows/ci.yml
2+ #
3+ # CI/CD pipeline for omni_tool_runtime
4+ #
5+ # Jobs
6+ # ────
7+ # lint – ruff check + format
8+ # typecheck – mypy
9+ # test – pytest + coverage (matrix: 3.11, 3.12, 3.13)
10+ # publish – build sdist/wheel, push to PyPI (on version tag push)
11+ # docker – build & push Docker image (on version tag push)
12+
13+ name : CI/CD
14+
15+ on :
16+ push :
17+ branches : ["main", "master"]
18+ tags : ["v*.*.*"]
19+ pull_request :
20+ branches : ["main", "master"]
21+
22+ # Cancel in-flight runs for the same ref so PRs don't queue up
23+ concurrency :
24+ group : ${{ github.workflow }}-${{ github.ref }}
25+ cancel-in-progress : true
26+
27+ env :
28+ # Keeps pip quiet and ensures outputs are unbuffered
29+ PIP_DISABLE_PIP_VERSION_CHECK : " 1"
30+ PYTHONUNBUFFERED : " 1"
31+
32+ # ──────────────────────────────────────────────────────────────────────────────
33+ # 1. LINT (ruff)
34+ # ──────────────────────────────────────────────────────────────────────────────
35+ jobs :
36+ lint :
37+ name : Lint (ruff)
38+ runs-on : ubuntu-latest
39+
40+ steps :
41+ - uses : actions/checkout@v4
42+
43+ - uses : actions/setup-python@v5
44+ with :
45+ python-version : " 3.12"
46+
47+ - name : Cache pip
48+ uses : actions/cache@v4
49+ with :
50+ path : ~/.cache/pip
51+ key : lint-pip-${{ hashFiles('**/pyproject.toml', '**/requirements*.txt') }}
52+
53+ - name : Install ruff
54+ run : pip install ruff
55+
56+ - name : ruff check (lint)
57+ run : ruff check .
58+
59+ - name : ruff format (check only)
60+ run : ruff format --check .
61+
62+ # ──────────────────────────────────────────────────────────────────────────────
63+ # 2. TYPE-CHECK (mypy)
64+ # ──────────────────────────────────────────────────────────────────────────────
65+ typecheck :
66+ name : Type-check (mypy)
67+ runs-on : ubuntu-latest
68+
69+ steps :
70+ - uses : actions/checkout@v4
71+
72+ - uses : actions/setup-python@v5
73+ with :
74+ python-version : " 3.12"
75+
76+ - name : Cache pip
77+ uses : actions/cache@v4
78+ with :
79+ path : ~/.cache/pip
80+ key : mypy-pip-${{ hashFiles('**/pyproject.toml', '**/requirements*.txt') }}
81+
82+ - name : Install package + mypy
83+ # Install with all extras so mypy can see optional deps (azure, aws)
84+ run : |
85+ pip install -e ".[azure,aws]"
86+ pip install mypy boto3-stubs[s3] azure-storage-blob azure-identity
87+
88+ - name : mypy
89+ run : mypy omni_tool_runtime --ignore-missing-imports
90+
91+ # ──────────────────────────────────────────────────────────────────────────────
92+ # 3. TEST (pytest + coverage matrix)
93+ # ──────────────────────────────────────────────────────────────────────────────
94+ test :
95+ name : Test (Python ${{ matrix.python-version }})
96+ runs-on : ubuntu-latest
97+ needs : [lint, typecheck]
98+
99+ strategy :
100+ fail-fast : false
101+ matrix :
102+ python-version : ["3.11", "3.12", "3.13"]
103+
104+ steps :
105+ - uses : actions/checkout@v4
106+
107+ - uses : actions/setup-python@v5
108+ with :
109+ python-version : ${{ matrix.python-version }}
110+
111+ - name : Cache pip
112+ uses : actions/cache@v4
113+ with :
114+ path : ~/.cache/pip
115+ key : test-pip-${{ matrix.python-version }}-${{ hashFiles('**/pyproject.toml', '**/requirements*.txt') }}
116+
117+ - name : Install package + test deps
118+ run : pip install -e ".[dev]"
119+
120+ - name : Run tests with coverage
121+ run : |
122+ pytest \
123+ --cov=omni_tool_runtime \
124+ --cov-report=term-missing \
125+ --cov-report=xml:coverage.xml \
126+ --cov-fail-under=85 \
127+ -v
128+
129+ - name : Upload coverage report
130+ # Only upload once (Python 3.12) to avoid duplicate reports
131+ if : matrix.python-version == '3.12'
132+ uses : actions/upload-artifact@v4
133+ with :
134+ name : coverage-report
135+ path : coverage.xml
136+ retention-days : 7
137+
138+ # ──────────────────────────────────────────────────────────────────────────────
139+ # 4. PUBLISH TO PyPI (only on version tags, e.g. v1.2.3)
140+ # ──────────────────────────────────────────────────────────────────────────────
141+ publish :
142+ name : Publish to PyPI
143+ runs-on : ubuntu-latest
144+ needs : [test]
145+ if : startsWith(github.ref, 'refs/tags/v')
146+
147+ # Trusted publishing — no API token stored in secrets
148+ permissions :
149+ id-token : write # required for OIDC PyPI trusted publishing
150+ contents : read
151+
152+ environment :
153+ name : pypi
154+ url : https://pypi.org/p/omnibioai-tool-runtime
155+
156+ steps :
157+ - uses : actions/checkout@v4
158+
159+ - uses : actions/setup-python@v5
160+ with :
161+ python-version : " 3.12"
162+
163+ - name : Install build tools
164+ run : pip install build
165+
166+ - name : Build sdist + wheel
167+ run : python -m build
168+
169+ - name : Verify dist contents
170+ run : ls -lh dist/
171+
172+ - name : Publish to PyPI
173+ uses : pypa/gh-action-pypi-publish@release/v1
174+ # Uses OIDC trusted publishing — configure the PyPI project to trust
175+ # this repo's GitHub Actions environment ("pypi") before first release.
176+ # No PYPI_API_TOKEN secret needed.
177+
178+ # ──────────────────────────────────────────────────────────────────────────────
179+ # 5. DOCKER (only on version tags)
180+ # ──────────────────────────────────────────────────────────────────────────────
181+ docker :
182+ name : Build & Push Docker image
183+ runs-on : ubuntu-latest
184+ needs : [test]
185+ if : startsWith(github.ref, 'refs/tags/v')
186+
187+ permissions :
188+ contents : read
189+ packages : write # required to push to GHCR
190+
191+ steps :
192+ - uses : actions/checkout@v4
193+
194+ - name : Extract version from tag
195+ id : meta
196+ uses : docker/metadata-action@v5
197+ with :
198+ images : |
199+ ghcr.io/${{ github.repository }}
200+ tags : |
201+ # tag the image with the git tag (v1.2.3 → 1.2.3)
202+ type=semver,pattern={{version}}
203+ # also push a "latest" tag
204+ type=raw,value=latest
205+
206+ - name : Set up Docker Buildx
207+ uses : docker/setup-buildx-action@v3
208+
209+ - name : Log in to GitHub Container Registry
210+ uses : docker/login-action@v3
211+ with :
212+ registry : ghcr.io
213+ username : ${{ github.actor }}
214+ password : ${{ secrets.GITHUB_TOKEN }}
215+
216+ - name : Build and push
217+ uses : docker/build-push-action@v5
218+ with :
219+ context : .
220+ push : true
221+ tags : ${{ steps.meta.outputs.tags }}
222+ labels : ${{ steps.meta.outputs.labels }}
223+ # Layer cache speeds up rebuilds
224+ cache-from : type=gha
225+ cache-to : type=gha,mode=max
0 commit comments