CI and releases¶
What GitHub Actions runs on your pull request, and how releases, nightly
builds, the docs site and link checks work. Workflows live in
.github/workflows/.
CI on every PR¶
ci.yml runs on every pull request and every push to main. Each job maps
to a recipe you can run before pushing:
CI job |
Local equivalent |
Needs |
|---|---|---|
|
|
Python, |
|
|
Docker |
|
|
Flutter SDK |
|
|
Docker, Flutter SDK, Chromium |
|
none (merges the numbers from |
Notes:
checkalso fails ifuv.lockis stale. After editing apyproject.tomldependency, runuv lock.e2euploads a screenshot when a browser test fails (artifacte2e-debug).See Environment setup for tool requirements and Running and testing for what each recipe does.
Coverage¶
Backend and mcp_server are measured with pytest-cov and must stay at 100%
line and branch coverage. The Flutter frontend (just frontend coverage) must
stay at or above 50% line coverage. e2e is not measured. No external service
is involved.
PR comment. The
coveragejob (.github/scripts/coverage_report.py) comments backend, MCP server and frontend coverage on your PR, compared with the latest run onmain. It updates the same comment on each push. On PRs from forks the token is read-only, so the comment may be missing; the numbers are still on each job’s summary page.Reports. Per-file tables appear on the run’s summary page. The
checkandfrontendjobs upload browsable HTML (backend-coverage-html,frontend-coverage-html).Locally.
just backend coverage,just mcp_server coverageandjust frontend coveragewrite the same reports.
PR and issue templates¶
.github/pull_request_template.md asks for what and why, a passing
just check, and tests. .github/ISSUE_TEMPLATE/ has bug-report and
feature-request forms. Blank issues are off, and the config sends security
reports to a private advisory form.
Documentation site¶
docs.yml runs on push to main (or manually). It runs just install and
just docs docs, then publishes docs/_build/html to GitHub Pages.
just docs docs # build; warnings are errors (-W --keep-going)
just docs docs-serve # build, then serve on http://127.0.0.1:8000
A broken cross-link or toctree entry fails the same way locally and in CI.
just check includes the build.
Link checking¶
lychee checks links in README.md,
CONTRIBUTING.md, AGENTS.md and docs/**. Its config, including each
exclusion and why, is lychee.toml.
Offline, in
just check: thelychee-offlinepre-commit hook checks relative links and#anchors. No network, so it cannot flake.Online, not in
just check:just check-linksalso fetches external URLs.links.ymlruns it weekly, on demand, and on PRs that touch Markdown. It is not part of the blockingcheckjob, because a third-party outage must not block unrelated PRs. Exclude a flaky-but-valid host inlychee.tomlwith a comment.
The lychee-bin version is pinned in both .pre-commit-config.yaml and the
root justfile. Bump both together.
Other lint hooks (run by just check)¶
hadolintlints theDockerfile.shellcheck(--severity=style) andshfmt(2-space indent, check only) lint shell scripts inbackend/,packaging/linux/andtools/. Files with a shebang must be executable. Fix formatting withuvx --from shfmt-py shfmt -i 2 -w <file>.markdownlint-cli2lints Markdown; rules are in.markdownlint-cli2.yaml.
All are pre-commit hooks installed as wheels or via uvx, so nothing needs
installing system-wide. just hooks installs them as git pre-commit hooks.
Releases¶
A release is a Git tag named v<version> (for example v1.2.3). Pushing the
tag triggers release.yml. There is no manual build step.
just release dry # print the next patch tag (v1.2.3 -> v1.2.4) without creating it
just release # create and push that tag at HEAD
The workflow builds these in parallel:
Docker image (backend plus built web app), pushed to
ghcr.io/<owner>/dinatosas<version>andlatest(linux/amd64only). TheDockerfilebuilds the Flutter web app itself in an earlier stage.Linux desktop client as a
.deband an.AppImage, from oneflutter build linux --releasebundle.Android APK, debug-signed. A real release keystore is not set up yet.
A version job strips the leading v once so every artifact and the GitHub
Release agree on the version. A final release job gathers everything into one
GitHub Release.
Caveats:
First push to GHCR. The package defaults to private even if the repo is public. Make it public once in the package’s GitHub settings.
No rollback. If a later step fails (for example the
.debafter the Docker image was pushed), the partial release stays on GHCR. Test changes topackaging/linux/or theDockerfilelocally before tagging.
Building the artifacts locally¶
The workflow calls these same recipes:
just packaging build-apk <version> [build-number]
just packaging build-deb <version>
just packaging build-appimage <version>
just packaging build-linux-artifacts <version> # .deb and AppImage, one shared build
just docker build # the Docker image
The APK lands in frontend/build/app/outputs/flutter-apk/. Linux packages
go to dist/, or to the directory you pass as the second argument. The
Linux build needs the apt packages listed in
Environment setup.
Nightly releases¶
nightly.yml runs daily at 02:17 UTC, or manually (the force input
releases even with no new commits). If main has no commits since the last
nightly, it does nothing. Otherwise it calls release.yml to publish a GitHub
prerelease with the same image, packages and APK. No v* tag is needed.
Item |
Value |
|---|---|
Tag |
|
Version |
newest |
Python package version |
|
Docker tags |
the version and a moving |
Retention |
the 14 newest nightlies and their tags. Old images on GHCR are not pruned |
Why not a v* tag: it would trigger release.yml as a stable release and sort
among real versions. A tag pushed with GITHUB_TOKEN cannot start a workflow
anyway, so the nightly calls release.yml directly (workflow_call).
The Docker build stamps the version into the backend’s pyproject.toml, so
the API, its OpenAPI document and backups report it.