Shuffle/.forgejo/workflows/ci.yml
cschlaefke 15ccae2b52
All checks were successful
CI / flutter (pull_request) Successful in 1m15s
CI / web-build (pull_request) Successful in 56s
CI / supabase (pull_request) Successful in 3s
CI / supabase-tests (pull_request) Successful in 31s
CI / e2e (pull_request) Successful in 4m34s
ci(e2e): seed mail templates in dind before supabase start
Refs #38

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-03 11:16:13 +01:00

341 lines
15 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# CI für Pull Requests und main – läuft auf einem Forgejo-Runner mit Docker-Label.
# Flutter-Projekt liegt in app/, Supabase in supabase/.
name: CI
on:
pull_request:
push:
branches: [main]
env:
SUPABASE_CLI_VERSION: 3.0.0
# Einzige Stelle für die Flutter-Version (= lokale Version, Dart 3.13.7; muss zu app/pubspec.yaml passen).
# Muss ein existierendes Tag in github.com/flutter/flutter sein. Ein Cirrus-Image mit dieser Version gibt es nicht.
FLUTTER_VERSION: 3.47.9
FLUTTER_HOME: /opt/flutter
jobs:
# Format, Analyse, Tests. Generierte Dateien (*.g.dart, gen-l10n) sind eingecheckt und
# werden hier neu erzeugt; ein Diff bedeutet: Generierung im PR vergessen.
flutter:
runs-on: docker
container:
image: node:22.12-bookworm # gepinnt; enthält git, curl, unzip, xz und Node (für JS-Actions)
env:
PUB_CACHE: ${{ github.workspace }}/.pub-cache
defaults:
run:
working-directory: app
steps:
- uses: https://code.forgejo.org/actions/checkout@v4
- name: Flutter-SDK-Cache
id: flutter-cache
uses: https://code.forgejo.org/actions/cache@v4
with:
path: ${{ env.FLUTTER_HOME }}
key: flutter-${{ env.FLUTTER_VERSION }}-node22.12-bookworm
- name: Flutter installieren
run: |
if [ ! -x "$FLUTTER_HOME/bin/flutter" ]; then
rm -rf "$FLUTTER_HOME"
git clone --depth 1 --branch "$FLUTTER_VERSION" https://github.com/flutter/flutter.git "$FLUTTER_HOME"
fi
echo "$FLUTTER_HOME/bin" >> "$GITHUB_PATH"
export PATH="$FLUTTER_HOME/bin:$PATH"
export CI=true
flutter config --no-analytics --enable-web
flutter --version
flutter precache --web
working-directory: .
- name: Pub-Cache
uses: https://code.forgejo.org/actions/cache@v4
with:
path: ${{ github.workspace }}/.pub-cache
key: pub-${{ env.FLUTTER_VERSION }}-${{ hashFiles('app/pubspec.lock') }}
restore-keys: pub-${{ env.FLUTTER_VERSION }}-
- name: Abhängigkeiten (erzeugt auch gen-l10n)
run: flutter pub get
- name: Code-Generierung
run: dart run build_runner build --delete-conflicting-outputs
- name: Generierte Dateien aktuell?
run: |
git config --global --add safe.directory "$GITHUB_WORKSPACE"
if ! git diff --exit-code -- . ; then
echo "::error::Generierte Dateien sind nicht aktuell. Lokal 'dart run build_runner build --delete-conflicting-outputs' und 'flutter gen-l10n' ausführen und committen."
exit 1
fi
untracked=$(git ls-files --others --exclude-standard -- lib test)
if [ -n "$untracked" ]; then echo "Nicht eingecheckte generierte Dateien:"; echo "$untracked"; exit 1; fi
- name: Format
run: dart format --set-exit-if-changed .
- name: Analyse
run: flutter analyze --fatal-infos
- name: Tests
run: flutter test
# Web-Build parallel zu den Prüfungen; Platzhalterwerte aus app/env/ci.example.json (keine Secrets).
web-build:
runs-on: docker
container:
image: node:22.12-bookworm # gepinnt; enthält git, curl, unzip, xz und Node (für JS-Actions)
env:
PUB_CACHE: ${{ github.workspace }}/.pub-cache
defaults:
run:
working-directory: app
steps:
- uses: https://code.forgejo.org/actions/checkout@v4
- name: Flutter-SDK-Cache
id: flutter-cache
uses: https://code.forgejo.org/actions/cache@v4
with:
path: ${{ env.FLUTTER_HOME }}
key: flutter-${{ env.FLUTTER_VERSION }}-node22.12-bookworm
- name: Flutter installieren
run: |
if [ ! -x "$FLUTTER_HOME/bin/flutter" ]; then
rm -rf "$FLUTTER_HOME"
git clone --depth 1 --branch "$FLUTTER_VERSION" https://github.com/flutter/flutter.git "$FLUTTER_HOME"
fi
echo "$FLUTTER_HOME/bin" >> "$GITHUB_PATH"
export PATH="$FLUTTER_HOME/bin:$PATH"
export CI=true
flutter config --no-analytics --enable-web
flutter --version
flutter precache --web
working-directory: .
- name: Pub-Cache
uses: https://code.forgejo.org/actions/cache@v4
with:
path: ${{ github.workspace }}/.pub-cache
key: pub-${{ env.FLUTTER_VERSION }}-${{ hashFiles('app/pubspec.lock') }}
restore-keys: pub-${{ env.FLUTTER_VERSION }}-
- name: Abhängigkeiten
run: flutter pub get
- name: Web-Build
run: flutter build web --release --dart-define-from-file=env/ci.example.json
supabase:
runs-on: docker
container:
image: node:22.12-bookworm
steps:
- uses: https://code.forgejo.org/actions/checkout@v4
with:
fetch-depth: 0
- name: Migrationen prüfen (ohne laufende DB)
run: |
test -d supabase/migrations || { echo "Keine Migrationen – übersprungen"; exit 0; }
# Bereits gemergte Migrationen dürfen nicht verändert werden
if [ "${{ github.event_name }}" = "pull_request" ]; then
git fetch origin "${{ github.base_ref }}" --depth=1
changed=$(git diff --name-only --diff-filter=M "origin/${{ github.base_ref }}"...HEAD -- supabase/migrations || true)
if [ -n "$changed" ]; then echo "Geänderte bestehende Migrationen: $changed"; exit 1; fi
fi
# RLS-/pgTAP-Tests: braucht einen Runner mit Docker-Zugriff (Label "docker-host").
# Der Job-Container hat kein Docker-CLI und keinen Socket (Isolation, Repo ist öffentlich). Stattdessen
# wird ein gepinntes Docker-CLI installiert und der dind-Daemon des Runners per TCP angesprochen
# (erreichbar über das Default-Gateway des Job-Netzes). Supabase-Ports liegen dann im dind;
# die Supabase-CLI nimmt den Host aus DOCKER_HOST automatisch.
# Nutzt die eingecheckte supabase/config.toml. Ohne passenden Runner Job bei Bedarf deaktivieren.
supabase-tests:
runs-on: docker-host
concurrency:
group: docker-host-supabase
cancel-in-progress: false
env:
DOCKER_CLI_VERSION: 27.3.1
DIND_PORT: 2375
steps:
- uses: https://code.forgejo.org/actions/checkout@v4
- name: Docker-CLI installieren und dind anbinden
run: |
arch=$(uname -m)
curl -fsSL "https://download.docker.com/linux/static/stable/${arch}/docker-${DOCKER_CLI_VERSION}.tgz" \
| tar -xz -C /usr/local/bin --strip-components=1 docker/docker
# Default-Gateway des Job-Containers (= dind-Host) aus /proc/net/route; `ip` fehlt im Image
hex=$(awk '$2=="00000000"{print $3; exit}' /proc/net/route)
gw=$(printf '%d.%d.%d.%d' "0x${hex:6:2}" "0x${hex:4:2}" "0x${hex:2:2}" "0x${hex:0:2}")
echo "DOCKER_HOST=tcp://${gw}:${DIND_PORT}" >> "$GITHUB_ENV"
DOCKER_HOST="tcp://${gw}:${DIND_PORT}" docker version --format 'dind {{.Server.Version}}'
- name: Supabase starten
run: |
test -f supabase/config.toml || { echo "supabase/config.toml fehlt"; exit 1; }
test -d supabase/migrations || { echo "Keine Migrationen – übersprungen"; exit 0; }
npx --yes supabase@${{ env.SUPABASE_CLI_VERSION }} start -x studio,imgproxy,edge-runtime,logflare,vector
- name: Datenbanktests (pgTAP)
run: |
test -d supabase/migrations || exit 0
bash .forgejo/scripts/supabase-db-tests.sh
- name: Aufräumen
if: always()
run: npx --yes supabase@${{ env.SUPABASE_CLI_VERSION }} stop --no-backup || true
# Web-E2E (Playwright: api, web, throttle) gegen lokalen Supabase-Stack und gebaute Flutter-Web-App (Issue #38, Spec #1 D2).
# Läuft wie supabase-tests auf "docker-host" (Docker-CLI + dind per TCP). Der Stack liegt im dind; ein
# TCP-Relay (e2e/support/tcp-forward.mjs) macht ihn im Job unter 127.0.0.1 erreichbar.
# SICHERHEIT: Der Job führt PR-Code mit Zugriff auf den dind-Daemon des Runners aus (Daemon = Root-Äquivalent).
# Der `if:` unten ist KEIN Schutz vor böswilligen Forks: Bei pull_request kommt der Workflow aus dem PR-Head,
# ein Fork kann die Bedingung einfach entfernen. Er verhindert nur versehentliche Läufe. Die echte Absicherung
# gehört in Repo-/Runner-Einstellungen (Freigabe für Fork-PRs, Runner nur für vertrauenswürdige Repos; Folge-Issue).
# Gleiches Risiko hat supabase-tests.
# needs + concurrency: Beide Jobs belegen dieselben festen Container-Namen/Ports (project_id shuffle, 54321 ...) im dind.
# `needs` serialisiert innerhalb eines Laufs; die concurrency-Gruppe (auch an supabase-tests) soll Läufe verschiedener
# PRs hintereinander schalten. Ob Forgejo 8.0.3 / Runner v5.0.4 Job-concurrency auswertet, ist hier nicht verifiziert;
# sonst wird der Schlüssel ignoriert und Kollisionen bleiben möglich (dann Runner mit capacity 1 verwenden).
# Der Spike (SPIKE_GOTRUE_URL, Issue #37) läuft nicht in CI.
e2e:
runs-on: docker-host
concurrency:
group: docker-host-supabase
cancel-in-progress: false
needs: [supabase-tests]
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
env:
DOCKER_CLI_VERSION: 27.3.1
DIND_PORT: 2375
PUB_CACHE: ${{ github.workspace }}/.pub-cache
PLAYWRIGHT_BROWSERS_PATH: ${{ github.workspace }}/.playwright-browsers
steps:
- uses: https://code.forgejo.org/actions/checkout@v4
- name: Werkzeuge prüfen
run: |
missing=""
for t in git curl unzip xz jq; do
command -v "$t" >/dev/null || { [ "$t" = xz ] && missing="$missing xz-utils" || missing="$missing $t"; }
done
if [ -n "$missing" ]; then
apt-get update -qq && apt-get install -y -qq --no-install-recommends $missing ca-certificates
fi
node --version
- name: Docker-CLI installieren und dind anbinden
run: |
arch=$(uname -m)
curl -fsSL "https://download.docker.com/linux/static/stable/${arch}/docker-${DOCKER_CLI_VERSION}.tgz" \
| tar -xz -C /usr/local/bin --strip-components=1 docker/docker
hex=$(awk '$2=="00000000"{print $3; exit}' /proc/net/route)
gw=$(printf '%d.%d.%d.%d' "0x${hex:6:2}" "0x${hex:4:2}" "0x${hex:2:2}" "0x${hex:0:2}")
echo "DOCKER_GW=${gw}" >> "$GITHUB_ENV"
echo "DOCKER_HOST=tcp://${gw}:${DIND_PORT}" >> "$GITHUB_ENV"
DOCKER_HOST="tcp://${gw}:${DIND_PORT}" docker version --format 'dind {{.Server.Version}}'
- name: Mailvorlagen im dind anlegen
# Unter dind existiert der Bind-Mount-Pfad der Vorlagen nicht; sie müssen VOR `supabase start` dort liegen,
# sonst entstehen leere Verzeichnis-Mounts und GoTrue verschickt still eine Fehlerseite. Details im Skript.
run: bash .forgejo/scripts/remote-templates.sh seed
- name: Supabase starten (mit Mailpit, ohne Studio u. a.)
# Ausgabe nicht ins Log: Die Zusammenfassung von `start` enthält die lokalen Schlüssel (noch nicht maskiert).
run: |
if ! npx --yes supabase@${{ env.SUPABASE_CLI_VERSION }} start -x studio,imgproxy,edge-runtime,logflare,vector >"${RUNNER_TEMP:-/tmp}/supabase-start.log" 2>&1; then
echo "supabase start fehlgeschlagen, letzte Zeilen (Schlüssel gefiltert):"
tail -n 40 "${RUNNER_TEMP:-/tmp}/supabase-start.log" | grep -viE 'key|secret|token|jwt'
exit 1
fi
echo "supabase start ok"
- name: Datenbank zurücksetzen
run: npx --yes supabase@${{ env.SUPABASE_CLI_VERSION }} db reset
- name: Mailvorlagen im dind prüfen
run: bash .forgejo/scripts/remote-templates.sh verify
- name: Lokale Schlüssel lesen
# Öffentliche Demo-Schlüssel der CLI; trotzdem wird die Maske gesetzt, bevor sie irgendwo ausgegeben werden.
run: |
status=$(npx --yes supabase@${{ env.SUPABASE_CLI_VERSION }} status -o env 2>/dev/null)
get() { printf '%s\n' "$status" | sed -n "s/^$1=\"\{0,1\}\([^\"]*\)\"\{0,1\}$/\1/p" | head -n1; }
anon=$(get ANON_KEY); service=$(get SERVICE_ROLE_KEY)
[ -z "$anon" ] || echo "::add-mask::$anon"
[ -z "$service" ] || echo "::add-mask::$service"
test -n "$anon" && test -n "$service" || { echo "ANON_KEY/SERVICE_ROLE_KEY nicht in 'supabase status -o env' gefunden"; exit 1; }
echo "SUPABASE_ANON_KEY=${anon}" >> "$GITHUB_ENV"
echo "SUPABASE_SERVICE_ROLE_KEY=${service}" >> "$GITHUB_ENV"
# Env-Datei für den Web-Build: Platzhalter-Datei bleibt unverändert, der Schlüssel kommt zur Laufzeit.
jq --arg k "$anon" '.SUPABASE_ANON_KEY = $k | .SUPABASE_URL = "http://127.0.0.1:54321"' \
app/env/ci.example.json > "${RUNNER_TEMP:-/tmp}/ci.e2e.json"
echo "E2E_ENV_FILE=${RUNNER_TEMP:-/tmp}/ci.e2e.json" >> "$GITHUB_ENV"
- name: Extra-GoTrue für Drosselung (Krit. 7) starten
run: |
BIND_ADDR=0.0.0.0 HEALTH_HOST="$DOCKER_GW" bash e2e/support/start-throttle-gotrue.sh start throttle
- name: Flutter-SDK-Cache
uses: https://code.forgejo.org/actions/cache@v4
with:
path: ${{ env.FLUTTER_HOME }}
key: flutter-${{ env.FLUTTER_VERSION }}-docker-host
- name: Flutter installieren
run: |
if [ ! -x "$FLUTTER_HOME/bin/flutter" ]; then
rm -rf "$FLUTTER_HOME"
git clone --depth 1 --branch "$FLUTTER_VERSION" https://github.com/flutter/flutter.git "$FLUTTER_HOME"
fi
echo "$FLUTTER_HOME/bin" >> "$GITHUB_PATH"
export PATH="$FLUTTER_HOME/bin:$PATH"
export CI=true
flutter config --no-analytics --enable-web
flutter precache --web
- name: Pub-Cache
uses: https://code.forgejo.org/actions/cache@v4
with:
path: ${{ github.workspace }}/.pub-cache
key: pub-${{ env.FLUTTER_VERSION }}-${{ hashFiles('app/pubspec.lock') }}
restore-keys: pub-${{ env.FLUTTER_VERSION }}-
- name: Web-Build mit lokalem Anon-Key
working-directory: app
run: |
flutter pub get
flutter build web --release --dart-define-from-file="$E2E_ENV_FILE"
- name: Playwright-Cache
uses: https://code.forgejo.org/actions/cache@v4
with:
path: ${{ github.workspace }}/.playwright-browsers
key: playwright-${{ hashFiles('e2e/package-lock.json') }}
- name: E2E-Abhängigkeiten und Browser
working-directory: e2e
run: |
npm ci
npx playwright install --with-deps chromium
- name: Playwright (api, web, throttle)
run: bash .forgejo/scripts/e2e-run.sh
- name: Artefakte hochladen (Report, Traces)
if: failure()
uses: https://code.forgejo.org/forgejo/upload-artifact@v4
with:
name: playwright-${{ github.run_number }}
path: |
e2e/playwright-report
e2e/test-results
retention-days: 7
if-no-files-found: ignore
- name: Aufräumen
if: always()
run: |
bash e2e/support/start-throttle-gotrue.sh stop throttle || true
npx --yes supabase@${{ env.SUPABASE_CLI_VERSION }} stop --no-backup || true