Compare commits

...

11 commits

Author SHA1 Message Date
c0a07e8adc Merge pull request 'feat(ci): build, teste et empaquette un APK Android signé sur chaque push main' (#67) from feat/mobile-ci-android-apk into main
Some checks failed
CI / lint (push) Successful in 2m48s
CI / build (push) Successful in 2m51s
CI / intent-service-test (push) Successful in 4m9s
CI / e2e (push) Successful in 9m14s
CI / test (push) Successful in 17m18s
CI / build-android (push) Failing after 4m15s
2026-09-06 14:44:35 +02:00
92f364bc1f chore: relance la CI (9e collision infra sur le Postgres partagé)
All checks were successful
CI / build (push) Successful in 3m9s
CI / lint (push) Successful in 3m40s
CI / intent-service-test (push) Successful in 5m11s
CI / e2e (push) Successful in 7m29s
CI / test (push) Successful in 17m34s
CI / build-android (push) Has been skipped
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 14:26:31 +02:00
c662d02da6 chore: relance la CI (8e collision infra sur le Postgres partagé)
Some checks failed
CI / build-android (push) Blocked by required conditions
CI / lint (push) Successful in 4m7s
CI / build (push) Successful in 4m13s
CI / test (push) Failing after 12m19s
CI / intent-service-test (push) Successful in 9m10s
CI / e2e (push) Has been cancelled
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 14:12:46 +02:00
b3581b2fb5 chore: relance la CI (7e collision infra sur le Postgres partagé)
Some checks failed
CI / build (push) Successful in 4m5s
CI / lint (push) Successful in 4m25s
CI / intent-service-test (push) Successful in 6m47s
CI / e2e (push) Successful in 13m15s
CI / test (push) Failing after 20m7s
CI / build-android (push) Has been skipped
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 12:06:53 +02:00
85dd1b6255 chore: relance la CI (6e collision infra sur le Postgres partagé)
Some checks failed
CI / build-android (push) Blocked by required conditions
CI / lint (push) Successful in 4m33s
CI / build (push) Successful in 3m50s
CI / intent-service-test (push) Successful in 5m28s
CI / test (push) Failing after 10m28s
CI / e2e (push) Has been cancelled
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 11:54:51 +02:00
4f7a452924 chore: relance la CI (5e collision infra — course d'upsert concurrente sur le seed Postgres partagé)
Some checks failed
CI / build-android (push) Blocked by required conditions
CI / lint (push) Successful in 3m34s
CI / build (push) Successful in 3m17s
CI / intent-service-test (push) Successful in 5m16s
CI / test (push) Failing after 8m39s
CI / e2e (push) Has been cancelled
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 11:45:08 +02:00
f37a61266f chore: relance la CI (4e collision infra — cette fois sur le cache d'actions du runner)
Some checks failed
CI / build-android (push) Blocked by required conditions
CI / lint (push) Successful in 4m7s
CI / build (push) Successful in 4m43s
CI / intent-service-test (push) Successful in 7m56s
CI / test (push) Failing after 11m11s
CI / e2e (push) Has been cancelled
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 11:31:39 +02:00
275cc2d500 chore: relance la CI (3e collision infra sur le Postgres partagé)
Some checks failed
CI / build-android (push) Blocked by required conditions
CI / lint (push) Successful in 1m6s
CI / build (push) Failing after 35s
CI / e2e (push) Has been cancelled
CI / intent-service-test (push) Has been cancelled
CI / test (push) Has been cancelled
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 11:27:25 +02:00
bf994c396a chore: relance la CI (2e collision infra sur le Postgres partagé, connexion coupée en cours de run)
Some checks failed
CI / lint (push) Successful in 2m15s
CI / build (push) Successful in 3m54s
CI / intent-service-test (push) Successful in 6m41s
CI / e2e (push) Successful in 16m11s
CI / test (push) Failing after 17m33s
CI / build-android (push) Has been skipped
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 11:04:32 +02:00
548cee721f chore: relance la CI (job test cassé par la course d'infra connue sur le Postgres partagé)
Some checks failed
CI / build-android (push) Blocked by required conditions
CI / lint (push) Successful in 2m22s
CI / intent-service-test (push) Successful in 5m35s
CI / build (push) Successful in 4m17s
CI / test (push) Failing after 10m27s
CI / e2e (push) Has been cancelled
18 échecs éparpillés (timeouts 10s sur des suites sans rapport entre elles,
PrismaClientKnownRequestError, un mismatch d'ID façon ré-seed concurrent) sur
373 passing — signature exacte de la course documentée dans
ci-forgejo-runner-constraints (postgres partagé + contention RAM/CPU du
runner), pas un vrai échec de code : ce PR ne touche que ci.yml/le projet
Android/le README, rien que la suite Mocha exerce différemment.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 10:45:05 +02:00
76a99f0e77 feat(ci): build, teste et empaquette un APK Android signé sur chaque push main
Some checks failed
CI / build-android (push) Blocked by required conditions
CI / build (push) Successful in 3m21s
CI / lint (push) Successful in 3m31s
CI / intent-service-test (push) Successful in 4m51s
CI / e2e (push) Successful in 10m34s
CI / test (push) Failing after 14m37s
Nouveau job build-android dans ci.yml, gardé par needs: sur les 5 jobs
existants (lint/test/intent-service-test/build/e2e) et limité à main via
if: github.ref == 'refs/heads/main' — c'est ce qui garantit que l'APK n'est
empaqueté qu'une fois la suite de tests existante verte, pas juste construit
en parallèle d'elle.

APK signé avec un vrai keystore de release (jamais généré par la CI —
Android refuse une mise à jour signée par une clé différente de celle déjà
installée) : ajoute signingConfigs.release à build.gradle, lu depuis
l'environnement (ANDROID_KEYSTORE_PATH/_PASSWORD, ANDROID_KEY_ALIAS/_PASSWORD),
jamais codé en dur. *.jks/*.keystore désormais réellement ignorés par git
(étaient commentés dans le .gitignore généré par Capacitor).

README : documente les 5 secrets Forgejo requis et la génération du
keystore. Aucune suite de tests instrumentés Android n'existe (zéro code
natif au-delà de la coquille Capacitor) — "testée" est satisfait par le
gate sur la suite existante, pas par une suite native à inventer.

Risque documenté, pas résolu par avance : aucun build Android n'a jamais
tourné sur ce runner Forgejo auto-hébergé, contrairement aux 4 autres jobs.

Closes #64

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 10:27:55 +02:00
4 changed files with 134 additions and 4 deletions

View file

@ -203,3 +203,81 @@ jobs:
# `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 }}
run: ./gradlew assembleRelease
- uses: https://github.com/actions/upload-artifact@v4
with:
name: batchcooking-release.apk
path: apps/web/android/app/build/outputs/apk/release/app-release.apk

View file

@ -266,6 +266,34 @@ Les icônes et le splash sont générés depuis `apps/web/assets/logo.svg` (voir
`apps/web/assets/README.md`). iOS est hors périmètre pour l'instant (nécessite
Xcode/macOS).
**CI — build automatique d'un APK signé** (`.github/workflows/ci.yml`, job
`build-android`, issue #64) : à chaque push sur `main`, une fois les 5 autres
jobs verts, un APK release signé est construit et déposé comme artefact de
run téléchargeable. Secrets du dépôt Forgejo requis (Réglages du dépôt →
Secrets) :
| Secret | Contenu |
|---|---|
| `MOBILE_API_URL` | URL HTTPS publique de l'API — même valeur que `VITE_API_URL` dans `.env.capacitor` |
| `ANDROID_KEYSTORE_BASE64` | Le keystore de release (`.jks`) encodé en base64 |
| `ANDROID_KEYSTORE_PASSWORD` | Mot de passe du keystore |
| `ANDROID_KEY_ALIAS` | Alias de la clé dans le keystore |
| `ANDROID_KEY_PASSWORD` | Mot de passe de la clé |
Le keystore doit être généré **une seule fois**, à la main, et rester stable
pour toujours : Android refuse d'installer la mise à jour d'une appli signée
par une clé différente de celle déjà installée.
```bash
keytool -genkeypair -v -keystore batchcooking-release.jks \
-alias batchcooking -keyalg RSA -keysize 2048 -validity 10000
base64 -w0 batchcooking-release.jks # → coller comme secret ANDROID_KEYSTORE_BASE64
```
Conserver le fichier `.jks` et ses mots de passe en lieu sûr en dehors du
dépôt (jamais commité — voir `apps/web/android/.gitignore`) : le perdre
empêche de publier toute future mise à jour de l'appli.
## Auth (apps/api)
Inscription (création de profil + foyer) et connexion, JWT dans un cookie httpOnly.

View file

@ -52,10 +52,12 @@ captures/
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
.idea/navEditor.xml
# Keystore files
# Uncomment the following lines if you do not want to check your keystore files in.
#*.jks
#*.keystore
# Keystore files — jamais commités : le keystore de release est un secret
# généré une fois à la main (voir la doc du README, section "Application
# mobile"), décodé depuis le secret CI ANDROID_KEYSTORE_BASE64 à chaque build
# de release, jamais versionné ici (issue #64).
*.jks
*.keystore
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild

View file

@ -16,8 +16,30 @@ android {
ignoreAssetsPattern = '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
}
}
// Lu depuis l'environnement, jamais codé en dur — même règle que les
// secrets de apps/api (JWT_SECRET, etc.) : aucune valeur par défaut
// devinable. Le keystore lui-même (ANDROID_KEYSTORE_PATH) est un fichier
// décodé à la volée depuis le secret CI ANDROID_KEYSTORE_BASE64, jamais
// commité (voir apps/web/android/.gitignore) — généré une fois à la main,
// voir la doc du README "Application mobile". N'affecte que
// `assembleRelease` : `assembleDebug` continue d'utiliser le keystore de
// debug généré automatiquement par Android, sans configuration.
signingConfigs {
release {
storeFile System.getenv("ANDROID_KEYSTORE_PATH") ? file(System.getenv("ANDROID_KEYSTORE_PATH")) : null
storePassword System.getenv("ANDROID_KEYSTORE_PASSWORD")
keyAlias System.getenv("ANDROID_KEY_ALIAS")
keyPassword System.getenv("ANDROID_KEY_PASSWORD")
}
}
buildTypes {
release {
// `signingConfig` reste posé même quand les variables d'environnement
// ci-dessus sont absentes (dev local) : Gradle accepte une
// signingConfig aux champs `null`, la build échoue seulement au
// moment de la signature effective (assembleRelease), jamais à la
// configuration — un `assembleDebug` local n'en a jamais besoin.
signingConfig signingConfigs.release
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}