|
1 | | -name: CI |
2 | | - |
3 | | -on: |
4 | | - push: |
5 | | - branches: [main, master, develop] |
6 | | - pull_request: |
7 | | - branches: [main, master, develop] |
8 | | - |
9 | | -permissions: |
10 | | - contents: read |
11 | | - |
12 | | -concurrency: |
13 | | - group: ${{ github.workflow }}-${{ github.ref }} |
14 | | - cancel-in-progress: true |
15 | | - |
16 | | -jobs: |
17 | | - detect: |
18 | | - name: Detect Languages |
19 | | - runs-on: ubuntu-latest |
20 | | - outputs: |
21 | | - has_rust: ${{ steps.detect.outputs.has_rust }} |
22 | | - has_deny: ${{ steps.detect.outputs.has_deny }} |
23 | | - has_python: ${{ steps.detect.outputs.has_python }} |
24 | | - has_go: ${{ steps.detect.outputs.has_go }} |
25 | | - has_typescript: ${{ steps.detect.outputs.has_typescript }} |
26 | | - has_security: ${{ steps.detect.outputs.has_security }} |
27 | | - has_trunk: ${{ steps.detect.outputs.has_trunk }} |
28 | | - steps: |
29 | | - - uses: actions/checkout@v4 |
30 | | - with: |
31 | | - fetch-depth: 1 |
32 | | - |
33 | | - - id: detect |
34 | | - run: | |
35 | | - set +e |
36 | | - # Initialize all outputs to false (critical: every output must be set) |
37 | | - echo "has_rust=false" >> $GITHUB_OUTPUT |
38 | | - echo "has_deny=false" >> $GITHUB_OUTPUT |
39 | | - echo "has_python=false" >> $GITHUB_OUTPUT |
40 | | - echo "has_go=false" >> $GITHUB_OUTPUT |
41 | | - echo "has_typescript=false" >> $GITHUB_OUTPUT |
42 | | - echo "has_security=false" >> $GITHUB_OUTPUT |
43 | | - echo "has_trunk=false" >> $GITHUB_OUTPUT |
44 | | -
|
45 | | - # Rust: needs Cargo.toml AND deny.toml |
46 | | - if [ -f "Cargo.toml" ] || [ -f "**/Cargo.toml" ]; then |
47 | | - echo "has_rust=true" >> $GITHUB_OUTPUT |
48 | | - if [ -f "deny.toml" ]; then |
49 | | - echo "has_deny=true" >> $GITHUB_OUTPUT |
50 | | - fi |
51 | | - fi |
52 | | -
|
53 | | - # Python: needs pyproject.toml, setup.py, requirements.txt, or *.py |
54 | | - if [ -f "pyproject.toml" ] || [ -f "setup.py" ] || [ -f "requirements.txt" ] || find . -maxdepth 3 -name "*.py" -type f 2>/dev/null | head -1 | grep -q .; then |
55 | | - echo "has_python=true" >> $GITHUB_OUTPUT |
56 | | - fi |
57 | | -
|
58 | | - # Go: needs go.mod |
59 | | - if [ -f "go.mod" ]; then |
60 | | - echo "has_go=true" >> $GITHUB_OUTPUT |
61 | | - fi |
62 | | -
|
63 | | - # TypeScript / JS: needs package.json (with type=module or tsconfig or js files) |
64 | | - if [ -f "package.json" ]; then |
65 | | - echo "has_typescript=true" >> $GITHUB_OUTPUT |
66 | | - fi |
67 | | -
|
68 | | - # Security: always run (bandit/gitleaks/dependency-review) |
69 | | - echo "has_security=true" >> $GITHUB_OUTPUT |
70 | | -
|
71 | | - # Trunk: only if trunk.yaml exists |
72 | | - if [ -f "trunk.yaml" ]; then |
73 | | - echo "has_trunk=true" >> $GITHUB_OUTPUT |
74 | | - fi |
75 | | -
|
76 | | - echo "" |
77 | | - echo "=== Detected ===" |
78 | | - echo "rust=${{ steps.detect.outputs.has_rust }} deny=${{ steps.detect.outputs.has_deny }} python=${{ steps.detect.outputs.has_python }} go=${{ steps.detect.outputs.has_go }} typescript=${{ steps.detect.outputs.has_typescript }} security=${{ steps.detect.outputs.has_security }} trunk=${{ steps.detect.outputs.has_trunk }}" |
79 | | -
|
80 | | - rust: |
81 | | - name: Rust |
82 | | - needs: detect |
83 | | - if: needs.detect.outputs.has_rust == 'true' |
84 | | - runs-on: ubuntu-latest |
85 | | - steps: |
86 | | - - uses: actions/checkout@v4 |
87 | | - - uses: dtolnay/rust-toolchain@stable |
88 | | - - uses: Swatinem/rust-cache@v2 |
89 | | - - name: cargo fmt --check |
90 | | - run: | |
91 | | - cargo fmt --all -- --check |
92 | | - - name: cargo clippy |
93 | | - run: | |
94 | | - cargo clippy --all-targets -- -D warnings |
95 | | - - name: cargo test |
96 | | - run: | |
97 | | - cargo test --workspace --no-fail-fast |
98 | | -
|
99 | | - cargo-deny: |
100 | | - name: Cargo Deny (Advisories + Licenses) |
101 | | - needs: detect |
102 | | - if: needs.detect.outputs.has_deny == 'true' |
103 | | - runs-on: ubuntu-latest |
104 | | - steps: |
105 | | - - uses: actions/checkout@v4 |
106 | | - - uses: dtolnay/rust-toolchain@stable |
107 | | - - name: cargo-deny check |
108 | | - # v2 (SHA-pinned, same as rust-ci.yml/cargo-deny.yml): v1 bundles |
109 | | - # cargo-deny 0.14.21, which cannot parse the current RustSec advisory |
110 | | - # database (TOML parse error on RUSTSEC-2026-0109). |
111 | | - uses: EmbarkStudios/cargo-deny-action@3c6349835b2b7b196a839186cb8b78e02f7b5f25 # v2 |
112 | | - with: |
113 | | - arguments: --all-features |
114 | | - |
115 | | - python: |
116 | | - name: Python |
117 | | - needs: detect |
118 | | - if: needs.detect.outputs.has_python == 'true' |
119 | | - runs-on: ubuntu-latest |
120 | | - steps: |
121 | | - - uses: actions/checkout@v4 |
122 | | - - uses: actions/setup-python@v5 |
123 | | - with: |
124 | | - python-version: '3.11' |
125 | | - - name: Install ruff |
126 | | - run: pip install ruff |
127 | | - - name: ruff check |
128 | | - run: ruff check . |
129 | | - - name: ruff format --check |
130 | | - run: ruff format --check . |
131 | | - |
132 | | - go: |
133 | | - name: Go |
134 | | - needs: detect |
135 | | - if: needs.detect.outputs.has_go == 'true' |
136 | | - runs-on: ubuntu-latest |
137 | | - steps: |
138 | | - - uses: actions/checkout@v4 |
139 | | - - uses: actions/setup-go@v5 |
140 | | - with: |
141 | | - go-version: '1.22' |
142 | | - - name: gofmt check |
143 | | - run: gofmt -l . |
144 | | - - name: go vet |
145 | | - run: go vet ./... |
146 | | - - name: go test |
147 | | - run: go test ./... |
148 | | - |
149 | | - typescript: |
150 | | - name: TS/JS |
151 | | - needs: detect |
152 | | - if: needs.detect.outputs.has_typescript == 'true' |
153 | | - runs-on: ubuntu-latest |
154 | | - steps: |
155 | | - - uses: actions/checkout@v4 |
156 | | - - name: Setup pnpm |
157 | | - uses: pnpm/action-setup@a8198c4bff370c8506180b035930dea56dbd5288 # v5 |
158 | | - with: |
159 | | - run_install: false |
160 | | - - name: Setup Node.js |
161 | | - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 |
162 | | - with: |
163 | | - node-version: 22 |
164 | | - cache: pnpm |
165 | | - - name: Install dependencies |
166 | | - run: pnpm install --frozen-lockfile |
167 | | - - name: lint |
168 | | - run: | |
169 | | - if [ -f package.json ] && grep -q '"lint"' package.json; then |
170 | | - pnpm run lint |
171 | | - fi |
172 | | - - name: test |
173 | | - run: | |
174 | | - if [ -f package.json ] && grep -q '"test"' package.json; then |
175 | | - pnpm run test |
176 | | - fi |
177 | | -
|
178 | | - security: |
179 | | - name: Security Scan |
180 | | - needs: detect |
181 | | - if: needs.detect.outputs.has_security == 'true' |
182 | | - runs-on: ubuntu-latest |
183 | | - steps: |
184 | | - - uses: actions/checkout@v4 |
185 | | - with: |
186 | | - # gitleaks-action scans the PR commit range on pull_request events; |
187 | | - # the default depth-1 checkout lacks the parent commit and makes the |
188 | | - # git log range fail ("stderr is not empty"). |
189 | | - fetch-depth: 0 |
190 | | - - name: gitleaks |
191 | | - uses: gitleaks/gitleaks-action@v2 |
192 | | - env: |
193 | | - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
194 | | - |
195 | | - dep-review: |
196 | | - name: Dependency Review |
197 | | - if: github.event_name == 'pull_request' |
198 | | - runs-on: ubuntu-latest |
199 | | - steps: |
200 | | - - uses: actions/checkout@v4 |
201 | | - - uses: actions/dependency-review-action@v4 |
202 | | - with: |
203 | | - fail-on-severity: low |
204 | | - |
205 | | - lint: |
206 | | - name: ci / lint |
207 | | - if: always() |
208 | | - needs: [detect, rust, cargo-deny, python, go, typescript, security, dep-review] |
209 | | - runs-on: ubuntu-latest |
210 | | - steps: |
211 | | - - name: Aggregate lint gate |
212 | | - run: | |
213 | | - failed=0 |
214 | | - for pair in \ |
215 | | - "rust:${{ needs.rust.result }}" \ |
216 | | - "cargo-deny:${{ needs.cargo-deny.result }}" \ |
217 | | - "python:${{ needs.python.result }}" \ |
218 | | - "go:${{ needs.go.result }}" \ |
219 | | - "typescript:${{ needs.typescript.result }}" \ |
220 | | - "security:${{ needs.security.result }}" \ |
221 | | - "dep-review:${{ needs.dep-review.result }}" \ |
222 | | - ; do |
223 | | - name="${pair%%:*}" |
224 | | - result="${pair#*:}" |
225 | | - if [ "$result" = "failure" ] || [ "$result" = "cancelled" ]; then |
226 | | - echo " ✗ $name: $result" |
227 | | - failed=$((failed + 1)) |
228 | | - elif [ "$result" = "success" ] || [ "$result" = "skipped" ]; then |
229 | | - echo " ✓ $name: $result" |
230 | | - else |
231 | | - echo " ? $name: $result" |
232 | | - fi |
233 | | - done |
234 | | - if [ "$failed" -gt 0 ]; then |
235 | | - echo "" |
236 | | - echo "❌ $failed lint check(s) failed" |
237 | | - exit 1 |
238 | | - fi |
239 | | - echo "" |
240 | | - echo "✅ All lint checks passed (or were skipped)" |
241 | | -
|
242 | | - test: |
243 | | - name: ci / test |
244 | | - if: always() |
245 | | - needs: [lint] |
246 | | - runs-on: ubuntu-latest |
247 | | - steps: |
248 | | - - name: Test gate |
249 | | - run: | |
250 | | - # Test gate is currently combined with lint |
251 | | - echo "✅ All test stages passed (gated via ci / lint)" |
0 commit comments