-
Notifications
You must be signed in to change notification settings - Fork 1
283 lines (254 loc) · 12.7 KB
/
Copy pathci.yml
File metadata and controls
283 lines (254 loc) · 12.7 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
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
name: CI
# Runs on every push to Production and on all pull requests.
# Tests what actually matters for the current codebase:
# - Native macOS Swift app builds (Xcode on macos-latest)
# - GitHub Pages website linting
#
# Windows and Linux are not supported — Zerm is a native macOS Swift app
# built with AppKit/SwiftUI and has no cross-platform build path.
# The previous CI tested an archived Tauri prototype that is no longer shipped.
on:
push:
branches: [Production, main]
pull_request:
jobs:
# ── Swift build (macOS only) ─────────────────────────────────────────────
swift-build:
name: Swift build (macOS)
runs-on: macos-latest
timeout-minutes: 90
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Select Xcode
run: sudo xcode-select -s /Applications/Xcode.app
- name: Cache pinned native frameworks
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
with:
path: |
~/Zerm-Dependencies/whisper.cpp/build-apple/whisper.xcframework
~/Zerm-Dependencies/sherpa-onnx/build-swift-macos/sherpa-onnx.xcframework
~/Zerm-Dependencies/sherpa-onnx/build-swift-macos/onnxruntime.xcframework
~/Zerm-Dependencies/llama/build-apple/llama.xcframework
key: zerm-native-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('Makefile') }}
- name: Build pinned native frameworks
run: |
set -euo pipefail
command -v cmake >/dev/null || brew install cmake
make setup
- name: Resolve Swift packages
run: |
set -euo pipefail
xcodebuild -project Zerm.xcodeproj \
-scheme Zerm \
-resolvePackageDependencies \
-clonedSourcePackagesDirPath .ci-packages \
2>&1 | tail -5
# Fail with a focused diagnostic before xcodebuild if a pinned upstream packaging step
# changed its output layout. A successful job must compile and test the actual app.
- name: Check native dependencies are available
run: |
set -euo pipefail
MISSING=0
for fw in \
"$HOME/Zerm-Dependencies/whisper.cpp/build-apple/whisper.xcframework" \
"$HOME/Zerm-Dependencies/sherpa-onnx/build-swift-macos/sherpa-onnx.xcframework" \
"$HOME/Zerm-Dependencies/sherpa-onnx/build-swift-macos/onnxruntime.xcframework" \
"$HOME/Zerm-Dependencies/llama/build-apple/llama.xcframework"; do
if [ ! -d "$fw" ]; then echo "missing: $fw"; MISSING=1; fi
done
if [ $MISSING -eq 1 ]; then
echo "::error::Native XCFrameworks are absent, so the app cannot be built or tested here."
echo "::error::This job previously piped xcodebuild into xcpretty/tail, which discarded the"
echo "::error::exit code and reported success for a build that failed on every run."
exit 1
fi
- name: Build (no signing)
run: |
set -euo pipefail
xcodebuild \
-project Zerm.xcodeproj \
-scheme Zerm \
-configuration Debug \
-clonedSourcePackagesDirPath .ci-packages \
CODE_SIGN_IDENTITY="" \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGNING_ALLOWED=YES \
DEVELOPMENT_TEAM="" \
-quiet \
build
# The scheme has always had a TestAction wired to ZermTests, but nothing ever
# invoked it, so the suite never ran. Debug is required: `@testable import Zerm`
# needs ENABLE_TESTABILITY, which Release turns off.
- name: Unit tests
run: |
set -euo pipefail
xcodebuild test \
-project Zerm.xcodeproj \
-scheme Zerm \
-configuration Debug \
-destination 'platform=macOS' \
-only-testing:ZermTests \
-skip-testing:ZermTests/MeetingSessionIntegrationTests \
-skip-testing:ZermTests/MeetingDiarizerIntegrationTests \
-skip-testing:ZermTests/DiarizerModelLoadDiagnostic \
-skip-testing:ZermTests/MeetingTwoSpeakerTests \
-skip-testing:ZermTests/DiarizerCapacityDiagnostic \
-clonedSourcePackagesDirPath .ci-packages \
CODE_SIGN_IDENTITY="" \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGNING_ALLOWED=NO \
-quiet
# UI automation itself needs a logged-in, unlocked Aqua session plus a user-granted
# automation permission. GitHub-hosted runners cannot provide that trustworthy runtime
# gate, but they can still prevent an unbuildable UI-test harness from reaching a PR.
- name: UI tests compile and link
run: |
set -euo pipefail
xcodebuild build-for-testing \
-project Zerm.xcodeproj \
-scheme Zerm \
-configuration Debug \
-destination 'platform=macOS' \
-only-testing:ZermUITests \
-clonedSourcePackagesDirPath .ci-packages \
CODE_SIGN_IDENTITY="" \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGNING_ALLOWED=NO \
-quiet
# ── Website lint ─────────────────────────────────────────────────────────
website-lint:
name: Website lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Check HTML is valid (basic)
run: |
# Ensure index.html exists and has required landmarks
test -f docs/index.html || (echo "docs/index.html missing" && exit 1)
grep -q 'id="primary-download"' docs/index.html || (echo "Primary download button missing" && exit 1)
grep -q 'id="download"' docs/index.html || (echo "Download section anchor missing" && exit 1)
echo "HTML landmarks OK"
- name: Check site.js has no GitHub Releases redirects
run: |
# Ensure we never send users to /releases page (regression guard)
if grep -q 'github.com/arcusis/Zerm/releases"' docs/site.js; then
echo "FAIL: site.js redirects to GitHub Releases page — fix direct download logic"
exit 1
fi
echo "No GitHub Releases redirect found — OK"
- name: Check every in-app doc link has a published page
run: |
# Zerm/Resources/Links.swift is the single registry of URLs the app opens.
# Every Links.Doc slug must have a generated page under docs/docs/, or the
# app ships a link to a 404 — which is exactly how it ended up pointing at
# an unregistered domain for seventeen call sites.
REGISTRY=Zerm/Resources/Links.swift
test -f "$REGISTRY" || (echo "$REGISTRY missing" && exit 1)
SLUGS=$(sed -n 's/^ *case [A-Za-z]* = "\([a-z0-9-]*\)"$/\1/p' "$REGISTRY")
test -n "$SLUGS" || (echo "FAIL: no doc slugs parsed out of $REGISTRY" && exit 1)
MISSING=0
while IFS= read -r slug; do
if [ ! -f "docs/docs/$slug.html" ]; then
echo "FAIL: Links.Doc \"$slug\" has no page at docs/docs/$slug.html"
MISSING=1
fi
done <<< "$SLUGS"
# And the other way round, so a page never quietly stops being linked
for page in docs/docs/*.html; do
slug=$(basename "$page" .html)
[ "$slug" = "index" ] && continue
if ! echo "$SLUGS" | grep -qx "$slug"; then
echo "FAIL: docs/docs/$slug.html has no matching Links.Doc case"
MISSING=1
fi
done
[ "$MISSING" -eq 0 ] || exit 1
echo "All $(echo "$SLUGS" | wc -l | tr -d ' ') doc links resolve to a published page — OK"
- name: Check every docs screenshot exists
run: |
# The docs pages embed screenshots of the app. A missing file renders as a
# broken image on a published page, and nothing else in the pipeline notices.
MISSING=0
for page in docs/docs/*.html; do
for img in $(grep -oE 'src="img/[^"]+"' "$page" | sed 's/src="//; s/"$//'); do
if [ ! -s "docs/docs/$img" ]; then
echo "FAIL: $page references docs/docs/$img, which is missing or empty"
MISSING=1
fi
done
done
[ $MISSING -eq 0 ] || exit 1
echo "All docs screenshots resolve — OK"
- name: Check the announcements feed is published and parses
run: |
# AnnouncementsService fetches docs/announcements.json on every launch.
# An unpublished or malformed feed is a silent failure in the app, so it
# fails the PR instead. `make site` mirrors the repo-root source into docs/.
test -f docs/announcements.json || (echo "FAIL: docs/announcements.json missing — run 'make site'" && exit 1)
node -e '
const fs = require("fs");
const feed = JSON.parse(fs.readFileSync("docs/announcements.json", "utf8"));
if (!Array.isArray(feed)) throw new Error("announcements feed must be a JSON array");
const source = fs.readFileSync("announcements.json", "utf8");
if (source !== fs.readFileSync("docs/announcements.json", "utf8")) {
throw new Error("docs/announcements.json has drifted from the repo-root source — run \"make site\"");
}
console.log(`Announcements feed OK — ${feed.length} entries`);
'
- name: Check nothing points at the unregistered domain
run: |
# tryzerm.com is a find/replace artefact inherited from upstream VoiceInk's
# tryvoiceink.com. It has never been registered, so every one of these was a
# link to nowhere. Zerm/Resources/Links.swift is the replacement.
if git grep -n "tryzerm\.com" -- "*.swift" "*.json" "*.html" "*.md"; then
echo "FAIL: tryzerm.com is not a registered domain — use Links.swift"
exit 1
fi
echo "No links to the unregistered domain — OK"
# ── Security scan ────────────────────────────────────────────────────────
# Secret detection is handled by the GitGuardian app check and CodeQL, which
# run on every PR. This job only covers the repo-specific home-path check.
# (The repo's Actions policy is `selected` + sha-pinned, so third-party
# actions like gitleaks/gitleaks-action are not permitted to start.)
security-scan:
name: Secret / PII scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
with:
fetch-depth: 0
- name: Scan for hardcoded personal paths
run: |
# Ensure no absolute home-directory paths are committed in source
FOUND=$(git grep -rE "/Users/[a-z][a-z0-9_-]+" \
-- "*.swift" "*.yml" "*.yaml" "*.md" "*.html" "*.js" "*.json" \
| grep -v "arcusis\|example\|placeholder" || true)
if [ -n "$FOUND" ]; then
echo "FAIL: Hardcoded user home paths found:"
echo "$FOUND"
exit 1
fi
echo "No hardcoded home paths — OK"
- name: Validate Sparkle appcast integrity
run: |
# The appcast XML itself is not signed — only the enclosure bytes are.
# Guard the two properties that keep OTA safe: every advertised update
# must carry an EdDSA signature and be served from the official
# GitHub Releases host. A missing/blank signature or an off-host
# enclosure fails the PR instead of shipping a bad or unverifiable feed.
for feed in docs/appcast.xml appcast.xml; do
[ -f "$feed" ] || continue
echo "Checking $feed"
enclosures=$(grep -c "<enclosure " "$feed" || true)
if [ "$enclosures" -eq 0 ]; then
echo "FAIL: $feed has no <enclosure> element"; exit 1
fi
sigs=$(grep -c 'sparkle:edSignature="[^"]\{20,\}"' "$feed" || true)
if [ "$sigs" -ne "$enclosures" ]; then
echo "FAIL: $feed has $enclosures enclosure(s) but $sigs signed — every update must be EdDSA-signed"; exit 1
fi
if grep -q '<enclosure ' "$feed" && ! grep 'url="https://github.com/arcusis/Zerm/releases/download/' "$feed" >/dev/null; then
echo "FAIL: $feed enclosure is not served from the official GitHub Releases host"; exit 1
fi
done
echo "Appcast integrity — OK"