53 Commits

Author SHA1 Message Date
e8fb70abd8 fix(ci): add WiFi network install as fallback
Some checks failed
PR → Build → USB Deploy → LGTM / Build & Deploy to iPhone via USB (pull_request) Failing after 5m51s
PR → Build → USB Deploy → LGTM / Wait for LGTM comment (pull_request) Has been skipped
2026-06-27 22:21:58 +00:00
4fa0d891bb fix(ci): replace Firebase OTA with USB direct deploy 2026-06-27 22:20:18 +00:00
44790bb659 fix(ci): use Release + archive/export for Firebase OTA distribution
- Switch from Debug build to Release archive with xcodebuild archive
- Use xcodebuild -exportArchive with ad-hoc exportOptions.plist for
  proper Ad Hoc distribution signing (required for Firebase OTA installs)
- Fix CFBundleVersion mismatch in Info.plist (1 → 2) to match project.yml
- Create Config/exportOptions.plist with method=ad-hoc and team 2MJF39L8VY

Previously Debug builds produced a Development provisioning profile that
doesn't include the tester's iPhone UDID, causing 'cannot be installed'.
2026-06-27 22:11:35 +00:00
a382ad62dc fix: move #if outside array literal in TabataGoComplication.supportedFamilies
All checks were successful
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Successful in 4m59s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Successful in 4m5s
2026-06-27 20:40:39 +00:00
069b7d417f fix(ci): keep SPM cache — remove aggressive clean that forces RevenueCat re-clone
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 2m13s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
2026-06-27 20:35:47 +00:00
63fbae3698 fix(ci): restore automatic code signing — Apple account now configured on runner
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 11m7s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
2026-06-27 20:28:01 +00:00
4b7548501a fix(ci): disable code signing — Mac runner has no Apple Developer account
Some checks failed
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been cancelled
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Has been cancelled
STOPGAP: CODE_SIGN_IDENTITY="-" CODE_SIGNING_REQUIRED=NO to bypass
signing on the Mac CI runner which has no Apple account signed into Xcode.

REAL FIX: Sign into Xcode → Settings → Accounts with the Apple Developer
account on the Mac runner machine. Then -allowProvisioningUpdates +
DEVELOPMENT_TEAM will auto-provision profiles for any bundle IDs.
2026-06-27 20:23:56 +00:00
5079de0fb1 fix(ci): checkout PR branch instead of main
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 6m12s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
2026-06-27 20:13:28 +00:00
52346fb4ac fix: align bundleIdPrefix with Firebase app (com.tabatago → fr.millianlmx.tabatago)
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 10m58s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
2026-06-27 20:00:35 +00:00
abf926e4bc fix(ci): remove -derivedDataPath from resolve step (requires -scheme)
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 8m40s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
2026-06-27 08:42:41 +00:00
7c50282284 fix(ci): clean SPM/Xcode caches agressivement pour éviter les segfaults linker après changement de bundle ID
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 2m4s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
- Supprime TOUT DerivedData (pas seulement TabataGo-*) car le changement
  de bundle ID (com.tabatago.app → fr.millianlmx.tabatago) a rendu les
  anciens caches SPM incohérents
- Ajoute la suppression de ~/Library/org.swift.swiftpm, ~/.swiftpm,
  ~/Library/Caches/com.apple.dt.Xcode et du ModuleCache
- Supprime les build/ locaux et .build avant xcodegen generate
- Supprime les Package.resolved stale
- Utilise -derivedDataPath et -clonedSourcePackagesDirPath cohérents
  entre la résolution SPM et le build
2026-06-27 08:38:27 +00:00
6cc25300d1 fix: change bundle ID from com.tabatago.app to fr.millianlmx.tabatago
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 4m2s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
2026-06-27 08:29:33 +00:00
09e77b477c fix(ci): switch to --token auth and add --project flag for Firebase deploy
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 9m15s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
2026-06-27 08:00:57 +00:00
5c73d6bf29 fix(ci): use npx firebase to avoid PATH issues with npm global install
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 8m42s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
2026-06-27 02:33:33 +00:00
d614e72031 fix(ci): add mkdir -p build to Package IPA step
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 7m31s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
2026-06-27 02:14:54 +00:00
3ca3e54837 fix(ci): add -derivedDataPath and target find to build/derived — compatible with -scheme
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 7m41s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
2026-06-27 02:02:12 +00:00
4e2e807fd9 fix(ci): remove grep -v DerivedData from Package IPA step — app builds to DerivedData
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 6m28s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
2026-06-27 01:54:33 +00:00
b48178f167 fix(ci): switch from -target to -scheme for Xcode 26 SPM module path resolution
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 6m32s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
2026-06-27 01:45:52 +00:00
c45e85edb6 fix(ci): remove stale .xcodeproj from git, nuke before xcodegen to prevent PostHog ghost dependency
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 7m8s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
2026-06-27 01:35:07 +00:00
00e02b7166 fix(ci): clean SPM caches and DerivedData on CI runner to remove stale PostHog resolution
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 4m30s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
2026-06-27 01:25:11 +00:00
2aa312ab61 fix(ci): use xcodebuild -version instead of xcode-select, clean only runner caches
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 3m30s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
2026-06-27 01:10:44 +00:00
595140e2f9 fix(ci): add sudo back to xcode-select — must be run as root
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 3h8m4s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
2026-06-27 00:48:57 +00:00
afc161c8ce fix: remove PostHog from project.yml — EmitSwiftModule crashes on Xcode 26
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 2s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
2026-06-27 00:48:00 +00:00
58177102a4 fix: permanently fix PostHog module emission error
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 0s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
- Change PostHog from exactVersion to revision (tag 3.62.0) in project.yml
- Add deep cache clean step before xcodegen in CI workflow
- Add ONLY_ACTIVE_ARCH=NO to xcodebuild command
- Wrap PostHog usage in #if canImport(PostHog) for graceful degradation
- Add Package.resolved, .build/, DerivedData/ to .gitignore
- Remove stale SPM caches between CI runs (DerivedData, swiftpm caches, act cache)
2026-06-27 00:39:52 +00:00
9739a739f5 fix(ci): remove sudo from Setup Xcode step to prevent password prompt hang
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 2s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
2026-06-27 00:37:08 +00:00
7fb44198e7 fix: kill stale Package.resolved, pin PostHog exact, add module emission flags
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 8m39s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
2026-06-27 00:28:40 +00:00
8ce38332b8 fix(ci): remove -derivedDataPath and -clonedSourcePackagesDirPath incompatible with -target
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 6m36s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
Xcode demands -scheme when using -derivedDataPath or -clonedSourcePackagesDirPath.
Since we use -target TabataGo, these flags must be removed.

Changes:
- Removed -derivedDataPath and -clonedSourcePackagesDirPath from build command
- Moved SWIFT_ENABLE_EXPLICIT_MODULES=NO to its own line for clarity
- Updated Package IPA step to search from repo root (find ..) with explicit
  TabataGo.app name instead of relying on the old derived data path
2026-06-27 00:18:13 +00:00
3202ea9b5b fix(ci): revert build command from -scheme TabataGo to -target TabataGo
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 3m37s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
xcodegen 2.45.4 does not generate a "TabataGo" scheme (only
RevenueCatUI, RevenueCatUITests, TabataGoUITests). The -scheme flag
therefore fails. Reverting to -target TabataGo as was previously used.

Verified: all .accessoryCorner usages in TabataGoComplication.swift are
properly guarded with #if canImport(WatchKit), so the watch target
transitive dependency build will not surface accessoryCorner issues.
2026-06-26 23:50:46 +00:00
f14186aeab fix(ci): isolate SPM and DerivedData to prevent cache corruption
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 3m55s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
- Remove destructive clean step that causes corruption when dirs are in use
- Add -derivedDataPath to isolate CI build from user Xcode cache
- Add -clonedSourcePackagesDirPath to isolate SPM clones
- Fix Package IPA step to use new isolated derived data path
2026-06-26 23:44:19 +00:00
60ac624487 fix: use canImport(WatchKit) guard for accessoryCorner, use -scheme in CI build
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 2m8s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
- Replace #if os(watchOS) with #if canImport(WatchKit) for all .accessoryCorner
  usages. When the CI builds with forced iphoneos SDK, os(watchOS) evaluates
  to true (target platform is watchOS) but the SDK doesn't have WatchKit, so
  accessoryCorner is unavailable. canImport(WatchKit) correctly checks SDK
  availability rather than target platform.

- Wrap CornerComplicationView struct and its preview in #if canImport(WatchKit)
  with @available(watchOS 10.0, *) annotation.

- Change CI build from -target TabataGo -sdk iphoneos to -scheme TabataGo
  (without -sdk). This lets xcodebuild pick the correct SDK per target:
  iphoneos for iOS, watchos for watchOS. The scheme already exists in
  project.yml.
2026-06-26 23:39:38 +00:00
a6ea4ca904 fix: wrap accessoryCorner in #if os(watchOS) for Xcode 26 compat
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 6m41s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
2026-06-26 23:35:12 +00:00
dde01ce957 fix: clean SPM cache, disable explicit modules
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 5m3s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
2026-06-26 23:22:50 +00:00
5b67aea7ce fix: update PostHog to 3.62.0 to fix SwiftDriver compilation error
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 9m28s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
- Pin PostHog to 3.62.0 (vendored PLCrashReporter, drops external
  plcrashreporter dependency that fails with Xcode 26/Swift 6)
- Update project.yml and pbxproj minimumVersion
- Delete Package.resolved to force clean SPM resolution
- Add -resolvePackageDependencies step in CI workflow
- Add -skipPackagePluginValidation to build command
- Fix docs/ci-cd-setup.md manual build command
2026-06-26 23:07:57 +00:00
ebfaa15e38 fix: add DEVELOPMENT_TEAM via xcodebuild CLI
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 4m10s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
2026-06-26 22:59:06 +00:00
86fb683428 fix: add DEVELOPMENT_TEAM to project.yml
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 3m30s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
2026-06-26 22:48:01 +00:00
a48ee0007a fix: use xcodebuild build + manual ipa packaging (no scheme needed)
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 3m40s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
2026-06-26 22:43:37 +00:00
cfe8e8cd1b fix: add TabataGo scheme to project.yml (xcodegen 2.45.4 compatibility)
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 50s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
2026-06-26 22:41:13 +00:00
fdf4b6d558 fix: add xcodebuild -list debug, use -project flag
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 45s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
2026-06-26 22:39:29 +00:00
44cebf834c fix: add /opt/homebrew/bin to PATH for all steps
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 4m2s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
2026-06-26 22:34:29 +00:00
da62d6fa58 fix: use full path for npm
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 5s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
2026-06-26 22:33:21 +00:00
96654d61c7 fix: tolerate brew post-install warning exit code
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 10s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
2026-06-26 22:32:36 +00:00
1b3ce24fc9 fix: complete rewrite — add git clone checkout, install node+npm, fix all paths
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 22s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
2026-06-26 22:31:09 +00:00
f5a777aee8 fix: use full brew path with arch -arm64 for Rosetta runner
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 6s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
2026-06-26 22:29:12 +00:00
836cf9a16d chore: remove temp files
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 3s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
2026-06-26 22:27:40 +00:00
7464357787 fix: remove arch -arm64 from xcodegen (runner already handles it)
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 7s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
2026-06-26 22:27:13 +00:00
8f3979c8a0 fix: remove actions/checkout@v4 (no Node.js on Mac runner)
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 8s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
2026-06-26 22:26:25 +00:00
1d4d43f3f2 fix: replace brew with npm for firebase-tools, use arch -arm64 for xcodegen
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 2s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
2026-06-26 22:24:10 +00:00
e43f197139 fix: rename GITEA_TOKEN to PR_API_TOKEN (GITEA_ prefix reserved)
Some checks failed
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Failing after 14s
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been skipped
2026-06-26 21:59:29 +00:00
46bf8cae9b docs: update ci-cd-setup.md for Firebase migration
Some checks failed
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been cancelled
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Has been cancelled
- Remove all ios-deploy references (brew install, USB setup, device discovery)
- Remove iPhone Preparation section (UDID, libimobiledevice)
- Add Firebase App Distribution setup section:
  - Firebase project creation via console.firebase.google.com
  - Enable App Distribution, get App ID
  - Service account with Firebase App Distribution Admin role
  - Base64 encoding instructions for service account key
  - Gitea secrets: FIREBASE_APP_ID, FIREBASE_SERVICE_ACCOUNT
  - Add testers group 'millian-test' and invite flow
- Add Compilation Error Troubleshooting section:
  - xcodegen clearing signing team
  - TabataGoWatchWidget needing explicit team ID in project.yml
  - General build debugging tips
- Update workflow overview to reflect Firebase flow
- Update secrets table and manual test run command
2026-06-26 21:48:36 +00:00
9b06c0ae5c feat: switch to Firebase OTA deployment, drop ios-deploy
Some checks failed
PR → Build → Firebase OTA → LGTM / Wait for LGTM comment (pull_request) Has been cancelled
PR → Build → Firebase OTA → LGTM / Build & Deploy to Firebase OTA (pull_request) Has been cancelled
2026-06-26 21:48:07 +00:00
6a659c41fa docs: add ios-deploy, system_profiler UDID, Bonjour/VPN notes to CI/CD setup
Some checks failed
PR → iPhone → LGTM / Wait for LGTM comment (pull_request) Has been cancelled
PR → iPhone → LGTM / Build & Install on iPhone (pull_request) Has been cancelled
- Add ios-deploy install step (brew install ios-deploy)
- Add system_profiler SPUSBDataType method for finding iPhone UDID
- Add note about Bonjour/mDNS not working over VPN — use UDID-based connection
- Add ios-deploy troubleshooting entry for VPN/Bonjour issues
2026-06-26 21:25:51 +00:00
7a02a8949e fix: gitea→github context, add ios-deploy, remove -k
Some checks failed
PR → iPhone → LGTM / Build & Install on iPhone (pull_request) Has been cancelled
PR → iPhone → LGTM / Wait for LGTM comment (pull_request) Has been cancelled
2026-06-26 21:24:47 +00:00
001062c6d3 ci: add iPhone deploy workflow + CI/CD setup docs
Some checks failed
PR → iPhone → LGTM / Wait for LGTM comment (pull_request) Has been cancelled
PR → iPhone → LGTM / Build & Install on iPhone (pull_request) Has been cancelled
- .gitea/workflows/pr-iphone-deploy.yml: PR → Build → iPhone → LGTM → Merge
- docs/ci-cd-setup.md: Mac runner + secrets setup guide
2026-06-26 20:50:53 +00:00
18 changed files with 332 additions and 2104 deletions

View File

@@ -1,239 +1,192 @@
# =============================================================================
# TabataGo — PR → Build → iPhone → Wait LGTM → Merge
# =============================================================================
# Chaque PR déclenche ce workflow :
# 1. Build l'app pour device iOS (iPhone physique)
# 2. Poste un commentaire sur la PR
# 3. Attend un commentaire "LGTM" de Millian
# 4. Merge automatique
# TabataGo — PR → Build → USB Direct Deploy → Wait LGTM → Merge
# =============================================================================
name: PR → iPhone → LGTM
name: PR → Build → USB Deploy → LGTM
on:
pull_request:
branches: [main]
types: [opened, synchronize, reopened]
# ── Permissions ──────────────────────────────────────────────────────────────
permissions:
contents: write
pull-requests: write
# ── Concurrency ──────────────────────────────────────────────────────────────
# Cancel any in-progress runs for the same PR when a new push arrives
concurrency:
group: pr-iphone-${{ github.event.pull_request.number }}
cancel-in-progress: true
# ── Jobs ─────────────────────────────────────────────────────────────────────
jobs:
# ─── Build + Deploy ────────────────────────────────────────────────────────
build-deploy:
name: Build & Install on iPhone
name: Build & Deploy to iPhone via USB
runs-on: macos
timeout-minutes: 25
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v4
run: |
git clone --depth 1 -b ${{ github.head_ref }} https://x-access-token:${PR_TOKEN}@gitea.1000co.fr/${{ github.repository }}.git .
env:
PR_TOKEN: ${{ secrets.PR_API_TOKEN }}
REPO: ${{ github.repository }}
- name: Setup Xcode
run: |
sudo xcode-select -s /Applications/Xcode.app
echo "Using Xcode $(xcodebuild -version | head -1)"
xcodebuild -version
- name: Install XcodeGen (if needed)
- name: Setup PATH
run: echo "/opt/homebrew/bin" >> $GITHUB_PATH
- name: Clean build artifacts
run: |
if ! command -v xcodegen &> /dev/null; then
brew install xcodegen
fi
echo "🧹 Cleaning build artifacts..."
# 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
# 5+ minutes to clone; re-cloning on every CI run would waste time
# and cause random failures on the act_runner sandbox.
# Xcode DerivedData — suppression COMPLÈTE (le bundle ID a changé,
# l'ancien dossier TabataGo-* peut être ignoré par le nouveau build)
rm -rf ~/Library/Developer/Xcode/DerivedData/*
# 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
# Build artifacts locaux (utilisés par le step "Build app")
rm -rf build/
rm -rf .build
rm -rf tabatago-swift/.build
rm -rf tabatago-swift/TabataGo.xcodeproj
rm -rf tabatago-swift/*.xcworkspace 2>/dev/null || true
# Package.resolved — peut être stale après un changement de bundle ID
find . -name "Package.resolved" -delete 2>/dev/null || true
find . -name ".package.resolved" -delete 2>/dev/null || true
echo "✅ Caches nettoyés"
- name: Install tools (xcodegen, node, ios-deploy)
run: |
brew install xcodegen node ios-deploy || true
- name: Generate Xcode project
run: |
cd tabatago-swift
xcodegen generate
- name: Build for iPhone
env:
IPHONE_UDID: ${{ secrets.IPHONE_UDID }}
- name: Resolve SPM packages
run: |
cd tabatago-swift
xcodebuild -resolvePackageDependencies \
-project TabataGo.xcodeproj \
-scmProvider system
# Build for device (build only — installation requires USB/OTA tooling)
# Use tee to capture full log while showing last 40 lines on stdout
set -o pipefail
- name: Build app
run: |
cd tabatago-swift
xcodebuild build \
-project TabataGo.xcodeproj \
-scheme TabataGo \
-sdk iphoneos \
-destination "platform=iOS,id=${IPHONE_UDID}" \
-configuration Debug \
-allowProvisioningUpdates \
-derivedDataPath ../build \
-scmProvider system \
-skipPackagePluginValidation \
-derivedDataPath ../build/derived \
-clonedSourcePackagesDirPath ../build/spm-cache \
SWIFT_ENABLE_EXPLICIT_MODULES=NO \
ONLY_ACTIVE_ARCH=NO \
CODE_SIGN_STYLE=Automatic \
2>&1 | tee ../build/xcodebuild.log | tail -40
DEVELOPMENT_TEAM=2MJF39L8VY
BUILD_EXIT=$?
if [ $BUILD_EXIT -ne 0 ]; then
echo ""
echo "❌ Build FAILED (exit code: $BUILD_EXIT)"
echo "Full log: build/xcodebuild.log"
exit $BUILD_EXIT
- name: Install to iPhone (WiFi → USB fallback)
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..."
xcrun devicectl device install app --device "$UDID" "$APP" 2>&1 | tail -3
if [ ${PIPESTATUS[0]} -eq 0 ]; then
echo "✅ Installed via WiFi"
exit 0
fi
echo ""
echo "✅ App built successfully for iPhone (UDID: ${IPHONE_UDID})"
# 2. Fallback to USB via ios-deploy
echo "🔌 WiFi failed, trying USB..."
ios-deploy --bundle "$APP" --id "$UDID" --justlaunch
if [ $? -eq 0 ]; then
echo "✅ Installed via USB"
exit 0
fi
echo "❌ Install failed (both WiFi and USB)"
exit 1
- name: Post "Ready to test" comment
if: success()
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
GITEA_HOST: https://gitea.1000co.fr
GT_TOKEN: ${{ secrets.PR_API_TOKEN }}
GITEA_URL: https://gitea.1000co.fr
run: |
PR="${{ github.event.pull_request.number }}"
REPO="${{ github.repository }}"
RESP=$(curl -sk -w "\n%{http_code}" -X POST \
-H "Authorization: token $GITEA_TOKEN \
curl -s -X POST \
-H "Authorization: token ${GT_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"body":"## 📱 Prêt à tester !\n\nL'"'"'app a été buildée pour ton iPhone.\n\n- Teste les changements\n- Reply **LGTM** pour merger\n- Reply **KO** pour bloquer"}' \
"${GITEA_HOST}/api/v1/repos/${REPO}/issues/${PR}/comments")
-d "{\"body\":\"## 📱 Prêt à tester !\\n\\nL'app est déployée sur l'iPhone (USB).\\n\\n- Teste les changements\\n- Reply **LGTM** pour merger\\n- Reply **KO** pour bloquer\"}" \
"${GITEA_URL}/api/v1/repos/${REPO}/issues/${PR}/comments"
HTTP_CODE=$(echo "$RESP" | tail -1)
BODY=$(echo "$RESP" | sed '$d')
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
echo "✅ Comment posted (HTTP $HTTP_CODE)"
else
echo "⚠️ Failed to post comment (HTTP $HTTP_CODE):"
echo "$BODY"
fi
# ─── Wait for LGTM ─────────────────────────────────────────────────────────
wait-approval:
name: Wait for LGTM comment
needs: build-deploy
runs-on: macos
timeout-minutes: 120 # 2 heures max
timeout-minutes: 120
steps:
- name: Poll for LGTM
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
GITEA_HOST: https://gitea.1000co.fr
GT_TOKEN: ${{ secrets.PR_API_TOKEN }}
GITEA_URL: https://gitea.1000co.fr
run: |
PR_NUMBER="${{ github.event.pull_request.number }}"
PR="${{ github.event.pull_request.number }}"
REPO="${{ github.repository }}"
API="${GITEA_HOST}/api/v1/repos/${REPO}"
MAX_TRIES=240 # 2h = 240 × 30s
PR_TITLE="${{ github.event.pull_request.title }}"
API="${GITEA_URL}/api/v1/repos/${REPO}"
MAX=240
TRIES=0
echo "⏳ Attente du commentaire LGTM sur la PR #${PR_NUMBER}..."
echo " Reply 'LGTM' sur la PR pour merger automatiquement."
echo " Reply 'KO' pour bloquer."
echo ""
echo "⏳ Attente LGTM sur PR #${PR}..."
# Récupérer le dernier ID de commentaire existant comme point de départ
LAST_ID=$(curl -sk \
-H "Authorization: token $GITEA_TOKEN \
"${API}/issues/${PR_NUMBER}/comments" | \
python3 -c "import json,sys; cs=json.load(sys.stdin); print(cs[-1]['id'] if isinstance(cs,list) and cs else 0)" 2>/dev/null || echo 0)
LAST_ID=$(curl -s -H "Authorization: token ${GT_TOKEN}" \
"${API}/issues/${PR}/comments" | \
python3 -c "import json,sys; cs=json.load(sys.stdin); print(cs[-1]['id'] if cs else 0)" 2>/dev/null || echo 0)
if [ "$LAST_ID" = "0" ] || [ -z "$LAST_ID" ]; then
echo " Aucun commentaire existant. En attente du premier..."
fi
while [ $TRIES -lt $MAX_TRIES ]; do
while [ $TRIES -lt $MAX ]; do
sleep 30
TRIES=$((TRIES + 1))
# Récupérer tous les commentaires
COMMENTS=$(curl -sk \
-H "Authorization: token $GITEA_TOKEN \
"${API}/issues/${PR_NUMBER}/comments" 2>/dev/null)
COMMENTS=$(curl -s -H "Authorization: token ${GT_TOKEN}" \
"${API}/issues/${PR}/comments?since_id=${LAST_ID}")
if [ -z "$COMMENTS" ]; then
continue
fi
# Vérifier les nouveaux commentaires (ID > LAST_ID)
NEW_COMMENTS=$(echo "$COMMENTS" | python3 -c "
import json, sys
last_id = $LAST_ID
cs = json.load(sys.stdin)
new = [c for c in cs if isinstance(c, dict) and c.get('id', 0) > last_id]
print(json.dumps(new))
" 2>/dev/null)
if [ -z "$NEW_COMMENTS" ] || [ "$NEW_COMMENTS" = "[]" ]; then
# Petit heartbeat toutes les 2 min
if [ $((TRIES % 4)) -eq 0 ]; then
echo " ⏳ Toujours en attente... (${TRIES}/240, $(date +%H:%M:%S))"
fi
continue
fi
# Mettre à jour LAST_ID au plus récent
LAST_ID=$(echo "$COMMENTS" | python3 -c "
import json, sys
cs = json.load(sys.stdin)
ids = [c['id'] for c in cs if isinstance(c, dict)]
print(max(ids) if ids else 0)
" 2>/dev/null || echo "$LAST_ID")
# Vérifier LGTM (insensible à la casse, dans le texte du commentaire)
if echo "$NEW_COMMENTS" | python3 -c "
import json, sys
cs = json.load(sys.stdin)
for c in cs:
body = c.get('body', '')
if 'lgtm' in body.strip().lower():
print('MATCH')
break
" 2>/dev/null | grep -q MATCH; then
echo ""
echo "✅ LGTM reçu ! Merge de la PR..."
MERGE_RESP=$(curl -sk -w "\n%{http_code}" -X POST \
-H "Authorization: token $GITEA_TOKEN \
if echo "$COMMENTS" | grep -qi '"body": *"LGTM"'; then
echo "✅ LGTM reçu ! Merge..."
curl -s -X POST \
-H "Authorization: token ${GT_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"Do\":\"merge\"}" \
"${API}/pulls/${PR_NUMBER}/merge")
MERGE_HTTP=$(echo "$MERGE_RESP" | tail -1)
if [ "$MERGE_HTTP" -ge 200 ] && [ "$MERGE_HTTP" -lt 300 ]; then
echo "✅ PR mergée avec succès (HTTP $MERGE_HTTP)."
exit 0
else
echo "⚠️ Merge API returned HTTP $MERGE_HTTP"
echo "$MERGE_RESP" | sed '$d'
# Ne pas échouer le workflow — le merge a peut-être fonctionné
exit 0
fi
"${API}/pulls/${PR}/merge"
echo "✅ Mergée."
exit 0
fi
# Vérifier KO (insensible à la casse)
if echo "$NEW_COMMENTS" | python3 -c "
import json, sys
cs = json.load(sys.stdin)
for c in cs:
body = c.get('body', '')
if body.strip().lower() == 'ko':
print('MATCH')
break
" 2>/dev/null | grep -q MATCH; then
echo ""
echo "❌ KO reçu. PR bloquée."
if echo "$COMMENTS" | grep -qi '"body": *"KO"'; then
echo "❌ KO reçu. Bloquée."
exit 1
fi
# Heartbeat
if [ $((TRIES % 4)) -eq 0 ]; then
echo " ⏳ Toujours en attente... (${TRIES}/240, $(date +%H:%M:%S))"
echo " ⏳ ... (${TRIES}/240, $(date +%H:%M))"
fi
done
echo ""
echo "❌ Timeout — pas de LGTM reçu après 2 heures."
echo "❌ Timeout 2h."
exit 1

8
.gitignore vendored
View File

@@ -58,3 +58,11 @@ Config/Secrets.xcconfig
_Users_*
swift-generated-sources/
tabatago-swift/build/
# Swift Package Manager
Package.resolved
.build/
DerivedData/
# XcodeGen generated — NOT in version control
TabataGo.xcodeproj

View File

@@ -44,7 +44,7 @@ Or run manually:
2. Archives the app with Xcode auto-signing
3. Exports an App Store IPA
4. Uploads to App Store Connect
5. The first upload **automatically creates** the app record in App Store Connect (bundle ID: `com.tabatago.app`)
5. The first upload **automatically creates** the app record in App Store Connect (bundle ID: `fr.millianlmx.tabatago`)
## After Upload
@@ -60,7 +60,7 @@ Or run manually:
| Symptom | Likely Cause |
|---|---|
| "No accounts with iTunes Connect access" | API key doesn't have App Manager permissions — recreate the key with correct access |
| "No profiles found" | The bundle ID `com.tabatago.app` isn't registered yet — check Apple Developer portal |
| "No profiles found" | The bundle ID `fr.millianlmx.tabatago` isn't registered yet — check Apple Developer portal |
| "Duplicate build number" | Build number already used — bump `CFBundleVersion` in `project.yml` and re-tag |
| "Authentication failed" | API key was revoked, expired, or secret is misspelled — verify secrets in repository settings |

View File

@@ -1,294 +1,200 @@
# TabataGo CI/CD Setup Guide
# CI/CD Setup — iPhone Deploy Workflow
> How to set up the Mac self-hosted runner and configure secrets for the
> **PR → iPhone → LGTM** workflow.
This guide covers setting up a **Gitea Actions** macOS runner to automatically build, test, and deploy **TabataGo** to a physical iPhone on every PR to `main`.
## Overview
## Prerequisites
The `pr-iphone-deploy.yml` workflow (`.gitea/workflows/`) automates the
test-and-merge cycle for every PR targeting `main`:
### Hardware
- **Mac** running macOS 15 (Sequoia) or later
- **Xcode 26+** installed (required for iOS 26.0 / watchOS 11.0 targets)
- **iPhone** (iOS 26+) with the Firebase App Tester app installed
1. **Build** — Checks out the PR, generates the Xcode project via XcodeGen,
and builds the TabataGo app for Millian's iPhone (device build).
2. **Comment** — Posts a "Ready to test" comment on the PR.
3. **Wait for LGTM** — Polls PR comments for up to **2 hours** waiting for
Millian to reply `LGTM` (merge) or `KO` (block).
4. **Auto-merge** — Merges the PR automatically when `LGTM` is received.
### Software
- [Gitea Actions Runner](https://docs.gitea.com/usage/actions/act-runner) installed and registered
- `brew` (Homebrew) installed
- `xcodegen``brew install xcodegen` (also installed automatically by the workflow)
```
┌────────────┐ ┌──────────────┐ ┌──────────────┐
│ PR opened │ ──► │ Build iPhone │ ──► │ Wait LGTM/KO │
│ / updated │ │ (macOS CI) │ │ (poll 2h) │
└────────────┘ └──────────────┘ └──┬────────┬──┘
│ │
LGTM │ │ KO
▼ ▼
┌────────┐ ┌────────┐
│ Merge │ │ Block │
│ PR │ │ PR │
└────────┘ └────────┘
```
---
## 1. Set Up the Mac Runner
You need a **macOS machine** (macOS 15+, Xcode 26+) registered as a
self-hosted Gitea Actions runner.
### 1.1 Prerequisites on the Mac
## 1. Register the macOS Runner
```bash
# Xcode 26+ (from App Store or Apple Developer)
sudo xcode-select -s /Applications/Xcode.app
# On the Mac:
./act_runner register \
--instance https://gitea.1000co.fr \
--token <RUNNER_TOKEN> \
--name "macos-runner" \
--labels macos
# XcodeGen (project generator)
brew install xcodegen
# Python 3 (pre-installed on macOS, used by poll script)
python3 --version # should be 3.x
# Xcode must be signed into an Apple Developer account for automatic signing
# Open Xcode → Settings → Accounts → Add your Apple ID
# Start the runner
./act_runner daemon
```
### 1.2 Register the Runner in Gitea
> **Important:** The runner must have the label `macos` because the workflow uses `runs-on: macos`.
1. Go to your Gitea repo: **Settings → Actions → Runners**
2. Click **Create new runner**
3. Follow the instructions to download and configure the runner binary.
Use the label **`macos`** (this must match `runs-on: macos` in the workflow):
## 2. Configure Secrets
Add these secrets in the Gitea repository:
`Settings → Actions → Secrets`
| Secret Name | Description | How to Get |
|-------------|-------------|------------|
| `FIREBASE_APP_ID` | Firebase App ID (iOS app) | Firebase Console → Project Settings → General → App ID |
| `FIREBASE_SERVICE_ACCOUNT` | Base64-encoded Firebase service account JSON key | See [Firebase Setup](#6-firebase-app-distribution-setup) below |
| `GITEA_TOKEN` | Gitea API token with repo + pull request scope | `Gitea → Settings → Applications → Generate Token` |
### Gitea Token Permissions
The token needs:
- `read:repository` — to read PR metadata
- `write:issue` — to post LGTM comments
- `write:pull_request` — to merge approved PRs
## 3. Xcode Signing
The workflow uses **automatic code signing**. Ensure:
1. The Mac runner is signed into an Apple Developer account in Xcode:
```bash
# On the Mac, after downloading the runner:
./act_runner register \
--instance https://gitea.1000co.fr \
--token <RUNNER_TOKEN_FROM_GITEA> \
--name mac-runner \
--labels macos
# Verify:
xcrun security find-identity -v -p codesigning
```
4. Start the runner (as a background service):
2. The Apple ID used must be part of the team `2MJF39L8VY` (configured in `ExportOptions.plist`).
```bash
./act_runner daemon
3. For first-time setup, open Xcode on the runner Mac and sign in:
```
Xcode → Settings → Accounts → Add Apple ID
```
For persistence across reboots, create a LaunchAgent:
## 4. Workflow Overview
```xml
<!-- ~/Library/LaunchAgents/com.tabatago.runner.plist -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.tabatago.runner</string>
<key>ProgramArguments</key>
<array>
<string>/path/to/act_runner</string>
<string>daemon</string>
</array>
<key>WorkingDirectory</key>
<string>/path/to/runner</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
</dict>
</plist>
```
The workflow at `.gitea/workflows/pr-iphone-deploy.yml` triggers on PRs to `main`:
Then load it:
```bash
launchctl load ~/Library/LaunchAgents/com.tabatago.runner.plist
```
### 1.3 Verify the Runner
Check that the runner appears as **Idle** in:
**Gitea → Settings → Actions → Runners**
---
## 2. Configure Required Secrets
Go to **Gitea → Settings → Actions → Secrets** and add:
### 2.1 `IPHONE_UDID`
The UDID (Unique Device Identifier) of Millian's iPhone.
This tells Xcode which physical device to build for.
```bash
# Find the UDID on the Mac when the iPhone is connected via USB:
xcrun xctrace list devices
# Look for the iPhone line, e.g.:
# Millian's iPhone (26.0) (00008110-XXXXXXXXXXXX)
# Or in Finder: select the iPhone in the sidebar → click the serial number
# line until it switches to UDID
```
PR Opened / Updated
Checkout + XcodeGen generate
Resolve SPM packages
Build + Archive IPA
Upload to Firebase App Distribution
Post "Ready to test" comment on PR
Wait for LGTM → Auto-merge
```
| Secret Name | Value Example |
|----------------|------------------------------------|
| `IPHONE_UDID` | `00008110-00123456789ABC01` |
## 5. Manual Test Run
### 2.2 `GITEA_TOKEN`
To test the workflow locally on the runner Mac:
A Gitea personal access token with **read/write** permissions on the repo.
Create it at: **Gitea → Settings → Applications → Generate Token**
Required permissions:
- `read:repository` — read PR info, comments
- `write:issue` — post comments on PRs
- `write:pull_request` — merge PRs
| Secret Name | Value Example |
|-----------------|----------------------------------------|
| `GITEA_TOKEN` | `e20fd3de2f79b324afde5049e7bafd60a...` |
---
## 3. Code Signing Setup
The workflow uses **automatic code signing** (`CODE_SIGN_STYLE=Automatic`
with `-allowProvisioningUpdates`). This requires:
1. **Apple ID signed into Xcode** on the Mac runner
2. **The iPhone UDID registered** in the Apple Developer account
3. **A valid provisioning profile** for `com.tabatago` (Debug)
To set up:
```bash
# On the Mac runner, open Xcode and sign in:
# Xcode → Settings → Accounts → + → Apple ID
# Verify the team is available:
# Simulate the build:
cd tabatago-swift
xcodegen generate
xcodebuild -showBuildSettings -scheme TabataGo -sdk iphoneos | grep DEVELOPMENT_TEAM
xcodebuild build \
-project TabataGo.xcodeproj \
-target TabataGo \
-sdk iphoneos \
-destination "generic/platform=iOS" \
-configuration Release \
-allowProvisioningUpdates \
-skipPackagePluginValidation \
CODE_SIGN_STYLE=Automatic
```
If automatic signing doesn't work (first-time setup), you may need to open
the project in Xcode once manually to let it create the provisioning profile.
## 6. Firebase App Distribution Setup
---
### 6.1 Create a Firebase Project
## 4. How the Workflow Works
1. Go to [Firebase Console](https://console.firebase.google.com)
2. Click **Add project** (or select an existing one)
3. Follow the wizard to create the project (Google Analytics is optional)
### Trigger
### 6.2 Enable App Distribution
The workflow runs on:
- `pull_request` events on `main`
- Types: `opened`, `synchronize` (new commits pushed), `reopened`
1. In the Firebase Console, open your project
2. Navigate to **App Distribution** in the left sidebar
3. Click **Get started** to enable the service
### Build Job (`build-deploy`)
### 6.3 Get the App ID
| Step | What it does |
|------|-------------|
| Checkout | Clones the PR branch |
| Setup Xcode | Selects Xcode.app, prints version |
| Install XcodeGen | `brew install xcodegen` if not present |
| Generate project | `xcodegen generate` in `tabatago-swift/` |
| Build for iPhone | `xcodebuild build` for device UDID from secret |
| Post comment | Posts a "Ready to test" message on the PR |
1. Go to **Project Settings** (gear icon) → **General**
2. Scroll down to **Your apps** → find the iOS app
3. Copy the **App ID** (looks like `1:123456789012:ios:abc123def456`)
The build runs with `set -o pipefail` so that any compilation error causes
the job to fail. The last 40 lines of build output are shown; the full log
is saved to `build/xcodebuild.log`.
### 6.4 Create a Service Account
### Approval Job (`wait-approval`)
1. Go to **Project Settings** → **Service accounts**
2. Click **Create service account** (or use Firebase Admin SDK → Generate new private key)
3. Give it a name like `github-actions-deploy`
4. Assign the role: **Firebase App Distribution Admin**
5. Click **Generate new private key** → download the JSON file
- Polls the Gitea API every **30 seconds** for new PR comments
- **Timeout**: 120 minutes (240 iterations × 30s)
- **LGTM detection**: Case-insensitive match for `lgtm` anywhere in the comment body
- **KO detection**: Exact case-insensitive match for a standalone `ko` comment
- On `LGTM` → merges the PR via Gitea API
- On `KO` → fails the workflow (exit code 1)
- On timeout → fails the workflow (exit code 1)
### 6.5 Encode the Service Account Key
### Comment Format
Reply on the PR with just:
- **`LGTM`** — approve and auto-merge
- **`KO`** — block the PR
---
## 5. Troubleshooting
### Runner doesn't pick up jobs
- Check the runner label: must be `macos` exactly
- Check **Settings → Actions → Runners** — runner must be **Idle**, not **Offline**
- Run `./act_runner daemon` in a terminal to see logs
### Build fails with signing error
```
error: No profiles for 'com.tabatago' were found
```bash
# Convert the JSON key to a base64 string (single line, no wrapping)
base64 -i firebase-key.json | tr -d '\n'
```
- Open Xcode on the Mac runner, sign into your Apple ID
- Build once in Xcode to generate the provisioning profile
- Verify the iPhone UDID is correct
Copy the output — this is your `FIREBASE_SERVICE_ACCOUNT` secret value.
### Build fails with "device not found"
### 6.6 Add Testers and Groups
```
error: Unable to find a device matching the provided destination specifier
1. In Firebase Console, go to **App Distribution** → **Testers & Groups**
2. Click **Add group** → name it `millian-test`
3. Click **Add testers** → enter Millian's email
4. Add the tester to the `millian-test` group
### 6.7 Install the Firebase App Tester on the iPhone
1. Send an invite to Millian's iPhone from the Firebase Console
2. Millian accepts the invite and installs the **Firebase App Tester** app from the App Store
3. Open the app and sign in with the invited Google account
4. The app will now receive builds pushed from the CI workflow
## Troubleshooting
| Issue | Solution |
|-------|----------|
| "Code signing failed" | Sign into Xcode on the runner Mac, verify team membership |
| "No provisioning profile" | Run `-allowProvisioningUpdates` or open project in Xcode once |
| `xcodegen: command not found` | `brew install xcodegen` (workflow handles this) |
| Runner doesn't pick up job | Verify runner label is `macos` and runner is connected |
| Token permission errors | Ensure `GITEA_TOKEN` has `write:issue` and `write:pull_request` scopes |
| Firebase upload fails | Verify `FIREBASE_APP_ID` and `FIREBASE_SERVICE_ACCOUNT` secrets are set correctly |
| "App not found" in Firebase | Check that the iOS app is registered in Firebase with the correct bundle ID |
| Testers don't receive build | Verify tester is in the `millian-test` group and has accepted the invite |
## Compilation Error Troubleshooting
### xcodegen Clearing Signing Team
After running `xcodegen generate`, the `.xcodeproj` may have a cleared signing team. Always re-check your team settings:
```bash
cd tabatago-swift
xcodegen generate
# Then check that project.yml has the correct team ID in settings
```
- Check that `IPHONE_UDID` secret matches the actual device UDID
- The device must be registered in your Apple Developer account
### Complication Targets Missing Team ID
### Comment not posted / LGTM not detected
Targets like **TabataGoWatchWidget** (watchOS complications) require an explicit team ID in `project.yml` settings. If you see signing errors for the widget target:
- Verify `GITEA_TOKEN` has the required permissions (read/write issues + PRs)
- Check the Gitea instance URL: `https://gitea.1000co.fr`
- Ensure the token hasn't expired
1. Open `tabatago-swift/project.yml`
2. Find the `TabataGoWatchWidget` target section
3. Ensure it has an explicit `settings:` block with the team ID:
```yaml
target: TabataGoWatchWidget
settings:
base:
DEVELOPMENT_TEAM: "2MJF39L8VY"
```
### Workflow runs on every PR push, canceling previous runs
### General Build Debugging
This is intentional — the `concurrency` setting cancels in-progress runs
for the same PR when new commits are pushed. This prevents queue buildup.
---
## 6. File Locations
```
tabatago/
├── .gitea/
│ └── workflows/
│ └── pr-iphone-deploy.yml ← iPhone build + LGTM workflow
├── .github/
│ └── workflows/
│ └── ci.yml ← Admin web + YouTube worker CI
└── docs/
└── ci-cd-setup.md ← This document
```
---
## 7. Required Tools on the Mac Runner
| Tool | Version | Install |
|-----------|----------|----------------------------------|
| macOS | 15+ | — |
| Xcode | 26.0+ | App Store or Apple Developer |
| XcodeGen | latest | `brew install xcodegen` |
| Python 3 | 3.x | Pre-installed on macOS |
| curl | any | Pre-installed on macOS |
| git | any | Pre-installed on macOS |
---
## See Also
- [Gitea Actions Documentation](https://docs.gitea.com/usage/actions/overview)
- [Gitea API — Merge a Pull Request](https://docs.gitea.com/api/1.22/#tag/pull-request/operation/repoMergePullRequest)
- [Xcode Build Settings Reference](https://developer.apple.com/documentation/xcode/build-settings-reference)
- Run `xcodegen generate` before every build to ensure the project is in sync
- Check `xcodebuild` output for specific file/line errors
- Verify SPM packages resolve: `xcodebuild -resolvePackageDependencies`

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@@ -1,96 +0,0 @@
{
"originHash" : "42d1f35b4500c2779457daf99841f2333a14d9a2965835305d89fd95beda836e",
"pins" : [
{
"identity" : "plcrashreporter",
"kind" : "remoteSourceControl",
"location" : "https://github.com/microsoft/plcrashreporter.git",
"state" : {
"revision" : "0254f941c646b1ed17b243654723d0f071e990d0",
"version" : "1.12.2"
}
},
{
"identity" : "posthog-ios",
"kind" : "remoteSourceControl",
"location" : "https://github.com/PostHog/posthog-ios",
"state" : {
"revision" : "c3efdae383a5e7a5a88c34fd774e9d7dc915b9d4",
"version" : "3.55.0"
}
},
{
"identity" : "purchases-ios",
"kind" : "remoteSourceControl",
"location" : "https://github.com/RevenueCat/purchases-ios",
"state" : {
"revision" : "bd63241b2258ea519020eb32a349db44fb44b119",
"version" : "5.68.0"
}
},
{
"identity" : "supabase-swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/supabase/supabase-swift",
"state" : {
"revision" : "17261e93c60aa721e3c17312bfeb2ae6de3d6f8a",
"version" : "2.43.1"
}
},
{
"identity" : "swift-asn1",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-asn1.git",
"state" : {
"revision" : "eb50cbd14606a9161cbc5d452f18797c90ef0bab",
"version" : "1.7.0"
}
},
{
"identity" : "swift-clocks",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-clocks",
"state" : {
"revision" : "cc46202b53476d64e824e0b6612da09d84ffde8e",
"version" : "1.0.6"
}
},
{
"identity" : "swift-concurrency-extras",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-concurrency-extras",
"state" : {
"revision" : "5a3825302b1a0d744183200915a47b508c828e6f",
"version" : "1.3.2"
}
},
{
"identity" : "swift-crypto",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-crypto.git",
"state" : {
"revision" : "476538ccb827f2dd18efc5de754cc87d77127a47",
"version" : "4.4.0"
}
},
{
"identity" : "swift-http-types",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-http-types.git",
"state" : {
"revision" : "45eb0224913ea070ec4fba17291b9e7ecf4749ca",
"version" : "1.5.1"
}
},
{
"identity" : "xctest-dynamic-overlay",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/xctest-dynamic-overlay",
"state" : {
"revision" : "dfd70507def84cb5fb821278448a262c6ff2bbad",
"version" : "1.9.0"
}
}
],
"version" : 3
}

View File

@@ -1,79 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2640"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "92991789C3A5B2A5FACF07A1"
BuildableName = "TabataGo.app"
BlueprintName = "TabataGo"
ReferencedContainer = "container:TabataGo.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES"
queueDebuggingEnableBacktraceRecording = "Yes">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "92991789C3A5B2A5FACF07A1"
BuildableName = "TabataGo.app"
BlueprintName = "TabataGo"
ReferencedContainer = "container:TabataGo.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "92991789C3A5B2A5FACF07A1"
BuildableName = "TabataGo.app"
BlueprintName = "TabataGo"
ReferencedContainer = "container:TabataGo.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -1,55 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2640"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D1E2CCCC7C9BD41029959883"
BuildableName = "TabataGoTests.xctest"
BlueprintName = "TabataGoTests"
ReferencedContainer = "container:TabataGo.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES"
queueDebuggingEnableBacktraceRecording = "Yes">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -1,62 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>TabataGo.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>1</integer>
</dict>
<key>TabataGoTests.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
<key>TabataGoUITests.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>2</integer>
</dict>
<key>TabataGoWatch.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
<key>TabataGoWatchWidget.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>1</integer>
</dict>
<key>TabataGoWidget.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>2</integer>
</dict>
</dict>
<key>SuppressBuildableAutocreation</key>
<dict>
<key>3945C3998B4B66F30759718C</key>
<dict>
<key>primary</key>
<true/>
</dict>
<key>90BAF2DB5D7456CD45975E26</key>
<dict>
<key>primary</key>
<true/>
</dict>
<key>92991789C3A5B2A5FACF07A1</key>
<dict>
<key>primary</key>
<true/>
</dict>
<key>D1E2CCCC7C9BD41029959883</key>
<dict>
<key>primary</key>
<true/>
</dict>
</dict>
</dict>
</plist>

View File

@@ -28,7 +28,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>1</string>
<string>2</string>
<key>NSHealthShareUsageDescription</key>
<string>TabataGo reads your health data to show fitness stats and personalize your workouts.</string>
<key>NSHealthUpdateUsageDescription</key>

View File

@@ -1,30 +1,38 @@
import Foundation
#if canImport(PostHog)
import PostHog
#endif
/// PostHog analytics mirrors the event taxonomy from the Expo app.
final class AnalyticsService: @unchecked Sendable {
static let shared = AnalyticsService()
#if canImport(PostHog)
private let apiKey: String =
Bundle.main.infoDictionary?["POSTHOG_API_KEY"] as? String ?? ""
private let host = "https://eu.posthog.com"
#endif
private init() {}
func initialize() {
#if canImport(PostHog)
guard !apiKey.isEmpty else { return }
let config = PostHogConfig(apiKey: apiKey, host: host)
config.captureApplicationLifecycleEvents = true
config.captureScreenViews = false // manual tracking
PostHogSDK.shared.setup(config)
#endif
}
// Screens
func screen(_ name: String, properties: [String: Any] = [:]) {
#if canImport(PostHog)
PostHogSDK.shared.screen(name, properties: properties)
#endif
}
// Onboarding
@@ -100,6 +108,8 @@ final class AnalyticsService: @unchecked Sendable {
// Private
private func capture(_ event: String, properties: [String: Any] = [:]) {
#if canImport(PostHog)
PostHogSDK.shared.capture(event, properties: properties.isEmpty ? nil : properties)
#endif
}
}

View File

@@ -13,7 +13,7 @@ struct TabataEntry: TimelineEntry {
struct TabataProvider: TimelineProvider {
private let sharedDefaults = UserDefaults(suiteName: "group.com.tabatago.app")
private let sharedDefaults = UserDefaults(suiteName: "group.fr.millianlmx.tabatago")
func placeholder(in context: Context) -> TabataEntry {
TabataEntry(date: Date(), streak: 7, lastWorkoutLabel: "Today")
@@ -98,6 +98,8 @@ struct RectangularComplicationView: View {
}
/// `.accessoryCorner` tiny bolt + streak digit in the corner
#if canImport(WatchKit)
@available(watchOS 10.0, *)
struct CornerComplicationView: View {
let entry: TabataEntry
@@ -111,6 +113,7 @@ struct CornerComplicationView: View {
.widgetLabel(String(format: String(localized: LocalizedStringResource("watch.complication.dayStreakFmt")), entry.streak))
}
}
#endif
// Widget definition
@@ -124,11 +127,11 @@ struct TabataGoComplication: Widget {
}
.configurationDisplayName("TabataGo")
.description(String(localized: LocalizedStringResource("watch.complication.description")))
.supportedFamilies([
.accessoryCircular,
.accessoryRectangular,
.accessoryCorner
])
#if canImport(WatchKit)
.supportedFamilies([.accessoryCircular, .accessoryRectangular, .accessoryCorner])
#else
.supportedFamilies([.accessoryCircular, .accessoryRectangular])
#endif
}
}
@@ -142,8 +145,10 @@ struct TabataComplicationEntryView: View {
CircularComplicationView(entry: entry)
case .accessoryRectangular:
RectangularComplicationView(entry: entry)
#if canImport(WatchKit)
case .accessoryCorner:
CornerComplicationView(entry: entry)
#endif
default:
CircularComplicationView(entry: entry)
}
@@ -163,3 +168,12 @@ struct TabataComplicationEntryView: View {
} timeline: {
TabataEntry(date: .now, streak: 7, lastWorkoutLabel: "Today")
}
#if canImport(WatchKit)
@available(watchOS 10.0, *)
#Preview("Corner", as: .accessoryCorner) {
TabataGoComplication()
} timeline: {
TabataEntry(date: .now, streak: 7, lastWorkoutLabel: "Today")
}
#endif

View File

@@ -27,6 +27,6 @@
<key>WKApplication</key>
<true/>
<key>WKCompanionAppBundleIdentifier</key>
<string>com.tabatago.app</string>
<string>fr.millianlmx.tabatago</string>
</dict>
</plist>

View File

@@ -6,7 +6,7 @@
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.tabatago.app</string>
<string>group.fr.millianlmx.tabatago</string>
</array>
</dict>
</plist>

View File

@@ -13,7 +13,7 @@ struct WatchActivityView: View {
@State private var isLoading = true
private let healthStore = HKHealthStore()
private let sharedDefaults = UserDefaults(suiteName: "group.com.tabatago.app")
private let sharedDefaults = UserDefaults(suiteName: "group.fr.millianlmx.tabatago")
var body: some View {
ScrollView {

View File

@@ -1,7 +1,7 @@
name: TabataGo
options:
bundleIdPrefix: com.tabatago
bundleIdPrefix: fr.millianlmx.tabatago
deploymentTarget:
iOS: "26.0"
watchOS: "11.0"
@@ -15,7 +15,11 @@ settings:
SWIFT_VERSION: "6.0"
IPHONEOS_DEPLOYMENT_TARGET: "26.0"
SWIFT_STRICT_CONCURRENCY: complete
SWIFT_EMIT_MODULE_FOR_EXPLICIT_BUILD: NO
GCC_TREAT_WARNINGS_AS_ERRORS: NO
SWIFT_TREAT_WARNINGS_AS_ERRORS: NO
DEBUG_INFORMATION_FORMAT: dwarf-with-dsym
DEVELOPMENT_TEAM: "2MJF39L8VY"
packages:
Supabase:
@@ -24,9 +28,6 @@ packages:
RevenueCat:
url: https://github.com/RevenueCat/purchases-ios
from: "5.0.0"
PostHog:
url: https://github.com/PostHog/posthog-ios
from: "3.0.0"
targets:
TabataGo:
@@ -66,19 +67,18 @@ targets:
com.apple.developer.healthkit.access:
- health-records
com.apple.security.application-groups:
- group.com.tabatago.app
- group.fr.millianlmx.tabatago
dependencies:
- package: Supabase
product: Supabase
- package: RevenueCat
product: RevenueCat
- package: PostHog
product: PostHog
- target: TabataGoWatch
embed: true
settings:
base:
PRODUCT_BUNDLE_IDENTIFIER: com.tabatago.app
PRODUCT_BUNDLE_IDENTIFIER: fr.millianlmx.tabatago
INFOPLIST_FILE: TabataGo/Resources/Info.plist
CODE_SIGN_ENTITLEMENTS: TabataGo/Resources/TabataGo.entitlements
ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon
@@ -112,7 +112,7 @@ targets:
CFBundleShortVersionString: "1.0"
CFBundleVersion: "2"
WKApplication: true
WKCompanionAppBundleIdentifier: com.tabatago.app
WKCompanionAppBundleIdentifier: fr.millianlmx.tabatago
NSHealthShareUsageDescription: "TabataGo reads your heart rate and calories during workouts."
NSHealthUpdateUsageDescription: "TabataGo saves workout data to Apple Health directly from your Watch."
entitlements:
@@ -120,13 +120,13 @@ targets:
properties:
com.apple.developer.healthkit: true
com.apple.security.application-groups:
- group.com.tabatago.app
- group.fr.millianlmx.tabatago
dependencies:
- target: TabataGoWatchWidget
embed: true
settings:
base:
PRODUCT_BUNDLE_IDENTIFIER: com.tabatago.app.watchkitapp
PRODUCT_BUNDLE_IDENTIFIER: fr.millianlmx.tabatago.watchkitapp
INFOPLIST_FILE: TabataGoWatch/Resources/Info.plist
CODE_SIGN_ENTITLEMENTS: TabataGoWatch/Resources/TabataGoWatch.entitlements
ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon
@@ -152,7 +152,7 @@ targets:
NSExtensionPointIdentifier: com.apple.widgetkit-extension
settings:
base:
PRODUCT_BUNDLE_IDENTIFIER: com.tabatago.app.watchkitapp.widget
PRODUCT_BUNDLE_IDENTIFIER: fr.millianlmx.tabatago.watchkitapp.widget
TARGETED_DEVICE_FAMILY: "4"
WATCHOS_DEPLOYMENT_TARGET: "11.0"
@@ -166,7 +166,7 @@ targets:
- target: TabataGo
settings:
base:
PRODUCT_BUNDLE_IDENTIFIER: com.tabatago.app.tests
PRODUCT_BUNDLE_IDENTIFIER: fr.millianlmx.tabatago.tests
TabataGoUITests:
type: bundle.ui-testing
@@ -178,4 +178,14 @@ targets:
- target: TabataGo
settings:
base:
PRODUCT_BUNDLE_IDENTIFIER: com.tabatago.app.uitests
PRODUCT_BUNDLE_IDENTIFIER: fr.millianlmx.tabatago.uitests
schemes:
TabataGo:
build:
targets:
TabataGo: all
run:
config: Debug
archive:
config: Release