ci: replace dead Expo CI with linux-only monorepo pipeline
Some checks failed
CI / Admin Web CI (pull_request) Failing after 34s
CI / YouTube Worker (pull_request) Failing after 5s
CI / Deploy (pull_request) Has been skipped
CI / Detect Changes (pull_request) Successful in 6s

Remove: root Expo typecheck/lint/test/build-check (project deleted Apr 19)
Remove: swift-build-test + app-store.yml (no macOS runner on Gitea)
Add: path-filtered conditional jobs with dorny/paths-filter@v3
Add: youtube-worker deploy to deploy-functions job
Fix: deploy-functions always() to handle skipped upstream jobs
Fix: SPM cache key includes Package.resolved (before removal)
Fix: remove continue-on-error from vitest/playwright steps
This commit is contained in:
Millian Lamiaux
2026-05-23 12:09:28 +02:00
parent df9fd48964
commit 38576fd528
2 changed files with 83 additions and 231 deletions

View File

@@ -1,82 +0,0 @@
name: App Store Submission
on:
push:
tags:
- 'v*'
workflow_dispatch:
jobs:
upload-to-app-store:
name: Archive & Upload to App Store
runs-on: macos-15
timeout-minutes: 60
defaults:
run:
working-directory: tabatago-swift
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Select Xcode
run: sudo xcode-select -switch /Applications/Xcode.app
- name: Cache SPM dependencies
uses: actions/cache@v4
with:
path: ~/Library/Developer/Xcode/DerivedData/**/SourcePackages
key: spm-macos-${{ hashFiles('tabatago-swift/project.yml') }}
restore-keys: |
spm-macos-
- name: Write App Store Connect API key
env:
API_KEY_P8: ${{ secrets.APP_STORE_CONNECT_API_KEY_P8 }}
run: |
printf '%s' "$API_KEY_P8" > "$RUNNER_TEMP/AuthKey.p8"
- name: Archive
run: |
xcodebuild archive \
-project TabataGo.xcodeproj \
-scheme TabataGo \
-configuration Release \
-archivePath ./build/TabataGo.xcarchive \
-allowProvisioningUpdates \
-authenticationKeyPath "$RUNNER_TEMP/AuthKey.p8" \
-authenticationKeyID "${{ secrets.APP_STORE_CONNECT_KEY_ID }}" \
-authenticationKeyIssuerID "${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}" \
|| { echo "❌ Archive failed — check signing and API key permissions"; exit 1; }
- name: Verify build settings
run: |
echo "Checking version and build number..."
xcodebuild -showBuildSettings \
-project TabataGo.xcodeproj \
-scheme TabataGo \
-configuration Release \
| grep -E "MARKETING_VERSION|CURRENT_PROJECT_VERSION"
- name: Export IPA
run: |
xcodebuild -exportArchive \
-archivePath ./build/TabataGo.xcarchive \
-exportPath ./build/export \
-exportOptionsPlist ExportOptions.plist \
-allowProvisioningUpdates \
-authenticationKeyPath "$RUNNER_TEMP/AuthKey.p8" \
-authenticationKeyID "${{ secrets.APP_STORE_CONNECT_KEY_ID }}" \
-authenticationKeyIssuerID "${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}" \
|| { echo "❌ Export failed — check ExportOptions.plist and provisioning"; exit 1; }
# NOTE: The first upload automatically creates the app record in
# App Store Connect if one does not already exist.
- name: Upload to App Store
run: |
xcrun altool --upload-app \
--type ios \
--file ./build/export/TabataGo.ipa \
--apiKey "${{ secrets.APP_STORE_CONNECT_KEY_ID }}" \
--apiIssuer "${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}" \
|| { echo "❌ Upload failed — check API key permissions and app record"; exit 1; }

View File

@@ -2,150 +2,42 @@ name: CI
on:
push:
branches: [main, master]
branches: [main]
pull_request:
branches: [main, master]
branches: [main]
jobs:
typecheck:
name: TypeScript
# ── Path filter — determines which downstream jobs run ──
changes:
name: Detect Changes
runs-on: ubuntu-latest
outputs:
admin-web: ${{ steps.filter.outputs.admin-web }}
youtube-worker: ${{ steps.filter.outputs.youtube-worker }}
supabase-functions: ${{ steps.filter.outputs.supabase-functions }}
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
- uses: dorny/paths-filter@v3
id: filter
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Type check
run: npx tsc --noEmit
lint:
name: ESLint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
test:
name: Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run unit tests with coverage
run: npm run test:coverage
- name: Run component render tests
run: npm run test:render
- name: Upload coverage report
uses: actions/upload-artifact@v4
if: always()
with:
name: coverage-report
path: coverage/
retention-days: 7
- name: Coverage summary
if: always()
run: |
echo "## Test Coverage Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ -f coverage/coverage-summary.json ]; then
echo '```' >> $GITHUB_STEP_SUMMARY
node -e "
const c = require('./coverage/coverage-summary.json').total;
const fmt = (v) => v.pct + '%';
console.log('Statements: ' + fmt(c.statements));
console.log('Branches: ' + fmt(c.branches));
console.log('Functions: ' + fmt(c.functions));
console.log('Lines: ' + fmt(c.lines));
" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
elif [ -f coverage/coverage-final.json ]; then
echo "Coverage report generated. Download the artifact for details." >> $GITHUB_STEP_SUMMARY
else
echo "Coverage report not found." >> $GITHUB_STEP_SUMMARY
fi
- name: Comment coverage on PR
if: github.event_name == 'pull_request' && always()
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
let body = '## Test Coverage Report\n\n';
try {
const summary = JSON.parse(fs.readFileSync('coverage/coverage-summary.json', 'utf8'));
const total = summary.total;
const fmt = (v) => `${v.pct}%`;
const icon = (v) => v.pct >= 80 ? '✅' : v.pct >= 60 ? '⚠️' : '❌';
body += '| Metric | Coverage | Status |\n';
body += '|--------|----------|--------|\n';
body += `| Statements | ${fmt(total.statements)} | ${icon(total.statements)} |\n`;
body += `| Branches | ${fmt(total.branches)} | ${icon(total.branches)} |\n`;
body += `| Functions | ${fmt(total.functions)} | ${icon(total.functions)} |\n`;
body += `| Lines | ${fmt(total.lines)} | ${icon(total.lines)} |\n`;
} catch (e) {
body += '_Coverage summary not available._\n';
}
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const existing = comments.find(c =>
c.user.type === 'Bot' && c.body.includes('## Test Coverage Report')
);
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body,
});
}
filters: |
admin-web:
- 'admin-web/**'
- '.github/workflows/ci.yml'
youtube-worker:
- 'youtube-worker/**'
- '.github/workflows/ci.yml'
supabase-functions:
- 'supabase/functions/**'
- 'youtube-worker/**'
- '.github/workflows/ci.yml'
# ── Admin Web: Next.js ──
admin-web-test:
name: Admin Web Tests
name: Admin Web CI
needs: changes
if: needs.changes.outputs.admin-web == 'true'
runs-on: ubuntu-latest
defaults:
run:
@@ -168,19 +60,22 @@ jobs:
- name: Run unit tests
run: npx vitest run
continue-on-error: true
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium
- name: Run E2E tests
run: npx playwright test
continue-on-error: true
build-check:
name: Build Check
# ── YouTube Worker: Node.js microservice ──
youtube-worker-check:
name: YouTube Worker
needs: changes
if: needs.changes.outputs.youtube-worker == 'true'
runs-on: ubuntu-latest
needs: [typecheck, lint, test]
defaults:
run:
working-directory: youtube-worker
steps:
- uses: actions/checkout@v4
@@ -189,39 +84,78 @@ jobs:
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: youtube-worker/package-lock.json
- name: Install dependencies
run: npm ci
- name: Export web build
run: npx expo export --platform web
continue-on-error: true
- name: Validate syntax
run: node --check server.js
# ── Deploy: Supabase edge functions + YouTube worker ──
deploy-functions:
name: Deploy Edge Functions
name: Deploy
needs: [changes, admin-web-test, youtube-worker-check]
if: |
always() &&
github.ref == 'refs/heads/main' &&
github.event_name == 'push' &&
!contains(needs.*.result, 'failure') &&
(needs.changes.outputs.supabase-functions == 'true' ||
needs.changes.outputs.youtube-worker == 'true')
runs-on: ubuntu-latest
needs: [typecheck, lint, test]
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
steps:
- uses: actions/checkout@v4
- name: Deploy to self-hosted Supabase
- name: Setup SSH
env:
DEPLOY_HOST: ${{ secrets.SUPABASE_DEPLOY_HOST }}
DEPLOY_USER: ${{ secrets.SUPABASE_DEPLOY_USER }}
DEPLOY_PATH: ${{ secrets.SUPABASE_DEPLOY_PATH }}
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SUPABASE_SSH_KEY }}" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan -H $DEPLOY_HOST >> ~/.ssh/known_hosts 2>/dev/null
- name: Deploy Supabase Edge Functions
env:
DEPLOY_HOST: ${{ secrets.SUPABASE_DEPLOY_HOST }}
DEPLOY_USER: ${{ secrets.SUPABASE_DEPLOY_USER }}
DEPLOY_PATH: ${{ secrets.SUPABASE_DEPLOY_PATH }}
run: |
rsync -avz --delete \
--exclude='node_modules' \
--exclude='.DS_Store' \
-e "ssh -i ~/.ssh/deploy_key" \
supabase/functions/ \
"$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/"
ssh -i ~/.ssh/deploy_key "$DEPLOY_USER@$DEPLOY_HOST" \
"docker restart supabase-edge-functions"
- name: Deploy YouTube Worker
env:
DEPLOY_HOST: ${{ secrets.SUPABASE_DEPLOY_HOST }}
DEPLOY_USER: ${{ secrets.SUPABASE_DEPLOY_USER }}
WORKER_PATH: /opt/supabase/youtube-worker
run: |
rsync -avz --delete \
--exclude='node_modules' \
--exclude='.DS_Store' \
-e "ssh -i ~/.ssh/deploy_key" \
youtube-worker/ \
"$DEPLOY_USER@$DEPLOY_HOST:$WORKER_PATH/"
ssh -i ~/.ssh/deploy_key "$DEPLOY_USER@$DEPLOY_HOST" "\
cd $WORKER_PATH && \
docker build -t youtube-worker:latest . && \
docker stop youtube-worker 2>/dev/null || true && \
docker rm youtube-worker 2>/dev/null || true && \
docker run -d \
--name youtube-worker \
--restart unless-stopped \
--network supabase_supabase-network \
-e SUPABASE_URL=\$(docker exec supabase-edge-functions printenv SUPABASE_URL) \
-e SUPABASE_SERVICE_ROLE_KEY=\$(docker exec supabase-edge-functions printenv SUPABASE_SERVICE_ROLE_KEY) \
-e SUPABASE_PUBLIC_URL=https://supabase.1000co.fr \
-e GEMINI_API_KEY=\$(cat /opt/supabase/.env.gemini 2>/dev/null || echo '') \
-e STORAGE_BUCKET=workout-audio \
-e PORT=3001 \
youtube-worker:latest"