halkyon/tool/check.sh

55 lines
1.9 KiB
Shell
Raw Normal View History

#!/usr/bin/env bash
# The checks every push has to pass: formatting, static analysis (infos are fatal too), and
ci: check every push on sahaquiel and deploy a green main Pushes to main were deployed by hand. Now Forgejo Actions runs tool/check.sh on every push, and a green main deploys learn.halkyon.eu by itself. The runner (forgejo-runner v13.1.0) runs on sahaquiel as the unprivileged user `runner`, with jobs directly on the host: containers would take Docker access, which is root on the production server, and Forgejo warns that host jobs have no isolation from the runner's user. So its systemd unit (deploy/runner/forgejo-runner.service) provides it: /home and the Docker socket invisible, Docker's container networks unreachable, no capabilities, two cores and 3 GB at most, lowest priority. It is registered to this repository only, with the UUID and token of Forgejo's "Create new runner" (the old `register` is deprecated); the token reaches it as a systemd credential. The configuration (deploy/runner/config.yml) was dry-run against v13.1.0, the unit rates 1.8 in `systemd-analyze security`. CI runs on pushes only: a pull request from a fork would run its code on the production server. The deploy job calls deploy/redeploy.sh as `deploy`, through a key that authorized_keys pins to that script and to connections from sahaquiel itself. The script deploys the commit CI checked while it is still main's tip, as a fast-forward, only if every new commit is signed by a key in deploy/allowed-signers (as the deployed checkout lists them), after dumping the database (the last ten are kept), and fails unless the server answers afterwards; a failure puts the checkout back. Tested against the local stack: refusals of a malformed argument, a stale commit, a forged unsigned commit and a non-fast-forward, a real deploy with its dump, a repeat, and a failed build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-09-30 21:49:04 +02:00
# the server's and the app's tests. The pre-push hook runs this script, and so does CI on
# every push (.forgejo/workflows/ci.yaml).
#
# Install the hook in a fresh clone: ln -s ../../tool/check.sh .git/hooks/pre-push
# Skip it for one push: git push --no-verify
#
# It checks the working tree, so uncommitted changes count as well.
set -euo pipefail
cd "$(git rev-parse --show-toplevel)"
# As the pre-push hook, first: commits going to main must be signed as deploying it requires
# (tool/check_signatures.sh), which takes a moment, where the rest takes minutes.
if [ "$(basename "$0")" = pre-push ]; then
tool/check_signatures.sh "$@"
fi
if ! git diff --quiet HEAD; then
echo 'Note: the working tree has uncommitted changes; they are checked too.'
fi
feat(app): the UI foundation: theme, tabs and routes, strings The app gets its look and navigation before more features land on them. - Theme (core/theme/): hand-written light and dark Material 3 colour schemes around cobalt and copper; ThemeExtras for the colours Material has no role for (states, the four FSRS ratings); the 20 colours a person can give content. Tests hold text to WCAG AA contrast on each palette colour and on the dark theme's primary colour and surface. - Layout and shared widgets: Material 3's window-size classes (core/layout/); SectionCard, AppLoadingIndicator, confirmDestructiveAction and FoundById (core/widget/). Lucide icons. - Navigation with go_router: four tabs (Home, Library, Explore, Profile), each keeping its own stack of screens, as a navigation rail with the app's icon from 600 dp and as a navigation bar below; choosing the open tab again returns to its first screen. A studyset's screen sits in the library; studying, cards and settings cover the tabs. - Routes carry ids: FoundById finds the item in the repository's live list, so a studyset deleted on another device, or an old link, says it no longer exists. A screen that deletes its own item keeps showing it while it closes, rather than flashing that message. - Every person gets an app of their own (PersonApp, lib/src/app/): a MaterialApp.router with its own GoRouter and PersonScope around the navigator, built by SessionGate inside the person's keyed subtree. Screens read the person's repositories wherever they are built, so the static route() helpers and the listener that closed every pushed screen on a change of person are gone: a change of person now disposes the whole router and starts again at home. - The account dialog becomes the Profile tab, with Settings (sign out, delete the account, About) behind a gear. A waiting GitHub sign-in now keeps waiting while another tab shows; a change of person still cancels it. - The new screens' strings come from Flutter's gen-l10n (English for now). The generated files are committed, left out of analysis and formatting, and written anew by `flutter pub get`, so CI's check of generated code also catches stale ones. Widget tests now start the whole app (test/support/pump_app.dart) at the screen's path and move through it as a person would, by tabs and taps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 00:18:31 +02:00
# Our own Dart files. Generated code is left out: the client package as a whole, the
# server's generated sources and test tools, and the app's localizations (gen-l10n). (A
# loop instead of mapfile: macOS ships bash 3.)
own=()
while IFS= read -r file; do own+=("$file"); done < <(
git ls-files '*.dart' |
feat(app): the UI foundation: theme, tabs and routes, strings The app gets its look and navigation before more features land on them. - Theme (core/theme/): hand-written light and dark Material 3 colour schemes around cobalt and copper; ThemeExtras for the colours Material has no role for (states, the four FSRS ratings); the 20 colours a person can give content. Tests hold text to WCAG AA contrast on each palette colour and on the dark theme's primary colour and surface. - Layout and shared widgets: Material 3's window-size classes (core/layout/); SectionCard, AppLoadingIndicator, confirmDestructiveAction and FoundById (core/widget/). Lucide icons. - Navigation with go_router: four tabs (Home, Library, Explore, Profile), each keeping its own stack of screens, as a navigation rail with the app's icon from 600 dp and as a navigation bar below; choosing the open tab again returns to its first screen. A studyset's screen sits in the library; studying, cards and settings cover the tabs. - Routes carry ids: FoundById finds the item in the repository's live list, so a studyset deleted on another device, or an old link, says it no longer exists. A screen that deletes its own item keeps showing it while it closes, rather than flashing that message. - Every person gets an app of their own (PersonApp, lib/src/app/): a MaterialApp.router with its own GoRouter and PersonScope around the navigator, built by SessionGate inside the person's keyed subtree. Screens read the person's repositories wherever they are built, so the static route() helpers and the listener that closed every pushed screen on a change of person are gone: a change of person now disposes the whole router and starts again at home. - The account dialog becomes the Profile tab, with Settings (sign out, delete the account, About) behind a gear. A waiting GitHub sign-in now keeps waiting while another tab shows; a change of person still cancels it. - The new screens' strings come from Flutter's gen-l10n (English for now). The generated files are committed, left out of analysis and formatting, and written anew by `flutter pub get`, so CI's check of generated code also catches stale ones. Widget tests now start the whole app (test/support/pump_app.dart) at the screen's path and move through it as a person would, by tabs and taps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 00:18:31 +02:00
grep -v -E '^halkyon_learn_client/|/lib/src/generated/|/serverpod_test_tools\.dart$|/lib/l10n/app_localizations'
)
echo '▸ Formatting'
if [ ${#own[@]} -gt 0 ]; then
dart format --output=none --set-exit-if-changed "${own[@]}"
fi
echo '▸ Analysis'
dart analyze --fatal-infos
echo '▸ Server tests'
if git ls-files 'halkyon_learn_server/test/*_test.dart' | grep -q .; then
(cd halkyon_learn_server && dart test)
else
echo 'No server tests yet.'
fi
echo '▸ Curator tests'
(cd halkyon_learn_curator && dart test)
echo '▸ App tests'
test(app): studysets synced against a running server The first tests that need serverpod start running (ADR 0001, rule 9: the client's and the server's sync engines can't share one process), tagged requires-server: tool/check.sh skips them, flutter test -t requires-server runs them. Every device is its own client and local database with the app's real gateway and repository, and syncs only when a test says so. They state: two devices of one person end up with the same studysets; a deleted studyset leaves the other devices, and its payload is blank on the server, checked with SQL against the development database because the engine keeps deleted rows; a studyset written into another person's space never reaches them; claiming another person's space shows none of their studysets, since the server sends only the spaces the signed-in person has access to. As written here, the forgery test also expects the server to refuse and log the write, and the SQL checks connect over TCP; the first run against the server corrected both (see the commit "the sync tests pass against the running server"). They live in the app package: the server's Docker build resolves its dependencies with only the server package present, so a dev dependency on the client package would break it. They never initialize a Flutter test binding, which would replace real HTTP with a mock. Without a server they fail at sign-in with "connection refused". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-09-30 12:15:30 +02:00
# The sync tests need `serverpod start` running; run them by hand with
# `flutter test -t requires-server` in the app package.
(cd halkyon_learn_flutter && flutter test -x requires-server)
echo '✓ All checks passed.'