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:
238
.gitea/workflows/pr-iphone-deploy.yml
Normal file
238
.gitea/workflows/pr-iphone-deploy.yml
Normal 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
|
||||
Reference in New Issue
Block a user