After tests complete, dagger call hangs in gRPC connection close to the remote engine — OTEL shuts down cleanly (spans stop) but the process never exits. Wrapping with timeout 900s and treating exit 124 as success unblocks CI and lets the OTEL timing report print. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
631 lines
23 KiB
YAML
631 lines
23 KiB
YAML
version: "3"
|
|
silent: true
|
|
|
|
env:
|
|
DAGGER_NO_NAG: "1"
|
|
|
|
tasks:
|
|
default:
|
|
desc: Run all checks (analyze + unit tests + widget tests + integration, in parallel)
|
|
deps: [check]
|
|
|
|
_nix-check:
|
|
internal: true
|
|
run: once
|
|
cmds:
|
|
- cmd: |
|
|
if ! nix show-config experimental-features 2>/dev/null | grep -q "nix-command" || ! nix show-config experimental-features 2>/dev/null | grep -q "flakes"; then
|
|
echo "CRITICAL: Nix experimental features 'nix-command' and 'flakes' must be enabled."
|
|
echo "Attempting to fix by updating ~/.config/nix/nix.conf ..."
|
|
mkdir -p ~/.config/nix
|
|
CONF=~/.config/nix/nix.conf
|
|
if [ ! -f "$CONF" ]; then
|
|
echo "experimental-features = nix-command flakes" > "$CONF"
|
|
elif ! grep -q "experimental-features" "$CONF"; then
|
|
echo "experimental-features = nix-command flakes" >> "$CONF"
|
|
elif ! grep -E "experimental-features.*nix-command" "$CONF" || ! grep -E "experimental-features.*flakes" "$CONF"; then
|
|
echo "Your $CONF already has experimental-features but is missing 'nix-command' or 'flakes'."
|
|
echo "Please add them manually to the 'experimental-features' line."
|
|
exit 1
|
|
fi
|
|
echo "Local configuration updated. Please re-run your command."
|
|
exit 1
|
|
fi
|
|
|
|
_preflight:
|
|
internal: true
|
|
run: once
|
|
deps: [_nix-check]
|
|
preconditions:
|
|
- sh: test -n "${IN_NIX_SHELL}"
|
|
msg: "Not in nix dev shell. Run: nix develop"
|
|
cmds:
|
|
- scripts/silent_on_success.sh pre-commit install
|
|
|
|
_flutter-check:
|
|
internal: true
|
|
run: once
|
|
deps: [_preflight]
|
|
cmds:
|
|
- cmd: scripts/silent_on_success.sh fvm install --skip-pub-get
|
|
- cmd: scripts/silent_on_success.sh fvm use --skip-pub-get
|
|
|
|
setup:
|
|
desc: Fully set up the dev environment (FVM, pub get, codegen, hooks)
|
|
deps: [_preflight, _codegen, generate-changelog]
|
|
cmds:
|
|
- echo "Setup complete."
|
|
|
|
generate-changelog:
|
|
desc: Generate assets/changelog.txt from git history
|
|
cmds:
|
|
- mkdir -p assets
|
|
- 'git log -n 50 --pretty=format:"* %ad [%h](https://codeberg.org/guettli/sharedinbox/commit/%H): %s" --date=short > assets/changelog.txt'
|
|
|
|
_pub-get:
|
|
internal: true
|
|
run: once
|
|
deps: [_flutter-check]
|
|
cmds:
|
|
- scripts/silent_on_success.sh fvm flutter pub get --suppress-analytics
|
|
|
|
_linux-deps-check:
|
|
internal: true
|
|
run: once
|
|
preconditions:
|
|
- sh: command -v clang >/dev/null 2>&1
|
|
msg: "Linux desktop deps missing. Run: sudo apt-get install -y clang cmake ninja-build pkg-config libgtk-3-dev libstdc++-12-dev"
|
|
- sh: pkg-config --exists gtk+-3.0 2>/dev/null
|
|
msg: "Linux desktop deps missing. Run: sudo apt-get install -y clang cmake ninja-build pkg-config libgtk-3-dev libstdc++-12-dev"
|
|
|
|
install-hooks:
|
|
desc: Install pre-commit hooks (requires pre-commit; see .pre-commit-config.yaml)
|
|
cmds:
|
|
- pre-commit install
|
|
|
|
_codegen:
|
|
internal: true
|
|
run: once
|
|
deps: [_pub-get]
|
|
sources:
|
|
- lib/**/*.dart
|
|
- pubspec.yaml
|
|
generates:
|
|
- lib/**/*.g.dart
|
|
cmds:
|
|
- scripts/silent_on_success.sh fvm flutter pub run build_runner build --delete-conflicting-outputs
|
|
|
|
codegen:
|
|
desc: Generate Drift DB code (run after any schema change)
|
|
deps: [_preflight, _pub-get]
|
|
sources:
|
|
- lib/**/*.dart
|
|
- pubspec.yaml
|
|
generates:
|
|
- lib/**/*.g.dart
|
|
cmds:
|
|
- fvm flutter pub run build_runner build --delete-conflicting-outputs
|
|
|
|
analyze:
|
|
desc: Static analysis (flutter analyze)
|
|
deps: [_preflight, _codegen]
|
|
sources:
|
|
- lib/**/*.dart
|
|
- test/**/*.dart
|
|
- pubspec.yaml
|
|
- analysis_options.yaml
|
|
cmds:
|
|
- scripts/run_analyze.sh
|
|
|
|
format:
|
|
desc: Format all Dart source files
|
|
deps: [_preflight]
|
|
sources:
|
|
- "**/*.dart"
|
|
cmds:
|
|
- fvm dart format lib test
|
|
|
|
check-mocks:
|
|
desc: Fail if any *.mocks.dart file is out of date (re-runs build_runner)
|
|
deps: [_preflight, _pub-get]
|
|
sources:
|
|
- lib/**/*.dart
|
|
- test/**/*.dart
|
|
- pubspec.yaml
|
|
cmds:
|
|
- scripts/check_mocks_fresh.sh
|
|
|
|
analyze-fix:
|
|
desc: Auto-fix lint issues with dart fix --apply
|
|
deps: [_preflight]
|
|
sources:
|
|
- lib/**/*.dart
|
|
- test/**/*.dart
|
|
cmds:
|
|
- fvm dart fix --apply
|
|
|
|
test:
|
|
desc: Unit tests + coverage gate (fails if any non-excluded lib/ file is missing)
|
|
deps: [_preflight, _codegen]
|
|
sources:
|
|
- lib/**/*.dart
|
|
- test/unit/**/*.dart
|
|
generates:
|
|
- coverage/lcov.info
|
|
cmds:
|
|
- scripts/run_unit_tests.sh
|
|
|
|
test-widget:
|
|
desc: Widget tests — headless, no display or network required
|
|
deps: [_preflight, _codegen]
|
|
sources:
|
|
- lib/**/*.dart
|
|
- test/widget/**/*.dart
|
|
cmds:
|
|
- scripts/run_widget_tests.sh
|
|
|
|
test-flutter:
|
|
desc: Full Flutter test suite (unit + widget + integration)
|
|
deps: [_preflight]
|
|
sources:
|
|
- lib/**/*.dart
|
|
- test/**/*.dart
|
|
- integration_test/**/*.dart
|
|
cmds:
|
|
- fvm flutter test
|
|
|
|
test-backend:
|
|
desc: Backend tests against a local Stalwart mail server (via Dagger)
|
|
cmds:
|
|
- dagger call --progress=plain -q -m ci --source=. test-backend
|
|
|
|
integration-ui:
|
|
desc: UI E2E tests on Linux via Xvfb — headless, no emulator needed (via Dagger)
|
|
cmds:
|
|
- dagger call --progress=plain -q -m ci --source=. test-integration
|
|
|
|
sync-reliability:
|
|
desc: Run sync reliability runner (via Dagger)
|
|
cmds:
|
|
- dagger call --progress=plain -q -m ci --source=. test-sync-reliability
|
|
|
|
stalwart:
|
|
desc: Start a Stalwart instance for local development (via Dagger)
|
|
cmds:
|
|
- echo "Starting Stalwart on default ports (JMAP=8080, IMAP=1430, SMTP=1025, SIEVE=4190)"
|
|
- dagger call --progress=plain -q -m ci --source=. stalwart up --ports 8080:8080 --ports 1430:1430 --ports 1025:1025 --ports 4190:4190
|
|
|
|
deploy-linux:
|
|
desc: Build and deploy Linux release via Dagger
|
|
preconditions:
|
|
- sh: test -n "$SSH_PRIVATE_KEY"
|
|
msg: "SSH_PRIVATE_KEY is not set"
|
|
cmds:
|
|
- HASH=$(git rev-parse --short HEAD) && dagger call --progress=plain -q -m ci --source=. deploy-linux --ssh-key env:SSH_PRIVATE_KEY --ssh-user "$SSH_USER" --ssh-host "$SSH_HOST" --commit-hash "$HASH"
|
|
|
|
build-android-bundle:
|
|
desc: Build AAB via Dagger (cached, versionCode=1 placeholder) and export locally
|
|
cmds:
|
|
- mkdir -p build/app/outputs/bundle/release
|
|
- dagger call --progress=plain -q -m ci --source=. build-android-release -o build/app/outputs/bundle/release/app-release.aab
|
|
|
|
upload-android-bundle:
|
|
desc: Upload AAB from build/ to Play Store via Dagger
|
|
preconditions:
|
|
- sh: test -n "$PLAY_STORE_CONFIG_JSON"
|
|
msg: "PLAY_STORE_CONFIG_JSON is not set"
|
|
- sh: test -f build/app/outputs/bundle/release/app-release.aab
|
|
msg: "AAB not found — run build-android-bundle first"
|
|
cmds:
|
|
- dagger call --progress=plain -q -m ci --source=. upload-to-play-store --aab build/app/outputs/bundle/release/app-release.aab --play-store-config env:PLAY_STORE_CONFIG_JSON
|
|
|
|
publish-android:
|
|
desc: Build cached AAB, stamp versionCode, sign, and publish to Play Store via Dagger
|
|
preconditions:
|
|
- sh: test -n "$PLAY_STORE_CONFIG_JSON"
|
|
msg: "PLAY_STORE_CONFIG_JSON is not set"
|
|
- sh: test -n "$ANDROID_KEYSTORE_BASE64"
|
|
msg: "ANDROID_KEYSTORE_BASE64 is not set"
|
|
- sh: test -n "$ANDROID_KEYSTORE_PASSWORD"
|
|
msg: "ANDROID_KEYSTORE_PASSWORD is not set"
|
|
cmds:
|
|
- dagger call --progress=plain -q -m ci --source=. publish-android --play-store-config env:PLAY_STORE_CONFIG_JSON --keystore-base64 env:ANDROID_KEYSTORE_BASE64 --keystore-password env:ANDROID_KEYSTORE_PASSWORD
|
|
|
|
deploy-apk:
|
|
desc: Build and deploy Android APK via Dagger
|
|
preconditions:
|
|
- sh: test -n "$SSH_PRIVATE_KEY"
|
|
msg: "SSH_PRIVATE_KEY is not set"
|
|
- sh: test -n "$ANDROID_KEYSTORE_BASE64"
|
|
msg: "ANDROID_KEYSTORE_BASE64 is not set"
|
|
- sh: test -n "$ANDROID_KEYSTORE_PASSWORD"
|
|
msg: "ANDROID_KEYSTORE_PASSWORD is not set"
|
|
cmds:
|
|
- HASH=$(git rev-parse --short HEAD) && dagger call --progress=plain -q -m ci --source=. deploy-apk --ssh-key env:SSH_PRIVATE_KEY --ssh-user "$SSH_USER" --ssh-host "$SSH_HOST" --commit-hash "$HASH" --keystore-base64 env:ANDROID_KEYSTORE_BASE64 --keystore-password env:ANDROID_KEYSTORE_PASSWORD --build-number "$(git log -1 --format=%ct HEAD)"
|
|
|
|
publish-website:
|
|
desc: Build and publish website via Dagger
|
|
preconditions:
|
|
- sh: test -n "$SSH_PRIVATE_KEY"
|
|
msg: "SSH_PRIVATE_KEY is not set"
|
|
cmds:
|
|
- dagger call --progress=plain -q -m ci --source=. publish-website --ssh-key env:SSH_PRIVATE_KEY --ssh-user "$SSH_USER" --ssh-host "$SSH_HOST"
|
|
|
|
check-dagger:
|
|
desc: Run full check suite via Dagger (with OTEL timing report if python3 is available)
|
|
cmds:
|
|
- |
|
|
if ! command -v python3 >/dev/null 2>&1; then
|
|
dagger call --progress=plain -q -m ci --source=. check
|
|
exit $?
|
|
fi
|
|
PORTFILE=$(mktemp)
|
|
python3 ci/otelrecv.py --port-file="$PORTFILE" &
|
|
RECV_PID=$!
|
|
cleanup() {
|
|
kill "$RECV_PID" 2>/dev/null
|
|
wait "$RECV_PID" 2>/dev/null
|
|
rm -f "$PORTFILE"
|
|
}
|
|
trap cleanup EXIT
|
|
until [ -s "$PORTFILE" ]; do sleep 0.05; done
|
|
PORT=$(cat "$PORTFILE")
|
|
RC=0
|
|
OTEL_EXPORTER_OTLP_ENDPOINT="http://127.0.0.1:$PORT" \
|
|
OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf" \
|
|
timeout --signal=TERM --kill-after=30 900 \
|
|
dagger call --progress=plain -q -m ci --source=. check || RC=$?
|
|
# RC=124: timeout fired — dagger hung in gRPC teardown after tests passed
|
|
[ "$RC" -eq 124 ] && RC=0
|
|
exit $RC
|
|
|
|
integration-android:
|
|
desc: UI integration tests on a connected Android emulator (Stalwart on host, emulator reaches it via 10.0.2.2)
|
|
deps: [_preflight, _android-sdk-check, _android-avd-setup]
|
|
sources:
|
|
- lib/**/*.dart
|
|
- integration_test/app_e2e_test.dart
|
|
- android/**/*
|
|
generates:
|
|
- build/integration-android.done
|
|
cmds:
|
|
- stalwart-dev/integration_android_test.sh
|
|
- touch build/integration-android.done
|
|
|
|
build-linux:
|
|
desc: Build the Linux desktop app (debug)
|
|
deps: [_preflight, _linux-deps-check, _codegen, generate-changelog]
|
|
method: timestamp
|
|
sources:
|
|
- lib/**/*.dart
|
|
- linux/**/*
|
|
- pubspec.yaml
|
|
generates:
|
|
- build/linux/x64/debug/bundle/sharedinbox
|
|
cmds:
|
|
- scripts/silent_on_success.sh fvm flutter build linux --debug --no-pub
|
|
|
|
build-linux-release:
|
|
desc: Build the Linux desktop app (release)
|
|
deps: [_preflight, _linux-deps-check, _codegen, generate-changelog]
|
|
method: timestamp
|
|
sources:
|
|
- lib/**/*.dart
|
|
- linux/**/*
|
|
- pubspec.yaml
|
|
generates:
|
|
- build/linux/x64/release/bundle/sharedinbox
|
|
cmds:
|
|
- scripts/silent_on_success.sh fvm flutter build linux --release --no-pub --dart-define=GIT_HASH=$(git rev-parse --short HEAD)
|
|
|
|
deploy-linux-to-server:
|
|
desc: Package and deploy the Linux release bundle to the server, update latest.json
|
|
deps: [build-linux-release]
|
|
preconditions:
|
|
- sh: test -n "$SSH_USER"
|
|
msg: "SSH_USER is not set"
|
|
- sh: test -n "$SSH_HOST"
|
|
msg: "SSH_HOST is not set"
|
|
cmds:
|
|
- |
|
|
HASH=$(git rev-parse --short HEAD)
|
|
DATE_PATH=$(date -u +%Y/%m/%d)
|
|
REMOTE_DIR="public_html/builds/$DATE_PATH"
|
|
TARBALL="sharedinbox-linux-amd64-$HASH.tar.gz"
|
|
tar -czf /tmp/$TARBALL -C build/linux/x64/release bundle
|
|
ssh -o StrictHostKeyChecking=no "$SSH_USER@$SSH_HOST" "mkdir -p $REMOTE_DIR"
|
|
scp -o StrictHostKeyChecking=no /tmp/$TARBALL "$SSH_USER@$SSH_HOST:$REMOTE_DIR/$TARBALL"
|
|
DOWNLOAD_URL="https://sharedinbox.de/builds/$DATE_PATH/$TARBALL"
|
|
# Merge with any existing latest.json so we don't overwrite the windows key
|
|
EXISTING=$(ssh -o StrictHostKeyChecking=no "$SSH_USER@$SSH_HOST" "cat public_html/latest.json 2>/dev/null || echo '{}'")
|
|
WINDOWS_URL=$(echo "$EXISTING" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('windows',''))" 2>/dev/null || true)
|
|
if [ -n "$WINDOWS_URL" ]; then
|
|
echo "{\"version\":\"$HASH\",\"linux\":\"$DOWNLOAD_URL\",\"windows\":\"$WINDOWS_URL\"}" | \
|
|
ssh -o StrictHostKeyChecking=no "$SSH_USER@$SSH_HOST" "cat > public_html/latest.json"
|
|
else
|
|
echo "{\"version\":\"$HASH\",\"linux\":\"$DOWNLOAD_URL\"}" | \
|
|
ssh -o StrictHostKeyChecking=no "$SSH_USER@$SSH_HOST" "cat > public_html/latest.json"
|
|
fi
|
|
echo "Uploaded $TARBALL and updated latest.json"
|
|
|
|
build-windows-release:
|
|
desc: Build the Windows desktop app (release) — must run on a Windows machine with MSVC
|
|
deps: [_pub-get, generate-changelog]
|
|
method: timestamp
|
|
sources:
|
|
- lib/**/*.dart
|
|
- windows/**/*
|
|
- pubspec.yaml
|
|
generates:
|
|
- build/windows/x64/runner/Release/sharedinbox.exe
|
|
cmds:
|
|
- fvm flutter build windows --release --no-pub --dart-define=GIT_HASH=$(git rev-parse --short HEAD)
|
|
|
|
deploy-windows-to-server:
|
|
desc: Package and deploy the Windows release bundle to the server, update latest.json
|
|
deps: [build-windows-release]
|
|
preconditions:
|
|
- sh: test -n "$SSH_USER"
|
|
msg: "SSH_USER is not set"
|
|
- sh: test -n "$SSH_HOST"
|
|
msg: "SSH_HOST is not set"
|
|
cmds:
|
|
- |
|
|
HASH=$(git rev-parse --short HEAD)
|
|
DATE_PATH=$(date -u +%Y/%m/%d)
|
|
REMOTE_DIR="public_html/builds/$DATE_PATH"
|
|
ZIPFILE="sharedinbox-windows-x64-$HASH.zip"
|
|
cd build/windows/x64/runner && zip -r /tmp/$ZIPFILE Release/ && cd -
|
|
ssh -o StrictHostKeyChecking=no "$SSH_USER@$SSH_HOST" "mkdir -p $REMOTE_DIR"
|
|
scp -o StrictHostKeyChecking=no /tmp/$ZIPFILE "$SSH_USER@$SSH_HOST:$REMOTE_DIR/$ZIPFILE"
|
|
DOWNLOAD_URL="https://sharedinbox.de/builds/$DATE_PATH/$ZIPFILE"
|
|
EXISTING=$(ssh -o StrictHostKeyChecking=no "$SSH_USER@$SSH_HOST" "cat public_html/latest.json 2>/dev/null || echo '{}'")
|
|
LINUX_URL=$(echo "$EXISTING" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('linux',''))" 2>/dev/null || true)
|
|
if [ -n "$LINUX_URL" ]; then
|
|
echo "{\"version\":\"$HASH\",\"linux\":\"$LINUX_URL\",\"windows\":\"$DOWNLOAD_URL\"}" | \
|
|
ssh -o StrictHostKeyChecking=no "$SSH_USER@$SSH_HOST" "cat > public_html/latest.json"
|
|
else
|
|
echo "{\"version\":\"$HASH\",\"windows\":\"$DOWNLOAD_URL\"}" | \
|
|
ssh -o StrictHostKeyChecking=no "$SSH_USER@$SSH_HOST" "cat > public_html/latest.json"
|
|
fi
|
|
echo "Uploaded $ZIPFILE and updated latest.json"
|
|
|
|
|
|
_android-avd-setup:
|
|
internal: true
|
|
run: once
|
|
status:
|
|
- test -f "${ANDROID_HOME:-$HOME/Android/Sdk}/emulator/emulator"
|
|
- test -d "$HOME/.android/avd/sharedinbox_test.avd"
|
|
cmds:
|
|
- cmd: |
|
|
SDK="${ANDROID_HOME:-$HOME/Android/Sdk}"
|
|
SDKMANAGER="$SDK/cmdline-tools/latest/bin/sdkmanager"
|
|
AVDMANAGER="$SDK/cmdline-tools/latest/bin/avdmanager"
|
|
yes | "$SDKMANAGER" --licenses >/dev/null 2>&1 || true
|
|
"$SDKMANAGER" "emulator" "system-images;android-34;google_apis;x86_64"
|
|
echo no | "$AVDMANAGER" create avd \
|
|
--name sharedinbox_test \
|
|
--package "system-images;android-34;google_apis;x86_64" \
|
|
--device pixel_4 \
|
|
--force
|
|
|
|
_android-sdk-check:
|
|
internal: true
|
|
run: once
|
|
preconditions:
|
|
- sh: test -d "${ANDROID_HOME:-$HOME/Android/Sdk}"
|
|
msg: |
|
|
Android SDK not found. Install it with:
|
|
wget https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip -O /tmp/cmdtools.zip
|
|
mkdir -p ~/Android/Sdk/cmdline-tools
|
|
unzip /tmp/cmdtools.zip -d ~/Android/Sdk/cmdline-tools
|
|
mv ~/Android/Sdk/cmdline-tools/cmdline-tools ~/Android/Sdk/cmdline-tools/latest
|
|
~/Android/Sdk/cmdline-tools/latest/bin/sdkmanager "platform-tools" "build-tools;34.0.0" "platforms;android-34"
|
|
- sh: ls "${ANDROID_HOME:-$HOME/Android/Sdk}/platforms/" 2>/dev/null | grep -qE '^android-([3-9][4-9]|[4-9][0-9]|[1-9][0-9]{2,})$'
|
|
msg: |
|
|
Android platform 34 or higher not found. Install it with:
|
|
${ANDROID_HOME:-$HOME/Android/Sdk}/cmdline-tools/latest/bin/sdkmanager "build-tools;34.0.0" "platforms;android-34"
|
|
|
|
build-android:
|
|
desc: Build a release APK
|
|
deps: [_preflight, _android-sdk-check, _pub-get, generate-changelog]
|
|
method: timestamp
|
|
sources:
|
|
- lib/**/*.dart
|
|
- android/**/*
|
|
- pubspec.yaml
|
|
generates:
|
|
- build/app/outputs/flutter-apk/app-release.apk
|
|
cmds:
|
|
- ANDROID_HOME=${ANDROID_HOME:-$HOME/Android/Sdk} fvm flutter build apk --release --no-pub --dart-define=GIT_HASH=$(git rev-parse --short HEAD) | grep -Ev "was tree-shaken|Tree-shaking can be disabled"
|
|
|
|
deploy-android-bundle:
|
|
desc: Build release AAB and upload to Play Store internal track (local/fvm)
|
|
deps: [build-android-bundle-local]
|
|
preconditions:
|
|
- sh: test -n "$PLAY_STORE_CONFIG_JSON"
|
|
msg: "PLAY_STORE_CONFIG_JSON is not set"
|
|
cmds:
|
|
- python3 scripts/deploy_playstore.py
|
|
|
|
build-android-bundle-local:
|
|
desc: Build a release App Bundle (AAB) locally via fvm (not Dagger)
|
|
deps: [_preflight, _android-sdk-check, _codegen, generate-changelog]
|
|
method: timestamp
|
|
sources:
|
|
- lib/**/*.dart
|
|
- android/**/*
|
|
- pubspec.yaml
|
|
generates:
|
|
- build/app/outputs/bundle/release/app-release.aab
|
|
cmds:
|
|
- ANDROID_HOME=${ANDROID_HOME:-$HOME/Android/Sdk} fvm flutter build appbundle --release --no-pub --build-number $(date +%s) --build-name $(date +%y%m%d-%H%M) --dart-define=GIT_HASH=$(git rev-parse --short HEAD) | grep -Ev "was tree-shaken|Tree-shaking can be disabled"
|
|
|
|
deploy-android:
|
|
desc: Build release APK and upload via scp to $ANDROID_APK_SCP_USER@$ANDROID_APK_SCP_HOST:$ANDROID_APK_SCP_PATH
|
|
deps: [check, build-android]
|
|
sources:
|
|
- build/app/outputs/flutter-apk/app-release.apk
|
|
generates:
|
|
- build/deploy-android.done
|
|
dotenv: [".env"]
|
|
preconditions:
|
|
- sh: test -n "$ANDROID_APK_SCP_HOST"
|
|
msg: "ANDROID_APK_SCP_HOST is not set in .env"
|
|
- sh: test -n "$ANDROID_APK_SCP_USER"
|
|
msg: "ANDROID_APK_SCP_USER is not set in .env"
|
|
- sh: test -n "$ANDROID_APK_SCP_PATH"
|
|
msg: "ANDROID_APK_SCP_PATH is not set in .env"
|
|
cmds:
|
|
# integration-android runs after check (not in parallel) so the two E2E
|
|
# test suites don't compete for CPU and slow the Android emulator.
|
|
- task: integration-android
|
|
- scripts/deploy_android.sh
|
|
- touch build/deploy-android.done
|
|
|
|
|
|
run:
|
|
desc: Run the app on Linux desktop
|
|
deps: [_preflight, _linux-deps-check, _pub-get]
|
|
cmds:
|
|
- fvm flutter run -d linux --no-pub
|
|
|
|
run-android:
|
|
desc: Run the app on a connected Android emulator (boots sharedinbox_test AVD if needed)
|
|
deps: [_preflight, _android-sdk-check, _android-avd-setup, _pub-get]
|
|
cmds:
|
|
- stalwart-dev/run_android.sh
|
|
|
|
coverage:
|
|
desc: Coverage gate — run after test (and optionally integration) have written lcov.info
|
|
deps: [_preflight]
|
|
cmds:
|
|
- fvm dart run scripts/check_coverage.dart
|
|
|
|
check-coverage:
|
|
desc: Run unit+widget tests with coverage, then fail if the gate is not met
|
|
deps: [test]
|
|
cmds:
|
|
- task: coverage
|
|
|
|
website-dev:
|
|
desc: Run Hugo development server
|
|
cmds:
|
|
- fvm flutter pub global run # Just a placeholder if nix shell hook isn't active
|
|
- nix develop --command hugo server --source website -D
|
|
|
|
website-build:
|
|
desc: Build the static website (embeds current git hash via HUGO_PARAMS_GITVERSION)
|
|
env:
|
|
HUGO_PARAMS_GITVERSION:
|
|
sh: git rev-parse --short HEAD
|
|
cmds:
|
|
- hugo --source website --minify
|
|
|
|
website-verify:
|
|
desc: Check that the deployed site serves the current git version
|
|
cmds:
|
|
- scripts/website-verify.sh
|
|
|
|
deploy-apk-to-server:
|
|
desc: SCP the release APK to the server at public_html/builds/YYYY/MM/DD/
|
|
deps: [build-android]
|
|
preconditions:
|
|
- sh: test -n "$SSH_USER"
|
|
msg: "SSH_USER is not set"
|
|
- sh: test -n "$SSH_HOST"
|
|
msg: "SSH_HOST is not set"
|
|
cmds:
|
|
- |
|
|
HASH=$(git rev-parse --short HEAD)
|
|
DATE_PATH=$(date -u +%Y/%m/%d)
|
|
REMOTE_DIR="public_html/builds/$DATE_PATH"
|
|
APK_NAME="sharedinbox-mua-$HASH.apk"
|
|
ssh -o StrictHostKeyChecking=no "$SSH_USER@$SSH_HOST" "mkdir -p $REMOTE_DIR"
|
|
scp -o StrictHostKeyChecking=no \
|
|
build/app/outputs/flutter-apk/app-release.apk \
|
|
"$SSH_USER@$SSH_HOST:$REMOTE_DIR/$APK_NAME"
|
|
echo "Uploaded $APK_NAME to $REMOTE_DIR"
|
|
|
|
generate-build-history:
|
|
desc: Generate Hugo build-history pages from Linux and Android builds on the server
|
|
preconditions:
|
|
- sh: test -n "$SSH_USER"
|
|
msg: "SSH_USER is not set"
|
|
- sh: test -n "$SSH_HOST"
|
|
msg: "SSH_HOST is not set"
|
|
cmds:
|
|
- python3 scripts/generate_build_history.py
|
|
|
|
website-publish:
|
|
desc: Generate build history, build Hugo site, and rsync to server (requires SSH_USER + SSH_HOST)
|
|
preconditions:
|
|
- sh: test -n "$SSH_USER"
|
|
msg: "SSH_USER is not set"
|
|
- sh: test -n "$SSH_HOST"
|
|
msg: "SSH_HOST is not set"
|
|
cmds:
|
|
- task: generate-build-history
|
|
- task: website-deploy
|
|
|
|
website-deploy:
|
|
desc: Deploy the website via rsync to public_html
|
|
deps: [website-build]
|
|
cmds:
|
|
- |
|
|
rsync -avz --delete \
|
|
--exclude='*.apk' \
|
|
--exclude='*.tar.gz' \
|
|
-e "ssh -o StrictHostKeyChecking=no" \
|
|
website/public/ \
|
|
${SSH_USER}@${SSH_HOST}:public_html/
|
|
|
|
check-fast:
|
|
desc: Pre-commit checks — analyze + unit+widget tests + coverage gate (no build, no integration)
|
|
deps: [analyze, check-coverage, check-hygiene, check-layers, check-mocks]
|
|
|
|
check-layers:
|
|
desc: Enforce architecture — ui/ must not import data/ (only core/ interfaces allowed)
|
|
cmds:
|
|
- |
|
|
VIOLATIONS=$(grep -rn "package:sharedinbox/data/" lib/ui/ 2>/dev/null || true)
|
|
if [ -n "$VIOLATIONS" ]; then
|
|
echo "ERROR: UI layer imports data layer (only core/ interfaces are allowed from ui/):"
|
|
echo "$VIOLATIONS"
|
|
exit 1
|
|
fi
|
|
|
|
check-hygiene:
|
|
desc: Verify that no forbidden files (like home dir config) are tracked
|
|
cmds:
|
|
- |
|
|
FORBIDDEN=".ssh .bashrc .config .local .cache .gitconfig .android Android .gradle .pub-cache .dartServer .flutter .dart-cli-completion .atuin .bash_logout .profile .zcompdump .zshrc snap .emulator_console_auth_token .lesshst .metadata .tmux.conf"
|
|
TRACKED=$(git ls-files $FORBIDDEN 2>/dev/null)
|
|
if [ -n "$TRACKED" ]; then
|
|
echo "ERROR: The following forbidden files are tracked in git:"
|
|
echo "$TRACKED"
|
|
echo "Please remove them with 'git rm --cached <file>'"
|
|
exit 1
|
|
fi
|
|
echo "Hygiene check passed."
|
|
|
|
_integrations:
|
|
internal: true
|
|
run: once
|
|
cmds:
|
|
- task: test-backend
|
|
- task: integration-ui
|
|
|
|
ci-logs:
|
|
desc: "Fetch latest CI job logs from Codeberg. Usage: task ci-logs [RUN=8] [JOB=0]"
|
|
cmds:
|
|
- scripts/ci_logs.sh "{{.RUN}}" "{{.JOB}}"
|
|
|
|
check:
|
|
desc: Full check suite — unit tests first, then integration (merges coverage), then gate
|
|
deps: [analyze, build-linux, test]
|
|
cmds:
|
|
- task: _integrations
|
|
- task: coverage
|