75657e7
commit 75657e7
2 files changed
+265 -114
Showing 2 changed files+265-114
▼.github/workflows/ci.yml
+199-36
| @@ -2,7 +2,7 @@ name: CI | ||
2 | 2 | |
3 | 3 | on: |
4 | 4 | push: |
5 | - branches: [main] | |
5 | + branches: [main, initial-setup] | |
6 | 6 | tags: ["v*"] |
7 | 7 | pull_request: |
8 | 8 | branches: [main] |
| @@ -12,6 +12,10 @@ env: | ||
12 | 12 | RUSTFLAGS: "-D warnings" |
13 | 13 | |
14 | 14 | jobs: |
15 | + # ============================================================ | |
16 | + # Stage 1: Code Quality & Build | |
17 | + # ============================================================ | |
18 | + | |
15 | 19 | check: |
16 | 20 | name: Build & Lint |
17 | 21 | runs-on: ubuntu-latest |
| @@ -51,8 +55,12 @@ jobs: | ||
51 | 55 | - name: Test |
52 | 56 | run: cargo test --workspace --all-features |
53 | 57 | |
58 | + # ============================================================ | |
59 | + # Stage 2: Memory Safety & Security | |
60 | + # ============================================================ | |
61 | + | |
54 | 62 | miri: |
55 | - name: Miri (justapi-core) | |
63 | + name: Miri (Memory Safety) | |
56 | 64 | runs-on: ubuntu-latest |
57 | 65 | steps: |
58 | 66 | - uses: actions/checkout@v4 |
| @@ -77,8 +85,45 @@ jobs: | ||
77 | 85 | run: cargo +nightly miri test -p justapi-core |
78 | 86 | env: |
79 | 87 | MIRIFLAGS: "-Zmiri-disable-isolation" |
88 | + | |
89 | + audit: | |
90 | + name: Security Audit | |
91 | + runs-on: ubuntu-latest | |
92 | + steps: | |
93 | + - uses: actions/checkout@v4 | |
94 | + | |
95 | + - name: Install cargo-audit | |
96 | + run: cargo install cargo-audit --locked | |
97 | + | |
98 | + - name: Cache cargo registry | |
99 | + uses: actions/cache@v4 | |
100 | + with: | |
101 | + path: | | |
102 | + ~/.cargo/registry | |
103 | + ~/.cargo/git | |
104 | + key: ${{ runner.os }}-audit-${{ hashFiles('**/Cargo.lock') }} | |
105 | + | |
106 | + - name: Run audit | |
107 | + run: cargo audit | |
108 | + | |
109 | + deny: | |
110 | + name: License & Dependency Check | |
111 | + runs-on: ubuntu-latest | |
112 | + steps: | |
113 | + - uses: actions/checkout@v4 | |
114 | + | |
115 | + - name: Install cargo-deny | |
116 | + uses: EmbarkStudios/cargo-deny-action@v2 | |
117 | + with: | |
118 | + command: check | |
119 | + arguments: --all-features | |
120 | + | |
121 | + # ============================================================ | |
122 | + # Stage 3: Python Integration Tests | |
123 | + # ============================================================ | |
124 | + | |
80 | 125 | pytest: |
81 | - name: Pytest (justapi-py) | |
126 | + name: Pytest (Python Integration) | |
82 | 127 | runs-on: ubuntu-latest |
83 | 128 | steps: |
84 | 129 | - uses: actions/checkout@v4 |
| @@ -97,10 +142,10 @@ jobs: | ||
97 | 142 | |
98 | 143 | - name: Run pytest |
99 | 144 | run: | |
100 | - uv run -- PYTHONPATH=./crates/justapi-py/python pytest | |
145 | + uv run -- PYTHONPATH=./crates/justapi-py/python pytest -v | |
101 | 146 | |
102 | 147 | pytest-freethreaded: |
103 | - name: Pytest (free-threaded / no GIL) | |
148 | + name: Pytest (Free-threaded Python) | |
104 | 149 | runs-on: ubuntu-latest |
105 | 150 | steps: |
106 | 151 | - uses: actions/checkout@v4 |
| @@ -124,10 +169,90 @@ jobs: | ||
124 | 169 | - name: Run pytest with the GIL disabled |
125 | 170 | run: | |
126 | 171 | . .venv-ft/bin/activate |
127 | - PYTHON_GIL=0 PYTHONPATH=./crates/justapi-py/python pytest | |
172 | + PYTHON_GIL=0 PYTHONPATH=./crates/justapi-py/python pytest -v | |
173 | + | |
174 | + # ============================================================ | |
175 | + # Stage 4: Integration Tests | |
176 | + # ============================================================ | |
177 | + | |
178 | + integration-tests: | |
179 | + name: Integration Tests | |
180 | + runs-on: ubuntu-latest | |
181 | + needs: [check, pytest] | |
182 | + steps: | |
183 | + - uses: actions/checkout@v4 | |
184 | + | |
185 | + - name: Install Rust toolchain | |
186 | + uses: dtolnay/rust-toolchain@stable | |
187 | + | |
188 | + - name: Install Python (for PyO3) | |
189 | + uses: actions/setup-python@v5 | |
190 | + with: | |
191 | + python-version: "3.12" | |
192 | + | |
193 | + - name: Install uv | |
194 | + uses: astral-sh/setup-uv@v5 | |
195 | + | |
196 | + - name: Cache cargo registry | |
197 | + uses: actions/cache@v4 | |
198 | + with: | |
199 | + path: | | |
200 | + ~/.cargo/registry | |
201 | + ~/.cargo/git | |
202 | + target | |
203 | + key: ${{ runner.os }}-integration-${{ hashFiles('**/Cargo.lock') }} | |
204 | + restore-keys: | | |
205 | + ${{ runner.os }}-integration- | |
206 | + | |
207 | + - name: Build justapi | |
208 | + run: cargo build --release -p justapi-cli --features tls,compression | |
209 | + | |
210 | + - name: Build Python wheel | |
211 | + run: maturin build -m crates/justapi-py/Cargo.toml --release -o wheels/ | |
212 | + | |
213 | + - name: Install wheel | |
214 | + run: pip install wheels/*.whl --break-system-packages | |
215 | + | |
216 | + - name: Start server | |
217 | + run: | | |
218 | + justapi serve --addr 127.0.0.1:8080 & | |
219 | + sleep 3 | |
220 | + | |
221 | + - name: Health check | |
222 | + run: | | |
223 | + curl -f http://127.0.0.1:8080/health || exit 1 | |
224 | + curl -f http://127.0.0.1:8080/live || exit 1 | |
225 | + curl -f http://127.0.0.1:8080/ready || exit 1 | |
226 | + | |
227 | + - name: Test endpoints | |
228 | + run: | | |
229 | + # Hello world | |
230 | + curl -f http://127.0.0.1:8080/ || exit 1 | |
231 | + | |
232 | + # Metrics | |
233 | + curl -f http://127.0.0.1:8080/metrics || exit 1 | |
234 | + | |
235 | + # OpenAPI | |
236 | + curl -f http://127.0.0.1:8080/openapi.json || exit 1 | |
237 | + | |
238 | + - name: Test WebSocket | |
239 | + run: | | |
240 | + # Install websocat | |
241 | + cargo install websocat | |
242 | + | |
243 | + # Test WebSocket echo | |
244 | + echo "hello" | websocat -t ws://127.0.0.1:8080/ws || true | |
245 | + | |
246 | + - name: Stop server | |
247 | + if: always() | |
248 | + run: pkill -f "justapi serve" || true | |
249 | + | |
250 | + # ============================================================ | |
251 | + # Stage 5: Code Quality Gates | |
252 | + # ============================================================ | |
128 | 253 | |
129 | 254 | verify-rust-first: |
130 | - name: Verify Rust-first mandate (AGENTS.md §2) | |
255 | + name: Verify Rust-first Mandate | |
131 | 256 | runs-on: ubuntu-latest |
132 | 257 | steps: |
133 | 258 | - uses: actions/checkout@v4 |
| @@ -146,37 +271,33 @@ jobs: | ||
146 | 271 | fi |
147 | 272 | echo "OK: no Python-side scheduling/thread-pool classes found." |
148 | 273 | |
149 | - audit: | |
150 | - name: Security Audit (cargo-audit) | |
151 | - runs-on: ubuntu-latest | |
152 | - steps: | |
153 | - - uses: actions/checkout@v4 | |
154 | - | |
155 | - - name: Install cargo-audit | |
156 | - run: cargo install cargo-audit --locked | |
157 | - | |
158 | - - name: Cache cargo registry | |
159 | - uses: actions/cache@v4 | |
160 | - with: | |
161 | - path: | | |
162 | - ~/.cargo/registry | |
163 | - ~/.cargo/git | |
164 | - key: ${{ runner.os }}-audit-${{ hashFiles('**/Cargo.lock') }} | |
165 | - | |
166 | - - name: Run audit | |
167 | - run: cargo audit | |
168 | - | |
169 | - deny: | |
170 | - name: License & Dependency Check (cargo-deny) | |
274 | + verify-no-unwrap-on-hot-path: | |
275 | + name: Verify No unwrap() on Hot Paths | |
171 | 276 | runs-on: ubuntu-latest |
172 | 277 | steps: |
173 | 278 | - uses: actions/checkout@v4 |
279 | + - name: Check for unwrap() on hot paths | |
280 | + run: | | |
281 | + echo "Checking for unwrap() on request hot paths..." | |
282 | + # Check for unwrap() in handler dispatch code (excluding tests and init) | |
283 | + matches=$(grep -rn "\.unwrap()" crates/justapi-core/src/server/ \ | |
284 | + --include="*.rs" \ | |
285 | + | grep -v "test" \ | |
286 | + | grep -v "expect" \ | |
287 | + | grep -v "LazyLock" \ | |
288 | + | grep -v "unwrap_or" \ | |
289 | + | grep -v "unwrap_or_else" \ | |
290 | + | grep -v "unwrap_or_default" \ | |
291 | + || true) | |
292 | + if [ -n "$matches" ]; then | |
293 | + echo "::warning::Potential unwrap() on hot path:" | |
294 | + echo "$matches" | |
295 | + fi | |
296 | + echo "Check complete." | |
174 | 297 | |
175 | - - name: Install cargo-deny | |
176 | - uses: EmbarkStudios/cargo-deny-action@v2 | |
177 | - with: | |
178 | - command: check | |
179 | - arguments: --all-features | |
298 | + # ============================================================ | |
299 | + # Stage 6: Benchmark | |
300 | + # ============================================================ | |
180 | 301 | |
181 | 302 | benchmark: |
182 | 303 | name: Benchmark Regression Gate |
| @@ -239,10 +360,14 @@ jobs: | ||
239 | 360 | |
240 | 361 | echo "Benchmark check passed" |
241 | 362 | |
363 | + # ============================================================ | |
364 | + # Stage 7: Docker Build | |
365 | + # ============================================================ | |
366 | + | |
242 | 367 | docker: |
243 | - name: Build & Push Docker Image | |
368 | + name: Build Docker Image | |
244 | 369 | runs-on: ubuntu-latest |
245 | - needs: [check] | |
370 | + needs: [check, pytest, integration-tests] | |
246 | 371 | if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) |
247 | 372 | steps: |
248 | 373 | - uses: actions/checkout@v4 |
| @@ -277,3 +402,41 @@ jobs: | ||
277 | 402 | labels: ${{ steps.meta.outputs.labels }} |
278 | 403 | cache-from: type=gha |
279 | 404 | cache-to: type=gha,mode=max |
405 | + | |
406 | + # ============================================================ | |
407 | + # Stage 8: PyPI Publish (on release) | |
408 | + # ============================================================ | |
409 | + | |
410 | + pypi-publish: | |
411 | + name: Publish to PyPI | |
412 | + runs-on: ubuntu-latest | |
413 | + needs: [check, pytest, integration-tests, docker] | |
414 | + if: startsWith(github.ref, 'refs/tags/v') | |
415 | + environment: pypi | |
416 | + permissions: | |
417 | + id-token: write | |
418 | + steps: | |
419 | + - uses: actions/checkout@v4 | |
420 | + | |
421 | + - name: Install Rust toolchain | |
422 | + uses: dtolnay/rust-toolchain@stable | |
423 | + | |
424 | + - name: Install Python (for PyO3) | |
425 | + uses: actions/setup-python@v5 | |
426 | + with: | |
427 | + python-version: "3.12" | |
428 | + | |
429 | + - name: Build wheel | |
430 | + run: | | |
431 | + pip install maturin | |
432 | + maturin build --release -m crates/justapi-py/Cargo.toml -o wheels/ | |
433 | + | |
434 | + - name: Verify wheel | |
435 | + run: | | |
436 | + pip install wheels/*.whl --break-system-packages | |
437 | + python -c "import justapi; print(f'JustAPI {justapi.__version__} installed successfully')" | |
438 | + | |
439 | + - name: Publish to PyPI | |
440 | + uses: pypa/gh-action-pypi-publish@release/v1 | |
441 | + with: | |
442 | + packages-dir: wheels/ | |
280 | 443 | |
▼.github/workflows/publish.yml
+66-78
| @@ -4,102 +4,90 @@ on: | ||
4 | 4 | release: |
5 | 5 | types: [published] |
6 | 6 | workflow_dispatch: |
7 | - | |
8 | -permissions: | |
9 | - contents: read | |
10 | - id-token: write | |
7 | + inputs: | |
8 | + version: | |
9 | + description: 'Version to publish (e.g., 2.0.8)' | |
10 | + required: true | |
11 | 11 | |
12 | 12 | jobs: |
13 | - check-version: | |
14 | - name: Check Version | |
13 | + build: | |
14 | + name: Build Wheel | |
15 | 15 | runs-on: ubuntu-latest |
16 | - outputs: | |
17 | - version: ${{ steps.version.outputs.version }} | |
18 | 16 | steps: |
19 | 17 | - uses: actions/checkout@v4 |
20 | - - id: version | |
21 | - run: echo "version=$(grep '^version = ' crates/justapi-py/Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/')" >> "$GITHUB_OUTPUT" | |
22 | 18 | |
23 | - test-rust: | |
24 | - name: Cargo Test & Lint | |
25 | - runs-on: ubuntu-latest | |
26 | - needs: check-version | |
27 | - steps: | |
28 | - - uses: actions/checkout@v4 | |
29 | - - uses: dtolnay/rust-toolchain@stable | |
30 | - with: | |
31 | - components: clippy, rustfmt | |
32 | - - name: Install cargo-deny | |
33 | - uses: taiki-e/install-action@v2 | |
34 | - with: | |
35 | - tool: cargo-deny | |
36 | - - name: Security & License Check | |
37 | - run: cargo deny check | |
38 | - - name: Run Tests | |
39 | - run: cargo test --workspace | |
40 | - - name: Clippy & Formatting | |
41 | - run: | | |
42 | - cargo clippy --workspace --tests -- -D warnings | |
43 | - cargo fmt --check | |
19 | + - name: Install Rust toolchain | |
20 | + uses: dtolnay/rust-toolchain@stable | |
44 | 21 | |
45 | - build-wheels: | |
46 | - name: Build (${{ matrix.target }}) | |
47 | - runs-on: ${{ matrix.os }} | |
48 | - needs: test-rust | |
49 | - strategy: | |
50 | - fail-fast: false | |
51 | - matrix: | |
52 | - include: | |
53 | - - os: ubuntu-latest | |
54 | - target: x86_64 | |
55 | - args: --manylinux 2_28 | |
56 | - - os: ubuntu-latest | |
57 | - target: aarch64 | |
58 | - args: --manylinux 2_28 | |
59 | - - os: macos-latest | |
60 | - target: x86_64 | |
61 | - - os: macos-latest | |
62 | - target: aarch64 | |
63 | - - os: windows-latest | |
64 | - target: x64 | |
65 | - steps: | |
66 | - - uses: actions/checkout@v4 | |
67 | - - uses: astral-sh/setup-uv@v5 | |
22 | + - name: Install Python (for PyO3) | |
23 | + uses: actions/setup-python@v5 | |
68 | 24 | with: |
69 | - python-version: '3.12' | |
70 | - enable-cache: true | |
71 | - cache-dependency-glob: crates/justapi-py/pyproject.toml | |
72 | - - uses: PyO3/maturin-action@v1 | |
25 | + python-version: "3.12" | |
26 | + | |
27 | + - name: Cache cargo registry | |
28 | + uses: actions/cache@v4 | |
73 | 29 | with: |
74 | - target: ${{ matrix.target }} | |
75 | - args: --release --out dist ${{ matrix.args }} | |
76 | - manifest-path: crates/justapi-py/Cargo.toml | |
77 | - - name: Smoke test | |
78 | - shell: bash | |
30 | + path: | | |
31 | + ~/.cargo/registry | |
32 | + ~/.cargo/git | |
33 | + target | |
34 | + key: ${{ runner.os }}-publish-${{ hashFiles('**/Cargo.lock') }} | |
35 | + restore-keys: | | |
36 | + ${{ runner.os }}-publish- | |
37 | + | |
38 | + - name: Install maturin | |
39 | + run: pip install maturin | |
40 | + | |
41 | + - name: Build wheel | |
42 | + run: maturin build --release -m crates/justapi-py/Cargo.toml -o wheels/ | |
43 | + | |
44 | + - name: Verify wheel | |
79 | 45 | run: | |
80 | - uv pip install dist/*.whl | |
81 | - python -c "import justapi; print(justapi.__version__)" | |
82 | - - uses: actions/upload-artifact@v4 | |
46 | + pip install wheels/*.whl --break-system-packages | |
47 | + python -c "import justapi; print(f'JustAPI installed successfully')" | |
48 | + | |
49 | + - name: Upload wheel artifact | |
50 | + uses: actions/upload-artifact@v4 | |
83 | 51 | with: |
84 | - name: wheels-${{ matrix.target }} | |
85 | - path: dist/*.whl | |
52 | + name: wheels | |
53 | + path: wheels/*.whl | |
86 | 54 | |
87 | - publish: | |
55 | + publish-pypi: | |
88 | 56 | name: Publish to PyPI |
89 | 57 | runs-on: ubuntu-latest |
90 | - needs: build-wheels | |
58 | + needs: [build] | |
91 | 59 | environment: pypi |
92 | 60 | permissions: |
93 | 61 | id-token: write |
62 | + steps: | |
63 | + - name: Download wheel artifact | |
64 | + uses: actions/download-artifact@v4 | |
65 | + with: | |
66 | + name: wheels | |
67 | + path: wheels/ | |
68 | + | |
69 | + - name: Publish to PyPI | |
70 | + uses: pypa/gh-action-pypi-publish@release/v1 | |
71 | + with: | |
72 | + packages-dir: wheels/ | |
73 | + | |
74 | + create-github-release: | |
75 | + name: Create GitHub Release | |
76 | + runs-on: ubuntu-latest | |
77 | + needs: [publish-pypi] | |
78 | + permissions: | |
79 | + contents: write | |
94 | 80 | steps: |
95 | 81 | - uses: actions/checkout@v4 |
96 | - - uses: astral-sh/setup-uv@v5 | |
82 | + | |
83 | + - name: Download wheel artifact | |
84 | + uses: actions/download-artifact@v4 | |
97 | 85 | with: |
98 | - python-version: '3.12' | |
99 | - - uses: actions/download-artifact@v4 | |
86 | + name: wheels | |
87 | + path: wheels/ | |
88 | + | |
89 | + - name: Create GitHub Release | |
90 | + uses: softprops/action-gh-release@v2 | |
100 | 91 | with: |
101 | - pattern: wheels-* | |
102 | - merge-multiple: true | |
103 | - path: dist | |
104 | - - name: Publish | |
105 | - run: uv publish --package-dir crates/justapi-py --trusted-publishing always --verbose | |
92 | + files: wheels/*.whl | |
93 | + generate_release_notes: true | |
106 | 94 | |