@ramonclaudio/create-vexpo 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -10
- package/dist/index.js +8 -7
- package/dist/templates/default/.eas/workflows/asc-events.yml +9 -6
- package/dist/templates/default/.eas/workflows/deploy-production.yml +28 -15
- package/dist/templates/default/.eas/workflows/e2e-tests.yml +3 -2
- package/dist/templates/default/.eas/workflows/pr-preview.yml +12 -21
- package/dist/templates/default/.eas/workflows/release.yml +3 -7
- package/dist/templates/default/.eas/workflows/rollback.yml +54 -28
- package/dist/templates/default/.eas/workflows/rollout.yml +27 -33
- package/dist/templates/default/.eas/workflows/rotate-apple-jwt.yml +1 -5
- package/dist/templates/default/.eas/workflows/testflight.yml +3 -7
- package/dist/templates/default/.github/workflows/check.yml +20 -12
- package/dist/templates/default/.maestro/launch.yaml +19 -10
- package/dist/templates/default/AGENTS.md +25 -8
- package/dist/templates/default/DESIGN.md +14 -10
- package/dist/templates/default/README.md +83 -78
- package/dist/templates/default/SETUP.md +159 -152
- package/dist/templates/default/__tests__/convex/_auth-harness.test.ts +112 -0
- package/dist/templates/default/__tests__/convex/_harness.ts +132 -0
- package/dist/templates/default/__tests__/convex/appAttest.test.ts +172 -0
- package/dist/templates/default/__tests__/convex/appAttestStore.test.ts +48 -0
- package/dist/templates/default/__tests__/convex/pushTokens-remove.test.ts +106 -0
- package/dist/templates/default/__tests__/convex/pushTokens-upsert.test.ts +146 -0
- package/dist/templates/default/__tests__/convex/users-deleteAccount.test.ts +140 -0
- package/dist/templates/default/__tests__/convex/users-deleteAvatar.test.ts +104 -0
- package/dist/templates/default/__tests__/convex/users-getMe.test.ts +98 -0
- package/dist/templates/default/__tests__/convex/users-getUser.test.ts +120 -0
- package/dist/templates/default/__tests__/convex/users-hardDeleteExpired.test.ts +67 -0
- package/dist/templates/default/__tests__/convex/users-restoreAccount.test.ts +96 -0
- package/dist/templates/default/__tests__/convex/users-updateAvatar.test.ts +92 -0
- package/dist/templates/default/__tests__/convex/users-updateProfile.test.ts +126 -0
- package/dist/templates/default/__tests__/convex/webhook.test.ts +31 -0
- package/dist/templates/default/__tests__/lib/deep-link.test.ts +51 -6
- package/dist/templates/default/__tests__/lib/schemas.test.ts +205 -0
- package/dist/templates/default/__tests__/lib/text-style.test.ts +31 -0
- package/dist/templates/default/_env.example +7 -7
- package/dist/templates/default/_gitattributes +1 -1
- package/dist/templates/default/_gitignore +17 -2
- package/dist/templates/default/_npmrc +7 -0
- package/dist/templates/default/_oxlintrc.json +1 -1
- package/dist/templates/default/app-store/accessibility.config.json +20 -0
- package/dist/templates/default/app-store/privacy.config.json +27 -0
- package/dist/templates/default/app.config.ts +105 -33
- package/dist/templates/default/app.json +1 -9
- package/dist/templates/default/convex/_generated/api.d.ts +12 -0
- package/dist/templates/default/convex/admin.ts +0 -13
- package/dist/templates/default/convex/appAttest.ts +467 -0
- package/dist/templates/default/convex/appAttestStore.ts +141 -0
- package/dist/templates/default/convex/apple.ts +53 -0
- package/dist/templates/default/convex/auth.ts +6 -45
- package/dist/templates/default/convex/constants.ts +2 -7
- package/dist/templates/default/convex/crons.ts +12 -5
- package/dist/templates/default/convex/email.ts +4 -24
- package/dist/templates/default/convex/env.ts +0 -4
- package/dist/templates/default/convex/errors.ts +0 -7
- package/dist/templates/default/convex/functions.ts +0 -26
- package/dist/templates/default/convex/http.ts +3 -5
- package/dist/templates/default/convex/log.ts +2 -25
- package/dist/templates/default/convex/pushSender.ts +145 -0
- package/dist/templates/default/convex/pushTokens.ts +110 -13
- package/dist/templates/default/convex/rateLimit.ts +8 -39
- package/dist/templates/default/convex/schema.ts +48 -5
- package/dist/templates/default/convex/tsconfig.json +1 -0
- package/dist/templates/default/convex/users.ts +143 -61
- package/dist/templates/default/convex/validators.ts +1 -38
- package/dist/templates/default/convex/webhook.ts +1 -31
- package/dist/templates/default/convex.json +1 -2
- package/dist/templates/default/metro.config.js +9 -1
- package/dist/templates/default/package.json +67 -70
- package/dist/templates/default/plugins/README.md +5 -1
- package/dist/templates/default/scripts/README.md +9 -9
- package/dist/templates/default/scripts/_run.mjs +3 -20
- package/dist/templates/default/scripts/clean.ts +81 -69
- package/dist/templates/default/scripts/gen-update-cert.mjs +98 -0
- package/dist/templates/default/{app → src/app}/(app)/(tabs)/(home)/index.tsx +21 -6
- package/dist/templates/default/{app → src/app}/(app)/(tabs)/(home,search)/_layout.tsx +9 -8
- package/dist/templates/default/{app → src/app}/(app)/(tabs)/(search)/index.tsx +26 -24
- package/dist/templates/default/{app → src/app}/(app)/(tabs)/_layout.tsx +3 -4
- package/dist/templates/default/{app → src/app}/(app)/(tabs)/settings/_layout.tsx +10 -6
- package/dist/templates/default/{app → src/app}/(app)/(tabs)/settings/index.tsx +81 -51
- package/dist/templates/default/{app → src/app}/(app)/(tabs)/settings/preferences.tsx +72 -12
- package/dist/templates/default/src/app/(app)/_layout.tsx +147 -0
- package/dist/templates/default/{app/(auth) → src/app/(app)/auth}/_layout.tsx +4 -5
- package/dist/templates/default/{app/(auth) → src/app/(app)/auth}/forgot-password.tsx +15 -9
- package/dist/templates/default/{app/(auth) → src/app/(app)/auth}/reset-password.tsx +88 -14
- package/dist/templates/default/{app/(auth) → src/app/(app)/auth}/sign-in.tsx +65 -35
- package/dist/templates/default/{app/(auth) → src/app/(app)/auth}/sign-up.tsx +131 -196
- package/dist/templates/default/src/app/(app)/debug.tsx +479 -0
- package/dist/templates/default/{app → src/app}/(app)/help.tsx +76 -64
- package/dist/templates/default/{app → src/app}/(app)/linked.tsx +21 -27
- package/dist/templates/default/{app → src/app}/(app)/privacy.tsx +35 -8
- package/dist/templates/default/src/app/(app)/profile/change-password.tsx +264 -0
- package/dist/templates/default/{app/(app)/profile.tsx → src/app/(app)/profile/index.tsx} +179 -255
- package/dist/templates/default/src/app/(app)/restore-account.tsx +192 -0
- package/dist/templates/default/src/app/(app)/sessions.tsx +287 -0
- package/dist/templates/default/src/app/(app)/welcome.tsx +194 -0
- package/dist/templates/default/src/app/+native-intent.tsx +25 -0
- package/dist/templates/default/src/app/+not-found.tsx +43 -0
- package/dist/templates/default/{app → src/app}/_layout.tsx +28 -37
- package/dist/templates/default/src/components/auth/apple-button.tsx +51 -0
- package/dist/templates/default/{components → src/components}/auth/otp-verification.tsx +79 -58
- package/dist/templates/default/{components → src/components}/auth/password-field.tsx +74 -18
- package/dist/templates/default/src/components/auth/segmented-toggle.tsx +71 -0
- package/dist/templates/default/src/components/ui/content-unavailable.tsx +81 -0
- package/dist/templates/default/src/components/ui/convex-error.tsx +21 -0
- package/dist/templates/default/src/components/ui/error-boundary.tsx +89 -0
- package/dist/templates/default/{components → src/components}/ui/loading-screen.tsx +5 -4
- package/dist/templates/default/{components → src/components}/ui/material.tsx +50 -17
- package/dist/templates/default/src/components/ui/offline-banner.tsx +59 -0
- package/dist/templates/default/{components → src/components}/ui/prominent-button.tsx +8 -11
- package/dist/templates/default/{components → src/components}/ui/skeleton.tsx +31 -13
- package/dist/templates/default/src/components/ui/status-text.tsx +64 -0
- package/dist/templates/default/src/components/ui/update-banner.tsx +85 -0
- package/dist/templates/default/{constants → src/constants}/layout.ts +0 -6
- package/dist/templates/default/{constants → src/constants}/theme.ts +49 -64
- package/dist/templates/default/{constants → src/constants}/ui.ts +13 -4
- package/dist/templates/default/src/hooks/use-debounce.ts +12 -0
- package/dist/templates/default/src/hooks/use-deep-link.ts +51 -0
- package/dist/templates/default/src/hooks/use-delete-account.ts +35 -0
- package/dist/templates/default/src/hooks/use-motion-screen-options.ts +13 -0
- package/dist/templates/default/src/hooks/use-network.ts +34 -0
- package/dist/templates/default/{hooks → src/hooks}/use-notifications.ts +39 -30
- package/dist/templates/default/src/hooks/use-reduce-transparency.ts +30 -0
- package/dist/templates/default/{hooks → src/hooks}/use-theme.ts +0 -5
- package/dist/templates/default/src/lib/appAttest.ts +78 -0
- package/dist/templates/default/src/lib/assets.ts +9 -0
- package/dist/templates/default/src/lib/deep-link.ts +82 -0
- package/dist/templates/default/{lib → src/lib}/dev-menu.ts +0 -4
- package/dist/templates/default/{lib → src/lib}/device.ts +1 -13
- package/dist/templates/default/{lib → src/lib}/dynamic-font.ts +13 -10
- package/dist/templates/default/src/lib/dynamic-symbol-size.ts +33 -0
- package/dist/templates/default/src/lib/masks.ts +21 -0
- package/dist/templates/default/src/lib/native-state.ts +20 -0
- package/dist/templates/default/{lib → src/lib}/notifications.ts +7 -45
- package/dist/templates/default/{lib → src/lib}/preferences.ts +0 -2
- package/dist/templates/default/{lib → src/lib}/schemas.ts +19 -16
- package/dist/templates/default/src/lib/text-style.ts +20 -0
- package/dist/templates/default/{lib → src/lib}/updates.ts +0 -7
- package/dist/templates/default/store.config.json +1 -1
- package/dist/templates/default/tsconfig.json +3 -1
- package/dist/templates/default/vitest.config.ts +8 -1
- package/package.json +5 -5
- package/dist/templates/default/app/(app)/_layout.tsx +0 -73
- package/dist/templates/default/app/(app)/debug.tsx +0 -389
- package/dist/templates/default/app/(app)/sessions.tsx +0 -191
- package/dist/templates/default/app/(app)/welcome.tsx +0 -140
- package/dist/templates/default/app/+native-intent.tsx +0 -14
- package/dist/templates/default/app/+not-found.tsx +0 -51
- package/dist/templates/default/bun.lock +0 -1860
- package/dist/templates/default/components/auth/segmented-toggle.tsx +0 -47
- package/dist/templates/default/components/ui/convex-error.tsx +0 -32
- package/dist/templates/default/components/ui/error-boundary.tsx +0 -57
- package/dist/templates/default/components/ui/offline-banner.tsx +0 -58
- package/dist/templates/default/components/ui/status-text.tsx +0 -49
- package/dist/templates/default/components/ui/update-banner.tsx +0 -82
- package/dist/templates/default/fingerprint.config.js +0 -9
- package/dist/templates/default/hooks/use-debounce.ts +0 -20
- package/dist/templates/default/hooks/use-deep-link.ts +0 -43
- package/dist/templates/default/hooks/use-network.ts +0 -11
- package/dist/templates/default/lib/assets.ts +0 -17
- package/dist/templates/default/lib/deep-link.ts +0 -71
- package/dist/templates/default/patches/PR-368.patch +0 -91
- package/dist/templates/default/patches/convex-dev-better-auth-0.12.2.tgz +0 -0
- /package/dist/templates/default/{hooks → src/hooks}/use-navigation-tracking.ts +0 -0
- /package/dist/templates/default/{hooks → src/hooks}/use-onboarding.ts +0 -0
- /package/dist/templates/default/{hooks → src/hooks}/use-reduced-motion.ts +0 -0
- /package/dist/templates/default/{hooks → src/hooks}/use-updates.ts +0 -0
- /package/dist/templates/default/{lib → src/lib}/a11y.ts +0 -0
- /package/dist/templates/default/{lib → src/lib}/app.ts +0 -0
- /package/dist/templates/default/{lib → src/lib}/auth-client.ts +0 -0
- /package/dist/templates/default/{lib → src/lib}/convex-auth.tsx +0 -0
- /package/dist/templates/default/{lib → src/lib}/env.ts +0 -0
- /package/dist/templates/default/{lib → src/lib}/haptics.ts +0 -0
- /package/dist/templates/default/{lib → src/lib}/storage.ts +0 -0
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
# Setup reference
|
|
2
2
|
|
|
3
|
-
Long-form companion to the README. Walks every phase `
|
|
3
|
+
Long-form companion to the README. Walks every phase `npx vexpo full` runs, the prompts you'll see, env-var alternatives for non-interactive runs (CI), recovery paths, and what state ends up where.
|
|
4
4
|
|
|
5
|
-
The orchestrator is the published [`vexpo` CLI](https://www.npmjs.com/package/vexpo) (run via `
|
|
5
|
+
The orchestrator is the published [`vexpo` CLI](https://www.npmjs.com/package/vexpo) (run via `npx vexpo lite` (dev) or `npx vexpo full` (TestFlight-ready)). `package.json` exposes every phase as a `npx vexpo <phase-name>` shortcut. State lives in `.setup-state.json` (gitignored), `.env.local` (gitignored), Convex deployment env (server-side), and EAS project env (per-environment, with secret-visibility entries powering the JWT rotation cron).
|
|
6
6
|
|
|
7
7
|
## TL;DR
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
git clone <repo-url> my-app
|
|
11
11
|
cd my-app
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
npm install
|
|
13
|
+
npx vexpo full
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
Plan ~30 minutes if your accounts already exist, ~60-90 if you're enrolling in the Apple Developer Program for the first time.
|
|
@@ -19,13 +19,13 @@ Plan ~30 minutes if your accounts already exist, ~60-90 if you're enrolling in t
|
|
|
19
19
|
|
|
20
20
|
vexpo has three entry points:
|
|
21
21
|
|
|
22
|
-
| Mode | Command
|
|
23
|
-
| ----------------- |
|
|
24
|
-
| Lite (dev) | `
|
|
25
|
-
| Full (TestFlight) | `
|
|
26
|
-
| Env sync | `
|
|
22
|
+
| Mode | Command | When |
|
|
23
|
+
| ----------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
24
|
+
| Lite (dev) | `npx vexpo lite` | Greenfield, dev-mode shortcut. Provisions Convex + Better Auth only. ~60 seconds to the iOS Simulator. |
|
|
25
|
+
| Full (TestFlight) | `npx vexpo full` | Greenfield, production setup. Walks signups, provisions Resend + Apple + EAS, signs JWTs, rebrands. |
|
|
26
|
+
| Env sync | `npx vexpo env push` | You already have all values in `.env.local` + `.env.prod`: just push them to Convex env and EAS env. No signups. |
|
|
27
27
|
|
|
28
|
-
`
|
|
28
|
+
`npx vexpo env push` reads `.env.local` (dev) and `.env.prod` or `.env.production` (prod), classifies each key by destination, and pushes to Convex env and EAS env. Per-file confirmation, fingerprint diff on overwrites, no provisioning. Secret-visibility EAS env vars (rotation cron) need `eas env:create --visibility secret` and the command prints the exact invocations when it sees those keys.
|
|
29
29
|
|
|
30
30
|
Pick `env push` when:
|
|
31
31
|
|
|
@@ -40,10 +40,10 @@ Pick `lite` for the 60-second simulator path with no Apple Developer account or
|
|
|
40
40
|
|
|
41
41
|
All modes accept `--dry-run`. Print every action the script would take, then exit without touching anything.
|
|
42
42
|
|
|
43
|
-
| Command
|
|
44
|
-
|
|
|
45
|
-
| `
|
|
46
|
-
| `
|
|
43
|
+
| Command | Output |
|
|
44
|
+
| ------------------------------ | ----------------------------------------------------------------------------------------------------- |
|
|
45
|
+
| `npx vexpo full --dry-run` | One block per phase: action (`run`/`skip (cached)`/`run (interactive)`), summary list of what it does |
|
|
46
|
+
| `npx vexpo env push --dry-run` | Per-source-file plan: every key, every destination, with `create`/`update`/`noop` status + diff |
|
|
47
47
|
|
|
48
48
|
Use it to:
|
|
49
49
|
|
|
@@ -54,24 +54,24 @@ Use it to:
|
|
|
54
54
|
|
|
55
55
|
`--dry-run` does not hit the network for verification, doesn't prompt for credentials, doesn't write state. It only reads what already exists locally and prints the plan.
|
|
56
56
|
|
|
57
|
-
## What `
|
|
58
|
-
|
|
59
|
-
The orchestrator runs the following phases in order, skipping any that are cached fresh in `.setup-state.json`. Each phase is also runnable standalone via `
|
|
60
|
-
|
|
61
|
-
| Phase | Command
|
|
62
|
-
| ----- |
|
|
63
|
-
| 0 | `
|
|
64
|
-
| 1 | `
|
|
65
|
-
| 2 | `
|
|
66
|
-
| 3 | `
|
|
67
|
-
| 4 | `
|
|
68
|
-
| 5 | `
|
|
69
|
-
| 6 | `
|
|
70
|
-
| 7 | `
|
|
71
|
-
| 7.5 | `
|
|
72
|
-
| 8 | `
|
|
73
|
-
| 9 | `
|
|
74
|
-
| 10 | `
|
|
57
|
+
## What `npx vexpo full` does
|
|
58
|
+
|
|
59
|
+
The orchestrator runs the following phases in order, skipping any that are cached fresh in `.setup-state.json`. Each phase is also runnable standalone via `npx vexpo <phase-name>`.
|
|
60
|
+
|
|
61
|
+
| Phase | Command | Layer | What it does |
|
|
62
|
+
| ----- | -------------------------------------- | ----- | -------------------------------------------------------------------------------------------------------------- |
|
|
63
|
+
| 0 | `npx vexpo accounts` | meta | Apple Developer / Expo / Convex / Resend signup confirmation |
|
|
64
|
+
| 1 | `npx vexpo rebrand` | ours | Replace template defaults (interactive, only if forking) |
|
|
65
|
+
| 2 | `npx vexpo convex` | ours | Provision Convex deployment, write `.env.local` |
|
|
66
|
+
| 3 | `npx vexpo better-auth` | ours | Generate `BETTER_AUTH_SECRET`, push `SITE_URL`, `APP_NAME` |
|
|
67
|
+
| 4 | `npx vexpo resend` | ours | Resend sending key + webhook (manual: DNS records at registrar) |
|
|
68
|
+
| 5 | `npx vexpo review-account` | ours | Seed App Review demo account on Convex |
|
|
69
|
+
| 6 | `npx vexpo full` (EAS phase) | eas | Thin wrapper: `eas init` + `eas env:push` from `.env.local` |
|
|
70
|
+
| 7 | `npx vexpo apple asc-key` | ours | Validate ASC API key against ASC `/v1/apps` (no upload) |
|
|
71
|
+
| 7.5 | `npx vexpo apple credentials` | ours | Wraps `eas credentials -p ios`. Pre-passes cached ASC creds, EAS auto-generates dist cert + profile + push key |
|
|
72
|
+
| 8 | `npx vexpo apple services-id` | ours | Attach SIWA capability via ASC API (manual: create the Services ID itself) |
|
|
73
|
+
| 9 | `npx vexpo apple jwt` | ours | Sign SIWA ES256 client_secret JWT, push to Convex env |
|
|
74
|
+
| 10 | `npx vexpo apple eas-rotation-secrets` | ours | Push the 5 EAS production secrets the JWT rotation cron needs |
|
|
75
75
|
|
|
76
76
|
Phases marked "manual" pause the CLI while you do something a Resend dashboard or Apple Developer portal can't be automated through. The CLI prints exact instructions and waits for you to press Enter.
|
|
77
77
|
|
|
@@ -85,7 +85,7 @@ eas submit -p ios --profile production # auto-creates the App Store record on
|
|
|
85
85
|
|
|
86
86
|
We don't reinvent any of those, `eas-cli` owns the iOS platform layer end-to-end. The EAS init phase is a thin wrapper that does `eas init` + `eas env:push` because the orchestrator wants one entry point for the env mirror. You can run those two commands directly and skip our wrapper entirely.
|
|
87
87
|
|
|
88
|
-
## Phase 0: Accounts (`
|
|
88
|
+
## Phase 0: Accounts (`npx vexpo accounts`)
|
|
89
89
|
|
|
90
90
|
Splits "things you bring" from "things vexpo signs you up for":
|
|
91
91
|
|
|
@@ -100,25 +100,25 @@ For both, the script asks "do you have this?" and prints links if you don't. If
|
|
|
100
100
|
|
|
101
101
|
### Instant signups (we walk you through)
|
|
102
102
|
|
|
103
|
-
| Account | Validation
|
|
104
|
-
| ------- |
|
|
105
|
-
| Convex | `~/.convex/config.json` exists after `
|
|
106
|
-
| Expo | `
|
|
107
|
-
| Resend | `RESEND_FULL_ACCESS_KEY` env probes 200 on `/api-keys`
|
|
103
|
+
| Account | Validation |
|
|
104
|
+
| ------- | --------------------------------------------------------- |
|
|
105
|
+
| Convex | `~/.convex/config.json` exists after `npx convex login` |
|
|
106
|
+
| Expo | `npx eas whoami` returns a username after `npx eas login` |
|
|
107
|
+
| Resend | `RESEND_FULL_ACCESS_KEY` env probes 200 on `/api-keys` |
|
|
108
108
|
|
|
109
109
|
Each opens the signup page (free-tier accounts, instant), then runs the corresponding CLI login. For Resend, paste a full-access key into the env once: `export RESEND_FULL_ACCESS_KEY=re_...` (the script also prompts interactively if absent). The key is never persisted by vexpo, it's used to provision a scoped sending key + webhook, then forgotten.
|
|
110
110
|
|
|
111
111
|
### What you'll be prompted for in later phases
|
|
112
112
|
|
|
113
|
-
| Phase
|
|
114
|
-
|
|
|
115
|
-
| `
|
|
116
|
-
| `
|
|
117
|
-
| DNS records
|
|
113
|
+
| Phase | What it needs from you |
|
|
114
|
+
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
115
|
+
| `npx vexpo apple asc-key` | App Store Connect API key (issuer ID, key ID, .p8), created at [appstoreconnect.apple.com/access/integrations/api](https://appstoreconnect.apple.com/access/integrations/api) |
|
|
116
|
+
| `npx vexpo apple jwt` | Sign In with Apple key (key ID, .p8), created at [developer.apple.com/account/resources/authkeys/list](https://developer.apple.com/account/resources/authkeys/list) |
|
|
117
|
+
| DNS records | Added by you at your registrar after `npx vexpo resend`. Resend's dashboard shows them and verifies. We don't automate this. |
|
|
118
118
|
|
|
119
|
-
Skip the whole phase: `
|
|
119
|
+
Skip the whole phase: `npx vexpo full # (accounts walk only runs with --new)`.
|
|
120
120
|
|
|
121
|
-
## Phase 1: Rebrand (`
|
|
121
|
+
## Phase 1: Rebrand (`npx vexpo rebrand`)
|
|
122
122
|
|
|
123
123
|
Interactive wizard for forks. Detects template defaults like `com.example.vexpo`, `slug: "vexpo"`, `scheme: "vexpo"`. Prompts for:
|
|
124
124
|
|
|
@@ -140,9 +140,9 @@ Edits:
|
|
|
140
140
|
|
|
141
141
|
Backups land in `.rebrand-backup/<timestamp>/` before any write. Idempotent: re-runs detect "already rebranded" via state and skip unless `--force` is passed.
|
|
142
142
|
|
|
143
|
-
Skip: `
|
|
143
|
+
Skip: `npx vexpo full --skip-rebrand`. Or pre-detect by reading from `.env.local` (`EXPO_PUBLIC_APP_BUNDLE_ID`). If it differs from `com.example.vexpo`, the orchestrator marks the step `cached`.
|
|
144
144
|
|
|
145
|
-
## Phase 2: Convex (`
|
|
145
|
+
## Phase 2: Convex (`npx vexpo convex`)
|
|
146
146
|
|
|
147
147
|
Provisions a fresh Convex deployment (or connects to an existing one). Writes:
|
|
148
148
|
|
|
@@ -151,15 +151,15 @@ Provisions a fresh Convex deployment (or connects to an existing one). Writes:
|
|
|
151
151
|
|
|
152
152
|
Prompts for the iOS bundle ID (reverse DNS, e.g. `com.yourname.myapp`) and your 10-character Apple Team ID (in Apple Developer → Membership). Both can be provided non-interactively via `EXPO_PUBLIC_APP_BUNDLE_ID` and `EXPO_PUBLIC_APPLE_TEAM_ID` env vars.
|
|
153
153
|
|
|
154
|
-
`--fresh` wipes `.env.local` and reprovisions a brand new deployment. `--local` runs against `
|
|
154
|
+
`--fresh` wipes `.env.local` and reprovisions a brand new deployment. `--local` runs against `npx convex dev --local` (self-hosted backend).
|
|
155
155
|
|
|
156
|
-
## Phase 3: Better Auth (`
|
|
156
|
+
## Phase 3: Better Auth (`npx vexpo better-auth`)
|
|
157
157
|
|
|
158
158
|
Generates a 32-byte base64 `BETTER_AUTH_SECRET`. Sets `SITE_URL`, `APP_NAME` on Convex. No prompts, no env required.
|
|
159
159
|
|
|
160
160
|
If `BETTER_AUTH_SECRET` is already set on Convex, the script preserves it.
|
|
161
161
|
|
|
162
|
-
## Phase 4: Resend (`
|
|
162
|
+
## Phase 4: Resend (`npx vexpo resend`)
|
|
163
163
|
|
|
164
164
|
Prompts once for a Resend full-access key (or reads `RESEND_FULL_ACCESS_KEY`). Picks a verified domain (or the first one if there's only one). Creates:
|
|
165
165
|
|
|
@@ -180,7 +180,7 @@ This is the one thing that gates real email sending. Until the domain is verifie
|
|
|
180
180
|
|
|
181
181
|
The webhook subscribes to 9 events: `email.sent`, `email.delivered`, `email.delivery_delayed`, `email.bounced`, `email.complained`, `email.failed`, `email.suppressed`, `email.opened`, `email.clicked`. The 4 actionable failure events (`bounced`, `complained`, `suppressed`, `failed`) tell you when a user's address is dead. `convex/email.ts` logs them with `console.warn` and you extend `handleEmailEvent` to flag the user account if you want to stop retrying. `opened` and `clicked` only fire when per-email tracking is enabled (we don't enable it by default, toggle on individual sends if you want it).
|
|
182
182
|
|
|
183
|
-
`
|
|
183
|
+
`npx vexpo doctor` confirms the webhook subscription includes all 4 actionable events. If you ever drop one accidentally, re-run `npx vexpo resend` to refresh.
|
|
184
184
|
|
|
185
185
|
### Sign In with Apple + Hide My Email (Apple Private Email Relay)
|
|
186
186
|
|
|
@@ -193,7 +193,7 @@ Resend authenticates via SPF + DKIM by default, which is what Apple wants. So on
|
|
|
193
193
|
|
|
194
194
|
Apple imposes a 100/day limit per relay address, but that's a per-user cap, not a per-app one.
|
|
195
195
|
|
|
196
|
-
## Phase 5: Review account (`
|
|
196
|
+
## Phase 5: Review account (`npx vexpo review-account`)
|
|
197
197
|
|
|
198
198
|
Reads `apple.review.demoUsername` / `demoPassword` from `store.config.json`. Creates the user via Better Auth's signup flow, then flips `emailVerified: true` directly via the adapter so Apple's reviewer doesn't see an OTP prompt.
|
|
199
199
|
|
|
@@ -201,13 +201,18 @@ Pass `--email` / `--password` to override the values from `store.config.json`. S
|
|
|
201
201
|
|
|
202
202
|
## Phase 6: EAS (auto, no standalone command, runs as part of `vexpo full`)
|
|
203
203
|
|
|
204
|
-
Runs `eas init` (creates the project, or links to an existing one) and writes `extra.eas.projectId` to `app.json`. Mirrors every `EXPO_PUBLIC_*` from `.env.local` to EAS
|
|
205
|
-
|
|
206
|
-
Pass `--skip-init` to only mirror env, `--skip-env` to only init.
|
|
204
|
+
Runs `eas init` (creates the project, or links to an existing one) and writes `extra.eas.projectId` to `app.json`. Mirrors every `EXPO_PUBLIC_*` from `.env.local` to the EAS `development` environment using `npx eas env:create --visibility plaintext`. Prod and preview values come from `.env.prod` via `vexpo env push`, which routes to `["production", "preview"]`.
|
|
207
205
|
|
|
208
206
|
After this, `expo prebuild` and `eas build` both find the right project + env. The `extra.eas.projectId` write also enables `app.config.ts → updates.url`.
|
|
209
207
|
|
|
210
|
-
|
|
208
|
+
The `fingerprint` runtime version policy hashes the resolved config, so `projectId` must resolve to the same value on your machine and on the EAS worker. Otherwise `extra.eas`/`updates.url`/`updates.enabled` diverge and the build fails at `CONFIGURE_EXPO_UPDATES`. Two ways to get that parity:
|
|
209
|
+
|
|
210
|
+
- **Commit `app.json`** (simplest, for a real app): after `eas init` writes `extra.eas.projectId`, commit `app.json`. EAS uploads it, so the worker reads the same projectId you do. No env var, nothing else.
|
|
211
|
+
- **Plaintext `EAS_PROJECT_ID` env var**: for a fork that keeps `app.json` a stub, or CI that can't commit a projectId (this is how this template repo itself builds). `npx eas env:create --name EAS_PROJECT_ID --value <uuid> --visibility plaintext --environment development --environment preview --environment production`. Plaintext vars are injected before the worker evaluates `app.config.ts`, so it resolves the same projectId. Don't `.gitignore` `app.json`, that strips the worker's copy and forces this path.
|
|
212
|
+
|
|
213
|
+
Either way the template needs no `fingerprint.config.js` and no `sourceSkips`.
|
|
214
|
+
|
|
215
|
+
## Phase 7: ASC API key (`npx vexpo apple asc-key`)
|
|
211
216
|
|
|
212
217
|
The App Store Connect API key is needed for `eas submit` (and for vexpo's Phase 8 Services ID provisioning). The first key has to be created in the ASC web UI, there's no bootstrap path because you can't authenticate the API without already having a key.
|
|
213
218
|
|
|
@@ -222,13 +227,13 @@ Then prompts for issuer ID, key ID, and `.p8` path. Validates by signing an ES25
|
|
|
222
227
|
|
|
223
228
|
Records `{issuerId, keyId, p8Path, validatedAt}` in `.setup-state.json`. The .p8 file itself stays where you put it, vexpo never copies it.
|
|
224
229
|
|
|
225
|
-
Env-var skip: `APPLE_ASC_ISSUER_ID=... APPLE_ASC_KEY_ID=... APPLE_ASC_P8_PATH=/path/to/AuthKey_X.p8
|
|
230
|
+
Env-var skip: `APPLE_ASC_ISSUER_ID=... APPLE_ASC_KEY_ID=... APPLE_ASC_P8_PATH=/path/to/AuthKey_X.p8 npx vexpo apple asc-key`.
|
|
226
231
|
|
|
227
|
-
Re-validate cached creds without re-prompting: `
|
|
232
|
+
Re-validate cached creds without re-prompting: `npx vexpo apple asc-key --revalidate`.
|
|
228
233
|
|
|
229
|
-
## Phase 7.5: EAS iOS credentials (`
|
|
234
|
+
## Phase 7.5: EAS iOS credentials (`npx vexpo apple credentials`)
|
|
230
235
|
|
|
231
|
-
`
|
|
236
|
+
`npx vexpo apple credentials` wraps the eas-cli wizard. With our env-var pre-passing (`EXPO_ASC_API_KEY_PATH`, `EXPO_ASC_KEY_ID`, `EXPO_ASC_ISSUER_ID`), the wizard skips Apple Developer login entirely. You walk through ~6 Y/n prompts (each "Generate new" or "Use existing"), each takes 1-2 seconds. Apple's API does the actual work server-side.
|
|
232
237
|
|
|
233
238
|
What the wizard sets up:
|
|
234
239
|
|
|
@@ -239,11 +244,11 @@ What the wizard sets up:
|
|
|
239
244
|
|
|
240
245
|
All credentials are stored encrypted on EAS infrastructure. Subsequent `eas build` + `eas submit` runs are non-interactive.
|
|
241
246
|
|
|
242
|
-
Standalone: `
|
|
247
|
+
Standalone: `npx vexpo apple credentials [-e <profile>]`.
|
|
243
248
|
|
|
244
|
-
Bypass entirely: `
|
|
249
|
+
Bypass entirely: `npx eas credentials -p ios` runs the wizard directly. The vexpo wrapper just pre-passes the cached ASC creds.
|
|
245
250
|
|
|
246
|
-
## Phase 8: Sign In with Apple Services ID (`
|
|
251
|
+
## Phase 8: Sign In with Apple Services ID (`npx vexpo apple services-id`)
|
|
247
252
|
|
|
248
253
|
The Services ID is a separate `BundleId` resource (with `platform: "SERVICES"`) used by your backend to identify the OAuth client to Apple. EAS doesn't manage these. The regular bundle ID it provisions is for the iOS app itself, not the OAuth backend.
|
|
249
254
|
|
|
@@ -282,7 +287,7 @@ Apple may ask you to upload an `apple-developer-domain-association.txt` to verif
|
|
|
282
287
|
|
|
283
288
|
After saving, return to the CLI and press Enter. The CLI re-lists, finds the Services ID, attaches the capability via API, and continues to Phase 9.
|
|
284
289
|
|
|
285
|
-
## Phase 9: Apple Sign In JWT (`
|
|
290
|
+
## Phase 9: Apple Sign In JWT (`npx vexpo apple jwt`)
|
|
286
291
|
|
|
287
292
|
Signs an ES256 `client_secret` JWT (180-day expiry, Apple's max) from a Sign In with Apple `.p8` file. Writes:
|
|
288
293
|
|
|
@@ -299,15 +304,15 @@ Prompts:
|
|
|
299
304
|
|
|
300
305
|
Records `{servicesId, teamId, keyId, p8Path, signedAt, expiresAt}` in state.
|
|
301
306
|
|
|
302
|
-
Env-var skip: `APPLE_SERVICES_ID=... APPLE_TEAM_ID=... APPLE_KEY_ID=... APPLE_P8_PATH=/path/to/AuthKey_X.p8
|
|
307
|
+
Env-var skip: `APPLE_SERVICES_ID=... APPLE_TEAM_ID=... APPLE_KEY_ID=... APPLE_P8_PATH=/path/to/AuthKey_X.p8 npx vexpo apple jwt`.
|
|
303
308
|
|
|
304
|
-
Rotate without re-prompting IDs: `
|
|
309
|
+
Rotate without re-prompting IDs: `npx vexpo apple jwt --rotate`.
|
|
305
310
|
|
|
306
311
|
### JWT rotation
|
|
307
312
|
|
|
308
|
-
Apple caps `client_secret` JWTs at 180 days. The `.eas/workflows/rotate-apple-jwt.yml` cron fires every 90 days, signs a fresh JWT, and pushes it to your prod Convex deployment. Runs on EAS infrastructure with all secrets read from EAS env (production, secret visibility), no GitHub repo secrets needed. Set up once, never think about it again. Manual fallback: `
|
|
313
|
+
Apple caps `client_secret` JWTs at 180 days. The `.eas/workflows/rotate-apple-jwt.yml` cron fires every 90 days, signs a fresh JWT, and pushes it to your prod Convex deployment. Runs on EAS infrastructure with all secrets read from EAS env (production, secret visibility), no GitHub repo secrets needed. Set up once, never think about it again. Manual fallback: `npx vexpo apple jwt --rotate`.
|
|
309
314
|
|
|
310
|
-
## Phase 10: EAS rotation secrets (`
|
|
315
|
+
## Phase 10: EAS rotation secrets (`npx vexpo apple eas-rotation-secrets`)
|
|
311
316
|
|
|
312
317
|
Pushes the 5 EAS production secrets the rotation cron needs. The orchestrator runs this last. It's also a standalone command.
|
|
313
318
|
|
|
@@ -322,8 +327,8 @@ Pushes the 5 EAS production secrets the rotation cron needs. The orchestrator ru
|
|
|
322
327
|
The 4 Apple secrets get pulled automatically. `CONVEX_DEPLOY_KEY` is prompted because the CLI can't generate Convex deploy keys, you create it once in the Convex dashboard and paste it back.
|
|
323
328
|
|
|
324
329
|
```bash
|
|
325
|
-
|
|
326
|
-
|
|
330
|
+
npx vexpo apple eas-rotation-secrets # interactive
|
|
331
|
+
npx vexpo apple eas-rotation-secrets --force # overwrite existing values
|
|
327
332
|
```
|
|
328
333
|
|
|
329
334
|
If you'd rather run the raw `eas env:create` calls yourself:
|
|
@@ -336,37 +341,37 @@ eas env:create --name APPLE_SERVICES_ID --value <value> --
|
|
|
336
341
|
eas env:create --name CONVEX_DEPLOY_KEY --value <prod-deploy-key> --environment production --visibility secret
|
|
337
342
|
```
|
|
338
343
|
|
|
339
|
-
`
|
|
344
|
+
`npx vexpo doctor --channel prod` lists which of the 5 are present (names appear, values stay opaque since they're secret visibility).
|
|
340
345
|
|
|
341
|
-
## What `
|
|
346
|
+
## What `npx vexpo full` does NOT do
|
|
342
347
|
|
|
343
348
|
These are explicit non-goals, EAS or third parties already handle them well:
|
|
344
349
|
|
|
345
|
-
- **iOS distribution cert / provisioning profile / push notification key (.p8)** are EAS-owned. We wrap the `eas credentials -p ios` wizard via `
|
|
350
|
+
- **iOS distribution cert / provisioning profile / push notification key (.p8)** are EAS-owned. We wrap the `eas credentials -p ios` wizard via `npx vexpo apple credentials` so the orchestrator records that it ran, but eas-cli does the work.
|
|
346
351
|
- **iOS bundle ID for the app**, EAS auto-creates on first `eas credentials -p ios` if it doesn't exist.
|
|
347
352
|
- **iOS capability sync**, EAS auto-syncs from `ios.entitlements` (which `app.config.ts` populates from `usesAppleSignIn: true`, `expo-notifications`, `associatedDomains`, etc.) on every `eas build`.
|
|
348
353
|
- **App Store Connect app record**, `eas submit` auto-creates on first run from `app.config.ts → name` + `package.json → name`.
|
|
349
354
|
- **Apple Developer account creation**, manual signup, $99/yr, identity verification, 2FA.
|
|
350
355
|
|
|
351
|
-
## Lite-mode env sync (`
|
|
356
|
+
## Lite-mode env sync (`npx vexpo env push`)
|
|
352
357
|
|
|
353
358
|
```bash
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
+
npx vexpo env push # interactive (per-file confirm)
|
|
360
|
+
npx vexpo env push --force # overwrite without prompting
|
|
361
|
+
npx vexpo env push --dry-run # show plan, don't apply
|
|
362
|
+
npx vexpo env push --local-file foo # override .env.local path
|
|
363
|
+
npx vexpo env push --prod-file foo # override .env.prod path
|
|
359
364
|
```
|
|
360
365
|
|
|
361
366
|
Lite mode reads source files and pushes values to remote destinations. Zero provisioning, no API calls beyond `convex env set --from-file` and `eas env:push --path`.
|
|
362
367
|
|
|
363
368
|
### Source files
|
|
364
369
|
|
|
365
|
-
| File | Channel | Default destinations
|
|
366
|
-
| ----------------- | ------- |
|
|
367
|
-
| `.env.local` | dev | Convex dev env, EAS development env
|
|
368
|
-
| `.env.prod` | prod | Convex prod env, EAS production+preview
|
|
369
|
-
| `.env.production` | prod | (used if `.env.prod` is absent)
|
|
370
|
+
| File | Channel | Default destinations |
|
|
371
|
+
| ----------------- | ------- | --------------------------------------- |
|
|
372
|
+
| `.env.local` | dev | Convex dev env, EAS development env |
|
|
373
|
+
| `.env.prod` | prod | Convex prod env, EAS production+preview |
|
|
374
|
+
| `.env.production` | prod | (used if `.env.prod` is absent) |
|
|
370
375
|
|
|
371
376
|
Override paths with `--local-file` / `--prod-file`. Both files are optional, lite mode runs with whatever it finds.
|
|
372
377
|
|
|
@@ -374,21 +379,23 @@ Override paths with `--local-file` / `--prod-file`. Both files are optional, lit
|
|
|
374
379
|
|
|
375
380
|
Each known env-var has a fixed routing in `vexpo`'s env-files module ([source](https://github.com/ramonclaudio/vexpo/blob/main/packages/vexpo/src/lib/env-files.ts)):
|
|
376
381
|
|
|
377
|
-
| Source key | Convex (dev) | Convex (prod) | EAS env |
|
|
378
|
-
| --------------------------------------- | ----------------- | ----------------- | --------------------- |
|
|
379
|
-
| `EXPO_PUBLIC_*` | n/a | n/a | dev (or prod+preview) |
|
|
380
|
-
| `BETTER_AUTH_SECRET` | dev | prod | n/a |
|
|
381
|
-
| `RESEND_API_KEY` | dev | prod | n/a |
|
|
382
|
-
| `RESEND_WEBHOOK_SECRET` | dev | prod | n/a |
|
|
383
|
-
| `RESEND_TEST_MODE`, `EMAIL_FROM` | dev | prod | n/a |
|
|
384
|
-
| `APP_NAME`, `SITE_URL`, `APP_BUNDLE_ID` | dev | prod | n/a |
|
|
385
|
-
| `APPLE_CLIENT_ID` | dev | prod | n/a |
|
|
386
|
-
| `APPLE_CLIENT_SECRET` | dev | prod | n/a |
|
|
387
|
-
| `APPLE_TEAM_ID` | dev | prod | n/a |
|
|
388
|
-
| `APPLE_KEY_ID` | dev | prod | n/a |
|
|
389
|
-
| `APPLE_SERVICES_ID` | `APPLE_CLIENT_ID` | `APPLE_CLIENT_ID` | n/a |
|
|
390
|
-
| `APPLE_P8_PRIVATE_KEY` | n/a | n/a | n/a |
|
|
391
|
-
| `CONVEX_DEPLOY_KEY` | n/a | n/a | n/a |
|
|
382
|
+
| Source key | Convex (dev) | Convex (prod) | EAS env |
|
|
383
|
+
| --------------------------------------- | ----------------- | ----------------- | --------------------- |
|
|
384
|
+
| `EXPO_PUBLIC_*` | n/a | n/a | dev (or prod+preview) |
|
|
385
|
+
| `BETTER_AUTH_SECRET` | dev | prod | n/a |
|
|
386
|
+
| `RESEND_API_KEY` | dev | prod | n/a |
|
|
387
|
+
| `RESEND_WEBHOOK_SECRET` | dev | prod | n/a |
|
|
388
|
+
| `RESEND_TEST_MODE`, `EMAIL_FROM` | dev | prod | n/a |
|
|
389
|
+
| `APP_NAME`, `SITE_URL`, `APP_BUNDLE_ID` | dev | prod | n/a |
|
|
390
|
+
| `APPLE_CLIENT_ID` | dev | prod | n/a |
|
|
391
|
+
| `APPLE_CLIENT_SECRET` | dev | prod | n/a |
|
|
392
|
+
| `APPLE_TEAM_ID` | dev | prod | n/a |
|
|
393
|
+
| `APPLE_KEY_ID` | dev | prod | n/a |
|
|
394
|
+
| `APPLE_SERVICES_ID` | `APPLE_CLIENT_ID` | `APPLE_CLIENT_ID` | n/a |
|
|
395
|
+
| `APPLE_P8_PRIVATE_KEY` | n/a | n/a | n/a |
|
|
396
|
+
| `CONVEX_DEPLOY_KEY` | n/a | n/a | n/a |
|
|
397
|
+
|
|
398
|
+
The five rotation-cron secrets (`APPLE_TEAM_ID`, `APPLE_KEY_ID`, `APPLE_SERVICES_ID`, `APPLE_P8_PRIVATE_KEY`, `CONVEX_DEPLOY_KEY`) live in EAS env at `secret` visibility but `vexpo env push` does not write them — run `npx vexpo apple eas-rotation-secrets` once to push the full set.
|
|
392
399
|
|
|
393
400
|
Notes:
|
|
394
401
|
|
|
@@ -415,7 +422,7 @@ Secret-visibility EAS env vars (`APPLE_P8_PRIVATE_KEY`, `CONVEX_DEPLOY_KEY`) are
|
|
|
415
422
|
### When NOT to use lite mode
|
|
416
423
|
|
|
417
424
|
- You don't have all the values yet. Lite mode doesn't generate `BETTER_AUTH_SECRET`, doesn't sign Apple JWTs, doesn't create Resend keys. Use full mode for first setup.
|
|
418
|
-
- You haven't run `eas init` yet. Lite mode pushes to EAS env but won't init the project, run full mode or `
|
|
425
|
+
- You haven't run `eas init` yet. Lite mode pushes to EAS env but won't init the project, run full mode or `npx eas init && npx eas env:push --path .env.local` once first.
|
|
419
426
|
|
|
420
427
|
### Example flow
|
|
421
428
|
|
|
@@ -423,27 +430,27 @@ Move a working app to a new machine:
|
|
|
423
430
|
|
|
424
431
|
```bash
|
|
425
432
|
# On the old machine:
|
|
426
|
-
|
|
427
|
-
|
|
433
|
+
npx convex env list > /tmp/dev-env
|
|
434
|
+
npx convex env list --prod > /tmp/prod-env
|
|
428
435
|
# Edit each into .env.local / .env.prod with the values you want carried over.
|
|
429
436
|
|
|
430
437
|
# On the new machine:
|
|
431
438
|
git clone <repo>
|
|
432
439
|
cd <repo>
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
440
|
+
npm install
|
|
441
|
+
npx vexpo env push # syncs from those files
|
|
442
|
+
npx eas credentials -p ios # re-uploads cert / profile / keys
|
|
443
|
+
npm run convex:dev
|
|
444
|
+
npm run ios
|
|
438
445
|
```
|
|
439
446
|
|
|
440
|
-
## Verification (`
|
|
447
|
+
## Verification (`npx vexpo doctor`)
|
|
441
448
|
|
|
442
449
|
```bash
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
450
|
+
npx vexpo doctor # verify dev (default)
|
|
451
|
+
npx vexpo doctor --channel prod # verify prod (Convex --prod env)
|
|
452
|
+
npx vexpo doctor --json # machine-readable output
|
|
453
|
+
npx vexpo doctor --strict # exit non-zero on warnings
|
|
447
454
|
```
|
|
448
455
|
|
|
449
456
|
Runs a battery of checks that auth-test each credential and cross-reference the values across `.env.local`, Convex env, EAS env, GitHub secrets, and `app.config.ts`. Lite mode runs the same battery automatically after sync (skip with `--no-verify`). Results are grouped by category:
|
|
@@ -464,38 +471,38 @@ Each check has a severity:
|
|
|
464
471
|
- `fail`, broken (JWT expired, bundle ID mismatch between local and Convex, Resend API key rejected).
|
|
465
472
|
- `skip`, can't be checked (no `.env.prod`, ASC creds not cached, EAS not signed in).
|
|
466
473
|
|
|
467
|
-
Exit status: `0` for ok+warn, `1` if any fail, `1` for warn under `--strict`. Run after every `
|
|
474
|
+
Exit status: `0` for ok+warn, `1` if any fail, `1` for warn under `--strict`. Run after every `npx vexpo env push` to confirm nothing drifted, in CI to catch credential rotation issues, or after a `npx vexpo apple jwt --rotate` to confirm the new JWT is signed correctly.
|
|
468
475
|
|
|
469
476
|
The check that catches the most real-world bugs: `apple/jwt-iss-matches`. Apple JWTs are easy to sign with the wrong Team ID, happens when you reuse a `.p8` from another project. Verify catches it instantly.
|
|
470
477
|
|
|
471
478
|
## Iterating: post-setup commands
|
|
472
479
|
|
|
473
480
|
```bash
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
481
|
+
npm run convex:dev # T1: Convex functions
|
|
482
|
+
npm run ios # T2: prebuild + simulator
|
|
483
|
+
|
|
484
|
+
npx eas build -p ios --profile production # production iOS build
|
|
485
|
+
npx eas submit -p ios --profile production # auto-creates the App Store record on first run
|
|
486
|
+
npm run eas:tf # build + auto-submit to TestFlight
|
|
487
|
+
npx eas metadata:push # push store.config.json to App Store Connect
|
|
488
|
+
npx eas credentials -p ios # manage iOS dist cert / profile / keys
|
|
482
489
|
```
|
|
483
490
|
|
|
484
491
|
EAS Workflows (`.eas/workflows/`) automate these for you on push to `main`, push tag `v*`, push to `beta/*`, on PR, and on `eas workflow:run`.
|
|
485
492
|
|
|
486
493
|
## Recovery: rotating things
|
|
487
494
|
|
|
488
|
-
| Thing | Command
|
|
489
|
-
| -------------------------- |
|
|
490
|
-
| Convex deployment | `
|
|
491
|
-
| Better Auth secret | `
|
|
492
|
-
| Resend key + webhook | `
|
|
493
|
-
| Apple Sign In JWT (manual) | `
|
|
494
|
-
| Apple Sign In JWT (auto) | EAS Workflows → `rotate-apple-jwt` → Run
|
|
495
|
-
| ASC API key | `
|
|
496
|
-
| EAS env mirror | `
|
|
497
|
-
| EAS rotation secrets | `
|
|
498
|
-
| State cache | `trash .setup-state.json`
|
|
495
|
+
| Thing | Command |
|
|
496
|
+
| -------------------------- | ---------------------------------------------------------------- |
|
|
497
|
+
| Convex deployment | `npx vexpo full --fresh` |
|
|
498
|
+
| Better Auth secret | `npx vexpo better-auth --force` |
|
|
499
|
+
| Resend key + webhook | `npx vexpo resend` |
|
|
500
|
+
| Apple Sign In JWT (manual) | `npx vexpo apple jwt --rotate` |
|
|
501
|
+
| Apple Sign In JWT (auto) | EAS Workflows → `rotate-apple-jwt` → Run |
|
|
502
|
+
| ASC API key | `npx vexpo apple asc-key` (re-runs validation) |
|
|
503
|
+
| EAS env mirror | `npx eas init && npx eas env:push --path .env.local --skip-init` |
|
|
504
|
+
| EAS rotation secrets | `npx vexpo apple eas-rotation-secrets --force` |
|
|
505
|
+
| State cache | `trash .setup-state.json` |
|
|
499
506
|
|
|
500
507
|
## Recovery: things break
|
|
501
508
|
|
|
@@ -503,20 +510,20 @@ EAS Workflows (`.eas/workflows/`) automate these for you on push to `main`, push
|
|
|
503
510
|
|
|
504
511
|
```
|
|
505
512
|
trash .setup-state.json
|
|
506
|
-
|
|
513
|
+
npx vexpo full --no-state # full live re-probe
|
|
507
514
|
```
|
|
508
515
|
|
|
509
516
|
`.env.local` deleted, but Convex deployment still exists:
|
|
510
517
|
|
|
511
518
|
```
|
|
512
|
-
|
|
519
|
+
npx eas env:pull --environment development # pulls EXPO_PUBLIC_*
|
|
513
520
|
echo "CONVEX_DEPLOYMENT=dev:happy-frog-123" >> .env.local
|
|
514
521
|
```
|
|
515
522
|
|
|
516
|
-
`
|
|
523
|
+
`npm run ios` fails with provisioning profile errors:
|
|
517
524
|
|
|
518
525
|
```
|
|
519
|
-
|
|
526
|
+
npx eas credentials -p ios # interactive wizard, regenerate cert/profile
|
|
520
527
|
```
|
|
521
528
|
|
|
522
529
|
Resend domain unverified or DNS records changed:
|
|
@@ -526,8 +533,8 @@ Open `https://resend.com/domains/<id>`. Resend shows what's missing. Add the rec
|
|
|
526
533
|
ASC API key revoked or replaced:
|
|
527
534
|
|
|
528
535
|
```
|
|
529
|
-
|
|
530
|
-
|
|
536
|
+
npx vexpo apple asc-key # validates cached, prompts for new on failure
|
|
537
|
+
npx eas credentials -p ios # re-upload to EAS
|
|
531
538
|
```
|
|
532
539
|
|
|
533
540
|
## CI
|
|
@@ -535,8 +542,8 @@ bunx eas credentials -p ios # re-upload to EAS
|
|
|
535
542
|
Use `--no-state` to ignore the local state cache. Provide every interactive value via env. The orchestrator runs in non-TTY mode and skips any step that would prompt without env-var fallbacks.
|
|
536
543
|
|
|
537
544
|
```yaml
|
|
538
|
-
- run:
|
|
539
|
-
- run:
|
|
545
|
+
- run: npm install
|
|
546
|
+
- run: npx vexpo full --no-state --skip-rebrand
|
|
540
547
|
env:
|
|
541
548
|
EXPO_PUBLIC_APP_BUNDLE_ID: com.yourname.myapp
|
|
542
549
|
EXPO_PUBLIC_APPLE_TEAM_ID: ABCDE12345
|
|
@@ -553,16 +560,16 @@ Use `--no-state` to ignore the local state cache. Provide every interactive valu
|
|
|
553
560
|
|
|
554
561
|
## Files
|
|
555
562
|
|
|
556
|
-
| Path
|
|
557
|
-
|
|
|
558
|
-
| `.env.local`
|
|
559
|
-
| `.setup-state.json`
|
|
560
|
-
| Convex env (`
|
|
561
|
-
| EAS env (`
|
|
562
|
-
| `app.config.ts`
|
|
563
|
-
| `app.json`
|
|
564
|
-
| `store.config.json`
|
|
565
|
-
| `package.json`
|
|
563
|
+
| Path | Purpose | Source of truth |
|
|
564
|
+
| ---------------------------------- | ------------------------------------------------------ | --------------------------------------------------------------------------------- |
|
|
565
|
+
| `.env.local` | Public env (build-time + Convex URL) | written by setup |
|
|
566
|
+
| `.setup-state.json` | Per-step verifyAt cache (gitignored) | written by setup, read by orchestrator |
|
|
567
|
+
| Convex env (`npx convex env list`) | Server-side secrets | written by setup |
|
|
568
|
+
| EAS env (`npx eas env:list`) | Build-time env per environment + rotation cron secrets | written by the EAS phase of `vexpo full` + `npx vexpo apple eas-rotation-secrets` |
|
|
569
|
+
| `app.config.ts` | Expo app config (reads `.env.local`) | edited by rebrand |
|
|
570
|
+
| `app.json` | Static `eas.projectId` | written by `eas init` |
|
|
571
|
+
| `store.config.json` | App Store metadata + review contact | edited by rebrand, gitignored |
|
|
572
|
+
| `package.json` | Project metadata | edited by rebrand |
|
|
566
573
|
|
|
567
574
|
## State schema
|
|
568
575
|
|
|
@@ -605,8 +612,8 @@ Atomic writes via `tmp + rename`. Schema mismatches fail-loud, `setup` will refu
|
|
|
605
612
|
|
|
606
613
|
| Class | Lives in | Rotates |
|
|
607
614
|
| --------------------- | -------------------------------------------------------------- | --------------------------------------------------------- |
|
|
608
|
-
| `BETTER_AUTH_SECRET` | Convex env | rotate via `
|
|
609
|
-
| Resend sending key | Convex env (`RESEND_API_KEY`) | `
|
|
615
|
+
| `BETTER_AUTH_SECRET` | Convex env | rotate via `npx vexpo better-auth --force` |
|
|
616
|
+
| Resend sending key | Convex env (`RESEND_API_KEY`) | `npx vexpo resend` deletes the named key + recreates |
|
|
610
617
|
| Resend webhook secret | Convex env (`RESEND_WEBHOOK_SECRET`) | rotated alongside the key |
|
|
611
618
|
| Apple Sign In JWT | Convex env (`APPLE_CLIENT_SECRET`) | 180-day max, auto-rotated by EAS Workflows cron every 90d |
|
|
612
619
|
| Apple Sign In `.p8` | EAS env `APPLE_P8_PRIVATE_KEY` (secret visibility, production) | rotate the key in Apple Developer Console |
|