create-better-t-stack 2.49.1 → 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 (100) 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-DfbhNFZ9.js} +527 -261
  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/_gitignore +34 -0
  8. package/templates/api/orpc/server/package.json.hbs +24 -0
  9. package/templates/api/orpc/server/{base/src/lib → src}/context.ts.hbs +6 -6
  10. package/templates/{backend/server/server-base → api/orpc/server}/src/routers/index.ts.hbs +2 -2
  11. package/templates/api/orpc/server/tsconfig.json.hbs +10 -0
  12. package/templates/api/orpc/server/tsdown.config.ts.hbs +7 -0
  13. package/templates/api/orpc/web/nuxt/app/plugins/orpc.ts.hbs +1 -1
  14. package/templates/api/orpc/web/react/base/src/utils/orpc.ts.hbs +4 -2
  15. package/templates/api/orpc/web/solid/src/utils/orpc.ts.hbs +1 -1
  16. package/templates/api/orpc/web/svelte/src/lib/orpc.ts.hbs +1 -1
  17. package/templates/api/trpc/fullstack/next/src/app/api/trpc/[trpc]/route.ts.hbs +14 -0
  18. package/templates/api/trpc/server/_gitignore +34 -0
  19. package/templates/api/trpc/server/package.json.hbs +23 -0
  20. package/templates/api/trpc/server/{base/src/lib → src}/context.ts.hbs +6 -6
  21. package/templates/api/trpc/server/src/routers/index.ts.hbs +55 -0
  22. package/templates/api/trpc/server/tsconfig.json.hbs +13 -0
  23. package/templates/api/trpc/server/tsdown.config.ts.hbs +7 -0
  24. package/templates/api/trpc/web/react/base/src/utils/trpc.ts.hbs +6 -4
  25. 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
  26. package/templates/auth/better-auth/server/base/_gitignore +34 -0
  27. package/templates/auth/better-auth/server/base/package.json.hbs +24 -0
  28. package/templates/auth/better-auth/server/base/src/{lib/auth.ts.hbs → index.ts.hbs} +7 -7
  29. package/templates/auth/better-auth/server/base/tsconfig.json.hbs +13 -0
  30. package/templates/auth/better-auth/server/base/tsdown.config.ts.hbs +7 -0
  31. package/templates/auth/clerk/convex/web/react/tanstack-start/src/server.ts.hbs +1 -0
  32. package/templates/backend/server/{server-base → base}/package.json.hbs +0 -1
  33. package/templates/backend/server/{server-base → base}/tsconfig.json.hbs +5 -10
  34. package/templates/backend/server/base/tsdown.config.ts.hbs +14 -0
  35. package/templates/backend/server/elysia/src/index.ts.hbs +6 -6
  36. package/templates/backend/server/express/src/index.ts.hbs +6 -6
  37. package/templates/backend/server/fastify/src/index.ts.hbs +6 -6
  38. package/templates/backend/server/hono/src/index.ts.hbs +7 -7
  39. package/templates/base/_gitignore +47 -1
  40. package/templates/base/package.json.hbs +1 -3
  41. package/templates/base/tsconfig.base.json +23 -0
  42. package/templates/db/base/_gitignore +34 -0
  43. package/templates/db/base/package.json.hbs +23 -0
  44. package/templates/db/base/tsconfig.json.hbs +13 -0
  45. package/templates/db/base/tsdown.config.ts.hbs +7 -0
  46. package/templates/db/drizzle/mysql/drizzle.config.ts.hbs +7 -2
  47. package/templates/db/drizzle/mysql/src/{db/index.ts.hbs → index.ts.hbs} +1 -1
  48. package/templates/db/drizzle/postgres/drizzle.config.ts.hbs +7 -2
  49. package/templates/db/drizzle/postgres/src/{db/index.ts.hbs → index.ts.hbs} +1 -1
  50. package/templates/db/drizzle/sqlite/drizzle.config.ts.hbs +7 -2
  51. package/templates/db/drizzle/sqlite/src/{db/index.ts.hbs → index.ts.hbs} +1 -1
  52. package/templates/db/prisma/mongodb/prisma.config.ts.hbs +5 -1
  53. package/templates/db/prisma/mongodb/src/index.ts.hbs +5 -0
  54. package/templates/db/prisma/mysql/prisma.config.ts.hbs +5 -1
  55. package/templates/db/prisma/mysql/src/{db/index.ts.hbs → index.ts.hbs} +1 -1
  56. package/templates/db/prisma/postgres/prisma.config.ts.hbs +7 -3
  57. package/templates/db/prisma/postgres/src/{db/index.ts.hbs → index.ts.hbs} +1 -1
  58. package/templates/db/prisma/sqlite/prisma.config.ts.hbs +5 -1
  59. package/templates/db/prisma/sqlite/src/{db/index.ts.hbs → index.ts.hbs} +3 -3
  60. package/templates/deploy/wrangler/web/react/tanstack-start/wrangler.jsonc.hbs +1 -1
  61. package/templates/examples/ai/fullstack/next/src/app/api/ai/route.ts.hbs +15 -0
  62. package/templates/examples/todo/server/drizzle/base/src/routers/todo.ts.hbs +7 -7
  63. package/templates/examples/todo/server/mongoose/base/src/routers/todo.ts.hbs +4 -4
  64. package/templates/examples/todo/server/prisma/base/src/routers/todo.ts.hbs +4 -4
  65. package/templates/frontend/react/next/package.json.hbs +1 -1
  66. package/templates/frontend/react/tanstack-router/src/routes/__root.tsx.hbs +1 -1
  67. package/templates/frontend/react/tanstack-start/package.json.hbs +7 -7
  68. package/templates/frontend/react/tanstack-start/src/routes/__root.tsx.hbs +5 -5
  69. package/templates/frontend/react/tanstack-start/vite.config.ts.hbs +1 -1
  70. package/templates/api/orpc/server/next/src/app/rpc/[...all]/route.ts.hbs +0 -52
  71. package/templates/api/trpc/server/next/src/app/trpc/[trpc]/route.ts +0 -14
  72. package/templates/backend/server/next/next-env.d.ts +0 -5
  73. package/templates/backend/server/next/next.config.ts +0 -7
  74. package/templates/backend/server/next/package.json.hbs +0 -27
  75. package/templates/backend/server/next/src/app/route.ts +0 -5
  76. package/templates/backend/server/next/src/middleware.ts +0 -19
  77. package/templates/backend/server/next/tsconfig.json.hbs +0 -33
  78. package/templates/db/prisma/mongodb/src/db/index.ts.hbs +0 -5
  79. package/templates/examples/ai/server/next/src/app/ai/route.ts.hbs +0 -15
  80. /package/templates/api/orpc/server/{base/src/lib/orpc.ts.hbs → src/index.ts.hbs} +0 -0
  81. /package/templates/api/trpc/server/{base/src/lib/trpc.ts.hbs → src/index.ts.hbs} +0 -0
  82. /package/templates/auth/better-auth/server/db/drizzle/mysql/src/{db/schema/auth.ts → schema/auth.ts.hbs} +0 -0
  83. /package/templates/auth/better-auth/server/db/drizzle/postgres/src/{db/schema/auth.ts → schema/auth.ts.hbs} +0 -0
  84. /package/templates/auth/better-auth/server/db/drizzle/sqlite/src/{db/schema/auth.ts → schema/auth.ts.hbs} +0 -0
  85. /package/templates/auth/better-auth/server/db/mongoose/mongodb/src/{db/models/auth.model.ts → models/auth.model.ts.hbs} +0 -0
  86. /package/templates/auth/better-auth/server/db/prisma/mongodb/prisma/schema/{auth.prisma → auth.prisma.hbs} +0 -0
  87. /package/templates/auth/better-auth/server/db/prisma/mysql/prisma/schema/{auth.prisma → auth.prisma.hbs} +0 -0
  88. /package/templates/auth/better-auth/server/db/prisma/postgres/prisma/schema/{auth.prisma → auth.prisma.hbs} +0 -0
  89. /package/templates/auth/better-auth/server/db/prisma/sqlite/prisma/schema/{auth.prisma → auth.prisma.hbs} +0 -0
  90. /package/templates/auth/better-auth/web/nuxt/app/middleware/{auth.ts → auth.ts.hbs} +0 -0
  91. /package/templates/backend/server/{server-base → base}/_gitignore +0 -0
  92. /package/templates/db/mongoose/mongodb/src/{db/index.ts.hbs → index.ts.hbs} +0 -0
  93. /package/templates/examples/todo/server/drizzle/mysql/src/{db/schema → schema}/todo.ts +0 -0
  94. /package/templates/examples/todo/server/drizzle/postgres/src/{db/schema → schema}/todo.ts +0 -0
  95. /package/templates/examples/todo/server/drizzle/sqlite/src/{db/schema → schema}/todo.ts +0 -0
  96. /package/templates/examples/todo/server/mongoose/mongodb/src/{db/models/todo.model.ts → models/todo.model.ts.hbs} +0 -0
  97. /package/templates/examples/todo/server/prisma/mongodb/prisma/schema/{todo.prisma → todo.prisma.hbs} +0 -0
  98. /package/templates/examples/todo/server/prisma/mysql/prisma/schema/{todo.prisma → todo.prisma.hbs} +0 -0
  99. /package/templates/examples/todo/server/prisma/postgres/prisma/schema/{todo.prisma → todo.prisma.hbs} +0 -0
  100. /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.5.4",
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
@@ -2718,6 +2749,10 @@ async function setupFrontendTemplates(projectDir, context) {
2718
2749
  const apiWebBaseDir = path.join(PKG_ROOT, `templates/api/${context.api}/web/react/base`);
2719
2750
  if (await fs.pathExists(apiWebBaseDir)) await processAndCopyFiles("**/*", apiWebBaseDir, webAppDir, context);
2720
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
+ }
2721
2756
  }
2722
2757
  } else if (hasNuxtWeb) {
2723
2758
  const nuxtBaseDir = path.join(PKG_ROOT, "templates/frontend/nuxt");
@@ -2758,35 +2793,52 @@ async function setupFrontendTemplates(projectDir, context) {
2758
2793
  }
2759
2794
  }
2760
2795
  }
2761
- async function setupBackendFramework(projectDir, context) {
2762
- 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) {
2763
2821
  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
2822
  await fs.ensureDir(serverAppDir);
2773
- const serverBaseDir = path.join(PKG_ROOT, "templates/backend/server/server-base");
2823
+ const serverBaseDir = path.join(PKG_ROOT, "templates/backend/server/base");
2774
2824
  if (await fs.pathExists(serverBaseDir)) await processAndCopyFiles("**/*", serverBaseDir, serverAppDir, context);
2775
2825
  const frameworkSrcDir = path.join(PKG_ROOT, `templates/backend/server/${context.backend}`);
2776
2826
  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
2827
  }
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);
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;
2838
+ }
2839
+ await setupServerApp(projectDir, context);
2840
+ await setupApiPackage(projectDir, context);
2841
+ await setupDbPackage(projectDir, context);
2790
2842
  }
2791
2843
  async function setupAuthTemplate(projectDir, context) {
2792
2844
  if (!context.auth || context.auth === "none") return;
@@ -2866,21 +2918,21 @@ async function setupAuthTemplate(projectDir, context) {
2866
2918
  }
2867
2919
  return;
2868
2920
  }
2869
- if (serverAppDirExists && context.backend !== "convex") {
2921
+ if ((serverAppDirExists || context.backend === "self") && context.backend !== "convex") {
2922
+ const authPackageDir = path.join(projectDir, "packages/auth");
2923
+ await fs.ensureDir(authPackageDir);
2870
2924
  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
- }
2925
+ if (await fs.pathExists(authServerBaseSrc)) await processAndCopyFiles("**/*", authServerBaseSrc, authPackageDir, context);
2876
2926
  if (context.orm !== "none" && context.database !== "none") {
2927
+ const dbPackageDir = path.join(projectDir, "packages/db");
2928
+ await fs.ensureDir(dbPackageDir);
2877
2929
  const orm = context.orm;
2878
2930
  const db = context.database;
2879
2931
  let authDbSrc = "";
2880
2932
  if (orm === "drizzle") authDbSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/db/drizzle/${db}`);
2881
2933
  else if (orm === "prisma") authDbSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/db/prisma/${db}`);
2882
2934
  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);
2935
+ if (authDbSrc && await fs.pathExists(authDbSrc)) await processAndCopyFiles("**/*", authDbSrc, dbPackageDir, context);
2884
2936
  }
2885
2937
  }
2886
2938
  if ((hasReactWeb || hasNuxtWeb || hasSvelteWeb || hasSolidWeb) && webAppDirExists) {
@@ -2896,6 +2948,10 @@ async function setupAuthTemplate(projectDir, context) {
2896
2948
  if (reactFramework) {
2897
2949
  const authWebFrameworkSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/web/react/${reactFramework}`);
2898
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
+ }
2899
2955
  }
2900
2956
  } else if (hasNuxtWeb) {
2901
2957
  const authWebNuxtSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/web/nuxt`);
@@ -2926,9 +2982,11 @@ async function setupPaymentsTemplate(projectDir, context) {
2926
2982
  const webAppDir = path.join(projectDir, "apps/web");
2927
2983
  const serverAppDirExists = await fs.pathExists(serverAppDir);
2928
2984
  const webAppDirExists = await fs.pathExists(webAppDir);
2929
- if (serverAppDirExists && context.backend !== "convex") {
2985
+ if ((serverAppDirExists || context.backend === "self") && context.backend !== "convex") {
2986
+ const authPackageDir = path.join(projectDir, "packages/auth");
2987
+ await fs.ensureDir(authPackageDir);
2930
2988
  const paymentsServerSrc = path.join(PKG_ROOT, `templates/payments/${context.payments}/server/base`);
2931
- if (await fs.pathExists(paymentsServerSrc)) await processAndCopyFiles("**/*", paymentsServerSrc, serverAppDir, context);
2989
+ if (await fs.pathExists(paymentsServerSrc)) await processAndCopyFiles("**/*", paymentsServerSrc, authPackageDir, context);
2932
2990
  }
2933
2991
  const hasReactWeb = context.frontend.some((f) => [
2934
2992
  "tanstack-router",
@@ -3004,17 +3062,19 @@ async function setupExamplesTemplate(projectDir, context) {
3004
3062
  for (const example of context.examples) {
3005
3063
  if (example === "none") continue;
3006
3064
  const exampleBaseDir = path.join(PKG_ROOT, `templates/examples/${example}`);
3007
- if (serverAppDirExists && context.backend !== "convex" && context.backend !== "none") {
3065
+ if ((serverAppDirExists || context.backend === "self") && context.backend !== "convex" && context.backend !== "none") {
3008
3066
  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);
3067
+ if (context.api !== "none") {
3068
+ const apiPackageDir = path.join(projectDir, "packages/api");
3069
+ await fs.ensureDir(apiPackageDir);
3070
+ const exampleOrmBaseSrc = path.join(exampleServerSrc, context.orm, "base");
3071
+ if (await fs.pathExists(exampleOrmBaseSrc)) await processAndCopyFiles("**/*", exampleOrmBaseSrc, apiPackageDir, context, false);
3012
3072
  }
3013
3073
  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);
3074
+ const dbPackageDir = path.join(projectDir, "packages/db");
3075
+ await fs.ensureDir(dbPackageDir);
3016
3076
  const exampleDbSchemaSrc = path.join(exampleServerSrc, context.orm, context.database);
3017
- if (await fs.pathExists(exampleDbSchemaSrc)) await processAndCopyFiles("**/*", exampleDbSchemaSrc, serverAppDir, context, false);
3077
+ if (await fs.pathExists(exampleDbSchemaSrc)) await processAndCopyFiles("**/*", exampleDbSchemaSrc, dbPackageDir, context, false);
3018
3078
  }
3019
3079
  }
3020
3080
  if (webAppDirExists) {
@@ -3034,6 +3094,10 @@ async function setupExamplesTemplate(projectDir, context) {
3034
3094
  if (reactFramework) {
3035
3095
  const exampleWebFrameworkSrc = path.join(exampleWebSrc, reactFramework);
3036
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
+ }
3037
3101
  }
3038
3102
  }
3039
3103
  } else if (hasNuxtWeb) {
@@ -3081,9 +3145,9 @@ async function handleExtras(projectDir, context) {
3081
3145
  }
3082
3146
  async function setupDockerComposeTemplates(projectDir, context) {
3083
3147
  if (context.dbSetup !== "docker" || context.database === "none") return;
3084
- const serverAppDir = path.join(projectDir, "apps/server");
3148
+ const dbPackageDir = path.join(projectDir, "packages/db");
3085
3149
  const dockerSrcDir = path.join(PKG_ROOT, `templates/db-setup/docker-compose/${context.database}`);
3086
- if (await fs.pathExists(dockerSrcDir)) await processAndCopyFiles("**/*", dockerSrcDir, serverAppDir, context);
3150
+ if (await fs.pathExists(dockerSrcDir)) await processAndCopyFiles("**/*", dockerSrcDir, dbPackageDir, context);
3087
3151
  }
3088
3152
  async function setupDeploymentTemplates(projectDir, context) {
3089
3153
  if (context.webDeploy === "alchemy" || context.serverDeploy === "alchemy") if (context.webDeploy === "alchemy" && context.serverDeploy === "alchemy") {
@@ -3504,8 +3568,8 @@ async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, opti
3504
3568
  await addPackageDependency({
3505
3569
  devDependencies: [
3506
3570
  "alchemy",
3507
- "nitropack",
3508
- "dotenv"
3571
+ "dotenv",
3572
+ "@cloudflare/vite-plugin"
3509
3573
  ],
3510
3574
  projectDir: webAppDir
3511
3575
  });
@@ -3533,17 +3597,6 @@ async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, opti
3533
3597
  defaultImport: "alchemy"
3534
3598
  });
3535
3599
  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
3600
  const exportAssignment = sourceFile.getExportAssignment((d) => !d.isExportEquals());
3548
3601
  if (!exportAssignment) return;
3549
3602
  const defineConfigCall = exportAssignment.getExpression();
@@ -3551,47 +3604,11 @@ async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, opti
3551
3604
  let configObject = defineConfigCall.getArguments()[0];
3552
3605
  if (!configObject) configObject = defineConfigCall.addArgument("{}");
3553
3606
  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
3607
  const pluginsProperty = configObject.getProperty("plugins");
3564
3608
  if (pluginsProperty && Node.isPropertyAssignment(pluginsProperty)) {
3565
3609
  const initializer = pluginsProperty.getInitializer();
3566
3610
  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}()`);
3611
+ if (!initializer.getElements().some((el) => el.getText().includes("alchemy("))) initializer.addElement("alchemy()");
3595
3612
  }
3596
3613
  } else configObject.addPropertyAssignment({
3597
3614
  name: "plugins",
@@ -3602,16 +3619,6 @@ async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, opti
3602
3619
  } catch (error) {
3603
3620
  console.warn("Failed to update vite.config.ts:", error);
3604
3621
  }
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
3622
  }
3616
3623
 
3617
3624
  //#endregion
@@ -3778,7 +3785,7 @@ async function setupTanstackStartWorkersDeploy(projectDir, packageManager) {
3778
3785
  const webAppDir = path.join(projectDir, "apps/web");
3779
3786
  if (!await fs.pathExists(webAppDir)) return;
3780
3787
  await addPackageDependency({
3781
- devDependencies: ["wrangler"],
3788
+ devDependencies: ["wrangler", "@cloudflare/vite-plugin"],
3782
3789
  projectDir: webAppDir
3783
3790
  });
3784
3791
  const pkgPath = path.join(webAppDir, "package.json");
@@ -3795,6 +3802,12 @@ async function setupTanstackStartWorkersDeploy(projectDir, packageManager) {
3795
3802
  if (!await fs.pathExists(viteConfigPath)) return;
3796
3803
  const sourceFile = tsProject.addSourceFileAtPathIfExists(viteConfigPath);
3797
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" });
3798
3811
  const reactImport = sourceFile.getImportDeclaration("@vitejs/plugin-react");
3799
3812
  let reactPluginIdentifier = "viteReact";
3800
3813
  if (!reactImport) sourceFile.addImportDeclaration({
@@ -3814,10 +3827,7 @@ async function setupTanstackStartWorkersDeploy(projectDir, packageManager) {
3814
3827
  const configObj = defineCall.getArguments()[0];
3815
3828
  if (!configObj) return;
3816
3829
  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);
3830
+ if (!pluginsArray.getElements().some((el) => el.getText().includes("cloudflare("))) pluginsArray.insertElement(0, "cloudflare({ viteEnvironment: { name: 'ssr' } })");
3821
3831
  if (!pluginsArray.getElements().some((el) => Node.isCallExpression(el) && el.getExpression().getText() === reactPluginIdentifier)) {
3822
3832
  const nextIndex = pluginsArray.getElements().findIndex((el) => el.getText().includes("tanstackStart(")) + 1;
3823
3833
  if (nextIndex > 0) pluginsArray.insertElement(nextIndex, `${reactPluginIdentifier}()`);
@@ -3955,18 +3965,137 @@ async function addDeploymentToProject(input) {
3955
3965
  }
3956
3966
  }
3957
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
+
3958
4072
  //#endregion
3959
4073
  //#region src/helpers/addons/examples-setup.ts
3960
4074
  async function setupExamples(config) {
3961
- const { examples, frontend, backend, projectDir } = config;
4075
+ const { examples, frontend, backend, projectDir, orm } = config;
3962
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
+ }
3963
4092
  if (examples.includes("ai")) {
3964
4093
  const webClientDir = path.join(projectDir, "apps/web");
3965
4094
  const nativeClientDir = path.join(projectDir, "apps/native");
3966
- const serverDir = path.join(projectDir, "apps/server");
4095
+ const apiDir$1 = path.join(projectDir, "packages/api");
3967
4096
  const webClientDirExists = await fs.pathExists(webClientDir);
3968
4097
  const nativeClientDirExists = await fs.pathExists(nativeClientDir);
3969
- const serverDirExists = await fs.pathExists(serverDir);
4098
+ const apiDirExists = await fs.pathExists(apiDir$1);
3970
4099
  const hasNuxt = frontend.includes("nuxt");
3971
4100
  const hasSvelte = frontend.includes("svelte");
3972
4101
  const hasReactWeb = frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("next") || frontend.includes("tanstack-start");
@@ -3987,9 +4116,9 @@ async function setupExamples(config) {
3987
4116
  dependencies: ["ai", "@ai-sdk/react"],
3988
4117
  projectDir: nativeClientDir
3989
4118
  });
3990
- if (serverDirExists && backend !== "none") await addPackageDependency({
4119
+ if (apiDirExists && backend !== "none") await addPackageDependency({
3991
4120
  dependencies: ["ai", "@ai-sdk/google"],
3992
- projectDir: serverDir
4121
+ projectDir: apiDir$1
3993
4122
  });
3994
4123
  }
3995
4124
  }
@@ -4105,32 +4234,57 @@ function getConvexDependencies(frontend) {
4105
4234
  return deps;
4106
4235
  }
4107
4236
  async function setupApi(config) {
4108
- const { api, projectName, frontend, backend, packageManager, projectDir } = config;
4237
+ const { api, projectName, frontend, backend, packageManager, projectDir, auth } = config;
4109
4238
  const isConvex = backend === "convex";
4110
4239
  const webDir = path.join(projectDir, "apps/web");
4111
4240
  const nativeDir = path.join(projectDir, "apps/native");
4112
4241
  const serverDir = path.join(projectDir, "apps/server");
4113
4242
  const webDirExists = await fs.pathExists(webDir);
4114
4243
  const nativeDirExists = await fs.pathExists(nativeDir);
4115
- const serverDirExists = await fs.pathExists(serverDir);
4244
+ await fs.pathExists(serverDir);
4116
4245
  const frontendType = getFrontendType(frontend);
4117
4246
  if (!isConvex && api !== "none") {
4118
4247
  const apiDeps = getApiDependencies(api, frontendType);
4119
- if (serverDirExists && apiDeps.server) {
4248
+ const apiPackageDir = path.join(projectDir, "packages/api");
4249
+ if (apiDeps.server) {
4120
4250
  await addPackageDependency({
4121
4251
  dependencies: apiDeps.server.dependencies,
4122
- projectDir: serverDir
4252
+ projectDir: apiPackageDir
4253
+ });
4254
+ const frameworkDeps = [];
4255
+ if (backend === "hono") frameworkDeps.push("hono");
4256
+ else if (backend === "elysia") frameworkDeps.push("elysia");
4257
+ else if (backend === "express") frameworkDeps.push("express", "@types/express");
4258
+ else if (backend === "fastify") frameworkDeps.push("fastify");
4259
+ else if (backend === "self") {
4260
+ if (frontend.includes("next")) frameworkDeps.push("next");
4261
+ }
4262
+ if (frameworkDeps.length > 0) await addPackageDependency({
4263
+ dependencies: frameworkDeps,
4264
+ projectDir: apiPackageDir
4123
4265
  });
4124
4266
  if (api === "trpc") {
4125
4267
  if (backend === "hono") await addPackageDependency({
4126
4268
  dependencies: ["@hono/trpc-server"],
4127
- projectDir: serverDir
4269
+ projectDir: apiPackageDir
4128
4270
  });
4129
4271
  else if (backend === "elysia") await addPackageDependency({
4130
4272
  dependencies: ["@elysiajs/trpc"],
4131
- projectDir: serverDir
4273
+ projectDir: apiPackageDir
4274
+ });
4275
+ else if (backend === "express") await addPackageDependency({
4276
+ dependencies: ["@trpc/server"],
4277
+ projectDir: apiPackageDir
4278
+ });
4279
+ else if (backend === "fastify") await addPackageDependency({
4280
+ dependencies: ["@trpc/server"],
4281
+ projectDir: apiPackageDir
4132
4282
  });
4133
4283
  }
4284
+ if (auth === "better-auth") await addPackageDependency({
4285
+ dependencies: ["better-auth"],
4286
+ projectDir: apiPackageDir
4287
+ });
4134
4288
  }
4135
4289
  if (webDirExists && apiDeps.web) await addPackageDependency({
4136
4290
  dependencies: apiDeps.web.dependencies,
@@ -4174,7 +4328,7 @@ async function setupApi(config) {
4174
4328
  //#endregion
4175
4329
  //#region src/helpers/core/backend-setup.ts
4176
4330
  async function setupBackendDependencies(config) {
4177
- const { backend, runtime, api, projectDir } = config;
4331
+ const { backend, runtime, api, auth, examples, projectDir } = config;
4178
4332
  if (backend === "convex") return;
4179
4333
  const framework = backend;
4180
4334
  const serverDir = path.join(projectDir, "apps/server");
@@ -4202,6 +4356,13 @@ async function setupBackendDependencies(config) {
4202
4356
  dependencies.push("fastify", "@fastify/cors");
4203
4357
  if (runtime === "node") devDependencies.push("tsx", "@types/node");
4204
4358
  }
4359
+ if (api === "trpc") {
4360
+ if (framework === "express") dependencies.push("@trpc/server");
4361
+ else if (framework === "fastify") dependencies.push("@trpc/server");
4362
+ else if (runtime === "workers") dependencies.push("@trpc/server");
4363
+ } else if (api === "orpc") dependencies.push("@orpc/server", "@orpc/openapi", "@orpc/zod");
4364
+ if (auth === "better-auth") dependencies.push("better-auth");
4365
+ if (examples.includes("ai")) dependencies.push("ai", "@ai-sdk/google");
4205
4366
  if (runtime === "bun") devDependencies.push("@types/bun");
4206
4367
  if (dependencies.length > 0 || devDependencies.length > 0) await addPackageDependency({
4207
4368
  dependencies,
@@ -4220,7 +4381,7 @@ async function setupAuth(config) {
4220
4381
  const nativeDir = path.join(projectDir, "apps/native");
4221
4382
  const clientDirExists = await fs.pathExists(clientDir);
4222
4383
  const nativeDirExists = await fs.pathExists(nativeDir);
4223
- const serverDirExists = await fs.pathExists(serverDir);
4384
+ await fs.pathExists(serverDir);
4224
4385
  try {
4225
4386
  if (backend === "convex") {
4226
4387
  if (auth === "clerk" && clientDirExists) {
@@ -4276,9 +4437,11 @@ async function setupAuth(config) {
4276
4437
  });
4277
4438
  return;
4278
4439
  }
4279
- if (serverDirExists && auth === "better-auth") await addPackageDependency({
4440
+ const authPackageDir = path.join(projectDir, "packages/auth");
4441
+ const authPackageDirExists = await fs.pathExists(authPackageDir);
4442
+ if (authPackageDirExists && auth === "better-auth") await addPackageDependency({
4280
4443
  dependencies: ["better-auth"],
4281
- projectDir: serverDir
4444
+ projectDir: authPackageDir
4282
4445
  });
4283
4446
  if (frontend.some((f) => [
4284
4447
  "react-router",
@@ -4300,9 +4463,9 @@ async function setupAuth(config) {
4300
4463
  dependencies: ["better-auth", "@better-auth/expo"],
4301
4464
  projectDir: nativeDir
4302
4465
  });
4303
- if (serverDirExists) await addPackageDependency({
4466
+ if (authPackageDirExists) await addPackageDependency({
4304
4467
  dependencies: ["@better-auth/expo"],
4305
- projectDir: serverDir
4468
+ projectDir: authPackageDir
4306
4469
  });
4307
4470
  }
4308
4471
  }
@@ -4394,7 +4557,7 @@ async function setupEnvironmentVariables(config) {
4394
4557
  const clientVars = [{
4395
4558
  key: envVarName,
4396
4559
  value: serverUrl,
4397
- condition: true
4560
+ condition: backend !== "self"
4398
4561
  }];
4399
4562
  if (backend === "convex" && auth === "clerk") {
4400
4563
  if (hasNextJs) clientVars.push({
@@ -4485,8 +4648,6 @@ async function setupEnvironmentVariables(config) {
4485
4648
  return;
4486
4649
  }
4487
4650
  const serverDir = path.join(projectDir, "apps/server");
4488
- if (!await fs.pathExists(serverDir)) return;
4489
- const envPath = path.join(serverDir, ".env");
4490
4651
  let corsOrigin = "http://localhost:3001";
4491
4652
  if (hasReactRouter || hasSvelte) corsOrigin = "http://localhost:5173";
4492
4653
  let databaseUrl = null;
@@ -4502,15 +4663,10 @@ async function setupEnvironmentVariables(config) {
4502
4663
  break;
4503
4664
  case "sqlite":
4504
4665
  if (config.runtime === "workers") databaseUrl = "http://127.0.0.1:8080";
4505
- else databaseUrl = "file:./local.db";
4666
+ else databaseUrl = `file:${path.join(config.projectDir, "apps/server", "local.db")}`;
4506
4667
  break;
4507
4668
  }
4508
4669
  const serverVars = [
4509
- {
4510
- key: "CORS_ORIGIN",
4511
- value: corsOrigin,
4512
- condition: true
4513
- },
4514
4670
  {
4515
4671
  key: "BETTER_AUTH_SECRET",
4516
4672
  value: generateAuthSecret(),
@@ -4521,16 +4677,6 @@ async function setupEnvironmentVariables(config) {
4521
4677
  value: "http://localhost:3000",
4522
4678
  condition: !!auth
4523
4679
  },
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
4680
  {
4535
4681
  key: "POLAR_ACCESS_TOKEN",
4536
4682
  value: "",
@@ -4540,9 +4686,27 @@ async function setupEnvironmentVariables(config) {
4540
4686
  key: "POLAR_SUCCESS_URL",
4541
4687
  value: `${corsOrigin}/success?checkout_id={CHECKOUT_ID}`,
4542
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"
4543
4704
  }
4544
4705
  ];
4545
- await addEnvVariablesToFile(envPath, serverVars);
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);
4546
4710
  const isUnifiedAlchemy = webDeploy === "alchemy" && serverDeploy === "alchemy";
4547
4711
  const isIndividualAlchemy = webDeploy === "alchemy" || serverDeploy === "alchemy";
4548
4712
  if (isUnifiedAlchemy) {
@@ -4562,12 +4726,15 @@ async function setupEnvironmentVariables(config) {
4562
4726
  }]);
4563
4727
  }
4564
4728
  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"), [{
4729
+ const serverAlchemyVars = [{
4567
4730
  key: "ALCHEMY_PASSWORD",
4568
4731
  value: "please-change-this",
4569
4732
  condition: true
4570
- }]);
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);
4571
4738
  }
4572
4739
  }
4573
4740
  }
@@ -4603,7 +4770,7 @@ async function setupCloudflareD1(config) {
4603
4770
  const envPath = path.join(projectDir, "apps/server", ".env");
4604
4771
  const variables = [{
4605
4772
  key: "DATABASE_URL",
4606
- value: "file:./local.db",
4773
+ value: `file:${path.join(projectDir, "apps/server", "local.db")}`,
4607
4774
  condition: true
4608
4775
  }];
4609
4776
  try {
@@ -5133,9 +5300,9 @@ async function writeEnvFile$1(projectDir, config) {
5133
5300
  }
5134
5301
  async function addDotenvImportToPrismaConfig(projectDir) {
5135
5302
  try {
5136
- const prismaConfigPath = path.join(projectDir, "apps/server/prisma.config.ts");
5303
+ const prismaConfigPath = path.join(projectDir, "packages/db/prisma.config.ts");
5137
5304
  let content = await fs.readFile(prismaConfigPath, "utf8");
5138
- content = `import "dotenv/config";\n${content}`;
5305
+ content = `import dotenv from "dotenv";\ndotenv.config({ path: "../../apps/server/.env" });\n${content}`;
5139
5306
  await fs.writeFile(prismaConfigPath, content);
5140
5307
  } catch (_error) {
5141
5308
  consola$1.error("Failed to update prisma.config.ts");
@@ -5151,11 +5318,12 @@ function displayManualSetupInstructions$1() {
5151
5318
 
5152
5319
  DATABASE_URL="your_database_url"`);
5153
5320
  }
5154
- async function addPrismaAccelerateExtension(serverDir) {
5321
+ async function addPrismaAccelerateExtension(projectDir) {
5155
5322
  try {
5323
+ const dbPackageDir = path.join(projectDir, "packages/db");
5156
5324
  await addPackageDependency({
5157
5325
  dependencies: ["@prisma/extension-accelerate"],
5158
- projectDir: serverDir
5326
+ projectDir: dbPackageDir
5159
5327
  });
5160
5328
  return true;
5161
5329
  } catch (_error) {
@@ -5216,7 +5384,7 @@ async function setupPrismaPostgres(config, cliInput) {
5216
5384
  await writeEnvFile$1(projectDir, prismaConfig);
5217
5385
  if (orm === "prisma") {
5218
5386
  await addDotenvImportToPrismaConfig(projectDir);
5219
- await addPrismaAccelerateExtension(serverDir);
5387
+ await addPrismaAccelerateExtension(projectDir);
5220
5388
  }
5221
5389
  const connectionType = orm === "drizzle" ? "direct connection" : "Prisma Accelerate";
5222
5390
  log.success(pc.green(`Prisma Postgres database configured successfully with ${connectionType}!`));
@@ -5316,18 +5484,18 @@ function displayManualSupabaseInstructions(output) {
5316
5484
  log.info(`"Manual Supabase Setup Instructions:"
5317
5485
  1. Ensure Docker is installed and running.
5318
5486
  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.
5487
+ 3. Run \`supabase init\` in your project's \`packages/db\` directory.
5488
+ 4. Run \`supabase start\` in your project's \`packages/db\` directory.
5321
5489
  5. Copy the 'DB URL' from the output.${output ? `
5322
5490
  ${pc.bold("Relevant output from `supabase start`:")}
5323
5491
  ${pc.dim(output)}` : ""}
5324
- 6. Add the DB URL to the .env file in \`apps/server/.env\` as \`DATABASE_URL\`:
5492
+ 6. Add the DB URL to the .env file in \`packages/db/.env\` as \`DATABASE_URL\`:
5325
5493
  ${pc.gray("DATABASE_URL=\"your_supabase_db_url\"")}`);
5326
5494
  }
5327
5495
  async function setupSupabase(config, cliInput) {
5328
5496
  const { projectDir, packageManager } = config;
5329
5497
  const manualDb = cliInput?.manualDb ?? false;
5330
- const serverDir = path.join(projectDir, "apps", "server");
5498
+ const serverDir = path.join(projectDir, "packages", "db");
5331
5499
  try {
5332
5500
  await fs.ensureDir(serverDir);
5333
5501
  if (manualDb) {
@@ -5610,15 +5778,15 @@ async function setupDatabase(config, cliInput) {
5610
5778
  const { database, orm, dbSetup, backend, projectDir } = config;
5611
5779
  if (backend === "convex" || database === "none") {
5612
5780
  if (backend !== "convex") {
5613
- const serverDir$1 = path.join(projectDir, "apps/server");
5614
- const serverDbDir = path.join(serverDir$1, "src/db");
5781
+ const serverDir = path.join(projectDir, "apps/server");
5782
+ const serverDbDir = path.join(serverDir, "src/db");
5615
5783
  if (await fs.pathExists(serverDbDir)) await fs.remove(serverDbDir);
5616
5784
  }
5617
5785
  return;
5618
5786
  }
5619
5787
  const s = spinner();
5620
- const serverDir = path.join(projectDir, "apps/server");
5621
- if (!await fs.pathExists(serverDir)) return;
5788
+ const dbPackageDir = path.join(projectDir, "packages/db");
5789
+ if (!await fs.pathExists(dbPackageDir)) return;
5622
5790
  try {
5623
5791
  if (orm === "prisma") if (database === "mysql" && dbSetup === "planetscale") await addPackageDependency({
5624
5792
  dependencies: [
@@ -5627,23 +5795,27 @@ async function setupDatabase(config, cliInput) {
5627
5795
  "@planetscale/database"
5628
5796
  ],
5629
5797
  devDependencies: ["prisma"],
5630
- projectDir: serverDir
5798
+ projectDir: dbPackageDir
5631
5799
  });
5632
5800
  else if (database === "sqlite" && dbSetup === "turso") await addPackageDependency({
5633
5801
  dependencies: ["@prisma/client", "@prisma/adapter-libsql"],
5634
5802
  devDependencies: ["prisma"],
5635
- projectDir: serverDir
5803
+ projectDir: dbPackageDir
5636
5804
  });
5637
5805
  else await addPackageDependency({
5638
5806
  dependencies: ["@prisma/client"],
5639
5807
  devDependencies: ["prisma"],
5640
- projectDir: serverDir
5808
+ projectDir: dbPackageDir
5641
5809
  });
5642
5810
  else if (orm === "drizzle") {
5643
5811
  if (database === "sqlite") await addPackageDependency({
5644
- dependencies: ["drizzle-orm", "@libsql/client"],
5812
+ dependencies: [
5813
+ "drizzle-orm",
5814
+ "@libsql/client",
5815
+ "libsql"
5816
+ ],
5645
5817
  devDependencies: ["drizzle-kit"],
5646
- projectDir: serverDir
5818
+ projectDir: dbPackageDir
5647
5819
  });
5648
5820
  else if (database === "postgres") if (dbSetup === "neon") await addPackageDependency({
5649
5821
  dependencies: [
@@ -5652,32 +5824,32 @@ async function setupDatabase(config, cliInput) {
5652
5824
  "ws"
5653
5825
  ],
5654
5826
  devDependencies: ["drizzle-kit", "@types/ws"],
5655
- projectDir: serverDir
5827
+ projectDir: dbPackageDir
5656
5828
  });
5657
5829
  else if (dbSetup === "planetscale") await addPackageDependency({
5658
5830
  dependencies: ["drizzle-orm", "pg"],
5659
5831
  devDependencies: ["drizzle-kit", "@types/pg"],
5660
- projectDir: serverDir
5832
+ projectDir: dbPackageDir
5661
5833
  });
5662
5834
  else await addPackageDependency({
5663
5835
  dependencies: ["drizzle-orm", "pg"],
5664
5836
  devDependencies: ["drizzle-kit", "@types/pg"],
5665
- projectDir: serverDir
5837
+ projectDir: dbPackageDir
5666
5838
  });
5667
5839
  else if (database === "mysql") if (dbSetup === "planetscale") await addPackageDependency({
5668
5840
  dependencies: ["drizzle-orm", "@planetscale/database"],
5669
5841
  devDependencies: ["drizzle-kit"],
5670
- projectDir: serverDir
5842
+ projectDir: dbPackageDir
5671
5843
  });
5672
5844
  else await addPackageDependency({
5673
5845
  dependencies: ["drizzle-orm", "mysql2"],
5674
5846
  devDependencies: ["drizzle-kit"],
5675
- projectDir: serverDir
5847
+ projectDir: dbPackageDir
5676
5848
  });
5677
5849
  } else if (orm === "mongoose") await addPackageDependency({
5678
5850
  dependencies: ["mongoose"],
5679
5851
  devDependencies: [],
5680
- projectDir: serverDir
5852
+ projectDir: dbPackageDir
5681
5853
  });
5682
5854
  if (dbSetup === "docker") await setupDockerCompose(config);
5683
5855
  else if (database === "sqlite" && dbSetup === "turso") await setupTurso(config, cliInput);
@@ -5700,7 +5872,7 @@ async function setupDatabase(config, cliInput) {
5700
5872
  //#region src/helpers/core/runtime-setup.ts
5701
5873
  async function setupRuntime(config) {
5702
5874
  const { runtime, backend, projectDir } = config;
5703
- if (backend === "convex" || backend === "next" || runtime === "none") return;
5875
+ if (backend === "convex" || backend === "self" || runtime === "none") return;
5704
5876
  const serverDir = path.join(projectDir, "apps/server");
5705
5877
  if (!await fs.pathExists(serverDir)) return;
5706
5878
  if (runtime === "bun") await setupBunRuntime(serverDir, backend);
@@ -6220,7 +6392,7 @@ async function displayPostInstallInstructions(config) {
6220
6392
  else if (!hasNative && !addons?.includes("starlight")) output += `${pc.yellow("NOTE:")} You are creating a backend-only app\n (no frontend selected)\n`;
6221
6393
  if (!isConvex) {
6222
6394
  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`;
6395
+ if (api === "orpc") if (backend === "self") output += `${pc.cyan("•")} OpenAPI (Scalar UI): http://localhost:3000/rpc/api\n`;
6224
6396
  else output += `${pc.cyan("•")} OpenAPI (Scalar UI): http://localhost:3000/api\n`;
6225
6397
  }
6226
6398
  if (addons?.includes("starlight")) output += `${pc.cyan("•")} Docs: http://localhost:4321\n`;
@@ -6334,12 +6506,79 @@ function getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy) {
6334
6506
  return instructions.length ? `\n${instructions.join("\n")}` : "";
6335
6507
  }
6336
6508
 
6509
+ //#endregion
6510
+ //#region src/helpers/core/workspace-setup.ts
6511
+ async function setupWorkspaceDependencies(projectDir, options) {
6512
+ const projectName = options.projectName;
6513
+ const workspaceVersion = options.packageManager === "npm" ? "*" : "workspace:*";
6514
+ const commonDeps = ["dotenv", "zod"];
6515
+ const commonDevDeps = ["tsdown"];
6516
+ const dbPackageDir = path.join(projectDir, "packages/db");
6517
+ if (await fs.pathExists(dbPackageDir)) await addPackageDependency({
6518
+ dependencies: commonDeps,
6519
+ devDependencies: commonDevDeps,
6520
+ projectDir: dbPackageDir
6521
+ });
6522
+ const authPackageDir = path.join(projectDir, "packages/auth");
6523
+ if (await fs.pathExists(authPackageDir)) await addPackageDependency({
6524
+ dependencies: commonDeps,
6525
+ devDependencies: commonDevDeps,
6526
+ customDependencies: { [`@${projectName}/db`]: workspaceVersion },
6527
+ projectDir: authPackageDir
6528
+ });
6529
+ const apiPackageDir = path.join(projectDir, "packages/api");
6530
+ if (await fs.pathExists(apiPackageDir)) await addPackageDependency({
6531
+ dependencies: commonDeps,
6532
+ devDependencies: commonDevDeps,
6533
+ customDependencies: {
6534
+ [`@${projectName}/auth`]: workspaceVersion,
6535
+ [`@${projectName}/db`]: workspaceVersion
6536
+ },
6537
+ projectDir: apiPackageDir
6538
+ });
6539
+ const serverPackageDir = path.join(projectDir, "apps/server");
6540
+ if (await fs.pathExists(serverPackageDir)) await addPackageDependency({
6541
+ dependencies: commonDeps,
6542
+ devDependencies: commonDevDeps,
6543
+ customDependencies: {
6544
+ [`@${projectName}/api`]: workspaceVersion,
6545
+ [`@${projectName}/auth`]: workspaceVersion,
6546
+ [`@${projectName}/db`]: workspaceVersion
6547
+ },
6548
+ projectDir: serverPackageDir
6549
+ });
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,
6561
+ projectDir: webPackageDir
6562
+ });
6563
+ }
6564
+ }
6565
+
6337
6566
  //#endregion
6338
6567
  //#region src/helpers/core/project-config.ts
6339
6568
  async function updatePackageConfigurations(projectDir, options) {
6340
6569
  await updateRootPackageJson(projectDir, options);
6341
- if (options.backend !== "convex") await updateServerPackageJson(projectDir, options);
6342
- else await updateConvexPackageJson(projectDir, options);
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") {
6577
+ await updateServerPackageJson(projectDir, options);
6578
+ await updateAuthPackageJson(projectDir, options);
6579
+ await updateApiPackageJson(projectDir, options);
6580
+ await setupWorkspaceDependencies(projectDir, options);
6581
+ }
6343
6582
  }
6344
6583
  async function updateRootPackageJson(projectDir, options) {
6345
6584
  const rootPackageJsonPath = path.join(projectDir, "package.json");
@@ -6349,6 +6588,7 @@ async function updateRootPackageJson(projectDir, options) {
6349
6588
  if (!packageJson.scripts) packageJson.scripts = {};
6350
6589
  const scripts = packageJson.scripts;
6351
6590
  const backendPackageName = options.backend === "convex" ? `@${options.projectName}/backend` : "server";
6591
+ const dbPackageName = `@${options.projectName}/db`;
6352
6592
  let serverDevScript = "";
6353
6593
  if (options.addons.includes("turborepo")) serverDevScript = `turbo -F ${backendPackageName} dev`;
6354
6594
  else if (options.packageManager === "bun") serverDevScript = `bun run --filter ${backendPackageName} dev`;
@@ -6365,17 +6605,17 @@ async function updateRootPackageJson(projectDir, options) {
6365
6605
  scripts["check-types"] = "turbo check-types";
6366
6606
  scripts["dev:native"] = "turbo -F native dev";
6367
6607
  scripts["dev:web"] = "turbo -F web dev";
6368
- scripts["dev:server"] = serverDevScript;
6608
+ if (options.backend !== "self" && options.backend !== "none") scripts["dev:server"] = serverDevScript;
6369
6609
  if (options.backend === "convex") scripts["dev:setup"] = `turbo -F ${backendPackageName} dev:setup`;
6370
6610
  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`;
6611
+ scripts["db:push"] = `turbo -F ${dbPackageName} db:push`;
6612
+ if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `turbo -F ${dbPackageName} db:studio`;
6373
6613
  if (options.orm === "prisma") {
6374
- scripts["db:generate"] = `turbo -F ${backendPackageName} db:generate`;
6375
- scripts["db:migrate"] = `turbo -F ${backendPackageName} db:migrate`;
6614
+ scripts["db:generate"] = `turbo -F ${dbPackageName} db:generate`;
6615
+ scripts["db:migrate"] = `turbo -F ${dbPackageName} db:migrate`;
6376
6616
  } 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`;
6617
+ scripts["db:generate"] = `turbo -F ${dbPackageName} db:generate`;
6618
+ if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `turbo -F ${dbPackageName} db:migrate`;
6379
6619
  }
6380
6620
  }
6381
6621
  if (options.dbSetup === "docker") {
@@ -6390,17 +6630,17 @@ async function updateRootPackageJson(projectDir, options) {
6390
6630
  scripts["check-types"] = "pnpm -r check-types";
6391
6631
  scripts["dev:native"] = "pnpm --filter native dev";
6392
6632
  scripts["dev:web"] = "pnpm --filter web dev";
6393
- scripts["dev:server"] = serverDevScript;
6633
+ if (options.backend !== "self" && options.backend !== "none") scripts["dev:server"] = serverDevScript;
6394
6634
  if (options.backend === "convex") scripts["dev:setup"] = `pnpm --filter ${backendPackageName} dev:setup`;
6395
6635
  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`;
6636
+ scripts["db:push"] = `pnpm --filter ${dbPackageName} db:push`;
6637
+ if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `pnpm --filter ${dbPackageName} db:studio`;
6398
6638
  if (options.orm === "prisma") {
6399
- scripts["db:generate"] = `pnpm --filter ${backendPackageName} db:generate`;
6400
- scripts["db:migrate"] = `pnpm --filter ${backendPackageName} db:migrate`;
6639
+ scripts["db:generate"] = `pnpm --filter ${dbPackageName} db:generate`;
6640
+ scripts["db:migrate"] = `pnpm --filter ${dbPackageName} db:migrate`;
6401
6641
  } 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`;
6642
+ scripts["db:generate"] = `pnpm --filter ${dbPackageName} db:generate`;
6643
+ if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `pnpm --filter ${dbPackageName} db:migrate`;
6404
6644
  }
6405
6645
  }
6406
6646
  if (options.dbSetup === "docker") {
@@ -6415,17 +6655,17 @@ async function updateRootPackageJson(projectDir, options) {
6415
6655
  scripts["check-types"] = "npm run check-types --workspaces";
6416
6656
  scripts["dev:native"] = "npm run dev --workspace native";
6417
6657
  scripts["dev:web"] = "npm run dev --workspace web";
6418
- scripts["dev:server"] = serverDevScript;
6658
+ if (options.backend !== "self" && options.backend !== "none") scripts["dev:server"] = serverDevScript;
6419
6659
  if (options.backend === "convex") scripts["dev:setup"] = `npm run dev:setup --workspace ${backendPackageName}`;
6420
6660
  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}`;
6661
+ scripts["db:push"] = `npm run db:push --workspace ${dbPackageName}`;
6662
+ if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `npm run db:studio --workspace ${dbPackageName}`;
6423
6663
  if (options.orm === "prisma") {
6424
- scripts["db:generate"] = `npm run db:generate --workspace ${backendPackageName}`;
6425
- scripts["db:migrate"] = `npm run db:migrate --workspace ${backendPackageName}`;
6664
+ scripts["db:generate"] = `npm run db:generate --workspace ${dbPackageName}`;
6665
+ scripts["db:migrate"] = `npm run db:migrate --workspace ${dbPackageName}`;
6426
6666
  } 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}`;
6667
+ scripts["db:generate"] = `npm run db:generate --workspace ${dbPackageName}`;
6668
+ if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `npm run db:migrate --workspace ${dbPackageName}`;
6429
6669
  }
6430
6670
  }
6431
6671
  if (options.dbSetup === "docker") {
@@ -6440,17 +6680,17 @@ async function updateRootPackageJson(projectDir, options) {
6440
6680
  scripts["check-types"] = "bun run --filter '*' check-types";
6441
6681
  scripts["dev:native"] = "bun run --filter native dev";
6442
6682
  scripts["dev:web"] = "bun run --filter web dev";
6443
- scripts["dev:server"] = serverDevScript;
6683
+ if (options.backend !== "self" && options.backend !== "none") scripts["dev:server"] = serverDevScript;
6444
6684
  if (options.backend === "convex") scripts["dev:setup"] = `bun run --filter ${backendPackageName} dev:setup`;
6445
6685
  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`;
6686
+ scripts["db:push"] = `bun run --filter ${dbPackageName} db:push`;
6687
+ if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `bun run --filter ${dbPackageName} db:studio`;
6448
6688
  if (options.orm === "prisma") {
6449
- scripts["db:generate"] = `bun run --filter ${backendPackageName} db:generate`;
6450
- scripts["db:migrate"] = `bun run --filter ${backendPackageName} db:migrate`;
6689
+ scripts["db:generate"] = `bun run --filter ${dbPackageName} db:generate`;
6690
+ scripts["db:migrate"] = `bun run --filter ${dbPackageName} db:migrate`;
6451
6691
  } 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`;
6692
+ scripts["db:generate"] = `bun run --filter ${dbPackageName} db:generate`;
6693
+ if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `bun run --filter ${dbPackageName} db:migrate`;
6454
6694
  }
6455
6695
  }
6456
6696
  if (options.dbSetup === "docker") {
@@ -6483,6 +6723,22 @@ async function updateServerPackageJson(projectDir, options) {
6483
6723
  const serverPackageJson = await fs.readJson(serverPackageJsonPath);
6484
6724
  if (!serverPackageJson.scripts) serverPackageJson.scripts = {};
6485
6725
  const scripts = serverPackageJson.scripts;
6726
+ if (options.dbSetup === "docker") {
6727
+ scripts["db:start"] = "docker compose up -d";
6728
+ scripts["db:watch"] = "docker compose up";
6729
+ scripts["db:stop"] = "docker compose stop";
6730
+ scripts["db:down"] = "docker compose down";
6731
+ }
6732
+ await fs.writeJson(serverPackageJsonPath, serverPackageJson, { spaces: 2 });
6733
+ await updateDbPackageJson(projectDir, options);
6734
+ }
6735
+ async function updateDbPackageJson(projectDir, options) {
6736
+ const dbPackageJsonPath = path.join(projectDir, "packages/db/package.json");
6737
+ if (!await fs.pathExists(dbPackageJsonPath)) return;
6738
+ const dbPackageJson = await fs.readJson(dbPackageJsonPath);
6739
+ dbPackageJson.name = `@${options.projectName}/db`;
6740
+ if (!dbPackageJson.scripts) dbPackageJson.scripts = {};
6741
+ const scripts = dbPackageJson.scripts;
6486
6742
  if (options.database !== "none") {
6487
6743
  if (options.database === "sqlite" && options.orm === "drizzle" && options.dbSetup !== "d1") scripts["db:local"] = "turso dev --db-file local.db";
6488
6744
  if (options.orm === "prisma") {
@@ -6497,13 +6753,21 @@ async function updateServerPackageJson(projectDir, options) {
6497
6753
  if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = "drizzle-kit migrate";
6498
6754
  }
6499
6755
  }
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 });
6756
+ await fs.writeJson(dbPackageJsonPath, dbPackageJson, { spaces: 2 });
6757
+ }
6758
+ async function updateAuthPackageJson(projectDir, options) {
6759
+ const authPackageJsonPath = path.join(projectDir, "packages/auth/package.json");
6760
+ if (!await fs.pathExists(authPackageJsonPath)) return;
6761
+ const authPackageJson = await fs.readJson(authPackageJsonPath);
6762
+ authPackageJson.name = `@${options.projectName}/auth`;
6763
+ await fs.writeJson(authPackageJsonPath, authPackageJson, { spaces: 2 });
6764
+ }
6765
+ async function updateApiPackageJson(projectDir, options) {
6766
+ const apiPackageJsonPath = path.join(projectDir, "packages/api/package.json");
6767
+ if (!await fs.pathExists(apiPackageJsonPath)) return;
6768
+ const apiPackageJson = await fs.readJson(apiPackageJsonPath);
6769
+ apiPackageJson.name = `@${options.projectName}/api`;
6770
+ await fs.writeJson(apiPackageJsonPath, apiPackageJson, { spaces: 2 });
6507
6771
  }
6508
6772
  async function updateConvexPackageJson(projectDir, options) {
6509
6773
  const convexPackageJsonPath = path.join(projectDir, "packages/backend/package.json");
@@ -6519,15 +6783,14 @@ async function updateConvexPackageJson(projectDir, options) {
6519
6783
  async function createProject(options, cliInput) {
6520
6784
  const projectDir = options.projectDir;
6521
6785
  const isConvex = options.backend === "convex";
6786
+ const isSelfBackend = options.backend === "self";
6787
+ const needsServerSetup = !isConvex && !isSelfBackend;
6522
6788
  try {
6523
6789
  await fs.ensureDir(projectDir);
6524
6790
  await copyBaseTemplate(projectDir, options);
6525
6791
  await setupFrontendTemplates(projectDir, options);
6526
6792
  await setupBackendFramework(projectDir, options);
6527
- if (!isConvex) {
6528
- await setupDbOrmTemplates(projectDir, options);
6529
- await setupDockerComposeTemplates(projectDir, options);
6530
- }
6793
+ if (needsServerSetup) await setupDockerComposeTemplates(projectDir, options);
6531
6794
  await setupAuthTemplate(projectDir, options);
6532
6795
  if (options.payments && options.payments !== "none") await setupPaymentsTemplate(projectDir, options);
6533
6796
  if (options.examples.length > 0 && options.examples[0] !== "none") await setupExamplesTemplate(projectDir, options);
@@ -6535,9 +6798,11 @@ async function createProject(options, cliInput) {
6535
6798
  await setupDeploymentTemplates(projectDir, options);
6536
6799
  await setupApi(options);
6537
6800
  if (!isConvex) {
6538
- await setupBackendDependencies(options);
6801
+ if (needsServerSetup) {
6802
+ await setupBackendDependencies(options);
6803
+ await setupRuntime(options);
6804
+ }
6539
6805
  await setupDatabase(options, cliInput);
6540
- await setupRuntime(options);
6541
6806
  if (options.examples.length > 0 && options.examples[0] !== "none") await setupExamples(options);
6542
6807
  }
6543
6808
  if (options.addons.length > 0 && options.addons[0] !== "none") await setupAddons(options);
@@ -6546,6 +6811,7 @@ async function createProject(options, cliInput) {
6546
6811
  await handleExtras(projectDir, options);
6547
6812
  await setupEnvironmentVariables(options);
6548
6813
  await updatePackageConfigurations(projectDir, options);
6814
+ await setupCatalogs(projectDir, options);
6549
6815
  await setupWebDeploy(options);
6550
6816
  await setupServerDeploy(options);
6551
6817
  await createReadme(projectDir, options);