mirror of
https://github.com/zadam/trilium.git
synced 2026-09-12 19:50:20 +05:00
Two runs of this branch died at the same point: 100 of 121 spec files
completed, then the browser session either dropped mid-run or stopped
responding until the step timed out. Chromedriver and Chrome are
correctly paired (150.0.7871.124 / .128), and the DOM is clean at the
end of every file — no editors, math fields, balloons or previews are
left behind, so the tests themselves are not leaking.
What accumulates is v8 coverage data. Browser mode keeps one page for
the whole run, and coverage collects for every script loaded into it.
Measured locally over the same file order:
file without --coverage with --coverage
20 218 MB 235 MB
60 291 MB 498 MB
100 236 MB 847 MB
106 - 873 MB (peak)
Without coverage the heap sawtooths and GC keeps up. With it, the heap
climbs to ~3.6x by the 100th file — exactly where CI gives out. Locally
there is enough headroom to survive it; a runner has less.
Run the suite as two shards of ~60 files, which peaks around 500MB, and
merge the blob reports afterwards. Thresholds are zeroed for the shard
runs (each only exercises half the tree, so they would fail on files the
other shard covers) and enforced on the merged report, which reproduces
the single-run numbers exactly and still writes the JUnit file Codecov
uploads.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
415 lines
15 KiB
YAML
415 lines
15 KiB
YAML
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:
|
|
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 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: Run the server-side tests
|
|
id: test-server
|
|
run: pnpm run --filter=server test --coverage
|
|
|
|
- name: Upload server test report
|
|
uses: actions/upload-artifact@v7
|
|
if: always()
|
|
with:
|
|
name: server-test-report
|
|
path: apps/server/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: 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
|
|
|
|
- name: Run the standalone tests
|
|
id: test-standalone
|
|
# 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
|
|
|
|
- name: Run the CKEditor 5 aggregate tests
|
|
id: test-ckeditor5
|
|
# 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. Run the suite in two shards (~60 files each, peaking
|
|
# around 500MB) and merge the blob reports afterwards, so the 100% gate is still evaluated
|
|
# over the whole suite rather than per shard.
|
|
SHARD_ARGS="--reporter=blob --coverage \
|
|
--coverage.thresholds.lines=0 --coverage.thresholds.functions=0 \
|
|
--coverage.thresholds.branches=0 --coverage.thresholds.statements=0"
|
|
pnpm run --filter=ckeditor5 test --shard=1/2 $SHARD_ARGS
|
|
pnpm run --filter=ckeditor5 test --shard=2/2 $SHARD_ARGS
|
|
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
|
|
|
|
- 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 express-partial-content tests
|
|
id: test-express-partial-content
|
|
run: pnpm run --filter=express-partial-content test --coverage
|
|
|
|
- name: Upload express-partial-content coverage to Codecov
|
|
uses: codecov/codecov-action@v7
|
|
if: ${{ !cancelled() && steps.test-express-partial-content.outcome == 'success' }}
|
|
with:
|
|
files: packages/express-partial-content/test-output/vitest/coverage/lcov.info
|
|
flags: express-partial-content
|
|
disable_search: true
|
|
fail_ci_if_error: false
|
|
|
|
- name: Upload express-partial-content test results to Codecov
|
|
uses: codecov/test-results-action@v1
|
|
if: ${{ !cancelled() && steps.test-express-partial-content.outcome != 'skipped' }}
|
|
with:
|
|
token: ${{ secrets.CODECOV_TOKEN }}
|
|
files: packages/express-partial-content/test-output/vitest/junit.xml
|
|
flags: express-partial-content
|
|
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=\!express-partial-content --filter=\!highlightjs --filter=\!pdfjs-viewer test
|
|
|
|
build_docker:
|
|
name: Build Docker image
|
|
runs-on: ubuntu-latest
|
|
needs:
|
|
- test_dev
|
|
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
|
|
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
|