Repository navigation
618 lines (559 loc) · 22.9 KB
/
Copy pathdev.yml
File metadata and controls
618 lines (559 loc) · 22.9 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
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
name: Dev
on:
push:
branches:
- main
- standalone
- "release/*"
pull_request:
branches:
- main
- standalone
- "release/*"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
GHCR_REGISTRY: ghcr.io
DOCKERHUB_REGISTRY: docker.io
IMAGE_NAME: ${{ github.repository}}
TEST_TAG: ${{ github.repository}}:test
permissions:
pull-requests: write # for PR comments
jobs:
# Decides which of the two heavyweight suites a given push/PR can actually
# affect. Both suites also run the shared trilium-core spec set, so anything
# under that package (or the workspace roots) has to trigger both.
changes:
name: Detect affected suites
runs-on: ubuntu-latest
outputs:
server: ${{ steps.filter.outputs.server }}
standalone: ${{ steps.filter.outputs.standalone }}
ckeditor5: ${{ steps.filter.outputs.ckeditor5 }}
steps:
- uses: actions/checkout@v7
- uses: dorny/paths-filter@v4
id: filter
with:
filters: |
# Workspace-wide inputs: these change how anything installs or builds.
# Kept as its own anchor so the ckeditor5 filter can pick it up
# without inheriting the much wider trilium-core closure below.
workspace: &workspace
- 'pnpm-lock.yaml'
- 'pnpm-workspace.yaml'
- 'package.json'
- 'tsconfig*.json'
- '.npmrc'
- '.nvmrc'
# pnpm-workspace.yaml patches @excalidraw/excalidraw, so editing a
# patch changes what actually gets installed.
- 'patches/**'
- '.github/workflows/dev.yml'
# apps/server and apps/standalone each include
# ../../packages/trilium-core/src/**/*.spec.ts in their vitest
# `include` — the standalone suite exists precisely to re-run that
# set under happy-dom + sql.js — so both need core and everything
# core pulls in. turndown-plugin-gfm is here because trilium-core
# depends on it; it is not a direct dependency of either app.
core: &core
- 'packages/trilium-core/**'
- 'packages/commons/**'
- 'packages/turndown-plugin-gfm/**'
- 'packages/trilium-backup-container/**'
- 'packages/highlightjs/**'
server:
- *workspace
- *core
- 'apps/server/**'
# Widest closure in the repo: apps/standalone depends on eight
# workspace packages. It reaches into apps/client too, but only
# through ../../client/src/index.js, which main.spec.ts mocks — no
# client code executes in this suite, so apps/client is left out.
standalone:
- *workspace
- *core
- 'apps/standalone/**'
- 'packages/ckeditor5/**'
- 'packages/codemirror/**'
- 'packages/share-theme/**'
- 'packages/splitjs/**'
# Narrowest closure of the three: the aggregate depends only on
# commons. Note this filter does not guard the t()/translation-key
# contract — apps/client/src/services/i18n.spec.ts reads
# packages/ckeditor5/src off disk to enforce that, and it runs in
# test_dev, which is ungated.
ckeditor5:
- *workspace
- 'packages/ckeditor5/**'
- 'packages/commons/**'
test_dev:
name: Test development
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v7
- uses: pnpm/action-setup@v6
- name: Set up node & dependencies
uses: actions/setup-node@v7
with:
node-version: 24
cache: "pnpm"
- run: pnpm install --frozen-lockfile
- name: Typecheck
run: pnpm typecheck
- name: Run the repo-script tests
run: pnpm scripts:test
- name: Run the client-side tests
id: test-client
run: pnpm run --filter=client test --coverage
- name: Upload client test report
uses: actions/upload-artifact@v7
if: always()
with:
name: client-test-report
path: apps/client/test-output/vitest/html/
retention-days: 30
- name: Upload client coverage to Codecov
uses: codecov/codecov-action@v7
if: ${{ !cancelled() && steps.test-client.outcome == 'success' }}
with:
files: apps/client/test-output/vitest/coverage/lcov.info
flags: client
disable_search: true
fail_ci_if_error: false
- name: Upload client test results to Codecov
uses: codecov/test-results-action@v1
if: ${{ !cancelled() && steps.test-client.outcome != 'skipped' }}
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: apps/client/test-output/vitest/junit.xml
flags: client
disable_search: true
fail_ci_if_error: false
- name: Run the desktop tests
id: test-desktop
run: pnpm run --filter=desktop test --coverage
- name: Upload desktop coverage to Codecov
uses: codecov/codecov-action@v7
if: ${{ !cancelled() && steps.test-desktop.outcome == 'success' }}
with:
files: apps/desktop/test-output/vitest/coverage/lcov.info
flags: desktop
disable_search: true
fail_ci_if_error: false
- name: Upload desktop test results to Codecov
uses: codecov/test-results-action@v1
if: ${{ !cancelled() && steps.test-desktop.outcome != 'skipped' }}
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: apps/desktop/test-output/vitest/junit.xml
flags: desktop
disable_search: true
fail_ci_if_error: false
- name: Run the commons tests
id: test-commons
run: pnpm run --filter=commons test --coverage
- name: Upload commons coverage to Codecov
uses: codecov/codecov-action@v7
if: ${{ !cancelled() && steps.test-commons.outcome == 'success' }}
with:
files: packages/commons/test-output/vitest/coverage/lcov.info
flags: commons
disable_search: true
fail_ci_if_error: false
- name: Upload commons test results to Codecov
uses: codecov/test-results-action@v1
if: ${{ !cancelled() && steps.test-commons.outcome != 'skipped' }}
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: packages/commons/test-output/vitest/junit.xml
flags: commons
disable_search: true
fail_ci_if_error: false
- name: Run the codemirror tests
id: test-codemirror
run: pnpm run --filter=codemirror test --coverage
- name: Upload codemirror coverage to Codecov
uses: codecov/codecov-action@v7
if: ${{ !cancelled() && steps.test-codemirror.outcome == 'success' }}
with:
files: packages/codemirror/test-output/vitest/coverage/lcov.info
flags: codemirror
disable_search: true
fail_ci_if_error: false
- name: Upload codemirror test results to Codecov
uses: codecov/test-results-action@v1
if: ${{ !cancelled() && steps.test-codemirror.outcome != 'skipped' }}
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: packages/codemirror/test-output/vitest/junit.xml
flags: codemirror
disable_search: true
fail_ci_if_error: false
- name: Run the highlightjs tests
id: test-highlightjs
run: pnpm run --filter=highlightjs test --coverage
- name: Upload highlightjs coverage to Codecov
uses: codecov/codecov-action@v7
if: ${{ !cancelled() && steps.test-highlightjs.outcome == 'success' }}
with:
files: packages/highlightjs/test-output/vitest/coverage/lcov.info
flags: highlightjs
disable_search: true
fail_ci_if_error: false
- name: Upload highlightjs test results to Codecov
uses: codecov/test-results-action@v1
if: ${{ !cancelled() && steps.test-highlightjs.outcome != 'skipped' }}
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: packages/highlightjs/test-output/vitest/junit.xml
flags: highlightjs
disable_search: true
fail_ci_if_error: false
- name: Run the pdfjs-viewer tests
id: test-pdfjs-viewer
run: pnpm run --filter=pdfjs-viewer test --coverage
- name: Upload pdfjs-viewer coverage to Codecov
uses: codecov/codecov-action@v7
if: ${{ !cancelled() && steps.test-pdfjs-viewer.outcome == 'success' }}
with:
files: packages/pdfjs-viewer/test-output/vitest/coverage/lcov.info
flags: pdfjs-viewer
disable_search: true
fail_ci_if_error: false
- name: Upload pdfjs-viewer test results to Codecov
uses: codecov/test-results-action@v1
if: ${{ !cancelled() && steps.test-pdfjs-viewer.outcome != 'skipped' }}
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: packages/pdfjs-viewer/test-output/vitest/junit.xml
flags: pdfjs-viewer
disable_search: true
fail_ci_if_error: false
- name: Run the rest of the tests
run: pnpm run --filter=\!client --filter=\!standalone --filter=\!server --filter=\!desktop --filter=\!commons --filter=\!ckeditor5 --filter=\!codemirror --filter=\!highlightjs --filter=\!pdfjs-viewer test
# The two suites below are split out of test_dev so they run concurrently with
# it (and each other) rather than queueing behind the client/package suites.
#
# Each job always starts and gates its *steps* on the filter, rather than
# carrying a job-level `if:`. A job skipped at the job level reports no result
# to a branch-protection required check, and propagates the skip through
# `needs:` to build_docker. Gating the steps keeps the job green and the
# dependency graph intact on a run that touches neither suite; the cost is one
# checkout on an unaffected run.
test_server:
name: Test server
runs-on: ubuntu-latest
needs: changes
env:
AFFECTED: ${{ needs.changes.outputs.server }}
steps:
- name: Checkout the repository
if: env.AFFECTED == 'true'
uses: actions/checkout@v7
- uses: pnpm/action-setup@v6
if: env.AFFECTED == 'true'
- name: Set up node & dependencies
if: env.AFFECTED == 'true'
uses: actions/setup-node@v7
with:
node-version: 24
cache: "pnpm"
- name: Install dependencies
if: env.AFFECTED == 'true'
run: pnpm install --frozen-lockfile
- name: Run the server-side tests
id: test-server
if: env.AFFECTED == 'true'
run: pnpm run --filter=server test --coverage
- name: Upload server test report
uses: actions/upload-artifact@v7
if: ${{ !cancelled() && steps.test-server.outcome != 'skipped' }}
with:
name: server-test-report
path: apps/server/test-output/vitest/html/
retention-days: 30
- name: Upload server coverage to Codecov
uses: codecov/codecov-action@v7
if: ${{ !cancelled() && steps.test-server.outcome == 'success' }}
with:
files: apps/server/test-output/vitest/coverage/lcov.info
flags: server
disable_search: true
fail_ci_if_error: false
- name: Upload server test results to Codecov
uses: codecov/test-results-action@v1
if: ${{ !cancelled() && steps.test-server.outcome != 'skipped' }}
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: apps/server/test-output/vitest/junit.xml
flags: server
disable_search: true
fail_ci_if_error: false
test_standalone:
name: Test standalone
runs-on: ubuntu-latest
needs: changes
env:
AFFECTED: ${{ needs.changes.outputs.standalone }}
steps:
- name: Checkout the repository
if: env.AFFECTED == 'true'
uses: actions/checkout@v7
- uses: pnpm/action-setup@v6
if: env.AFFECTED == 'true'
- name: Set up node & dependencies
if: env.AFFECTED == 'true'
uses: actions/setup-node@v7
with:
node-version: 24
cache: "pnpm"
- name: Install dependencies
if: env.AFFECTED == 'true'
run: pnpm install --frozen-lockfile
- name: Run the standalone tests
id: test-standalone
if: env.AFFECTED == 'true'
# Runs the same trilium-core spec set as the server suite, but in
# happy-dom + sql.js WASM via BrowserSqlProvider (see
# apps/standalone/src/test_setup.ts). Catches differences
# between the Node-side and browser-side runtimes.
run: pnpm run --filter=standalone test --coverage
- name: Upload standalone coverage to Codecov
uses: codecov/codecov-action@v7
if: ${{ !cancelled() && steps.test-standalone.outcome == 'success' }}
with:
files: apps/standalone/test-output/vitest/coverage/lcov.info
flags: standalone
disable_search: true
fail_ci_if_error: false
- name: Upload standalone test results to Codecov
uses: codecov/test-results-action@v1
if: ${{ !cancelled() && steps.test-standalone.outcome != 'skipped' }}
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: apps/standalone/test-output/vitest/junit.xml
flags: standalone
disable_search: true
fail_ci_if_error: false
# The two shards the suite was already split into now run as separate jobs
# rather than back-to-back in one step, and a merge job evaluates the coverage
# gate over the combined blob reports. Sharding exists for renderer memory, not
# speed (see the comment on the test step); running the shards concurrently is
# what makes it also a speedup.
test_ckeditor5:
name: Test CKEditor 5 (shard ${{ matrix.shard }})
runs-on: ubuntu-latest
needs: changes
strategy:
fail-fast: false
matrix:
shard: [1, 2]
env:
AFFECTED: ${{ needs.changes.outputs.ckeditor5 }}
steps:
- name: Checkout the repository
if: env.AFFECTED == 'true'
uses: actions/checkout@v7
- uses: pnpm/action-setup@v6
if: env.AFFECTED == 'true'
- name: Set up node & dependencies
if: env.AFFECTED == 'true'
uses: actions/setup-node@v7
with:
node-version: 24
cache: "pnpm"
- name: Install dependencies
if: env.AFFECTED == 'true'
run: pnpm install --frozen-lockfile
- name: Run the CKEditor 5 aggregate tests
id: test-ckeditor5
if: env.AFFECTED == 'true'
# The suite drives a real headless Chrome through webdriverio. When the browser session
# fails to start, vitest waits on it rather than erroring, so cap the step instead of
# letting it sit until the job's default timeout.
timeout-minutes: 15
run: |
export CHROMEDRIVER_PATH=$(which chromedriver || find /usr/local/share -name chromedriver -type f 2>/dev/null | head -1)
echo "Using chromedriver at: $CHROMEDRIVER_PATH"
# A chromedriver whose major version does not match the installed Chrome fails the
# session handshake, which surfaces as a hang or a mid-run disconnect rather than a
# clear error — so record both up front.
"$CHROMEDRIVER_PATH" --version || echo "could not read the chromedriver version"
(google-chrome --version || chromium --version || chromium-browser --version) 2>/dev/null \
|| echo "could not read the Chrome version"
# Browser mode keeps one page for the whole run, and v8 coverage data accumulates in it:
# measured locally, the heap reaches ~850MB by the 100th spec file with --coverage against
# ~240MB without it. That is where a CI runner's renderer gives out — the session either
# drops mid-run or stops responding. Each shard is ~60 files, peaking around 500MB. The
# thresholds are zeroed here and evaluated once over the merged report instead, so the
# gate still covers the whole suite rather than each shard separately.
pnpm run --filter=ckeditor5 test --shard=${{ matrix.shard }}/${{ strategy.job-total }} \
--reporter=blob --coverage \
--coverage.thresholds.lines=0 --coverage.thresholds.functions=0 \
--coverage.thresholds.branches=0 --coverage.thresholds.statements=0
# Vitest names blobs `.vitest-reports/blob-<index>-<count>.json`, so the two
# shards produce distinct filenames and merge-multiple cannot clobber them.
# That directory is dot-prefixed, and upload-artifact skips hidden paths
# unless told otherwise — without include-hidden-files it silently matches
# nothing.
- name: Upload the shard blob report
uses: actions/upload-artifact@v7
if: ${{ !cancelled() && steps.test-ckeditor5.outcome == 'success' }}
with:
name: ckeditor5-blob-${{ matrix.shard }}
path: packages/ckeditor5/.vitest-reports/
include-hidden-files: true
if-no-files-found: error
retention-days: 1
test_ckeditor5_merge:
name: CKEditor 5 coverage gate
runs-on: ubuntu-latest
needs:
- changes
- test_ckeditor5
env:
AFFECTED: ${{ needs.changes.outputs.ckeditor5 }}
steps:
- name: Checkout the repository
if: env.AFFECTED == 'true'
uses: actions/checkout@v7
- uses: pnpm/action-setup@v6
if: env.AFFECTED == 'true'
- name: Set up node & dependencies
if: env.AFFECTED == 'true'
uses: actions/setup-node@v7
with:
node-version: 24
cache: "pnpm"
- name: Install dependencies
if: env.AFFECTED == 'true'
run: pnpm install --frozen-lockfile
- name: Download the shard blob reports
if: env.AFFECTED == 'true'
uses: actions/download-artifact@v8
with:
pattern: ckeditor5-blob-*
merge-multiple: true
path: packages/ckeditor5/.vitest-reports
# Re-runs no tests; it replays the blobs to produce the junit and lcov
# output and to evaluate the package's real coverage thresholds over the
# whole suite.
- name: Merge the shard reports and evaluate the coverage gate
id: test-ckeditor5
if: env.AFFECTED == 'true'
run: pnpm run --filter=ckeditor5 test --mergeReports --coverage
- name: Upload ckeditor5 coverage to Codecov
uses: codecov/codecov-action@v7
if: ${{ !cancelled() && steps.test-ckeditor5.outcome == 'success' }}
with:
files: packages/ckeditor5/test-output/vitest/coverage/lcov.info
flags: ckeditor5
disable_search: true
fail_ci_if_error: false
- name: Upload ckeditor5 test results to Codecov
uses: codecov/test-results-action@v1
if: ${{ !cancelled() && steps.test-ckeditor5.outcome != 'skipped' }}
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: packages/ckeditor5/test-output/vitest/junit.xml
flags: ckeditor5
disable_search: true
fail_ci_if_error: false
# Deliberately ungated: this validates that the client and server build and
# that the image assembles, none of which depends on a test outcome. Waiting
# for the suites put ~4m of Docker work in series behind the slowest of them
# for no signal. It now runs alongside them, so the workflow finishes with the
# longest test job rather than after it.
#
# The trade is that a push whose tests fail still spends runner time building
# images. That is the accepted cost of taking the Docker work off the critical
# path; the concurrency group above still cancels superseded runs.
build_docker:
name: Build Docker image
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: pnpm/action-setup@v6
- name: Set up node & dependencies
uses: actions/setup-node@v7
with:
node-version: 24
cache: "pnpm"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Update build info
run: pnpm run chore:update-build-info
- name: Trigger client build
run: pnpm client:build
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
- name: Trigger server build
run: pnpm run server:build
- uses: docker/setup-buildx-action@v4
- uses: docker/build-push-action@v7
with:
context: apps/server
cache-from: type=gha
cache-to: type=gha,mode=max
test_docker:
name: Check Docker build
runs-on: ubuntu-latest
# Kept: build_docker populates the type=gha buildx cache this job reads. The
# two together (~4m) still finish well inside the slowest test job, so
# breaking the chain would buy nothing and would leave both jobs racing to
# write the same cache.
needs:
- build_docker
strategy:
matrix:
include:
- dockerfile: Dockerfile.alpine
- dockerfile: Dockerfile
steps:
- name: Checkout the repository
uses: actions/checkout@v7
- uses: pnpm/action-setup@v6
- name: Set up node & dependencies
uses: actions/setup-node@v7
with:
node-version: 24
cache: "pnpm"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Update build info
run: pnpm run chore:update-build-info
- name: Trigger build
run: pnpm server:build
- name: Set IMAGE_NAME to lowercase
run: echo "IMAGE_NAME=${IMAGE_NAME,,}" >> $GITHUB_ENV
- name: Set TEST_TAG to lowercase
run: echo "TEST_TAG=${TEST_TAG,,}" >> $GITHUB_ENV
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Build and export to Docker
uses: docker/build-push-action@v7
with:
context: apps/server
file: apps/server/${{ matrix.dockerfile }}
load: true
tags: ${{ env.TEST_TAG }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Validate container run output
run: |
CONTAINER_ID=$(docker run -d --log-driver=journald --rm --name trilium_local ${{ env.TEST_TAG }})
echo "Container ID: $CONTAINER_ID"
- name: Wait for the healthchecks to pass
uses: stringbean/docker-healthcheck-action@v3
with:
container: trilium_local
wait-time: 50
require-status: running
require-healthy: true
# Print the entire log of the container thus far, regardless if the healthcheck failed or succeeded
- name: Print entire log
if: always()
run: journalctl -u docker CONTAINER_NAME=trilium_local --no-pager