Repository navigation
260 lines (229 loc) · 9.56 KB
/
Copy pathtest.yml
File metadata and controls
260 lines (229 loc) · 9.56 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
name: Checks & Unit Tests
on:
push:
branches:
- main
# Skip pushes to main that only touch the version bump, CI/workflow config,
# or docs — none need the suite re-run (PRs already validated them, and the
# [release] bump is owned by release-studio).
paths-ignore:
- "apps/api/package.json"
- ".github/**"
- "**/*.md"
pull_request:
branches:
- main
# Both triggers above skip `.github/**`-only changes, so a CI-infrastructure
# fix cannot validate itself. Dispatch runs the suite on demand from any branch.
workflow_dispatch:
# A force-push to a PR obsoletes the in-flight run — cancel it instead of
# letting 6 jobs run to completion. Pushes to main all run (no cancel) so
# every merged commit keeps its own result.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
# Gate: skip the (required) check jobs when a PR only touches workflow YAML,
# docs, or markdown. Required checks can't be skipped via workflow-level
# `paths` filters — a never-scheduled required check blocks the merge forever.
# A job skipped via `if`, by contrast, reports as passed. So we keep the
# trigger broad and skip at the job level. Pushes to main always run.
changes:
runs-on: ubuntu-latest
outputs:
relevant: ${{ steps.filter.outputs.relevant }}
web: ${{ steps.filter.outputs.web }}
steps:
- name: Detect relevant changes
id: filter
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HEAD_MSG: ${{ github.event.head_commit.message }}
run: |
if [ "${{ github.event_name }}" != "pull_request" ]; then
# Push to main runs everything — except the [release]: version-bump
# commit, which only touches the version string. release-studio picks
# that commit up on its own push trigger and cuts the release.
case "$HEAD_MSG" in
'[release]:'*)
echo "relevant=false" >> "$GITHUB_OUTPUT"
echo "web=false" >> "$GITHUB_OUTPUT"
;;
*)
echo "relevant=true" >> "$GITHUB_OUTPUT"
echo "web=true" >> "$GITHUB_OUTPUT"
;;
esac
exit 0
fi
set +e
if ! FILES=$(gh api --paginate \
"repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/files" \
--jq '.[].filename'); then
echo "Could not list PR files — running to be safe."
echo "relevant=true" >> "$GITHUB_OUTPUT"
echo "web=true" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "Changed files:"; echo "$FILES"
# relevant: run unless every changed file is workflow/docs/markdown.
# web: gate for web-component-tests (browser tests only when web
# code can be affected).
relevant=false
web=false
while IFS= read -r f; do
[ -z "$f" ] && continue
case "$f" in
.github/*|deploy/*|*.md) ;;
*) relevant=true ;;
esac
case "$f" in
apps/web/*|packages/ui/*) web=true ;;
esac
done <<< "$FILES"
echo "relevant=$relevant" >> "$GITHUB_OUTPUT"
echo "web=$web" >> "$GITHUB_OUTPUT"
format:
needs: changes
if: needs.changes.outputs.relevant == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Bun and install dependencies
uses: ./.github/actions/setup-bun
- name: Run format check
run: bun run fmt:check
lint:
needs: changes
if: needs.changes.outputs.relevant == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Bun and install dependencies
uses: ./.github/actions/setup-bun
- name: Run lint
run: bun run lint
# knip only reads source (knip.jsonc references no dist/ paths), so it
# lives here instead of serializing after the builds in the build job.
- name: Run knip
run: bun run knip
typecheck:
needs: changes
if: needs.changes.outputs.relevant == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "22"
- name: Setup Bun and install dependencies
uses: ./.github/actions/setup-bun
- name: Run typecheck
run: bun run check:ci
test:
needs: changes
if: needs.changes.outputs.relevant == 'true'
runs-on: ubuntu-latest
# The suite normally finishes in ~1 min. A single hung file under the
# parallel runner stalls the whole job silently (per-file output is
# buffered, so the culprit prints nothing) — without this bound that's
# GitHub's 6h default; it already cost one 50-minute zombie job.
timeout-minutes: 10
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Bun and install dependencies
uses: ./.github/actions/setup-bun
- name: Run tests
# The root command discovers the same filename-based unit tier and runs
# each file with Bun's per-file isolation. Integration, daemon E2E,
# and Playwright suites remain owned by their dedicated workflows.
run: bun run test
build:
needs: changes
if: needs.changes.outputs.relevant == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Bun and install dependencies
uses: ./.github/actions/setup-bun
- name: Build server bundle
run: bun run --cwd=apps/api build:server
- name: Verify bundle outputs
run: |
test -f apps/api/dist/server/server.js
test -f apps/api/dist/server/migrate.js
test -f apps/api/dist/server/cli.js
# `build:web` rather than `build` so VITE_TAURI_APP=0 is stated, not
# inherited from the variable happening to be unset — the desktop-free
# assertion below is only meaningful against a known browser build.
- name: Build web app
run: bun run --cwd=apps/web build:web
- name: Verify web output
run: test -f apps/web/dist/index.html
- name: Assert no Tauri desktop code in the browser bundle
run: bun run check:web-bundle
# Playwright component tests for the sections-editor field widgets
# (apps/web/ct). Self-contained — real chromium, no server/DB — so it lives
# here rather than e2e.yml, gated to web changes only.
web-component-tests:
needs: changes
if: needs.changes.outputs.web == 'true'
runs-on: ubuntu-latest
# Headroom for the worst case of the mirror fallback below: 5 min for the
# browser download plus 3 mirrors x 2 min for the system deps.
timeout-minutes: 20
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Bun and install dependencies
uses: ./.github/actions/setup-bun
# Key on the RESOLVED Playwright version, not a hash of the manifest: the
# manifest carries a caret range, so its hash does not change when the
# lockfile resolves a new version — and the cache would then serve
# browsers built for a different Playwright than the tests run against.
- name: Resolve Playwright version
id: playwright-version
working-directory: apps/web
run: |
version=$(node -p "require('@playwright/test/package.json').version")
echo "version=$version" >> "$GITHUB_OUTPUT"
- name: Cache Playwright browsers
id: cache-playwright
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-ct-${{ runner.os }}-${{ steps.playwright-version.outputs.version }}
# On a key rotation (a Playwright bump) the fallback restores the
# previous browsers and `playwright install` downloads only the
# missing version instead of everything.
restore-keys: |
playwright-ct-${{ runner.os }}-
# Browser binary only (Playwright's own CDN, no apt involved) — kept
# separate from the system deps below so an Ubuntu mirror outage can
# never block the download that actually has to succeed. Invoked via
# ./node_modules/.bin/playwright, never `npx playwright`: npx ignores the
# lockfile and resolves from the registry, so a cache miss would install
# browsers for the LATEST release while the tests run against the pinned
# one — a mismatch a warm cache hides.
- name: Install Playwright chromium
if: steps.cache-playwright.outputs.cache-hit != 'true'
working-directory: apps/web
timeout-minutes: 5
run: ./node_modules/.bin/playwright install chromium
# Everything chromium links against is already in the runner image — the
# only thing this step downloads is ~21 MB of font packages. So a total
# mirror outage costs us glyph coverage, not a red build; if a library
# ever really is missing, chromium fails to launch and the suite says so.
- name: Install Playwright chromium system deps
uses: ./.github/actions/apt-mirror-fallback
continue-on-error: true
with:
run: cd apps/web && ./node_modules/.bin/playwright install-deps chromium
- name: Run component tests
run: bun run --cwd=apps/web test:ct