-
Notifications
You must be signed in to change notification settings - Fork 1
238 lines (215 loc) · 8.67 KB
/
Copy pathci.yml
File metadata and controls
238 lines (215 loc) · 8.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
name: CI
on:
push:
branches: [main]
# Skip the whole pipeline when a commit only touches non-functional
# files. GitHub's rule is "skip iff every changed file matches one of
# these patterns" — touching any source file still triggers a full run.
# Add to this list as new doc-shaped files appear (CHANGELOG, etc.).
paths-ignore:
- '**/*.md'
- '.gitignore'
- 'LICENSE'
pull_request:
branches: [main]
paths-ignore:
- '**/*.md'
- '.gitignore'
- 'LICENSE'
# Lets us trigger CI manually from the Actions tab — handy when poking
# at a workflow change without pushing extra commits.
workflow_dispatch:
# Cancel in-progress runs on the same ref when a new push arrives. Keeps the
# Actions tab tidy and saves a few CI-minutes for a project that ships small
# personal-tool patches.
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
env-manifest:
name: Env-var contract guard
# Pure bash + grep — Ubuntu is faster and cheaper than macOS for this.
# Runs in parallel with the heavier build jobs so it fans out signal
# early: a PR that drifts the env-var contract sees the failure within
# 30 seconds, not after waiting for the full Swift build.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run env-manifest guard
run: ./scripts/check-env-manifest.sh
server:
name: Vapor server (swift build + test)
# macos-15 ships Xcode 16+ (with Swift Testing bundled and Swift 6
# concurrency rules that match the local dev setup). macos-14's default
# Xcode 15.4 doesn't have the `Testing` module and rejects some of our
# weak-self captures.
runs-on: macos-15
steps:
- uses: actions/checkout@v4
# macos-14 ships with Swift 5.10 in the system toolchain — no extra
# setup-action needed. Verify the version so a future runner change
# doesn't silently downgrade us.
- name: Show Swift version
run: swift --version
- name: Cache SwiftPM build artifacts
uses: actions/cache@v4
with:
path: |
.build
~/.cache/org.swift.swiftpm
key: ${{ runner.os }}-spm-server-${{ hashFiles('Package.resolved', 'Package.swift') }}
restore-keys: |
${{ runner.os }}-spm-server-
- name: swift build
run: swift build
- name: swift test
run: swift test
spa:
name: Svelte SPA (type-check + build)
# SPA build is platform-agnostic; Ubuntu is faster and cheaper than macOS
# runners for plain npm work.
runs-on: ubuntu-latest
defaults:
run:
working-directory: web
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
# Node 24 — current LTS. Bumped from 20 (M3.23) ahead of
# GitHub Actions' Node 20 deprecation deadline (June 2026)
# and because 20 hit EOL April 2026. Vite 6 + Svelte 5 +
# svelte-check all run clean on 24.
node-version: '24'
# npm ci reads the lockfile — caching the npm store makes the
# 100-ish-package install instant on cache hits.
cache: npm
cache-dependency-path: web/package-lock.json
- name: npm ci
run: npm ci
- name: Type-check (svelte-check)
run: npm run check
- name: Build production bundle
run: npm run build
mac-app:
name: Menubar app (swift build)
# macos-15 ships Xcode 16+ (with Swift Testing bundled and Swift 6
# concurrency rules that match the local dev setup). macos-14's default
# Xcode 15.4 doesn't have the `Testing` module and rejects some of our
# weak-self captures.
runs-on: macos-15
defaults:
run:
working-directory: mac
steps:
- uses: actions/checkout@v4
- name: Show Swift version
run: swift --version
- name: Cache SwiftPM build artifacts
uses: actions/cache@v4
with:
path: |
mac/.build
~/.cache/org.swift.swiftpm
key: ${{ runner.os }}-spm-mac-${{ hashFiles('mac/Package.swift') }}
restore-keys: |
${{ runner.os }}-spm-mac-
- name: swift build
# Builds the SwiftUI MenuBarExtra binary. Bundling into Keywordista.app
# via build-app.sh is intentionally a release-time concern; CI just
# proves the Swift compiles + links.
run: swift build
- name: swift test
# Pure-function regression guards (e.g. ServiceSupervisorEnvTests
# pins the v0.3.5 spawn-env bug shut). No process launches, no
# network, no real backend — those stay in the release pipeline.
run: swift test
image-size:
name: Server image (build + size gate)
# Builds the Dockerfile on every PR — same as release-image.yml does,
# but amd64 only (no QEMU arm64 emulation; release-image.yml owns
# multi-arch) and not pushed anywhere. Catches Dockerfile breakage
# and image-bloat regressions before they hit a real release.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# GHA layer cache means re-runs on this branch are seconds, not
# minutes. The first run after Dockerfile changes invalidates
# caches for affected layers only.
- name: Build server image (amd64, local load)
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
platforms: linux/amd64
push: false
load: true
tags: keywordista:ci
build-args: |
KEYWORDISTA_BUILD_VERSION=ci-${{ github.sha }}
KEYWORDISTA_BUILD_COMMIT_SHA=${{ github.sha }}
KEYWORDISTA_BUILD_DATE=${{ github.event.head_commit.timestamp || github.event.pull_request.updated_at }}
cache-from: type=gha
cache-to: type=gha,mode=max
# Hard ceiling: 450 MB. The original plan §4.6.1 said <150 MB —
# that turns out to be arm64-only reality (swift:6.1-jammy-slim
# on arm64 is ~120 MB; on amd64 it's ~300+ MB). This CI job
# measures the amd64 build, which is the worst-case platform.
# Current sizes (with dynamic Swift stdlib, swift:6.1-jammy-slim):
# arm64 (local): ~130 MB
# amd64 (CI): ~360 MB (rough; refined as more builds land)
# Real shrink path is M6 polish: distroless/cc + manual Swift
# runtime lib bundling can probably land both arches under
# 100 MB. Not M0 work.
#
# The gate still has value: a ceiling 25% above current amd64
# blocks "we shipped Foundation twice by accident"-style
# regressions while not blocking on inherent base-image size.
- name: Enforce image-size ceiling (450 MB, amd64 worst-case)
run: |
set -euo pipefail
BYTES=$(docker image inspect keywordista:ci --format '{{.Size}}')
MB=$(( BYTES / 1024 / 1024 ))
CEILING=450
echo "→ keywordista:ci is ${MB} MB (ceiling: ${CEILING} MB)"
if (( MB > CEILING )); then
echo "✘ image grew past the ${CEILING} MB ceiling."
echo " If the growth is intentional, raise the ceiling in"
echo " .github/workflows/ci.yml and document the reason."
exit 1
fi
# Surface the size on the GHA run summary so trends are
# visible in the Actions tab over time.
{
echo "## Image size"
echo ""
echo "**${MB} MB** / ${CEILING} MB ceiling (amd64)"
echo ""
echo "_M6 polish opportunity: distroless/cc + manual Swift"
echo "runtime lib bundling can plausibly cut this by 60%+._"
} >> "$GITHUB_STEP_SUMMARY"
# End-to-end boot smoke: a sealed image that doesn't actually run
# is useless. Same shape as `make docker-smoke` locally.
- name: Smoke test (boot + /health)
run: |
set -euo pipefail
docker run -d --name kw-smoke \
-p 8080:8080 \
-e KEYWORDISTA_ENCRYPTION_KEY=$(openssl rand -hex 32) \
-e KEYWORDISTA_PUBLIC_BASE_URL=http://localhost:8080 \
keywordista:ci
# Wait up to 30s for /health to become 200.
for i in $(seq 1 15); do
if curl -fsS http://localhost:8080/health > /dev/null 2>&1; then
echo "✓ /health is green after ${i}×2s"
docker stop kw-smoke > /dev/null
exit 0
fi
sleep 2
done
echo "✘ /health didn't respond within 30s"
docker logs kw-smoke
docker stop kw-smoke > /dev/null
exit 1