All checks were successful
Le tout premier build-android complet et signé (keystore enfin correct) échouait quand même à la toute dernière étape : GHESNotSupportedError: @actions/artifact v2.0.0+, upload-artifact@v4+ et download-artifact@v4+ ne sont pas actuellement supportés sur GHES. v4+ exige l'API "Results" propre à github.com — non implémentée par ce runner Forgejo self-hosted. v3 utilise encore l'ancienne API Artifacts, seule compatible ici ; l'interface name/path utilisée par ce job est identique entre les deux versions, aucun autre changement nécessaire. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
292 lines
13 KiB
YAML
292 lines
13 KiB
YAML
name: CI
|
|
|
|
# Every push, on every branch — not just main — so build breakage shows up
|
|
# on the first commit of a branch, not only once a PR targets main. No
|
|
# separate pull_request trigger: for a PR from a branch on this same repo,
|
|
# push already fires on that branch, and GitHub attaches the run to the PR
|
|
# by commit SHA regardless of which event triggered it — adding
|
|
# pull_request too would just run every job twice per push. (Re-add it,
|
|
# scoped to forks, only if this repo starts accepting fork PRs — push
|
|
# events from a fork never reach here.)
|
|
on:
|
|
push:
|
|
|
|
env:
|
|
# Hostname `postgres` = the container the `test` job starts itself (see
|
|
# that job's "Start Postgres" step) — a plain `docker run`, not a
|
|
# `services:` block, for the same reason the intent-service below can't be
|
|
# one: this repo's Forgejo `act` runner does not keep a `services:`
|
|
# container as the *same* instance across a job's steps, so `migrate
|
|
# deploy` would land the schema in one Postgres and `pnpm --filter api
|
|
# test`'s `beforeAll` would then hit an empty one ("table … does not
|
|
# exist" right after "migrations successfully applied"). One long-lived
|
|
# container in the job, shared by every step, removes that failure mode.
|
|
DATABASE_URL: "postgresql://ci:ci@postgres:5432/batchcooking_ci?schema=public"
|
|
# Test-only secret, never used outside CI — real deployments must set their own.
|
|
JWT_SECRET: "ci-only-secret-not-used-anywhere-else-32chars+"
|
|
# Same reasoning as JWT_SECRET above — lets tech-step-worker.routes.test.ts
|
|
# exercise the success path (matching secret), not just the "unset"
|
|
# rejection every environment that doesn't set this gets by default.
|
|
INTERNAL_WORKER_SECRET: "ci-only-worker-secret-not-used-anywhere-else-32chars+"
|
|
# Same reasoning — lets admin-auth.test.ts exercise the admin login
|
|
# success path + the requireAdmin-guarded routes, not just the "no admin
|
|
# secret configured -> 401" path.
|
|
ADMIN_JWT_SECRET: "ci-only-admin-secret-not-used-anywhere-else-32chars+"
|
|
# Shared between the `test` job's own uvicorn step (below) and apps/api's
|
|
# IntentServiceClient — that step runs `uvicorn` as a plain background
|
|
# process (not a `services:` block or even a container) because CI can
|
|
# only pull a published image, not build
|
|
# services/tech-step-intent-service/Dockerfile from this checkout.
|
|
INTENT_SERVICE_BASE_URL: "http://localhost:8000"
|
|
INTENT_SERVICE_SECRET: "ci-only-intent-secret-not-used-anywhere-else-32chars+"
|
|
|
|
jobs:
|
|
# Five independent jobs, no needs: between them — each starts in parallel
|
|
# and reports as its own check, instead of the previous single chained
|
|
# "lint-and-test then e2e" pipeline.
|
|
lint:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: https://github.com/actions/checkout@v4
|
|
|
|
- uses: pnpm/action-setup@v4
|
|
|
|
- uses: https://github.com/actions/setup-node@v4
|
|
with:
|
|
node-version: 22
|
|
cache: pnpm
|
|
|
|
- run: pnpm install --frozen-lockfile
|
|
- run: pnpm lint
|
|
|
|
test:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Install curl and the docker CLI
|
|
run: |
|
|
apt-get update && apt-get install -y curl docker.io
|
|
|
|
# Postgres as a plain background container instead of a `services:`
|
|
# block — see this workflow's top-level `env.DATABASE_URL` comment for
|
|
# why. Reachable as `postgres` from this job (the runner mounts the
|
|
# docker socket and runs jobs on a shared user-defined network, so a
|
|
# `--name postgres` sibling resolves by name); the network is
|
|
# discovered from the job container itself rather than hardcoded.
|
|
- name: Start Postgres in the background
|
|
run: |
|
|
net="$(docker inspect "$(hostname)" \
|
|
--format '{{range $k, $_ := .NetworkSettings.Networks}}{{$k}} {{end}}' \
|
|
| awk '{print $1}')"
|
|
echo "job network: ${net:?could not determine the job container network}"
|
|
docker rm -f postgres >/dev/null 2>&1 || true
|
|
docker run -d --name postgres --network "$net" \
|
|
-e POSTGRES_USER=ci -e POSTGRES_PASSWORD=ci -e POSTGRES_DB=batchcooking_ci \
|
|
postgres:16-alpine
|
|
for _ in $(seq 1 60); do
|
|
docker exec postgres pg_isready -U ci -d batchcooking_ci && exit 0
|
|
sleep 2
|
|
done
|
|
echo "Postgres did not become ready in time" >&2
|
|
docker logs postgres || true
|
|
exit 1
|
|
|
|
- uses: https://github.com/actions/checkout@v4
|
|
|
|
- uses: pnpm/action-setup@v4
|
|
|
|
- uses: https://github.com/actions/setup-node@v4
|
|
with:
|
|
node-version: 22
|
|
cache: pnpm
|
|
|
|
- uses: https://github.com/astral-sh/setup-uv@v5
|
|
with:
|
|
python-version: "3.12"
|
|
enable-cache: true
|
|
|
|
# A `services:` block can only pull an already-published image — it
|
|
# can't build services/tech-step-intent-service/Dockerfile from this
|
|
# checkout. Running `uvicorn` as a plain background step instead: it
|
|
# keeps running for the rest of this job (steps in one job share the
|
|
# same process tree), and `pnpm --filter api test` below needs a real
|
|
# instance to talk to per this repo's "never mock an internal service"
|
|
# test convention — same "real dependency, started by the job itself"
|
|
# posture as the Postgres step above, not a mock HTTP server.
|
|
- name: Install services/tech-step-intent-service
|
|
working-directory: services/tech-step-intent-service
|
|
run: uv sync --frozen
|
|
- name: Start services/tech-step-intent-service in the background
|
|
working-directory: services/tech-step-intent-service
|
|
run: |
|
|
uv run uvicorn intent_service.main:app --host 0.0.0.0 --port 8000 &
|
|
# `/health` only returns 200 once this service has finished
|
|
# training itself from scratch (no model ever persisted to disk —
|
|
# see its own README) — measured at ~3min combined (fr+en) against
|
|
# the current ~74-technique corpus, down from ~930s once
|
|
# `spacy.TextCatBOW.v3`'s `length` stopped being left at its
|
|
# 262144 default (see `_TEXTCAT_MODEL_LENGTH` in
|
|
# `locale_pipeline.py`, and docker-compose.yml's healthcheck for
|
|
# the same reasoning). The timeout stays well above that: it's a
|
|
# ceiling for a slow/contended runner, not a target.
|
|
timeout 600 bash -c 'until curl -sf http://localhost:8000/health > /dev/null; do sleep 2; done'
|
|
|
|
- run: pnpm install --frozen-lockfile
|
|
- run: pnpm --filter api exec prisma migrate deploy
|
|
- run: pnpm --filter api test
|
|
|
|
intent-service-test:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: https://github.com/actions/checkout@v4
|
|
|
|
- uses: https://github.com/astral-sh/setup-uv@v5
|
|
with:
|
|
python-version: "3.12"
|
|
enable-cache: true
|
|
|
|
- name: Install services/tech-step-intent-service
|
|
working-directory: services/tech-step-intent-service
|
|
run: uv sync --frozen
|
|
- name: Run pytest
|
|
working-directory: services/tech-step-intent-service
|
|
run: uv run pytest -q
|
|
|
|
build:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: https://github.com/actions/checkout@v4
|
|
|
|
- uses: pnpm/action-setup@v4
|
|
|
|
- uses: https://github.com/actions/setup-node@v4
|
|
with:
|
|
node-version: 22
|
|
cache: pnpm
|
|
|
|
- run: pnpm install --frozen-lockfile
|
|
- run: pnpm build
|
|
|
|
e2e:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: https://github.com/actions/checkout@v4
|
|
|
|
- name: Install Xvfb and Cypress dependencies
|
|
run: |
|
|
apt-get update
|
|
apt-get install -y xvfb libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libnss3 libxss1 libasound2 libxtst6 xauth dbus-x11
|
|
|
|
- uses: pnpm/action-setup@v4
|
|
|
|
- uses: https://github.com/actions/setup-node@v4
|
|
with:
|
|
node-version: 22
|
|
cache: pnpm
|
|
|
|
- name: Cache Cypress binary
|
|
uses: https://github.com/actions/cache@v4
|
|
with:
|
|
path: ~/.cache/Cypress
|
|
key: cypress-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
|
|
|
|
- run: pnpm install --frozen-lockfile
|
|
# pnpm install doesn't reliably trigger Cypress's postinstall binary
|
|
# download (see apps/web's cypress caveat in the README) — install it
|
|
# explicitly so `cypress run` finds it.
|
|
- run: pnpm --filter web exec cypress install
|
|
- name: Run E2E tests
|
|
env:
|
|
HOST: "0.0.0.0"
|
|
run: pnpm --filter web e2e
|
|
# No dev server needed here — Cypress spins up its own Vite dev
|
|
# server internally for component testing (see cypress.config.ts's
|
|
# `component.devServer`), unlike `e2e` above which needs the real app
|
|
# running first.
|
|
- run: pnpm --filter web cy:run:component
|
|
|
|
# Builds and packages a signed, downloadable Android APK on every push to
|
|
# `main` — issue #64. `needs:` on every other job is what actually makes
|
|
# this "build, TEST, and package" rather than just "build and package in
|
|
# parallel with tests that might still be failing": Forgejo/GitHub Actions
|
|
# has no native way for a job in a *different* workflow file to block on
|
|
# one here, so this has to live in this same file to get a real dependency
|
|
# rather than a same-push coincidence. `if:` (not a separate trigger) keeps
|
|
# every other job running on every branch as today — only this one is
|
|
# main-only.
|
|
#
|
|
# No dedicated Android test suite exists (zero native code beyond the
|
|
# Capacitor-generated shell) — "testée" is satisfied by gating on the 5
|
|
# existing jobs, not by inventing an instrumented test suite for this.
|
|
#
|
|
# Unverified on this runner as of writing: no Android build has ever run
|
|
# on this self-hosted Forgejo `act` runner (unlike lint/test/build/e2e,
|
|
# exercised for months). If SDK installation turns out too slow/heavy here,
|
|
# cache `~/.android/sdk`/`~/.gradle` (same idea as the Cypress binary cache
|
|
# in `e2e` above) rather than accepting a slow job on every push.
|
|
build-android:
|
|
runs-on: ubuntu-latest
|
|
needs: [lint, test, intent-service-test, build, e2e]
|
|
if: github.ref == 'refs/heads/main'
|
|
steps:
|
|
- uses: https://github.com/actions/checkout@v4
|
|
|
|
- uses: pnpm/action-setup@v4
|
|
|
|
- uses: https://github.com/actions/setup-node@v4
|
|
with:
|
|
node-version: 22
|
|
cache: pnpm
|
|
|
|
- uses: https://github.com/actions/setup-java@v4
|
|
with:
|
|
distribution: temurin
|
|
# Matches what Android Gradle Plugin 8.x (bundled with the
|
|
# Capacitor-generated project, see apps/web/android/build.gradle)
|
|
# expects — bump alongside AGP if that version ever moves.
|
|
java-version: "21"
|
|
|
|
- uses: https://github.com/android-actions/setup-android@v3
|
|
|
|
- run: pnpm install --frozen-lockfile
|
|
|
|
# Unlike the normal web build (served same-origin as the API), a
|
|
# packaged Capacitor build runs from the WebView's own local origin and
|
|
# needs an absolute, public HTTPS URL baked in at build time — see
|
|
# apps/web/.env.capacitor.example's own comment on this.
|
|
- name: Build le bundle web pour Capacitor
|
|
env:
|
|
VITE_API_URL: ${{ secrets.MOBILE_API_URL }}
|
|
run: pnpm --filter web build:capacitor
|
|
- run: pnpm --filter web exec cap sync android
|
|
|
|
# Le keystore de release est un secret généré une fois à la main (voir
|
|
# le README, section "Application mobile") — jamais régénéré ici, un
|
|
# nouveau keystore casserait la mise à jour de l'appli déjà installée
|
|
# (Android refuse une signature différente de celle déjà installée).
|
|
- name: Décode le keystore de release
|
|
env:
|
|
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
|
|
run: echo "$ANDROID_KEYSTORE_BASE64" | base64 -d > apps/web/android/app/release.keystore
|
|
|
|
- name: Build l'APK signé
|
|
working-directory: apps/web/android
|
|
env:
|
|
ANDROID_KEYSTORE_PATH: release.keystore
|
|
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
|
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
|
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
|
# chmod défensif : le dev de ce repo se fait sous Windows, où le bit
|
|
# exécutable git de gradlew ne veut rien dire et peut être reperdu
|
|
# silencieusement par un futur commit — ne pas dépendre uniquement
|
|
# du mode suivi par git (voir aussi le fix de ce mode lui-même).
|
|
run: |
|
|
chmod +x ./gradlew
|
|
./gradlew assembleRelease
|
|
|
|
# v4+ exige l'API "Results" de GitHub, que ce runner Forgejo self-hosted
|
|
# ne supporte pas ("GHESNotSupportedError") — v3 utilise encore
|
|
# l'ancienne API Artifacts (compatible), seule version utilisable ici.
|
|
- uses: https://github.com/actions/upload-artifact@v3
|
|
with:
|
|
name: batchcooking-release.apk
|
|
path: apps/web/android/app/build/outputs/apk/release/app-release.apk
|