create-better-t-stack 2.45.4 → 2.46.0-canary.85c43fef

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.
Files changed (75) hide show
  1. package/dist/cli.js +1 -1
  2. package/dist/index.d.ts +10 -0
  3. package/dist/index.js +1 -1
  4. package/dist/{src-Cun9EO6e.js → src-lN80CwOs.js} +396 -162
  5. package/package.json +1 -1
  6. package/templates/auth/better-auth/convex/backend/convex/auth.config.ts.hbs +8 -0
  7. package/templates/auth/better-auth/convex/backend/convex/auth.ts.hbs +48 -0
  8. package/templates/auth/better-auth/convex/backend/convex/convex.config.ts.hbs +7 -0
  9. package/templates/auth/better-auth/convex/backend/convex/http.ts.hbs +12 -0
  10. package/templates/auth/better-auth/convex/backend/convex/privateData.ts.hbs +16 -0
  11. package/templates/auth/better-auth/convex/web/react/next/src/app/api/auth/[...all]/route.ts.hbs +3 -0
  12. package/templates/auth/better-auth/convex/web/react/next/src/app/dashboard/page.tsx.hbs +40 -0
  13. package/templates/auth/better-auth/convex/web/react/next/src/components/sign-in-form.tsx.hbs +129 -0
  14. package/templates/auth/better-auth/convex/web/react/next/src/components/sign-up-form.tsx.hbs +154 -0
  15. package/templates/auth/better-auth/convex/web/react/next/src/components/user-menu.tsx.hbs +48 -0
  16. package/templates/auth/better-auth/convex/web/react/next/src/lib/auth-client.ts.hbs +6 -0
  17. package/templates/auth/better-auth/convex/web/react/next/src/lib/auth-server.ts.hbs +6 -0
  18. package/templates/auth/better-auth/convex/web/react/tanstack-router/src/lib/auth-client.ts.hbs +10 -0
  19. package/templates/auth/better-auth/convex/web/react/tanstack-router/src/routes/dashboard.tsx.hbs +43 -0
  20. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/components/sign-in-form.tsx.hbs +133 -0
  21. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/components/sign-up-form.tsx.hbs +158 -0
  22. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/components/user-menu.tsx.hbs +50 -0
  23. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/lib/auth-client.ts.hbs +6 -0
  24. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/lib/auth-server.ts.hbs +5 -0
  25. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/routes/api/auth/$.ts.hbs +11 -0
  26. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/routes/dashboard.tsx.hbs +43 -0
  27. package/templates/auth/better-auth/server/base/src/lib/auth.ts.hbs +163 -16
  28. package/templates/auth/better-auth/web/nuxt/app/pages/dashboard.vue.hbs +36 -2
  29. package/templates/auth/better-auth/web/nuxt/app/plugins/auth-client.ts.hbs +22 -0
  30. package/templates/auth/better-auth/web/react/base/src/lib/auth-client.ts.hbs +6 -0
  31. package/templates/auth/better-auth/web/react/next/src/app/dashboard/dashboard.tsx.hbs +58 -0
  32. package/templates/auth/better-auth/web/react/next/src/app/dashboard/page.tsx.hbs +31 -41
  33. package/templates/auth/better-auth/web/react/react-router/src/routes/dashboard.tsx.hbs +37 -3
  34. package/templates/auth/better-auth/web/react/tanstack-router/src/routes/dashboard.tsx.hbs +50 -32
  35. package/templates/auth/better-auth/web/react/tanstack-start/src/routes/dashboard.tsx.hbs +51 -36
  36. package/templates/auth/better-auth/web/solid/src/lib/auth-client.ts.hbs +11 -0
  37. package/templates/auth/better-auth/web/solid/src/routes/dashboard.tsx.hbs +52 -29
  38. package/templates/auth/better-auth/web/svelte/src/lib/{auth-client.ts → auth-client.ts.hbs} +6 -0
  39. package/templates/auth/better-auth/web/svelte/src/routes/dashboard/+page.svelte.hbs +24 -3
  40. package/templates/backend/server/server-base/package.json.hbs +1 -1
  41. package/templates/extras/bunfig.toml.hbs +4 -0
  42. package/templates/frontend/react/next/src/components/providers.tsx.hbs +8 -0
  43. package/templates/frontend/react/tanstack-router/src/main.tsx.hbs +8 -1
  44. package/templates/frontend/react/tanstack-start/src/routes/__root.tsx.hbs +51 -0
  45. package/templates/frontend/react/tanstack-start/src/routes/index.tsx.hbs +2 -2
  46. package/templates/frontend/react/web-base/src/components/header.tsx.hbs +2 -2
  47. package/templates/frontend/solid/package.json.hbs +10 -10
  48. package/templates/payments/polar/server/base/src/lib/payments.ts.hbs +6 -0
  49. package/templates/payments/polar/web/nuxt/app/pages/success.vue.hbs +11 -0
  50. package/templates/payments/polar/web/react/next/src/app/success/page.tsx.hbs +15 -0
  51. package/templates/payments/polar/web/react/react-router/src/routes/success.tsx.hbs +13 -0
  52. package/templates/payments/polar/web/react/tanstack-router/src/routes/success.tsx.hbs +19 -0
  53. package/templates/payments/polar/web/react/tanstack-start/src/routes/success.tsx.hbs +19 -0
  54. package/templates/payments/polar/web/solid/src/routes/success.tsx.hbs +23 -0
  55. package/templates/payments/polar/web/svelte/src/routes/success/+page.svelte.hbs +12 -0
  56. package/templates/auth/better-auth/web/nuxt/app/plugins/auth-client.ts +0 -16
  57. package/templates/auth/better-auth/web/react/next/src/components/theme-provider.tsx +0 -11
  58. package/templates/auth/better-auth/web/solid/src/lib/auth-client.ts +0 -5
  59. /package/templates/auth/better-auth/web/nuxt/app/components/{SignInForm.vue → SignInForm.vue.hbs} +0 -0
  60. /package/templates/auth/better-auth/web/nuxt/app/components/{SignUpForm.vue → SignUpForm.vue.hbs} +0 -0
  61. /package/templates/auth/better-auth/web/nuxt/app/components/{UserMenu.vue → UserMenu.vue.hbs} +0 -0
  62. /package/templates/auth/better-auth/web/nuxt/app/pages/{login.vue → login.vue.hbs} +0 -0
  63. /package/templates/auth/better-auth/web/react/next/src/app/login/{page.tsx → page.tsx.hbs} +0 -0
  64. /package/templates/auth/better-auth/web/react/next/src/components/{sign-in-form.tsx → sign-in-form.tsx.hbs} +0 -0
  65. /package/templates/auth/better-auth/web/react/next/src/components/{sign-up-form.tsx → sign-up-form.tsx.hbs} +0 -0
  66. /package/templates/auth/better-auth/web/react/next/src/components/{user-menu.tsx → user-menu.tsx.hbs} +0 -0
  67. /package/templates/auth/better-auth/web/react/react-router/src/components/{sign-in-form.tsx → sign-in-form.tsx.hbs} +0 -0
  68. /package/templates/auth/better-auth/web/react/react-router/src/components/{sign-up-form.tsx → sign-up-form.tsx.hbs} +0 -0
  69. /package/templates/auth/better-auth/web/react/react-router/src/components/{user-menu.tsx → user-menu.tsx.hbs} +0 -0
  70. /package/templates/auth/better-auth/web/react/react-router/src/routes/{login.tsx → login.tsx.hbs} +0 -0
  71. /package/templates/auth/better-auth/web/svelte/src/components/{SignInForm.svelte → SignInForm.svelte.hbs} +0 -0
  72. /package/templates/auth/better-auth/web/svelte/src/components/{SignUpForm.svelte → SignUpForm.svelte.hbs} +0 -0
  73. /package/templates/auth/better-auth/web/svelte/src/components/{UserMenu.svelte → UserMenu.svelte.hbs} +0 -0
  74. /package/templates/auth/better-auth/web/svelte/src/routes/login/{+page.svelte → +page.svelte.hbs} +0 -0
  75. /package/templates/frontend/react/web-base/src/components/{loader.tsx → loader.tsx.hbs} +0 -0
@@ -36,6 +36,7 @@ const DEFAULT_CONFIG_BASE = {
36
36
  database: "sqlite",
37
37
  orm: "drizzle",
38
38
  auth: "better-auth",
39
+ payments: "none",
39
40
  addons: ["turborepo"],
40
41
  examples: [],
41
42
  git: true,
@@ -59,8 +60,8 @@ function getDefaultConfig() {
59
60
  }
60
61
  const DEFAULT_CONFIG = getDefaultConfig();
61
62
  const dependencyVersionMap = {
62
- "better-auth": "^1.3.9",
63
- "@better-auth/expo": "^1.3.9",
63
+ "better-auth": "^1.3.10",
64
+ "@better-auth/expo": "^1.3.10",
64
65
  "@clerk/nextjs": "^6.31.5",
65
66
  "@clerk/clerk-react": "^5.45.0",
66
67
  "@clerk/tanstack-react-start": "^0.23.1",
@@ -125,15 +126,16 @@ const dependencyVersionMap = {
125
126
  "convex-svelte": "^0.0.11",
126
127
  "convex-nuxt": "0.1.5",
127
128
  "convex-vue": "^0.1.5",
129
+ "@convex-dev/better-auth": "^0.8.4",
128
130
  "@tanstack/svelte-query": "^5.85.3",
129
131
  "@tanstack/svelte-query-devtools": "^5.85.3",
130
132
  "@tanstack/vue-query-devtools": "^5.83.0",
131
133
  "@tanstack/vue-query": "^5.83.0",
132
134
  "@tanstack/react-query-devtools": "^5.85.5",
133
135
  "@tanstack/react-query": "^5.85.5",
134
- "@tanstack/solid-query": "^5.75.0",
135
- "@tanstack/solid-query-devtools": "^5.75.0",
136
- "@tanstack/solid-router-devtools": "^1.131.25",
136
+ "@tanstack/solid-query": "^5.87.4",
137
+ "@tanstack/solid-query-devtools": "^5.87.4",
138
+ "@tanstack/solid-router-devtools": "^1.131.44",
137
139
  wrangler: "^4.23.0",
138
140
  "@cloudflare/vite-plugin": "^1.9.0",
139
141
  "@opennextjs/cloudflare": "^1.6.5",
@@ -142,7 +144,9 @@ const dependencyVersionMap = {
142
144
  "@cloudflare/workers-types": "^4.20250822.0",
143
145
  alchemy: "^0.65.1",
144
146
  nitropack: "^2.12.4",
145
- dotenv: "^17.2.1"
147
+ dotenv: "^17.2.1",
148
+ "@polar-sh/better-auth": "^1.1.3",
149
+ "@polar-sh/sdk": "^0.34.16"
146
150
  };
147
151
  const ADDON_COMPATIBILITY = {
148
152
  pwa: [
@@ -256,6 +260,7 @@ const AuthSchema = z.enum([
256
260
  "clerk",
257
261
  "none"
258
262
  ]).describe("Authentication provider");
263
+ const PaymentsSchema = z.enum(["polar", "none"]).describe("Payments provider");
259
264
  const ProjectNameSchema = z.string().min(1, "Project name cannot be empty").max(255, "Project name must be less than 255 characters").refine((name) => name === "." || !name.startsWith("."), "Project name cannot start with a dot (except for '.')").refine((name) => name === "." || !name.startsWith("-"), "Project name cannot start with a dash").refine((name) => {
260
265
  const invalidChars = [
261
266
  "<",
@@ -286,28 +291,16 @@ const DirectoryConflictSchema = z.enum([
286
291
  ]).describe("How to handle existing directory conflicts");
287
292
 
288
293
  //#endregion
289
- //#region src/utils/addon-compatibility.ts
290
- function validateAddonCompatibility(addon, frontend) {
291
- const compatibleFrontends = ADDON_COMPATIBILITY[addon];
292
- if (compatibleFrontends.length === 0) return { isCompatible: true };
293
- const hasCompatibleFrontend = frontend.some((f) => compatibleFrontends.includes(f));
294
- if (!hasCompatibleFrontend) {
295
- const frontendList = compatibleFrontends.join(", ");
296
- return {
297
- isCompatible: false,
298
- reason: `${addon} addon requires one of these frontends: ${frontendList}`
299
- };
300
- }
301
- return { isCompatible: true };
302
- }
303
- function getCompatibleAddons(allAddons, frontend, existingAddons = []) {
304
- return allAddons.filter((addon) => {
305
- if (existingAddons.includes(addon)) return false;
306
- if (addon === "none") return false;
307
- const { isCompatible } = validateAddonCompatibility(addon, frontend);
308
- return isCompatible;
309
- });
310
- }
294
+ //#region src/utils/compatibility.ts
295
+ const WEB_FRAMEWORKS = [
296
+ "tanstack-router",
297
+ "react-router",
298
+ "tanstack-start",
299
+ "next",
300
+ "nuxt",
301
+ "svelte",
302
+ "solid"
303
+ ];
311
304
 
312
305
  //#endregion
313
306
  //#region src/utils/errors.ts
@@ -331,6 +324,120 @@ function handleError(error, fallbackMessage) {
331
324
  process.exit(1);
332
325
  }
333
326
 
327
+ //#endregion
328
+ //#region src/utils/compatibility-rules.ts
329
+ function isWebFrontend(value) {
330
+ return WEB_FRAMEWORKS.includes(value);
331
+ }
332
+ function splitFrontends(values = []) {
333
+ const web = values.filter((f) => isWebFrontend(f));
334
+ const native = values.filter((f) => f === "native-nativewind" || f === "native-unistyles");
335
+ return {
336
+ web,
337
+ native
338
+ };
339
+ }
340
+ function ensureSingleWebAndNative(frontends) {
341
+ const { web, native } = splitFrontends(frontends);
342
+ if (web.length > 1) exitWithError("Cannot select multiple web frameworks. Choose only one of: tanstack-router, tanstack-start, react-router, next, nuxt, svelte, solid");
343
+ if (native.length > 1) exitWithError("Cannot select multiple native frameworks. Choose only one of: native-nativewind, native-unistyles");
344
+ }
345
+ function validateWorkersCompatibility(providedFlags, options, config) {
346
+ if (providedFlags.has("runtime") && options.runtime === "workers" && config.backend && config.backend !== "hono") exitWithError(`Cloudflare Workers runtime (--runtime workers) is only supported with Hono backend (--backend hono). Current backend: ${config.backend}. Please use '--backend hono' or choose a different runtime.`);
347
+ if (providedFlags.has("backend") && config.backend && config.backend !== "hono" && config.runtime === "workers") exitWithError(`Backend '${config.backend}' is not compatible with Cloudflare Workers runtime. Cloudflare Workers runtime is only supported with Hono backend. Please use '--backend hono' or choose a different runtime.`);
348
+ if (providedFlags.has("runtime") && options.runtime === "workers" && config.database === "mongodb") exitWithError("Cloudflare Workers runtime (--runtime workers) is not compatible with MongoDB database. MongoDB requires Prisma or Mongoose ORM, but Workers runtime only supports Drizzle or Prisma ORM. Please use a different database or runtime.");
349
+ if (providedFlags.has("runtime") && options.runtime === "workers" && config.dbSetup === "docker") exitWithError("Cloudflare Workers runtime (--runtime workers) is not compatible with Docker setup. Workers runtime uses serverless databases (D1) and doesn't support local Docker containers. Please use '--db-setup d1' for SQLite or choose a different runtime.");
350
+ if (providedFlags.has("database") && config.database === "mongodb" && config.runtime === "workers") exitWithError("MongoDB database is not compatible with Cloudflare Workers runtime. MongoDB requires Prisma or Mongoose ORM, but Workers runtime only supports Drizzle or Prisma ORM. Please use a different database or runtime.");
351
+ if (providedFlags.has("dbSetup") && options.dbSetup === "docker" && config.runtime === "workers") exitWithError("Docker setup (--db-setup docker) is not compatible with Cloudflare Workers runtime. Workers runtime uses serverless databases (D1) and doesn't support local Docker containers. Please use '--db-setup d1' for SQLite or choose a different runtime.");
352
+ }
353
+ function validateApiFrontendCompatibility(api, frontends = []) {
354
+ const includesNuxt = frontends.includes("nuxt");
355
+ const includesSvelte = frontends.includes("svelte");
356
+ const includesSolid = frontends.includes("solid");
357
+ if ((includesNuxt || includesSvelte || includesSolid) && api === "trpc") exitWithError(`tRPC API is not supported with '${includesNuxt ? "nuxt" : includesSvelte ? "svelte" : "solid"}' frontend. Please use --api orpc or --api none or remove '${includesNuxt ? "nuxt" : includesSvelte ? "svelte" : "solid"}' from --frontend.`);
358
+ }
359
+ function isFrontendAllowedWithBackend(frontend, backend, auth) {
360
+ if (backend === "convex" && frontend === "solid") return false;
361
+ if (auth === "clerk" && backend === "convex") {
362
+ const incompatibleFrontends = [
363
+ "nuxt",
364
+ "svelte",
365
+ "solid"
366
+ ];
367
+ if (incompatibleFrontends.includes(frontend)) return false;
368
+ }
369
+ return true;
370
+ }
371
+ function allowedApisForFrontends(frontends = []) {
372
+ const includesNuxt = frontends.includes("nuxt");
373
+ const includesSvelte = frontends.includes("svelte");
374
+ const includesSolid = frontends.includes("solid");
375
+ const base = [
376
+ "trpc",
377
+ "orpc",
378
+ "none"
379
+ ];
380
+ if (includesNuxt || includesSvelte || includesSolid) return ["orpc", "none"];
381
+ return base;
382
+ }
383
+ function isExampleTodoAllowed(backend, database) {
384
+ return !(backend !== "convex" && backend !== "none" && database === "none");
385
+ }
386
+ function isExampleAIAllowed(_backend, frontends = []) {
387
+ const includesSolid = frontends.includes("solid");
388
+ if (includesSolid) return false;
389
+ return true;
390
+ }
391
+ function validateWebDeployRequiresWebFrontend(webDeploy, hasWebFrontendFlag) {
392
+ if (webDeploy && webDeploy !== "none" && !hasWebFrontendFlag) exitWithError("'--web-deploy' requires a web frontend. Please select a web frontend or set '--web-deploy none'.");
393
+ }
394
+ function validateServerDeployRequiresBackend(serverDeploy, backend) {
395
+ if (serverDeploy && serverDeploy !== "none" && (!backend || backend === "none")) exitWithError("'--server-deploy' requires a backend. Please select a backend or set '--server-deploy none'.");
396
+ }
397
+ function validateAddonCompatibility(addon, frontend, _auth) {
398
+ const compatibleFrontends = ADDON_COMPATIBILITY[addon];
399
+ if (compatibleFrontends.length > 0) {
400
+ const hasCompatibleFrontend = frontend.some((f) => compatibleFrontends.includes(f));
401
+ if (!hasCompatibleFrontend) {
402
+ const frontendList = compatibleFrontends.join(", ");
403
+ return {
404
+ isCompatible: false,
405
+ reason: `${addon} addon requires one of these frontends: ${frontendList}`
406
+ };
407
+ }
408
+ }
409
+ return { isCompatible: true };
410
+ }
411
+ function getCompatibleAddons(allAddons, frontend, existingAddons = [], auth) {
412
+ return allAddons.filter((addon) => {
413
+ if (existingAddons.includes(addon)) return false;
414
+ if (addon === "none") return false;
415
+ const { isCompatible } = validateAddonCompatibility(addon, frontend, auth);
416
+ return isCompatible;
417
+ });
418
+ }
419
+ function validateAddonsAgainstFrontends(addons = [], frontends = [], auth) {
420
+ for (const addon of addons) {
421
+ if (addon === "none") continue;
422
+ const { isCompatible, reason } = validateAddonCompatibility(addon, frontends, auth);
423
+ if (!isCompatible) exitWithError(`Incompatible addon/frontend combination: ${reason}`);
424
+ }
425
+ }
426
+ function validatePaymentsCompatibility(payments, auth, _backend, frontends = []) {
427
+ if (!payments || payments === "none") return;
428
+ if (payments === "polar") {
429
+ if (!auth || auth === "none" || auth !== "better-auth") exitWithError("Polar payments requires Better Auth. Please use '--auth better-auth' or choose a different payments provider.");
430
+ const { web } = splitFrontends(frontends);
431
+ if (web.length === 0 && frontends.length > 0) exitWithError("Polar payments requires a web frontend or no frontend. Please select a web frontend or choose a different payments provider.");
432
+ }
433
+ }
434
+ function validateExamplesCompatibility(examples, backend, database, frontend) {
435
+ const examplesArr = examples ?? [];
436
+ if (examplesArr.length === 0 || examplesArr.includes("none")) return;
437
+ if (examplesArr.includes("todo") && backend !== "convex" && backend !== "none" && database === "none") exitWithError("The 'todo' example requires a database if a backend (other than Convex) is present. Cannot use --examples todo when database is 'none' and a backend is selected.");
438
+ if (examplesArr.includes("ai") && (frontend ?? []).includes("solid")) exitWithError("The 'ai' example is not compatible with the Solid frontend.");
439
+ }
440
+
334
441
  //#endregion
335
442
  //#region src/prompts/addons.ts
336
443
  function getAddonDisplay(addon) {
@@ -401,7 +508,7 @@ const ADDON_GROUPS = {
401
508
  "husky"
402
509
  ]
403
510
  };
404
- async function getAddonsChoice(addons, frontends) {
511
+ async function getAddonsChoice(addons, frontends, auth) {
405
512
  if (addons !== void 0) return addons;
406
513
  const allAddons = AddonsSchema.options.filter((addon) => addon !== "none");
407
514
  const groupedOptions = {
@@ -411,7 +518,7 @@ async function getAddonsChoice(addons, frontends) {
411
518
  };
412
519
  const frontendsArray = frontends || [];
413
520
  for (const addon of allAddons) {
414
- const { isCompatible } = validateAddonCompatibility(addon, frontendsArray);
521
+ const { isCompatible } = validateAddonCompatibility(addon, frontendsArray, auth);
415
522
  if (!isCompatible) continue;
416
523
  const { label, hint } = getAddonDisplay(addon);
417
524
  const option = {
@@ -437,14 +544,14 @@ async function getAddonsChoice(addons, frontends) {
437
544
  if (isCancel(response)) return exitCancelled("Operation cancelled");
438
545
  return response;
439
546
  }
440
- async function getAddonsToAdd(frontend, existingAddons = []) {
547
+ async function getAddonsToAdd(frontend, existingAddons = [], auth) {
441
548
  const groupedOptions = {
442
549
  Documentation: [],
443
550
  Linting: [],
444
551
  Other: []
445
552
  };
446
553
  const frontendArray = frontend || [];
447
- const compatibleAddons = getCompatibleAddons(AddonsSchema.options.filter((addon) => addon !== "none"), frontendArray, existingAddons);
554
+ const compatibleAddons = getCompatibleAddons(AddonsSchema.options.filter((addon) => addon !== "none"), frontendArray, existingAddons, auth);
448
555
  for (const addon of compatibleAddons) {
449
556
  const { label, hint } = getAddonDisplay(addon);
450
557
  const option = {
@@ -470,102 +577,6 @@ async function getAddonsToAdd(frontend, existingAddons = []) {
470
577
  return response;
471
578
  }
472
579
 
473
- //#endregion
474
- //#region src/utils/compatibility.ts
475
- const WEB_FRAMEWORKS = [
476
- "tanstack-router",
477
- "react-router",
478
- "tanstack-start",
479
- "next",
480
- "nuxt",
481
- "svelte",
482
- "solid"
483
- ];
484
-
485
- //#endregion
486
- //#region src/utils/compatibility-rules.ts
487
- function isWebFrontend(value) {
488
- return WEB_FRAMEWORKS.includes(value);
489
- }
490
- function splitFrontends(values = []) {
491
- const web = values.filter((f) => isWebFrontend(f));
492
- const native = values.filter((f) => f === "native-nativewind" || f === "native-unistyles");
493
- return {
494
- web,
495
- native
496
- };
497
- }
498
- function ensureSingleWebAndNative(frontends) {
499
- const { web, native } = splitFrontends(frontends);
500
- if (web.length > 1) exitWithError("Cannot select multiple web frameworks. Choose only one of: tanstack-router, tanstack-start, react-router, next, nuxt, svelte, solid");
501
- if (native.length > 1) exitWithError("Cannot select multiple native frameworks. Choose only one of: native-nativewind, native-unistyles");
502
- }
503
- function validateWorkersCompatibility(providedFlags, options, config) {
504
- if (providedFlags.has("runtime") && options.runtime === "workers" && config.backend && config.backend !== "hono") exitWithError(`Cloudflare Workers runtime (--runtime workers) is only supported with Hono backend (--backend hono). Current backend: ${config.backend}. Please use '--backend hono' or choose a different runtime.`);
505
- if (providedFlags.has("backend") && config.backend && config.backend !== "hono" && config.runtime === "workers") exitWithError(`Backend '${config.backend}' is not compatible with Cloudflare Workers runtime. Cloudflare Workers runtime is only supported with Hono backend. Please use '--backend hono' or choose a different runtime.`);
506
- if (providedFlags.has("runtime") && options.runtime === "workers" && config.database === "mongodb") exitWithError("Cloudflare Workers runtime (--runtime workers) is not compatible with MongoDB database. MongoDB requires Prisma or Mongoose ORM, but Workers runtime only supports Drizzle or Prisma ORM. Please use a different database or runtime.");
507
- if (providedFlags.has("runtime") && options.runtime === "workers" && config.dbSetup === "docker") exitWithError("Cloudflare Workers runtime (--runtime workers) is not compatible with Docker setup. Workers runtime uses serverless databases (D1) and doesn't support local Docker containers. Please use '--db-setup d1' for SQLite or choose a different runtime.");
508
- if (providedFlags.has("database") && config.database === "mongodb" && config.runtime === "workers") exitWithError("MongoDB database is not compatible with Cloudflare Workers runtime. MongoDB requires Prisma or Mongoose ORM, but Workers runtime only supports Drizzle or Prisma ORM. Please use a different database or runtime.");
509
- if (providedFlags.has("dbSetup") && options.dbSetup === "docker" && config.runtime === "workers") exitWithError("Docker setup (--db-setup docker) is not compatible with Cloudflare Workers runtime. Workers runtime uses serverless databases (D1) and doesn't support local Docker containers. Please use '--db-setup d1' for SQLite or choose a different runtime.");
510
- }
511
- function validateApiFrontendCompatibility(api, frontends = []) {
512
- const includesNuxt = frontends.includes("nuxt");
513
- const includesSvelte = frontends.includes("svelte");
514
- const includesSolid = frontends.includes("solid");
515
- if ((includesNuxt || includesSvelte || includesSolid) && api === "trpc") exitWithError(`tRPC API is not supported with '${includesNuxt ? "nuxt" : includesSvelte ? "svelte" : "solid"}' frontend. Please use --api orpc or --api none or remove '${includesNuxt ? "nuxt" : includesSvelte ? "svelte" : "solid"}' from --frontend.`);
516
- }
517
- function isFrontendAllowedWithBackend(frontend, backend, auth) {
518
- if (backend === "convex" && frontend === "solid") return false;
519
- if (auth === "clerk" && backend === "convex") {
520
- const incompatibleFrontends = [
521
- "nuxt",
522
- "svelte",
523
- "solid"
524
- ];
525
- if (incompatibleFrontends.includes(frontend)) return false;
526
- }
527
- return true;
528
- }
529
- function allowedApisForFrontends(frontends = []) {
530
- const includesNuxt = frontends.includes("nuxt");
531
- const includesSvelte = frontends.includes("svelte");
532
- const includesSolid = frontends.includes("solid");
533
- const base = [
534
- "trpc",
535
- "orpc",
536
- "none"
537
- ];
538
- if (includesNuxt || includesSvelte || includesSolid) return ["orpc", "none"];
539
- return base;
540
- }
541
- function isExampleTodoAllowed(backend, database) {
542
- return !(backend !== "convex" && backend !== "none" && database === "none");
543
- }
544
- function isExampleAIAllowed(_backend, frontends = []) {
545
- const includesSolid = frontends.includes("solid");
546
- if (includesSolid) return false;
547
- return true;
548
- }
549
- function validateWebDeployRequiresWebFrontend(webDeploy, hasWebFrontendFlag) {
550
- if (webDeploy && webDeploy !== "none" && !hasWebFrontendFlag) exitWithError("'--web-deploy' requires a web frontend. Please select a web frontend or set '--web-deploy none'.");
551
- }
552
- function validateServerDeployRequiresBackend(serverDeploy, backend) {
553
- if (serverDeploy && serverDeploy !== "none" && (!backend || backend === "none")) exitWithError("'--server-deploy' requires a backend. Please select a backend or set '--server-deploy none'.");
554
- }
555
- function validateAddonsAgainstFrontends(addons = [], frontends = []) {
556
- for (const addon of addons) {
557
- if (addon === "none") continue;
558
- const { isCompatible, reason } = validateAddonCompatibility(addon, frontends);
559
- if (!isCompatible) exitWithError(`Incompatible addon/frontend combination: ${reason}`);
560
- }
561
- }
562
- function validateExamplesCompatibility(examples, backend, database, frontend) {
563
- const examplesArr = examples ?? [];
564
- if (examplesArr.length === 0 || examplesArr.includes("none")) return;
565
- if (examplesArr.includes("todo") && backend !== "convex" && backend !== "none" && database === "none") exitWithError("The 'todo' example requires a database if a backend (other than Convex) is present. Cannot use --examples todo when database is 'none' and a backend is selected.");
566
- if (examplesArr.includes("ai") && (frontend ?? []).includes("solid")) exitWithError("The 'ai' example is not compatible with the Solid frontend.");
567
- }
568
-
569
580
  //#endregion
570
581
  //#region src/prompts/api.ts
571
582
  async function getApiChoice(Api, frontend, backend) {
@@ -602,20 +613,36 @@ async function getAuthChoice(auth, hasDatabase, backend, frontend) {
602
613
  const unsupportedFrontends = frontend?.filter((f) => [
603
614
  "nuxt",
604
615
  "svelte",
605
- "solid"
616
+ "solid",
617
+ "native-nativewind",
618
+ "native-unistyles"
606
619
  ].includes(f));
607
620
  if (unsupportedFrontends && unsupportedFrontends.length > 0) return "none";
621
+ const hasReactFrontends = frontend?.some((f) => [
622
+ "react-router",
623
+ "tanstack-router",
624
+ "tanstack-start",
625
+ "next"
626
+ ].includes(f));
627
+ const options = [{
628
+ value: "clerk",
629
+ label: "Clerk",
630
+ hint: "More than auth, Complete User Management"
631
+ }];
632
+ if (hasReactFrontends) options.unshift({
633
+ value: "better-auth",
634
+ label: "Better-Auth",
635
+ hint: "comprehensive auth framework for TypeScript"
636
+ });
637
+ options.push({
638
+ value: "none",
639
+ label: "None",
640
+ hint: "No auth"
641
+ });
608
642
  const response$1 = await select({
609
643
  message: "Select authentication provider",
610
- options: [{
611
- value: "clerk",
612
- label: "Clerk",
613
- hint: "More than auth, Complete User Management"
614
- }, {
615
- value: "none",
616
- label: "None"
617
- }],
618
- initialValue: "clerk"
644
+ options,
645
+ initialValue: "none"
619
646
  });
620
647
  if (isCancel(response$1)) return exitCancelled("Operation cancelled");
621
648
  return response$1;
@@ -1037,6 +1064,30 @@ async function getPackageManagerChoice(packageManager) {
1037
1064
  return response;
1038
1065
  }
1039
1066
 
1067
+ //#endregion
1068
+ //#region src/prompts/payments.ts
1069
+ async function getPaymentsChoice(payments, auth, backend, frontends) {
1070
+ if (payments !== void 0) return payments;
1071
+ const isPolarCompatible = auth === "better-auth" && backend !== "convex" && (frontends?.length === 0 || splitFrontends(frontends).web.length > 0);
1072
+ if (!isPolarCompatible) return "none";
1073
+ const options = [{
1074
+ value: "polar",
1075
+ label: "Polar",
1076
+ hint: "Turn your software into a business. 6 lines of code."
1077
+ }, {
1078
+ value: "none",
1079
+ label: "None",
1080
+ hint: "No payments integration"
1081
+ }];
1082
+ const response = await select({
1083
+ message: "Select payments provider",
1084
+ options,
1085
+ initialValue: DEFAULT_CONFIG.payments
1086
+ });
1087
+ if (isCancel(response)) return exitCancelled("Operation cancelled");
1088
+ return response;
1089
+ }
1090
+
1040
1091
  //#endregion
1041
1092
  //#region src/prompts/runtime.ts
1042
1093
  async function getRuntimeChoice(runtime, backend) {
@@ -1226,7 +1277,8 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
1226
1277
  orm: ({ results }) => getORMChoice(flags.orm, results.database !== "none", results.database, results.backend, results.runtime),
1227
1278
  api: ({ results }) => getApiChoice(flags.api, results.frontend, results.backend),
1228
1279
  auth: ({ results }) => getAuthChoice(flags.auth, results.database !== "none", results.backend, results.frontend),
1229
- addons: ({ results }) => getAddonsChoice(flags.addons, results.frontend),
1280
+ payments: ({ results }) => getPaymentsChoice(flags.payments, results.auth, results.backend, results.frontend),
1281
+ addons: ({ results }) => getAddonsChoice(flags.addons, results.frontend, results.auth),
1230
1282
  examples: ({ results }) => getExamplesChoice(flags.examples, results.database, results.frontend, results.backend, results.api),
1231
1283
  dbSetup: ({ results }) => getDBSetupChoice(results.database ?? "none", flags.dbSetup, results.orm, results.backend, results.runtime),
1232
1284
  webDeploy: ({ results }) => getDeploymentChoice(flags.webDeploy, results.runtime, results.backend, results.frontend),
@@ -1245,6 +1297,7 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
1245
1297
  database: result.database,
1246
1298
  orm: result.orm,
1247
1299
  auth: result.auth,
1300
+ payments: result.payments,
1248
1301
  addons: result.addons,
1249
1302
  examples: result.examples,
1250
1303
  git: result.git,
@@ -1332,7 +1385,7 @@ const getLatestCLIVersion = () => {
1332
1385
  */
1333
1386
  function isTelemetryEnabled() {
1334
1387
  const BTS_TELEMETRY_DISABLED = process.env.BTS_TELEMETRY_DISABLED;
1335
- const BTS_TELEMETRY = "1";
1388
+ const BTS_TELEMETRY = "0";
1336
1389
  if (BTS_TELEMETRY_DISABLED !== void 0) return BTS_TELEMETRY_DISABLED !== "1";
1337
1390
  if (BTS_TELEMETRY !== void 0) return BTS_TELEMETRY === "1";
1338
1391
  return true;
@@ -1340,8 +1393,8 @@ function isTelemetryEnabled() {
1340
1393
 
1341
1394
  //#endregion
1342
1395
  //#region src/utils/analytics.ts
1343
- const POSTHOG_API_KEY = "phc_8ZUxEwwfKMajJLvxz1daGd931dYbQrwKNficBmsdIrs";
1344
- const POSTHOG_HOST = "https://us.i.posthog.com";
1396
+ const POSTHOG_API_KEY = "random";
1397
+ const POSTHOG_HOST = "random";
1345
1398
  function generateSessionId() {
1346
1399
  const rand = Math.random().toString(36).slice(2);
1347
1400
  const now = Date.now().toString(36);
@@ -1388,6 +1441,7 @@ function displayConfig(config) {
1388
1441
  if (config.database !== void 0) configDisplay.push(`${pc.blue("Database:")} ${String(config.database)}`);
1389
1442
  if (config.orm !== void 0) configDisplay.push(`${pc.blue("ORM:")} ${String(config.orm)}`);
1390
1443
  if (config.auth !== void 0) configDisplay.push(`${pc.blue("Auth:")} ${String(config.auth)}`);
1444
+ if (config.payments !== void 0) configDisplay.push(`${pc.blue("Payments:")} ${String(config.payments)}`);
1391
1445
  if (config.addons !== void 0) {
1392
1446
  const addons = Array.isArray(config.addons) ? config.addons : [config.addons];
1393
1447
  const addonsText = addons.length > 0 && addons[0] !== void 0 ? addons.join(", ") : "none";
@@ -1426,6 +1480,7 @@ function generateReproducibleCommand(config) {
1426
1480
  flags.push(`--orm ${config.orm}`);
1427
1481
  flags.push(`--api ${config.api}`);
1428
1482
  flags.push(`--auth ${config.auth}`);
1483
+ flags.push(`--payments ${config.payments}`);
1429
1484
  if (config.addons && config.addons.length > 0) flags.push(`--addons ${config.addons.join(" ")}`);
1430
1485
  else flags.push("--addons none");
1431
1486
  if (config.examples && config.examples.length > 0) flags.push(`--examples ${config.examples.join(" ")}`);
@@ -1595,6 +1650,7 @@ function processFlags(options, projectName) {
1595
1650
  if (options.database) config.database = options.database;
1596
1651
  if (options.orm) config.orm = options.orm;
1597
1652
  if (options.auth !== void 0) config.auth = options.auth;
1653
+ if (options.payments !== void 0) config.payments = options.payments;
1598
1654
  if (options.git !== void 0) config.git = options.git;
1599
1655
  if (options.install !== void 0) config.install = options.install;
1600
1656
  if (options.runtime) config.runtime = options.runtime;
@@ -1692,7 +1748,16 @@ function validateConvexConstraints(config, providedFlags) {
1692
1748
  if (has("api") && config.api !== "none") exitWithError("Convex backend requires '--api none'. Please remove the --api flag or set it to 'none'.");
1693
1749
  if (has("dbSetup") && config.dbSetup !== "none") exitWithError("Convex backend requires '--db-setup none'. Please remove the --db-setup flag or set it to 'none'.");
1694
1750
  if (has("serverDeploy") && config.serverDeploy !== "none") exitWithError("Convex backend requires '--server-deploy none'. Please remove the --server-deploy flag or set it to 'none'.");
1695
- if (has("auth") && config.auth === "better-auth") exitWithError("Better-Auth is not compatible with Convex backend. Please use '--auth clerk' or '--auth none'.");
1751
+ if (has("auth") && config.auth === "better-auth") {
1752
+ const hasUnsupportedFrontends = config.frontend?.some((f) => [
1753
+ "nuxt",
1754
+ "svelte",
1755
+ "solid",
1756
+ "native-nativewind",
1757
+ "native-unistyles"
1758
+ ].includes(f));
1759
+ if (hasUnsupportedFrontends) exitWithError("Better-Auth with Convex backend is not supported for non-React frontends (nuxt, svelte, solid) or native frontends (native-nativewind, native-unistyles). Please use '--auth clerk' or '--auth none'.");
1760
+ }
1696
1761
  }
1697
1762
  function validateBackendNoneConstraints(config, providedFlags) {
1698
1763
  const { backend } = config;
@@ -1751,7 +1816,7 @@ function validateFullConfig(config, providedFlags, options) {
1751
1816
  validateWorkersCompatibility(providedFlags, options, config);
1752
1817
  if (config.runtime === "workers" && config.serverDeploy === "none") exitWithError("Cloudflare Workers runtime requires a server deployment. Please choose 'wrangler' or 'alchemy' for --server-deploy.");
1753
1818
  if (config.addons && config.addons.length > 0) {
1754
- validateAddonsAgainstFrontends(config.addons, config.frontend);
1819
+ validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth);
1755
1820
  config.addons = [...new Set(config.addons)];
1756
1821
  }
1757
1822
  validateExamplesCompatibility(config.examples ?? [], config.backend, config.database, config.frontend ?? []);
@@ -1761,7 +1826,8 @@ function validateConfigForProgrammaticUse(config) {
1761
1826
  validateDatabaseOrmAuth(config);
1762
1827
  if (config.frontend && config.frontend.length > 0) ensureSingleWebAndNative(config.frontend);
1763
1828
  validateApiFrontendCompatibility(config.api, config.frontend);
1764
- if (config.addons && config.addons.length > 0) validateAddonsAgainstFrontends(config.addons, config.frontend);
1829
+ validatePaymentsCompatibility(config.payments, config.auth, config.backend, config.frontend);
1830
+ if (config.addons && config.addons.length > 0) validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth);
1765
1831
  validateExamplesCompatibility(config.examples ?? [], config.backend, config.database, config.frontend ?? []);
1766
1832
  } catch (error) {
1767
1833
  if (error instanceof Error) throw error;
@@ -1859,6 +1925,7 @@ async function writeBtsConfig(projectConfig) {
1859
1925
  addons: projectConfig.addons,
1860
1926
  examples: projectConfig.examples,
1861
1927
  auth: projectConfig.auth,
1928
+ payments: projectConfig.payments,
1862
1929
  packageManager: projectConfig.packageManager,
1863
1930
  dbSetup: projectConfig.dbSetup,
1864
1931
  api: projectConfig.api,
@@ -1937,18 +2004,18 @@ async function updateBtsConfig(projectDir, updates) {
1937
2004
  //#endregion
1938
2005
  //#region src/utils/add-package-deps.ts
1939
2006
  const addPackageDependency = async (opts) => {
1940
- const { dependencies = [], devDependencies = [], projectDir } = opts;
2007
+ const { dependencies = [], devDependencies = [], customDependencies = {}, customDevDependencies = {}, projectDir } = opts;
1941
2008
  const pkgJsonPath = path.join(projectDir, "package.json");
1942
2009
  const pkgJson = await fs.readJson(pkgJsonPath);
1943
2010
  if (!pkgJson.dependencies) pkgJson.dependencies = {};
1944
2011
  if (!pkgJson.devDependencies) pkgJson.devDependencies = {};
1945
2012
  for (const pkgName of dependencies) {
1946
- const version = dependencyVersionMap[pkgName];
2013
+ const version = customDependencies[pkgName] || dependencyVersionMap[pkgName];
1947
2014
  if (version) pkgJson.dependencies[pkgName] = version;
1948
2015
  else console.warn(`Warning: Dependency ${pkgName} not found in version map.`);
1949
2016
  }
1950
2017
  for (const pkgName of devDependencies) {
1951
- const version = dependencyVersionMap[pkgName];
2018
+ const version = customDevDependencies[pkgName] || dependencyVersionMap[pkgName];
1952
2019
  if (version) pkgJson.devDependencies[pkgName] = version;
1953
2020
  else console.warn(`Warning: Dev dependency ${pkgName} not found in version map.`);
1954
2021
  }
@@ -2483,6 +2550,7 @@ async function detectProjectConfig(projectDir) {
2483
2550
  addons: btsConfig.addons,
2484
2551
  examples: btsConfig.examples,
2485
2552
  auth: btsConfig.auth,
2553
+ payments: btsConfig.payments,
2486
2554
  packageManager: btsConfig.packageManager,
2487
2555
  dbSetup: btsConfig.dbSetup,
2488
2556
  api: btsConfig.api,
@@ -2809,6 +2877,29 @@ async function setupAuthTemplate(projectDir, context) {
2809
2877
  }
2810
2878
  return;
2811
2879
  }
2880
+ if (context.backend === "convex" && authProvider === "better-auth") {
2881
+ const convexBackendDestDir = path.join(projectDir, "packages/backend");
2882
+ const convexBetterAuthBackendSrc = path.join(PKG_ROOT, "templates/auth/better-auth/convex/backend");
2883
+ if (await fs.pathExists(convexBetterAuthBackendSrc)) {
2884
+ await fs.ensureDir(convexBackendDestDir);
2885
+ await processAndCopyFiles("**/*", convexBetterAuthBackendSrc, convexBackendDestDir, context);
2886
+ }
2887
+ if (webAppDirExists && hasReactWeb) {
2888
+ const convexBetterAuthWebBaseSrc = path.join(PKG_ROOT, "templates/auth/better-auth/convex/web/react/base");
2889
+ if (await fs.pathExists(convexBetterAuthWebBaseSrc)) await processAndCopyFiles("**/*", convexBetterAuthWebBaseSrc, webAppDir, context);
2890
+ const reactFramework = context.frontend.find((f) => [
2891
+ "tanstack-router",
2892
+ "react-router",
2893
+ "tanstack-start",
2894
+ "next"
2895
+ ].includes(f));
2896
+ if (reactFramework) {
2897
+ const convexBetterAuthWebSrc = path.join(PKG_ROOT, `templates/auth/better-auth/convex/web/react/${reactFramework}`);
2898
+ if (await fs.pathExists(convexBetterAuthWebSrc)) await processAndCopyFiles("**/*", convexBetterAuthWebSrc, webAppDir, context);
2899
+ }
2900
+ }
2901
+ return;
2902
+ }
2812
2903
  if (serverAppDirExists && context.backend !== "convex") {
2813
2904
  const authServerBaseSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/base`);
2814
2905
  if (await fs.pathExists(authServerBaseSrc)) await processAndCopyFiles("**/*", authServerBaseSrc, serverAppDir, context);
@@ -2863,6 +2954,49 @@ async function setupAuthTemplate(projectDir, context) {
2863
2954
  }
2864
2955
  }
2865
2956
  }
2957
+ async function setupPaymentsTemplate(projectDir, context) {
2958
+ if (!context.payments || context.payments === "none") return;
2959
+ const serverAppDir = path.join(projectDir, "apps/server");
2960
+ const webAppDir = path.join(projectDir, "apps/web");
2961
+ const serverAppDirExists = await fs.pathExists(serverAppDir);
2962
+ const webAppDirExists = await fs.pathExists(webAppDir);
2963
+ if (serverAppDirExists && context.backend !== "convex") {
2964
+ const paymentsServerSrc = path.join(PKG_ROOT, `templates/payments/${context.payments}/server/base`);
2965
+ if (await fs.pathExists(paymentsServerSrc)) await processAndCopyFiles("**/*", paymentsServerSrc, serverAppDir, context);
2966
+ }
2967
+ const hasReactWeb = context.frontend.some((f) => [
2968
+ "tanstack-router",
2969
+ "react-router",
2970
+ "tanstack-start",
2971
+ "next"
2972
+ ].includes(f));
2973
+ const hasNuxtWeb = context.frontend.includes("nuxt");
2974
+ const hasSvelteWeb = context.frontend.includes("svelte");
2975
+ const hasSolidWeb = context.frontend.includes("solid");
2976
+ if (webAppDirExists && (hasReactWeb || hasNuxtWeb || hasSvelteWeb || hasSolidWeb)) {
2977
+ if (hasReactWeb) {
2978
+ const reactFramework = context.frontend.find((f) => [
2979
+ "tanstack-router",
2980
+ "react-router",
2981
+ "tanstack-start",
2982
+ "next"
2983
+ ].includes(f));
2984
+ if (reactFramework) {
2985
+ const paymentsWebSrc = path.join(PKG_ROOT, `templates/payments/${context.payments}/web/react/${reactFramework}`);
2986
+ if (await fs.pathExists(paymentsWebSrc)) await processAndCopyFiles("**/*", paymentsWebSrc, webAppDir, context);
2987
+ }
2988
+ } else if (hasNuxtWeb) {
2989
+ const paymentsWebNuxtSrc = path.join(PKG_ROOT, `templates/payments/${context.payments}/web/nuxt`);
2990
+ if (await fs.pathExists(paymentsWebNuxtSrc)) await processAndCopyFiles("**/*", paymentsWebNuxtSrc, webAppDir, context);
2991
+ } else if (hasSvelteWeb) {
2992
+ const paymentsWebSvelteSrc = path.join(PKG_ROOT, `templates/payments/${context.payments}/web/svelte`);
2993
+ if (await fs.pathExists(paymentsWebSvelteSrc)) await processAndCopyFiles("**/*", paymentsWebSvelteSrc, webAppDir, context);
2994
+ } else if (hasSolidWeb) {
2995
+ const paymentsWebSolidSrc = path.join(PKG_ROOT, `templates/payments/${context.payments}/web/solid`);
2996
+ if (await fs.pathExists(paymentsWebSolidSrc)) await processAndCopyFiles("**/*", paymentsWebSolidSrc, webAppDir, context);
2997
+ }
2998
+ }
2999
+ }
2866
3000
  async function setupAddonsTemplate(projectDir, context) {
2867
3001
  if (!context.addons || context.addons.length === 0) return;
2868
3002
  for (const addon of context.addons) {
@@ -3057,6 +3191,7 @@ async function addAddonsToProject(input) {
3057
3191
  addons: input.addons,
3058
3192
  examples: detectedConfig.examples || [],
3059
3193
  auth: detectedConfig.auth || "none",
3194
+ payments: detectedConfig.payments || "none",
3060
3195
  git: false,
3061
3196
  packageManager: input.packageManager || detectedConfig.packageManager || "npm",
3062
3197
  install: input.install || false,
@@ -3843,6 +3978,7 @@ async function addDeploymentToProject(input) {
3843
3978
  addons: detectedConfig.addons || [],
3844
3979
  examples: detectedConfig.examples || [],
3845
3980
  auth: detectedConfig.auth || "none",
3981
+ payments: detectedConfig.payments || "none",
3846
3982
  git: false,
3847
3983
  packageManager: input.packageManager || detectedConfig.packageManager || "npm",
3848
3984
  install: input.install || false,
@@ -4155,6 +4291,35 @@ async function setupAuth(config) {
4155
4291
  projectDir: clientDir
4156
4292
  });
4157
4293
  }
4294
+ if (auth === "better-auth") {
4295
+ const convexBackendDir = path.join(projectDir, "packages/backend");
4296
+ const convexBackendDirExists = await fs.pathExists(convexBackendDir);
4297
+ if (convexBackendDirExists) await addPackageDependency({
4298
+ dependencies: ["better-auth", "@convex-dev/better-auth"],
4299
+ customDependencies: { "better-auth": "1.3.8" },
4300
+ projectDir: convexBackendDir
4301
+ });
4302
+ if (clientDirExists) {
4303
+ const hasNextJs = frontend.includes("next");
4304
+ const hasTanStackStart = frontend.includes("tanstack-start");
4305
+ const hasViteReactOther = frontend.some((f) => ["tanstack-router", "react-router"].includes(f));
4306
+ if (hasNextJs) await addPackageDependency({
4307
+ dependencies: ["better-auth", "@convex-dev/better-auth"],
4308
+ customDependencies: { "better-auth": "1.3.8" },
4309
+ projectDir: clientDir
4310
+ });
4311
+ else if (hasTanStackStart) await addPackageDependency({
4312
+ dependencies: ["better-auth", "@convex-dev/better-auth"],
4313
+ customDependencies: { "better-auth": "1.3.8" },
4314
+ projectDir: clientDir
4315
+ });
4316
+ else if (hasViteReactOther) await addPackageDependency({
4317
+ dependencies: ["better-auth", "@convex-dev/better-auth"],
4318
+ customDependencies: { "better-auth": "1.3.8" },
4319
+ projectDir: clientDir
4320
+ });
4321
+ }
4322
+ }
4158
4323
  const hasNativeWind = frontend.includes("native-nativewind");
4159
4324
  const hasUnistyles = frontend.includes("native-unistyles");
4160
4325
  if (auth === "clerk" && nativeDirExists && (hasNativeWind || hasUnistyles)) await addPackageDependency({
@@ -4312,6 +4477,18 @@ async function setupEnvironmentVariables(config) {
4312
4477
  });
4313
4478
  }
4314
4479
  }
4480
+ if (backend === "convex" && auth === "better-auth") {
4481
+ if (hasNextJs) clientVars.push({
4482
+ key: "NEXT_PUBLIC_CONVEX_SITE_URL",
4483
+ value: "https://<YOUR_CONVEX_URL>",
4484
+ condition: true
4485
+ });
4486
+ else if (hasReactRouter || hasTanStackRouter || hasTanStackStart) clientVars.push({
4487
+ key: "VITE_CONVEX_SITE_URL",
4488
+ value: "https://<YOUR_CONVEX_URL>",
4489
+ condition: true
4490
+ });
4491
+ }
4315
4492
  await addEnvVariablesToFile(path.join(clientDir, ".env"), clientVars);
4316
4493
  }
4317
4494
  }
@@ -4337,7 +4514,24 @@ async function setupEnvironmentVariables(config) {
4337
4514
  await addEnvVariablesToFile(path.join(nativeDir, ".env"), nativeVars);
4338
4515
  }
4339
4516
  }
4340
- if (backend === "convex") return;
4517
+ if (backend === "convex") {
4518
+ if (auth === "better-auth") {
4519
+ const convexBackendDir = path.join(projectDir, "packages/backend");
4520
+ if (await fs.pathExists(convexBackendDir)) {
4521
+ const convexBackendVars = [{
4522
+ key: hasTanStackStart ? "NEXT_PUBLIC_CONVEX_SITE_URL" : "VITE_CONVEX_SITE_URL",
4523
+ value: "",
4524
+ condition: true
4525
+ }, {
4526
+ key: "SITE_URL",
4527
+ value: "http://localhost:3001",
4528
+ condition: true
4529
+ }];
4530
+ await addEnvVariablesToFile(path.join(convexBackendDir, ".env.local"), convexBackendVars);
4531
+ }
4532
+ }
4533
+ return;
4534
+ }
4341
4535
  const serverDir = path.join(projectDir, "apps/server");
4342
4536
  if (!await fs.pathExists(serverDir)) return;
4343
4537
  const envPath = path.join(serverDir, ".env");
@@ -4384,6 +4578,16 @@ async function setupEnvironmentVariables(config) {
4384
4578
  key: "GOOGLE_GENERATIVE_AI_API_KEY",
4385
4579
  value: "",
4386
4580
  condition: examples?.includes("ai") || false
4581
+ },
4582
+ {
4583
+ key: "POLAR_ACCESS_TOKEN",
4584
+ value: "",
4585
+ condition: config.payments === "polar"
4586
+ },
4587
+ {
4588
+ key: "POLAR_SUCCESS_URL",
4589
+ value: `${corsOrigin}/success?checkout_id={CHECKOUT_ID}`,
4590
+ condition: config.payments === "polar"
4387
4591
  }
4388
4592
  ];
4389
4593
  await addEnvVariablesToFile(envPath, serverVars);
@@ -5612,17 +5816,6 @@ async function setupNodeRuntime(serverDir, backend) {
5612
5816
  });
5613
5817
  }
5614
5818
 
5615
- //#endregion
5616
- //#region src/helpers/core/convex-codegen.ts
5617
- async function runConvexCodegen(projectDir, packageManager) {
5618
- const backendDir = path.join(projectDir, "packages/backend");
5619
- const cmd = getPackageExecutionCommand(packageManager, "convex codegen");
5620
- await execa(cmd, {
5621
- cwd: backendDir,
5622
- shell: true
5623
- });
5624
- }
5625
-
5626
5819
  //#endregion
5627
5820
  //#region src/helpers/core/create-readme.ts
5628
5821
  async function createReadme(projectDir, options) {
@@ -5944,6 +6137,39 @@ async function initializeGit(projectDir, useGit) {
5944
6137
  await $({ cwd: projectDir })`git commit -m ${"initial commit"}`;
5945
6138
  }
5946
6139
 
6140
+ //#endregion
6141
+ //#region src/helpers/core/payments-setup.ts
6142
+ async function setupPayments(config) {
6143
+ const { payments, projectDir, frontend } = config;
6144
+ if (!payments || payments === "none") return;
6145
+ const serverDir = path.join(projectDir, "apps/server");
6146
+ const clientDir = path.join(projectDir, "apps/web");
6147
+ const serverDirExists = await fs.pathExists(serverDir);
6148
+ const clientDirExists = await fs.pathExists(clientDir);
6149
+ if (!serverDirExists) return;
6150
+ if (payments === "polar") {
6151
+ await addPackageDependency({
6152
+ dependencies: ["@polar-sh/better-auth", "@polar-sh/sdk"],
6153
+ projectDir: serverDir
6154
+ });
6155
+ if (clientDirExists) {
6156
+ const hasWebFrontend$1 = frontend.some((f) => [
6157
+ "react-router",
6158
+ "tanstack-router",
6159
+ "tanstack-start",
6160
+ "next",
6161
+ "nuxt",
6162
+ "svelte",
6163
+ "solid"
6164
+ ].includes(f));
6165
+ if (hasWebFrontend$1) await addPackageDependency({
6166
+ dependencies: ["@polar-sh/better-auth"],
6167
+ projectDir: clientDir
6168
+ });
6169
+ }
6170
+ }
6171
+ }
6172
+
5947
6173
  //#endregion
5948
6174
  //#region src/utils/docker-utils.ts
5949
6175
  async function isDockerInstalled() {
@@ -6011,6 +6237,7 @@ async function displayPostInstallInstructions(config) {
6011
6237
  const pwaInstructions = addons?.includes("pwa") && frontend?.includes("react-router") ? getPwaInstructions() : "";
6012
6238
  const starlightInstructions = addons?.includes("starlight") ? getStarlightInstructions(runCmd) : "";
6013
6239
  const clerkInstructions = isConvex && config.auth === "clerk" ? getClerkInstructions() : "";
6240
+ const polarInstructions = config.payments === "polar" && config.auth === "better-auth" ? getPolarInstructions() : "";
6014
6241
  const wranglerDeployInstructions = getWranglerDeployInstructions(runCmd, webDeploy, serverDeploy);
6015
6242
  const alchemyDeployInstructions = getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy);
6016
6243
  const hasWeb = frontend?.some((f) => [
@@ -6063,6 +6290,7 @@ async function displayPostInstallInstructions(config) {
6063
6290
  if (alchemyDeployInstructions) output += `\n${alchemyDeployInstructions.trim()}\n`;
6064
6291
  if (starlightInstructions) output += `\n${starlightInstructions.trim()}\n`;
6065
6292
  if (clerkInstructions) output += `\n${clerkInstructions.trim()}\n`;
6293
+ if (polarInstructions) output += `\n${polarInstructions.trim()}\n`;
6066
6294
  if (noOrmWarning) output += `\n${noOrmWarning.trim()}\n`;
6067
6295
  if (bunWebNativeWarning) output += `\n${bunWebNativeWarning.trim()}\n`;
6068
6296
  output += `\n${pc.bold("Update all dependencies:\n")}${pc.cyan(tazeCommand)}\n\n`;
@@ -6151,6 +6379,9 @@ function getWranglerDeployInstructions(runCmd, webDeploy, serverDeploy) {
6151
6379
  function getClerkInstructions() {
6152
6380
  return `${pc.bold("Clerk Authentication Setup:")}\n${pc.cyan("•")} Follow the guide: ${pc.underline("https://docs.convex.dev/auth/clerk")}\n${pc.cyan("•")} Set CLERK_JWT_ISSUER_DOMAIN in Convex Dashboard\n${pc.cyan("•")} Set CLERK_PUBLISHABLE_KEY in apps/*/.env`;
6153
6381
  }
6382
+ function getPolarInstructions() {
6383
+ return `${pc.bold("Polar Payments Setup:")}\n${pc.cyan("•")} Get access token & product ID from ${pc.underline("https://sandbox.polar.sh/")}\n${pc.cyan("•")} Set POLAR_ACCESS_TOKEN in apps/server/.env`;
6384
+ }
6154
6385
  function getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy) {
6155
6386
  const instructions = [];
6156
6387
  if (webDeploy === "alchemy" && serverDeploy !== "alchemy") instructions.push(`${pc.bold("Deploy web with Alchemy:")}\n${pc.cyan("•")} Dev: ${`cd apps/web && ${runCmd} dev`}\n${pc.cyan("•")} Deploy: ${`cd apps/web && ${runCmd} deploy`}\n${pc.cyan("•")} Destroy: ${`cd apps/web && ${runCmd} destroy`}`);
@@ -6355,6 +6586,7 @@ async function createProject(options, cliInput) {
6355
6586
  await setupDockerComposeTemplates(projectDir, options);
6356
6587
  }
6357
6588
  await setupAuthTemplate(projectDir, options);
6589
+ if (options.payments && options.payments !== "none") await setupPaymentsTemplate(projectDir, options);
6358
6590
  if (options.examples.length > 0 && options.examples[0] !== "none") await setupExamplesTemplate(projectDir, options);
6359
6591
  await setupAddonsTemplate(projectDir, options);
6360
6592
  await setupDeploymentTemplates(projectDir, options);
@@ -6367,6 +6599,7 @@ async function createProject(options, cliInput) {
6367
6599
  }
6368
6600
  if (options.addons.length > 0 && options.addons[0] !== "none") await setupAddons(options);
6369
6601
  if (options.auth && options.auth !== "none") await setupAuth(options);
6602
+ if (options.payments && options.payments !== "none") await setupPayments(options);
6370
6603
  await handleExtras(projectDir, options);
6371
6604
  await setupEnvironmentVariables(options);
6372
6605
  await updatePackageConfigurations(projectDir, options);
@@ -6374,7 +6607,6 @@ async function createProject(options, cliInput) {
6374
6607
  await setupServerDeploy(options);
6375
6608
  await createReadme(projectDir, options);
6376
6609
  await writeBtsConfig(options);
6377
- if (isConvex) await runConvexCodegen(projectDir, options.packageManager);
6378
6610
  log.success("Project template successfully scaffolded!");
6379
6611
  if (options.install) await installDependencies({
6380
6612
  projectDir,
@@ -6445,6 +6677,7 @@ async function createProjectHandler(input) {
6445
6677
  addons: [],
6446
6678
  examples: [],
6447
6679
  auth: "none",
6680
+ payments: "none",
6448
6681
  git: false,
6449
6682
  packageManager: "npm",
6450
6683
  install: false,
@@ -6552,7 +6785,7 @@ async function addAddonsHandler(input) {
6552
6785
  const detectedConfig = await detectProjectConfig(projectDir);
6553
6786
  if (!detectedConfig) exitWithError("Could not detect project configuration. Please ensure this is a valid Better-T-Stack project.");
6554
6787
  if (!input.addons || input.addons.length === 0) {
6555
- const addonsPrompt = await getAddonsToAdd(detectedConfig.frontend || [], detectedConfig.addons || []);
6788
+ const addonsPrompt = await getAddonsToAdd(detectedConfig.frontend || [], detectedConfig.addons || [], detectedConfig.auth);
6556
6789
  if (addonsPrompt.length > 0) input.addons = addonsPrompt;
6557
6790
  }
6558
6791
  if (!input.webDeploy) {
@@ -6691,6 +6924,7 @@ const router = t.router({
6691
6924
  database: DatabaseSchema.optional(),
6692
6925
  orm: ORMSchema.optional(),
6693
6926
  auth: AuthSchema.optional(),
6927
+ payments: PaymentsSchema.optional(),
6694
6928
  frontend: z.array(FrontendSchema).optional(),
6695
6929
  addons: z.array(AddonsSchema).optional(),
6696
6930
  examples: z.array(ExamplesSchema).optional(),