12 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
27 changed files with 67 additions and 304 deletions

View File

View File

View File

@@ -1,8 +1,8 @@
# =============================================================================
# TabataGo — PR → Build → Archive → Firebase OTA → Wait LGTM → Merge
# TabataGo — PR → Build → USB Direct Deploy → Wait LGTM → Merge
# =============================================================================
name: PR → Build → Firebase OTA → LGTM
name: PR → Build → USB Deploy → LGTM
on:
pull_request:
@@ -16,14 +16,14 @@ permissions:
jobs:
build-deploy:
name: Build & Deploy to Firebase OTA
name: Build & Deploy to iPhone via USB
runs-on: macos
timeout-minutes: 20
steps:
- name: Checkout
run: |
git clone --depth 1 https://x-access-token:${PR_TOKEN}@gitea.1000co.fr/${REPO}.git .
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 }}
@@ -35,18 +35,41 @@ jobs:
- name: Setup PATH
run: echo "/opt/homebrew/bin" >> $GITHUB_PATH
- name: Clean act runner caches
- name: Clean build artifacts
run: |
rm -rf ~/.cache/act/*/hostexecutor/tabatago-swift/build
rm -rf .build
rm -rf ~/Library/Caches/org.swift.swiftpm
rm -rf ~/Library/Developer/Xcode/DerivedData/TabataGo-*
rm -rf tabatago-swift/TabataGo.xcodeproj
echo "🧹 Cleaning build artifacts..."
- name: Install tools (xcodegen, node, firebase)
# 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 || true
npm install -g firebase-tools
brew install xcodegen node ios-deploy || true
- name: Generate Xcode project
run: |
@@ -56,12 +79,13 @@ jobs:
- name: Resolve SPM packages
run: |
cd tabatago-swift
xcodebuild -resolvePackageDependencies -project TabataGo.xcodeproj -scmProvider system
xcodebuild -resolvePackageDependencies \
-project TabataGo.xcodeproj \
-scmProvider system
- name: Build app
run: |
cd tabatago-swift
xcodebuild -list
xcodebuild build \
-project TabataGo.xcodeproj \
-scheme TabataGo \
@@ -72,28 +96,32 @@ jobs:
-clonedSourcePackagesDirPath ../build/spm-cache \
SWIFT_ENABLE_EXPLICIT_MODULES=NO \
ONLY_ACTIVE_ARCH=NO \
CODE_SIGN_STYLE=Automatic DEVELOPMENT_TEAM=2MJF39L8VY
CODE_SIGN_STYLE=Automatic \
DEVELOPMENT_TEAM=2MJF39L8VY
- name: Package IPA
- name: Install to iPhone (WiFi → USB fallback)
run: |
cd tabatago-swift
mkdir -p Payload build
UDID="00008120-000925CE3672201E"
APP=$(find ../build/derived -name 'TabataGo.app' -type d | head -1)
cp -R "$APP" Payload/
zip -r build/TabataGo.ipa Payload/
rm -rf Payload
- name: Deploy to Firebase
env:
FIREBASE_APP_ID: ${{ secrets.FIREBASE_APP_ID }}
FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}
run: |
npx firebase appdistribution:distribute tabatago-swift/build/TabataGo.ipa \
--app "${FIREBASE_APP_ID}" \
--groups "millian-test" \
--project tabatago-19a80 \
--token "${FIREBASE_TOKEN}" \
--release-notes "PR: ${{ github.event.pull_request.title }}"
# 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
# 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
env:
@@ -105,7 +133,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 via Firebase.\\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 (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"
wait-approval:

0
.gt
View File

0
.gtb64
View File

View File

@@ -1 +0,0 @@
e20fd3de2f79b324afde5049e7bafd60a50fe9e9

View File

View File

View File

View File

View File

View File

@@ -1,63 +0,0 @@
#!/usr/bin/env python3
"""Fetch CI status for tabatago - check latest runs and get logs for failures."""
import urllib.request, json, base64, sys, os
repo = "millianlmx/tabatago"
base_url = f"https://gitea.1000co.fr/api/v1/repos/{repo}/actions"
# Load token from base64 file
script_dir = os.path.dirname(os.path.abspath(__file__))
with open(os.path.join(script_dir, ".gtb64")) as f:
token = base64.b64decode(f.read().strip()).decode().strip()
def api(path, expect_json=True):
req = urllib.request.Request(f"{base_url}{path}")
req.add_header("Authorization", f"token {token}")
with urllib.request.urlopen(req) as resp:
body = resp.read()
if expect_json:
return json.loads(body)
return body.decode('utf-8', 'replace')
# Check mode
mode = sys.argv[1] if len(sys.argv) > 1 else "status"
if mode == "status":
data = api("/runs?limit=5")
runs = data.get('workflow_runs', [])
for r in runs:
print(f"Run #{r['run_number']} (ID={r['id']}) status={r['status']} conclusion={r.get('conclusion','?')}")
elif mode == "check":
# Check latest run and return status
data = api("/runs?limit=1")
runs = data.get('workflow_runs', [])
if runs:
r = runs[0]
print(f"run_number={r['run_number']}|id={r['id']}|status={r['status']}|conclusion={r.get('conclusion','?')}")
elif mode == "jobs":
run_id = sys.argv[2]
try:
jobs_data = api(f"/runs/{run_id}/jobs")
jobs = jobs_data if isinstance(jobs_data, list) else jobs_data.get('jobs', [])
for j in jobs:
print(f"job_id={j['id']}|name={j.get('name','?')}|status={j.get('status')}|conclusion={j.get('conclusion','?')}")
except Exception as e:
print(f"ERROR: {e}", file=sys.stderr)
elif mode == "logs":
job_id = sys.argv[2]
try:
logs = api(f"/jobs/{job_id}/logs", expect_json=False)
# Get last 150 lines
lines = logs.split('\n')
print(f"Total lines: {len(lines)}")
# Show error-context lines
for i, line in enumerate(lines[-200:]):
print(line)
except Exception as e:
print(f"ERROR: {e}", file=sys.stderr)
else:
print(f"Unknown mode: {mode}")

View File

@@ -1,78 +0,0 @@
#!/usr/bin/env python3
"""Check TabataGo CI status via Gitea API."""
import urllib.request, json, base64, sys, os
REPO = "millianlmx/tabatago"
BASE = "https://gitea.1000co.fr/api/v1"
TOKEN_FILE = "/opt/data/projects/tabatago/.gtb64"
def get_token():
with open(TOKEN_FILE) as f:
return base64.b64decode(f.read().strip()).decode()
def api(path, expect_json=True):
url = f"{BASE}{path}"
req = urllib.request.Request(url, headers={"Authorization": f"token {get_token()}"})
resp = urllib.request.urlopen(req)
if expect_json:
return json.loads(resp.read())
else:
return resp.read().decode('utf-8', 'replace')
# 1. Get latest runs
runs_data = api(f"/repos/{REPO}/actions/runs?limit=3")
runs = runs_data['workflow_runs']
for run in runs:
rid = run['id']
rn = run['run_number']
status = run['status']
conclusion = run.get('conclusion', 'N/A')
commit_msg = run.get('title', '?')
print(f"Run #{rn} (id={rid}) status={status} conclusion={conclusion}")
print(f" Commit: {commit_msg}")
# Get jobs for this run
jobs_data = api(f"/repos/{REPO}/actions/runs/{rid}/jobs")
jobs = jobs_data if isinstance(jobs_data, list) else jobs_data.get('jobs', [])
for j in jobs:
jid = j['id']
jname = j['name']
jstatus = j.get('status', '?')
jconclusion = j.get('conclusion', 'N/A')
print(f" Job: {jname} status={jstatus} conclusion={jconclusion}")
if jconclusion == 'failure':
# Fetch last portion of logs
try:
log_text = api(f"/repos/{REPO}/actions/jobs/{jid}/logs", expect_json=False)
lines = log_text.split('\n')
print(f"\n --- LOG TAIL ({len(lines)} total lines) ---")
# Find errors
error_lines = [l for l in lines if '##[error]' in l or 'error:' in l.lower() or 'Error' in l or 'fatal' in l.lower()]
for el in error_lines[-20:]:
print(f" {el.strip()[:300]}")
# Show last 30 lines for context
print(f"\n --- LAST 30 LINES ---")
for l in lines[-30:]:
print(f" {l.strip()[:300]}")
except Exception as e:
print(f" Failed to fetch logs for job {jid}: {e}")
print()
# Summary
latest = runs[0]
print(f"\nLATEST RUN STATUS: #{latest['run_number']} conclusion={latest.get('conclusion')}")
if latest.get('conclusion') == 'success':
print("CI IS GREEN!")
sys.exit(0)
elif latest.get('conclusion') == 'failure':
print("CI IS RED - needs fix")
sys.exit(1)
elif latest.get('status') == 'in_progress':
print("CI IS RUNNING - check back later")
sys.exit(2)
else:
print(f"CI STATUS: {latest.get('conclusion')}")
sys.exit(3)

View File

View File

View File

@@ -1,24 +0,0 @@
import subprocess, json, base64
# Read token from base64-encoded file
with open('.gtb64') as f:
b64 = f.read().strip()
token = base64.b64decode(b64).decode('utf-8')
print(f"Token length: {len(token)}")
# Fetch job 473 logs
import urllib.request
req = urllib.request.Request(
"https://gitea.1000co.fr/api/v1/repos/millianlmx/tabatago/actions/jobs/473/logs",
headers={"Authorization": f"token {token}"}
)
try:
with urllib.request.urlopen(req) as resp:
logs = resp.read().decode('utf-8')
with open('.logs.txt', 'w') as f:
f.write(logs)
print(f"Logs written: {len(logs)} chars")
except urllib.error.HTTPError as e:
print(f"HTTP Error: {e.code} {e.reason}")
body = e.read().decode('utf-8')
print(f"Body: {body[:500]}")

View File

View File

View File

View File

@@ -1,97 +0,0 @@
#!/usr/bin/env python3
"""Gitea CI monitoring script — checks latest run status and fetches failure logs.
Usage:
python3 ci-status.py <repo_path> [--all-runs] [--run N] [--logs]
Requires: token file at <repo_path>/.gtb64 (base64-encoded, created via write_file tool
to bypass Hermes secret redaction) or <repo_path>/.gtoken (plain text).
Prefer .gtb64 — Hermes redacts plain-text token patterns in write_file.
"""
import urllib.request, json, sys, os, base64
BASE = "https://gitea.1000co.fr/api/v1/repos/millianlmx/tabatago/actions"
def load_token(repo_path):
# Try base64-encoded token first (Hermes-safe), fall back to plain text
b64_file = os.path.join(repo_path, ".gtb64")
plain_file = os.path.join(repo_path, ".gtoken")
if os.path.exists(b64_file):
with open(b64_file) as f:
return base64.b64decode(f.read().strip()).decode().strip()
elif os.path.exists(plain_file):
with open(plain_file) as f:
return f.read().strip().strip()
raise FileNotFoundError(f"No token file found: {b64_file} or {plain_file}")
def api(token, path, expect_json=True):
req = urllib.request.Request(f"{BASE}{path}")
req.add_header("Authorization", f"token {token}")
with urllib.request.urlopen(req) as resp:
body = resp.read()
if expect_json:
return json.loads(body)
return body.decode('utf-8', 'replace')
def main():
repo_path = sys.argv[1] if len(sys.argv) > 1 else "."
show_all = "--all-runs" in sys.argv
target_run = None
show_logs = "--logs" in sys.argv or "-l" in sys.argv
for arg in sys.argv:
if arg.startswith("--run="):
target_run = int(arg.split("=")[1])
token = load_token(repo_path)
limit = 10 if show_all else (1 if target_run is None else 5)
data = api(token, f"/runs?limit={limit}")
runs = data.get('workflow_runs', [])
if target_run is not None:
runs = [r for r in runs if r['run_number'] == target_run]
for run in runs:
rid = run['id']
rn = run['run_number']
print(f"Run #{rn} (ID={rid}): {run['status']}/{run.get('conclusion','?')} "
f"{run.get('title','')[:80]}")
if not show_all and not show_logs:
# Quick mode: just status and conclusion
continue
# Get jobs
try:
jobs_data = api(token, f"/runs/{rid}/jobs")
jobs = jobs_data if isinstance(jobs_data, list) else jobs_data.get('jobs', [])
for j in jobs:
jid = j['id']
name = j.get('name', '?')
conclusion = j.get('conclusion', '?')
print(f" Job: {name}{conclusion}")
if show_logs and conclusion in ('failure',):
try:
logs = api(token, f"/jobs/{jid}/logs", expect_json=False)
lines = logs.split('\n')
# Show tail: focused error extraction
errors = [l.strip() for l in lines[-120:]
if any(k in l.lower() for k in
('##[error]', 'error:', 'auth',
'failed', 'BUILD SUCCEEDED',
'command not found', 'no such module'))]
if errors:
for e in errors[-15:]:
if len(e) < 500: # Skip giant compilation lines
print(f" ⚠️ {e[:200]}")
except Exception as e:
print(f" ⚠️ Log fetch error: {e}")
except Exception as e:
print(f" Job fetch error: {e}")
print()
if __name__ == "__main__":
main()

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

@@ -127,13 +127,11 @@ struct TabataGoComplication: Widget {
}
.configurationDisplayName("TabataGo")
.description(String(localized: LocalizedStringResource("watch.complication.description")))
.supportedFamilies([
.accessoryCircular,
.accessoryRectangular,
#if canImport(WatchKit)
.accessoryCorner
.supportedFamilies([.accessoryCircular, .accessoryRectangular, .accessoryCorner])
#else
.supportedFamilies([.accessoryCircular, .accessoryRectangular])
#endif
])
}
}

View File

@@ -1,7 +1,7 @@
name: TabataGo
options:
bundleIdPrefix: com.tabatago
bundleIdPrefix: fr.millianlmx.tabatago
deploymentTarget:
iOS: "26.0"
watchOS: "11.0"