Compare commits
1 Commits
df9fd48964
...
revamp-tim
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38576fd528 |
82
.github/workflows/app-store.yml
vendored
82
.github/workflows/app-store.yml
vendored
@@ -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; }
|
||||
232
.github/workflows/ci.yml
vendored
232
.github/workflows/ci.yml
vendored
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user