Repository navigation
561 lines (512 loc) · 25.6 KB
/
Copy pathci.yml
File metadata and controls
561 lines (512 loc) · 25.6 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
name: CI
# Pull requests into main are the merge gate (CONTRIBUTING.md, "What is
# enforced": green CI on a branch up to date with main); the push trigger keeps
# main's own history green after a merge or a direct push.
#
# Pull requests into a tracker/** branch are gated too. A large change is
# delivered as a chain of review-sized slices that stack onto a tracker branch,
# and only the tracker opens a pull request against main. Gating main alone would
# leave every slice unvalidated until the whole chain was already assembled --
# and the local loop cannot stand in for CI here, because the test matrix also
# runs 3.14 and the build job smoke-tests an isolated wheel. A slice that breaks
# either would stay hidden until the last pull request in the chain.
#
# There are deliberately no path filters. Docs-only changes touch nothing these
# jobs validate, but a job skipped by a path filter reports no status at all, and
# a required check that never reports blocks a pull request indefinitely. Running
# every change through the full suite keeps the branch-protection story simple
# and keeps the "green CI" claim honest. The jobs take seconds.
on:
push:
branches: [main]
pull_request:
branches: [main, 'tracker/**']
# Least privilege. This is a public repository; no job here reads or writes
# repository state beyond checking the code out.
permissions:
contents: read
# Supersede in-flight runs for the same pull request, but never cancel a run on
# main: every commit on the default branch keeps a status of its own.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
# Actions are pinned by commit SHA rather than by moving tag: a tag can be
# repointed at new code, a SHA cannot. The trailing comment records the release
# each SHA corresponds to. Note that astral-sh/setup-uv publishes no moving major
# tag for its v8 series, so an exact reference is required there regardless.
jobs:
# Ruff reads its target version from `project.requires-python` and MyPy pins
# `python_version = "3.14"`, so both analyse for 3.14 whatever interpreter they
# run on. They run once, on the only supported interpreter.
quality:
name: quality (ruff, mypy)
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
with:
python-version: "3.14"
enable-cache: true
# `--locked` installs exactly what uv.lock pins and fails if the lock has
# drifted from pyproject.toml, so a dependency edit cannot merge unlocked.
- name: Install dependencies
run: uv sync --locked
- name: Lint
run: uv run ruff check .
# `--check` reports formatting instead of rewriting it; CONTRIBUTING.md
# tells contributors to run `uv run ruff format .` locally to fix it.
- name: Check formatting
run: uv run ruff format --check .
- name: Type check
run: uv run mypy .
# 3.14 is the only supported interpreter (ADR-0054), so the matrix has one entry;
# it stays a matrix so a future interpreter is a one-line addition. Ubuntu only
# for now.
#
# This is the one job with a longer timeout than the others. The full suite
# under coverage takes 10-18 minutes depending on the runner and interpreter,
# and slow runners were cancelled under earlier 10- and 20-minute limits:
# a failure that says nothing about the code. The limit exists to stop a hung
# test, and 30 minutes still does that with headroom over the slowest run.
test:
name: test (${{ matrix.python-version }})
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
# Report every interpreter even when one fails. The difference between
# "3.14 alone is red" and "all are red" is the whole reason the matrix
# exists, and cancelling a sibling job hides it.
fail-fast: false
matrix:
python-version: ["3.14"]
steps:
# Full history, so the API_VERSION bump check can find the merge base with
# origin/main (tests/unit/api/test_api_surface.py). A pull request checkout
# already contains its own regenerated snapshot, so only the base branch's
# snapshot can tell a breaking diff from no diff.
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
with:
python-version: ${{ matrix.python-version }}
enable-cache: true
- name: Install dependencies
run: uv sync --locked
# `--cov` enables the coverage gate configured in pyproject.toml
# ([tool.coverage.report] fail_under). The flag stays out of pytest's
# addopts so plain local runs stay fast, and a local `uv run pytest --cov`
# gates identically to this step.
# This job collects `tests/smoke/` too, as part of the full suite, and
# runs it against the EDITABLE install -- so it declares that, the
# same way the cross-platform smoke job declares the wheel. Both
# directions are asserted, which is what stops the provenance check
# from being satisfiable by doing nothing.
- name: Run tests with coverage
env:
OPENKOS_SMOKE_EXPECT_WHEEL: "0"
# Locally the bump check skips when no base ref is reachable; in CI a
# missing base must fail, or the check would skip where it must enforce.
OPENKOS_REQUIRE_API_BASE_REF: "1"
run: uv run pytest --cov
# The eval harnesses carry their own `--self-test` entry points, and until
# #831 nothing ran them. Two were red — one since #754 gave the judge a retry,
# one since #719 renamed a report label — for as long as it took someone to
# run them by hand. A self-test nobody runs reads as a guarantee while
# guaranteeing nothing, and does so exactly when a number from that harness
# is being trusted.
#
# One interpreter, not the matrix: these check that a harness still agrees
# with production, which is a property of the code and not of the runtime.
# The whole sweep takes about two seconds because every self-test is
# model-free by construction — and the runner poisons `OLLAMA_HOST` to a
# closed port so that stays a check rather than a claim.
self-tests:
name: eval harness self-tests
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
with:
python-version: "3.14"
enable-cache: true
- name: Install dependencies
run: uv sync --locked
# Harnesses are discovered, never listed: a checked-in list would leave a
# new harness silently unguarded, which is the same rot this job exists to
# catch. Discovering none is itself a failure.
- name: Run harness self-tests
run: uv run python evals/run_self_tests.py
# Packaging is validated before any release exists: `uv build` exercises the
# uv_build backend, and installing the wheel into an isolated environment
# proves the artifact itself is sound (the console script resolves and runs)
# — not just that the editable dev install happens to work.
build:
name: build (uv build + wheel smoke test)
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
with:
python-version: "3.14"
enable-cache: true
- name: Build sdist and wheel
run: uv build
# A green build is not a legally complete distribution. Both artifacts
# carried `License-Expression: Apache-2.0` while containing no LICENSE
# file at all, which Apache-2.0 section 4 requires of every
# redistribution (#924). Metadata cannot prove this -- only the archive
# contents can -- so this step reads inside both artifacts rather than
# trusting the backend to have honoured `license-files`.
- name: License file ships in both artifacts
run: |
shopt -s nullglob
sdists=(dist/openkos-*.tar.gz)
wheels=(dist/openkos-*.whl)
test "${#sdists[@]}" -eq 1 && test "${#wheels[@]}" -eq 1 || {
echo "license check: expected exactly one sdist and one wheel" >&2
exit 1
}
tar -tzf "${sdists[0]}" | grep -qx 'openkos-[^/]*/LICENSE' || {
echo "::error::sdist ${sdists[0]} ships no LICENSE file" >&2
exit 1
}
unzip -l "${wheels[0]}" | grep -qE 'openkos-[^/]*\.dist-info/licenses/LICENSE$' || {
echo "::error::wheel ${wheels[0]} ships no LICENSE file" >&2
exit 1
}
echo "LICENSE present in both the sdist and the wheel."
# `--isolated --no-project` installs only the built wheel, so invoking
# the console script exercises the artifact's own entry-point metadata
# rather than the checkout or the dev environment.
- name: Wheel smoke test
run: |
# Resolve the glob to exactly one wheel up front: with a stale second
# wheel in `dist/`, an unquoted `--with dist/openkos-*.whl` would expand
# to two arguments and fail with an unrelated-looking spawn error.
# `nullglob` makes the no-wheel case collapse to an empty array so the
# guard below reports it, instead of passing the literal pattern through.
shopt -s nullglob
wheels=(dist/openkos-*.whl)
test "${#wheels[@]}" -eq 1 || {
echo "wheel smoke test: expected exactly one wheel, found ${wheels[*]}" >&2
exit 1
}
wheel="${wheels[0]}"
uv run --isolated --no-project --with "$wheel" openkos --help
# `--version` exits 0 even on its degraded `openkos unknown` fallback, so
# asserting the exit status alone would let a wheel whose distribution
# metadata is unreadable pass this step green. Compare the printed line
# against the version in the built wheel's own filename instead -- that is
# what makes this step proof the flag reads real packaged metadata.
expected="openkos $(basename "$wheel" | cut -d- -f2)"
actual=$(uv run --isolated --no-project --with "$wheel" openkos --version)
test "$actual" = "$expected" || {
echo "wheel smoke test: expected '$expected', got '$actual'" >&2
exit 1
}
# The unit suite runs `init` against the editable install, where
# `src/openkos/templates/` is just a directory on disk -- it would
# stay green even if a packaging change silently dropped the
# templates from the wheel. This step is the only proof the
# templates actually ship: run `init` against the isolated wheel
# itself and assert all five artifacts land.
- name: Init smoke test (packaging proof for templates/)
run: |
workdir="$(mktemp -d)"
cd "$workdir"
uv run --isolated --no-project --with "${GITHUB_WORKSPACE}"/dist/openkos-*.whl openkos init
test -d raw
test -f bundle/index.md
test -f bundle/log.md
test -f openkos.yaml
test -f AGENTS.md
# The wheel is installed WITHOUT the `[api]` extra, so `openkos serve`
# must fail closed: exit 2, the install remedy on stderr, nothing on
# stdout. `openkos --help` already ran above against this same install,
# which is what proves the extra's packages are never imported at
# start-up.
- name: Serve without the api extra (missing-extra remedy)
run: |
workdir="$(mktemp -d)"
cd "$workdir"
uv run --isolated --no-project --with "${GITHUB_WORKSPACE}"/dist/openkos-*.whl openkos init
set +e
uv run --isolated --no-project --with "${GITHUB_WORKSPACE}"/dist/openkos-*.whl \
openkos serve --workspace "$workdir" </dev/null >stdout.txt 2>stderr.txt
status=$?
set -e
test "$status" -eq 2 || {
echo "::error::openkos serve exited $status without the extra, expected 2" >&2
cat stderr.txt >&2
exit 1
}
grep -q 'openkos\[api\]' stderr.txt || {
echo "::error::stderr does not name openkos[api]" >&2
cat stderr.txt >&2
exit 1
}
test ! -s stdout.txt || {
echo "::error::stdout is not empty" >&2
cat stdout.txt >&2
exit 1
}
# The desktop shell (`desktop/`, ADR-0055) is a Rust crate the Python jobs
# never see. Its supervisor tests spawn real processes -- fake engines that
# speak the `openkos serve` handshake through POSIX `sh` -- including one that
# SIGKILLs a shell holding an engine and asserts the engine exits, which is
# the orphan guarantee itself. A second, ignored-by-default test then runs the
# shell against the REAL engine of this checkout, so a change to the engine's
# handshake or its Origin allowlist breaks here rather than in the app. No
# model is involved. The job runs beside the Python suite, not after it, so
# it adds no time to a pull request's wall clock.
#
# Tauri links the system web view, which on Linux is WebKitGTK: those are the
# packages below. The runner image's preinstalled rustup provides the
# toolchain; `--locked` fails on a Cargo.lock that has drifted from
# Cargo.toml, as `uv sync --locked` does for uv.lock. The Rust build cache is
# keyed on that lock file.
desktop:
name: desktop shell (fmt, clippy, test)
runs-on: ubuntu-latest
timeout-minutes: 20
defaults:
run:
working-directory: desktop/src-tauri
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Install the system web view
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
libwebkit2gtk-4.1-dev libxdo-dev libssl-dev librsvg2-dev
- name: Install the Rust toolchain
run: |
rustup toolchain install stable --profile minimal --component clippy,rustfmt
rustup default stable
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
workspaces: desktop/src-tauri
# Tauri embeds the built page (desktop/dist, its frontendDist) at compile
# time and refuses to compile without it (ADR-0065), so it is built first.
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
with:
package_json_file: desktop/package.json
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version-file: desktop/.node-version
cache: pnpm
cache-dependency-path: desktop/pnpm-lock.yaml
- name: Build the page
working-directory: desktop
run: |
pnpm install --frozen-lockfile
pnpm build
- name: Check formatting
run: cargo fmt --check
- name: Lint
run: cargo clippy --locked --all-targets -- -D warnings
- name: Run tests
run: cargo test --locked
# The engine the live test starts is `uv run openkos serve` in this
# checkout, so it needs the same environment the Python jobs install.
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
with:
python-version: "3.14"
enable-cache: true
- name: Install the engine
working-directory: .
run: uv sync --locked
- name: Run the shell against the real engine
run: cargo test --locked --test live_engine -- --ignored
# The desktop web view's client for the local API (desktop/client/,
# ADR-0065). Its types are generated from the committed OpenAPI snapshot
# (tests/unit/localapi/openapi.json), which the Python suite holds equal to
# the document the engine serves; `check:schema` holds the committed types
# equal to the snapshot, so an API change that regenerates the snapshot but
# not the client fails here. The typecheck then fails on any field the client
# or the page uses that the API no longer has, and the tests hold the
# client's error codes equal to the engine's table, and the page is built as
# the app bundles it. `--frozen-lockfile` fails on a pnpm-lock.yaml that has
# drifted from package.json, as `uv sync --locked` does for uv.lock. pnpm's version comes from `packageManager` and
# Node's from desktop/.node-version. No engine and no model are involved.
desktop-client:
name: desktop client (typecheck, test, schema, build)
runs-on: ubuntu-latest
timeout-minutes: 10
defaults:
run:
working-directory: desktop
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
with:
package_json_file: desktop/package.json
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version-file: desktop/.node-version
cache: pnpm
cache-dependency-path: desktop/pnpm-lock.yaml
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Type check
run: pnpm typecheck
- name: Run tests
run: pnpm test
- name: Check the client against the OpenAPI snapshot
run: pnpm check:schema
- name: Build the page
run: pnpm build
# Every job above runs on `ubuntu-latest` only. macOS and Windows behavior
# is otherwise UNVERIFIED until a user hits it, and this repo has already
# paid for that twice: platform-gated tests that first ran on Linux cost
# two fix cycles on PR #492, and HFS+/SMB normalization differences
# produced real graph-edge bugs (#430). A full suite times three
# platforms on every PR is not warranted -- most of the suite exercises
# platform-independent logic Linux already covers -- but zero
# cross-platform signal is not acceptable either. This runs a REDUCED,
# explicitly marked subset instead (`-m cross_platform_smoke`, defined in
# `pyproject.toml`) that targets what Linux structurally cannot:
# filesystem normalization, symlink/permission privilege boundaries, the
# workspace lock's `fcntl`/`msvcrt` branches, SQLite WAL behavior, and
# `init`/`ingest`/`doctor` against the packaged wheel.
#
# No path filter here either, for the same reason the top of this file
# gives for the whole workflow: a job skipped by a path filter reports no
# status at all, and a required check that never reports blocks a pull
# request indefinitely. The subset is deliberately kept small (currently
# ~30 tests, single-digit seconds) specifically so that running it on
# EVERY pull request, unconditionally, stays cheap -- the same tradeoff
# the top-of-file comment makes, applied at job scope instead of workflow
# scope. Growing this marker into a second full suite would break that
# tradeoff; see the marker's own registration comment in pyproject.toml.
cross-platform-smoke:
name: cross-platform smoke (${{ matrix.os }})
runs-on: ${{ matrix.os }}
timeout-minutes: 10
strategy:
# Report both platforms even when one fails -- same reasoning as the
# `test` job's own matrix: a Windows-only red must never hide behind
# a cancelled macOS run, or vice versa.
fail-fast: false
matrix:
os: [macos-latest, windows-latest]
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
with:
python-version: "3.14"
enable-cache: true
# Dev dependencies (pytest, typer's test client, ...) plus an
# EDITABLE `openkos` -- overwritten by the packaged wheel below.
# Installing them first, separately, is what lets the wheel step
# stay a plain single-package `uv pip install` rather than having to
# reassemble the whole test-dependency set around an isolated wheel
# install.
- name: Install dependencies
run: uv sync --locked
- name: Build the wheel
run: uv build
# Overwrites the editable `openkos` with the WHEEL `uv build` just
# produced, in the SAME environment pytest runs in below --
# `--reinstall-package` touches only that one package, leaving
# pytest/typer/etc. from the `uv sync` above untouched. This is the
# "install the built wheel" this job exists to prove: every test
# below now imports the packaged distribution, not the checkout --
# the same concern the Linux `build` job's own wheel/init smoke
# steps exist for, extended here to macOS/Windows packaging.
#
# `shell: bash` is explicit (Windows runners default to PowerShell,
# which does not understand this syntax) -- the nullglob/array guard
# mirrors the `build` job's own wheel-smoke-test step above, for the
# same reason: an unquoted glob against a stale second wheel would
# expand to two arguments and fail with an unrelated-looking error.
- name: Install the built wheel over the editable dev install
shell: bash
run: |
shopt -s nullglob
wheels=(dist/openkos-*.whl)
test "${#wheels[@]}" -eq 1 || {
echo "cross-platform smoke: expected exactly one wheel, found ${wheels[*]}" >&2
exit 1
}
uv pip install --reinstall-package openkos "${wheels[0]}"
# `uv run` resyncs the project environment against `pyproject.toml`
# before every invocation by default -- which would silently
# reinstall the EDITABLE `openkos` over the wheel the previous step
# just installed, undoing it without printing an error. `--no-sync`
# is what makes the wheel install actually stick for this run
# (confirmed empirically while writing this job: omitting the flag
# reverts to the editable install before pytest even starts
# collecting, silently).
#
# `-m cross_platform_smoke` selects the marked subset; pytest itself
# is the guard against a false green here -- deselecting every test
# (a typo'd marker name, or a future change that strips the last
# marked test) makes pytest report "no tests collected" and exit 5,
# not 0, so an empty subset FAILS this step rather than passing
# having run nothing.
# `OPENKOS_SMOKE_EXPECT_WHEEL=1` is what makes the wheel install above
# self-verifying instead of load-bearing-but-unasserted: without it
# every test in `tests/smoke/` would still pass against the editable
# checkout if `--no-sync`, the reinstall, or the build regressed, and
# this job would report green having proved nothing about packaging.
# An unset declaration is a hard FAILURE under CI, never a skip --
# see `test_the_openkos_under_test_is_the_declared_install`.
- name: Cross-platform smoke subset
shell: bash
env:
OPENKOS_SMOKE_EXPECT_WHEEL: "1"
run: uv run --no-sync pytest -m cross_platform_smoke -v
# The desktop app ships the engine frozen with PyInstaller inside its bundle
# (ADR-0056), and a freeze breaks only at run time: a hidden import it
# missed, a native extension left behind, an interpreter that is not 3.14.
# So this job builds the app exactly as a contributor does
# (`desktop/scripts/build-app.sh`): it freezes the engine from uv.lock on a
# uv-managed 3.14, bundles the shell with the freeze under
# Contents/Resources/engine, deep ad-hoc re-signs and verifies the .app,
# checks the interpreter inside it, and runs the smoke check against the
# engine inside the .app -- it starts and answers `/v1/status`, is one
# process, exits on stdin EOF and when its parent is killed, and loads
# sqlite-vec -- before writing the .dmg. Any of those failing fails the job.
# No model is involved.
#
# Apple Silicon only, the one platform the freeze targets; PyInstaller is not
# a cross-compiler, so this cannot run on the Linux runners. No path filter,
# for the reason the top of this file gives. The Tauri CLI is pinned and,
# like the release build of the shell, kept by the Rust cache, which also
# caches `~/.cargo/bin`.
desktop-bundle:
name: desktop bundle (macOS)
runs-on: macos-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
with:
python-version: "3.14"
enable-cache: true
- name: Install the Rust toolchain
run: |
rustup toolchain install stable --profile minimal
rustup default stable
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
workspaces: desktop/src-tauri
key: bundle
# build-app.sh builds the page with pnpm before it bundles (ADR-0065).
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
with:
package_json_file: desktop/package.json
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version-file: desktop/.node-version
cache: pnpm
cache-dependency-path: desktop/pnpm-lock.yaml
- name: Install the Tauri CLI
run: cargo install tauri-cli --version 2.12.3 --locked
- name: Build the app and smoke-check its engine
run: desktop/scripts/build-app.sh