11name : CI
22
33on :
4- # Run on every PR so the required checks always report a status (a required
5- # check that never runs blocks merges forever). Path scoping is applied inside
6- # each job via its "Detect changes" step, not with top-level path filters.
74 pull_request :
85 push :
96 branches : [main]
107
118jobs :
12- # ── Frontend: bun workspace (lockfile at the repo root), biome, build, tests ──
13- frontend :
9+ # ── One cheap job computes which areas a PR touches; the heavy jobs gate on its
10+ # outputs (`needs` + job-level `if`), so an irrelevant area's job never starts
11+ # (no runner spun up — notably the Windows one). A skipped job doesn't satisfy a
12+ # required status check, so the single required check is the `ci` aggregator at
13+ # the bottom, not the individual jobs. ──
14+ changes :
1415 runs-on : ubuntu-latest
16+ outputs :
17+ frontend : ${{ steps.detect.outputs.frontend }}
18+ rust : ${{ steps.detect.outputs.rust }}
19+ desktop : ${{ steps.detect.outputs.desktop }}
1520 steps :
1621 - uses : actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
1722 with :
1823 fetch-depth : 0
1924
2025 - name : Detect changes
21- id : changes
26+ id : detect
2227 run : |
2328 if [ "${{ github.event_name }}" = "pull_request" ]; then
2429 base="${{ github.event.pull_request.base.sha }}"
@@ -27,47 +32,62 @@ jobs:
2732 base="${{ github.event.before }}"
2833 head="${{ github.sha }}"
2934 fi
35+ # No usable base (first push, force-push, shallow clone) → run everything.
3036 if [ -z "$base" ] || ! git cat-file -e "$base" 2>/dev/null; then
31- echo "relevant=true" >> "$GITHUB_OUTPUT"; exit 0
32- fi
33- if git diff --name-only "$base" "$head" | grep -Eq '^(frontend/|module/|scripts/|package\.json|bun\.lock|biome\.json)'; then
34- echo "relevant =true" >> "$GITHUB_OUTPUT "
35- else
36- echo "relevant=false" >> "$GITHUB_OUTPUT"
37+ {
38+ echo "frontend=true"
39+ echo "rust=true"
40+ echo "desktop =true"
41+ } >> "$GITHUB_OUTPUT"
42+ exit 0
3743 fi
44+ files=$(git diff --name-only "$base" "$head")
45+ match() { printf '%s\n' "$files" | grep -Eq "$1"; }
46+ fe=false; rs=false; dk=false
47+ match '^(frontend/|module/|scripts/|package\.json|bun\.lock|biome\.json)' && fe=true
48+ match '^(crates/|src-tauri/|Cargo\.(toml|lock)|flake\.(nix|lock))' && rs=true
49+ match '^(crates/|src-tauri/|frontend/|Cargo\.(toml|lock))' && dk=true
50+ {
51+ echo "frontend=$fe"
52+ echo "rust=$rs"
53+ echo "desktop=$dk"
54+ } >> "$GITHUB_OUTPUT"
55+
56+ # ── Frontend: bun workspace (lockfile at the repo root), biome, build, tests ──
57+ frontend :
58+ needs : changes
59+ if : needs.changes.outputs.frontend == 'true'
60+ runs-on : ubuntu-latest
61+ steps :
62+ - uses : actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
63+ with :
64+ fetch-depth : 0
3865
3966 - name : Setup Bun
40- if : steps.changes.outputs.relevant == 'true'
4167 uses : oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
4268 with :
4369 bun-version : latest
4470
4571 - name : Install dependencies
46- if : steps.changes.outputs.relevant == 'true'
4772 # The bun workspace is rooted at the repo; install from the root so the
4873 # frontend member and its deps resolve against the single bun.lock.
4974 run : bun install --frozen-lockfile
5075
5176 - name : Biome check (format + lint)
52- if : steps.changes.outputs.relevant == 'true'
5377 run : bunx biome check
5478
5579 - name : Build (tsc + vite)
56- if : steps.changes.outputs.relevant == 'true'
5780 working-directory : frontend
5881 run : bun run build
5982
6083 - name : Unit tests
61- if : steps.changes.outputs.relevant == 'true'
6284 working-directory : frontend
6385 run : bunx vitest run
6486
6587 - name : i18n dictionaries
66- if : steps.changes.outputs.relevant == 'true'
6788 run : bun run frontend/scripts/check-i18n.ts
6889
6990 - name : ShellCheck
70- if : steps.changes.outputs.relevant == 'true'
7191 run : |
7292 # The module is now a thin launcher over the Rust daemon: service.sh /
7393 # action.sh / uninstall.sh run under Android sh; customize.sh runs under
@@ -80,44 +100,24 @@ jobs:
80100 # ── Rust workspace: fmt, clippy, tests, and generated-bindings drift, all in
81101 # the flake devshell (carries the rust toolchain + webkit for src-tauri). ──
82102 rust :
103+ needs : changes
104+ if : needs.changes.outputs.rust == 'true'
83105 runs-on : ubuntu-latest
84106 steps :
85107 - uses : actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
86108 with :
87109 fetch-depth : 0
88110
89- - name : Detect changes
90- id : changes
91- run : |
92- if [ "${{ github.event_name }}" = "pull_request" ]; then
93- base="${{ github.event.pull_request.base.sha }}"
94- head="${{ github.event.pull_request.head.sha }}"
95- else
96- base="${{ github.event.before }}"
97- head="${{ github.sha }}"
98- fi
99- if [ -z "$base" ] || ! git cat-file -e "$base" 2>/dev/null; then
100- echo "relevant=true" >> "$GITHUB_OUTPUT"; exit 0
101- fi
102- if git diff --name-only "$base" "$head" | grep -Eq '^(crates/|src-tauri/|Cargo\.(toml|lock)|flake\.(nix|lock))'; then
103- echo "relevant=true" >> "$GITHUB_OUTPUT"
104- else
105- echo "relevant=false" >> "$GITHUB_OUTPUT"
106- fi
107-
108111 - name : Install Nix
109- if : steps.changes.outputs.relevant == 'true'
110112 uses : DeterminateSystems/nix-installer-action@da36cb69b1c3247ad7a1f931ebfd954a1105ef14 # v14
111113 with :
112114 extra-conf : |
113115 accept-flake-config = true
114116
115117 - name : Nix store cache
116- if : steps.changes.outputs.relevant == 'true'
117118 uses : DeterminateSystems/magic-nix-cache-action@87b14cf437d03d37989d87f0fa5ce4f5dc1a330b # v8
118119
119120 - name : fmt + clippy + test + codegen drift
120- if : steps.changes.outputs.relevant == 'true'
121121 run : |
122122 nix develop --command bash -euo pipefail -c '
123123 cargo fmt --all --check
@@ -135,60 +135,38 @@ jobs:
135135 # platform/linker breakage without the slow installer. macOS is still deferred
136136 # (no Platform port yet). ──
137137 desktop-linux :
138+ needs : changes
139+ if : needs.changes.outputs.desktop == 'true'
138140 runs-on : ubuntu-latest
139141 steps :
140142 - uses : actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
141143 with :
142144 fetch-depth : 0
143145
144- - name : Detect changes
145- id : changes
146- run : |
147- if [ "${{ github.event_name }}" = "pull_request" ]; then
148- base="${{ github.event.pull_request.base.sha }}"
149- head="${{ github.event.pull_request.head.sha }}"
150- else
151- base="${{ github.event.before }}"
152- head="${{ github.sha }}"
153- fi
154- if [ -z "$base" ] || ! git cat-file -e "$base" 2>/dev/null; then
155- echo "relevant=true" >> "$GITHUB_OUTPUT"; exit 0
156- fi
157- if git diff --name-only "$base" "$head" | grep -Eq '^(crates/|src-tauri/|frontend/|Cargo\.(toml|lock))'; then
158- echo "relevant=true" >> "$GITHUB_OUTPUT"
159- else
160- echo "relevant=false" >> "$GITHUB_OUTPUT"
161- fi
162-
163146 - name : Setup Rust
164- if : steps.changes.outputs.relevant == 'true'
165147 uses : dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
166148 with :
167149 toolchain : stable
168150 targets : x86_64-unknown-linux-gnu
169151
170152 - name : Linux desktop deps
171- if : steps.changes.outputs.relevant == 'true'
172153 run : |
173154 sudo apt-get update
174155 sudo apt-get install -y \
175156 libwebkit2gtk-4.1-dev libgtk-3-dev librsvg2-dev \
176157 libayatana-appindicator3-dev patchelf
177158
178159 - name : Setup Bun
179- if : steps.changes.outputs.relevant == 'true'
180160 uses : oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
181161 with :
182162 bun-version : latest
183163
184164 - name : Build the UI (generate_context embeds frontendDist)
185- if : steps.changes.outputs.relevant == 'true'
186165 run : |
187166 bun install --frozen-lockfile
188167 ( cd frontend && bun run build )
189168
190169 - name : Compile the Tauri app (no bundle)
191- if : steps.changes.outputs.relevant == 'true'
192170 run : cargo build -p kasumi-desktop
193171
194172 # ── Windows desktop compile smoke. Mirrors desktop-linux: the `rust` job runs on
@@ -198,54 +176,32 @@ jobs:
198176 # portable Rust shelling out to ip/iptables — so the `rust` workspace job already
199177 # compiles it on every PR. Only the rarer NDK cross-link is left to nightly.) ──
200178 desktop-windows :
179+ needs : changes
180+ if : needs.changes.outputs.desktop == 'true'
201181 runs-on : windows-latest
202182 steps :
203183 - uses : actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
204184 with :
205185 fetch-depth : 0
206186
207- - name : Detect changes
208- id : changes
209- shell : bash
210- run : |
211- if [ "${{ github.event_name }}" = "pull_request" ]; then
212- base="${{ github.event.pull_request.base.sha }}"
213- head="${{ github.event.pull_request.head.sha }}"
214- else
215- base="${{ github.event.before }}"
216- head="${{ github.sha }}"
217- fi
218- if [ -z "$base" ] || ! git cat-file -e "$base" 2>/dev/null; then
219- echo "relevant=true" >> "$GITHUB_OUTPUT"; exit 0
220- fi
221- if git diff --name-only "$base" "$head" | grep -Eq '^(crates/|src-tauri/|frontend/|Cargo\.(toml|lock))'; then
222- echo "relevant=true" >> "$GITHUB_OUTPUT"
223- else
224- echo "relevant=false" >> "$GITHUB_OUTPUT"
225- fi
226-
227187 - name : Setup Rust
228- if : steps.changes.outputs.relevant == 'true'
229188 uses : dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
230189 with :
231190 toolchain : stable
232191 targets : x86_64-pc-windows-msvc
233192
234193 - name : Setup Bun
235- if : steps.changes.outputs.relevant == 'true'
236194 uses : oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
237195 with :
238196 bun-version : latest
239197
240198 - name : Build the UI (generate_context embeds frontendDist)
241- if : steps.changes.outputs.relevant == 'true'
242199 shell : bash
243200 run : |
244201 bun install --frozen-lockfile
245202 ( cd frontend && bun run build )
246203
247204 - name : Stub the wintun bundle resource (compile-smoke doesn't bundle)
248- if : steps.changes.outputs.relevant == 'true'
249205 shell : bash
250206 run : |
251207 # tauri-build validates declared bundle.resources at compile time. The
@@ -255,5 +211,25 @@ jobs:
255211 : > src-tauri/binaries/wintun.dll
256212
257213 - name : Compile the Tauri app (no bundle)
258- if : steps.changes.outputs.relevant == 'true'
259214 run : cargo build -p kasumi-desktop
215+
216+ # ── The single required status check. It always runs and aggregates the gated
217+ # jobs above: a skipped area (its files weren't touched) is fine; only a real
218+ # failure or cancellation fails CI. Point branch protection at THIS context so
219+ # path-skipped jobs never block a merge. ──
220+ ci :
221+ needs : [changes, frontend, rust, desktop-linux, desktop-windows]
222+ if : always()
223+ runs-on : ubuntu-latest
224+ steps :
225+ - name : Verify no required job failed
226+ run : |
227+ results="${{ join(needs.*.result, ' ') }}"
228+ echo "job results: $results"
229+ for r in $results; do
230+ if [ "$r" = "failure" ] || [ "$r" = "cancelled" ]; then
231+ echo "a CI job did not pass"
232+ exit 1
233+ fi
234+ done
235+ echo "all CI jobs passed or were skipped"
0 commit comments