anhedral 0.1.4 → 0.1.6
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 +31 -9
- package/dist/dependencies.js +2 -4
- package/dist/scaffold.js +344 -37
- package/dist/templates/backend.js +4 -49
- package/dist/templates/extension.js +7 -4
- package/dist/templates/frontend.js +59 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -51,7 +51,7 @@ anhedral init
|
|
|
51
51
|
- RevenueCat + Stripe
|
|
52
52
|
- Vercel deploy
|
|
53
53
|
|
|
54
|
-
The scaffold is a pnpm workspace with `
|
|
54
|
+
The scaffold is a pnpm workspace with top-level `Frontend`, `Backend`, `Extension`, and `packages/*` folders. Shared code such as the Drizzle schema lives under `packages/db` so apps do not need separate database schemas.
|
|
55
55
|
|
|
56
56
|
Each scaffold writes a `stack.json` file with the selected architecture, generated outputs, and the verified dependency manifest used for that run.
|
|
57
57
|
|
|
@@ -59,26 +59,48 @@ Each scaffold writes a `stack.json` file with the selected architecture, generat
|
|
|
59
59
|
|
|
60
60
|
The generated workspace is one Git repository with two Vercel projects:
|
|
61
61
|
|
|
62
|
-
- `
|
|
63
|
-
- `
|
|
62
|
+
- `Frontend`: Expo web build. Build command: `pnpm build:web`. Output directory: `dist`.
|
|
63
|
+
- `Backend`: Fastify API. Build command: `pnpm build`. Vercel detects `src/index.ts` as the Fastify entrypoint.
|
|
64
64
|
|
|
65
|
-
The `
|
|
65
|
+
The `Frontend` source is also the EAS source for iOS and Android builds; Vercel only deploys its web export.
|
|
66
66
|
|
|
67
67
|
Import the same repository twice in Vercel and set each project Root Directory to the app path above. Enable source access outside the app root so `packages/*` workspace dependencies are available during builds.
|
|
68
68
|
|
|
69
69
|
Vercel deployments are Git-driven. After both projects are connected, pushes and pull requests deploy the Expo web app and Fastify API automatically from GitHub commits.
|
|
70
70
|
|
|
71
|
+
Generated projects also include `PRODUCTION.md` with the provider checklist and these root verification scripts:
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
pnpm verify
|
|
75
|
+
pnpm verify:frontend
|
|
76
|
+
pnpm verify:backend
|
|
77
|
+
pnpm verify:extension
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Generated Provider Setup
|
|
81
|
+
|
|
82
|
+
New projects ship with placeholder env values so the repository can be inspected before accounts are provisioned. A production UI requires real keys from each provider. The generated `README.md` and `PRODUCTION.md` now include setup links and exact env placement for:
|
|
83
|
+
|
|
84
|
+
- Neon database: `DATABASE_URL`
|
|
85
|
+
- Clerk auth: `EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY`, `CLERK_PUBLISHABLE_KEY`, `CLERK_SECRET_KEY`, `VITE_CLERK_PUBLISHABLE_KEY`
|
|
86
|
+
- RevenueCat + Stripe: `EXPO_PUBLIC_RC_*`, `RC_SECRET_API_KEY`, `RC_WEBHOOK_SECRET`
|
|
87
|
+
- Cloudflare R2/CDN: `R2_ACCOUNT_ID`, `R2_ACCESS_KEY_ID`, `R2_SECRET_ACCESS_KEY`, `R2_BUCKET`
|
|
88
|
+
- Vercel: two projects from the same Git repo, rooted at `Frontend` and `Backend`
|
|
89
|
+
- EAS: native iOS and Android builds from `Frontend`
|
|
90
|
+
- Chrome Web Store: WXT ZIP upload from `Extension/.output`
|
|
91
|
+
|
|
71
92
|
## Native App Deployment
|
|
72
93
|
|
|
73
|
-
Use `
|
|
94
|
+
Use `Frontend` as the Expo project root for iOS and Android. Vercel only deploys the web export from this app; native builds go through EAS.
|
|
74
95
|
|
|
75
96
|
```sh
|
|
76
|
-
cd
|
|
97
|
+
cd Frontend
|
|
77
98
|
pnpm dlx eas-cli@latest login
|
|
78
99
|
pnpm dlx eas-cli@latest init
|
|
79
|
-
pnpm dlx eas-cli@latest build:configure
|
|
80
100
|
```
|
|
81
101
|
|
|
102
|
+
The scaffold generates `Frontend/eas.json` with `development`, `preview`, and `production` profiles.
|
|
103
|
+
|
|
82
104
|
Build commands:
|
|
83
105
|
|
|
84
106
|
```sh
|
|
@@ -123,10 +145,10 @@ pnpm dlx eas-cli@latest credentials
|
|
|
123
145
|
|
|
124
146
|
## Extension Release
|
|
125
147
|
|
|
126
|
-
The WXT extension is built from `
|
|
148
|
+
The WXT extension is built from `Extension`. It is not deployed by Vercel.
|
|
127
149
|
|
|
128
150
|
```sh
|
|
129
|
-
cd
|
|
151
|
+
cd Extension
|
|
130
152
|
pnpm build
|
|
131
153
|
pnpm zip
|
|
132
154
|
```
|
package/dist/dependencies.js
CHANGED
|
@@ -91,8 +91,6 @@ export const BACKEND_DEPENDENCIES = {
|
|
|
91
91
|
vitest: '4.0.16',
|
|
92
92
|
// renovate: datasource=npm depName=@vitest/coverage-v8
|
|
93
93
|
'@vitest/coverage-v8': '4.0.16',
|
|
94
|
-
// renovate: datasource=npm depName=@vercel/node
|
|
95
|
-
'@vercel/node': '5.5.13',
|
|
96
94
|
// renovate: datasource=npm depName=eslint
|
|
97
95
|
eslint: '9.39.2',
|
|
98
96
|
// renovate: datasource=npm depName=@eslint/js
|
|
@@ -144,8 +142,8 @@ export const EXTENSION_DEPENDENCIES = {
|
|
|
144
142
|
};
|
|
145
143
|
export const FRONTEND_ADDON_DEPENDENCIES = {
|
|
146
144
|
'@anhedral/api-client': 'workspace:*',
|
|
147
|
-
// renovate: datasource=npm depName=@clerk/
|
|
148
|
-
'@clerk/
|
|
145
|
+
// renovate: datasource=npm depName=@clerk/expo
|
|
146
|
+
'@clerk/expo': '3.2.11',
|
|
149
147
|
// renovate: datasource=npm depName=react-native-purchases
|
|
150
148
|
'react-native-purchases': '10.1.1',
|
|
151
149
|
// renovate: datasource=npm depName=react-native-purchases-ui
|
package/dist/scaffold.js
CHANGED
|
@@ -147,25 +147,71 @@ ${generatedPaths}
|
|
|
147
147
|
\`\`\`bash
|
|
148
148
|
pnpm install
|
|
149
149
|
cp .env.example .env
|
|
150
|
-
#
|
|
151
|
-
cp
|
|
150
|
+
# Frontend/.env is generated for local Expo development
|
|
151
|
+
cp Extension/.env.example Extension/.env
|
|
152
152
|
pnpm db:generate
|
|
153
153
|
pnpm db:migrate
|
|
154
|
-
pnpm
|
|
154
|
+
pnpm verify
|
|
155
|
+
pnpm dev:backend
|
|
155
156
|
pnpm dev:frontend
|
|
156
157
|
pnpm dev:extension
|
|
157
158
|
\`\`\`
|
|
158
159
|
|
|
159
|
-
|
|
160
|
+
The generated env files intentionally contain placeholder provider values. That is enough to inspect the project structure and run backend smoke tests, but the full UI needs real provider keys before auth, billing, uploads, and extension sign-in behave like production.
|
|
161
|
+
|
|
162
|
+
For a provider-free backend smoke test, keep \`ANHEDRAL_DEMO=true\` in \`Backend/.env\`. Demo mode returns a signed-in sample user and active subscription responses without Clerk, RevenueCat, or Stripe credentials. It is for local development only.
|
|
160
163
|
|
|
161
164
|
## Provider Setup
|
|
162
165
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
-
|
|
167
|
-
-
|
|
168
|
-
-
|
|
166
|
+
Use this order so each service has the domains and callback URLs it needs:
|
|
167
|
+
|
|
168
|
+
1. Neon database
|
|
169
|
+
- Create a Neon project and database: https://neon.com/docs/get-started-with-neon/connect-neon
|
|
170
|
+
- Put the pooled connection string in \`DATABASE_URL\` in \`.env\`, \`Backend/.env\`, and the Backend Vercel project.
|
|
171
|
+
- Run \`pnpm db:generate\` and \`pnpm db:migrate\` after \`DATABASE_URL\` is real.
|
|
172
|
+
|
|
173
|
+
2. Clerk auth
|
|
174
|
+
- Expo setup: https://docs.expo.dev/guides/using-clerk/
|
|
175
|
+
- Clerk Expo quickstart: https://clerk.com/docs/quickstarts/get-started-with-expo
|
|
176
|
+
- Set \`EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY\` in \`Frontend/.env\`, Vercel Frontend, and EAS.
|
|
177
|
+
- Set \`CLERK_PUBLISHABLE_KEY\` and \`CLERK_SECRET_KEY\` in \`Backend/.env\` and Vercel Backend.
|
|
178
|
+
- Set \`VITE_CLERK_PUBLISHABLE_KEY\` in \`Extension/.env\`.
|
|
179
|
+
- Add allowed origins for \`http://localhost:8081\`, the Frontend Vercel domain, and the extension origin after Chrome assigns an extension id.
|
|
180
|
+
|
|
181
|
+
3. RevenueCat + Stripe
|
|
182
|
+
- RevenueCat Web overview: https://www.revenuecat.com/docs/web/overview
|
|
183
|
+
- RevenueCat Stripe Billing integration: https://www.revenuecat.com/docs/web/integrations/stripe
|
|
184
|
+
- Stripe API keys: https://docs.stripe.com/keys
|
|
185
|
+
- Create an entitlement named \`pro\`.
|
|
186
|
+
- Create iOS, Android, and Web apps in RevenueCat and copy their public SDK keys into \`EXPO_PUBLIC_RC_API_KEY_IOS\`, \`EXPO_PUBLIC_RC_API_KEY_ANDROID\`, and \`EXPO_PUBLIC_RC_WEB_API_KEY\`.
|
|
187
|
+
- Set \`EXPO_PUBLIC_RC_ENTITLEMENT_ID=pro\`.
|
|
188
|
+
- Set \`RC_SECRET_API_KEY\` and \`RC_WEBHOOK_SECRET\` only in Backend envs.
|
|
189
|
+
- Point the RevenueCat webhook to \`https://<backend-domain>/webhooks/revenuecat\`.
|
|
190
|
+
|
|
191
|
+
4. Cloudflare R2/CDN
|
|
192
|
+
- R2 S3-compatible setup: https://developers.cloudflare.com/r2/get-started/s3/
|
|
193
|
+
- R2 API tokens: https://developers.cloudflare.com/r2/api/tokens/
|
|
194
|
+
- Create a bucket and least-privilege API token.
|
|
195
|
+
- Set \`R2_ACCOUNT_ID\`, \`R2_ACCESS_KEY_ID\`, \`R2_SECRET_ACCESS_KEY\`, and \`R2_BUCKET\` only in Backend envs.
|
|
196
|
+
- If you use a public CDN/custom domain for uploaded files, add that URL to your own app config before exposing uploads broadly.
|
|
197
|
+
|
|
198
|
+
5. Vercel web/API deploys
|
|
199
|
+
- Monorepo docs: https://vercel.com/docs/monorepos
|
|
200
|
+
- Monorepo FAQ for source outside root: https://vercel.com/docs/monorepos/monorepo-faq
|
|
201
|
+
- Import this same Git repository twice. Use \`Frontend\` as the root directory for the Expo web project and \`Backend\` as the root directory for the Fastify API project.
|
|
202
|
+
- Enable access to source files outside each project root so workspace packages under \`packages/*\` are available during builds.
|
|
203
|
+
- Frontend build command: \`pnpm build:web\`; output directory: \`dist\`.
|
|
204
|
+
- Backend build command: \`pnpm build\`; Vercel detects \`Backend/src/index.ts\` as the Fastify entrypoint.
|
|
205
|
+
|
|
206
|
+
6. EAS native app builds
|
|
207
|
+
- EAS docs: https://docs.expo.dev/eas/
|
|
208
|
+
- Store submission docs: https://docs.expo.dev/deploy/submit-to-app-stores/
|
|
209
|
+
- Use \`Frontend\` as the Expo project root for iOS and Android builds. Vercel deploys the web export from the same source.
|
|
210
|
+
|
|
211
|
+
7. Chrome Web Store
|
|
212
|
+
- WXT publishing: https://wxt.dev/guide/essentials/publishing.html
|
|
213
|
+
- Chrome publishing: https://developer.chrome.com/docs/webstore/publish
|
|
214
|
+
- Build with \`pnpm extension:zip\`, then upload \`Extension/.output/*-chrome.zip\` in the Chrome Web Store Developer Dashboard.
|
|
169
215
|
|
|
170
216
|
## Setup Notes
|
|
171
217
|
|
|
@@ -173,14 +219,24 @@ For a provider-free smoke test, keep \`ANHEDRAL_DEMO=true\` in \`apps/api/.env\`
|
|
|
173
219
|
2. Review \`install-skills.sh\` and run the listed skill commands manually so you can choose scope and agent targets.
|
|
174
220
|
3. Add the real provider configuration for Clerk, RevenueCat, Stripe web billing, Neon, and R2 where the scaffolded helper files indicate.
|
|
175
221
|
4. Use \`stack.json\` when debugging generated projects; it records the verified dependency manifest used by this init run.
|
|
176
|
-
5.
|
|
222
|
+
5. Review \`PRODUCTION.md\` before connecting live provider projects.
|
|
223
|
+
6. Run database generation and migrations from the shared DB package:
|
|
177
224
|
|
|
178
225
|
\`\`\`bash
|
|
179
226
|
pnpm db:generate
|
|
180
227
|
pnpm db:migrate
|
|
181
228
|
\`\`\`
|
|
182
229
|
|
|
183
|
-
|
|
230
|
+
Run deployment readiness checks before pushing:
|
|
231
|
+
|
|
232
|
+
\`\`\`bash
|
|
233
|
+
pnpm verify
|
|
234
|
+
pnpm verify:frontend
|
|
235
|
+
pnpm verify:backend
|
|
236
|
+
pnpm verify:extension
|
|
237
|
+
\`\`\`
|
|
238
|
+
|
|
239
|
+
7. Build or run the extension separately. It is not deployed by Vercel:
|
|
184
240
|
|
|
185
241
|
\`\`\`bash
|
|
186
242
|
pnpm dev:extension
|
|
@@ -188,6 +244,248 @@ pnpm extension:zip
|
|
|
188
244
|
\`\`\`
|
|
189
245
|
`);
|
|
190
246
|
}
|
|
247
|
+
function writeProductionGuide(root, options) {
|
|
248
|
+
writeFile(path.join(root, 'PRODUCTION.md'), `# ${options.displayName} Production Guide
|
|
249
|
+
|
|
250
|
+
## Project Structure
|
|
251
|
+
|
|
252
|
+
The generated app is one pnpm monorepo:
|
|
253
|
+
|
|
254
|
+
\`\`\`txt
|
|
255
|
+
.
|
|
256
|
+
├─ Frontend/ Expo + React Native Reusables app
|
|
257
|
+
├─ Backend/ Fastify API
|
|
258
|
+
├─ Extension/ WXT Chrome extension
|
|
259
|
+
├─ packages/
|
|
260
|
+
│ ├─ api-client/ shared typed API client
|
|
261
|
+
│ ├─ config/ shared config constants/helpers
|
|
262
|
+
│ ├─ db/ Drizzle schema, Neon client, migrations
|
|
263
|
+
│ └─ types/ shared TypeScript types
|
|
264
|
+
├─ PRODUCTION.md development and deployment guide
|
|
265
|
+
├─ turbo.json workspace build graph
|
|
266
|
+
└─ package.json root scripts
|
|
267
|
+
\`\`\`
|
|
268
|
+
|
|
269
|
+
## Frontend
|
|
270
|
+
|
|
271
|
+
\`Frontend\` is the single Expo source for:
|
|
272
|
+
|
|
273
|
+
- Web on Vercel
|
|
274
|
+
- iOS through EAS
|
|
275
|
+
- Android through EAS
|
|
276
|
+
|
|
277
|
+
Important files:
|
|
278
|
+
|
|
279
|
+
- \`Frontend/package.json\`: Expo scripts
|
|
280
|
+
- \`Frontend/vercel.json\`: Vercel web export config
|
|
281
|
+
- \`Frontend/eas.json\`: native build profiles
|
|
282
|
+
- \`Frontend/app\`: Expo Router screens
|
|
283
|
+
|
|
284
|
+
Build output goes to \`Frontend/dist\`.
|
|
285
|
+
|
|
286
|
+
## Backend
|
|
287
|
+
|
|
288
|
+
\`Backend\` is the Fastify API.
|
|
289
|
+
|
|
290
|
+
Important files:
|
|
291
|
+
|
|
292
|
+
- \`Backend/src/index.ts\`: Vercel Fastify entrypoint
|
|
293
|
+
- \`Backend/src/app.ts\`: Fastify app construction
|
|
294
|
+
- \`Backend/vercel.json\`: minimal Vercel config
|
|
295
|
+
- \`Backend/src/routes\`: health/auth/subscription routes
|
|
296
|
+
|
|
297
|
+
## Extension
|
|
298
|
+
|
|
299
|
+
\`Extension\` is the WXT Chrome extension using the Side Panel API.
|
|
300
|
+
|
|
301
|
+
Important files:
|
|
302
|
+
|
|
303
|
+
- \`Extension/wxt.config.ts\`: manifest, side panel, permissions
|
|
304
|
+
- \`Extension/src/entrypoints/sidepanel\`: side panel UI
|
|
305
|
+
- \`Extension/src/entrypoints/background.ts\`: side panel behavior
|
|
306
|
+
|
|
307
|
+
ZIP output goes to \`Extension/.output\`.
|
|
308
|
+
|
|
309
|
+
## Develop
|
|
310
|
+
|
|
311
|
+
From the repository root:
|
|
312
|
+
|
|
313
|
+
\`\`\`sh
|
|
314
|
+
pnpm install
|
|
315
|
+
pnpm dev
|
|
316
|
+
\`\`\`
|
|
317
|
+
|
|
318
|
+
Or run one surface at a time:
|
|
319
|
+
|
|
320
|
+
\`\`\`sh
|
|
321
|
+
pnpm dev:frontend
|
|
322
|
+
pnpm dev:backend
|
|
323
|
+
pnpm dev:extension
|
|
324
|
+
\`\`\`
|
|
325
|
+
|
|
326
|
+
Use these before deploy:
|
|
327
|
+
|
|
328
|
+
\`\`\`sh
|
|
329
|
+
pnpm verify
|
|
330
|
+
pnpm verify:frontend
|
|
331
|
+
pnpm verify:backend
|
|
332
|
+
pnpm verify:extension
|
|
333
|
+
\`\`\`
|
|
334
|
+
|
|
335
|
+
Database workflow:
|
|
336
|
+
|
|
337
|
+
\`\`\`sh
|
|
338
|
+
pnpm db:generate
|
|
339
|
+
pnpm db:migrate
|
|
340
|
+
\`\`\`
|
|
341
|
+
|
|
342
|
+
Local demo mode is enabled through generated \`.env\` files. Real provider behavior needs Clerk, RevenueCat/Stripe, Neon, and R2 credentials.
|
|
343
|
+
|
|
344
|
+
The generated placeholder values are intentionally non-production. They let you inspect files and run backend demo checks, but browser auth, paid subscriptions, uploads, and extension sign-in require the setup below.
|
|
345
|
+
|
|
346
|
+
## Environment Variables
|
|
347
|
+
|
|
348
|
+
- Root/local: \`DATABASE_URL\` for shared Drizzle commands.
|
|
349
|
+
- Frontend/Vercel Frontend: only \`EXPO_PUBLIC_*\` values.
|
|
350
|
+
- Backend/Vercel Backend: server secrets such as \`DATABASE_URL\`, \`CLERK_SECRET_KEY\`, \`RC_SECRET_API_KEY\`, \`RC_WEBHOOK_SECRET\`, and R2 credentials.
|
|
351
|
+
- Extension: \`VITE_API_URL\`, \`VITE_CLERK_PUBLISHABLE_KEY\`, and optionally \`VITE_CRX_PUBLIC_KEY\`.
|
|
352
|
+
- EAS: add native app public keys with \`eas secret:create\` or the EAS dashboard.
|
|
353
|
+
|
|
354
|
+
## Provider Setup
|
|
355
|
+
|
|
356
|
+
### Neon + Drizzle
|
|
357
|
+
|
|
358
|
+
Docs:
|
|
359
|
+
|
|
360
|
+
- https://neon.com/docs/get-started-with-neon/connect-neon
|
|
361
|
+
- https://orm.drizzle.team/docs/tutorials/drizzle-with-neon
|
|
362
|
+
|
|
363
|
+
Steps:
|
|
364
|
+
|
|
365
|
+
1. Create a Neon project and Postgres database.
|
|
366
|
+
2. Copy the pooled connection string.
|
|
367
|
+
3. Set \`DATABASE_URL\` in the root \`.env\`, \`Backend/.env\`, and the Backend Vercel project.
|
|
368
|
+
4. Run:
|
|
369
|
+
|
|
370
|
+
\`\`\`sh
|
|
371
|
+
pnpm db:generate
|
|
372
|
+
pnpm db:migrate
|
|
373
|
+
\`\`\`
|
|
374
|
+
|
|
375
|
+
### Clerk
|
|
376
|
+
|
|
377
|
+
Docs:
|
|
378
|
+
|
|
379
|
+
- https://docs.expo.dev/guides/using-clerk/
|
|
380
|
+
- https://clerk.com/docs/quickstarts/get-started-with-expo
|
|
381
|
+
|
|
382
|
+
Steps:
|
|
383
|
+
|
|
384
|
+
1. Create a Clerk application.
|
|
385
|
+
2. Set \`EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY\` in \`Frontend/.env\`, the Frontend Vercel project, and EAS.
|
|
386
|
+
3. Set \`CLERK_PUBLISHABLE_KEY\` and \`CLERK_SECRET_KEY\` in \`Backend/.env\` and the Backend Vercel project.
|
|
387
|
+
4. Set \`VITE_CLERK_PUBLISHABLE_KEY\` in \`Extension/.env\`.
|
|
388
|
+
5. Add allowed origins for local Expo web, the deployed Frontend Vercel domain, and the Chrome extension origin after the extension id is stable.
|
|
389
|
+
6. Configure native redirect/deep-link settings for the Expo scheme in \`Frontend/app.json\`.
|
|
390
|
+
|
|
391
|
+
### RevenueCat + Stripe
|
|
392
|
+
|
|
393
|
+
Docs:
|
|
394
|
+
|
|
395
|
+
- https://www.revenuecat.com/docs/web/overview
|
|
396
|
+
- https://www.revenuecat.com/docs/web/integrations/stripe
|
|
397
|
+
- https://docs.stripe.com/keys
|
|
398
|
+
|
|
399
|
+
Steps:
|
|
400
|
+
|
|
401
|
+
1. Create the RevenueCat project.
|
|
402
|
+
2. Create an entitlement named \`pro\`.
|
|
403
|
+
3. Create iOS, Android, and Web apps in RevenueCat.
|
|
404
|
+
4. Copy the public SDK keys into \`EXPO_PUBLIC_RC_API_KEY_IOS\`, \`EXPO_PUBLIC_RC_API_KEY_ANDROID\`, and \`EXPO_PUBLIC_RC_WEB_API_KEY\`.
|
|
405
|
+
5. Set \`EXPO_PUBLIC_RC_ENTITLEMENT_ID=pro\`.
|
|
406
|
+
6. Connect Stripe as the RevenueCat web billing source.
|
|
407
|
+
7. Set \`RC_SECRET_API_KEY\` and \`RC_WEBHOOK_SECRET\` only in Backend envs.
|
|
408
|
+
8. Configure the RevenueCat webhook URL as \`https://<backend-domain>/webhooks/revenuecat\`.
|
|
409
|
+
|
|
410
|
+
### Cloudflare R2/CDN
|
|
411
|
+
|
|
412
|
+
Docs:
|
|
413
|
+
|
|
414
|
+
- https://developers.cloudflare.com/r2/get-started/s3/
|
|
415
|
+
- https://developers.cloudflare.com/r2/api/tokens/
|
|
416
|
+
|
|
417
|
+
Steps:
|
|
418
|
+
|
|
419
|
+
1. Create an R2 bucket.
|
|
420
|
+
2. Create a least-privilege API token for that bucket.
|
|
421
|
+
3. Set \`R2_ACCOUNT_ID\`, \`R2_ACCESS_KEY_ID\`, \`R2_SECRET_ACCESS_KEY\`, and \`R2_BUCKET\` only in Backend envs.
|
|
422
|
+
4. Configure a public bucket URL or custom domain if uploaded assets need public CDN delivery.
|
|
423
|
+
|
|
424
|
+
## Deploy
|
|
425
|
+
|
|
426
|
+
Vercel deployment is two Vercel projects from the same Git repo:
|
|
427
|
+
|
|
428
|
+
1. Frontend Vercel project
|
|
429
|
+
- Root Directory: \`Frontend\`
|
|
430
|
+
- Build command: \`pnpm build:web\`
|
|
431
|
+
- Output directory: \`dist\`
|
|
432
|
+
|
|
433
|
+
2. Backend Vercel project
|
|
434
|
+
- Root Directory: \`Backend\`
|
|
435
|
+
- Build command: \`pnpm build\`
|
|
436
|
+
- Fastify entrypoint: \`src/index.ts\`
|
|
437
|
+
|
|
438
|
+
Enable Vercel access to source files outside each project root so \`packages/*\` workspace packages resolve.
|
|
439
|
+
|
|
440
|
+
Native app deployment:
|
|
441
|
+
|
|
442
|
+
\`\`\`sh
|
|
443
|
+
cd Frontend
|
|
444
|
+
pnpm dlx eas-cli@latest login
|
|
445
|
+
pnpm dlx eas-cli@latest init
|
|
446
|
+
pnpm dlx eas-cli@latest build --platform all --profile production
|
|
447
|
+
pnpm dlx eas-cli@latest submit --platform all --latest --profile production
|
|
448
|
+
\`\`\`
|
|
449
|
+
|
|
450
|
+
Chrome extension deployment:
|
|
451
|
+
|
|
452
|
+
\`\`\`sh
|
|
453
|
+
pnpm extension:zip
|
|
454
|
+
\`\`\`
|
|
455
|
+
|
|
456
|
+
Upload \`Extension/.output/*-chrome.zip\` to the Chrome Web Store Developer Dashboard.
|
|
457
|
+
|
|
458
|
+
## Production Checklist
|
|
459
|
+
|
|
460
|
+
- Replace every \`*_placeholder\` value before production deploy.
|
|
461
|
+
- Keep server secrets out of Frontend, Extension, and EAS public envs.
|
|
462
|
+
- Confirm Clerk works on local web, Vercel web, iOS, Android, and the Chrome extension.
|
|
463
|
+
- Confirm RevenueCat returns the \`pro\` entitlement after Stripe web purchase and native store purchase.
|
|
464
|
+
- Confirm \`pnpm db:migrate\` has run against the production Neon database.
|
|
465
|
+
- Confirm R2 upload and signed avatar URL retrieval work from the deployed Backend domain.
|
|
466
|
+
- Confirm both Vercel projects can resolve \`packages/*\` workspace packages.
|
|
467
|
+
- Confirm the Chrome extension ZIP is tested locally with \`chrome://extensions\` before Chrome Web Store upload.
|
|
468
|
+
|
|
469
|
+
## Relevant Docs
|
|
470
|
+
|
|
471
|
+
- [Vercel monorepos](https://vercel.com/docs/monorepos)
|
|
472
|
+
- [Vercel monorepo FAQ](https://vercel.com/docs/monorepos/monorepo-faq)
|
|
473
|
+
- [Expo web publishing](https://docs.expo.dev/guides/publishing-websites/)
|
|
474
|
+
- [Expo EAS](https://docs.expo.dev/eas/)
|
|
475
|
+
- [Expo store submissions](https://docs.expo.dev/deploy/submit-to-app-stores/)
|
|
476
|
+
- [Clerk with Expo](https://docs.expo.dev/guides/using-clerk/)
|
|
477
|
+
- [Clerk Expo quickstart](https://clerk.com/docs/quickstarts/get-started-with-expo)
|
|
478
|
+
- [Neon connection strings](https://neon.com/docs/get-started-with-neon/connect-neon)
|
|
479
|
+
- [Drizzle with Neon](https://orm.drizzle.team/docs/tutorials/drizzle-with-neon)
|
|
480
|
+
- [RevenueCat Web](https://www.revenuecat.com/docs/web/overview)
|
|
481
|
+
- [RevenueCat Stripe Billing](https://www.revenuecat.com/docs/web/integrations/stripe)
|
|
482
|
+
- [Stripe API keys](https://docs.stripe.com/keys)
|
|
483
|
+
- [Cloudflare R2 S3 API](https://developers.cloudflare.com/r2/get-started/s3/)
|
|
484
|
+
- [Cloudflare R2 API tokens](https://developers.cloudflare.com/r2/api/tokens/)
|
|
485
|
+
- [WXT publishing](https://wxt.dev/guide/essentials/publishing.html)
|
|
486
|
+
- [Chrome Web Store publishing](https://developer.chrome.com/docs/webstore/publish)
|
|
487
|
+
`);
|
|
488
|
+
}
|
|
191
489
|
function writeStackFile(root, stack) {
|
|
192
490
|
writeFile(path.join(root, 'stack.json'), JSON.stringify(stack, null, 2) + '\n');
|
|
193
491
|
}
|
|
@@ -755,9 +1053,9 @@ R2_BUCKET=
|
|
|
755
1053
|
${paymentsEnv}`);
|
|
756
1054
|
}
|
|
757
1055
|
function writeRootVercelFiles(root) {
|
|
758
|
-
writeFile(path.join(root, '.vercelignore'), `
|
|
759
|
-
|
|
760
|
-
|
|
1056
|
+
writeFile(path.join(root, '.vercelignore'), `Extension/.output
|
|
1057
|
+
Extension/.wxt
|
|
1058
|
+
Extension/dist
|
|
761
1059
|
`);
|
|
762
1060
|
}
|
|
763
1061
|
function writeGeneratedCiWorkflow(root) {
|
|
@@ -799,22 +1097,22 @@ jobs:
|
|
|
799
1097
|
run: pnpm install --frozen-lockfile
|
|
800
1098
|
|
|
801
1099
|
- name: Check Expo dependency alignment
|
|
802
|
-
run: pnpm --filter ./
|
|
1100
|
+
run: pnpm --filter ./Frontend exec expo install --check
|
|
803
1101
|
|
|
804
1102
|
- name: Build Expo web
|
|
805
|
-
run: pnpm --filter ./
|
|
1103
|
+
run: pnpm --filter ./Frontend build:web
|
|
806
1104
|
|
|
807
1105
|
- name: Test API
|
|
808
|
-
run: pnpm --filter ./
|
|
1106
|
+
run: pnpm --filter ./Backend test
|
|
809
1107
|
|
|
810
1108
|
- name: Build API
|
|
811
|
-
run: pnpm --filter ./
|
|
1109
|
+
run: pnpm --filter ./Backend build
|
|
812
1110
|
|
|
813
1111
|
- name: Build extension
|
|
814
|
-
run: pnpm --filter ./
|
|
1112
|
+
run: pnpm --filter ./Extension build
|
|
815
1113
|
|
|
816
1114
|
- name: Zip extension
|
|
817
|
-
run: pnpm --filter ./
|
|
1115
|
+
run: pnpm --filter ./Extension zip
|
|
818
1116
|
|
|
819
1117
|
- name: Build workspace
|
|
820
1118
|
run: pnpm build
|
|
@@ -822,32 +1120,36 @@ jobs:
|
|
|
822
1120
|
}
|
|
823
1121
|
function writeFullstackRootFiles(root, options) {
|
|
824
1122
|
const appFilters = [
|
|
825
|
-
'./
|
|
826
|
-
'./
|
|
827
|
-
'./
|
|
1123
|
+
'./Frontend',
|
|
1124
|
+
'./Backend',
|
|
1125
|
+
'./Extension',
|
|
828
1126
|
];
|
|
829
1127
|
const parallelFilters = appFilters.map((entry) => `--filter=${entry}`).join(' ');
|
|
830
1128
|
const scripts = {
|
|
831
1129
|
dev: `turbo dev --parallel ${parallelFilters}`,
|
|
832
|
-
'dev:
|
|
1130
|
+
'dev:backend': 'pnpm --filter ./Backend dev',
|
|
833
1131
|
build: 'turbo build',
|
|
834
1132
|
typecheck: 'turbo typecheck',
|
|
1133
|
+
verify: 'pnpm verify:frontend && pnpm verify:backend && pnpm verify:extension',
|
|
1134
|
+
'verify:frontend': 'pnpm --filter ./Frontend exec expo install --check && pnpm --filter ./Frontend build:web',
|
|
1135
|
+
'verify:backend': 'pnpm --filter ./Backend test && pnpm --filter ./Backend build',
|
|
1136
|
+
'verify:extension': 'pnpm --filter ./Extension typecheck && pnpm --filter ./Extension zip',
|
|
835
1137
|
'db:generate': 'pnpm --filter @anhedral/db db:generate',
|
|
836
1138
|
'db:migrate': 'pnpm --filter @anhedral/db db:migrate',
|
|
837
1139
|
};
|
|
838
|
-
scripts['dev:frontend'] = 'pnpm --filter ./
|
|
839
|
-
scripts['dev:extension'] = 'pnpm --filter ./
|
|
840
|
-
scripts['extension:zip'] = 'pnpm --filter ./
|
|
841
|
-
writeRootPackageJson(root, options.projectName, scripts, ['
|
|
1140
|
+
scripts['dev:frontend'] = 'pnpm --filter ./Frontend dev';
|
|
1141
|
+
scripts['dev:extension'] = 'pnpm --filter ./Extension dev';
|
|
1142
|
+
scripts['extension:zip'] = 'pnpm --filter ./Extension zip';
|
|
1143
|
+
writeRootPackageJson(root, options.projectName, scripts, ['Frontend', 'Backend', 'Extension', 'packages/*'], {
|
|
842
1144
|
devDependencies: ROOT_DEPENDENCIES.devDependencies,
|
|
843
1145
|
});
|
|
844
|
-
writePnpmWorkspace(root, ['
|
|
1146
|
+
writePnpmWorkspace(root, ['Frontend', 'Backend', 'Extension', 'packages/*']);
|
|
845
1147
|
writeFile(path.join(root, 'turbo.json'), JSON.stringify({
|
|
846
1148
|
$schema: 'https://turborepo.dev/schema.json',
|
|
847
1149
|
tasks: {
|
|
848
1150
|
build: {
|
|
849
1151
|
dependsOn: ['^build'],
|
|
850
|
-
outputs: [
|
|
1152
|
+
outputs: [],
|
|
851
1153
|
},
|
|
852
1154
|
typecheck: {
|
|
853
1155
|
dependsOn: ['^build'],
|
|
@@ -861,7 +1163,9 @@ function writeFullstackRootFiles(root, options) {
|
|
|
861
1163
|
appendGitignore(root, [
|
|
862
1164
|
'node_modules',
|
|
863
1165
|
'.turbo',
|
|
864
|
-
'
|
|
1166
|
+
'Frontend/node_modules',
|
|
1167
|
+
'Backend/node_modules',
|
|
1168
|
+
'Extension/node_modules',
|
|
865
1169
|
'packages/*/node_modules',
|
|
866
1170
|
...SHARED_ENV_GITIGNORE_LINES,
|
|
867
1171
|
...SHARED_TYPESCRIPT_GITIGNORE_LINES,
|
|
@@ -903,9 +1207,10 @@ async function scaffoldFullstack(root, options) {
|
|
|
903
1207
|
});
|
|
904
1208
|
return [
|
|
905
1209
|
'.github/workflows/ci.yml',
|
|
906
|
-
'
|
|
907
|
-
'
|
|
908
|
-
'
|
|
1210
|
+
'PRODUCTION.md',
|
|
1211
|
+
'Frontend',
|
|
1212
|
+
'Backend',
|
|
1213
|
+
'Extension',
|
|
909
1214
|
'packages/db',
|
|
910
1215
|
'packages/types',
|
|
911
1216
|
'packages/config',
|
|
@@ -927,6 +1232,7 @@ export async function scaffoldProject(options) {
|
|
|
927
1232
|
writeSkillsGuide(root, skillCommands);
|
|
928
1233
|
const stack = normalizeStack(options, generatedPaths);
|
|
929
1234
|
writeRootDocs(root, options, stack);
|
|
1235
|
+
writeProductionGuide(root, options);
|
|
930
1236
|
writeStackFile(root, stack);
|
|
931
1237
|
anhedralPrint.done('Project metadata written');
|
|
932
1238
|
console.log('');
|
|
@@ -938,9 +1244,10 @@ export async function scaffoldProject(options) {
|
|
|
938
1244
|
anhedralPrint.info('Next commands:');
|
|
939
1245
|
console.log(' pnpm install');
|
|
940
1246
|
console.log(' cp .env.example .env');
|
|
941
|
-
console.log(' cp
|
|
1247
|
+
console.log(' cp Extension/.env.example Extension/.env');
|
|
942
1248
|
console.log(' pnpm db:generate && pnpm db:migrate');
|
|
943
|
-
console.log(' pnpm
|
|
1249
|
+
console.log(' pnpm verify');
|
|
1250
|
+
console.log(' pnpm dev:backend');
|
|
944
1251
|
console.log(' pnpm dev:frontend');
|
|
945
1252
|
console.log(' pnpm dev:extension');
|
|
946
1253
|
console.log('');
|
|
@@ -4,7 +4,7 @@ import { anhedralPrint } from '../print.js';
|
|
|
4
4
|
import { getBackendInstallCommands } from '../commands.js';
|
|
5
5
|
import { BACKEND_DEPENDENCIES } from '../dependencies.js';
|
|
6
6
|
export async function scaffoldBackend(root, { projectName, displayName, frontendUrl, skipInstall }) {
|
|
7
|
-
const dir = path.join(root, '
|
|
7
|
+
const dir = path.join(root, 'Backend');
|
|
8
8
|
anhedralPrint.section('Backend (Fastify)');
|
|
9
9
|
anhedralPrint.step('Writing backend source files');
|
|
10
10
|
writePackageJson(dir, projectName);
|
|
@@ -24,7 +24,6 @@ export async function scaffoldBackend(root, { projectName, displayName, frontend
|
|
|
24
24
|
writeRepositories(dir);
|
|
25
25
|
writeRoutes(dir, displayName);
|
|
26
26
|
writeAppAndIndex(dir, displayName);
|
|
27
|
-
writeVercelEntry(dir);
|
|
28
27
|
writeVercelConfig(dir);
|
|
29
28
|
writeTestFiles(dir);
|
|
30
29
|
anhedralPrint.done('Backend source files written');
|
|
@@ -2174,55 +2173,11 @@ main().catch((err) => {
|
|
|
2174
2173
|
});
|
|
2175
2174
|
`);
|
|
2176
2175
|
}
|
|
2177
|
-
function writeVercelEntry(dir) {
|
|
2178
|
-
writeFile(path.join(dir, 'api/index.ts'), `import type { VercelRequest, VercelResponse } from '@vercel/node';
|
|
2179
|
-
import type { FastifyInstance } from 'fastify';
|
|
2180
|
-
import { buildApp } from '../src/app.js';
|
|
2181
|
-
|
|
2182
|
-
let appReadyPromise: Promise<FastifyInstance> | undefined;
|
|
2183
|
-
|
|
2184
|
-
async function getApp(): Promise<FastifyInstance> {
|
|
2185
|
-
if (!appReadyPromise) {
|
|
2186
|
-
appReadyPromise = (async () => {
|
|
2187
|
-
const app = await buildApp();
|
|
2188
|
-
await app.ready();
|
|
2189
|
-
return app;
|
|
2190
|
-
})().catch((err) => {
|
|
2191
|
-
appReadyPromise = undefined;
|
|
2192
|
-
throw err;
|
|
2193
|
-
});
|
|
2194
|
-
}
|
|
2195
|
-
return appReadyPromise;
|
|
2196
|
-
}
|
|
2197
|
-
|
|
2198
|
-
export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|
2199
|
-
const app = await getApp();
|
|
2200
|
-
if (req.url === '/api' || req.url === '/backend') {
|
|
2201
|
-
req.url = '/';
|
|
2202
|
-
} else if (req.url?.startsWith('/api/')) {
|
|
2203
|
-
req.url = req.url.slice('/api'.length);
|
|
2204
|
-
} else if (req.url?.startsWith('/backend/')) {
|
|
2205
|
-
req.url = req.url.slice('/backend'.length);
|
|
2206
|
-
}
|
|
2207
|
-
app.server.emit('request', req, res);
|
|
2208
|
-
}
|
|
2209
|
-
`);
|
|
2210
|
-
}
|
|
2211
2176
|
function writeVercelConfig(dir) {
|
|
2212
2177
|
writeFile(path.join(dir, 'vercel.json'), JSON.stringify({
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
src: 'api/index.ts',
|
|
2217
|
-
use: '@vercel/node',
|
|
2218
|
-
},
|
|
2219
|
-
],
|
|
2220
|
-
routes: [
|
|
2221
|
-
{
|
|
2222
|
-
src: '/(.*)',
|
|
2223
|
-
dest: 'api/index.ts',
|
|
2224
|
-
},
|
|
2225
|
-
],
|
|
2178
|
+
$schema: 'https://openapi.vercel.sh/vercel.json',
|
|
2179
|
+
buildCommand: 'pnpm build',
|
|
2180
|
+
devCommand: 'vercel dev',
|
|
2226
2181
|
}, null, 2) + '\n');
|
|
2227
2182
|
}
|
|
2228
2183
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -5,13 +5,11 @@ import { anhedralPrint } from '../print.js';
|
|
|
5
5
|
import { EXTENSION_DEPENDENCIES } from '../dependencies.js';
|
|
6
6
|
import { resolveToolchainChannel, resolveToolchain, toolPackageRef } from '../toolchain.js';
|
|
7
7
|
export async function scaffoldExtension(root, { projectName, displayName, skipInstall }) {
|
|
8
|
-
const
|
|
9
|
-
const dir = path.join(appsRoot, 'extension');
|
|
8
|
+
const dir = path.join(root, 'Extension');
|
|
10
9
|
const toolchain = resolveToolchain(resolveToolchainChannel(process.env.ANHEDRAL_TOOLCHAIN));
|
|
11
10
|
anhedralPrint.section('Chrome extension (WXT)');
|
|
12
11
|
anhedralPrint.step('Scaffolding WXT extension');
|
|
13
|
-
|
|
14
|
-
exec(`pnpm dlx ${toolPackageRef('wxt', toolchain.wxt)} init extension -t react --pm pnpm`, appsRoot);
|
|
12
|
+
exec(`pnpm dlx --allow-build=esbuild --allow-build=spawn-sync ${toolPackageRef('wxt', toolchain.wxt)} init Extension -t react --pm pnpm`, root);
|
|
15
13
|
writePackageJson(dir, projectName);
|
|
16
14
|
anhedralPrint.done('WXT extension scaffolded');
|
|
17
15
|
anhedralPrint.step('Installing Clerk + React + Tailwind dependencies');
|
|
@@ -98,6 +96,11 @@ export default defineConfig({
|
|
|
98
96
|
};
|
|
99
97
|
},
|
|
100
98
|
modules: ['@wxt-dev/module-react'],
|
|
99
|
+
vite: () => ({
|
|
100
|
+
build: {
|
|
101
|
+
chunkSizeWarningLimit: 3000,
|
|
102
|
+
},
|
|
103
|
+
}),
|
|
101
104
|
});
|
|
102
105
|
`);
|
|
103
106
|
}
|
|
@@ -6,7 +6,7 @@ import { appendGitignore, writeFile, exec, execExpect, liftNestedProject } from
|
|
|
6
6
|
import { FRONTEND_ADDON_DEPENDENCIES, withVersions } from '../dependencies.js';
|
|
7
7
|
import { resolveToolchainChannel, resolveToolchain, toolPackageRef } from '../toolchain.js';
|
|
8
8
|
export async function scaffoldFrontend(root, { projectName, displayName, skipInstall }) {
|
|
9
|
-
const dir = path.join(root, '
|
|
9
|
+
const dir = path.join(root, 'Frontend');
|
|
10
10
|
const toolchain = resolveToolchain(resolveToolchainChannel(process.env.ANHEDRAL_TOOLCHAIN));
|
|
11
11
|
anhedralPrint.section('Frontend (Expo)');
|
|
12
12
|
anhedralPrint.step('Scaffolding Expo app with react-native-reusables');
|
|
@@ -21,6 +21,7 @@ export async function scaffoldFrontend(root, { projectName, displayName, skipIns
|
|
|
21
21
|
anhedralPrint.step('Installing frontend dependencies');
|
|
22
22
|
patchFrontendPackageJson(dir);
|
|
23
23
|
patchExpoAppConfig(dir);
|
|
24
|
+
patchFrontendTsConfig(dir);
|
|
24
25
|
if (!skipInstall) {
|
|
25
26
|
exec('pnpm install --no-frozen-lockfile', dir);
|
|
26
27
|
exec('pnpm exec expo install --fix --pnpm', dir);
|
|
@@ -36,13 +37,14 @@ export async function scaffoldFrontend(root, { projectName, displayName, skipIns
|
|
|
36
37
|
writeEnvExample(dir);
|
|
37
38
|
writeEnvFile(dir);
|
|
38
39
|
writeVercelConfig(dir);
|
|
40
|
+
writeEasConfig(dir);
|
|
39
41
|
writeAnhedralLogo(dir);
|
|
40
42
|
writeSubscriptionProvider(dir);
|
|
41
43
|
writeUseSubscription(dir);
|
|
42
44
|
anhedralPrint.step('Installing Expo native packages');
|
|
43
45
|
if (skipInstall) {
|
|
44
46
|
anhedralPrint.info('Skipping Expo native package install (--skip-install)');
|
|
45
|
-
anhedralPrint.info('Run after init: pnpm --filter ./
|
|
47
|
+
anhedralPrint.info('Run after init: pnpm --filter ./Frontend exec expo install --pnpm expo-image-picker');
|
|
46
48
|
}
|
|
47
49
|
else {
|
|
48
50
|
exec('pnpm exec expo install --pnpm expo-image-picker', dir);
|
|
@@ -56,7 +58,7 @@ export async function scaffoldFrontend(root, { projectName, displayName, skipIns
|
|
|
56
58
|
'@revenuecat/purchases-js': FRONTEND_ADDON_DEPENDENCIES['@revenuecat/purchases-js'],
|
|
57
59
|
});
|
|
58
60
|
if (skipInstall) {
|
|
59
|
-
anhedralPrint.info(`Skipping RevenueCat SDK install (--skip-install). Run after init: pnpm --filter ./
|
|
61
|
+
anhedralPrint.info(`Skipping RevenueCat SDK install (--skip-install). Run after init: pnpm --filter ./Frontend add ${revenueCatDependencies.join(' ')}`);
|
|
60
62
|
}
|
|
61
63
|
else {
|
|
62
64
|
exec(`pnpm add ${revenueCatDependencies.join(' ')}`, dir);
|
|
@@ -69,7 +71,7 @@ function patchFrontendPackageJson(dir) {
|
|
|
69
71
|
packageJson.dependencies = {
|
|
70
72
|
...(packageJson.dependencies ?? {}),
|
|
71
73
|
'@anhedral/api-client': FRONTEND_ADDON_DEPENDENCIES['@anhedral/api-client'],
|
|
72
|
-
'@clerk/
|
|
74
|
+
'@clerk/expo': FRONTEND_ADDON_DEPENDENCIES['@clerk/expo'],
|
|
73
75
|
'react-native-purchases': FRONTEND_ADDON_DEPENDENCIES['react-native-purchases'],
|
|
74
76
|
'react-native-purchases-ui': FRONTEND_ADDON_DEPENDENCIES['react-native-purchases-ui'],
|
|
75
77
|
'@revenuecat/purchases-js': FRONTEND_ADDON_DEPENDENCIES['@revenuecat/purchases-js'],
|
|
@@ -82,6 +84,17 @@ function patchFrontendPackageJson(dir) {
|
|
|
82
84
|
};
|
|
83
85
|
writeFile(filePath, JSON.stringify(packageJson, null, 2) + '\n');
|
|
84
86
|
}
|
|
87
|
+
function patchFrontendTsConfig(dir) {
|
|
88
|
+
const filePath = path.join(dir, 'tsconfig.json');
|
|
89
|
+
if (!fs.existsSync(filePath)) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const tsConfig = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
93
|
+
if (tsConfig.extends === 'expo/tsconfig.base') {
|
|
94
|
+
tsConfig.extends = 'expo/tsconfig.base.json';
|
|
95
|
+
}
|
|
96
|
+
writeFile(filePath, JSON.stringify(tsConfig, null, 2) + '\n');
|
|
97
|
+
}
|
|
85
98
|
function patchExpoAppConfig(dir) {
|
|
86
99
|
const filePath = path.join(dir, 'app.json');
|
|
87
100
|
if (!fs.existsSync(filePath)) {
|
|
@@ -94,6 +107,10 @@ function patchExpoAppConfig(dir) {
|
|
|
94
107
|
bundler: 'metro',
|
|
95
108
|
output: 'static',
|
|
96
109
|
};
|
|
110
|
+
appJson.expo.plugins = Array.from(new Set([
|
|
111
|
+
...(Array.isArray(appJson.expo.plugins) ? appJson.expo.plugins : []),
|
|
112
|
+
'@clerk/expo',
|
|
113
|
+
]));
|
|
97
114
|
writeFile(filePath, JSON.stringify(appJson, null, 2) + '\n');
|
|
98
115
|
}
|
|
99
116
|
function writeApiClient(dir) {
|
|
@@ -169,7 +186,7 @@ export function getPlatformRevenueCatApiKey(): string {
|
|
|
169
186
|
`);
|
|
170
187
|
}
|
|
171
188
|
function writeApiHook(dir) {
|
|
172
|
-
writeFile(path.join(dir, 'hooks/useAPI.ts'), `import { useAuth } from '@clerk/
|
|
189
|
+
writeFile(path.join(dir, 'hooks/useAPI.ts'), `import { useAuth } from '@clerk/expo';
|
|
173
190
|
import { useMemo } from 'react';
|
|
174
191
|
import { APIClient } from '@/api/client';
|
|
175
192
|
import { apiBaseUrl } from '@/lib/config';
|
|
@@ -191,13 +208,39 @@ function writeVercelConfig(dir) {
|
|
|
191
208
|
rewrites: [
|
|
192
209
|
{
|
|
193
210
|
source: '/:path*',
|
|
194
|
-
destination: '/
|
|
211
|
+
destination: '/',
|
|
195
212
|
},
|
|
196
213
|
],
|
|
197
214
|
}, null, 2) + '\n');
|
|
198
215
|
}
|
|
216
|
+
function writeEasConfig(dir) {
|
|
217
|
+
writeFile(path.join(dir, 'eas.json'), JSON.stringify({
|
|
218
|
+
cli: {
|
|
219
|
+
version: '>= 16.24.0',
|
|
220
|
+
appVersionSource: 'remote',
|
|
221
|
+
},
|
|
222
|
+
build: {
|
|
223
|
+
development: {
|
|
224
|
+
developmentClient: true,
|
|
225
|
+
distribution: 'internal',
|
|
226
|
+
android: {
|
|
227
|
+
buildType: 'apk',
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
preview: {
|
|
231
|
+
distribution: 'internal',
|
|
232
|
+
},
|
|
233
|
+
production: {
|
|
234
|
+
autoIncrement: true,
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
submit: {
|
|
238
|
+
production: {},
|
|
239
|
+
},
|
|
240
|
+
}, null, 2) + '\n');
|
|
241
|
+
}
|
|
199
242
|
function writeAccountHook(dir) {
|
|
200
|
-
writeFile(path.join(dir, 'hooks/useAccount.ts'), `import { useAuth } from '@clerk/
|
|
243
|
+
writeFile(path.join(dir, 'hooks/useAccount.ts'), `import { useAuth } from '@clerk/expo';
|
|
201
244
|
import * as React from 'react';
|
|
202
245
|
import { useAPI } from '@/hooks/useAPI';
|
|
203
246
|
|
|
@@ -295,7 +338,7 @@ import Purchases, { type CustomerInfo, type PurchasesOfferings } from 'react-nat
|
|
|
295
338
|
import { Purchases as PurchasesWeb } from '@revenuecat/purchases-js';
|
|
296
339
|
import { AppState, Platform } from 'react-native';
|
|
297
340
|
import { subscriptionConfig, getPlatformRevenueCatApiKey } from '@/lib/config';
|
|
298
|
-
import { useAuth, useUser } from '@clerk/
|
|
341
|
+
import { useAuth, useUser } from '@clerk/expo';
|
|
299
342
|
import { useAPI } from '@/hooks/useAPI';
|
|
300
343
|
import RevenueCatUI, { PAYWALL_RESULT } from 'react-native-purchases-ui';
|
|
301
344
|
|
|
@@ -697,9 +740,10 @@ function writeAppShellFiles(dir, displayName) {
|
|
|
697
740
|
writeFile(path.join(dir, 'app/_layout.tsx'), `import '@/global.css';
|
|
698
741
|
|
|
699
742
|
import { SubscriptionProvider } from '@/contexts/SubscriptionProvider';
|
|
743
|
+
import { clerkPublishableKey } from '@/lib/config';
|
|
700
744
|
import { NAV_THEME } from '@/lib/theme';
|
|
701
|
-
import { ClerkProvider, useAuth } from '@clerk/
|
|
702
|
-
import { tokenCache } from '@clerk/
|
|
745
|
+
import { ClerkProvider, useAuth } from '@clerk/expo';
|
|
746
|
+
import { tokenCache } from '@clerk/expo/token-cache';
|
|
703
747
|
import { ThemeProvider } from '@react-navigation/native';
|
|
704
748
|
import { PortalHost } from '@rn-primitives/portal';
|
|
705
749
|
import { Stack } from 'expo-router';
|
|
@@ -714,7 +758,7 @@ export default function RootLayout() {
|
|
|
714
758
|
const { colorScheme } = useColorScheme();
|
|
715
759
|
|
|
716
760
|
return (
|
|
717
|
-
<ClerkProvider tokenCache={tokenCache}>
|
|
761
|
+
<ClerkProvider publishableKey={clerkPublishableKey} tokenCache={tokenCache}>
|
|
718
762
|
<ThemeProvider value={NAV_THEME[colorScheme ?? 'light']}>
|
|
719
763
|
<SubscriptionProvider>
|
|
720
764
|
<StatusBar style={colorScheme === 'dark' ? 'light' : 'dark'} />
|
|
@@ -763,7 +807,7 @@ function Routes() {
|
|
|
763
807
|
import { Button } from '@/components/ui/button';
|
|
764
808
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
|
765
809
|
import { Text } from '@/components/ui/text';
|
|
766
|
-
import { useAuth } from '@clerk/
|
|
810
|
+
import { useAuth } from '@clerk/expo';
|
|
767
811
|
import { Link } from 'expo-router';
|
|
768
812
|
import { CloudIcon, CreditCardIcon, ShieldCheckIcon, DatabaseIcon } from 'lucide-react-native';
|
|
769
813
|
import { ScrollView, View } from 'react-native';
|
|
@@ -1103,7 +1147,7 @@ import { Input } from '@/components/ui/input';
|
|
|
1103
1147
|
import { Label } from '@/components/ui/label';
|
|
1104
1148
|
import { Separator } from '@/components/ui/separator';
|
|
1105
1149
|
import { Text } from '@/components/ui/text';
|
|
1106
|
-
import { useSignIn } from '@clerk/
|
|
1150
|
+
import { useSignIn } from '@clerk/expo/legacy';
|
|
1107
1151
|
import { Link } from 'expo-router';
|
|
1108
1152
|
import * as React from 'react';
|
|
1109
1153
|
import { type TextInput, View } from 'react-native';
|
|
@@ -1217,7 +1261,7 @@ import { Input } from '@/components/ui/input';
|
|
|
1217
1261
|
import { Label } from '@/components/ui/label';
|
|
1218
1262
|
import { Separator } from '@/components/ui/separator';
|
|
1219
1263
|
import { Text } from '@/components/ui/text';
|
|
1220
|
-
import { useSignUp } from '@clerk/
|
|
1264
|
+
import { useSignUp } from '@clerk/expo/legacy';
|
|
1221
1265
|
import { Link, router } from 'expo-router';
|
|
1222
1266
|
import * as React from 'react';
|
|
1223
1267
|
import { TextInput, View } from 'react-native';
|
|
@@ -1321,7 +1365,7 @@ import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover
|
|
|
1321
1365
|
import { Text } from '@/components/ui/text';
|
|
1322
1366
|
import { useAccount } from '@/hooks/useAccount';
|
|
1323
1367
|
import { useSubscription } from '@/hooks/useSubscription';
|
|
1324
|
-
import { useAuth } from '@clerk/
|
|
1368
|
+
import { useAuth } from '@clerk/expo';
|
|
1325
1369
|
import type { TriggerRef } from '@rn-primitives/popover';
|
|
1326
1370
|
import { CameraIcon, CreditCardIcon, CoinsIcon, LoaderCircleIcon, LogOutIcon } from 'lucide-react-native';
|
|
1327
1371
|
import * as React from 'react';
|