ci: add PR → iPhone → LGTM workflow

- Triggers on PR to main (opened, synchronize, reopened)
- Build-deploy job: xcodegen + xcodebuild for iOS device
- Posts PR comment when build succeeds
- wait-approval job: polls for LGTM/KO comments via Gitea API
- Auto-merges on LGTM, blocks on KO, timeout after 2h
This commit is contained in:
2026-06-26 20:41:14 +00:00
parent 1ca8c3a467
commit 87e0610a62

View File

@@ -0,0 +1,238 @@
# =============================================================================
# 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
# =============================================================================
name: PR → iPhone → 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
runs-on: macos
timeout-minutes: 25
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Xcode
run: |
sudo xcode-select -s /Applications/Xcode.app
echo "Using Xcode $(xcodebuild -version | head -1)"
- name: Install XcodeGen (if needed)
run: |
if ! command -v xcodegen &> /dev/null; then
brew install xcodegen
fi
- name: Generate Xcode project
run: |
cd tabatago-swift
xcodegen generate
- name: Build & Install on iPhone
env:
IPHONE_UDID: ${{ secrets.IPHONE_UDID }}
run: |
cd tabatago-swift
# Build for device (build only — installation requires USB/OTA tooling)
# Use tee to capture full output while still streaming to stdout
xcodebuild build \
-scheme TabataGo \
-sdk iphoneos \
-destination "platform=iOS,id=${IPHONE_UDID}" \
-configuration Debug \
-allowProvisioningUpdates \
-derivedDataPath ../build \
-scmProvider system \
CODE_SIGN_STYLE=Automatic \
2>&1 | tee ../build/xcodebuild.log | tail -40
BUILD_EXIT=${PIPESTATUS[0]}
if [ $BUILD_EXIT -ne 0 ]; then
echo ""
echo "❌ Build FAILED (exit code: $BUILD_EXIT)"
echo "Full log: build/xcodebuild.log"
exit $BUILD_EXIT
fi
echo ""
echo "✅ App built successfully for iPhone (UDID: ${IPHONE_UDID})"
- name: Post "Ready to test" comment
if: success()
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
GITEA_HOST: 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" \
-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")
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
steps:
- name: Poll for LGTM
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
GITEA_HOST: https://gitea.1000co.fr
run: |
PR_NUMBER="${{ github.event.pull_request.number }}"
REPO="${{ github.repository }}"
API="${GITEA_HOST}/api/v1/repos/${REPO}"
MAX_TRIES=240 # 2h = 240 × 30s
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 ""
# 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)
if [ "$LAST_ID" = "0" ] || [ -z "$LAST_ID" ]; then
echo " Aucun commentaire existant. En attente du premier..."
fi
while [ $TRIES -lt $MAX_TRIES ]; 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)
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}" \
-H "Content-Type: application/json" \
-d "{\"Do\":\"merge\",\"MergeTitle\":\"${{ github.event.pull_request.title }}\"}" \
"${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
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."
exit 1
fi
# Heartbeat
if [ $((TRIES % 4)) -eq 0 ]; then
echo " ⏳ Toujours en attente... (${TRIES}/240, $(date +%H:%M:%S))"
fi
done
echo ""
echo "❌ Timeout — pas de LGTM reçu après 2 heures."
exit 1