Compare commits
5 Commits
v1.0.1
...
d6d5f40359
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6d5f40359 | ||
|
|
193e56a0b5 | ||
|
|
9cf7ea1f15 | ||
|
|
3dd044029e | ||
|
|
cb86bf55b3 |
124
.github/workflows/pr-iphone-deploy.yml
vendored
124
.github/workflows/pr-iphone-deploy.yml
vendored
@@ -1,35 +1,24 @@
|
||||
# =============================================================================
|
||||
# TabataGo — PR → Build → devicectl Deploy → Wait LGTM → Merge
|
||||
# TabataGo — PR → Build → WiFi/USB Direct Deploy → Wait LGTM → Merge
|
||||
# =============================================================================
|
||||
|
||||
name: PR → Build → devicectl Deploy → LGTM
|
||||
name: PR → Build → WiFi/USB Deploy → LGTM
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
types: [opened, synchronize, reopened]
|
||||
# Only run the iOS build+deploy when the PR actually touches the Swift app
|
||||
# or this workflow itself. Docs-only / backend-only / admin-web-only PRs
|
||||
# skip the runner entirely (no point rebuilding an unchanged .app).
|
||||
paths:
|
||||
- 'tabatago-swift/**'
|
||||
- '.github/workflows/pr-iphone-deploy.yml'
|
||||
|
||||
# Every mutation (comments, merge) goes through the Gitea API with PR_API_TOKEN,
|
||||
# never the runner's native GITHUB_TOKEN — so a `permissions:` block would be
|
||||
# a no-op here. Intentionally omitted.
|
||||
concurrency:
|
||||
# Per-PR grouping: superseded pushes cancel the previous pipeline, different
|
||||
# PRs still run in parallel. Prevents double-merges and wasted runner time.
|
||||
group: pr-iphone-deploy-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
|
||||
build-deploy:
|
||||
name: Build & Deploy to iPhone (devicectl)
|
||||
name: Build & Deploy to iPhone (WiFi/USB)
|
||||
runs-on: macos
|
||||
timeout-minutes: 30
|
||||
timeout-minutes: 20
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -53,11 +42,9 @@ jobs:
|
||||
# Act runner caches
|
||||
rm -rf ~/.cache/act/*/hostexecutor/tabatago-swift/build 2>/dev/null || true
|
||||
|
||||
# SPM cache is KEPT between runs — RevenueCat is ~1.10 GiB and takes
|
||||
# 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. It lives at
|
||||
# build/spm-cache (see -clonedSourcePackagesDirPath in the Build step)
|
||||
# so NEVER `rm -rf build/` — only delete build/derived.
|
||||
# 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)
|
||||
@@ -65,12 +52,10 @@ jobs:
|
||||
|
||||
# Module cache Xcode — des .pcm stale peuvent segfault le linker
|
||||
rm -rf ~/Library/Developer/Xcode/DerivedData/ModuleCache.noindex 2>/dev/null || true
|
||||
# NOTE: do NOT `rm -rf ~/Library/Caches/com.apple.dt.Xcode` — that is the
|
||||
# shared global cache; wiping it slows every subsequent build.
|
||||
rm -rf ~/Library/Caches/com.apple.dt.Xcode
|
||||
|
||||
# Build artifacts locaux — sparing build/spm-cache (see above).
|
||||
rm -rf build/derived
|
||||
rm -rf build/*.log 2>/dev/null || true
|
||||
# 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
|
||||
@@ -80,45 +65,11 @@ jobs:
|
||||
find . -name "Package.resolved" -delete 2>/dev/null || true
|
||||
find . -name ".package.resolved" -delete 2>/dev/null || true
|
||||
|
||||
echo "✅ Caches nettoyés (SPM cache préservé)"
|
||||
echo "✅ Caches nettoyés"
|
||||
|
||||
- name: Install tools (xcodegen)
|
||||
- name: Install tools (xcodegen, node, ios-deploy)
|
||||
run: |
|
||||
# The act_runner shell can land under Rosetta 2 on Apple Silicon;
|
||||
# Homebrew lives in /opt/homebrew (ARM prefix), so run brew natively.
|
||||
# (Brew refuses to install/upgrade under Rosetta in the ARM prefix.)
|
||||
# Only xcodegen is needed — node is NOT used by any step in this
|
||||
# workflow, and installing it triggers brew's "installed dependents
|
||||
# check" which tries to rebuild unrelated formulae (e.g. gemini-cli)
|
||||
# and fails the build.
|
||||
arch -arm64 brew install xcodegen
|
||||
|
||||
- name: Write Secrets.xcconfig from Gitea secrets
|
||||
env:
|
||||
SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
|
||||
SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }}
|
||||
REVENUECAT_API_KEY: ${{ secrets.REVENUECAT_API_KEY }}
|
||||
POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }}
|
||||
run: |
|
||||
# Preflight: fail loudly if a secret is missing, rather than producing
|
||||
# an .app that silently can't reach Supabase/RevenueCat at runtime.
|
||||
for v in SUPABASE_URL SUPABASE_ANON_KEY REVENUECAT_API_KEY; do
|
||||
val="$(printenv "$v")"
|
||||
if [ -z "$val" ]; then
|
||||
echo "❌ Secret missing: $v. Add it to Gitea repo secrets."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
# POSTHOG_API_KEY may legitimately be empty (PostHog not yet wired).
|
||||
# NOTE: heredoc body MUST start at column 0 — xcconfig keys are
|
||||
# whitespace-sensitive, leading spaces would break the build setting.
|
||||
cat > tabatago-swift/Config/Secrets.xcconfig <<EOF
|
||||
SUPABASE_URL = ${SUPABASE_URL}
|
||||
SUPABASE_ANON_KEY = ${SUPABASE_ANON_KEY}
|
||||
REVENUECAT_API_KEY = ${REVENUECAT_API_KEY}
|
||||
POSTHOG_API_KEY = ${POSTHOG_API_KEY}
|
||||
EOF
|
||||
echo "✅ Secrets.xcconfig written (values redacted)"
|
||||
brew install xcodegen node ios-deploy || true
|
||||
|
||||
- name: Generate Xcode project
|
||||
run: |
|
||||
@@ -148,22 +99,29 @@ jobs:
|
||||
CODE_SIGN_STYLE=Automatic \
|
||||
DEVELOPMENT_TEAM=2MJF39L8VY
|
||||
|
||||
- name: Install to iPhone (devicectl)
|
||||
- name: Install to iPhone (WiFi → USB fallback)
|
||||
run: |
|
||||
UDID="00008120-000925CE3672201E"
|
||||
APP=$(find build/derived -name 'TabataGo.app' -type d | head -1)
|
||||
|
||||
# devicectl handles both WiFi (network) and wired (USB-C/Thunderbolt)
|
||||
# discovery natively — no separate USB-fallback tool needed.
|
||||
echo "📱 Installing via devicectl..."
|
||||
if xcrun devicectl device install app --device "$UDID" "$APP"; then
|
||||
echo "✅ Installed via devicectl"
|
||||
# 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
|
||||
|
||||
echo "❌ devicectl install failed"
|
||||
exit 1
|
||||
# 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:
|
||||
@@ -175,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 sur l'iPhone (devicectl).\\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/WiFi).\\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:
|
||||
@@ -199,31 +157,29 @@ jobs:
|
||||
|
||||
echo "⏳ Attente LGTM sur PR #${PR}..."
|
||||
|
||||
# Re-fetch ALL comments each cycle (not since_id) so we also catch
|
||||
# edits — e.g. a reviewer changing "KO" → "LGTM". Negligible cost for
|
||||
# PRs with <50 comments.
|
||||
LAST_ID=$(curl -s -H "Authorization: token ${GT_TOKEN}" \
|
||||
"${API}/issues/${PR}/comments" | \
|
||||
python3 -c "import json,sys; cs=json.load(sys.stdin); print(cs[-1]['id'] if cs else 0)" 2>/dev/null || echo 0)
|
||||
|
||||
while [ $TRIES -lt $MAX ]; do
|
||||
sleep 30
|
||||
TRIES=$((TRIES + 1))
|
||||
|
||||
COMMENTS=$(curl -s -H "Authorization: token ${GT_TOKEN}" \
|
||||
"${API}/issues/${PR}/comments?limit=50&page=1")
|
||||
"${API}/issues/${PR}/comments?since_id=${LAST_ID}")
|
||||
|
||||
# Match LGTM/KO anywhere in the body (with a word boundary so "KOM"
|
||||
# or "LGTMX" don't trigger). Case-insensitive.
|
||||
if echo "$COMMENTS" | grep -qiE '"body":[[:space:]]*"[^"]*\bLGTM\b'; then
|
||||
echo "✅ LGTM reçu ! Squash-merge..."
|
||||
if echo "$COMMENTS" | grep -qi '"body": *"LGTM"'; then
|
||||
echo "✅ LGTM reçu ! Merge..."
|
||||
curl -s -X POST \
|
||||
-H "Authorization: token ${GT_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"Do\":\"squash\"}" \
|
||||
-d "{\"Do\":\"merge\"}" \
|
||||
"${API}/pulls/${PR}/merge"
|
||||
echo "✅ Mergée (squash)."
|
||||
echo "✅ Mergée."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if echo "$COMMENTS" | grep -qiE '"body":[[:space:]]*"[^"]*\bKO\b'; then
|
||||
if echo "$COMMENTS" | grep -qi '"body": *"KO"'; then
|
||||
echo "❌ KO reçu. Bloquée."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
44
AGENTS.md
44
AGENTS.md
@@ -152,43 +152,34 @@ Complications : `TabataGoComplication`.
|
||||
### Workflow : `.github/workflows/pr-iphone-deploy.yml`
|
||||
|
||||
**Trigger** : PR ouverte/synchronize/reopened sur `main`.
|
||||
**Runner** : label `macos` (auto-hébergé). `timeout-minutes: 30` (build-deploy), `120` (wait-approval).
|
||||
**Concurrency** : `group: pr-iphone-deploy-<PR#>`, `cancel-in-progress: true` → un push sur une PR existante annule le pipeline précédent (pas de double-merge, pas de runner gaspillé). Différentes PRs tournent en parallèle.
|
||||
**Runner** : label `macos` (auto-hébergé).
|
||||
**Déroulé** :
|
||||
|
||||
1. **Checkout** (shallow, branche head) via `PR_API_TOKEN`.
|
||||
2. **Setup PATH** : `echo "/opt/homebrew/bin" >> $GITHUB_PATH` — requis car Rosetta ne le voit pas.
|
||||
3. **Clean ciblé** :
|
||||
- Supprime `build/derived`, `TabataGo.xcodeproj`, `Package.resolved`, DerivedData, ModuleCache.
|
||||
- **GARDE le cache SPM** (`build/spm-cache` — c'est le chemin passé à `-clonedSourcePackagesDirPath`) — RevenueCat ≈ 1.1 GiB, re-cloner = 5+ min et échecs aléatoires du sandbox. **Ne jamais faire `rm -rf build/`** : ça détruirait ce cache. Le clean step ne touche que `build/derived`.
|
||||
- Ne touche **pas** à `~/Library/Caches/com.apple.dt.Xcode` (cache Xcode global — le nuke ralentit tous les builds suivants).
|
||||
4. **Install tools** : `arch -arm64 brew install xcodegen` (sans `|| true` — si brew down, on fail ici, pas deux steps plus loin). `arch -arm64` requis car le shell act_runner tourne sous Rosetta 2 sur Apple Silicon, mais Homebrew est dans le prefix ARM `/opt/homebrew` — sans ça, brew refuse d'installer/upgrader. Seul xcodegen est nécessaire : **node n'est pas utilisé** par ce workflow, et l'installer déclenche le "installed dependents check" de brew qui essaie de rebuilder des formulae sans rapport (ex. `gemini-cli`) et fail le build.
|
||||
5. **Write `Config/Secrets.xcconfig`** depuis les secrets Gitea (`SUPABASE_URL`, `SUPABASE_ANON_KEY`, `REVENUECAT_API_KEY`, `POSTHOG_API_KEY`). Preflight : fail loud si un secret requis manque. Le fichier est gitigné (règle #9) ; CI l'écrase à chaque run. `Info.plist` lit les valeurs via `$(SUPABASE_URL)` etc., et `project.yml` wire le xcconfig en Debug+Release — aucun autre changement nécessaire côté build.
|
||||
6. **xcodegen generate** → `xcodebuild -resolvePackageDependencies` → **build** (`-scheme TabataGo`, Debug, auto-provisioning, team `2MJF39L8VY`). Le scheme build les 4 targets : `TabataGo` (app iOS), `TabataGoWidget` (widget iOS, vrai target — ne pas confondre avec `TabataGoWatchWidget` qui est watchOS), `TabataGoWatch` (app watchOS), `TabataGoWatchWidget` (complication watchOS).
|
||||
7. **Deploy iPhone** UDID `00008120-000925CE3672201E` : `xcrun devicectl device install app` uniquement. `devicectl` gère nativement la découverte WiFi (réseau) et filaire (USB-C/Thunderbolt) — pas de fallback `ios-deploy`.
|
||||
8. **Post comment** "Prêt à tester" sur la PR.
|
||||
9. **Job `wait-approval`** : poll (30s, max 240 = 2h). **Re-fetche TOUS les comments** chaque cycle (pas `since_id`) pour attraper aussi les edits (un reviewer passant de "KO" à "LGTM"). Match LGTM/KO n'importe où dans le body (regex `\bLGTM\b` / `\bKO\b`, case-insensitive) — "Tested, LGTM!" compte ; "KOM" ne compte pas. `LGTM` → **squash-merge** (`{"Do":"squash"}`). `KO` → blocage. Timeout → fail.
|
||||
- Supprime build artifacts, `TabataGo.xcodeproj`, `Package.resolved`, DerivedData, ModuleCache.
|
||||
- **GARDE le cache SPM** (`../build/spm-cache`) — RevenueCat ≈ 1.1 GiB, re-cloner = 5+ min et échecs aléatoires du sandbox.
|
||||
4. **Install tools** : `brew install xcodegen node ios-deploy`.
|
||||
5. **xcodegen generate** → `xcodebuild -resolvePackageDependencies` → **build** (`-scheme TabataGo`, Debug, auto-provisioning, team `2MJF39L8VY`).
|
||||
6. **Deploy iPhone** UDID `00008120-000925CE3672201E` : `devicectl` WiFi d'abord, fallback `ios-deploy` USB.
|
||||
7. **Post comment** "Prêt à tester" sur la PR.
|
||||
8. **Job `wait-approval`** : poll (30s, max 240 = 2h) les commentaires de la PR. `LGTM` → auto-merge. `KO` → blocage. Timeout → fail.
|
||||
|
||||
### Secrets
|
||||
|
||||
| Secret | Usage |
|
||||
|--------|-------|
|
||||
| `PR_API_TOKEN` | Checkout + API Gitea (comment, merge). **JAMAIS `GITEA_TOKEN`/`GITHUB_TOKEN`**. Tout passe par l'API Gitea avec ce token — un bloc `permissions:` natif serait no-op, c'est pourquoi il n'y en a pas. |
|
||||
| `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `REVENUECAT_API_KEY`, `POSTHOG_API_KEY` | Un step CI écrit `Config/Secrets.xcconfig` depuis ces 4 secrets **avant** `xcodegen generate`. `Info.plist` lit les valeurs via `$(SUPABASE_URL)` etc. ; `project.yml` wire le xcconfig en Debug+Release. `POSTHOG_API_KEY` peut être vide (PostHog pas encore câblé en SPM). |
|
||||
|
||||
> ⚠️ **Historique** : `Config/Secrets.xcconfig` a été committé par erreur dans le passé (avant la règle gitignore) avec des valeurs réelles. Le fichier est maintenant gitigné et écrasé par CI, mais l'historique git contient encore l'ancienne version — **rotation des clés Supabase/RevenueCat recommandée côté ops**.
|
||||
| `PR_API_TOKEN` | Checkout + API Gitea (comment, merge). **JAMAIS `GITEA_TOKEN`/`GITHUB_TOKEN`** |
|
||||
| `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `REVENUECAT_API_KEY`, `POSTHOG_API_KEY` | Injectés via `Config/Secrets.xcconfig` → Info.plist |
|
||||
|
||||
### ⚠️ Pitfalls CI
|
||||
|
||||
- `/opt/homebrew/bin` **hors PATH par défaut** sous Rosetta → toujours l'ajouter.
|
||||
- **xcodegen 2.45.4 ne génère pas de schemes auto** → scheme `TabataGo` défini explicitement dans `project.yml` (ne pas le supprimer). Il liste **4 targets** : `TabataGo`, `TabataGoWidget`, `TabataGoWatch`, `TabataGoWatchWidget`.
|
||||
- **Ne jamais `rm -rf build/` ni `rm -rf build/spm-cache`** dans le CI — RevenueCat y vit. Le clean step ne touche que `build/derived`.
|
||||
- **xcodegen 2.45.4 ne génère pas de schemes auto** → scheme `TabataGo` défini explicitement dans `project.yml` (ne pas le supprimer).
|
||||
- **Ne jamais `rm -rf` le cache SPM** dans le CI (c'est l'inverse des build artifacts).
|
||||
- `SWIFT_ENABLE_EXPLICIT_MODULES=NO` au build — requis sinon segfault linker sur `.pcm` stale.
|
||||
- `-skipPackagePluginValidation -allowProvisioningUpdates`.
|
||||
- **Concurrency** : le bloc `concurrency:` est volontaire — ne pas le retirer, sinon les pushes successifs empilent des pipelines et tentent des double-merges.
|
||||
- **LGTM/KO regex** : matche n'importe où dans le body avec `\b...\b` (word boundary). Ne pas revenir à un `grep '"body": *"LGTM"'` ancré — il raterait "Tested, LGTM!".
|
||||
- **Squash-merge** (`{"Do":"squash"}`) — pas merge commit ni rebase. Garde l'historique `main` linéaire.
|
||||
- **Comment edits** : `wait-approval` re-fetche tous les comments chaque cycle (pas `since_id`) pour attraper les edits.
|
||||
|
||||
---
|
||||
|
||||
@@ -228,7 +219,7 @@ Node.js (`server.js`, `package.json`, `Dockerfile`). Télécharge l'audio de pla
|
||||
6. **Navigation = TabView + sheet/fullScreenCover.** **Pas de `NavigationStack`.**
|
||||
7. **Tout en français** côté user-facing (`Localizable.xcstrings` / `L10n`). Codes/types restent en anglais.
|
||||
8. **XcodeGen.** Éditer `project.yml`, pas `.xcodeproj`. Régénérer avec `xcodegen generate`.
|
||||
9. **Secrets.** Jamais committer `Config/Secrets.xcconfig`. Template = `.example`. En CI, un step écrit ce fichier depuis les secrets Gitea (`SUPABASE_URL`, `SUPABASE_ANON_KEY`, `REVENUECAT_API_KEY`, `POSTHOG_API_KEY`) **avant** `xcodegen generate` ; `Info.plist` lit les valeurs via `$(VAR)` et `project.yml` wire le xcconfig en Debug+Release.
|
||||
9. **Secrets.** Jamais committer `Config/Secrets.xcconfig`. Template = `.example`.
|
||||
10. **Lazy d'abord.** Avant d'ajouter une dépendance : stdlib Apple, puis ce qui existe déjà dans le repo, puis SPM déjà installé. PostHog n'est pas installé — ne pas l'ajouter sans justification.
|
||||
|
||||
---
|
||||
@@ -243,14 +234,9 @@ Node.js (`server.js`, `package.json`, `Dockerfile`). Télécharge l'audio de pla
|
||||
| `@ObservableObject` / `@Published` | `@Observable` |
|
||||
| Force-unwrap `!` | `guard let` |
|
||||
| Strings UI en anglais | Françaises dans `L10n`/`Localizable.xcstrings` |
|
||||
| `rm -rf build/` ou `rm -rf build/spm-cache` en CI | Garder `build/spm-cache` ; ne cleaner que `build/derived` |
|
||||
| `rm -rf ~/Library/Caches/com.apple.dt.Xcode` | Laisser le cache Xcode global intact (sinon tous les builds ralentissent) |
|
||||
| `rm -rf` le cache SPM en CI | Garder `build/spm-cache` |
|
||||
| Utiliser `GITEA_TOKEN` en CI | `PR_API_TOKEN` uniquement |
|
||||
| Compter sur un bloc `permissions:` natif Gitea Actions | Tout passe par l'API Gitea avec `PR_API_TOKEN` ; le bloc natif serait no-op |
|
||||
| Retirer le bloc `concurrency:` | Permet double-merge et pipelines empilés |
|
||||
| Ancre le grep LGTM au début du body (`'"body": *"LGTM"'`) | Regex `\bLGTM\b` n'importe où dans le body (attrape "Tested, LGTM!") |
|
||||
| `{"Do":"merge"}` (merge commit) | `{"Do":"squash"}` pour un `main` linéaire |
|
||||
| Supprimer le scheme explicite dans `project.yml` | xcodegen 2.45.4 n'en crée pas — scheme doit lister `TabataGo`/`TabataGoWidget`/`TabataGoWatch`/`TabataGoWatchWidget` |
|
||||
| Supprimer le scheme explicite dans `project.yml` | xcodegen 2.45.4 n'en crée pas |
|
||||
| Compter sur `admin-web/` pour la app iOS | Dashboard admin séparé, communique via Supabase uniquement |
|
||||
| Recréer `programs`/`program_workouts` | Remplacés par `workout_programs`/`program_tabatas` (migration 005) |
|
||||
|
||||
|
||||
@@ -91,10 +91,8 @@ describe('Sidebar', () => {
|
||||
})
|
||||
|
||||
it('should handle logout errors gracefully', async () => {
|
||||
// Real supabase.auth.signOut() never throws — it resolves with { error }.
|
||||
// Mocking it realistically avoids an unhandled rejection and matches prod behaviour.
|
||||
mockSignOut.mockResolvedValueOnce({ error: new Error('Network error') })
|
||||
|
||||
mockSignOut.mockRejectedValueOnce(new Error('Network error'))
|
||||
|
||||
render(<Sidebar />)
|
||||
|
||||
const logoutButton = screen.getByRole('button', { name: /logout/i })
|
||||
@@ -103,9 +101,6 @@ describe('Sidebar', () => {
|
||||
await waitFor(() => {
|
||||
expect(mockSignOut).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
// Graceful: even when signOut errors, the user is still redirected to login.
|
||||
expect(mockPush).toHaveBeenCalledWith('/login')
|
||||
})
|
||||
|
||||
it('should have correct styling for navigation', () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "my-app",
|
||||
"version": "1.0.1",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
|
||||
@@ -18,8 +18,17 @@
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>tabatago</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>2</string>
|
||||
<string>1</string>
|
||||
<key>NSHealthShareUsageDescription</key>
|
||||
<string>TabataGo reads your health data to show fitness stats and personalize your workouts.</string>
|
||||
<key>NSHealthUpdateUsageDescription</key>
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
<string>health-records</string>
|
||||
</array>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.fr.millianlmx.tabatago</string>
|
||||
</array>
|
||||
<array/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -28,55 +28,12 @@ final class AudioService {
|
||||
try audioSession.setCategory(
|
||||
.playback,
|
||||
mode: .default,
|
||||
options: [.allowAirPlay, .allowBluetooth]
|
||||
options: [.mixWithOthers, .allowAirPlay, .allowBluetooth]
|
||||
)
|
||||
try audioSession.setActive(true)
|
||||
} catch {
|
||||
print("[Audio] Session configuration failed: \(error)")
|
||||
}
|
||||
|
||||
// ─── Interruption Handler
|
||||
NotificationCenter.default.addObserver(
|
||||
forName: AVAudioSession.interruptionNotification,
|
||||
object: nil,
|
||||
queue: .main
|
||||
) { [weak self] notification in
|
||||
guard let self,
|
||||
let typeValue = notification.userInfo?[AVAudioSessionInterruptionTypeKey] as? UInt,
|
||||
let type = AVAudioSession.InterruptionType(rawValue: typeValue) else { return }
|
||||
|
||||
if type == .ended {
|
||||
guard let optionsValue = notification.userInfo?[AVAudioSessionInterruptionOptionKey] as? UInt,
|
||||
AVAudioSession.InterruptionOptions(rawValue: optionsValue).contains(.shouldResume) else { return }
|
||||
do {
|
||||
try self.audioSession.setActive(true)
|
||||
} catch {
|
||||
print("[Audio] Re-activation failed after interruption: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Route Change Handler
|
||||
NotificationCenter.default.addObserver(
|
||||
forName: AVAudioSession.routeChangeNotification,
|
||||
object: nil,
|
||||
queue: .main
|
||||
) { [weak self] notification in
|
||||
guard let self,
|
||||
let reasonValue = notification.userInfo?[AVAudioSessionRouteChangeReasonKey] as? UInt,
|
||||
let reason = AVAudioSession.RouteChangeReason(rawValue: reasonValue) else { return }
|
||||
|
||||
switch reason {
|
||||
case .newDeviceAvailable, .oldDeviceUnavailable:
|
||||
do {
|
||||
try self.audioSession.setActive(true)
|
||||
} catch {
|
||||
print("[Audio] Route re-activation failed: \(error)")
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Phase Audio Cues ─────────────────────────────────────────
|
||||
|
||||
@@ -39,7 +39,6 @@ final class PlayerViewModel: ObservableObject {
|
||||
@Published var isComplete: Bool = false
|
||||
@Published var showExitConfirmation: Bool = false
|
||||
@Published private(set) var completedSession: WorkoutSession? = nil
|
||||
@Published var liveActivityError: String? = nil
|
||||
|
||||
// Track info for Live Activity — set by View when music changes
|
||||
var currentTrackTitle = ""
|
||||
@@ -383,10 +382,7 @@ final class PlayerViewModel: ObservableObject {
|
||||
// ─── Dynamic Island / Live Activity ─────────────────────────────
|
||||
|
||||
func syncActivity(shouldAlert: Bool = false) {
|
||||
guard ActivityAuthorizationInfo().areActivitiesEnabled else {
|
||||
liveActivityError = "Live Activities are disabled in Settings"
|
||||
return
|
||||
}
|
||||
guard ActivityAuthorizationInfo().areActivitiesEnabled else { return }
|
||||
guard isRunning else { return }
|
||||
|
||||
let isPlayingMusic = (phase == .work || phase == .rest) && isRunning && !isPaused
|
||||
@@ -453,14 +449,8 @@ final class PlayerViewModel: ObservableObject {
|
||||
attributes: attrs,
|
||||
content: ActivityContent(state: state, staleDate: staleDate)
|
||||
)
|
||||
liveActivityError = nil
|
||||
observeActivityState()
|
||||
} catch let authError as ActivityAuthorizationError {
|
||||
liveActivityError = "Live Activities: \(authError.localizedDescription)"
|
||||
} catch {
|
||||
let msg = error.localizedDescription
|
||||
let truncated = msg.count > 200 ? String(msg.prefix(200)) + "…" : msg
|
||||
liveActivityError = "Live Activity error: \(truncated)"
|
||||
print("❌ Failed to start Live Activity: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,40 +66,6 @@ struct PlayerView: View {
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════
|
||||
// Live Activity error banner (non-blocking)
|
||||
// ══════════════════════════════════════════════════
|
||||
if let error = vm.liveActivityError {
|
||||
VStack {
|
||||
HStack(spacing: 8) {
|
||||
Image(systemName: "exclamationmark.triangle.fill")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.yellow)
|
||||
Text(error)
|
||||
.font(.caption2.weight(.medium))
|
||||
.foregroundStyle(.white.opacity(0.9))
|
||||
Spacer()
|
||||
Button {
|
||||
vm.liveActivityError = nil
|
||||
} label: {
|
||||
Image(systemName: "xmark")
|
||||
.font(.system(size: 10, weight: .bold))
|
||||
.foregroundStyle(.white.opacity(0.6))
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.vertical, 10)
|
||||
.background(.ultraThinMaterial.opacity(0.9))
|
||||
.clipShape(RoundedRectangle(cornerRadius: 12))
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.top, 60)
|
||||
Spacer()
|
||||
}
|
||||
.transition(.move(edge: .top).combined(with: .opacity))
|
||||
.animation(.spring(duration: 0.3), value: vm.liveActivityError)
|
||||
.zIndex(100)
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════
|
||||
// Layer 4 — Compact timer ring (top-right corner)
|
||||
// ══════════════════════════════════════════════════
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>2</string>
|
||||
<string>1</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>2</string>
|
||||
<string>1</string>
|
||||
<key>NSHealthShareUsageDescription</key>
|
||||
<string>TabataGo reads your heart rate and calories during workouts.</string>
|
||||
<key>NSHealthUpdateUsageDescription</key>
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>TabataGoWidget</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
|
||||
@@ -553,7 +553,7 @@ struct WorkoutLiveActivity: Widget {
|
||||
.contentMargins(.leading, 8, for: .expanded)
|
||||
.contentMargins(.trailing, 8, for: .expanded)
|
||||
.contentMargins(.bottom, 6, for: .expanded)
|
||||
.widgetURL(URL(string: "tabatago://workout"))
|
||||
.widgetURL(URL(string: "tabatago://workout")!)
|
||||
}
|
||||
.supplementalActivityFamilies([.small, .medium])
|
||||
}
|
||||
|
||||
@@ -60,10 +60,6 @@ targets:
|
||||
SUPABASE_ANON_KEY: $(SUPABASE_ANON_KEY)
|
||||
REVENUECAT_API_KEY: $(REVENUECAT_API_KEY)
|
||||
POSTHOG_API_KEY: $(POSTHOG_API_KEY)
|
||||
NSSupportsLiveActivities: true
|
||||
NSSupportsLiveActivitiesFrequentUpdates: true
|
||||
UIBackgroundModes:
|
||||
- audio
|
||||
entitlements:
|
||||
path: TabataGo/Resources/TabataGo.entitlements
|
||||
properties:
|
||||
@@ -80,8 +76,6 @@ targets:
|
||||
|
||||
- target: TabataGoWatch
|
||||
embed: true
|
||||
- target: TabataGoWidget
|
||||
embed: true
|
||||
settings:
|
||||
base:
|
||||
PRODUCT_BUNDLE_IDENTIFIER: fr.millianlmx.tabatago
|
||||
@@ -162,30 +156,6 @@ targets:
|
||||
TARGETED_DEVICE_FAMILY: "4"
|
||||
WATCHOS_DEPLOYMENT_TARGET: "11.0"
|
||||
|
||||
TabataGoWidget:
|
||||
type: app-extension
|
||||
platform: iOS
|
||||
deploymentTarget: "26.0"
|
||||
sources:
|
||||
- path: TabataGoWidget
|
||||
excludes:
|
||||
- "**/.DS_Store"
|
||||
- path: TabataGo/Models/WorkoutActivityAttributes.swift
|
||||
- path: TabataGo/Models/MusicActivityAttributes.swift
|
||||
info:
|
||||
path: TabataGoWidget/Info.plist
|
||||
properties:
|
||||
NSExtension:
|
||||
NSExtensionPointIdentifier: com.apple.widgetkit-extension
|
||||
NSSupportsLiveActivities: true
|
||||
NSSupportsLiveActivitiesFrequentUpdates: true
|
||||
settings:
|
||||
base:
|
||||
PRODUCT_BUNDLE_IDENTIFIER: fr.millianlmx.tabatago.widget
|
||||
IPHONEOS_DEPLOYMENT_TARGET: "26.0"
|
||||
TARGETED_DEVICE_FAMILY: "1"
|
||||
ENABLE_PREVIEWS: YES
|
||||
|
||||
TabataGoTests:
|
||||
type: bundle.unit-test
|
||||
platform: iOS
|
||||
@@ -215,9 +185,6 @@ schemes:
|
||||
build:
|
||||
targets:
|
||||
TabataGo: all
|
||||
TabataGoWidget: all
|
||||
TabataGoWatch: all
|
||||
TabataGoWatchWidget: all
|
||||
run:
|
||||
config: Debug
|
||||
archive:
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"version": "1.0.1"
|
||||
"version": "1.0.0"
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ RUN apt-get update && \
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci --omit=dev
|
||||
COPY package.json package-lock.json* ./
|
||||
RUN npm install --production
|
||||
|
||||
COPY server.js ./
|
||||
|
||||
|
||||
429
youtube-worker/package-lock.json
generated
429
youtube-worker/package-lock.json
generated
@@ -8,7 +8,6 @@
|
||||
"name": "youtube-worker",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@google/genai": "^1.46.0",
|
||||
"@supabase/supabase-js": "^2.49.1",
|
||||
"youtubei.js": "^17.0.1"
|
||||
}
|
||||
@@ -19,87 +18,6 @@
|
||||
"integrity": "sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==",
|
||||
"license": "(Apache-2.0 AND BSD-3-Clause)"
|
||||
},
|
||||
"node_modules/@google/genai": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.52.0.tgz",
|
||||
"integrity": "sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"google-auth-library": "^10.3.0",
|
||||
"p-retry": "^4.6.2",
|
||||
"protobufjs": "^7.5.4",
|
||||
"ws": "^8.18.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.25.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@modelcontextprotocol/sdk": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@protobufjs/aspromise": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
|
||||
"integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/base64": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
|
||||
"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/codegen": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz",
|
||||
"integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/eventemitter": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz",
|
||||
"integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/fetch": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz",
|
||||
"integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@protobufjs/aspromise": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@protobufjs/float": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
|
||||
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/path": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
|
||||
"integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/pool": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
|
||||
"integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/utf8": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz",
|
||||
"integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@supabase/auth-js": {
|
||||
"version": "2.100.0",
|
||||
"resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.100.0.tgz",
|
||||
@@ -195,12 +113,6 @@
|
||||
"undici-types": "~7.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/retry": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
|
||||
"integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/ws": {
|
||||
"version": "8.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
|
||||
@@ -210,193 +122,6 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/agent-base": {
|
||||
"version": "7.1.4",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
|
||||
"integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bignumber.js": {
|
||||
"version": "9.3.1",
|
||||
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz",
|
||||
"integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-equal-constant-time": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
|
||||
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/data-uri-to-buffer": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
|
||||
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/ecdsa-sig-formatter": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
|
||||
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/extend": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fetch-blob": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
|
||||
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/jimmywarting"
|
||||
},
|
||||
{
|
||||
"type": "paypal",
|
||||
"url": "https://paypal.me/jimmywarting"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"node-domexception": "^1.0.0",
|
||||
"web-streams-polyfill": "^3.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20 || >= 14.13"
|
||||
}
|
||||
},
|
||||
"node_modules/formdata-polyfill": {
|
||||
"version": "4.0.10",
|
||||
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
|
||||
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fetch-blob": "^3.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/gaxios": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.2.0.tgz",
|
||||
"integrity": "sha512-CUVb4wcYe+771XevyH6HtGmXFAGGKkIC3kswAP8Z1JCe0j80JMaTPZH930DWFrvo0atjh18Arc0pEyUCWa5bfg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"extend": "^3.0.2",
|
||||
"https-proxy-agent": "^7.0.1",
|
||||
"node-fetch": "^3.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/gcp-metadata": {
|
||||
"version": "8.1.2",
|
||||
"resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz",
|
||||
"integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"gaxios": "^7.0.0",
|
||||
"google-logging-utils": "^1.0.0",
|
||||
"json-bigint": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/google-auth-library": {
|
||||
"version": "10.9.0",
|
||||
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.9.0.tgz",
|
||||
"integrity": "sha512-xtvUqvINPhTaBm7nXqlYPcrMHJPm1lCNdSovxnKKhTm+4JsvQ+KGVYJViLoH9Yxu8w+T0Qv5HubzYT9BLrppJg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.0",
|
||||
"ecdsa-sig-formatter": "^1.0.11",
|
||||
"gaxios": "^7.1.4",
|
||||
"gcp-metadata": "8.1.2",
|
||||
"google-logging-utils": "1.1.3",
|
||||
"jws": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/google-logging-utils": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz",
|
||||
"integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/https-proxy-agent": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
|
||||
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"agent-base": "^7.1.2",
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/iceberg-js": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz",
|
||||
@@ -406,42 +131,6 @@
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/json-bigint": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
|
||||
"integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bignumber.js": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jwa": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
|
||||
"integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer-equal-constant-time": "^1.0.1",
|
||||
"ecdsa-sig-formatter": "1.0.11",
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/jws": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
|
||||
"integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jwa": "^2.0.1",
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/long": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
|
||||
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/meriyah": {
|
||||
"version": "6.1.4",
|
||||
"resolved": "https://registry.npmjs.org/meriyah/-/meriyah-6.1.4.tgz",
|
||||
@@ -451,115 +140,6 @@
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-domexception": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
|
||||
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
|
||||
"deprecated": "Use your platform's native DOMException instead",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/jimmywarting"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://paypal.me/jimmywarting"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
|
||||
"integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"data-uri-to-buffer": "^4.0.0",
|
||||
"fetch-blob": "^3.1.4",
|
||||
"formdata-polyfill": "^4.0.10"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/node-fetch"
|
||||
}
|
||||
},
|
||||
"node_modules/p-retry": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz",
|
||||
"integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/retry": "0.12.0",
|
||||
"retry": "^0.13.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/protobufjs": {
|
||||
"version": "7.6.5",
|
||||
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.5.tgz",
|
||||
"integrity": "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@protobufjs/aspromise": "^1.1.2",
|
||||
"@protobufjs/base64": "^1.1.2",
|
||||
"@protobufjs/codegen": "^2.0.5",
|
||||
"@protobufjs/eventemitter": "^1.1.1",
|
||||
"@protobufjs/fetch": "^1.1.1",
|
||||
"@protobufjs/float": "^1.0.2",
|
||||
"@protobufjs/path": "^1.1.2",
|
||||
"@protobufjs/pool": "^1.1.0",
|
||||
"@protobufjs/utf8": "^1.1.1",
|
||||
"@types/node": ">=13.7.0",
|
||||
"long": "^5.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/retry": {
|
||||
"version": "0.13.1",
|
||||
"resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
|
||||
"integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
@@ -572,15 +152,6 @@
|
||||
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/web-streams-polyfill": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
|
||||
"integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.20.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz",
|
||||
|
||||
Reference in New Issue
Block a user