create-better-t-stack 2.49.1 → 2.50.0-canary.dd7000f2

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 (96) 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-CKCxH6aF.js → src-dv6H37db.js} +518 -286
  5. package/package.json +2 -1
  6. package/templates/api/orpc/server/_gitignore +34 -0
  7. package/templates/api/orpc/server/package.json.hbs +24 -0
  8. package/templates/api/orpc/server/{base/src/lib → src}/context.ts.hbs +6 -6
  9. package/templates/{backend/server/server-base → api/orpc/server}/src/routers/index.ts.hbs +2 -2
  10. package/templates/api/orpc/server/tsconfig.json.hbs +10 -0
  11. package/templates/api/orpc/server/tsdown.config.ts.hbs +7 -0
  12. package/templates/api/orpc/web/nuxt/app/plugins/orpc.ts.hbs +1 -1
  13. package/templates/api/orpc/web/react/base/src/utils/orpc.ts.hbs +1 -1
  14. package/templates/api/orpc/web/solid/src/utils/orpc.ts.hbs +1 -1
  15. package/templates/api/orpc/web/svelte/src/lib/orpc.ts.hbs +1 -1
  16. package/templates/api/trpc/server/_gitignore +34 -0
  17. package/templates/api/trpc/server/package.json.hbs +23 -0
  18. package/templates/api/trpc/server/{base/src/lib → src}/context.ts.hbs +6 -6
  19. package/templates/api/trpc/server/src/routers/index.ts.hbs +55 -0
  20. package/templates/api/trpc/server/tsconfig.json.hbs +13 -0
  21. package/templates/api/trpc/server/tsdown.config.ts.hbs +7 -0
  22. package/templates/api/trpc/web/react/base/src/utils/trpc.ts.hbs +2 -2
  23. package/templates/auth/better-auth/server/base/_gitignore +34 -0
  24. package/templates/auth/better-auth/server/base/package.json.hbs +24 -0
  25. package/templates/auth/better-auth/server/base/src/{lib/auth.ts.hbs → index.ts.hbs} +7 -7
  26. package/templates/auth/better-auth/server/base/tsconfig.json.hbs +13 -0
  27. package/templates/auth/better-auth/server/base/tsdown.config.ts.hbs +7 -0
  28. package/templates/auth/clerk/convex/web/react/tanstack-start/src/server.ts.hbs +1 -0
  29. package/templates/backend/server/{server-base → base}/package.json.hbs +0 -1
  30. package/templates/backend/server/{server-base → base}/tsconfig.json.hbs +5 -10
  31. package/templates/backend/server/base/tsdown.config.ts.hbs +14 -0
  32. package/templates/backend/server/elysia/src/index.ts.hbs +6 -6
  33. package/templates/backend/server/express/src/index.ts.hbs +6 -6
  34. package/templates/backend/server/fastify/src/index.ts.hbs +6 -6
  35. package/templates/backend/server/hono/src/index.ts.hbs +7 -7
  36. package/templates/base/_gitignore +47 -1
  37. package/templates/base/package.json.hbs +1 -3
  38. package/templates/base/tsconfig.base.json +23 -0
  39. package/templates/db/base/_gitignore +34 -0
  40. package/templates/db/base/package.json.hbs +23 -0
  41. package/templates/db/base/tsconfig.json.hbs +13 -0
  42. package/templates/db/base/tsdown.config.ts.hbs +7 -0
  43. package/templates/db/drizzle/mysql/drizzle.config.ts.hbs +7 -2
  44. package/templates/db/drizzle/mysql/src/{db/index.ts.hbs → index.ts.hbs} +1 -1
  45. package/templates/db/drizzle/postgres/drizzle.config.ts.hbs +7 -2
  46. package/templates/db/drizzle/postgres/src/{db/index.ts.hbs → index.ts.hbs} +1 -1
  47. package/templates/db/drizzle/sqlite/drizzle.config.ts.hbs +7 -2
  48. package/templates/db/drizzle/sqlite/src/{db/index.ts.hbs → index.ts.hbs} +1 -1
  49. package/templates/db/prisma/mongodb/prisma.config.ts.hbs +5 -1
  50. package/templates/db/prisma/mongodb/src/index.ts.hbs +5 -0
  51. package/templates/db/prisma/mysql/prisma.config.ts.hbs +5 -1
  52. package/templates/db/prisma/mysql/src/{db/index.ts.hbs → index.ts.hbs} +1 -1
  53. package/templates/db/prisma/postgres/prisma.config.ts.hbs +7 -3
  54. package/templates/db/prisma/postgres/src/{db/index.ts.hbs → index.ts.hbs} +1 -1
  55. package/templates/db/prisma/sqlite/prisma.config.ts.hbs +5 -1
  56. package/templates/db/prisma/sqlite/src/{db/index.ts.hbs → index.ts.hbs} +3 -3
  57. package/templates/deploy/wrangler/web/react/tanstack-start/wrangler.jsonc.hbs +1 -1
  58. package/templates/examples/todo/server/drizzle/base/src/routers/todo.ts.hbs +6 -6
  59. package/templates/examples/todo/server/mongoose/base/src/routers/todo.ts.hbs +4 -4
  60. package/templates/examples/todo/server/prisma/base/src/routers/todo.ts.hbs +4 -4
  61. package/templates/frontend/react/tanstack-router/src/routes/__root.tsx.hbs +1 -1
  62. package/templates/frontend/react/tanstack-start/package.json.hbs +7 -7
  63. package/templates/frontend/react/tanstack-start/src/routes/__root.tsx.hbs +5 -5
  64. package/templates/frontend/react/tanstack-start/vite.config.ts.hbs +1 -1
  65. package/templates/api/orpc/server/next/src/app/rpc/[...all]/route.ts.hbs +0 -52
  66. package/templates/api/trpc/server/next/src/app/trpc/[trpc]/route.ts +0 -14
  67. package/templates/auth/better-auth/server/next/src/app/api/auth/[...all]/route.ts +0 -4
  68. package/templates/backend/server/next/next-env.d.ts +0 -5
  69. package/templates/backend/server/next/next.config.ts +0 -7
  70. package/templates/backend/server/next/package.json.hbs +0 -27
  71. package/templates/backend/server/next/src/app/route.ts +0 -5
  72. package/templates/backend/server/next/src/middleware.ts +0 -19
  73. package/templates/backend/server/next/tsconfig.json.hbs +0 -33
  74. package/templates/db/prisma/mongodb/src/db/index.ts.hbs +0 -5
  75. package/templates/examples/ai/server/next/src/app/ai/route.ts.hbs +0 -15
  76. /package/templates/api/orpc/server/{base/src/lib/orpc.ts.hbs → src/index.ts.hbs} +0 -0
  77. /package/templates/api/trpc/server/{base/src/lib/trpc.ts.hbs → src/index.ts.hbs} +0 -0
  78. /package/templates/auth/better-auth/server/db/drizzle/mysql/src/{db/schema/auth.ts → schema/auth.ts.hbs} +0 -0
  79. /package/templates/auth/better-auth/server/db/drizzle/postgres/src/{db/schema/auth.ts → schema/auth.ts.hbs} +0 -0
  80. /package/templates/auth/better-auth/server/db/drizzle/sqlite/src/{db/schema/auth.ts → schema/auth.ts.hbs} +0 -0
  81. /package/templates/auth/better-auth/server/db/mongoose/mongodb/src/{db/models/auth.model.ts → models/auth.model.ts.hbs} +0 -0
  82. /package/templates/auth/better-auth/server/db/prisma/mongodb/prisma/schema/{auth.prisma → auth.prisma.hbs} +0 -0
  83. /package/templates/auth/better-auth/server/db/prisma/mysql/prisma/schema/{auth.prisma → auth.prisma.hbs} +0 -0
  84. /package/templates/auth/better-auth/server/db/prisma/postgres/prisma/schema/{auth.prisma → auth.prisma.hbs} +0 -0
  85. /package/templates/auth/better-auth/server/db/prisma/sqlite/prisma/schema/{auth.prisma → auth.prisma.hbs} +0 -0
  86. /package/templates/auth/better-auth/web/nuxt/app/middleware/{auth.ts → auth.ts.hbs} +0 -0
  87. /package/templates/backend/server/{server-base → base}/_gitignore +0 -0
  88. /package/templates/db/mongoose/mongodb/src/{db/index.ts.hbs → index.ts.hbs} +0 -0
  89. /package/templates/examples/todo/server/drizzle/mysql/src/{db/schema → schema}/todo.ts +0 -0
  90. /package/templates/examples/todo/server/drizzle/postgres/src/{db/schema → schema}/todo.ts +0 -0
  91. /package/templates/examples/todo/server/drizzle/sqlite/src/{db/schema → schema}/todo.ts +0 -0
  92. /package/templates/examples/todo/server/mongoose/mongodb/src/{db/models/todo.model.ts → models/todo.model.ts.hbs} +0 -0
  93. /package/templates/examples/todo/server/prisma/mongodb/prisma/schema/{todo.prisma → todo.prisma.hbs} +0 -0
  94. /package/templates/examples/todo/server/prisma/mysql/prisma/schema/{todo.prisma → todo.prisma.hbs} +0 -0
  95. /package/templates/examples/todo/server/prisma/postgres/prisma/schema/{todo.prisma → todo.prisma.hbs} +0 -0
  96. /package/templates/examples/todo/server/prisma/sqlite/prisma/schema/{todo.prisma → todo.prisma.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,12 +66,13 @@ 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",
72
73
  "@planetscale/database": "^1.19.0",
73
74
  "@libsql/client": "^0.15.9",
75
+ libsql: "^0.5.22",
74
76
  "@neondatabase/serverless": "^1.0.1",
75
77
  pg: "^8.14.1",
76
78
  "@types/pg": "^8.11.11",
@@ -123,6 +125,7 @@ const dependencyVersionMap = {
123
125
  "@trpc/tanstack-react-query": "^11.5.0",
124
126
  "@trpc/server": "^11.5.0",
125
127
  "@trpc/client": "^11.5.0",
128
+ next: "^15.1.0",
126
129
  convex: "^1.27.0",
127
130
  "@convex-dev/react-query": "^0.0.0-alpha.8",
128
131
  "convex-svelte": "^0.0.11",
@@ -138,15 +141,17 @@ const dependencyVersionMap = {
138
141
  "@tanstack/solid-query": "^5.87.4",
139
142
  "@tanstack/solid-query-devtools": "^5.87.4",
140
143
  "@tanstack/solid-router-devtools": "^1.131.44",
141
- wrangler: "^4.23.0",
142
- "@cloudflare/vite-plugin": "^1.9.0",
144
+ wrangler: "^4.40.3",
145
+ "@cloudflare/vite-plugin": "^1.13.8",
143
146
  "@opennextjs/cloudflare": "^1.6.5",
144
147
  "nitro-cloudflare-dev": "^0.2.2",
145
148
  "@sveltejs/adapter-cloudflare": "^7.2.1",
146
149
  "@cloudflare/workers-types": "^4.20250822.0",
147
- alchemy: "^0.67.0",
150
+ alchemy: "^0.70.0",
148
151
  nitropack: "^2.12.4",
149
- dotenv: "^17.2.1",
152
+ dotenv: "^17.2.2",
153
+ tsdown: "^0.15.5",
154
+ zod: "^4.1.11",
150
155
  "@polar-sh/better-auth": "^1.1.3",
151
156
  "@polar-sh/sdk": "^0.34.16"
152
157
  };
@@ -195,9 +200,9 @@ const BackendSchema = z.enum([
195
200
  "hono",
196
201
  "express",
197
202
  "fastify",
198
- "next",
199
203
  "elysia",
200
204
  "convex",
205
+ "self",
201
206
  "none"
202
207
  ]).describe("Backend framework");
203
208
  const RuntimeSchema = z.enum([
@@ -343,6 +348,22 @@ function ensureSingleWebAndNative(frontends) {
343
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");
344
349
  if (native.length > 1) exitWithError("Cannot select multiple native frameworks. Choose only one of: native-nativewind, native-unistyles");
345
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
+ }
346
367
  function validateWorkersCompatibility(providedFlags, options, config) {
347
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.`);
348
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.`);
@@ -664,36 +685,39 @@ async function getAuthChoice(auth, hasDatabase, backend, frontend) {
664
685
 
665
686
  //#endregion
666
687
  //#region src/prompts/backend.ts
688
+ const FULLSTACK_FRONTENDS = [
689
+ "next",
690
+ "nuxt",
691
+ "svelte",
692
+ "tanstack-start"
693
+ ];
667
694
  async function getBackendFrameworkChoice(backendFramework, frontends) {
668
695
  if (backendFramework !== void 0) return backendFramework;
669
696
  const hasIncompatibleFrontend = frontends?.some((f) => f === "solid");
670
- const backendOptions = [
671
- {
672
- value: "hono",
673
- label: "Hono",
674
- hint: "Lightweight, ultrafast web framework"
675
- },
676
- {
677
- value: "next",
678
- label: "Next.js",
679
- hint: "separate api routes only backend"
680
- },
681
- {
682
- value: "express",
683
- label: "Express",
684
- hint: "Fast, unopinionated, minimalist web framework for Node.js"
685
- },
686
- {
687
- value: "fastify",
688
- label: "Fastify",
689
- hint: "Fast, low-overhead web framework for Node.js"
690
- },
691
- {
692
- value: "elysia",
693
- label: "Elysia",
694
- hint: "Ergonomic web framework for building backend servers"
695
- }
696
- ];
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
+ });
697
721
  if (!hasIncompatibleFrontend) backendOptions.push({
698
722
  value: "convex",
699
723
  label: "Convex",
@@ -707,7 +731,7 @@ async function getBackendFrameworkChoice(backendFramework, frontends) {
707
731
  const response = await select({
708
732
  message: "Select backend",
709
733
  options: backendOptions,
710
- initialValue: DEFAULT_CONFIG.backend
734
+ initialValue: hasFullstackFrontend ? "self" : DEFAULT_CONFIG.backend
711
735
  });
712
736
  if (isCancel(response)) return exitCancelled("Operation cancelled");
713
737
  return response;
@@ -1086,9 +1110,8 @@ async function getPaymentsChoice(payments, auth, backend, frontends) {
1086
1110
  //#endregion
1087
1111
  //#region src/prompts/runtime.ts
1088
1112
  async function getRuntimeChoice(runtime, backend) {
1089
- if (backend === "convex" || backend === "none") return "none";
1113
+ if (backend === "convex" || backend === "none" || backend === "self") return "none";
1090
1114
  if (runtime !== void 0) return runtime;
1091
- if (backend === "next") return "node";
1092
1115
  const runtimeOptions = [{
1093
1116
  value: "bun",
1094
1117
  label: "Bun",
@@ -1375,7 +1398,7 @@ const getLatestCLIVersion = () => {
1375
1398
  */
1376
1399
  function isTelemetryEnabled() {
1377
1400
  const BTS_TELEMETRY_DISABLED = process.env.BTS_TELEMETRY_DISABLED;
1378
- const BTS_TELEMETRY = "1";
1401
+ const BTS_TELEMETRY = "0";
1379
1402
  if (BTS_TELEMETRY_DISABLED !== void 0) return BTS_TELEMETRY_DISABLED !== "1";
1380
1403
  if (BTS_TELEMETRY !== void 0) return BTS_TELEMETRY === "1";
1381
1404
  return true;
@@ -1383,8 +1406,8 @@ function isTelemetryEnabled() {
1383
1406
 
1384
1407
  //#endregion
1385
1408
  //#region src/utils/analytics.ts
1386
- const POSTHOG_API_KEY = "phc_8ZUxEwwfKMajJLvxz1daGd931dYbQrwKNficBmsdIrs";
1387
- const POSTHOG_HOST = "https://us.i.posthog.com";
1409
+ const POSTHOG_API_KEY = "random";
1410
+ const POSTHOG_HOST = "random";
1388
1411
  function generateSessionId() {
1389
1412
  const rand = Math.random().toString(36).slice(2);
1390
1413
  return `cli_${Date.now().toString(36)}${rand}`;
@@ -1765,8 +1788,8 @@ function validateBackendConstraints(config, providedFlags, options) {
1765
1788
  ].includes(f));
1766
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.`);
1767
1790
  }
1768
- if (providedFlags.has("backend") && backend && backend !== "convex" && backend !== "none") {
1769
- 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.");
1770
1793
  }
1771
1794
  if (backend === "convex" && providedFlags.has("frontend") && options.frontend) {
1772
1795
  const incompatibleFrontends = options.frontend.filter((f) => f === "solid");
@@ -1796,6 +1819,7 @@ function validateFullConfig(config, providedFlags, options) {
1796
1819
  validateFrontendConstraints(config, providedFlags);
1797
1820
  validateApiConstraints(config, options);
1798
1821
  validateServerDeployRequiresBackend(config.serverDeploy, config.backend);
1822
+ validateSelfBackendCompatibility(providedFlags, options, config);
1799
1823
  validateWorkersCompatibility(providedFlags, options, config);
1800
1824
  if (config.runtime === "workers" && config.serverDeploy === "none") exitWithError("Cloudflare Workers runtime requires a server deployment. Please choose 'wrangler' or 'alchemy' for --server-deploy.");
1801
1825
  if (config.addons && config.addons.length > 0) {
@@ -1849,6 +1873,7 @@ const CORE_STACK_FLAGS = new Set([
1849
1873
  "examples",
1850
1874
  "auth",
1851
1875
  "dbSetup",
1876
+ "payments",
1852
1877
  "api",
1853
1878
  "webDeploy",
1854
1879
  "serverDeploy"
@@ -1992,15 +2017,17 @@ const addPackageDependency = async (opts) => {
1992
2017
  if (!pkgJson.dependencies) pkgJson.dependencies = {};
1993
2018
  if (!pkgJson.devDependencies) pkgJson.devDependencies = {};
1994
2019
  for (const pkgName of dependencies) {
1995
- const version = customDependencies[pkgName] || dependencyVersionMap[pkgName];
2020
+ const version = dependencyVersionMap[pkgName];
1996
2021
  if (version) pkgJson.dependencies[pkgName] = version;
1997
2022
  else console.warn(`Warning: Dependency ${pkgName} not found in version map.`);
1998
2023
  }
1999
2024
  for (const pkgName of devDependencies) {
2000
- const version = customDevDependencies[pkgName] || dependencyVersionMap[pkgName];
2025
+ const version = dependencyVersionMap[pkgName];
2001
2026
  if (version) pkgJson.devDependencies[pkgName] = version;
2002
2027
  else console.warn(`Warning: Dev dependency ${pkgName} not found in version map.`);
2003
2028
  }
2029
+ for (const [pkgName, version] of Object.entries(customDependencies)) pkgJson.dependencies[pkgName] = version;
2030
+ for (const [pkgName, version] of Object.entries(customDevDependencies)) pkgJson.devDependencies[pkgName] = version;
2004
2031
  await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
2005
2032
  };
2006
2033
 
@@ -2655,8 +2682,12 @@ async function processTemplate(srcPath, destPath, context) {
2655
2682
  }
2656
2683
  handlebars.registerHelper("eq", (a, b) => a === b);
2657
2684
  handlebars.registerHelper("ne", (a, b) => a !== b);
2658
- handlebars.registerHelper("and", (a, b) => a && b);
2659
- 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
+ });
2660
2691
  handlebars.registerHelper("includes", (array, value) => Array.isArray(array) && array.includes(value));
2661
2692
 
2662
2693
  //#endregion
@@ -2758,35 +2789,52 @@ async function setupFrontendTemplates(projectDir, context) {
2758
2789
  }
2759
2790
  }
2760
2791
  }
2761
- async function setupBackendFramework(projectDir, context) {
2762
- if (context.backend === "none") return;
2792
+ async function setupApiPackage(projectDir, context) {
2793
+ if (context.api === "none") return;
2794
+ const apiPackageDir = path.join(projectDir, "packages/api");
2795
+ await fs.ensureDir(apiPackageDir);
2796
+ const apiServerDir = path.join(PKG_ROOT, `templates/api/${context.api}/server`);
2797
+ if (await fs.pathExists(apiServerDir)) await processAndCopyFiles("**/*", apiServerDir, apiPackageDir, context);
2798
+ }
2799
+ async function setupDbPackage(projectDir, context) {
2800
+ if (context.database === "none" || context.orm === "none") return;
2801
+ const dbPackageDir = path.join(projectDir, "packages/db");
2802
+ await fs.ensureDir(dbPackageDir);
2803
+ const dbBaseDir = path.join(PKG_ROOT, "templates/db/base");
2804
+ if (await fs.pathExists(dbBaseDir)) await processAndCopyFiles("**/*", dbBaseDir, dbPackageDir, context);
2805
+ const dbOrmSrcDir = path.join(PKG_ROOT, `templates/db/${context.orm}/${context.database}`);
2806
+ if (await fs.pathExists(dbOrmSrcDir)) await processAndCopyFiles("**/*", dbOrmSrcDir, dbPackageDir, context);
2807
+ }
2808
+ async function setupConvexBackend(projectDir, context) {
2809
+ const serverAppDir = path.join(projectDir, "apps/server");
2810
+ if (await fs.pathExists(serverAppDir)) await fs.remove(serverAppDir);
2811
+ const convexBackendDestDir = path.join(projectDir, "packages/backend");
2812
+ const convexSrcDir = path.join(PKG_ROOT, "templates/backend/convex/packages/backend");
2813
+ await fs.ensureDir(convexBackendDestDir);
2814
+ if (await fs.pathExists(convexSrcDir)) await processAndCopyFiles("**/*", convexSrcDir, convexBackendDestDir, context);
2815
+ }
2816
+ async function setupServerApp(projectDir, context) {
2763
2817
  const serverAppDir = path.join(projectDir, "apps/server");
2764
- if (context.backend === "convex") {
2765
- if (await fs.pathExists(serverAppDir)) await fs.remove(serverAppDir);
2766
- const convexBackendDestDir = path.join(projectDir, "packages/backend");
2767
- const convexSrcDir = path.join(PKG_ROOT, "templates/backend/convex/packages/backend");
2768
- await fs.ensureDir(convexBackendDestDir);
2769
- if (await fs.pathExists(convexSrcDir)) await processAndCopyFiles("**/*", convexSrcDir, convexBackendDestDir, context);
2770
- return;
2771
- }
2772
2818
  await fs.ensureDir(serverAppDir);
2773
- const serverBaseDir = path.join(PKG_ROOT, "templates/backend/server/server-base");
2819
+ const serverBaseDir = path.join(PKG_ROOT, "templates/backend/server/base");
2774
2820
  if (await fs.pathExists(serverBaseDir)) await processAndCopyFiles("**/*", serverBaseDir, serverAppDir, context);
2775
2821
  const frameworkSrcDir = path.join(PKG_ROOT, `templates/backend/server/${context.backend}`);
2776
2822
  if (await fs.pathExists(frameworkSrcDir)) await processAndCopyFiles("**/*", frameworkSrcDir, serverAppDir, context, true);
2777
- if (context.api !== "none") {
2778
- const apiServerBaseDir = path.join(PKG_ROOT, `templates/api/${context.api}/server/base`);
2779
- if (await fs.pathExists(apiServerBaseDir)) await processAndCopyFiles("**/*", apiServerBaseDir, serverAppDir, context, true);
2780
- const apiServerFrameworkDir = path.join(PKG_ROOT, `templates/api/${context.api}/server/${context.backend}`);
2781
- if (await fs.pathExists(apiServerFrameworkDir)) await processAndCopyFiles("**/*", apiServerFrameworkDir, serverAppDir, context, true);
2782
- }
2783
2823
  }
2784
- async function setupDbOrmTemplates(projectDir, context) {
2785
- if (context.backend === "convex" || context.orm === "none" || context.database === "none") return;
2786
- const serverAppDir = path.join(projectDir, "apps/server");
2787
- await fs.ensureDir(serverAppDir);
2788
- const dbOrmSrcDir = path.join(PKG_ROOT, `templates/db/${context.orm}/${context.database}`);
2789
- if (await fs.pathExists(dbOrmSrcDir)) await processAndCopyFiles("**/*", dbOrmSrcDir, serverAppDir, context);
2824
+ async function setupBackendFramework(projectDir, context) {
2825
+ if (context.backend === "none") return;
2826
+ if (context.backend === "convex") {
2827
+ await setupConvexBackend(projectDir, context);
2828
+ return;
2829
+ }
2830
+ if (context.backend === "self") {
2831
+ await setupApiPackage(projectDir, context);
2832
+ await setupDbPackage(projectDir, context);
2833
+ return;
2834
+ }
2835
+ await setupServerApp(projectDir, context);
2836
+ await setupApiPackage(projectDir, context);
2837
+ await setupDbPackage(projectDir, context);
2790
2838
  }
2791
2839
  async function setupAuthTemplate(projectDir, context) {
2792
2840
  if (!context.auth || context.auth === "none") return;
@@ -2866,21 +2914,21 @@ async function setupAuthTemplate(projectDir, context) {
2866
2914
  }
2867
2915
  return;
2868
2916
  }
2869
- if (serverAppDirExists && context.backend !== "convex") {
2917
+ if ((serverAppDirExists || context.backend === "self") && context.backend !== "convex") {
2918
+ const authPackageDir = path.join(projectDir, "packages/auth");
2919
+ await fs.ensureDir(authPackageDir);
2870
2920
  const authServerBaseSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/base`);
2871
- if (await fs.pathExists(authServerBaseSrc)) await processAndCopyFiles("**/*", authServerBaseSrc, serverAppDir, context);
2872
- if (context.backend === "next") {
2873
- const authServerNextSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/next`);
2874
- if (await fs.pathExists(authServerNextSrc)) await processAndCopyFiles("**/*", authServerNextSrc, serverAppDir, context);
2875
- }
2921
+ if (await fs.pathExists(authServerBaseSrc)) await processAndCopyFiles("**/*", authServerBaseSrc, authPackageDir, context);
2876
2922
  if (context.orm !== "none" && context.database !== "none") {
2923
+ const dbPackageDir = path.join(projectDir, "packages/db");
2924
+ await fs.ensureDir(dbPackageDir);
2877
2925
  const orm = context.orm;
2878
2926
  const db = context.database;
2879
2927
  let authDbSrc = "";
2880
2928
  if (orm === "drizzle") authDbSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/db/drizzle/${db}`);
2881
2929
  else if (orm === "prisma") authDbSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/db/prisma/${db}`);
2882
2930
  else if (orm === "mongoose") authDbSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/db/mongoose/${db}`);
2883
- if (authDbSrc && await fs.pathExists(authDbSrc)) await processAndCopyFiles("**/*", authDbSrc, serverAppDir, context);
2931
+ if (authDbSrc && await fs.pathExists(authDbSrc)) await processAndCopyFiles("**/*", authDbSrc, dbPackageDir, context);
2884
2932
  }
2885
2933
  }
2886
2934
  if ((hasReactWeb || hasNuxtWeb || hasSvelteWeb || hasSolidWeb) && webAppDirExists) {
@@ -2926,9 +2974,11 @@ async function setupPaymentsTemplate(projectDir, context) {
2926
2974
  const webAppDir = path.join(projectDir, "apps/web");
2927
2975
  const serverAppDirExists = await fs.pathExists(serverAppDir);
2928
2976
  const webAppDirExists = await fs.pathExists(webAppDir);
2929
- if (serverAppDirExists && context.backend !== "convex") {
2977
+ if ((serverAppDirExists || context.backend === "self") && context.backend !== "convex") {
2978
+ const authPackageDir = path.join(projectDir, "packages/auth");
2979
+ await fs.ensureDir(authPackageDir);
2930
2980
  const paymentsServerSrc = path.join(PKG_ROOT, `templates/payments/${context.payments}/server/base`);
2931
- if (await fs.pathExists(paymentsServerSrc)) await processAndCopyFiles("**/*", paymentsServerSrc, serverAppDir, context);
2981
+ if (await fs.pathExists(paymentsServerSrc)) await processAndCopyFiles("**/*", paymentsServerSrc, authPackageDir, context);
2932
2982
  }
2933
2983
  const hasReactWeb = context.frontend.some((f) => [
2934
2984
  "tanstack-router",
@@ -3004,17 +3054,19 @@ async function setupExamplesTemplate(projectDir, context) {
3004
3054
  for (const example of context.examples) {
3005
3055
  if (example === "none") continue;
3006
3056
  const exampleBaseDir = path.join(PKG_ROOT, `templates/examples/${example}`);
3007
- if (serverAppDirExists && context.backend !== "convex" && context.backend !== "none") {
3057
+ if ((serverAppDirExists || context.backend === "self") && context.backend !== "convex" && context.backend !== "none") {
3008
3058
  const exampleServerSrc = path.join(exampleBaseDir, "server");
3009
- if (example === "ai" && context.backend === "next") {
3010
- const aiNextServerSrc = path.join(exampleServerSrc, "next");
3011
- if (await fs.pathExists(aiNextServerSrc)) await processAndCopyFiles("**/*", aiNextServerSrc, serverAppDir, context, false);
3059
+ if (context.api !== "none") {
3060
+ const apiPackageDir = path.join(projectDir, "packages/api");
3061
+ await fs.ensureDir(apiPackageDir);
3062
+ const exampleOrmBaseSrc = path.join(exampleServerSrc, context.orm, "base");
3063
+ if (await fs.pathExists(exampleOrmBaseSrc)) await processAndCopyFiles("**/*", exampleOrmBaseSrc, apiPackageDir, context, false);
3012
3064
  }
3013
3065
  if (context.orm !== "none" && context.database !== "none") {
3014
- const exampleOrmBaseSrc = path.join(exampleServerSrc, context.orm, "base");
3015
- if (await fs.pathExists(exampleOrmBaseSrc)) await processAndCopyFiles("**/*", exampleOrmBaseSrc, serverAppDir, context, false);
3066
+ const dbPackageDir = path.join(projectDir, "packages/db");
3067
+ await fs.ensureDir(dbPackageDir);
3016
3068
  const exampleDbSchemaSrc = path.join(exampleServerSrc, context.orm, context.database);
3017
- if (await fs.pathExists(exampleDbSchemaSrc)) await processAndCopyFiles("**/*", exampleDbSchemaSrc, serverAppDir, context, false);
3069
+ if (await fs.pathExists(exampleDbSchemaSrc)) await processAndCopyFiles("**/*", exampleDbSchemaSrc, dbPackageDir, context, false);
3018
3070
  }
3019
3071
  }
3020
3072
  if (webAppDirExists) {
@@ -3081,9 +3133,9 @@ async function handleExtras(projectDir, context) {
3081
3133
  }
3082
3134
  async function setupDockerComposeTemplates(projectDir, context) {
3083
3135
  if (context.dbSetup !== "docker" || context.database === "none") return;
3084
- const serverAppDir = path.join(projectDir, "apps/server");
3136
+ const dbPackageDir = path.join(projectDir, "packages/db");
3085
3137
  const dockerSrcDir = path.join(PKG_ROOT, `templates/db-setup/docker-compose/${context.database}`);
3086
- if (await fs.pathExists(dockerSrcDir)) await processAndCopyFiles("**/*", dockerSrcDir, serverAppDir, context);
3138
+ if (await fs.pathExists(dockerSrcDir)) await processAndCopyFiles("**/*", dockerSrcDir, dbPackageDir, context);
3087
3139
  }
3088
3140
  async function setupDeploymentTemplates(projectDir, context) {
3089
3141
  if (context.webDeploy === "alchemy" || context.serverDeploy === "alchemy") if (context.webDeploy === "alchemy" && context.serverDeploy === "alchemy") {
@@ -3504,8 +3556,8 @@ async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, opti
3504
3556
  await addPackageDependency({
3505
3557
  devDependencies: [
3506
3558
  "alchemy",
3507
- "nitropack",
3508
- "dotenv"
3559
+ "dotenv",
3560
+ "@cloudflare/vite-plugin"
3509
3561
  ],
3510
3562
  projectDir: webAppDir
3511
3563
  });
@@ -3533,17 +3585,6 @@ async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, opti
3533
3585
  defaultImport: "alchemy"
3534
3586
  });
3535
3587
  else alchemyImport.setModuleSpecifier("alchemy/cloudflare/tanstack-start");
3536
- const reactImport = sourceFile.getImportDeclaration("@vitejs/plugin-react");
3537
- let reactPluginIdentifier = "viteReact";
3538
- if (!reactImport) sourceFile.addImportDeclaration({
3539
- moduleSpecifier: "@vitejs/plugin-react",
3540
- defaultImport: "viteReact"
3541
- });
3542
- else {
3543
- const defaultImport = reactImport.getDefaultImport();
3544
- if (defaultImport) reactPluginIdentifier = defaultImport.getText();
3545
- else reactImport.setDefaultImport("viteReact");
3546
- }
3547
3588
  const exportAssignment = sourceFile.getExportAssignment((d) => !d.isExportEquals());
3548
3589
  if (!exportAssignment) return;
3549
3590
  const defineConfigCall = exportAssignment.getExpression();
@@ -3551,47 +3592,11 @@ async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, opti
3551
3592
  let configObject = defineConfigCall.getArguments()[0];
3552
3593
  if (!configObject) configObject = defineConfigCall.addArgument("{}");
3553
3594
  if (Node.isObjectLiteralExpression(configObject)) {
3554
- if (!configObject.getProperty("build")) configObject.addPropertyAssignment({
3555
- name: "build",
3556
- initializer: `{
3557
- target: "esnext",
3558
- rollupOptions: {
3559
- external: ["node:async_hooks", "cloudflare:workers"],
3560
- },
3561
- }`
3562
- });
3563
3595
  const pluginsProperty = configObject.getProperty("plugins");
3564
3596
  if (pluginsProperty && Node.isPropertyAssignment(pluginsProperty)) {
3565
3597
  const initializer = pluginsProperty.getInitializer();
3566
3598
  if (Node.isArrayLiteralExpression(initializer)) {
3567
- if (!initializer.getElements().some((el) => el.getText().includes("alchemy"))) initializer.addElement("alchemy()");
3568
- const tanstackElements = initializer.getElements().filter((el) => el.getText().includes("tanstackStart"));
3569
- let needsReactPlugin = false;
3570
- tanstackElements.forEach((element) => {
3571
- if (Node.isCallExpression(element)) {
3572
- const args = element.getArguments();
3573
- if (args.length === 0) {
3574
- element.addArgument(`{
3575
- target: "cloudflare-module",
3576
- customViteReactPlugin: true,
3577
- }`);
3578
- needsReactPlugin = true;
3579
- } else if (args.length === 1 && Node.isObjectLiteralExpression(args[0])) {
3580
- const configObj = args[0];
3581
- if (!configObj.getProperty("target")) configObj.addPropertyAssignment({
3582
- name: "target",
3583
- initializer: "\"cloudflare-module\""
3584
- });
3585
- if (!!!configObj.getProperty("customViteReactPlugin")) configObj.addPropertyAssignment({
3586
- name: "customViteReactPlugin",
3587
- initializer: "true"
3588
- });
3589
- needsReactPlugin = true;
3590
- }
3591
- }
3592
- });
3593
- const hasReactPlugin = initializer.getElements().some((el) => Node.isCallExpression(el) && el.getExpression().getText() === reactPluginIdentifier);
3594
- if (needsReactPlugin && !hasReactPlugin) initializer.addElement(`${reactPluginIdentifier}()`);
3599
+ if (!initializer.getElements().some((el) => el.getText().includes("alchemy("))) initializer.addElement("alchemy()");
3595
3600
  }
3596
3601
  } else configObject.addPropertyAssignment({
3597
3602
  name: "plugins",
@@ -3602,16 +3607,6 @@ async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, opti
3602
3607
  } catch (error) {
3603
3608
  console.warn("Failed to update vite.config.ts:", error);
3604
3609
  }
3605
- const nitroConfigPath = path.join(webAppDir, "nitro.config.ts");
3606
- await fs.writeFile(nitroConfigPath, `import { defineNitroConfig } from "nitropack/config";
3607
-
3608
- export default defineNitroConfig({
3609
- preset: "cloudflare-module",
3610
- cloudflare: {
3611
- nodeCompat: true,
3612
- },
3613
- });
3614
- `, "utf-8");
3615
3610
  }
3616
3611
 
3617
3612
  //#endregion
@@ -3778,7 +3773,7 @@ async function setupTanstackStartWorkersDeploy(projectDir, packageManager) {
3778
3773
  const webAppDir = path.join(projectDir, "apps/web");
3779
3774
  if (!await fs.pathExists(webAppDir)) return;
3780
3775
  await addPackageDependency({
3781
- devDependencies: ["wrangler"],
3776
+ devDependencies: ["wrangler", "@cloudflare/vite-plugin"],
3782
3777
  projectDir: webAppDir
3783
3778
  });
3784
3779
  const pkgPath = path.join(webAppDir, "package.json");
@@ -3795,6 +3790,12 @@ async function setupTanstackStartWorkersDeploy(projectDir, packageManager) {
3795
3790
  if (!await fs.pathExists(viteConfigPath)) return;
3796
3791
  const sourceFile = tsProject.addSourceFileAtPathIfExists(viteConfigPath);
3797
3792
  if (!sourceFile) return;
3793
+ const cfImport = sourceFile.getImportDeclaration("@cloudflare/vite-plugin");
3794
+ if (!cfImport) sourceFile.addImportDeclaration({
3795
+ moduleSpecifier: "@cloudflare/vite-plugin",
3796
+ namedImports: [{ name: "cloudflare" }]
3797
+ });
3798
+ else if (!cfImport.getNamedImports().some((ni) => ni.getName() === "cloudflare")) cfImport.addNamedImport({ name: "cloudflare" });
3798
3799
  const reactImport = sourceFile.getImportDeclaration("@vitejs/plugin-react");
3799
3800
  let reactPluginIdentifier = "viteReact";
3800
3801
  if (!reactImport) sourceFile.addImportDeclaration({
@@ -3814,10 +3815,7 @@ async function setupTanstackStartWorkersDeploy(projectDir, packageManager) {
3814
3815
  const configObj = defineCall.getArguments()[0];
3815
3816
  if (!configObj) return;
3816
3817
  const pluginsArray = ensureArrayProperty(configObj, "plugins");
3817
- const tanstackPluginIndex = pluginsArray.getElements().findIndex((el) => el.getText().includes("tanstackStart("));
3818
- const tanstackPluginText = "tanstackStart({ target: \"cloudflare-module\", customViteReactPlugin: true })";
3819
- if (tanstackPluginIndex === -1) pluginsArray.addElement(tanstackPluginText);
3820
- else pluginsArray.getElements()[tanstackPluginIndex].replaceWithText(tanstackPluginText);
3818
+ if (!pluginsArray.getElements().some((el) => el.getText().includes("cloudflare("))) pluginsArray.insertElement(0, "cloudflare({ viteEnvironment: { name: 'ssr' } })");
3821
3819
  if (!pluginsArray.getElements().some((el) => Node.isCallExpression(el) && el.getExpression().getText() === reactPluginIdentifier)) {
3822
3820
  const nextIndex = pluginsArray.getElements().findIndex((el) => el.getText().includes("tanstackStart(")) + 1;
3823
3821
  if (nextIndex > 0) pluginsArray.insertElement(nextIndex, `${reactPluginIdentifier}()`);
@@ -3955,6 +3953,110 @@ async function addDeploymentToProject(input) {
3955
3953
  }
3956
3954
  }
3957
3955
 
3956
+ //#endregion
3957
+ //#region src/utils/setup-catalogs.ts
3958
+ async function setupCatalogs(projectDir, options) {
3959
+ if (options.packageManager === "npm") return;
3960
+ const packagePaths = [
3961
+ "apps/server",
3962
+ "apps/web",
3963
+ "packages/api",
3964
+ "packages/db",
3965
+ "packages/auth",
3966
+ "packages/backend"
3967
+ ];
3968
+ const packagesInfo = [];
3969
+ for (const pkgPath of packagePaths) {
3970
+ const fullPath = path.join(projectDir, pkgPath);
3971
+ const pkgJsonPath = path.join(fullPath, "package.json");
3972
+ if (await fs.pathExists(pkgJsonPath)) {
3973
+ const pkgJson = await fs.readJson(pkgJsonPath);
3974
+ packagesInfo.push({
3975
+ path: fullPath,
3976
+ dependencies: pkgJson.dependencies || {},
3977
+ devDependencies: pkgJson.devDependencies || {}
3978
+ });
3979
+ }
3980
+ }
3981
+ const catalog = findDuplicateDependencies(packagesInfo, options.projectName);
3982
+ if (Object.keys(catalog).length === 0) return;
3983
+ if (options.packageManager === "bun") await setupBunCatalogs(projectDir, catalog);
3984
+ else if (options.packageManager === "pnpm") await setupPnpmCatalogs(projectDir, catalog);
3985
+ await updatePackageJsonsWithCatalogs(packagesInfo, catalog);
3986
+ }
3987
+ function findDuplicateDependencies(packagesInfo, projectName) {
3988
+ const depCount = /* @__PURE__ */ new Map();
3989
+ const projectScope = `@${projectName}/`;
3990
+ for (const pkg of packagesInfo) {
3991
+ const allDeps = {
3992
+ ...pkg.dependencies,
3993
+ ...pkg.devDependencies
3994
+ };
3995
+ for (const [depName, version] of Object.entries(allDeps)) {
3996
+ if (depName.startsWith(projectScope)) continue;
3997
+ if (version.startsWith("workspace:")) continue;
3998
+ const existing = depCount.get(depName);
3999
+ if (existing) existing.packages.push(pkg.path);
4000
+ else depCount.set(depName, {
4001
+ version,
4002
+ packages: [pkg.path]
4003
+ });
4004
+ }
4005
+ }
4006
+ const catalog = {};
4007
+ for (const [depName, info] of depCount.entries()) if (info.packages.length > 1) catalog[depName] = info.version;
4008
+ return catalog;
4009
+ }
4010
+ async function setupBunCatalogs(projectDir, catalog) {
4011
+ const rootPkgJsonPath = path.join(projectDir, "package.json");
4012
+ const rootPkgJson = await fs.readJson(rootPkgJsonPath);
4013
+ if (!rootPkgJson.workspaces) rootPkgJson.workspaces = {};
4014
+ if (Array.isArray(rootPkgJson.workspaces)) rootPkgJson.workspaces = {
4015
+ packages: rootPkgJson.workspaces,
4016
+ catalog
4017
+ };
4018
+ else if (typeof rootPkgJson.workspaces === "object") {
4019
+ if (!rootPkgJson.workspaces.catalog) rootPkgJson.workspaces.catalog = {};
4020
+ rootPkgJson.workspaces.catalog = {
4021
+ ...rootPkgJson.workspaces.catalog,
4022
+ ...catalog
4023
+ };
4024
+ }
4025
+ await fs.writeJson(rootPkgJsonPath, rootPkgJson, { spaces: 2 });
4026
+ }
4027
+ async function setupPnpmCatalogs(projectDir, catalog) {
4028
+ const workspaceYamlPath = path.join(projectDir, "pnpm-workspace.yaml");
4029
+ if (!await fs.pathExists(workspaceYamlPath)) return;
4030
+ const workspaceContent = await fs.readFile(workspaceYamlPath, "utf-8");
4031
+ const workspaceYaml = yaml.parse(workspaceContent);
4032
+ if (!workspaceYaml.catalog) workspaceYaml.catalog = {};
4033
+ workspaceYaml.catalog = {
4034
+ ...workspaceYaml.catalog,
4035
+ ...catalog
4036
+ };
4037
+ await fs.writeFile(workspaceYamlPath, yaml.stringify(workspaceYaml));
4038
+ }
4039
+ async function updatePackageJsonsWithCatalogs(packagesInfo, catalog) {
4040
+ for (const pkg of packagesInfo) {
4041
+ const pkgJsonPath = path.join(pkg.path, "package.json");
4042
+ const pkgJson = await fs.readJson(pkgJsonPath);
4043
+ let updated = false;
4044
+ if (pkgJson.dependencies) {
4045
+ for (const depName of Object.keys(pkgJson.dependencies)) if (catalog[depName]) {
4046
+ pkgJson.dependencies[depName] = "catalog:";
4047
+ updated = true;
4048
+ }
4049
+ }
4050
+ if (pkgJson.devDependencies) {
4051
+ for (const depName of Object.keys(pkgJson.devDependencies)) if (catalog[depName]) {
4052
+ pkgJson.devDependencies[depName] = "catalog:";
4053
+ updated = true;
4054
+ }
4055
+ }
4056
+ if (updated) await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
4057
+ }
4058
+ }
4059
+
3958
4060
  //#endregion
3959
4061
  //#region src/helpers/addons/examples-setup.ts
3960
4062
  async function setupExamples(config) {
@@ -3963,10 +4065,10 @@ async function setupExamples(config) {
3963
4065
  if (examples.includes("ai")) {
3964
4066
  const webClientDir = path.join(projectDir, "apps/web");
3965
4067
  const nativeClientDir = path.join(projectDir, "apps/native");
3966
- const serverDir = path.join(projectDir, "apps/server");
4068
+ const apiDir = path.join(projectDir, "packages/api");
3967
4069
  const webClientDirExists = await fs.pathExists(webClientDir);
3968
4070
  const nativeClientDirExists = await fs.pathExists(nativeClientDir);
3969
- const serverDirExists = await fs.pathExists(serverDir);
4071
+ const apiDirExists = await fs.pathExists(apiDir);
3970
4072
  const hasNuxt = frontend.includes("nuxt");
3971
4073
  const hasSvelte = frontend.includes("svelte");
3972
4074
  const hasReactWeb = frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("next") || frontend.includes("tanstack-start");
@@ -3987,9 +4089,9 @@ async function setupExamples(config) {
3987
4089
  dependencies: ["ai", "@ai-sdk/react"],
3988
4090
  projectDir: nativeClientDir
3989
4091
  });
3990
- if (serverDirExists && backend !== "none") await addPackageDependency({
4092
+ if (apiDirExists && backend !== "none") await addPackageDependency({
3991
4093
  dependencies: ["ai", "@ai-sdk/google"],
3992
- projectDir: serverDir
4094
+ projectDir: apiDir
3993
4095
  });
3994
4096
  }
3995
4097
  }
@@ -4105,32 +4207,57 @@ function getConvexDependencies(frontend) {
4105
4207
  return deps;
4106
4208
  }
4107
4209
  async function setupApi(config) {
4108
- const { api, projectName, frontend, backend, packageManager, projectDir } = config;
4210
+ const { api, projectName, frontend, backend, packageManager, projectDir, auth } = config;
4109
4211
  const isConvex = backend === "convex";
4110
4212
  const webDir = path.join(projectDir, "apps/web");
4111
4213
  const nativeDir = path.join(projectDir, "apps/native");
4112
4214
  const serverDir = path.join(projectDir, "apps/server");
4113
4215
  const webDirExists = await fs.pathExists(webDir);
4114
4216
  const nativeDirExists = await fs.pathExists(nativeDir);
4115
- const serverDirExists = await fs.pathExists(serverDir);
4217
+ await fs.pathExists(serverDir);
4116
4218
  const frontendType = getFrontendType(frontend);
4117
4219
  if (!isConvex && api !== "none") {
4118
4220
  const apiDeps = getApiDependencies(api, frontendType);
4119
- if (serverDirExists && apiDeps.server) {
4221
+ const apiPackageDir = path.join(projectDir, "packages/api");
4222
+ if (apiDeps.server) {
4120
4223
  await addPackageDependency({
4121
4224
  dependencies: apiDeps.server.dependencies,
4122
- projectDir: serverDir
4225
+ projectDir: apiPackageDir
4226
+ });
4227
+ const frameworkDeps = [];
4228
+ if (backend === "hono") frameworkDeps.push("hono");
4229
+ else if (backend === "elysia") frameworkDeps.push("elysia");
4230
+ else if (backend === "express") frameworkDeps.push("express", "@types/express");
4231
+ else if (backend === "fastify") frameworkDeps.push("fastify");
4232
+ else if (backend === "self") {
4233
+ if (frontend.includes("next")) frameworkDeps.push("next");
4234
+ }
4235
+ if (frameworkDeps.length > 0) await addPackageDependency({
4236
+ dependencies: frameworkDeps,
4237
+ projectDir: apiPackageDir
4123
4238
  });
4124
4239
  if (api === "trpc") {
4125
4240
  if (backend === "hono") await addPackageDependency({
4126
4241
  dependencies: ["@hono/trpc-server"],
4127
- projectDir: serverDir
4242
+ projectDir: apiPackageDir
4128
4243
  });
4129
4244
  else if (backend === "elysia") await addPackageDependency({
4130
4245
  dependencies: ["@elysiajs/trpc"],
4131
- projectDir: serverDir
4246
+ projectDir: apiPackageDir
4247
+ });
4248
+ else if (backend === "express") await addPackageDependency({
4249
+ dependencies: ["@trpc/server"],
4250
+ projectDir: apiPackageDir
4251
+ });
4252
+ else if (backend === "fastify") await addPackageDependency({
4253
+ dependencies: ["@trpc/server"],
4254
+ projectDir: apiPackageDir
4132
4255
  });
4133
4256
  }
4257
+ if (auth === "better-auth") await addPackageDependency({
4258
+ dependencies: ["better-auth"],
4259
+ projectDir: apiPackageDir
4260
+ });
4134
4261
  }
4135
4262
  if (webDirExists && apiDeps.web) await addPackageDependency({
4136
4263
  dependencies: apiDeps.web.dependencies,
@@ -4174,7 +4301,7 @@ async function setupApi(config) {
4174
4301
  //#endregion
4175
4302
  //#region src/helpers/core/backend-setup.ts
4176
4303
  async function setupBackendDependencies(config) {
4177
- const { backend, runtime, api, projectDir } = config;
4304
+ const { backend, runtime, api, auth, examples, projectDir } = config;
4178
4305
  if (backend === "convex") return;
4179
4306
  const framework = backend;
4180
4307
  const serverDir = path.join(projectDir, "apps/server");
@@ -4202,6 +4329,13 @@ async function setupBackendDependencies(config) {
4202
4329
  dependencies.push("fastify", "@fastify/cors");
4203
4330
  if (runtime === "node") devDependencies.push("tsx", "@types/node");
4204
4331
  }
4332
+ if (api === "trpc") {
4333
+ if (framework === "express") dependencies.push("@trpc/server");
4334
+ else if (framework === "fastify") dependencies.push("@trpc/server");
4335
+ else if (runtime === "workers") dependencies.push("@trpc/server");
4336
+ } else if (api === "orpc") dependencies.push("@orpc/server", "@orpc/openapi", "@orpc/zod");
4337
+ if (auth === "better-auth") dependencies.push("better-auth");
4338
+ if (examples.includes("ai")) dependencies.push("ai", "@ai-sdk/google");
4205
4339
  if (runtime === "bun") devDependencies.push("@types/bun");
4206
4340
  if (dependencies.length > 0 || devDependencies.length > 0) await addPackageDependency({
4207
4341
  dependencies,
@@ -4220,7 +4354,7 @@ async function setupAuth(config) {
4220
4354
  const nativeDir = path.join(projectDir, "apps/native");
4221
4355
  const clientDirExists = await fs.pathExists(clientDir);
4222
4356
  const nativeDirExists = await fs.pathExists(nativeDir);
4223
- const serverDirExists = await fs.pathExists(serverDir);
4357
+ await fs.pathExists(serverDir);
4224
4358
  try {
4225
4359
  if (backend === "convex") {
4226
4360
  if (auth === "clerk" && clientDirExists) {
@@ -4276,9 +4410,11 @@ async function setupAuth(config) {
4276
4410
  });
4277
4411
  return;
4278
4412
  }
4279
- if (serverDirExists && auth === "better-auth") await addPackageDependency({
4413
+ const authPackageDir = path.join(projectDir, "packages/auth");
4414
+ const authPackageDirExists = await fs.pathExists(authPackageDir);
4415
+ if (authPackageDirExists && auth === "better-auth") await addPackageDependency({
4280
4416
  dependencies: ["better-auth"],
4281
- projectDir: serverDir
4417
+ projectDir: authPackageDir
4282
4418
  });
4283
4419
  if (frontend.some((f) => [
4284
4420
  "react-router",
@@ -4300,9 +4436,9 @@ async function setupAuth(config) {
4300
4436
  dependencies: ["better-auth", "@better-auth/expo"],
4301
4437
  projectDir: nativeDir
4302
4438
  });
4303
- if (serverDirExists) await addPackageDependency({
4439
+ if (authPackageDirExists) await addPackageDependency({
4304
4440
  dependencies: ["@better-auth/expo"],
4305
- projectDir: serverDir
4441
+ projectDir: authPackageDir
4306
4442
  });
4307
4443
  }
4308
4444
  }
@@ -4485,8 +4621,6 @@ async function setupEnvironmentVariables(config) {
4485
4621
  return;
4486
4622
  }
4487
4623
  const serverDir = path.join(projectDir, "apps/server");
4488
- if (!await fs.pathExists(serverDir)) return;
4489
- const envPath = path.join(serverDir, ".env");
4490
4624
  let corsOrigin = "http://localhost:3001";
4491
4625
  if (hasReactRouter || hasSvelte) corsOrigin = "http://localhost:5173";
4492
4626
  let databaseUrl = null;
@@ -4502,47 +4636,50 @@ async function setupEnvironmentVariables(config) {
4502
4636
  break;
4503
4637
  case "sqlite":
4504
4638
  if (config.runtime === "workers") databaseUrl = "http://127.0.0.1:8080";
4505
- else databaseUrl = "file:./local.db";
4639
+ else databaseUrl = `file:${path.join(config.projectDir, "apps/server", "local.db")}`;
4506
4640
  break;
4507
4641
  }
4508
- const serverVars = [
4509
- {
4510
- key: "CORS_ORIGIN",
4511
- value: corsOrigin,
4512
- condition: true
4513
- },
4514
- {
4515
- key: "BETTER_AUTH_SECRET",
4516
- value: generateAuthSecret(),
4517
- condition: !!auth
4518
- },
4519
- {
4520
- key: "BETTER_AUTH_URL",
4521
- value: "http://localhost:3000",
4522
- condition: !!auth
4523
- },
4524
- {
4525
- key: "DATABASE_URL",
4526
- value: databaseUrl,
4527
- condition: database !== "none" && dbSetup === "none"
4528
- },
4529
- {
4530
- key: "GOOGLE_GENERATIVE_AI_API_KEY",
4531
- value: "",
4532
- condition: examples?.includes("ai") || false
4533
- },
4534
- {
4535
- key: "POLAR_ACCESS_TOKEN",
4536
- value: "",
4537
- condition: config.payments === "polar"
4538
- },
4539
- {
4540
- key: "POLAR_SUCCESS_URL",
4541
- value: `${corsOrigin}/success?checkout_id={CHECKOUT_ID}`,
4542
- condition: config.payments === "polar"
4543
- }
4544
- ];
4545
- await addEnvVariablesToFile(envPath, serverVars);
4642
+ if (await fs.pathExists(serverDir)) {
4643
+ const serverEnvPath = path.join(serverDir, ".env");
4644
+ const serverVars = [
4645
+ {
4646
+ key: "BETTER_AUTH_SECRET",
4647
+ value: generateAuthSecret(),
4648
+ condition: !!auth
4649
+ },
4650
+ {
4651
+ key: "BETTER_AUTH_URL",
4652
+ value: "http://localhost:3000",
4653
+ condition: !!auth
4654
+ },
4655
+ {
4656
+ key: "POLAR_ACCESS_TOKEN",
4657
+ value: "",
4658
+ condition: config.payments === "polar"
4659
+ },
4660
+ {
4661
+ key: "POLAR_SUCCESS_URL",
4662
+ value: `${corsOrigin}/success?checkout_id={CHECKOUT_ID}`,
4663
+ condition: config.payments === "polar"
4664
+ },
4665
+ {
4666
+ key: "CORS_ORIGIN",
4667
+ value: corsOrigin,
4668
+ condition: true
4669
+ },
4670
+ {
4671
+ key: "GOOGLE_GENERATIVE_AI_API_KEY",
4672
+ value: "",
4673
+ condition: examples?.includes("ai") || false
4674
+ },
4675
+ {
4676
+ key: "DATABASE_URL",
4677
+ value: databaseUrl,
4678
+ condition: database !== "none" && dbSetup === "none"
4679
+ }
4680
+ ];
4681
+ await addEnvVariablesToFile(serverEnvPath, serverVars);
4682
+ }
4546
4683
  const isUnifiedAlchemy = webDeploy === "alchemy" && serverDeploy === "alchemy";
4547
4684
  const isIndividualAlchemy = webDeploy === "alchemy" || serverDeploy === "alchemy";
4548
4685
  if (isUnifiedAlchemy) {
@@ -4561,14 +4698,11 @@ async function setupEnvironmentVariables(config) {
4561
4698
  condition: true
4562
4699
  }]);
4563
4700
  }
4564
- if (serverDeploy === "alchemy") {
4565
- const serverDir$1 = path.join(projectDir, "apps/server");
4566
- if (await fs.pathExists(serverDir$1)) await addEnvVariablesToFile(path.join(serverDir$1, ".env"), [{
4567
- key: "ALCHEMY_PASSWORD",
4568
- value: "please-change-this",
4569
- condition: true
4570
- }]);
4571
- }
4701
+ if (serverDeploy === "alchemy") await addEnvVariablesToFile(path.join(serverDir, ".env"), [{
4702
+ key: "ALCHEMY_PASSWORD",
4703
+ value: "please-change-this",
4704
+ condition: true
4705
+ }]);
4572
4706
  }
4573
4707
  }
4574
4708
 
@@ -4603,7 +4737,7 @@ async function setupCloudflareD1(config) {
4603
4737
  const envPath = path.join(projectDir, "apps/server", ".env");
4604
4738
  const variables = [{
4605
4739
  key: "DATABASE_URL",
4606
- value: "file:./local.db",
4740
+ value: `file:${path.join(projectDir, "apps/server", "local.db")}`,
4607
4741
  condition: true
4608
4742
  }];
4609
4743
  try {
@@ -5133,9 +5267,9 @@ async function writeEnvFile$1(projectDir, config) {
5133
5267
  }
5134
5268
  async function addDotenvImportToPrismaConfig(projectDir) {
5135
5269
  try {
5136
- const prismaConfigPath = path.join(projectDir, "apps/server/prisma.config.ts");
5270
+ const prismaConfigPath = path.join(projectDir, "packages/db/prisma.config.ts");
5137
5271
  let content = await fs.readFile(prismaConfigPath, "utf8");
5138
- content = `import "dotenv/config";\n${content}`;
5272
+ content = `import dotenv from "dotenv";\ndotenv.config({ path: "../../apps/server/.env" });\n${content}`;
5139
5273
  await fs.writeFile(prismaConfigPath, content);
5140
5274
  } catch (_error) {
5141
5275
  consola$1.error("Failed to update prisma.config.ts");
@@ -5151,11 +5285,12 @@ function displayManualSetupInstructions$1() {
5151
5285
 
5152
5286
  DATABASE_URL="your_database_url"`);
5153
5287
  }
5154
- async function addPrismaAccelerateExtension(serverDir) {
5288
+ async function addPrismaAccelerateExtension(projectDir) {
5155
5289
  try {
5290
+ const dbPackageDir = path.join(projectDir, "packages/db");
5156
5291
  await addPackageDependency({
5157
5292
  dependencies: ["@prisma/extension-accelerate"],
5158
- projectDir: serverDir
5293
+ projectDir: dbPackageDir
5159
5294
  });
5160
5295
  return true;
5161
5296
  } catch (_error) {
@@ -5216,7 +5351,7 @@ async function setupPrismaPostgres(config, cliInput) {
5216
5351
  await writeEnvFile$1(projectDir, prismaConfig);
5217
5352
  if (orm === "prisma") {
5218
5353
  await addDotenvImportToPrismaConfig(projectDir);
5219
- await addPrismaAccelerateExtension(serverDir);
5354
+ await addPrismaAccelerateExtension(projectDir);
5220
5355
  }
5221
5356
  const connectionType = orm === "drizzle" ? "direct connection" : "Prisma Accelerate";
5222
5357
  log.success(pc.green(`Prisma Postgres database configured successfully with ${connectionType}!`));
@@ -5316,18 +5451,18 @@ function displayManualSupabaseInstructions(output) {
5316
5451
  log.info(`"Manual Supabase Setup Instructions:"
5317
5452
  1. Ensure Docker is installed and running.
5318
5453
  2. Install the Supabase CLI (e.g., \`npm install -g supabase\`).
5319
- 3. Run \`supabase init\` in your project's \`apps/server\` directory.
5320
- 4. Run \`supabase start\` in your project's \`apps/server\` directory.
5454
+ 3. Run \`supabase init\` in your project's \`packages/db\` directory.
5455
+ 4. Run \`supabase start\` in your project's \`packages/db\` directory.
5321
5456
  5. Copy the 'DB URL' from the output.${output ? `
5322
5457
  ${pc.bold("Relevant output from `supabase start`:")}
5323
5458
  ${pc.dim(output)}` : ""}
5324
- 6. Add the DB URL to the .env file in \`apps/server/.env\` as \`DATABASE_URL\`:
5459
+ 6. Add the DB URL to the .env file in \`packages/db/.env\` as \`DATABASE_URL\`:
5325
5460
  ${pc.gray("DATABASE_URL=\"your_supabase_db_url\"")}`);
5326
5461
  }
5327
5462
  async function setupSupabase(config, cliInput) {
5328
5463
  const { projectDir, packageManager } = config;
5329
5464
  const manualDb = cliInput?.manualDb ?? false;
5330
- const serverDir = path.join(projectDir, "apps", "server");
5465
+ const serverDir = path.join(projectDir, "packages", "db");
5331
5466
  try {
5332
5467
  await fs.ensureDir(serverDir);
5333
5468
  if (manualDb) {
@@ -5610,15 +5745,15 @@ async function setupDatabase(config, cliInput) {
5610
5745
  const { database, orm, dbSetup, backend, projectDir } = config;
5611
5746
  if (backend === "convex" || database === "none") {
5612
5747
  if (backend !== "convex") {
5613
- const serverDir$1 = path.join(projectDir, "apps/server");
5614
- const serverDbDir = path.join(serverDir$1, "src/db");
5748
+ const serverDir = path.join(projectDir, "apps/server");
5749
+ const serverDbDir = path.join(serverDir, "src/db");
5615
5750
  if (await fs.pathExists(serverDbDir)) await fs.remove(serverDbDir);
5616
5751
  }
5617
5752
  return;
5618
5753
  }
5619
5754
  const s = spinner();
5620
- const serverDir = path.join(projectDir, "apps/server");
5621
- if (!await fs.pathExists(serverDir)) return;
5755
+ const dbPackageDir = path.join(projectDir, "packages/db");
5756
+ if (!await fs.pathExists(dbPackageDir)) return;
5622
5757
  try {
5623
5758
  if (orm === "prisma") if (database === "mysql" && dbSetup === "planetscale") await addPackageDependency({
5624
5759
  dependencies: [
@@ -5627,23 +5762,27 @@ async function setupDatabase(config, cliInput) {
5627
5762
  "@planetscale/database"
5628
5763
  ],
5629
5764
  devDependencies: ["prisma"],
5630
- projectDir: serverDir
5765
+ projectDir: dbPackageDir
5631
5766
  });
5632
5767
  else if (database === "sqlite" && dbSetup === "turso") await addPackageDependency({
5633
5768
  dependencies: ["@prisma/client", "@prisma/adapter-libsql"],
5634
5769
  devDependencies: ["prisma"],
5635
- projectDir: serverDir
5770
+ projectDir: dbPackageDir
5636
5771
  });
5637
5772
  else await addPackageDependency({
5638
5773
  dependencies: ["@prisma/client"],
5639
5774
  devDependencies: ["prisma"],
5640
- projectDir: serverDir
5775
+ projectDir: dbPackageDir
5641
5776
  });
5642
5777
  else if (orm === "drizzle") {
5643
5778
  if (database === "sqlite") await addPackageDependency({
5644
- dependencies: ["drizzle-orm", "@libsql/client"],
5779
+ dependencies: [
5780
+ "drizzle-orm",
5781
+ "@libsql/client",
5782
+ "libsql"
5783
+ ],
5645
5784
  devDependencies: ["drizzle-kit"],
5646
- projectDir: serverDir
5785
+ projectDir: dbPackageDir
5647
5786
  });
5648
5787
  else if (database === "postgres") if (dbSetup === "neon") await addPackageDependency({
5649
5788
  dependencies: [
@@ -5652,32 +5791,32 @@ async function setupDatabase(config, cliInput) {
5652
5791
  "ws"
5653
5792
  ],
5654
5793
  devDependencies: ["drizzle-kit", "@types/ws"],
5655
- projectDir: serverDir
5794
+ projectDir: dbPackageDir
5656
5795
  });
5657
5796
  else if (dbSetup === "planetscale") await addPackageDependency({
5658
5797
  dependencies: ["drizzle-orm", "pg"],
5659
5798
  devDependencies: ["drizzle-kit", "@types/pg"],
5660
- projectDir: serverDir
5799
+ projectDir: dbPackageDir
5661
5800
  });
5662
5801
  else await addPackageDependency({
5663
5802
  dependencies: ["drizzle-orm", "pg"],
5664
5803
  devDependencies: ["drizzle-kit", "@types/pg"],
5665
- projectDir: serverDir
5804
+ projectDir: dbPackageDir
5666
5805
  });
5667
5806
  else if (database === "mysql") if (dbSetup === "planetscale") await addPackageDependency({
5668
5807
  dependencies: ["drizzle-orm", "@planetscale/database"],
5669
5808
  devDependencies: ["drizzle-kit"],
5670
- projectDir: serverDir
5809
+ projectDir: dbPackageDir
5671
5810
  });
5672
5811
  else await addPackageDependency({
5673
5812
  dependencies: ["drizzle-orm", "mysql2"],
5674
5813
  devDependencies: ["drizzle-kit"],
5675
- projectDir: serverDir
5814
+ projectDir: dbPackageDir
5676
5815
  });
5677
5816
  } else if (orm === "mongoose") await addPackageDependency({
5678
5817
  dependencies: ["mongoose"],
5679
5818
  devDependencies: [],
5680
- projectDir: serverDir
5819
+ projectDir: dbPackageDir
5681
5820
  });
5682
5821
  if (dbSetup === "docker") await setupDockerCompose(config);
5683
5822
  else if (database === "sqlite" && dbSetup === "turso") await setupTurso(config, cliInput);
@@ -5700,7 +5839,7 @@ async function setupDatabase(config, cliInput) {
5700
5839
  //#region src/helpers/core/runtime-setup.ts
5701
5840
  async function setupRuntime(config) {
5702
5841
  const { runtime, backend, projectDir } = config;
5703
- if (backend === "convex" || backend === "next" || runtime === "none") return;
5842
+ if (backend === "convex" || backend === "self" || runtime === "none") return;
5704
5843
  const serverDir = path.join(projectDir, "apps/server");
5705
5844
  if (!await fs.pathExists(serverDir)) return;
5706
5845
  if (runtime === "bun") await setupBunRuntime(serverDir, backend);
@@ -6220,7 +6359,7 @@ async function displayPostInstallInstructions(config) {
6220
6359
  else if (!hasNative && !addons?.includes("starlight")) output += `${pc.yellow("NOTE:")} You are creating a backend-only app\n (no frontend selected)\n`;
6221
6360
  if (!isConvex) {
6222
6361
  output += `${pc.cyan("•")} Backend API: http://localhost:3000\n`;
6223
- if (api === "orpc") if (backend === "next") output += `${pc.cyan("•")} OpenAPI (Scalar UI): http://localhost:3000/rpc/api\n`;
6362
+ if (api === "orpc") if (backend === "self") output += `${pc.cyan("•")} OpenAPI (Scalar UI): http://localhost:3000/rpc/api\n`;
6224
6363
  else output += `${pc.cyan("•")} OpenAPI (Scalar UI): http://localhost:3000/api\n`;
6225
6364
  }
6226
6365
  if (addons?.includes("starlight")) output += `${pc.cyan("•")} Docs: http://localhost:4321\n`;
@@ -6334,12 +6473,78 @@ function getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy) {
6334
6473
  return instructions.length ? `\n${instructions.join("\n")}` : "";
6335
6474
  }
6336
6475
 
6476
+ //#endregion
6477
+ //#region src/helpers/core/workspace-setup.ts
6478
+ async function setupWorkspaceDependencies(projectDir, options) {
6479
+ const projectName = options.projectName;
6480
+ const workspaceVersion = options.packageManager === "npm" ? "*" : "workspace:*";
6481
+ const commonDeps = ["dotenv", "zod"];
6482
+ const commonDevDeps = ["tsdown"];
6483
+ const dbPackageDir = path.join(projectDir, "packages/db");
6484
+ if (await fs.pathExists(dbPackageDir)) await addPackageDependency({
6485
+ dependencies: commonDeps,
6486
+ devDependencies: commonDevDeps,
6487
+ projectDir: dbPackageDir
6488
+ });
6489
+ const authPackageDir = path.join(projectDir, "packages/auth");
6490
+ if (await fs.pathExists(authPackageDir)) await addPackageDependency({
6491
+ dependencies: commonDeps,
6492
+ devDependencies: commonDevDeps,
6493
+ customDependencies: { [`@${projectName}/db`]: workspaceVersion },
6494
+ projectDir: authPackageDir
6495
+ });
6496
+ const apiPackageDir = path.join(projectDir, "packages/api");
6497
+ if (await fs.pathExists(apiPackageDir)) await addPackageDependency({
6498
+ dependencies: commonDeps,
6499
+ devDependencies: commonDevDeps,
6500
+ customDependencies: {
6501
+ [`@${projectName}/auth`]: workspaceVersion,
6502
+ [`@${projectName}/db`]: workspaceVersion
6503
+ },
6504
+ projectDir: apiPackageDir
6505
+ });
6506
+ const serverPackageDir = path.join(projectDir, "apps/server");
6507
+ if (await fs.pathExists(serverPackageDir)) await addPackageDependency({
6508
+ dependencies: commonDeps,
6509
+ devDependencies: commonDevDeps,
6510
+ customDependencies: {
6511
+ [`@${projectName}/api`]: workspaceVersion,
6512
+ [`@${projectName}/auth`]: workspaceVersion,
6513
+ [`@${projectName}/db`]: workspaceVersion
6514
+ },
6515
+ projectDir: serverPackageDir
6516
+ });
6517
+ const needsApiDependency = options.api && options.api !== "none";
6518
+ const webPackageDir = path.join(projectDir, "apps/web");
6519
+ if (await fs.pathExists(webPackageDir)) {
6520
+ const webDeps = {};
6521
+ if (options.backend === "self") {
6522
+ webDeps[`@${projectName}/api`] = workspaceVersion;
6523
+ webDeps[`@${projectName}/auth`] = workspaceVersion;
6524
+ webDeps[`@${projectName}/db`] = workspaceVersion;
6525
+ } else if (needsApiDependency) webDeps[`@${projectName}/api`] = workspaceVersion;
6526
+ if (Object.keys(webDeps).length > 0) await addPackageDependency({
6527
+ customDependencies: webDeps,
6528
+ projectDir: webPackageDir
6529
+ });
6530
+ }
6531
+ }
6532
+
6337
6533
  //#endregion
6338
6534
  //#region src/helpers/core/project-config.ts
6339
6535
  async function updatePackageConfigurations(projectDir, options) {
6340
6536
  await updateRootPackageJson(projectDir, options);
6341
- if (options.backend !== "convex") await updateServerPackageJson(projectDir, options);
6342
- else await updateConvexPackageJson(projectDir, options);
6537
+ if (options.backend === "convex") await updateConvexPackageJson(projectDir, options);
6538
+ else if (options.backend === "self") {
6539
+ await updateAuthPackageJson(projectDir, options);
6540
+ await updateApiPackageJson(projectDir, options);
6541
+ await setupWorkspaceDependencies(projectDir, options);
6542
+ } else if (options.backend !== "none") {
6543
+ await updateServerPackageJson(projectDir, options);
6544
+ await updateAuthPackageJson(projectDir, options);
6545
+ await updateApiPackageJson(projectDir, options);
6546
+ await setupWorkspaceDependencies(projectDir, options);
6547
+ }
6343
6548
  }
6344
6549
  async function updateRootPackageJson(projectDir, options) {
6345
6550
  const rootPackageJsonPath = path.join(projectDir, "package.json");
@@ -6349,6 +6554,7 @@ async function updateRootPackageJson(projectDir, options) {
6349
6554
  if (!packageJson.scripts) packageJson.scripts = {};
6350
6555
  const scripts = packageJson.scripts;
6351
6556
  const backendPackageName = options.backend === "convex" ? `@${options.projectName}/backend` : "server";
6557
+ const dbPackageName = `@${options.projectName}/db`;
6352
6558
  let serverDevScript = "";
6353
6559
  if (options.addons.includes("turborepo")) serverDevScript = `turbo -F ${backendPackageName} dev`;
6354
6560
  else if (options.packageManager === "bun") serverDevScript = `bun run --filter ${backendPackageName} dev`;
@@ -6365,17 +6571,17 @@ async function updateRootPackageJson(projectDir, options) {
6365
6571
  scripts["check-types"] = "turbo check-types";
6366
6572
  scripts["dev:native"] = "turbo -F native dev";
6367
6573
  scripts["dev:web"] = "turbo -F web dev";
6368
- scripts["dev:server"] = serverDevScript;
6574
+ if (options.backend !== "self" && options.backend !== "none") scripts["dev:server"] = serverDevScript;
6369
6575
  if (options.backend === "convex") scripts["dev:setup"] = `turbo -F ${backendPackageName} dev:setup`;
6370
6576
  if (needsDbScripts) {
6371
- scripts["db:push"] = `turbo -F ${backendPackageName} db:push`;
6372
- if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `turbo -F ${backendPackageName} db:studio`;
6577
+ scripts["db:push"] = `turbo -F ${dbPackageName} db:push`;
6578
+ if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `turbo -F ${dbPackageName} db:studio`;
6373
6579
  if (options.orm === "prisma") {
6374
- scripts["db:generate"] = `turbo -F ${backendPackageName} db:generate`;
6375
- scripts["db:migrate"] = `turbo -F ${backendPackageName} db:migrate`;
6580
+ scripts["db:generate"] = `turbo -F ${dbPackageName} db:generate`;
6581
+ scripts["db:migrate"] = `turbo -F ${dbPackageName} db:migrate`;
6376
6582
  } else if (options.orm === "drizzle") {
6377
- scripts["db:generate"] = `turbo -F ${backendPackageName} db:generate`;
6378
- if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `turbo -F ${backendPackageName} db:migrate`;
6583
+ scripts["db:generate"] = `turbo -F ${dbPackageName} db:generate`;
6584
+ if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `turbo -F ${dbPackageName} db:migrate`;
6379
6585
  }
6380
6586
  }
6381
6587
  if (options.dbSetup === "docker") {
@@ -6390,17 +6596,17 @@ async function updateRootPackageJson(projectDir, options) {
6390
6596
  scripts["check-types"] = "pnpm -r check-types";
6391
6597
  scripts["dev:native"] = "pnpm --filter native dev";
6392
6598
  scripts["dev:web"] = "pnpm --filter web dev";
6393
- scripts["dev:server"] = serverDevScript;
6599
+ if (options.backend !== "self" && options.backend !== "none") scripts["dev:server"] = serverDevScript;
6394
6600
  if (options.backend === "convex") scripts["dev:setup"] = `pnpm --filter ${backendPackageName} dev:setup`;
6395
6601
  if (needsDbScripts) {
6396
- scripts["db:push"] = `pnpm --filter ${backendPackageName} db:push`;
6397
- if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `pnpm --filter ${backendPackageName} db:studio`;
6602
+ scripts["db:push"] = `pnpm --filter ${dbPackageName} db:push`;
6603
+ if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `pnpm --filter ${dbPackageName} db:studio`;
6398
6604
  if (options.orm === "prisma") {
6399
- scripts["db:generate"] = `pnpm --filter ${backendPackageName} db:generate`;
6400
- scripts["db:migrate"] = `pnpm --filter ${backendPackageName} db:migrate`;
6605
+ scripts["db:generate"] = `pnpm --filter ${dbPackageName} db:generate`;
6606
+ scripts["db:migrate"] = `pnpm --filter ${dbPackageName} db:migrate`;
6401
6607
  } else if (options.orm === "drizzle") {
6402
- scripts["db:generate"] = `pnpm --filter ${backendPackageName} db:generate`;
6403
- if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `pnpm --filter ${backendPackageName} db:migrate`;
6608
+ scripts["db:generate"] = `pnpm --filter ${dbPackageName} db:generate`;
6609
+ if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `pnpm --filter ${dbPackageName} db:migrate`;
6404
6610
  }
6405
6611
  }
6406
6612
  if (options.dbSetup === "docker") {
@@ -6415,17 +6621,17 @@ async function updateRootPackageJson(projectDir, options) {
6415
6621
  scripts["check-types"] = "npm run check-types --workspaces";
6416
6622
  scripts["dev:native"] = "npm run dev --workspace native";
6417
6623
  scripts["dev:web"] = "npm run dev --workspace web";
6418
- scripts["dev:server"] = serverDevScript;
6624
+ if (options.backend !== "self" && options.backend !== "none") scripts["dev:server"] = serverDevScript;
6419
6625
  if (options.backend === "convex") scripts["dev:setup"] = `npm run dev:setup --workspace ${backendPackageName}`;
6420
6626
  if (needsDbScripts) {
6421
- scripts["db:push"] = `npm run db:push --workspace ${backendPackageName}`;
6422
- if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `npm run db:studio --workspace ${backendPackageName}`;
6627
+ scripts["db:push"] = `npm run db:push --workspace ${dbPackageName}`;
6628
+ if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `npm run db:studio --workspace ${dbPackageName}`;
6423
6629
  if (options.orm === "prisma") {
6424
- scripts["db:generate"] = `npm run db:generate --workspace ${backendPackageName}`;
6425
- scripts["db:migrate"] = `npm run db:migrate --workspace ${backendPackageName}`;
6630
+ scripts["db:generate"] = `npm run db:generate --workspace ${dbPackageName}`;
6631
+ scripts["db:migrate"] = `npm run db:migrate --workspace ${dbPackageName}`;
6426
6632
  } else if (options.orm === "drizzle") {
6427
- scripts["db:generate"] = `npm run db:generate --workspace ${backendPackageName}`;
6428
- if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `npm run db:migrate --workspace ${backendPackageName}`;
6633
+ scripts["db:generate"] = `npm run db:generate --workspace ${dbPackageName}`;
6634
+ if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `npm run db:migrate --workspace ${dbPackageName}`;
6429
6635
  }
6430
6636
  }
6431
6637
  if (options.dbSetup === "docker") {
@@ -6440,17 +6646,17 @@ async function updateRootPackageJson(projectDir, options) {
6440
6646
  scripts["check-types"] = "bun run --filter '*' check-types";
6441
6647
  scripts["dev:native"] = "bun run --filter native dev";
6442
6648
  scripts["dev:web"] = "bun run --filter web dev";
6443
- scripts["dev:server"] = serverDevScript;
6649
+ if (options.backend !== "self" && options.backend !== "none") scripts["dev:server"] = serverDevScript;
6444
6650
  if (options.backend === "convex") scripts["dev:setup"] = `bun run --filter ${backendPackageName} dev:setup`;
6445
6651
  if (needsDbScripts) {
6446
- scripts["db:push"] = `bun run --filter ${backendPackageName} db:push`;
6447
- if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `bun run --filter ${backendPackageName} db:studio`;
6652
+ scripts["db:push"] = `bun run --filter ${dbPackageName} db:push`;
6653
+ if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `bun run --filter ${dbPackageName} db:studio`;
6448
6654
  if (options.orm === "prisma") {
6449
- scripts["db:generate"] = `bun run --filter ${backendPackageName} db:generate`;
6450
- scripts["db:migrate"] = `bun run --filter ${backendPackageName} db:migrate`;
6655
+ scripts["db:generate"] = `bun run --filter ${dbPackageName} db:generate`;
6656
+ scripts["db:migrate"] = `bun run --filter ${dbPackageName} db:migrate`;
6451
6657
  } else if (options.orm === "drizzle") {
6452
- scripts["db:generate"] = `bun run --filter ${backendPackageName} db:generate`;
6453
- if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `bun run --filter ${backendPackageName} db:migrate`;
6658
+ scripts["db:generate"] = `bun run --filter ${dbPackageName} db:generate`;
6659
+ if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `bun run --filter ${dbPackageName} db:migrate`;
6454
6660
  }
6455
6661
  }
6456
6662
  if (options.dbSetup === "docker") {
@@ -6483,6 +6689,22 @@ async function updateServerPackageJson(projectDir, options) {
6483
6689
  const serverPackageJson = await fs.readJson(serverPackageJsonPath);
6484
6690
  if (!serverPackageJson.scripts) serverPackageJson.scripts = {};
6485
6691
  const scripts = serverPackageJson.scripts;
6692
+ if (options.dbSetup === "docker") {
6693
+ scripts["db:start"] = "docker compose up -d";
6694
+ scripts["db:watch"] = "docker compose up";
6695
+ scripts["db:stop"] = "docker compose stop";
6696
+ scripts["db:down"] = "docker compose down";
6697
+ }
6698
+ await fs.writeJson(serverPackageJsonPath, serverPackageJson, { spaces: 2 });
6699
+ await updateDbPackageJson(projectDir, options);
6700
+ }
6701
+ async function updateDbPackageJson(projectDir, options) {
6702
+ const dbPackageJsonPath = path.join(projectDir, "packages/db/package.json");
6703
+ if (!await fs.pathExists(dbPackageJsonPath)) return;
6704
+ const dbPackageJson = await fs.readJson(dbPackageJsonPath);
6705
+ dbPackageJson.name = `@${options.projectName}/db`;
6706
+ if (!dbPackageJson.scripts) dbPackageJson.scripts = {};
6707
+ const scripts = dbPackageJson.scripts;
6486
6708
  if (options.database !== "none") {
6487
6709
  if (options.database === "sqlite" && options.orm === "drizzle" && options.dbSetup !== "d1") scripts["db:local"] = "turso dev --db-file local.db";
6488
6710
  if (options.orm === "prisma") {
@@ -6497,13 +6719,21 @@ async function updateServerPackageJson(projectDir, options) {
6497
6719
  if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = "drizzle-kit migrate";
6498
6720
  }
6499
6721
  }
6500
- if (options.dbSetup === "docker") {
6501
- scripts["db:start"] = "docker compose up -d";
6502
- scripts["db:watch"] = "docker compose up";
6503
- scripts["db:stop"] = "docker compose stop";
6504
- scripts["db:down"] = "docker compose down";
6505
- }
6506
- await fs.writeJson(serverPackageJsonPath, serverPackageJson, { spaces: 2 });
6722
+ await fs.writeJson(dbPackageJsonPath, dbPackageJson, { spaces: 2 });
6723
+ }
6724
+ async function updateAuthPackageJson(projectDir, options) {
6725
+ const authPackageJsonPath = path.join(projectDir, "packages/auth/package.json");
6726
+ if (!await fs.pathExists(authPackageJsonPath)) return;
6727
+ const authPackageJson = await fs.readJson(authPackageJsonPath);
6728
+ authPackageJson.name = `@${options.projectName}/auth`;
6729
+ await fs.writeJson(authPackageJsonPath, authPackageJson, { spaces: 2 });
6730
+ }
6731
+ async function updateApiPackageJson(projectDir, options) {
6732
+ const apiPackageJsonPath = path.join(projectDir, "packages/api/package.json");
6733
+ if (!await fs.pathExists(apiPackageJsonPath)) return;
6734
+ const apiPackageJson = await fs.readJson(apiPackageJsonPath);
6735
+ apiPackageJson.name = `@${options.projectName}/api`;
6736
+ await fs.writeJson(apiPackageJsonPath, apiPackageJson, { spaces: 2 });
6507
6737
  }
6508
6738
  async function updateConvexPackageJson(projectDir, options) {
6509
6739
  const convexPackageJsonPath = path.join(projectDir, "packages/backend/package.json");
@@ -6519,15 +6749,14 @@ async function updateConvexPackageJson(projectDir, options) {
6519
6749
  async function createProject(options, cliInput) {
6520
6750
  const projectDir = options.projectDir;
6521
6751
  const isConvex = options.backend === "convex";
6752
+ const isSelfBackend = options.backend === "self";
6753
+ const needsServerSetup = !isConvex && !isSelfBackend;
6522
6754
  try {
6523
6755
  await fs.ensureDir(projectDir);
6524
6756
  await copyBaseTemplate(projectDir, options);
6525
6757
  await setupFrontendTemplates(projectDir, options);
6526
6758
  await setupBackendFramework(projectDir, options);
6527
- if (!isConvex) {
6528
- await setupDbOrmTemplates(projectDir, options);
6529
- await setupDockerComposeTemplates(projectDir, options);
6530
- }
6759
+ if (needsServerSetup) await setupDockerComposeTemplates(projectDir, options);
6531
6760
  await setupAuthTemplate(projectDir, options);
6532
6761
  if (options.payments && options.payments !== "none") await setupPaymentsTemplate(projectDir, options);
6533
6762
  if (options.examples.length > 0 && options.examples[0] !== "none") await setupExamplesTemplate(projectDir, options);
@@ -6535,9 +6764,11 @@ async function createProject(options, cliInput) {
6535
6764
  await setupDeploymentTemplates(projectDir, options);
6536
6765
  await setupApi(options);
6537
6766
  if (!isConvex) {
6538
- await setupBackendDependencies(options);
6767
+ if (needsServerSetup) {
6768
+ await setupBackendDependencies(options);
6769
+ await setupRuntime(options);
6770
+ }
6539
6771
  await setupDatabase(options, cliInput);
6540
- await setupRuntime(options);
6541
6772
  if (options.examples.length > 0 && options.examples[0] !== "none") await setupExamples(options);
6542
6773
  }
6543
6774
  if (options.addons.length > 0 && options.addons[0] !== "none") await setupAddons(options);
@@ -6546,6 +6777,7 @@ async function createProject(options, cliInput) {
6546
6777
  await handleExtras(projectDir, options);
6547
6778
  await setupEnvironmentVariables(options);
6548
6779
  await updatePackageConfigurations(projectDir, options);
6780
+ await setupCatalogs(projectDir, options);
6549
6781
  await setupWebDeploy(options);
6550
6782
  await setupServerDeploy(options);
6551
6783
  await createReadme(projectDir, options);