create-better-t-stack 2.49.1-canary.80158905 → 2.50.0-canary.08568a05

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 (62) hide show
  1. package/dist/cli.js +1 -1
  2. package/dist/index.d.ts +2 -2
  3. package/dist/index.js +1 -1
  4. package/dist/{src-CyG8-I-3.js → src-DfbhNFZ9.js} +342 -208
  5. package/package.json +2 -1
  6. package/templates/api/orpc/fullstack/next/src/app/api/rpc/[[...rest]]/route.ts.hbs +21 -0
  7. package/templates/api/orpc/server/{rest/src → src}/context.ts.hbs +1 -1
  8. package/templates/api/orpc/web/react/base/src/utils/orpc.ts.hbs +3 -1
  9. package/templates/api/trpc/fullstack/next/src/app/api/trpc/[trpc]/route.ts.hbs +14 -0
  10. package/templates/api/trpc/server/{rest/src → src}/context.ts.hbs +1 -1
  11. package/templates/api/trpc/web/react/base/src/utils/trpc.ts.hbs +4 -2
  12. package/templates/auth/better-auth/{server/next/src/app/api/auth/[...all]/route.ts → fullstack/next/src/app/api/auth/[...all]/route.ts.hbs} +1 -1
  13. package/templates/auth/better-auth/server/base/src/index.ts.hbs +1 -1
  14. package/templates/auth/clerk/convex/web/react/tanstack-start/src/server.ts.hbs +1 -0
  15. package/templates/backend/server/elysia/src/index.ts.hbs +1 -1
  16. package/templates/backend/server/express/src/index.ts.hbs +1 -1
  17. package/templates/backend/server/fastify/src/index.ts.hbs +1 -1
  18. package/templates/backend/server/hono/src/index.ts.hbs +2 -2
  19. package/templates/db/drizzle/mysql/src/index.ts.hbs +1 -1
  20. package/templates/db/drizzle/postgres/src/index.ts.hbs +1 -1
  21. package/templates/db/drizzle/sqlite/src/index.ts.hbs +1 -1
  22. package/templates/deploy/wrangler/web/react/tanstack-start/wrangler.jsonc.hbs +1 -1
  23. package/templates/examples/ai/fullstack/next/src/app/api/ai/route.ts.hbs +15 -0
  24. package/templates/examples/todo/server/drizzle/base/src/routers/todo.ts.hbs +1 -1
  25. package/templates/frontend/react/next/package.json.hbs +1 -1
  26. package/templates/frontend/react/tanstack-start/package.json.hbs +7 -7
  27. package/templates/frontend/react/tanstack-start/src/routes/__root.tsx.hbs +4 -4
  28. package/templates/frontend/react/tanstack-start/vite.config.ts.hbs +1 -1
  29. package/templates/api/orpc/server/next/src/app/rpc/[...all]/route.ts.hbs +0 -52
  30. package/templates/api/trpc/server/next/src/app/trpc/[trpc]/route.ts +0 -14
  31. package/templates/backend/server/next/next-env.d.ts +0 -5
  32. package/templates/backend/server/next/next.config.ts +0 -7
  33. package/templates/backend/server/next/package.json.hbs +0 -27
  34. package/templates/backend/server/next/src/app/route.ts +0 -5
  35. package/templates/backend/server/next/src/middleware.ts +0 -19
  36. package/templates/backend/server/next/tsconfig.json.hbs +0 -33
  37. package/templates/examples/ai/server/next/src/app/ai/route.ts.hbs +0 -15
  38. /package/templates/api/orpc/server/{base/_gitignore → _gitignore} +0 -0
  39. /package/templates/api/orpc/server/{base/package.json.hbs → package.json.hbs} +0 -0
  40. /package/templates/api/orpc/server/{rest/src → src}/index.ts.hbs +0 -0
  41. /package/templates/api/orpc/server/{base/src → src}/routers/index.ts.hbs +0 -0
  42. /package/templates/api/orpc/server/{base/tsconfig.json.hbs → tsconfig.json.hbs} +0 -0
  43. /package/templates/api/orpc/server/{base/tsdown.config.ts.hbs → tsdown.config.ts.hbs} +0 -0
  44. /package/templates/api/trpc/server/{base/_gitignore → _gitignore} +0 -0
  45. /package/templates/api/trpc/server/{base/package.json.hbs → package.json.hbs} +0 -0
  46. /package/templates/api/trpc/server/{rest/src → src}/index.ts.hbs +0 -0
  47. /package/templates/api/trpc/server/{base/src → src}/routers/index.ts.hbs +0 -0
  48. /package/templates/api/trpc/server/{base/tsconfig.json.hbs → tsconfig.json.hbs} +0 -0
  49. /package/templates/api/trpc/server/{base/tsdown.config.ts.hbs → tsdown.config.ts.hbs} +0 -0
  50. /package/templates/auth/better-auth/server/db/drizzle/mysql/src/schema/{auth.ts → auth.ts.hbs} +0 -0
  51. /package/templates/auth/better-auth/server/db/drizzle/postgres/src/schema/{auth.ts → auth.ts.hbs} +0 -0
  52. /package/templates/auth/better-auth/server/db/drizzle/sqlite/src/schema/{auth.ts → auth.ts.hbs} +0 -0
  53. /package/templates/auth/better-auth/server/db/mongoose/mongodb/src/models/{auth.model.ts → auth.model.ts.hbs} +0 -0
  54. /package/templates/auth/better-auth/server/db/prisma/mongodb/prisma/schema/{auth.prisma → auth.prisma.hbs} +0 -0
  55. /package/templates/auth/better-auth/server/db/prisma/mysql/prisma/schema/{auth.prisma → auth.prisma.hbs} +0 -0
  56. /package/templates/auth/better-auth/server/db/prisma/postgres/prisma/schema/{auth.prisma → auth.prisma.hbs} +0 -0
  57. /package/templates/auth/better-auth/server/db/prisma/sqlite/prisma/schema/{auth.prisma → auth.prisma.hbs} +0 -0
  58. /package/templates/auth/better-auth/web/nuxt/app/middleware/{auth.ts → auth.ts.hbs} +0 -0
  59. /package/templates/examples/todo/server/drizzle/mysql/src/{db/schema → schema}/todo.ts +0 -0
  60. /package/templates/examples/todo/server/drizzle/postgres/src/{db/schema → schema}/todo.ts +0 -0
  61. /package/templates/examples/todo/server/drizzle/sqlite/src/{db/schema → schema}/todo.ts +0 -0
  62. /package/templates/examples/todo/server/mongoose/mongodb/src/{db/models → models}/todo.model.ts.hbs +0 -0
@@ -15,6 +15,7 @@ import { IndentationText, Node, Project, QuoteKind, SyntaxKind } from "ts-morph"
15
15
  import { glob } from "tinyglobby";
16
16
  import handlebars from "handlebars";
17
17
  import { Biome } from "@biomejs/js-api/nodejs";
18
+ import yaml from "yaml";
18
19
  import os$1 from "node:os";
19
20
 
20
21
  //#region src/utils/get-package-manager.ts
@@ -65,7 +66,7 @@ const dependencyVersionMap = {
65
66
  "@better-auth/expo": "^1.3.13",
66
67
  "@clerk/nextjs": "^6.31.5",
67
68
  "@clerk/clerk-react": "^5.45.0",
68
- "@clerk/tanstack-react-start": "^0.23.1",
69
+ "@clerk/tanstack-react-start": "^0.25.1",
69
70
  "@clerk/clerk-expo": "^2.14.25",
70
71
  "drizzle-orm": "^0.44.2",
71
72
  "drizzle-kit": "^0.31.2",
@@ -124,6 +125,7 @@ const dependencyVersionMap = {
124
125
  "@trpc/tanstack-react-query": "^11.5.0",
125
126
  "@trpc/server": "^11.5.0",
126
127
  "@trpc/client": "^11.5.0",
128
+ "next": "15.5.4",
127
129
  convex: "^1.27.0",
128
130
  "@convex-dev/react-query": "^0.0.0-alpha.8",
129
131
  "convex-svelte": "^0.0.11",
@@ -139,13 +141,13 @@ const dependencyVersionMap = {
139
141
  "@tanstack/solid-query": "^5.87.4",
140
142
  "@tanstack/solid-query-devtools": "^5.87.4",
141
143
  "@tanstack/solid-router-devtools": "^1.131.44",
142
- wrangler: "^4.23.0",
143
- "@cloudflare/vite-plugin": "^1.9.0",
144
+ wrangler: "^4.40.3",
145
+ "@cloudflare/vite-plugin": "^1.13.8",
144
146
  "@opennextjs/cloudflare": "^1.6.5",
145
147
  "nitro-cloudflare-dev": "^0.2.2",
146
148
  "@sveltejs/adapter-cloudflare": "^7.2.1",
147
149
  "@cloudflare/workers-types": "^4.20250822.0",
148
- alchemy: "^0.67.0",
150
+ alchemy: "^0.70.0",
149
151
  nitropack: "^2.12.4",
150
152
  dotenv: "^17.2.2",
151
153
  tsdown: "^0.15.5",
@@ -198,9 +200,9 @@ const BackendSchema = z.enum([
198
200
  "hono",
199
201
  "express",
200
202
  "fastify",
201
- "next",
202
203
  "elysia",
203
204
  "convex",
205
+ "self",
204
206
  "none"
205
207
  ]).describe("Backend framework");
206
208
  const RuntimeSchema = z.enum([
@@ -346,6 +348,22 @@ function ensureSingleWebAndNative(frontends) {
346
348
  if (web.length > 1) exitWithError("Cannot select multiple web frameworks. Choose only one of: tanstack-router, tanstack-start, react-router, next, nuxt, svelte, solid");
347
349
  if (native.length > 1) exitWithError("Cannot select multiple native frameworks. Choose only one of: native-nativewind, native-unistyles");
348
350
  }
351
+ const FULLSTACK_FRONTENDS$1 = [
352
+ "next",
353
+ "nuxt",
354
+ "svelte",
355
+ "tanstack-start"
356
+ ];
357
+ function validateSelfBackendCompatibility(providedFlags, options, config) {
358
+ const backend = config.backend || options.backend;
359
+ const frontends = config.frontend || options.frontend || [];
360
+ if (backend === "self") {
361
+ if (!frontends.some((f) => FULLSTACK_FRONTENDS$1.includes(f))) exitWithError("Backend 'self' (fullstack) requires a fullstack-capable frontend. Please use --frontend with one of: next, nuxt, svelte, tanstack-start");
362
+ if (frontends.length > 1) exitWithError("Backend 'self' (fullstack) can only be used with a single frontend framework.");
363
+ }
364
+ const hasFullstackFrontend = frontends.some((f) => FULLSTACK_FRONTENDS$1.includes(f));
365
+ if (providedFlags.has("backend") && !hasFullstackFrontend && backend === "self") exitWithError("Backend 'self' (fullstack) is only compatible with fullstack-capable frontends: next, nuxt, svelte, tanstack-start. Please choose a different backend or use a fullstack frontend.");
366
+ }
349
367
  function validateWorkersCompatibility(providedFlags, options, config) {
350
368
  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.`);
351
369
  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.`);
@@ -667,36 +685,39 @@ async function getAuthChoice(auth, hasDatabase, backend, frontend) {
667
685
 
668
686
  //#endregion
669
687
  //#region src/prompts/backend.ts
688
+ const FULLSTACK_FRONTENDS = [
689
+ "next",
690
+ "nuxt",
691
+ "svelte",
692
+ "tanstack-start"
693
+ ];
670
694
  async function getBackendFrameworkChoice(backendFramework, frontends) {
671
695
  if (backendFramework !== void 0) return backendFramework;
672
696
  const hasIncompatibleFrontend = frontends?.some((f) => f === "solid");
673
- const backendOptions = [
674
- {
675
- value: "hono",
676
- label: "Hono",
677
- hint: "Lightweight, ultrafast web framework"
678
- },
679
- {
680
- value: "next",
681
- label: "Next.js",
682
- hint: "separate api routes only backend"
683
- },
684
- {
685
- value: "express",
686
- label: "Express",
687
- hint: "Fast, unopinionated, minimalist web framework for Node.js"
688
- },
689
- {
690
- value: "fastify",
691
- label: "Fastify",
692
- hint: "Fast, low-overhead web framework for Node.js"
693
- },
694
- {
695
- value: "elysia",
696
- label: "Elysia",
697
- hint: "Ergonomic web framework for building backend servers"
698
- }
699
- ];
697
+ const hasFullstackFrontend = frontends?.some((f) => FULLSTACK_FRONTENDS.includes(f));
698
+ const backendOptions = [];
699
+ if (hasFullstackFrontend) backendOptions.push({
700
+ value: "self",
701
+ label: "Self (Fullstack)",
702
+ hint: "Use frontend's built-in backend capabilities"
703
+ });
704
+ backendOptions.push({
705
+ value: "hono",
706
+ label: "Hono",
707
+ hint: "Lightweight, ultrafast web framework"
708
+ }, {
709
+ value: "express",
710
+ label: "Express",
711
+ hint: "Fast, unopinionated, minimalist web framework for Node.js"
712
+ }, {
713
+ value: "fastify",
714
+ label: "Fastify",
715
+ hint: "Fast, low-overhead web framework for Node.js"
716
+ }, {
717
+ value: "elysia",
718
+ label: "Elysia",
719
+ hint: "Ergonomic web framework for building backend servers"
720
+ });
700
721
  if (!hasIncompatibleFrontend) backendOptions.push({
701
722
  value: "convex",
702
723
  label: "Convex",
@@ -710,7 +731,7 @@ async function getBackendFrameworkChoice(backendFramework, frontends) {
710
731
  const response = await select({
711
732
  message: "Select backend",
712
733
  options: backendOptions,
713
- initialValue: DEFAULT_CONFIG.backend
734
+ initialValue: hasFullstackFrontend ? "self" : DEFAULT_CONFIG.backend
714
735
  });
715
736
  if (isCancel(response)) return exitCancelled("Operation cancelled");
716
737
  return response;
@@ -1089,9 +1110,8 @@ async function getPaymentsChoice(payments, auth, backend, frontends) {
1089
1110
  //#endregion
1090
1111
  //#region src/prompts/runtime.ts
1091
1112
  async function getRuntimeChoice(runtime, backend) {
1092
- if (backend === "convex" || backend === "none") return "none";
1113
+ if (backend === "convex" || backend === "none" || backend === "self") return "none";
1093
1114
  if (runtime !== void 0) return runtime;
1094
- if (backend === "next") return "node";
1095
1115
  const runtimeOptions = [{
1096
1116
  value: "bun",
1097
1117
  label: "Bun",
@@ -1768,8 +1788,8 @@ function validateBackendConstraints(config, providedFlags, options) {
1768
1788
  ].includes(f));
1769
1789
  if (incompatibleFrontends.length > 0) exitWithError(`Clerk authentication is not compatible with the following frontends: ${incompatibleFrontends.join(", ")}. Please choose a different frontend or auth provider.`);
1770
1790
  }
1771
- if (providedFlags.has("backend") && backend && backend !== "convex" && backend !== "none") {
1772
- if (providedFlags.has("runtime") && options.runtime === "none") exitWithError("'--runtime none' is only supported with '--backend convex' or '--backend none'. Please choose 'bun', 'node', or remove the --runtime flag.");
1791
+ if (providedFlags.has("backend") && backend && backend !== "convex" && backend !== "none" && backend !== "self") {
1792
+ if (providedFlags.has("runtime") && options.runtime === "none") exitWithError("'--runtime none' is only supported with '--backend convex', '--backend none', or '--backend self'. Please choose 'bun', 'node', or remove the --runtime flag.");
1773
1793
  }
1774
1794
  if (backend === "convex" && providedFlags.has("frontend") && options.frontend) {
1775
1795
  const incompatibleFrontends = options.frontend.filter((f) => f === "solid");
@@ -1799,6 +1819,7 @@ function validateFullConfig(config, providedFlags, options) {
1799
1819
  validateFrontendConstraints(config, providedFlags);
1800
1820
  validateApiConstraints(config, options);
1801
1821
  validateServerDeployRequiresBackend(config.serverDeploy, config.backend);
1822
+ validateSelfBackendCompatibility(providedFlags, options, config);
1802
1823
  validateWorkersCompatibility(providedFlags, options, config);
1803
1824
  if (config.runtime === "workers" && config.serverDeploy === "none") exitWithError("Cloudflare Workers runtime requires a server deployment. Please choose 'wrangler' or 'alchemy' for --server-deploy.");
1804
1825
  if (config.addons && config.addons.length > 0) {
@@ -1852,6 +1873,7 @@ const CORE_STACK_FLAGS = new Set([
1852
1873
  "examples",
1853
1874
  "auth",
1854
1875
  "dbSetup",
1876
+ "payments",
1855
1877
  "api",
1856
1878
  "webDeploy",
1857
1879
  "serverDeploy"
@@ -2660,8 +2682,12 @@ async function processTemplate(srcPath, destPath, context) {
2660
2682
  }
2661
2683
  handlebars.registerHelper("eq", (a, b) => a === b);
2662
2684
  handlebars.registerHelper("ne", (a, b) => a !== b);
2663
- handlebars.registerHelper("and", (a, b) => a && b);
2664
- handlebars.registerHelper("or", (a, b) => a || b);
2685
+ handlebars.registerHelper("and", (...args) => {
2686
+ return args.slice(0, -1).every((value) => value);
2687
+ });
2688
+ handlebars.registerHelper("or", (...args) => {
2689
+ return args.slice(0, -1).some((value) => value);
2690
+ });
2665
2691
  handlebars.registerHelper("includes", (array, value) => Array.isArray(array) && array.includes(value));
2666
2692
 
2667
2693
  //#endregion
@@ -2723,6 +2749,10 @@ async function setupFrontendTemplates(projectDir, context) {
2723
2749
  const apiWebBaseDir = path.join(PKG_ROOT, `templates/api/${context.api}/web/react/base`);
2724
2750
  if (await fs.pathExists(apiWebBaseDir)) await processAndCopyFiles("**/*", apiWebBaseDir, webAppDir, context);
2725
2751
  }
2752
+ if (context.backend === "self" && reactFramework === "next" && context.api !== "none") {
2753
+ const apiFullstackDir = path.join(PKG_ROOT, `templates/api/${context.api}/fullstack/next`);
2754
+ if (await fs.pathExists(apiFullstackDir)) await processAndCopyFiles("**/*", apiFullstackDir, webAppDir, context);
2755
+ }
2726
2756
  }
2727
2757
  } else if (hasNuxtWeb) {
2728
2758
  const nuxtBaseDir = path.join(PKG_ROOT, "templates/frontend/nuxt");
@@ -2763,40 +2793,52 @@ async function setupFrontendTemplates(projectDir, context) {
2763
2793
  }
2764
2794
  }
2765
2795
  }
2766
- async function setupBackendFramework(projectDir, context) {
2767
- if (context.backend === "none") return;
2796
+ async function setupApiPackage(projectDir, context) {
2797
+ if (context.api === "none") return;
2798
+ const apiPackageDir = path.join(projectDir, "packages/api");
2799
+ await fs.ensureDir(apiPackageDir);
2800
+ const apiServerDir = path.join(PKG_ROOT, `templates/api/${context.api}/server`);
2801
+ if (await fs.pathExists(apiServerDir)) await processAndCopyFiles("**/*", apiServerDir, apiPackageDir, context);
2802
+ }
2803
+ async function setupDbPackage(projectDir, context) {
2804
+ if (context.database === "none" || context.orm === "none") return;
2805
+ const dbPackageDir = path.join(projectDir, "packages/db");
2806
+ await fs.ensureDir(dbPackageDir);
2807
+ const dbBaseDir = path.join(PKG_ROOT, "templates/db/base");
2808
+ if (await fs.pathExists(dbBaseDir)) await processAndCopyFiles("**/*", dbBaseDir, dbPackageDir, context);
2809
+ const dbOrmSrcDir = path.join(PKG_ROOT, `templates/db/${context.orm}/${context.database}`);
2810
+ if (await fs.pathExists(dbOrmSrcDir)) await processAndCopyFiles("**/*", dbOrmSrcDir, dbPackageDir, context);
2811
+ }
2812
+ async function setupConvexBackend(projectDir, context) {
2813
+ const serverAppDir = path.join(projectDir, "apps/server");
2814
+ if (await fs.pathExists(serverAppDir)) await fs.remove(serverAppDir);
2815
+ const convexBackendDestDir = path.join(projectDir, "packages/backend");
2816
+ const convexSrcDir = path.join(PKG_ROOT, "templates/backend/convex/packages/backend");
2817
+ await fs.ensureDir(convexBackendDestDir);
2818
+ if (await fs.pathExists(convexSrcDir)) await processAndCopyFiles("**/*", convexSrcDir, convexBackendDestDir, context);
2819
+ }
2820
+ async function setupServerApp(projectDir, context) {
2768
2821
  const serverAppDir = path.join(projectDir, "apps/server");
2769
- if (context.backend === "convex") {
2770
- if (await fs.pathExists(serverAppDir)) await fs.remove(serverAppDir);
2771
- const convexBackendDestDir = path.join(projectDir, "packages/backend");
2772
- const convexSrcDir = path.join(PKG_ROOT, "templates/backend/convex/packages/backend");
2773
- await fs.ensureDir(convexBackendDestDir);
2774
- if (await fs.pathExists(convexSrcDir)) await processAndCopyFiles("**/*", convexSrcDir, convexBackendDestDir, context);
2775
- return;
2776
- }
2777
2822
  await fs.ensureDir(serverAppDir);
2778
2823
  const serverBaseDir = path.join(PKG_ROOT, "templates/backend/server/base");
2779
2824
  if (await fs.pathExists(serverBaseDir)) await processAndCopyFiles("**/*", serverBaseDir, serverAppDir, context);
2780
2825
  const frameworkSrcDir = path.join(PKG_ROOT, `templates/backend/server/${context.backend}`);
2781
2826
  if (await fs.pathExists(frameworkSrcDir)) await processAndCopyFiles("**/*", frameworkSrcDir, serverAppDir, context, true);
2782
- if (context.api !== "none") {
2783
- const apiPackageDir = path.join(projectDir, "packages/api");
2784
- await fs.ensureDir(apiPackageDir);
2785
- const apiServerBaseDir = path.join(PKG_ROOT, `templates/api/${context.api}/server/base`);
2786
- if (await fs.pathExists(apiServerBaseDir)) await processAndCopyFiles("**/*", apiServerBaseDir, apiPackageDir, context);
2787
- let apiServerFrameworkDir = "";
2788
- if (context.backend === "next") apiServerFrameworkDir = path.join(PKG_ROOT, `templates/api/${context.api}/server/${context.backend}`);
2789
- else apiServerFrameworkDir = path.join(PKG_ROOT, `templates/api/${context.api}/server/rest`);
2790
- if (await fs.pathExists(apiServerFrameworkDir)) await processAndCopyFiles("**/*", apiServerFrameworkDir, apiPackageDir, context, true);
2791
- }
2792
- if (context.database !== "none" && context.orm !== "none") {
2793
- const dbPackageDir = path.join(projectDir, "packages/db");
2794
- await fs.ensureDir(dbPackageDir);
2795
- const dbBaseDir = path.join(PKG_ROOT, "templates/db/base");
2796
- if (await fs.pathExists(dbBaseDir)) await processAndCopyFiles("**/*", dbBaseDir, dbPackageDir, context);
2797
- const dbOrmSrcDir = path.join(PKG_ROOT, `templates/db/${context.orm}/${context.database}`);
2798
- if (await fs.pathExists(dbOrmSrcDir)) await processAndCopyFiles("**/*", dbOrmSrcDir, dbPackageDir, context);
2827
+ }
2828
+ async function setupBackendFramework(projectDir, context) {
2829
+ if (context.backend === "none") return;
2830
+ if (context.backend === "convex") {
2831
+ await setupConvexBackend(projectDir, context);
2832
+ return;
2833
+ }
2834
+ if (context.backend === "self") {
2835
+ await setupApiPackage(projectDir, context);
2836
+ await setupDbPackage(projectDir, context);
2837
+ return;
2799
2838
  }
2839
+ await setupServerApp(projectDir, context);
2840
+ await setupApiPackage(projectDir, context);
2841
+ await setupDbPackage(projectDir, context);
2800
2842
  }
2801
2843
  async function setupAuthTemplate(projectDir, context) {
2802
2844
  if (!context.auth || context.auth === "none") return;
@@ -2876,15 +2918,11 @@ async function setupAuthTemplate(projectDir, context) {
2876
2918
  }
2877
2919
  return;
2878
2920
  }
2879
- if (serverAppDirExists && context.backend !== "convex") {
2921
+ if ((serverAppDirExists || context.backend === "self") && context.backend !== "convex") {
2880
2922
  const authPackageDir = path.join(projectDir, "packages/auth");
2881
2923
  await fs.ensureDir(authPackageDir);
2882
2924
  const authServerBaseSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/base`);
2883
2925
  if (await fs.pathExists(authServerBaseSrc)) await processAndCopyFiles("**/*", authServerBaseSrc, authPackageDir, context);
2884
- if (context.backend === "next") {
2885
- const authServerNextSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/next`);
2886
- if (await fs.pathExists(authServerNextSrc)) await processAndCopyFiles("**/*", authServerNextSrc, authPackageDir, context);
2887
- }
2888
2926
  if (context.orm !== "none" && context.database !== "none") {
2889
2927
  const dbPackageDir = path.join(projectDir, "packages/db");
2890
2928
  await fs.ensureDir(dbPackageDir);
@@ -2910,6 +2948,10 @@ async function setupAuthTemplate(projectDir, context) {
2910
2948
  if (reactFramework) {
2911
2949
  const authWebFrameworkSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/web/react/${reactFramework}`);
2912
2950
  if (await fs.pathExists(authWebFrameworkSrc)) await processAndCopyFiles("**/*", authWebFrameworkSrc, webAppDir, context);
2951
+ if (context.backend === "self" && reactFramework === "next") {
2952
+ const authFullstackSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/fullstack/next`);
2953
+ if (await fs.pathExists(authFullstackSrc)) await processAndCopyFiles("**/*", authFullstackSrc, webAppDir, context);
2954
+ }
2913
2955
  }
2914
2956
  } else if (hasNuxtWeb) {
2915
2957
  const authWebNuxtSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/web/nuxt`);
@@ -2940,7 +2982,7 @@ async function setupPaymentsTemplate(projectDir, context) {
2940
2982
  const webAppDir = path.join(projectDir, "apps/web");
2941
2983
  const serverAppDirExists = await fs.pathExists(serverAppDir);
2942
2984
  const webAppDirExists = await fs.pathExists(webAppDir);
2943
- if (serverAppDirExists && context.backend !== "convex") {
2985
+ if ((serverAppDirExists || context.backend === "self") && context.backend !== "convex") {
2944
2986
  const authPackageDir = path.join(projectDir, "packages/auth");
2945
2987
  await fs.ensureDir(authPackageDir);
2946
2988
  const paymentsServerSrc = path.join(PKG_ROOT, `templates/payments/${context.payments}/server/base`);
@@ -3020,7 +3062,7 @@ async function setupExamplesTemplate(projectDir, context) {
3020
3062
  for (const example of context.examples) {
3021
3063
  if (example === "none") continue;
3022
3064
  const exampleBaseDir = path.join(PKG_ROOT, `templates/examples/${example}`);
3023
- if (serverAppDirExists && context.backend !== "convex" && context.backend !== "none") {
3065
+ if ((serverAppDirExists || context.backend === "self") && context.backend !== "convex" && context.backend !== "none") {
3024
3066
  const exampleServerSrc = path.join(exampleBaseDir, "server");
3025
3067
  if (context.api !== "none") {
3026
3068
  const apiPackageDir = path.join(projectDir, "packages/api");
@@ -3034,10 +3076,6 @@ async function setupExamplesTemplate(projectDir, context) {
3034
3076
  const exampleDbSchemaSrc = path.join(exampleServerSrc, context.orm, context.database);
3035
3077
  if (await fs.pathExists(exampleDbSchemaSrc)) await processAndCopyFiles("**/*", exampleDbSchemaSrc, dbPackageDir, context, false);
3036
3078
  }
3037
- if (example === "ai" && context.backend === "next") {
3038
- const aiNextServerSrc = path.join(exampleServerSrc, "next");
3039
- if (await fs.pathExists(aiNextServerSrc)) await processAndCopyFiles("**/*", aiNextServerSrc, serverAppDir, context, false);
3040
- }
3041
3079
  }
3042
3080
  if (webAppDirExists) {
3043
3081
  if (hasReactWeb) {
@@ -3056,6 +3094,10 @@ async function setupExamplesTemplate(projectDir, context) {
3056
3094
  if (reactFramework) {
3057
3095
  const exampleWebFrameworkSrc = path.join(exampleWebSrc, reactFramework);
3058
3096
  if (await fs.pathExists(exampleWebFrameworkSrc)) await processAndCopyFiles("**/*", exampleWebFrameworkSrc, webAppDir, context, false);
3097
+ if (context.backend === "self" && reactFramework === "next") {
3098
+ const exampleFullstackSrc = path.join(exampleBaseDir, "fullstack/next");
3099
+ if (await fs.pathExists(exampleFullstackSrc)) await processAndCopyFiles("**/*", exampleFullstackSrc, webAppDir, context, false);
3100
+ }
3059
3101
  }
3060
3102
  }
3061
3103
  } else if (hasNuxtWeb) {
@@ -3526,8 +3568,8 @@ async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, opti
3526
3568
  await addPackageDependency({
3527
3569
  devDependencies: [
3528
3570
  "alchemy",
3529
- "nitropack",
3530
- "dotenv"
3571
+ "dotenv",
3572
+ "@cloudflare/vite-plugin"
3531
3573
  ],
3532
3574
  projectDir: webAppDir
3533
3575
  });
@@ -3555,17 +3597,6 @@ async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, opti
3555
3597
  defaultImport: "alchemy"
3556
3598
  });
3557
3599
  else alchemyImport.setModuleSpecifier("alchemy/cloudflare/tanstack-start");
3558
- const reactImport = sourceFile.getImportDeclaration("@vitejs/plugin-react");
3559
- let reactPluginIdentifier = "viteReact";
3560
- if (!reactImport) sourceFile.addImportDeclaration({
3561
- moduleSpecifier: "@vitejs/plugin-react",
3562
- defaultImport: "viteReact"
3563
- });
3564
- else {
3565
- const defaultImport = reactImport.getDefaultImport();
3566
- if (defaultImport) reactPluginIdentifier = defaultImport.getText();
3567
- else reactImport.setDefaultImport("viteReact");
3568
- }
3569
3600
  const exportAssignment = sourceFile.getExportAssignment((d) => !d.isExportEquals());
3570
3601
  if (!exportAssignment) return;
3571
3602
  const defineConfigCall = exportAssignment.getExpression();
@@ -3573,47 +3604,11 @@ async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, opti
3573
3604
  let configObject = defineConfigCall.getArguments()[0];
3574
3605
  if (!configObject) configObject = defineConfigCall.addArgument("{}");
3575
3606
  if (Node.isObjectLiteralExpression(configObject)) {
3576
- if (!configObject.getProperty("build")) configObject.addPropertyAssignment({
3577
- name: "build",
3578
- initializer: `{
3579
- target: "esnext",
3580
- rollupOptions: {
3581
- external: ["node:async_hooks", "cloudflare:workers"],
3582
- },
3583
- }`
3584
- });
3585
3607
  const pluginsProperty = configObject.getProperty("plugins");
3586
3608
  if (pluginsProperty && Node.isPropertyAssignment(pluginsProperty)) {
3587
3609
  const initializer = pluginsProperty.getInitializer();
3588
3610
  if (Node.isArrayLiteralExpression(initializer)) {
3589
- if (!initializer.getElements().some((el) => el.getText().includes("alchemy"))) initializer.addElement("alchemy()");
3590
- const tanstackElements = initializer.getElements().filter((el) => el.getText().includes("tanstackStart"));
3591
- let needsReactPlugin = false;
3592
- tanstackElements.forEach((element) => {
3593
- if (Node.isCallExpression(element)) {
3594
- const args = element.getArguments();
3595
- if (args.length === 0) {
3596
- element.addArgument(`{
3597
- target: "cloudflare-module",
3598
- customViteReactPlugin: true,
3599
- }`);
3600
- needsReactPlugin = true;
3601
- } else if (args.length === 1 && Node.isObjectLiteralExpression(args[0])) {
3602
- const configObj = args[0];
3603
- if (!configObj.getProperty("target")) configObj.addPropertyAssignment({
3604
- name: "target",
3605
- initializer: "\"cloudflare-module\""
3606
- });
3607
- if (!!!configObj.getProperty("customViteReactPlugin")) configObj.addPropertyAssignment({
3608
- name: "customViteReactPlugin",
3609
- initializer: "true"
3610
- });
3611
- needsReactPlugin = true;
3612
- }
3613
- }
3614
- });
3615
- const hasReactPlugin = initializer.getElements().some((el) => Node.isCallExpression(el) && el.getExpression().getText() === reactPluginIdentifier);
3616
- if (needsReactPlugin && !hasReactPlugin) initializer.addElement(`${reactPluginIdentifier}()`);
3611
+ if (!initializer.getElements().some((el) => el.getText().includes("alchemy("))) initializer.addElement("alchemy()");
3617
3612
  }
3618
3613
  } else configObject.addPropertyAssignment({
3619
3614
  name: "plugins",
@@ -3624,16 +3619,6 @@ async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, opti
3624
3619
  } catch (error) {
3625
3620
  console.warn("Failed to update vite.config.ts:", error);
3626
3621
  }
3627
- const nitroConfigPath = path.join(webAppDir, "nitro.config.ts");
3628
- await fs.writeFile(nitroConfigPath, `import { defineNitroConfig } from "nitropack/config";
3629
-
3630
- export default defineNitroConfig({
3631
- preset: "cloudflare-module",
3632
- cloudflare: {
3633
- nodeCompat: true,
3634
- },
3635
- });
3636
- `, "utf-8");
3637
3622
  }
3638
3623
 
3639
3624
  //#endregion
@@ -3800,7 +3785,7 @@ async function setupTanstackStartWorkersDeploy(projectDir, packageManager) {
3800
3785
  const webAppDir = path.join(projectDir, "apps/web");
3801
3786
  if (!await fs.pathExists(webAppDir)) return;
3802
3787
  await addPackageDependency({
3803
- devDependencies: ["wrangler"],
3788
+ devDependencies: ["wrangler", "@cloudflare/vite-plugin"],
3804
3789
  projectDir: webAppDir
3805
3790
  });
3806
3791
  const pkgPath = path.join(webAppDir, "package.json");
@@ -3817,6 +3802,12 @@ async function setupTanstackStartWorkersDeploy(projectDir, packageManager) {
3817
3802
  if (!await fs.pathExists(viteConfigPath)) return;
3818
3803
  const sourceFile = tsProject.addSourceFileAtPathIfExists(viteConfigPath);
3819
3804
  if (!sourceFile) return;
3805
+ const cfImport = sourceFile.getImportDeclaration("@cloudflare/vite-plugin");
3806
+ if (!cfImport) sourceFile.addImportDeclaration({
3807
+ moduleSpecifier: "@cloudflare/vite-plugin",
3808
+ namedImports: [{ name: "cloudflare" }]
3809
+ });
3810
+ else if (!cfImport.getNamedImports().some((ni) => ni.getName() === "cloudflare")) cfImport.addNamedImport({ name: "cloudflare" });
3820
3811
  const reactImport = sourceFile.getImportDeclaration("@vitejs/plugin-react");
3821
3812
  let reactPluginIdentifier = "viteReact";
3822
3813
  if (!reactImport) sourceFile.addImportDeclaration({
@@ -3836,10 +3827,7 @@ async function setupTanstackStartWorkersDeploy(projectDir, packageManager) {
3836
3827
  const configObj = defineCall.getArguments()[0];
3837
3828
  if (!configObj) return;
3838
3829
  const pluginsArray = ensureArrayProperty(configObj, "plugins");
3839
- const tanstackPluginIndex = pluginsArray.getElements().findIndex((el) => el.getText().includes("tanstackStart("));
3840
- const tanstackPluginText = "tanstackStart({ target: \"cloudflare-module\", customViteReactPlugin: true })";
3841
- if (tanstackPluginIndex === -1) pluginsArray.addElement(tanstackPluginText);
3842
- else pluginsArray.getElements()[tanstackPluginIndex].replaceWithText(tanstackPluginText);
3830
+ if (!pluginsArray.getElements().some((el) => el.getText().includes("cloudflare("))) pluginsArray.insertElement(0, "cloudflare({ viteEnvironment: { name: 'ssr' } })");
3843
3831
  if (!pluginsArray.getElements().some((el) => Node.isCallExpression(el) && el.getExpression().getText() === reactPluginIdentifier)) {
3844
3832
  const nextIndex = pluginsArray.getElements().findIndex((el) => el.getText().includes("tanstackStart(")) + 1;
3845
3833
  if (nextIndex > 0) pluginsArray.insertElement(nextIndex, `${reactPluginIdentifier}()`);
@@ -3977,18 +3965,137 @@ async function addDeploymentToProject(input) {
3977
3965
  }
3978
3966
  }
3979
3967
 
3968
+ //#endregion
3969
+ //#region src/utils/setup-catalogs.ts
3970
+ async function setupCatalogs(projectDir, options) {
3971
+ if (options.packageManager === "npm") return;
3972
+ const packagePaths = [
3973
+ "apps/server",
3974
+ "apps/web",
3975
+ "packages/api",
3976
+ "packages/db",
3977
+ "packages/auth",
3978
+ "packages/backend"
3979
+ ];
3980
+ const packagesInfo = [];
3981
+ for (const pkgPath of packagePaths) {
3982
+ const fullPath = path.join(projectDir, pkgPath);
3983
+ const pkgJsonPath = path.join(fullPath, "package.json");
3984
+ if (await fs.pathExists(pkgJsonPath)) {
3985
+ const pkgJson = await fs.readJson(pkgJsonPath);
3986
+ packagesInfo.push({
3987
+ path: fullPath,
3988
+ dependencies: pkgJson.dependencies || {},
3989
+ devDependencies: pkgJson.devDependencies || {}
3990
+ });
3991
+ }
3992
+ }
3993
+ const catalog = findDuplicateDependencies(packagesInfo, options.projectName);
3994
+ if (Object.keys(catalog).length === 0) return;
3995
+ if (options.packageManager === "bun") await setupBunCatalogs(projectDir, catalog);
3996
+ else if (options.packageManager === "pnpm") await setupPnpmCatalogs(projectDir, catalog);
3997
+ await updatePackageJsonsWithCatalogs(packagesInfo, catalog);
3998
+ }
3999
+ function findDuplicateDependencies(packagesInfo, projectName) {
4000
+ const depCount = /* @__PURE__ */ new Map();
4001
+ const projectScope = `@${projectName}/`;
4002
+ for (const pkg of packagesInfo) {
4003
+ const allDeps = {
4004
+ ...pkg.dependencies,
4005
+ ...pkg.devDependencies
4006
+ };
4007
+ for (const [depName, version] of Object.entries(allDeps)) {
4008
+ if (depName.startsWith(projectScope)) continue;
4009
+ if (version.startsWith("workspace:")) continue;
4010
+ const existing = depCount.get(depName);
4011
+ if (existing) existing.packages.push(pkg.path);
4012
+ else depCount.set(depName, {
4013
+ version,
4014
+ packages: [pkg.path]
4015
+ });
4016
+ }
4017
+ }
4018
+ const catalog = {};
4019
+ for (const [depName, info] of depCount.entries()) if (info.packages.length > 1) catalog[depName] = info.version;
4020
+ return catalog;
4021
+ }
4022
+ async function setupBunCatalogs(projectDir, catalog) {
4023
+ const rootPkgJsonPath = path.join(projectDir, "package.json");
4024
+ const rootPkgJson = await fs.readJson(rootPkgJsonPath);
4025
+ if (!rootPkgJson.workspaces) rootPkgJson.workspaces = {};
4026
+ if (Array.isArray(rootPkgJson.workspaces)) rootPkgJson.workspaces = {
4027
+ packages: rootPkgJson.workspaces,
4028
+ catalog
4029
+ };
4030
+ else if (typeof rootPkgJson.workspaces === "object") {
4031
+ if (!rootPkgJson.workspaces.catalog) rootPkgJson.workspaces.catalog = {};
4032
+ rootPkgJson.workspaces.catalog = {
4033
+ ...rootPkgJson.workspaces.catalog,
4034
+ ...catalog
4035
+ };
4036
+ }
4037
+ await fs.writeJson(rootPkgJsonPath, rootPkgJson, { spaces: 2 });
4038
+ }
4039
+ async function setupPnpmCatalogs(projectDir, catalog) {
4040
+ const workspaceYamlPath = path.join(projectDir, "pnpm-workspace.yaml");
4041
+ if (!await fs.pathExists(workspaceYamlPath)) return;
4042
+ const workspaceContent = await fs.readFile(workspaceYamlPath, "utf-8");
4043
+ const workspaceYaml = yaml.parse(workspaceContent);
4044
+ if (!workspaceYaml.catalog) workspaceYaml.catalog = {};
4045
+ workspaceYaml.catalog = {
4046
+ ...workspaceYaml.catalog,
4047
+ ...catalog
4048
+ };
4049
+ await fs.writeFile(workspaceYamlPath, yaml.stringify(workspaceYaml));
4050
+ }
4051
+ async function updatePackageJsonsWithCatalogs(packagesInfo, catalog) {
4052
+ for (const pkg of packagesInfo) {
4053
+ const pkgJsonPath = path.join(pkg.path, "package.json");
4054
+ const pkgJson = await fs.readJson(pkgJsonPath);
4055
+ let updated = false;
4056
+ if (pkgJson.dependencies) {
4057
+ for (const depName of Object.keys(pkgJson.dependencies)) if (catalog[depName]) {
4058
+ pkgJson.dependencies[depName] = "catalog:";
4059
+ updated = true;
4060
+ }
4061
+ }
4062
+ if (pkgJson.devDependencies) {
4063
+ for (const depName of Object.keys(pkgJson.devDependencies)) if (catalog[depName]) {
4064
+ pkgJson.devDependencies[depName] = "catalog:";
4065
+ updated = true;
4066
+ }
4067
+ }
4068
+ if (updated) await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
4069
+ }
4070
+ }
4071
+
3980
4072
  //#endregion
3981
4073
  //#region src/helpers/addons/examples-setup.ts
3982
4074
  async function setupExamples(config) {
3983
- const { examples, frontend, backend, projectDir } = config;
4075
+ const { examples, frontend, backend, projectDir, orm } = config;
3984
4076
  if (backend === "convex" || !examples || examples.length === 0 || examples[0] === "none") return;
4077
+ const apiDir = path.join(projectDir, "packages/api");
4078
+ if (await fs.pathExists(apiDir) && backend !== "none") {
4079
+ if (orm === "drizzle") await addPackageDependency({
4080
+ dependencies: ["drizzle-orm"],
4081
+ projectDir: apiDir
4082
+ });
4083
+ else if (orm === "prisma") await addPackageDependency({
4084
+ dependencies: ["@prisma/client"],
4085
+ projectDir: apiDir
4086
+ });
4087
+ else if (orm === "mongoose") await addPackageDependency({
4088
+ dependencies: ["mongoose"],
4089
+ projectDir: apiDir
4090
+ });
4091
+ }
3985
4092
  if (examples.includes("ai")) {
3986
4093
  const webClientDir = path.join(projectDir, "apps/web");
3987
4094
  const nativeClientDir = path.join(projectDir, "apps/native");
3988
- const apiDir = path.join(projectDir, "packages/api");
4095
+ const apiDir$1 = path.join(projectDir, "packages/api");
3989
4096
  const webClientDirExists = await fs.pathExists(webClientDir);
3990
4097
  const nativeClientDirExists = await fs.pathExists(nativeClientDir);
3991
- const apiDirExists = await fs.pathExists(apiDir);
4098
+ const apiDirExists = await fs.pathExists(apiDir$1);
3992
4099
  const hasNuxt = frontend.includes("nuxt");
3993
4100
  const hasSvelte = frontend.includes("svelte");
3994
4101
  const hasReactWeb = frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("next") || frontend.includes("tanstack-start");
@@ -4011,7 +4118,7 @@ async function setupExamples(config) {
4011
4118
  });
4012
4119
  if (apiDirExists && backend !== "none") await addPackageDependency({
4013
4120
  dependencies: ["ai", "@ai-sdk/google"],
4014
- projectDir: apiDir
4121
+ projectDir: apiDir$1
4015
4122
  });
4016
4123
  }
4017
4124
  }
@@ -4149,6 +4256,9 @@ async function setupApi(config) {
4149
4256
  else if (backend === "elysia") frameworkDeps.push("elysia");
4150
4257
  else if (backend === "express") frameworkDeps.push("express", "@types/express");
4151
4258
  else if (backend === "fastify") frameworkDeps.push("fastify");
4259
+ else if (backend === "self") {
4260
+ if (frontend.includes("next")) frameworkDeps.push("next");
4261
+ }
4152
4262
  if (frameworkDeps.length > 0) await addPackageDependency({
4153
4263
  dependencies: frameworkDeps,
4154
4264
  projectDir: apiPackageDir
@@ -4447,7 +4557,7 @@ async function setupEnvironmentVariables(config) {
4447
4557
  const clientVars = [{
4448
4558
  key: envVarName,
4449
4559
  value: serverUrl,
4450
- condition: true
4560
+ condition: backend !== "self"
4451
4561
  }];
4452
4562
  if (backend === "convex" && auth === "clerk") {
4453
4563
  if (hasNextJs) clientVars.push({
@@ -4556,47 +4666,47 @@ async function setupEnvironmentVariables(config) {
4556
4666
  else databaseUrl = `file:${path.join(config.projectDir, "apps/server", "local.db")}`;
4557
4667
  break;
4558
4668
  }
4559
- if (await fs.pathExists(serverDir)) {
4560
- const serverEnvPath = path.join(serverDir, ".env");
4561
- const serverVars = [
4562
- {
4563
- key: "BETTER_AUTH_SECRET",
4564
- value: generateAuthSecret(),
4565
- condition: !!auth
4566
- },
4567
- {
4568
- key: "BETTER_AUTH_URL",
4569
- value: "http://localhost:3000",
4570
- condition: !!auth
4571
- },
4572
- {
4573
- key: "POLAR_ACCESS_TOKEN",
4574
- value: "",
4575
- condition: config.payments === "polar"
4576
- },
4577
- {
4578
- key: "POLAR_SUCCESS_URL",
4579
- value: `${corsOrigin}/success?checkout_id={CHECKOUT_ID}`,
4580
- condition: config.payments === "polar"
4581
- },
4582
- {
4583
- key: "CORS_ORIGIN",
4584
- value: corsOrigin,
4585
- condition: true
4586
- },
4587
- {
4588
- key: "GOOGLE_GENERATIVE_AI_API_KEY",
4589
- value: "",
4590
- condition: examples?.includes("ai") || false
4591
- },
4592
- {
4593
- key: "DATABASE_URL",
4594
- value: databaseUrl,
4595
- condition: database !== "none" && dbSetup === "none"
4596
- }
4597
- ];
4598
- await addEnvVariablesToFile(serverEnvPath, serverVars);
4599
- }
4669
+ const serverVars = [
4670
+ {
4671
+ key: "BETTER_AUTH_SECRET",
4672
+ value: generateAuthSecret(),
4673
+ condition: !!auth
4674
+ },
4675
+ {
4676
+ key: "BETTER_AUTH_URL",
4677
+ value: "http://localhost:3000",
4678
+ condition: !!auth
4679
+ },
4680
+ {
4681
+ key: "POLAR_ACCESS_TOKEN",
4682
+ value: "",
4683
+ condition: config.payments === "polar"
4684
+ },
4685
+ {
4686
+ key: "POLAR_SUCCESS_URL",
4687
+ value: `${corsOrigin}/success?checkout_id={CHECKOUT_ID}`,
4688
+ condition: config.payments === "polar"
4689
+ },
4690
+ {
4691
+ key: "CORS_ORIGIN",
4692
+ value: corsOrigin,
4693
+ condition: true
4694
+ },
4695
+ {
4696
+ key: "GOOGLE_GENERATIVE_AI_API_KEY",
4697
+ value: "",
4698
+ condition: examples?.includes("ai") || false
4699
+ },
4700
+ {
4701
+ key: "DATABASE_URL",
4702
+ value: databaseUrl,
4703
+ condition: database !== "none" && dbSetup === "none"
4704
+ }
4705
+ ];
4706
+ if (backend === "self") {
4707
+ const webDir = path.join(projectDir, "apps/web");
4708
+ if (await fs.pathExists(webDir)) await addEnvVariablesToFile(path.join(webDir, ".env"), serverVars);
4709
+ } else if (await fs.pathExists(serverDir)) await addEnvVariablesToFile(path.join(serverDir, ".env"), serverVars);
4600
4710
  const isUnifiedAlchemy = webDeploy === "alchemy" && serverDeploy === "alchemy";
4601
4711
  const isIndividualAlchemy = webDeploy === "alchemy" || serverDeploy === "alchemy";
4602
4712
  if (isUnifiedAlchemy) {
@@ -4615,11 +4725,17 @@ async function setupEnvironmentVariables(config) {
4615
4725
  condition: true
4616
4726
  }]);
4617
4727
  }
4618
- if (serverDeploy === "alchemy") await addEnvVariablesToFile(path.join(serverDir, ".env"), [{
4619
- key: "ALCHEMY_PASSWORD",
4620
- value: "please-change-this",
4621
- condition: true
4622
- }]);
4728
+ if (serverDeploy === "alchemy") {
4729
+ const serverAlchemyVars = [{
4730
+ key: "ALCHEMY_PASSWORD",
4731
+ value: "please-change-this",
4732
+ condition: true
4733
+ }];
4734
+ if (backend === "self") {
4735
+ const webDir = path.join(projectDir, "apps/web");
4736
+ if (await fs.pathExists(webDir)) await addEnvVariablesToFile(path.join(webDir, ".env"), serverAlchemyVars);
4737
+ } else await addEnvVariablesToFile(path.join(serverDir, ".env"), serverAlchemyVars);
4738
+ }
4623
4739
  }
4624
4740
  }
4625
4741
 
@@ -5756,7 +5872,7 @@ async function setupDatabase(config, cliInput) {
5756
5872
  //#region src/helpers/core/runtime-setup.ts
5757
5873
  async function setupRuntime(config) {
5758
5874
  const { runtime, backend, projectDir } = config;
5759
- if (backend === "convex" || backend === "next" || runtime === "none") return;
5875
+ if (backend === "convex" || backend === "self" || runtime === "none") return;
5760
5876
  const serverDir = path.join(projectDir, "apps/server");
5761
5877
  if (!await fs.pathExists(serverDir)) return;
5762
5878
  if (runtime === "bun") await setupBunRuntime(serverDir, backend);
@@ -6276,7 +6392,7 @@ async function displayPostInstallInstructions(config) {
6276
6392
  else if (!hasNative && !addons?.includes("starlight")) output += `${pc.yellow("NOTE:")} You are creating a backend-only app\n (no frontend selected)\n`;
6277
6393
  if (!isConvex) {
6278
6394
  output += `${pc.cyan("•")} Backend API: http://localhost:3000\n`;
6279
- if (api === "orpc") if (backend === "next") output += `${pc.cyan("•")} OpenAPI (Scalar UI): http://localhost:3000/rpc/api\n`;
6395
+ if (api === "orpc") if (backend === "self") output += `${pc.cyan("•")} OpenAPI (Scalar UI): http://localhost:3000/rpc/api\n`;
6280
6396
  else output += `${pc.cyan("•")} OpenAPI (Scalar UI): http://localhost:3000/api\n`;
6281
6397
  }
6282
6398
  if (addons?.includes("starlight")) output += `${pc.cyan("•")} Docs: http://localhost:4321\n`;
@@ -6431,10 +6547,17 @@ async function setupWorkspaceDependencies(projectDir, options) {
6431
6547
  },
6432
6548
  projectDir: serverPackageDir
6433
6549
  });
6434
- if (options.api && options.api !== "none") {
6435
- const webPackageDir = path.join(projectDir, "apps/web");
6436
- if (await fs.pathExists(webPackageDir)) await addPackageDependency({
6437
- customDependencies: { [`@${projectName}/api`]: workspaceVersion },
6550
+ const needsApiDependency = options.api && options.api !== "none";
6551
+ const webPackageDir = path.join(projectDir, "apps/web");
6552
+ if (await fs.pathExists(webPackageDir)) {
6553
+ const webDeps = {};
6554
+ if (options.backend === "self") {
6555
+ webDeps[`@${projectName}/api`] = workspaceVersion;
6556
+ webDeps[`@${projectName}/auth`] = workspaceVersion;
6557
+ webDeps[`@${projectName}/db`] = workspaceVersion;
6558
+ } else if (needsApiDependency) webDeps[`@${projectName}/api`] = workspaceVersion;
6559
+ if (Object.keys(webDeps).length > 0) await addPackageDependency({
6560
+ customDependencies: webDeps,
6438
6561
  projectDir: webPackageDir
6439
6562
  });
6440
6563
  }
@@ -6444,12 +6567,18 @@ async function setupWorkspaceDependencies(projectDir, options) {
6444
6567
  //#region src/helpers/core/project-config.ts
6445
6568
  async function updatePackageConfigurations(projectDir, options) {
6446
6569
  await updateRootPackageJson(projectDir, options);
6447
- if (options.backend !== "convex") {
6570
+ if (options.backend === "convex") await updateConvexPackageJson(projectDir, options);
6571
+ else if (options.backend === "self") {
6572
+ await updateDbPackageJson(projectDir, options);
6573
+ await updateAuthPackageJson(projectDir, options);
6574
+ await updateApiPackageJson(projectDir, options);
6575
+ await setupWorkspaceDependencies(projectDir, options);
6576
+ } else if (options.backend !== "none") {
6448
6577
  await updateServerPackageJson(projectDir, options);
6449
6578
  await updateAuthPackageJson(projectDir, options);
6450
6579
  await updateApiPackageJson(projectDir, options);
6451
6580
  await setupWorkspaceDependencies(projectDir, options);
6452
- } else await updateConvexPackageJson(projectDir, options);
6581
+ }
6453
6582
  }
6454
6583
  async function updateRootPackageJson(projectDir, options) {
6455
6584
  const rootPackageJsonPath = path.join(projectDir, "package.json");
@@ -6476,7 +6605,7 @@ async function updateRootPackageJson(projectDir, options) {
6476
6605
  scripts["check-types"] = "turbo check-types";
6477
6606
  scripts["dev:native"] = "turbo -F native dev";
6478
6607
  scripts["dev:web"] = "turbo -F web dev";
6479
- scripts["dev:server"] = serverDevScript;
6608
+ if (options.backend !== "self" && options.backend !== "none") scripts["dev:server"] = serverDevScript;
6480
6609
  if (options.backend === "convex") scripts["dev:setup"] = `turbo -F ${backendPackageName} dev:setup`;
6481
6610
  if (needsDbScripts) {
6482
6611
  scripts["db:push"] = `turbo -F ${dbPackageName} db:push`;
@@ -6501,7 +6630,7 @@ async function updateRootPackageJson(projectDir, options) {
6501
6630
  scripts["check-types"] = "pnpm -r check-types";
6502
6631
  scripts["dev:native"] = "pnpm --filter native dev";
6503
6632
  scripts["dev:web"] = "pnpm --filter web dev";
6504
- scripts["dev:server"] = serverDevScript;
6633
+ if (options.backend !== "self" && options.backend !== "none") scripts["dev:server"] = serverDevScript;
6505
6634
  if (options.backend === "convex") scripts["dev:setup"] = `pnpm --filter ${backendPackageName} dev:setup`;
6506
6635
  if (needsDbScripts) {
6507
6636
  scripts["db:push"] = `pnpm --filter ${dbPackageName} db:push`;
@@ -6526,7 +6655,7 @@ async function updateRootPackageJson(projectDir, options) {
6526
6655
  scripts["check-types"] = "npm run check-types --workspaces";
6527
6656
  scripts["dev:native"] = "npm run dev --workspace native";
6528
6657
  scripts["dev:web"] = "npm run dev --workspace web";
6529
- scripts["dev:server"] = serverDevScript;
6658
+ if (options.backend !== "self" && options.backend !== "none") scripts["dev:server"] = serverDevScript;
6530
6659
  if (options.backend === "convex") scripts["dev:setup"] = `npm run dev:setup --workspace ${backendPackageName}`;
6531
6660
  if (needsDbScripts) {
6532
6661
  scripts["db:push"] = `npm run db:push --workspace ${dbPackageName}`;
@@ -6551,7 +6680,7 @@ async function updateRootPackageJson(projectDir, options) {
6551
6680
  scripts["check-types"] = "bun run --filter '*' check-types";
6552
6681
  scripts["dev:native"] = "bun run --filter native dev";
6553
6682
  scripts["dev:web"] = "bun run --filter web dev";
6554
- scripts["dev:server"] = serverDevScript;
6683
+ if (options.backend !== "self" && options.backend !== "none") scripts["dev:server"] = serverDevScript;
6555
6684
  if (options.backend === "convex") scripts["dev:setup"] = `bun run --filter ${backendPackageName} dev:setup`;
6556
6685
  if (needsDbScripts) {
6557
6686
  scripts["db:push"] = `bun run --filter ${dbPackageName} db:push`;
@@ -6654,12 +6783,14 @@ async function updateConvexPackageJson(projectDir, options) {
6654
6783
  async function createProject(options, cliInput) {
6655
6784
  const projectDir = options.projectDir;
6656
6785
  const isConvex = options.backend === "convex";
6786
+ const isSelfBackend = options.backend === "self";
6787
+ const needsServerSetup = !isConvex && !isSelfBackend;
6657
6788
  try {
6658
6789
  await fs.ensureDir(projectDir);
6659
6790
  await copyBaseTemplate(projectDir, options);
6660
6791
  await setupFrontendTemplates(projectDir, options);
6661
6792
  await setupBackendFramework(projectDir, options);
6662
- if (!isConvex) await setupDockerComposeTemplates(projectDir, options);
6793
+ if (needsServerSetup) await setupDockerComposeTemplates(projectDir, options);
6663
6794
  await setupAuthTemplate(projectDir, options);
6664
6795
  if (options.payments && options.payments !== "none") await setupPaymentsTemplate(projectDir, options);
6665
6796
  if (options.examples.length > 0 && options.examples[0] !== "none") await setupExamplesTemplate(projectDir, options);
@@ -6667,9 +6798,11 @@ async function createProject(options, cliInput) {
6667
6798
  await setupDeploymentTemplates(projectDir, options);
6668
6799
  await setupApi(options);
6669
6800
  if (!isConvex) {
6670
- await setupBackendDependencies(options);
6801
+ if (needsServerSetup) {
6802
+ await setupBackendDependencies(options);
6803
+ await setupRuntime(options);
6804
+ }
6671
6805
  await setupDatabase(options, cliInput);
6672
- await setupRuntime(options);
6673
6806
  if (options.examples.length > 0 && options.examples[0] !== "none") await setupExamples(options);
6674
6807
  }
6675
6808
  if (options.addons.length > 0 && options.addons[0] !== "none") await setupAddons(options);
@@ -6678,6 +6811,7 @@ async function createProject(options, cliInput) {
6678
6811
  await handleExtras(projectDir, options);
6679
6812
  await setupEnvironmentVariables(options);
6680
6813
  await updatePackageConfigurations(projectDir, options);
6814
+ await setupCatalogs(projectDir, options);
6681
6815
  await setupWebDeploy(options);
6682
6816
  await setupServerDeploy(options);
6683
6817
  await createReadme(projectDir, options);