anhedral 0.1.4 → 0.1.5

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 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 `apps/*` and `packages/*`. Shared code such as the Drizzle schema lives under `packages/db` so apps do not need separate database schemas.
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,36 @@ 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
- - `apps/frontend`: Expo web build. Build command: `pnpm build:web`. Output directory: `dist`.
63
- - `apps/api`: Fastify API. Build command: `pnpm build`. Requests rewrite to `api/index.ts`.
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 `apps/frontend` source is also the EAS source for iOS and Android builds; Vercel only deploys its web export.
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
+
71
80
  ## Native App Deployment
72
81
 
73
- Use `apps/frontend` as the Expo project root for iOS and Android. Vercel only deploys the web export from this app; native builds go through EAS.
82
+ 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
83
 
75
84
  ```sh
76
- cd apps/frontend
85
+ cd Frontend
77
86
  pnpm dlx eas-cli@latest login
78
87
  pnpm dlx eas-cli@latest init
79
- pnpm dlx eas-cli@latest build:configure
80
88
  ```
81
89
 
90
+ The scaffold generates `Frontend/eas.json` with `development`, `preview`, and `production` profiles.
91
+
82
92
  Build commands:
83
93
 
84
94
  ```sh
@@ -123,10 +133,10 @@ pnpm dlx eas-cli@latest credentials
123
133
 
124
134
  ## Extension Release
125
135
 
126
- The WXT extension is built from `apps/extension`. It is not deployed by Vercel.
136
+ The WXT extension is built from `Extension`. It is not deployed by Vercel.
127
137
 
128
138
  ```sh
129
- cd apps/extension
139
+ cd Extension
130
140
  pnpm build
131
141
  pnpm zip
132
142
  ```
@@ -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/clerk-expo
148
- '@clerk/clerk-expo': '2.19.31',
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,16 +147,17 @@ ${generatedPaths}
147
147
  \`\`\`bash
148
148
  pnpm install
149
149
  cp .env.example .env
150
- # apps/frontend/.env is generated for local Expo development
151
- cp apps/extension/.env.example apps/extension/.env
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 dev:api
154
+ pnpm verify
155
+ pnpm dev:backend
155
156
  pnpm dev:frontend
156
157
  pnpm dev:extension
157
158
  \`\`\`
158
159
 
159
- For a provider-free smoke test, keep \`ANHEDRAL_DEMO=true\` in \`apps/api/.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
+ For a provider-free 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
161
 
161
162
  ## Provider Setup
162
163
 
@@ -164,8 +165,8 @@ For a provider-free smoke test, keep \`ANHEDRAL_DEMO=true\` in \`apps/api/.env\`
164
165
  - Clerk: create an application, configure allowed origins, and set \`CLERK_PUBLISHABLE_KEY\`, \`CLERK_SECRET_KEY\`, plus the Expo and extension publishable keys.
165
166
  - RevenueCat + Stripe: create an entitlement named \`pro\`, configure Stripe as a RevenueCat web billing source, configure app keys, then set \`RC_SECRET_API_KEY\`, \`RC_WEBHOOK_SECRET\`, and the \`EXPO_PUBLIC_RC_*\` values.
166
167
  - Cloudflare R2: create a bucket and API token, then set \`R2_ACCOUNT_ID\`, \`R2_ACCESS_KEY_ID\`, \`R2_SECRET_ACCESS_KEY\`, and \`R2_BUCKET\`.
167
- - Vercel: import this same Git repository twice. Use \`apps/frontend\` as the root directory for the Expo web project, and \`apps/api\` as the root directory for the Fastify API project. Enable access to source files outside each root directory so workspace packages under \`packages/*\` are available during builds.
168
- - EAS: use \`apps/frontend\` as the Expo project root for iOS and Android builds. Vercel deploys the web export from the same source.
168
+ - Vercel: 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. Enable access to source files outside each root directory so workspace packages under \`packages/*\` are available during builds. The Backend project uses Vercel Fastify entrypoint detection from \`Backend/src/index.ts\`.
169
+ - EAS: use \`Frontend\` as the Expo project root for iOS and Android builds. Vercel deploys the web export from the same source.
169
170
 
170
171
  ## Setup Notes
171
172
 
@@ -173,19 +174,184 @@ For a provider-free smoke test, keep \`ANHEDRAL_DEMO=true\` in \`apps/api/.env\`
173
174
  2. Review \`install-skills.sh\` and run the listed skill commands manually so you can choose scope and agent targets.
174
175
  3. Add the real provider configuration for Clerk, RevenueCat, Stripe web billing, Neon, and R2 where the scaffolded helper files indicate.
175
176
  4. Use \`stack.json\` when debugging generated projects; it records the verified dependency manifest used by this init run.
176
- 5. Run database generation and migrations from the shared DB package:
177
+ 5. Review \`PRODUCTION.md\` before connecting live provider projects.
178
+ 6. Run database generation and migrations from the shared DB package:
177
179
 
178
180
  \`\`\`bash
179
181
  pnpm db:generate
180
182
  pnpm db:migrate
181
183
  \`\`\`
182
184
 
183
- 6. Build or run the extension separately. It is not deployed by Vercel:
185
+ Run deployment readiness checks before pushing:
184
186
 
185
187
  \`\`\`bash
188
+ pnpm verify
189
+ pnpm verify:frontend
190
+ pnpm verify:backend
191
+ pnpm verify:extension
192
+ \`\`\`
193
+
194
+ 7. Build or run the extension separately. It is not deployed by Vercel:
195
+
196
+ \`\`\`bash
197
+ pnpm dev:extension
198
+ pnpm extension:zip
199
+ \`\`\`
200
+ `);
201
+ }
202
+ function writeProductionGuide(root, options) {
203
+ writeFile(path.join(root, 'PRODUCTION.md'), `# ${options.displayName} Production Guide
204
+
205
+ ## Project Structure
206
+
207
+ The generated app is one pnpm monorepo:
208
+
209
+ \`\`\`txt
210
+ .
211
+ ├─ Frontend/ Expo + React Native Reusables app
212
+ ├─ Backend/ Fastify API
213
+ ├─ Extension/ WXT Chrome extension
214
+ ├─ packages/
215
+ │ ├─ api-client/ shared typed API client
216
+ │ ├─ config/ shared config constants/helpers
217
+ │ ├─ db/ Drizzle schema, Neon client, migrations
218
+ │ └─ types/ shared TypeScript types
219
+ ├─ PRODUCTION.md development and deployment guide
220
+ ├─ turbo.json workspace build graph
221
+ └─ package.json root scripts
222
+ \`\`\`
223
+
224
+ ## Frontend
225
+
226
+ \`Frontend\` is the single Expo source for:
227
+
228
+ - Web on Vercel
229
+ - iOS through EAS
230
+ - Android through EAS
231
+
232
+ Important files:
233
+
234
+ - \`Frontend/package.json\`: Expo scripts
235
+ - \`Frontend/vercel.json\`: Vercel web export config
236
+ - \`Frontend/eas.json\`: native build profiles
237
+ - \`Frontend/app\`: Expo Router screens
238
+
239
+ Build output goes to \`Frontend/dist\`.
240
+
241
+ ## Backend
242
+
243
+ \`Backend\` is the Fastify API.
244
+
245
+ Important files:
246
+
247
+ - \`Backend/src/index.ts\`: Vercel Fastify entrypoint
248
+ - \`Backend/src/app.ts\`: Fastify app construction
249
+ - \`Backend/vercel.json\`: minimal Vercel config
250
+ - \`Backend/src/routes\`: health/auth/subscription routes
251
+
252
+ ## Extension
253
+
254
+ \`Extension\` is the WXT Chrome extension using the Side Panel API.
255
+
256
+ Important files:
257
+
258
+ - \`Extension/wxt.config.ts\`: manifest, side panel, permissions
259
+ - \`Extension/src/entrypoints/sidepanel\`: side panel UI
260
+ - \`Extension/src/entrypoints/background.ts\`: side panel behavior
261
+
262
+ ZIP output goes to \`Extension/.output\`.
263
+
264
+ ## Develop
265
+
266
+ From the repository root:
267
+
268
+ \`\`\`sh
269
+ pnpm install
270
+ pnpm dev
271
+ \`\`\`
272
+
273
+ Or run one surface at a time:
274
+
275
+ \`\`\`sh
276
+ pnpm dev:frontend
277
+ pnpm dev:backend
186
278
  pnpm dev:extension
279
+ \`\`\`
280
+
281
+ Use these before deploy:
282
+
283
+ \`\`\`sh
284
+ pnpm verify
285
+ pnpm verify:frontend
286
+ pnpm verify:backend
287
+ pnpm verify:extension
288
+ \`\`\`
289
+
290
+ Database workflow:
291
+
292
+ \`\`\`sh
293
+ pnpm db:generate
294
+ pnpm db:migrate
295
+ \`\`\`
296
+
297
+ Local demo mode is enabled through generated \`.env\` files. Real provider behavior needs Clerk, RevenueCat/Stripe, Neon, and R2 credentials.
298
+
299
+ ## Environment Variables
300
+
301
+ - Root/local: \`DATABASE_URL\` for shared Drizzle commands.
302
+ - Frontend/Vercel Frontend: only \`EXPO_PUBLIC_*\` values.
303
+ - Backend/Vercel Backend: server secrets such as \`DATABASE_URL\`, \`CLERK_SECRET_KEY\`, \`RC_SECRET_API_KEY\`, \`RC_WEBHOOK_SECRET\`, and R2 credentials.
304
+ - Extension: \`VITE_API_URL\`, \`VITE_CLERK_PUBLISHABLE_KEY\`, and optionally \`VITE_CRX_PUBLIC_KEY\`.
305
+ - EAS: add native app public keys with \`eas secret:create\` or the EAS dashboard.
306
+
307
+ ## Deploy
308
+
309
+ Vercel deployment is two Vercel projects from the same Git repo:
310
+
311
+ 1. Frontend Vercel project
312
+ - Root Directory: \`Frontend\`
313
+ - Build command: \`pnpm build:web\`
314
+ - Output directory: \`dist\`
315
+
316
+ 2. Backend Vercel project
317
+ - Root Directory: \`Backend\`
318
+ - Build command: \`pnpm build\`
319
+ - Fastify entrypoint: \`src/index.ts\`
320
+
321
+ Enable Vercel access to source files outside each project root so \`packages/*\` workspace packages resolve.
322
+
323
+ Native app deployment:
324
+
325
+ \`\`\`sh
326
+ cd Frontend
327
+ pnpm dlx eas-cli@latest login
328
+ pnpm dlx eas-cli@latest init
329
+ pnpm dlx eas-cli@latest build --platform all --profile production
330
+ pnpm dlx eas-cli@latest submit --platform all --latest --profile production
331
+ \`\`\`
332
+
333
+ Chrome extension deployment:
334
+
335
+ \`\`\`sh
187
336
  pnpm extension:zip
188
337
  \`\`\`
338
+
339
+ Upload \`Extension/.output/*-chrome.zip\` to the Chrome Web Store Developer Dashboard.
340
+
341
+ ## Provider Checklist
342
+
343
+ - Clerk: configure allowed origins for the Expo web Vercel domain, native redirect/deep-link settings for the Expo scheme in \`Frontend/app.json\`, and the Chrome extension origin after publishing or after assigning a stable extension key.
344
+ - RevenueCat + Stripe: create a \`pro\` entitlement, configure iOS/Android/Web app API keys separately, connect Stripe as the web billing source, set the RevenueCat webhook URL to \`https://<backend-domain>/webhooks/revenuecat\`, and keep \`RC_WEBHOOK_SECRET\` only in Backend.
345
+ - Neon + Drizzle: create the Neon database, set \`DATABASE_URL\` locally and in the Backend Vercel project, then run \`pnpm db:generate\` and \`pnpm db:migrate\` before production traffic.
346
+ - Cloudflare R2: create an R2 bucket and least-privilege API token, then set \`R2_ACCOUNT_ID\`, \`R2_ACCESS_KEY_ID\`, \`R2_SECRET_ACCESS_KEY\`, and \`R2_BUCKET\` in Backend only.
347
+
348
+ ## Relevant Docs
349
+
350
+ - [Vercel monorepos](https://vercel.com/docs/monorepos)
351
+ - [Expo web publishing](https://docs.expo.dev/guides/publishing-websites/)
352
+ - [Expo EAS](https://docs.expo.dev/eas/)
353
+ - [WXT publishing](https://wxt.dev/guide/essentials/publishing.html)
354
+ - [Chrome Web Store publishing](https://developer.chrome.com/docs/webstore/publish)
189
355
  `);
190
356
  }
191
357
  function writeStackFile(root, stack) {
@@ -755,9 +921,9 @@ R2_BUCKET=
755
921
  ${paymentsEnv}`);
756
922
  }
757
923
  function writeRootVercelFiles(root) {
758
- writeFile(path.join(root, '.vercelignore'), `apps/extension/.output
759
- apps/extension/.wxt
760
- apps/extension/dist
924
+ writeFile(path.join(root, '.vercelignore'), `Extension/.output
925
+ Extension/.wxt
926
+ Extension/dist
761
927
  `);
762
928
  }
763
929
  function writeGeneratedCiWorkflow(root) {
@@ -799,22 +965,22 @@ jobs:
799
965
  run: pnpm install --frozen-lockfile
800
966
 
801
967
  - name: Check Expo dependency alignment
802
- run: pnpm --filter ./apps/frontend exec expo install --check
968
+ run: pnpm --filter ./Frontend exec expo install --check
803
969
 
804
970
  - name: Build Expo web
805
- run: pnpm --filter ./apps/frontend build:web
971
+ run: pnpm --filter ./Frontend build:web
806
972
 
807
973
  - name: Test API
808
- run: pnpm --filter ./apps/api test
974
+ run: pnpm --filter ./Backend test
809
975
 
810
976
  - name: Build API
811
- run: pnpm --filter ./apps/api build
977
+ run: pnpm --filter ./Backend build
812
978
 
813
979
  - name: Build extension
814
- run: pnpm --filter ./apps/extension build
980
+ run: pnpm --filter ./Extension build
815
981
 
816
982
  - name: Zip extension
817
- run: pnpm --filter ./apps/extension zip
983
+ run: pnpm --filter ./Extension zip
818
984
 
819
985
  - name: Build workspace
820
986
  run: pnpm build
@@ -822,32 +988,36 @@ jobs:
822
988
  }
823
989
  function writeFullstackRootFiles(root, options) {
824
990
  const appFilters = [
825
- './apps/frontend',
826
- './apps/api',
827
- './apps/extension',
991
+ './Frontend',
992
+ './Backend',
993
+ './Extension',
828
994
  ];
829
995
  const parallelFilters = appFilters.map((entry) => `--filter=${entry}`).join(' ');
830
996
  const scripts = {
831
997
  dev: `turbo dev --parallel ${parallelFilters}`,
832
- 'dev:api': 'pnpm --filter ./apps/api dev',
998
+ 'dev:backend': 'pnpm --filter ./Backend dev',
833
999
  build: 'turbo build',
834
1000
  typecheck: 'turbo typecheck',
1001
+ verify: 'pnpm verify:frontend && pnpm verify:backend && pnpm verify:extension',
1002
+ 'verify:frontend': 'pnpm --filter ./Frontend exec expo install --check && pnpm --filter ./Frontend build:web',
1003
+ 'verify:backend': 'pnpm --filter ./Backend test && pnpm --filter ./Backend build',
1004
+ 'verify:extension': 'pnpm --filter ./Extension typecheck && pnpm --filter ./Extension zip',
835
1005
  'db:generate': 'pnpm --filter @anhedral/db db:generate',
836
1006
  'db:migrate': 'pnpm --filter @anhedral/db db:migrate',
837
1007
  };
838
- scripts['dev:frontend'] = 'pnpm --filter ./apps/frontend dev';
839
- scripts['dev:extension'] = 'pnpm --filter ./apps/extension dev';
840
- scripts['extension:zip'] = 'pnpm --filter ./apps/extension zip';
841
- writeRootPackageJson(root, options.projectName, scripts, ['apps/*', 'packages/*'], {
1008
+ scripts['dev:frontend'] = 'pnpm --filter ./Frontend dev';
1009
+ scripts['dev:extension'] = 'pnpm --filter ./Extension dev';
1010
+ scripts['extension:zip'] = 'pnpm --filter ./Extension zip';
1011
+ writeRootPackageJson(root, options.projectName, scripts, ['Frontend', 'Backend', 'Extension', 'packages/*'], {
842
1012
  devDependencies: ROOT_DEPENDENCIES.devDependencies,
843
1013
  });
844
- writePnpmWorkspace(root, ['apps/*', 'packages/*']);
1014
+ writePnpmWorkspace(root, ['Frontend', 'Backend', 'Extension', 'packages/*']);
845
1015
  writeFile(path.join(root, 'turbo.json'), JSON.stringify({
846
1016
  $schema: 'https://turborepo.dev/schema.json',
847
1017
  tasks: {
848
1018
  build: {
849
1019
  dependsOn: ['^build'],
850
- outputs: ['dist/**', '.output/**'],
1020
+ outputs: [],
851
1021
  },
852
1022
  typecheck: {
853
1023
  dependsOn: ['^build'],
@@ -861,7 +1031,9 @@ function writeFullstackRootFiles(root, options) {
861
1031
  appendGitignore(root, [
862
1032
  'node_modules',
863
1033
  '.turbo',
864
- 'apps/*/node_modules',
1034
+ 'Frontend/node_modules',
1035
+ 'Backend/node_modules',
1036
+ 'Extension/node_modules',
865
1037
  'packages/*/node_modules',
866
1038
  ...SHARED_ENV_GITIGNORE_LINES,
867
1039
  ...SHARED_TYPESCRIPT_GITIGNORE_LINES,
@@ -903,9 +1075,10 @@ async function scaffoldFullstack(root, options) {
903
1075
  });
904
1076
  return [
905
1077
  '.github/workflows/ci.yml',
906
- 'apps/frontend',
907
- 'apps/api',
908
- 'apps/extension',
1078
+ 'PRODUCTION.md',
1079
+ 'Frontend',
1080
+ 'Backend',
1081
+ 'Extension',
909
1082
  'packages/db',
910
1083
  'packages/types',
911
1084
  'packages/config',
@@ -927,6 +1100,7 @@ export async function scaffoldProject(options) {
927
1100
  writeSkillsGuide(root, skillCommands);
928
1101
  const stack = normalizeStack(options, generatedPaths);
929
1102
  writeRootDocs(root, options, stack);
1103
+ writeProductionGuide(root, options);
930
1104
  writeStackFile(root, stack);
931
1105
  anhedralPrint.done('Project metadata written');
932
1106
  console.log('');
@@ -938,9 +1112,10 @@ export async function scaffoldProject(options) {
938
1112
  anhedralPrint.info('Next commands:');
939
1113
  console.log(' pnpm install');
940
1114
  console.log(' cp .env.example .env');
941
- console.log(' cp apps/extension/.env.example apps/extension/.env');
1115
+ console.log(' cp Extension/.env.example Extension/.env');
942
1116
  console.log(' pnpm db:generate && pnpm db:migrate');
943
- console.log(' pnpm dev:api');
1117
+ console.log(' pnpm verify');
1118
+ console.log(' pnpm dev:backend');
944
1119
  console.log(' pnpm dev:frontend');
945
1120
  console.log(' pnpm dev:extension');
946
1121
  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, 'apps/api');
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
- version: 2,
2214
- builds: [
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 appsRoot = path.join(root, 'apps');
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
- writeFile(path.join(appsRoot, '.gitkeep'), '');
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, 'apps/frontend');
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 ./apps/frontend exec expo install --pnpm expo-image-picker');
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 ./apps/frontend add ${revenueCatDependencies.join(' ')}`);
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/clerk-expo': FRONTEND_ADDON_DEPENDENCIES['@clerk/clerk-expo'],
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/clerk-expo';
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: '/index.html',
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/clerk-expo';
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/clerk-expo';
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/clerk-expo';
702
- import { tokenCache } from '@clerk/clerk-expo/token-cache';
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/clerk-expo';
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/clerk-expo';
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/clerk-expo';
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/clerk-expo';
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';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anhedral",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Opinionated init CLI for Expo, Fastify, WXT, shared packages, Neon, Drizzle, Clerk, RevenueCat, Stripe, R2, and Vercel.",
5
5
  "type": "module",
6
6
  "bin": {