From d50e53e4ad0c7965665798d3387ccdc414c2b445 Mon Sep 17 00:00:00 2001 From: Millian Lamiaux Date: Sun, 19 Jul 2026 16:50:36 +0200 Subject: [PATCH 1/6] =?UTF-8?q?fix(ci):=20pr-iphone-deploy=20=E2=80=94=20c?= =?UTF-8?q?ache,=20secrets,=20LGTM=20regex,=20concurrency,=20squash?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 10 fixes to .github/workflows/pr-iphone-deploy.yml: Cache & build hygiene: - Stop deleting build/spm-cache (was wiped by 'rm -rf build/'); clean only build/derived. RevenueCat (~1.1 GiB) is finally actually preserved. - Stop nuking ~/Library/Caches/com.apple.dt.Xcode (slows every next build). - Drop '|| true' on brew install — fail loudly if brew is down. - Bump build-deploy timeout 20m → 30m for cold builds. Secrets: - New step writes Config/Secrets.xcconfig from Gitea secrets (SUPABASE_URL, SUPABASE_ANON_KEY, REVENUECAT_API_KEY, POSTHOG_API_KEY) before xcodegen. Preflight fails loud if a required secret is missing. Heredoc body at column 0 (xcconfig keys are whitespace-sensitive). LGTM/KO handling: - Regex now matches anywhere in body with word boundary: \bLGTM\b / \bKO\b. 'Tested, LGTM!' counts; 'KOM' / 'LGTMX' / 'OK' do not. - Re-fetch ALL comments each poll (not since_id) to catch edits. - Squash-merge ({"Do":"squash"}) instead of merge commit. Workflow structure: - Add concurrency block (per-PR, cancel-in-progress) — prevents double-merge and stacked pipelines on successive pushes. - Remove no-op 'permissions:' block (all mutations go via Gitea API with PR_API_TOKEN, native GITHUB_TOKEN is never used). --- .github/workflows/pr-iphone-deploy.yml | 80 +++++++++++++++++++------- 1 file changed, 60 insertions(+), 20 deletions(-) diff --git a/.github/workflows/pr-iphone-deploy.yml b/.github/workflows/pr-iphone-deploy.yml index d7b6b42..9ed691e 100644 --- a/.github/workflows/pr-iphone-deploy.yml +++ b/.github/workflows/pr-iphone-deploy.yml @@ -9,16 +9,21 @@ on: branches: [main] types: [opened, synchronize, reopened] -permissions: - contents: write - pull-requests: write +# Every mutation (comments, merge) goes through the Gitea API with PR_API_TOKEN, +# never the runner's native GITHUB_TOKEN — so a `permissions:` block would be +# a no-op here. Intentionally omitted. +concurrency: + # Per-PR grouping: superseded pushes cancel the previous pipeline, different + # PRs still run in parallel. Prevents double-merges and wasted runner time. + group: pr-iphone-deploy-${{ github.event.pull_request.number }} + cancel-in-progress: true jobs: build-deploy: name: Build & Deploy to iPhone (WiFi/USB) runs-on: macos - timeout-minutes: 20 + timeout-minutes: 30 steps: - name: Checkout @@ -42,9 +47,11 @@ jobs: # Act runner caches rm -rf ~/.cache/act/*/hostexecutor/tabatago-swift/build 2>/dev/null || true - # SPM caches are KEPT between runs — RevenueCat is 1.10 GiB and takes + # SPM cache is KEPT between runs — RevenueCat is ~1.10 GiB and takes # 5+ minutes to clone; re-cloning on every CI run would waste time - # and cause random failures on the act_runner sandbox. + # and cause random failures on the act_runner sandbox. It lives at + # build/spm-cache (see -clonedSourcePackagesDirPath in the Build step) + # so NEVER `rm -rf build/` — only delete build/derived. # Xcode DerivedData — suppression COMPLÈTE (le bundle ID a changé, # l'ancien dossier TabataGo-* peut être ignoré par le nouveau build) @@ -52,10 +59,12 @@ jobs: # Module cache Xcode — des .pcm stale peuvent segfault le linker rm -rf ~/Library/Developer/Xcode/DerivedData/ModuleCache.noindex 2>/dev/null || true - rm -rf ~/Library/Caches/com.apple.dt.Xcode + # NOTE: do NOT `rm -rf ~/Library/Caches/com.apple.dt.Xcode` — that is the + # shared global cache; wiping it slows every subsequent build. - # Build artifacts locaux (utilisés par le step "Build app") - rm -rf build/ + # Build artifacts locaux — sparing build/spm-cache (see above). + rm -rf build/derived + rm -rf build/*.log 2>/dev/null || true rm -rf .build rm -rf tabatago-swift/.build rm -rf tabatago-swift/TabataGo.xcodeproj @@ -65,11 +74,40 @@ jobs: find . -name "Package.resolved" -delete 2>/dev/null || true find . -name ".package.resolved" -delete 2>/dev/null || true - echo "✅ Caches nettoyés" + echo "✅ Caches nettoyés (SPM cache préservé)" - name: Install tools (xcodegen, node, ios-deploy) run: | - brew install xcodegen node ios-deploy || true + # No `|| true` — if brew is down, fail loudly here instead of producing + # confusing "command not found" errors in the next steps. + brew install xcodegen node ios-deploy + + - name: Write Secrets.xcconfig from Gitea secrets + env: + SUPABASE_URL: ${{ secrets.SUPABASE_URL }} + SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }} + REVENUECAT_API_KEY: ${{ secrets.REVENUECAT_API_KEY }} + POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }} + run: | + # Preflight: fail loudly if a secret is missing, rather than producing + # an .app that silently can't reach Supabase/RevenueCat at runtime. + for v in SUPABASE_URL SUPABASE_ANON_KEY REVENUECAT_API_KEY; do + val="$(printenv "$v")" + if [ -z "$val" ]; then + echo "❌ Secret missing: $v. Add it to Gitea repo secrets." + exit 1 + fi + done + # POSTHOG_API_KEY may legitimately be empty (PostHog not yet wired). + # NOTE: heredoc body MUST start at column 0 — xcconfig keys are + # whitespace-sensitive, leading spaces would break the build setting. + cat > tabatago-swift/Config/Secrets.xcconfig </dev/null || echo 0) + # Re-fetch ALL comments each cycle (not since_id) so we also catch + # edits — e.g. a reviewer changing "KO" → "LGTM". Negligible cost for + # PRs with <50 comments. while [ $TRIES -lt $MAX ]; do sleep 30 TRIES=$((TRIES + 1)) COMMENTS=$(curl -s -H "Authorization: token ${GT_TOKEN}" \ - "${API}/issues/${PR}/comments?since_id=${LAST_ID}") + "${API}/issues/${PR}/comments?limit=50&page=1") - if echo "$COMMENTS" | grep -qi '"body": *"LGTM"'; then - echo "✅ LGTM reçu ! Merge..." + # Match LGTM/KO anywhere in the body (with a word boundary so "KOM" + # or "LGTMX" don't trigger). Case-insensitive. + if echo "$COMMENTS" | grep -qiE '"body":[[:space:]]*"[^"]*\bLGTM\b'; then + echo "✅ LGTM reçu ! Squash-merge..." curl -s -X POST \ -H "Authorization: token ${GT_TOKEN}" \ -H "Content-Type: application/json" \ - -d "{\"Do\":\"merge\"}" \ + -d "{\"Do\":\"squash\"}" \ "${API}/pulls/${PR}/merge" - echo "✅ Mergée." + echo "✅ Mergée (squash)." exit 0 fi - if echo "$COMMENTS" | grep -qi '"body": *"KO"'; then + if echo "$COMMENTS" | grep -qiE '"body":[[:space:]]*"[^"]*\bKO\b'; then echo "❌ KO reçu. Bloquée." exit 1 fi -- 2.49.1 From 0b476f5871c00c8adaf563288430a07e8bac694e Mon Sep 17 00:00:00 2001 From: Millian Lamiaux Date: Sun, 19 Jul 2026 16:50:47 +0200 Subject: [PATCH 2/6] fix(project.yml): build watch targets in TabataGo scheme MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The TabataGo scheme only listed TabataGo + TabataGoWidget (iOS), so xcodebuild -scheme TabataGo never compiled the watchOS app in CI. Add TabataGoWatch + TabataGoWatchWidget to the build targets so watch-side breakage is caught before deploy. Note: TabataGoWidget is a real iOS widget target (defined at project.yml line 165, app-extension/iOS), NOT a phantom — the prior review was wrong on that point. The real gap was the missing watch targets. Embed deps were already correct (TabataGo embeds TabataGoWatch, which embeds TabataGoWatchWidget). Verified locally with xcodegen 2.45.4: the generated xcscheme now lists all 4 BlueprintNames. ⚠️ Risk: if the watch target has latent build issues (never compiled in CI before), this will surface them on the next PR. Desirable — catch before merge, not in the field. --- tabatago-swift/project.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tabatago-swift/project.yml b/tabatago-swift/project.yml index 0fe03fe..7e1004d 100644 --- a/tabatago-swift/project.yml +++ b/tabatago-swift/project.yml @@ -216,6 +216,8 @@ schemes: targets: TabataGo: all TabataGoWidget: all + TabataGoWatch: all + TabataGoWatchWidget: all run: config: Debug archive: -- 2.49.1 From 756b1a405c4bb28e507d5488b8ca60f449be87b8 Mon Sep 17 00:00:00 2001 From: Millian Lamiaux Date: Sun, 19 Jul 2026 16:51:02 +0200 Subject: [PATCH 3/6] =?UTF-8?q?docs(agents):=20align=20CI=20docs=20(=C2=A7?= =?UTF-8?q?5/=C2=A77/=C2=A78)=20with=20real=20workflow=20behavior?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rewrite AGENTS.md to match the corrected pr-iphone-deploy.yml: §5 Workflow: - Clean step: 'GARDE build/spm-cache' (not ../build/spm-cache), explicit warning never to 'rm -rf build/', note that Xcode global cache is spared. - New step 5: write Secrets.xcconfig from Gitea secrets before xcodegen. - Scheme lists 4 targets (TabataGo, TabataGoWidget, TabataGoWatch, TabataGoWatchWidget) — clarify TabataGoWidget is iOS, not watchOS. - wait-approval: re-fetches all comments each cycle, regex \bLGTM\b/\bKO\b anywhere in body, squash-merge. - Document concurrency block and timeouts. §5 Secrets table: - Replace false 'Injectés via Config/Secrets.xcconfig → Info.plist' with the real flow (CI writes file from secrets, Info.plist reads $(VAR)). - Add historical-exposure warning: Secrets.xcconfig was committed before the gitignore rule; key rotation recommended. §5 Pitfalls + §8 anti-patterns: - 'rm -rf build/' (not just 'rm -rf cache SPM'). - Don't nuke ~/Library/Caches/com.apple.dt.Xcode. - permissions: block is intentionally absent (no-op under Gitea API). - Don't remove concurrency block. - Don't anchor LGTM grep to body start. - {"Do":"squash"} not {"Do":"merge"}. - Scheme must list all 4 targets. §7 rule #9: expanded with the CI-writes-file flow. --- AGENTS.md | 44 +++++++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index a8c2ab3..cc6e685 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -152,34 +152,43 @@ Complications : `TabataGoComplication`. ### Workflow : `.github/workflows/pr-iphone-deploy.yml` **Trigger** : PR ouverte/synchronize/reopened sur `main`. -**Runner** : label `macos` (auto-hébergé). +**Runner** : label `macos` (auto-hébergé). `timeout-minutes: 30` (build-deploy), `120` (wait-approval). +**Concurrency** : `group: pr-iphone-deploy-`, `cancel-in-progress: true` → un push sur une PR existante annule le pipeline précédent (pas de double-merge, pas de runner gaspillé). Différentes PRs tournent en parallèle. **Déroulé** : 1. **Checkout** (shallow, branche head) via `PR_API_TOKEN`. 2. **Setup PATH** : `echo "/opt/homebrew/bin" >> $GITHUB_PATH` — requis car Rosetta ne le voit pas. 3. **Clean ciblé** : - - Supprime build artifacts, `TabataGo.xcodeproj`, `Package.resolved`, DerivedData, ModuleCache. - - **GARDE le cache SPM** (`../build/spm-cache`) — RevenueCat ≈ 1.1 GiB, re-cloner = 5+ min et échecs aléatoires du sandbox. -4. **Install tools** : `brew install xcodegen node ios-deploy`. -5. **xcodegen generate** → `xcodebuild -resolvePackageDependencies` → **build** (`-scheme TabataGo`, Debug, auto-provisioning, team `2MJF39L8VY`). -6. **Deploy iPhone** UDID `00008120-000925CE3672201E` : `devicectl` WiFi d'abord, fallback `ios-deploy` USB. -7. **Post comment** "Prêt à tester" sur la PR. -8. **Job `wait-approval`** : poll (30s, max 240 = 2h) les commentaires de la PR. `LGTM` → auto-merge. `KO` → blocage. Timeout → fail. + - Supprime `build/derived`, `TabataGo.xcodeproj`, `Package.resolved`, DerivedData, ModuleCache. + - **GARDE le cache SPM** (`build/spm-cache` — c'est le chemin passé à `-clonedSourcePackagesDirPath`) — RevenueCat ≈ 1.1 GiB, re-cloner = 5+ min et échecs aléatoires du sandbox. **Ne jamais faire `rm -rf build/`** : ça détruirait ce cache. Le clean step ne touche que `build/derived`. + - Ne touche **pas** à `~/Library/Caches/com.apple.dt.Xcode` (cache Xcode global — le nuke ralentit tous les builds suivants). +4. **Install tools** : `brew install xcodegen node ios-deploy` (sans `|| true` — si brew down, on fail ici, pas deux steps plus loin). +5. **Write `Config/Secrets.xcconfig`** depuis les secrets Gitea (`SUPABASE_URL`, `SUPABASE_ANON_KEY`, `REVENUECAT_API_KEY`, `POSTHOG_API_KEY`). Preflight : fail loud si un secret requis manque. Le fichier est gitigné (règle #9) ; CI l'écrase à chaque run. `Info.plist` lit les valeurs via `$(SUPABASE_URL)` etc., et `project.yml` wire le xcconfig en Debug+Release — aucun autre change­ment nécessaire côté build. +6. **xcodegen generate** → `xcodebuild -resolvePackageDependencies` → **build** (`-scheme TabataGo`, Debug, auto-provisioning, team `2MJF39L8VY`). Le scheme build les 4 targets : `TabataGo` (app iOS), `TabataGoWidget` (widget iOS, vrai target — ne pas confondre avec `TabataGoWatchWidget` qui est watchOS), `TabataGoWatch` (app watchOS), `TabataGoWatchWidget` (complication watchOS). +7. **Deploy iPhone** UDID `00008120-000925CE3672201E` : `devicectl` WiFi d'abord, fallback `ios-deploy` USB. +8. **Post comment** "Prêt à tester" sur la PR. +9. **Job `wait-approval`** : poll (30s, max 240 = 2h). **Re-fetche TOUS les comments** chaque cycle (pas `since_id`) pour attraper aussi les edits (un reviewer passant de "KO" à "LGTM"). Match LGTM/KO n'importe où dans le body (regex `\bLGTM\b` / `\bKO\b`, case-insensitive) — "Tested, LGTM!" compte ; "KOM" ne compte pas. `LGTM` → **squash-merge** (`{"Do":"squash"}`). `KO` → blocage. Timeout → fail. ### Secrets | Secret | Usage | |--------|-------| -| `PR_API_TOKEN` | Checkout + API Gitea (comment, merge). **JAMAIS `GITEA_TOKEN`/`GITHUB_TOKEN`** | -| `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `REVENUECAT_API_KEY`, `POSTHOG_API_KEY` | Injectés via `Config/Secrets.xcconfig` → Info.plist | +| `PR_API_TOKEN` | Checkout + API Gitea (comment, merge). **JAMAIS `GITEA_TOKEN`/`GITHUB_TOKEN`**. Tout passe par l'API Gitea avec ce token — un bloc `permissions:` natif serait no-op, c'est pourquoi il n'y en a pas. | +| `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `REVENUECAT_API_KEY`, `POSTHOG_API_KEY` | Un step CI écrit `Config/Secrets.xcconfig` depuis ces 4 secrets **avant** `xcodegen generate`. `Info.plist` lit les valeurs via `$(SUPABASE_URL)` etc. ; `project.yml` wire le xcconfig en Debug+Release. `POSTHOG_API_KEY` peut être vide (PostHog pas encore câblé en SPM). | + +> ⚠️ **Historique** : `Config/Secrets.xcconfig` a été committé par erreur dans le passé (avant la règle gitignore) avec des valeurs réelles. Le fichier est maintenant gitigné et écrasé par CI, mais l'historique git contient encore l'ancienne version — **rotation des clés Supabase/RevenueCat recommandée côté ops**. ### ⚠️ Pitfalls CI - `/opt/homebrew/bin` **hors PATH par défaut** sous Rosetta → toujours l'ajouter. -- **xcodegen 2.45.4 ne génère pas de schemes auto** → scheme `TabataGo` défini explicitement dans `project.yml` (ne pas le supprimer). -- **Ne jamais `rm -rf` le cache SPM** dans le CI (c'est l'inverse des build artifacts). +- **xcodegen 2.45.4 ne génère pas de schemes auto** → scheme `TabataGo` défini explicitement dans `project.yml` (ne pas le supprimer). Il liste **4 targets** : `TabataGo`, `TabataGoWidget`, `TabataGoWatch`, `TabataGoWatchWidget`. +- **Ne jamais `rm -rf build/` ni `rm -rf build/spm-cache`** dans le CI — RevenueCat y vit. Le clean step ne touche que `build/derived`. - `SWIFT_ENABLE_EXPLICIT_MODULES=NO` au build — requis sinon segfault linker sur `.pcm` stale. - `-skipPackagePluginValidation -allowProvisioningUpdates`. +- **Concurrency** : le bloc `concurrency:` est volontaire — ne pas le retirer, sinon les pushes successifs empilent des pipelines et tentent des double-merges. +- **LGTM/KO regex** : matche n'importe où dans le body avec `\b...\b` (word boundary). Ne pas revenir à un `grep '"body": *"LGTM"'` ancré — il raterait "Tested, LGTM!". +- **Squash-merge** (`{"Do":"squash"}`) — pas merge commit ni rebase. Garde l'historique `main` linéaire. +- **Comment edits** : `wait-approval` re-fetche tous les comments chaque cycle (pas `since_id`) pour attraper les edits. --- @@ -219,7 +228,7 @@ Node.js (`server.js`, `package.json`, `Dockerfile`). Télécharge l'audio de pla 6. **Navigation = TabView + sheet/fullScreenCover.** **Pas de `NavigationStack`.** 7. **Tout en français** côté user-facing (`Localizable.xcstrings` / `L10n`). Codes/types restent en anglais. 8. **XcodeGen.** Éditer `project.yml`, pas `.xcodeproj`. Régénérer avec `xcodegen generate`. -9. **Secrets.** Jamais committer `Config/Secrets.xcconfig`. Template = `.example`. +9. **Secrets.** Jamais committer `Config/Secrets.xcconfig`. Template = `.example`. En CI, un step écrit ce fichier depuis les secrets Gitea (`SUPABASE_URL`, `SUPABASE_ANON_KEY`, `REVENUECAT_API_KEY`, `POSTHOG_API_KEY`) **avant** `xcodegen generate` ; `Info.plist` lit les valeurs via `$(VAR)` et `project.yml` wire le xcconfig en Debug+Release. 10. **Lazy d'abord.** Avant d'ajouter une dépendance : stdlib Apple, puis ce qui existe déjà dans le repo, puis SPM déjà installé. PostHog n'est pas installé — ne pas l'ajouter sans justification. --- @@ -234,9 +243,14 @@ Node.js (`server.js`, `package.json`, `Dockerfile`). Télécharge l'audio de pla | `@ObservableObject` / `@Published` | `@Observable` | | Force-unwrap `!` | `guard let` | | Strings UI en anglais | Françaises dans `L10n`/`Localizable.xcstrings` | -| `rm -rf` le cache SPM en CI | Garder `build/spm-cache` | +| `rm -rf build/` ou `rm -rf build/spm-cache` en CI | Garder `build/spm-cache` ; ne cleaner que `build/derived` | +| `rm -rf ~/Library/Caches/com.apple.dt.Xcode` | Laisser le cache Xcode global intact (sinon tous les builds ralentissent) | | Utiliser `GITEA_TOKEN` en CI | `PR_API_TOKEN` uniquement | -| Supprimer le scheme explicite dans `project.yml` | xcodegen 2.45.4 n'en crée pas | +| Compter sur un bloc `permissions:` natif Gitea Actions | Tout passe par l'API Gitea avec `PR_API_TOKEN` ; le bloc natif serait no-op | +| Retirer le bloc `concurrency:` | Permet double-merge et pipelines empilés | +| Ancre le grep LGTM au début du body (`'"body": *"LGTM"'`) | Regex `\bLGTM\b` n'importe où dans le body (attrape "Tested, LGTM!") | +| `{"Do":"merge"}` (merge commit) | `{"Do":"squash"}` pour un `main` linéaire | +| Supprimer le scheme explicite dans `project.yml` | xcodegen 2.45.4 n'en crée pas — scheme doit lister `TabataGo`/`TabataGoWidget`/`TabataGoWatch`/`TabataGoWatchWidget` | | Compter sur `admin-web/` pour la app iOS | Dashboard admin séparé, communique via Supabase uniquement | | Recréer `programs`/`program_workouts` | Remplacés par `workout_programs`/`program_tabatas` (migration 005) | -- 2.49.1 From 244270488accf7cec12c63e5f8e85c6193bed2ca Mon Sep 17 00:00:00 2001 From: Millian Lamiaux Date: Sun, 19 Jul 2026 18:44:28 +0200 Subject: [PATCH 4/6] fix(ci): only run pr-iphone-deploy when tabatago-swift or the workflow changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a paths: filter to the pull_request trigger so the iOS build+deploy only fires when the PR actually touches: - tabatago-swift/** (the iOS/watchOS app source) - .github/workflows/pr-iphone-deploy.yml (this workflow itself) PRs touching only AGENTS.md, admin-web/, youtube-worker/, supabase/, docs/, or root config no longer spin up the macOS runner — no point rebuilding and deploying an unchanged .app. Including the workflow file itself in paths: ensures a PR that only edits the workflow logic still triggers it (so this branch's own PR will run to validate the changes). --- .github/workflows/pr-iphone-deploy.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/pr-iphone-deploy.yml b/.github/workflows/pr-iphone-deploy.yml index 9ed691e..3426907 100644 --- a/.github/workflows/pr-iphone-deploy.yml +++ b/.github/workflows/pr-iphone-deploy.yml @@ -8,6 +8,12 @@ on: pull_request: branches: [main] types: [opened, synchronize, reopened] + # Only run the iOS build+deploy when the PR actually touches the Swift app + # or this workflow itself. Docs-only / backend-only / admin-web-only PRs + # skip the runner entirely (no point rebuilding an unchanged .app). + paths: + - 'tabatago-swift/**' + - '.github/workflows/pr-iphone-deploy.yml' # Every mutation (comments, merge) goes through the Gitea API with PR_API_TOKEN, # never the runner's native GITHUB_TOKEN — so a `permissions:` block would be -- 2.49.1 From 437ec130fe205619c47f699831025f469735686d Mon Sep 17 00:00:00 2001 From: Millian Lamiaux Date: Sun, 19 Jul 2026 20:47:38 +0200 Subject: [PATCH 5/6] fix(ci): run brew under ARM + drop ios-deploy, use devicectl only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two related fixes to the Install tools / Deploy steps: 1. Rosetta/ARM-prefix conflict (unblocks run #142 failure) Run #142 failed at 'brew install': Cannot install under Rosetta 2 in ARM default prefix (/opt/homebrew)! The act_runner shell runs under Rosetta 2 on Apple Silicon, but Homebrew is installed natively in /opt/homebrew (ARM prefix). Brew refuses to mix architectures and exits 1 — surfaced now because xcodegen/node registered as outdated and brew tried to upgrade them. Prefix the brew call with 'arch -arm64' (brew's own suggested workaround) so the subprocess runs natively regardless of the parent shell's arch. 2. Drop ios-deploy, use xcrun devicectl only The deploy step previously tried devicectl (WiFi) first, then fell back to ios-deploy (USB). devicectl handles both network and wired discovery natively, so the separate USB-fallback tool is unnecessary — removed from the brew install and the deploy step. Implication: a devicectl failure = deploy failure (no second tool to fall back to). Acceptable: devicectl covers both transports. Also updates workflow name/job labels and the 'Prêt à tester' comment to drop 'WiFi/USB' wording, and aligns AGENTS.md §5 step 4 (arch -arm64 + tool list) and step 7 (devicectl only). --- .github/workflows/pr-iphone-deploy.yml | 38 +++++++++++--------------- AGENTS.md | 4 +-- 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/.github/workflows/pr-iphone-deploy.yml b/.github/workflows/pr-iphone-deploy.yml index 3426907..a9f6e38 100644 --- a/.github/workflows/pr-iphone-deploy.yml +++ b/.github/workflows/pr-iphone-deploy.yml @@ -1,8 +1,8 @@ # ============================================================================= -# TabataGo — PR → Build → WiFi/USB Direct Deploy → Wait LGTM → Merge +# TabataGo — PR → Build → devicectl Deploy → Wait LGTM → Merge # ============================================================================= -name: PR → Build → WiFi/USB Deploy → LGTM +name: PR → Build → devicectl Deploy → LGTM on: pull_request: @@ -27,7 +27,7 @@ concurrency: jobs: build-deploy: - name: Build & Deploy to iPhone (WiFi/USB) + name: Build & Deploy to iPhone (devicectl) runs-on: macos timeout-minutes: 30 @@ -82,11 +82,12 @@ jobs: echo "✅ Caches nettoyés (SPM cache préservé)" - - name: Install tools (xcodegen, node, ios-deploy) + - name: Install tools (xcodegen, node) run: | - # No `|| true` — if brew is down, fail loudly here instead of producing - # confusing "command not found" errors in the next steps. - brew install xcodegen node ios-deploy + # The act_runner shell can land under Rosetta 2 on Apple Silicon; + # Homebrew lives in /opt/homebrew (ARM prefix), so run brew natively. + # (Brew refuses to install/upgrade under Rosetta in the ARM prefix.) + arch -arm64 brew install xcodegen node - name: Write Secrets.xcconfig from Gitea secrets env: @@ -143,27 +144,20 @@ jobs: CODE_SIGN_STYLE=Automatic \ DEVELOPMENT_TEAM=2MJF39L8VY - - name: Install to iPhone (WiFi → USB fallback) + - name: Install to iPhone (devicectl) run: | UDID="00008120-000925CE3672201E" APP=$(find build/derived -name 'TabataGo.app' -type d | head -1) - # 1. Try WiFi first (Xcode 15+ devicectl network discovery) - echo "📱 Trying WiFi install via devicectl..." - if xcrun devicectl device install app --device "$UDID" "$APP" > /tmp/devicectl.log 2>&1; then - echo "✅ Installed via WiFi" - exit 0 - fi - tail -10 /tmp/devicectl.log - - # 2. Fallback to USB via ios-deploy - echo "🔌 WiFi failed, trying USB..." - if ios-deploy --bundle "$APP" --id "$UDID" --justlaunch; then - echo "✅ Installed via USB" + # devicectl handles both WiFi (network) and wired (USB-C/Thunderbolt) + # discovery natively — no separate USB-fallback tool needed. + echo "📱 Installing via devicectl..." + if xcrun devicectl device install app --device "$UDID" "$APP"; then + echo "✅ Installed via devicectl" exit 0 fi - echo "❌ Install failed (both WiFi and USB)" + echo "❌ devicectl install failed" exit 1 @@ -177,7 +171,7 @@ jobs: curl -s -X POST \ -H "Authorization: token ${GT_TOKEN}" \ -H "Content-Type: application/json" \ - -d "{\"body\":\"## 📱 Prêt à tester !\\n\\nL'app est déployée sur l'iPhone (USB/WiFi).\\n\\n- Teste les changements\\n- Reply **LGTM** pour merger\\n- Reply **KO** pour bloquer\"}" \ + -d "{\"body\":\"## 📱 Prêt à tester !\\n\\nL'app est déployée sur l'iPhone (devicectl).\\n\\n- Teste les changements\\n- Reply **LGTM** pour merger\\n- Reply **KO** pour bloquer\"}" \ "${GITEA_URL}/api/v1/repos/${REPO}/issues/${PR}/comments" wait-approval: diff --git a/AGENTS.md b/AGENTS.md index cc6e685..c14ebeb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -162,10 +162,10 @@ Complications : `TabataGoComplication`. - Supprime `build/derived`, `TabataGo.xcodeproj`, `Package.resolved`, DerivedData, ModuleCache. - **GARDE le cache SPM** (`build/spm-cache` — c'est le chemin passé à `-clonedSourcePackagesDirPath`) — RevenueCat ≈ 1.1 GiB, re-cloner = 5+ min et échecs aléatoires du sandbox. **Ne jamais faire `rm -rf build/`** : ça détruirait ce cache. Le clean step ne touche que `build/derived`. - Ne touche **pas** à `~/Library/Caches/com.apple.dt.Xcode` (cache Xcode global — le nuke ralentit tous les builds suivants). -4. **Install tools** : `brew install xcodegen node ios-deploy` (sans `|| true` — si brew down, on fail ici, pas deux steps plus loin). +4. **Install tools** : `arch -arm64 brew install xcodegen node` (sans `|| true` — si brew down, on fail ici, pas deux steps plus loin). `arch -arm64` requis car le shell act_runner tourne sous Rosetta 2 sur Apple Silicon, mais Homebrew est dans le prefix ARM `/opt/homebrew` — sans ça, brew refuse d'installer/upgrader. 5. **Write `Config/Secrets.xcconfig`** depuis les secrets Gitea (`SUPABASE_URL`, `SUPABASE_ANON_KEY`, `REVENUECAT_API_KEY`, `POSTHOG_API_KEY`). Preflight : fail loud si un secret requis manque. Le fichier est gitigné (règle #9) ; CI l'écrase à chaque run. `Info.plist` lit les valeurs via `$(SUPABASE_URL)` etc., et `project.yml` wire le xcconfig en Debug+Release — aucun autre change­ment nécessaire côté build. 6. **xcodegen generate** → `xcodebuild -resolvePackageDependencies` → **build** (`-scheme TabataGo`, Debug, auto-provisioning, team `2MJF39L8VY`). Le scheme build les 4 targets : `TabataGo` (app iOS), `TabataGoWidget` (widget iOS, vrai target — ne pas confondre avec `TabataGoWatchWidget` qui est watchOS), `TabataGoWatch` (app watchOS), `TabataGoWatchWidget` (complication watchOS). -7. **Deploy iPhone** UDID `00008120-000925CE3672201E` : `devicectl` WiFi d'abord, fallback `ios-deploy` USB. +7. **Deploy iPhone** UDID `00008120-000925CE3672201E` : `xcrun devicectl device install app` uniquement. `devicectl` gère nativement la découverte WiFi (réseau) et filaire (USB-C/Thunderbolt) — pas de fallback `ios-deploy`. 8. **Post comment** "Prêt à tester" sur la PR. 9. **Job `wait-approval`** : poll (30s, max 240 = 2h). **Re-fetche TOUS les comments** chaque cycle (pas `since_id`) pour attraper aussi les edits (un reviewer passant de "KO" à "LGTM"). Match LGTM/KO n'importe où dans le body (regex `\bLGTM\b` / `\bKO\b`, case-insensitive) — "Tested, LGTM!" compte ; "KOM" ne compte pas. `LGTM` → **squash-merge** (`{"Do":"squash"}`). `KO` → blocage. Timeout → fail. -- 2.49.1 From bebe38110091a3a65b257b899e74d5a1f319a139 Mon Sep 17 00:00:00 2001 From: Millian Lamiaux Date: Sun, 19 Jul 2026 20:54:17 +0200 Subject: [PATCH 6/6] =?UTF-8?q?fix(ci):=20drop=20node=20from=20brew=20inst?= =?UTF-8?q?all=20=E2=80=94=20unused,=20triggers=20gemini-cli=20rebuild?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Run failed at 'brew install xcodegen node' AFTER both formulae upgraded successfully under ARM: brew's 'installed dependents check' then tried to rebuild gemini-cli (0.40.1 -> 0.46.0, an unrelated dev tool on the runner that depends on node) and exited 1. Verified node is NOT used by any step in pr-iphone-deploy.yml — only xcodegen is. The only node references in the repo are in scripts/prepare-release.cjs and scripts/verify-admin-web-docker.sh, neither of which runs in this workflow. Node was a historical leftover. Drop node from the brew install. No node upgrade = no dependents check = no gemini-cli rebuild. Also sync AGENTS.md §5 step 4. --- .github/workflows/pr-iphone-deploy.yml | 8 ++++++-- AGENTS.md | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pr-iphone-deploy.yml b/.github/workflows/pr-iphone-deploy.yml index a9f6e38..05d977b 100644 --- a/.github/workflows/pr-iphone-deploy.yml +++ b/.github/workflows/pr-iphone-deploy.yml @@ -82,12 +82,16 @@ jobs: echo "✅ Caches nettoyés (SPM cache préservé)" - - name: Install tools (xcodegen, node) + - name: Install tools (xcodegen) run: | # The act_runner shell can land under Rosetta 2 on Apple Silicon; # Homebrew lives in /opt/homebrew (ARM prefix), so run brew natively. # (Brew refuses to install/upgrade under Rosetta in the ARM prefix.) - arch -arm64 brew install xcodegen node + # Only xcodegen is needed — node is NOT used by any step in this + # workflow, and installing it triggers brew's "installed dependents + # check" which tries to rebuild unrelated formulae (e.g. gemini-cli) + # and fails the build. + arch -arm64 brew install xcodegen - name: Write Secrets.xcconfig from Gitea secrets env: diff --git a/AGENTS.md b/AGENTS.md index c14ebeb..fc22ba4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -162,7 +162,7 @@ Complications : `TabataGoComplication`. - Supprime `build/derived`, `TabataGo.xcodeproj`, `Package.resolved`, DerivedData, ModuleCache. - **GARDE le cache SPM** (`build/spm-cache` — c'est le chemin passé à `-clonedSourcePackagesDirPath`) — RevenueCat ≈ 1.1 GiB, re-cloner = 5+ min et échecs aléatoires du sandbox. **Ne jamais faire `rm -rf build/`** : ça détruirait ce cache. Le clean step ne touche que `build/derived`. - Ne touche **pas** à `~/Library/Caches/com.apple.dt.Xcode` (cache Xcode global — le nuke ralentit tous les builds suivants). -4. **Install tools** : `arch -arm64 brew install xcodegen node` (sans `|| true` — si brew down, on fail ici, pas deux steps plus loin). `arch -arm64` requis car le shell act_runner tourne sous Rosetta 2 sur Apple Silicon, mais Homebrew est dans le prefix ARM `/opt/homebrew` — sans ça, brew refuse d'installer/upgrader. +4. **Install tools** : `arch -arm64 brew install xcodegen` (sans `|| true` — si brew down, on fail ici, pas deux steps plus loin). `arch -arm64` requis car le shell act_runner tourne sous Rosetta 2 sur Apple Silicon, mais Homebrew est dans le prefix ARM `/opt/homebrew` — sans ça, brew refuse d'installer/upgrader. Seul xcodegen est nécessaire : **node n'est pas utilisé** par ce workflow, et l'installer déclenche le "installed dependents check" de brew qui essaie de rebuilder des formulae sans rapport (ex. `gemini-cli`) et fail le build. 5. **Write `Config/Secrets.xcconfig`** depuis les secrets Gitea (`SUPABASE_URL`, `SUPABASE_ANON_KEY`, `REVENUECAT_API_KEY`, `POSTHOG_API_KEY`). Preflight : fail loud si un secret requis manque. Le fichier est gitigné (règle #9) ; CI l'écrase à chaque run. `Info.plist` lit les valeurs via `$(SUPABASE_URL)` etc., et `project.yml` wire le xcconfig en Debug+Release — aucun autre change­ment nécessaire côté build. 6. **xcodegen generate** → `xcodebuild -resolvePackageDependencies` → **build** (`-scheme TabataGo`, Debug, auto-provisioning, team `2MJF39L8VY`). Le scheme build les 4 targets : `TabataGo` (app iOS), `TabataGoWidget` (widget iOS, vrai target — ne pas confondre avec `TabataGoWatchWidget` qui est watchOS), `TabataGoWatch` (app watchOS), `TabataGoWatchWidget` (complication watchOS). 7. **Deploy iPhone** UDID `00008120-000925CE3672201E` : `xcrun devicectl device install app` uniquement. `devicectl` gère nativement la découverte WiFi (réseau) et filaire (USB-C/Thunderbolt) — pas de fallback `ios-deploy`. -- 2.49.1