create-better-t-stack 2.48.3 → 2.49.1-canary.206d95c1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/cli.d.ts +1 -2
  2. package/dist/cli.js +1 -1
  3. package/dist/index.d.ts +228 -101
  4. package/dist/index.js +1 -1
  5. package/dist/{src-BwIapwZk.js → src-CRbLnM5h.js} +557 -414
  6. package/package.json +10 -8
  7. package/templates/api/orpc/server/base/_gitignore +34 -0
  8. package/templates/api/orpc/server/base/package.json.hbs +24 -0
  9. package/templates/{backend/server/server-base → api/orpc/server/base}/src/routers/index.ts.hbs +2 -2
  10. package/templates/api/orpc/server/base/tsconfig.json.hbs +10 -0
  11. package/templates/api/orpc/server/base/tsdown.config.ts.hbs +7 -0
  12. package/templates/api/orpc/server/{base/src/lib → rest/src}/context.ts.hbs +5 -5
  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 +1 -1
  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/server/base/_gitignore +34 -0
  18. package/templates/api/trpc/server/base/package.json.hbs +23 -0
  19. package/templates/api/trpc/server/base/src/routers/index.ts.hbs +55 -0
  20. package/templates/api/trpc/server/base/tsconfig.json.hbs +13 -0
  21. package/templates/api/trpc/server/base/tsdown.config.ts.hbs +7 -0
  22. package/templates/api/trpc/server/{base/src/lib → rest/src}/context.ts.hbs +5 -5
  23. package/templates/api/trpc/web/react/base/src/utils/trpc.ts.hbs +2 -2
  24. package/templates/auth/better-auth/server/base/_gitignore +34 -0
  25. package/templates/auth/better-auth/server/base/package.json.hbs +24 -0
  26. package/templates/auth/better-auth/server/base/src/{lib/auth.ts.hbs → index.ts.hbs} +6 -6
  27. package/templates/auth/better-auth/server/base/tsconfig.json.hbs +13 -0
  28. package/templates/auth/better-auth/server/base/tsdown.config.ts.hbs +7 -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 +5 -5
  33. package/templates/backend/server/express/src/index.ts.hbs +5 -5
  34. package/templates/backend/server/fastify/src/index.ts.hbs +5 -5
  35. package/templates/backend/server/hono/src/index.ts.hbs +5 -5
  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/postgres/drizzle.config.ts.hbs +7 -2
  45. package/templates/db/drizzle/sqlite/drizzle.config.ts.hbs +7 -2
  46. package/templates/db/prisma/mongodb/prisma.config.ts.hbs +5 -1
  47. package/templates/db/prisma/mongodb/src/index.ts.hbs +5 -0
  48. package/templates/db/prisma/mysql/prisma.config.ts.hbs +5 -1
  49. package/templates/db/prisma/mysql/src/{db/index.ts.hbs → index.ts.hbs} +1 -1
  50. package/templates/db/prisma/postgres/prisma.config.ts.hbs +7 -3
  51. package/templates/db/prisma/postgres/src/{db/index.ts.hbs → index.ts.hbs} +1 -1
  52. package/templates/db/prisma/sqlite/prisma.config.ts.hbs +5 -1
  53. package/templates/db/prisma/sqlite/src/{db/index.ts.hbs → index.ts.hbs} +3 -3
  54. package/templates/examples/todo/server/drizzle/base/src/routers/todo.ts.hbs +6 -6
  55. package/templates/examples/todo/server/mongoose/base/src/routers/todo.ts.hbs +4 -4
  56. package/templates/examples/todo/server/prisma/base/src/routers/todo.ts.hbs +4 -4
  57. package/templates/frontend/react/tanstack-router/src/routes/__root.tsx.hbs +1 -1
  58. package/templates/frontend/react/tanstack-start/src/routes/__root.tsx.hbs +1 -1
  59. package/templates/db/prisma/mongodb/src/db/index.ts.hbs +0 -5
  60. /package/templates/api/orpc/server/{base/src/lib/orpc.ts.hbs → rest/src/index.ts.hbs} +0 -0
  61. /package/templates/api/trpc/server/{base/src/lib/trpc.ts.hbs → rest/src/index.ts.hbs} +0 -0
  62. /package/templates/auth/better-auth/server/db/drizzle/mysql/src/{db/schema → schema}/auth.ts +0 -0
  63. /package/templates/auth/better-auth/server/db/drizzle/postgres/src/{db/schema → schema}/auth.ts +0 -0
  64. /package/templates/auth/better-auth/server/db/drizzle/sqlite/src/{db/schema → schema}/auth.ts +0 -0
  65. /package/templates/auth/better-auth/server/db/mongoose/mongodb/src/{db/models → models}/auth.model.ts +0 -0
  66. /package/templates/backend/server/{server-base → base}/_gitignore +0 -0
  67. /package/templates/db/drizzle/mysql/src/{db/index.ts.hbs → index.ts.hbs} +0 -0
  68. /package/templates/db/drizzle/postgres/src/{db/index.ts.hbs → index.ts.hbs} +0 -0
  69. /package/templates/db/drizzle/sqlite/src/{db/index.ts.hbs → index.ts.hbs} +0 -0
  70. /package/templates/db/mongoose/mongodb/src/{db/index.ts.hbs → index.ts.hbs} +0 -0
  71. /package/templates/examples/todo/server/mongoose/mongodb/src/db/models/{todo.model.ts → todo.model.ts.hbs} +0 -0
  72. /package/templates/examples/todo/server/prisma/mongodb/prisma/schema/{todo.prisma → todo.prisma.hbs} +0 -0
  73. /package/templates/examples/todo/server/prisma/mysql/prisma/schema/{todo.prisma → todo.prisma.hbs} +0 -0
  74. /package/templates/examples/todo/server/prisma/postgres/prisma/schema/{todo.prisma → todo.prisma.hbs} +0 -0
  75. /package/templates/examples/todo/server/prisma/sqlite/prisma/schema/{todo.prisma → todo.prisma.hbs} +0 -0
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { autocompleteMultiselect, cancel, confirm, group, groupMultiselect, intro, isCancel, log, multiselect, outro, select, spinner, text } from "@clack/prompts";
3
+ import { createRouterClient, os } from "@orpc/server";
3
4
  import pc from "picocolors";
4
- import { createCli, trpcServer } from "trpc-cli";
5
- import z from "zod";
5
+ import { createCli } from "trpc-cli";
6
+ import z$1, { z } from "zod";
6
7
  import path from "node:path";
7
8
  import consola, { consola as consola$1 } from "consola";
8
9
  import fs from "fs-extra";
@@ -14,7 +15,8 @@ import { IndentationText, Node, Project, QuoteKind, SyntaxKind } from "ts-morph"
14
15
  import { glob } from "tinyglobby";
15
16
  import handlebars from "handlebars";
16
17
  import { Biome } from "@biomejs/js-api/nodejs";
17
- import os from "node:os";
18
+ import yaml from "yaml";
19
+ import os$1 from "node:os";
18
20
 
19
21
  //#region src/utils/get-package-manager.ts
20
22
  const getUserPkgManager = () => {
@@ -70,6 +72,7 @@ const dependencyVersionMap = {
70
72
  "drizzle-kit": "^0.31.2",
71
73
  "@planetscale/database": "^1.19.0",
72
74
  "@libsql/client": "^0.15.9",
75
+ libsql: "^0.5.22",
73
76
  "@neondatabase/serverless": "^1.0.1",
74
77
  pg: "^8.14.1",
75
78
  "@types/pg": "^8.11.11",
@@ -145,7 +148,9 @@ const dependencyVersionMap = {
145
148
  "@cloudflare/workers-types": "^4.20250822.0",
146
149
  alchemy: "^0.67.0",
147
150
  nitropack: "^2.12.4",
148
- dotenv: "^17.2.1",
151
+ dotenv: "^17.2.2",
152
+ tsdown: "^0.15.5",
153
+ zod: "^4.1.11",
149
154
  "@polar-sh/better-auth": "^1.1.3",
150
155
  "@polar-sh/sdk": "^0.34.16"
151
156
  };
@@ -263,7 +268,7 @@ const AuthSchema = z.enum([
263
268
  ]).describe("Authentication provider");
264
269
  const PaymentsSchema = z.enum(["polar", "none"]).describe("Payments provider");
265
270
  const ProjectNameSchema = z.string().min(1, "Project name cannot be empty").max(255, "Project name must be less than 255 characters").refine((name) => name === "." || !name.startsWith("."), "Project name cannot start with a dot (except for '.')").refine((name) => name === "." || !name.startsWith("-"), "Project name cannot start with a dash").refine((name) => {
266
- const invalidChars = [
271
+ return ![
267
272
  "<",
268
273
  ">",
269
274
  ":",
@@ -271,8 +276,7 @@ const ProjectNameSchema = z.string().min(1, "Project name cannot be empty").max(
271
276
  "|",
272
277
  "?",
273
278
  "*"
274
- ];
275
- return !invalidChars.some((char) => name.includes(char));
279
+ ].some((char) => name.includes(char));
276
280
  }, "Project name contains invalid characters").refine((name) => name.toLowerCase() !== "node_modules", "Project name is reserved").describe("Project name or path");
277
281
  const WebDeploySchema = z.enum([
278
282
  "wrangler",
@@ -360,12 +364,11 @@ function validateApiFrontendCompatibility(api, frontends = []) {
360
364
  function isFrontendAllowedWithBackend(frontend, backend, auth) {
361
365
  if (backend === "convex" && frontend === "solid") return false;
362
366
  if (auth === "clerk" && backend === "convex") {
363
- const incompatibleFrontends = [
367
+ if ([
364
368
  "nuxt",
365
369
  "svelte",
366
370
  "solid"
367
- ];
368
- if (incompatibleFrontends.includes(frontend)) return false;
371
+ ].includes(frontend)) return false;
369
372
  }
370
373
  return true;
371
374
  }
@@ -385,8 +388,7 @@ function isExampleTodoAllowed(backend, database) {
385
388
  return !(backend !== "convex" && backend !== "none" && database === "none");
386
389
  }
387
390
  function isExampleAIAllowed(_backend, frontends = []) {
388
- const includesSolid = frontends.includes("solid");
389
- if (includesSolid) return false;
391
+ if (frontends.includes("solid")) return false;
390
392
  return true;
391
393
  }
392
394
  function validateWebDeployRequiresWebFrontend(webDeploy, hasWebFrontendFlag) {
@@ -398,8 +400,7 @@ function validateServerDeployRequiresBackend(serverDeploy, backend) {
398
400
  function validateAddonCompatibility(addon, frontend, _auth) {
399
401
  const compatibleFrontends = ADDON_COMPATIBILITY[addon];
400
402
  if (compatibleFrontends.length > 0) {
401
- const hasCompatibleFrontend = frontend.some((f) => compatibleFrontends.includes(f));
402
- if (!hasCompatibleFrontend) {
403
+ if (!frontend.some((f) => compatibleFrontends.includes(f))) {
403
404
  const frontendList = compatibleFrontends.join(", ");
404
405
  return {
405
406
  isCompatible: false,
@@ -912,7 +913,7 @@ async function getFrontendChoice(frontendOptions, backend, auth) {
912
913
  if (isCancel(frontendTypes)) return exitCancelled("Operation cancelled");
913
914
  const result = [];
914
915
  if (frontendTypes.includes("web")) {
915
- const allWebOptions = [
916
+ const webOptions = [
916
917
  {
917
918
  value: "tanstack-router",
918
919
  label: "TanStack Router",
@@ -948,8 +949,7 @@ async function getFrontendChoice(frontendOptions, backend, auth) {
948
949
  label: "TanStack Start",
949
950
  hint: "SSR, Server Functions, API Routes and more with TanStack Router"
950
951
  }
951
- ];
952
- const webOptions = allWebOptions.filter((option) => isFrontendAllowedWithBackend(option.value, backend, auth));
952
+ ].filter((option) => isFrontendAllowedWithBackend(option.value, backend, auth));
953
953
  const webFramework = await select({
954
954
  message: "Choose web",
955
955
  options: webOptions,
@@ -1069,20 +1069,18 @@ async function getPackageManagerChoice(packageManager) {
1069
1069
  //#region src/prompts/payments.ts
1070
1070
  async function getPaymentsChoice(payments, auth, backend, frontends) {
1071
1071
  if (payments !== void 0) return payments;
1072
- const isPolarCompatible = auth === "better-auth" && backend !== "convex" && (frontends?.length === 0 || splitFrontends(frontends).web.length > 0);
1073
- if (!isPolarCompatible) return "none";
1074
- const options = [{
1075
- value: "polar",
1076
- label: "Polar",
1077
- hint: "Turn your software into a business. 6 lines of code."
1078
- }, {
1079
- value: "none",
1080
- label: "None",
1081
- hint: "No payments integration"
1082
- }];
1072
+ if (!(auth === "better-auth" && backend !== "convex" && (frontends?.length === 0 || splitFrontends(frontends).web.length > 0))) return "none";
1083
1073
  const response = await select({
1084
1074
  message: "Select payments provider",
1085
- options,
1075
+ options: [{
1076
+ value: "polar",
1077
+ label: "Polar",
1078
+ hint: "Turn your software into a business. 6 lines of code."
1079
+ }, {
1080
+ value: "none",
1081
+ label: "None",
1082
+ hint: "No payments integration"
1083
+ }],
1086
1084
  initialValue: DEFAULT_CONFIG.payments
1087
1085
  });
1088
1086
  if (isCancel(response)) return exitCancelled("Operation cancelled");
@@ -1211,12 +1209,11 @@ function getDeploymentDisplay(deployment) {
1211
1209
  async function getDeploymentChoice(deployment, _runtime, _backend, frontend = []) {
1212
1210
  if (deployment !== void 0) return deployment;
1213
1211
  if (!hasWebFrontend(frontend)) return "none";
1214
- const availableDeployments = [
1212
+ const options = [
1215
1213
  "wrangler",
1216
1214
  "alchemy",
1217
1215
  "none"
1218
- ];
1219
- const options = availableDeployments.map((deploy) => {
1216
+ ].map((deploy) => {
1220
1217
  const { label, hint } = getDeploymentDisplay(deploy);
1221
1218
  return {
1222
1219
  value: deploy,
@@ -1322,14 +1319,12 @@ function validateDirectoryName(name) {
1322
1319
  if (name === ".") return void 0;
1323
1320
  const result = ProjectNameSchema.safeParse(name);
1324
1321
  if (!result.success) return result.error.issues[0]?.message || "Invalid project name";
1325
- return void 0;
1326
1322
  }
1327
1323
  async function getProjectName(initialName) {
1328
1324
  if (initialName) {
1329
1325
  if (initialName === ".") return initialName;
1330
1326
  const finalDirName = path.basename(initialName);
1331
- const validationError = validateDirectoryName(finalDirName);
1332
- if (!validationError) {
1327
+ if (!validateDirectoryName(finalDirName)) {
1333
1328
  const projectDir = path.resolve(process.cwd(), initialName);
1334
1329
  if (isPathWithinCwd(projectDir)) return initialName;
1335
1330
  consola.error(pc.red("Project path must be within current directory"));
@@ -1358,7 +1353,6 @@ async function getProjectName(initialName) {
1358
1353
  const projectDir = path.resolve(process.cwd(), nameToUse);
1359
1354
  if (!isPathWithinCwd(projectDir)) return "Project path must be within current directory";
1360
1355
  }
1361
- return void 0;
1362
1356
  }
1363
1357
  });
1364
1358
  if (isCancel(response)) return exitCancelled("Operation cancelled.");
@@ -1372,8 +1366,7 @@ async function getProjectName(initialName) {
1372
1366
  //#region src/utils/get-latest-cli-version.ts
1373
1367
  const getLatestCLIVersion = () => {
1374
1368
  const packageJsonPath = path.join(PKG_ROOT, "package.json");
1375
- const packageJsonContent = fs.readJSONSync(packageJsonPath);
1376
- return packageJsonContent.version ?? "1.0.0";
1369
+ return fs.readJSONSync(packageJsonPath).version ?? "1.0.0";
1377
1370
  };
1378
1371
 
1379
1372
  //#endregion
@@ -1386,7 +1379,7 @@ const getLatestCLIVersion = () => {
1386
1379
  */
1387
1380
  function isTelemetryEnabled() {
1388
1381
  const BTS_TELEMETRY_DISABLED = process.env.BTS_TELEMETRY_DISABLED;
1389
- const BTS_TELEMETRY = "1";
1382
+ const BTS_TELEMETRY = "0";
1390
1383
  if (BTS_TELEMETRY_DISABLED !== void 0) return BTS_TELEMETRY_DISABLED !== "1";
1391
1384
  if (BTS_TELEMETRY !== void 0) return BTS_TELEMETRY === "1";
1392
1385
  return true;
@@ -1394,12 +1387,11 @@ function isTelemetryEnabled() {
1394
1387
 
1395
1388
  //#endregion
1396
1389
  //#region src/utils/analytics.ts
1397
- const POSTHOG_API_KEY = "phc_8ZUxEwwfKMajJLvxz1daGd931dYbQrwKNficBmsdIrs";
1398
- const POSTHOG_HOST = "https://us.i.posthog.com";
1390
+ const POSTHOG_API_KEY = "random";
1391
+ const POSTHOG_HOST = "random";
1399
1392
  function generateSessionId() {
1400
1393
  const rand = Math.random().toString(36).slice(2);
1401
- const now = Date.now().toString(36);
1402
- return `cli_${now}${rand}`;
1394
+ return `cli_${Date.now().toString(36)}${rand}`;
1403
1395
  }
1404
1396
  async function trackProjectCreation(config, disableAnalytics = false) {
1405
1397
  if (!isTelemetryEnabled() || disableAnalytics) return;
@@ -1506,9 +1498,7 @@ function generateReproducibleCommand(config) {
1506
1498
  async function handleDirectoryConflict(currentPathInput, silent = false) {
1507
1499
  while (true) {
1508
1500
  const resolvedPath = path.resolve(process.cwd(), currentPathInput);
1509
- const dirExists = await fs.pathExists(resolvedPath);
1510
- const dirIsNotEmpty = dirExists && (await fs.readdir(resolvedPath)).length > 0;
1511
- if (!dirIsNotEmpty) return {
1501
+ if (!(await fs.pathExists(resolvedPath) && (await fs.readdir(resolvedPath)).length > 0)) return {
1512
1502
  finalPathInput: currentPathInput,
1513
1503
  shouldClearDirectory: false
1514
1504
  };
@@ -1622,14 +1612,12 @@ const renderTitle = () => {
1622
1612
  const terminalWidth = process.stdout.columns || 80;
1623
1613
  const titleLines = TITLE_TEXT.split("\n");
1624
1614
  const titleWidth = Math.max(...titleLines.map((line) => line.length));
1625
- if (terminalWidth < titleWidth) {
1626
- const simplifiedTitle = `
1615
+ if (terminalWidth < titleWidth) console.log(gradient(Object.values(catppuccinTheme)).multiline(`
1627
1616
  ╔══════════════════╗
1628
1617
  ║ Better T Stack ║
1629
1618
  ╚══════════════════╝
1630
- `;
1631
- console.log(gradient(Object.values(catppuccinTheme)).multiline(simplifiedTitle));
1632
- } else console.log(gradient(Object.values(catppuccinTheme)).multiline(TITLE_TEXT));
1619
+ `));
1620
+ else console.log(gradient(Object.values(catppuccinTheme)).multiline(TITLE_TEXT));
1633
1621
  };
1634
1622
 
1635
1623
  //#endregion
@@ -1755,8 +1743,7 @@ function validateConvexConstraints(config, providedFlags) {
1755
1743
  "tanstack-start",
1756
1744
  "next"
1757
1745
  ];
1758
- const hasSupportedFrontend = config.frontend?.some((f) => supportedFrontends.includes(f));
1759
- if (!hasSupportedFrontend) exitWithError("Better-Auth with Convex backend is only supported with TanStack Router, TanStack Start, or Next.js frontends. Please use '--auth clerk' or '--auth none'.");
1746
+ if (!config.frontend?.some((f) => supportedFrontends.includes(f))) exitWithError("Better-Auth with Convex backend is only supported with TanStack Router, TanStack Start, or Next.js frontends. Please use '--auth clerk' or '--auth none'.");
1760
1747
  }
1761
1748
  }
1762
1749
  function validateBackendNoneConstraints(config, providedFlags) {
@@ -1987,8 +1974,7 @@ async function updateBtsConfig(projectDir, updates) {
1987
1974
  try {
1988
1975
  const configPath = path.join(projectDir, BTS_CONFIG_FILE);
1989
1976
  if (!await fs.pathExists(configPath)) return;
1990
- const configContent = await fs.readFile(configPath, "utf-8");
1991
- let modifiedContent = configContent;
1977
+ let modifiedContent = await fs.readFile(configPath, "utf-8");
1992
1978
  for (const [key, value] of Object.entries(updates)) {
1993
1979
  const editResult = JSONC.modify(modifiedContent, [key], value, { formattingOptions: {
1994
1980
  tabSize: 2,
@@ -2010,15 +1996,17 @@ const addPackageDependency = async (opts) => {
2010
1996
  if (!pkgJson.dependencies) pkgJson.dependencies = {};
2011
1997
  if (!pkgJson.devDependencies) pkgJson.devDependencies = {};
2012
1998
  for (const pkgName of dependencies) {
2013
- const version = customDependencies[pkgName] || dependencyVersionMap[pkgName];
1999
+ const version = dependencyVersionMap[pkgName];
2014
2000
  if (version) pkgJson.dependencies[pkgName] = version;
2015
2001
  else console.warn(`Warning: Dependency ${pkgName} not found in version map.`);
2016
2002
  }
2017
2003
  for (const pkgName of devDependencies) {
2018
- const version = customDevDependencies[pkgName] || dependencyVersionMap[pkgName];
2004
+ const version = dependencyVersionMap[pkgName];
2019
2005
  if (version) pkgJson.devDependencies[pkgName] = version;
2020
2006
  else console.warn(`Warning: Dev dependency ${pkgName} not found in version map.`);
2021
2007
  }
2008
+ for (const [pkgName, version] of Object.entries(customDependencies)) pkgJson.dependencies[pkgName] = version;
2009
+ for (const [pkgName, version] of Object.entries(customDevDependencies)) pkgJson.devDependencies[pkgName] = version;
2022
2010
  await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
2023
2011
  };
2024
2012
 
@@ -2078,8 +2066,7 @@ async function setupFumadocs(config) {
2078
2066
  initialValue: "next-mdx"
2079
2067
  });
2080
2068
  if (isCancel(template)) return exitCancelled("Operation cancelled");
2081
- const templateArg = TEMPLATES[template].value;
2082
- const commandWithArgs = `create-fumadocs-app@latest fumadocs --template ${templateArg} --src --no-install --pm ${packageManager} --no-eslint --no-git`;
2069
+ const commandWithArgs = `create-fumadocs-app@latest fumadocs --template ${TEMPLATES[template].value} --src --no-install --pm ${packageManager} --no-eslint --no-git`;
2083
2070
  const fumadocsInitCommand = getPackageExecutionCommand(packageManager, commandWithArgs);
2084
2071
  await execa(fumadocsInitCommand, {
2085
2072
  cwd: path.join(projectDir, "apps"),
@@ -2112,27 +2099,26 @@ async function setupRuler(config) {
2112
2099
  log.error(pc.red("Ruler template directory not found. Please ensure ruler addon is properly installed."));
2113
2100
  return;
2114
2101
  }
2115
- const EDITORS$1 = {
2116
- amp: { label: "AMP" },
2117
- copilot: { label: "GitHub Copilot" },
2118
- claude: { label: "Claude Code" },
2119
- codex: { label: "OpenAI Codex CLI" },
2120
- cursor: { label: "Cursor" },
2121
- windsurf: { label: "Windsurf" },
2122
- cline: { label: "Cline" },
2123
- aider: { label: "Aider" },
2124
- firebase: { label: "Firebase Studio" },
2125
- "gemini-cli": { label: "Gemini CLI" },
2126
- junie: { label: "Junie" },
2127
- kilocode: { label: "Kilo Code" },
2128
- opencode: { label: "OpenCode" },
2129
- crush: { label: "Crush" },
2130
- zed: { label: "Zed" },
2131
- qwen: { label: "Qwen" }
2132
- };
2133
2102
  const selectedEditors = await autocompleteMultiselect({
2134
2103
  message: "Select AI assistants for Ruler",
2135
- options: Object.entries(EDITORS$1).map(([key, v]) => ({
2104
+ options: Object.entries({
2105
+ amp: { label: "AMP" },
2106
+ copilot: { label: "GitHub Copilot" },
2107
+ claude: { label: "Claude Code" },
2108
+ codex: { label: "OpenAI Codex CLI" },
2109
+ cursor: { label: "Cursor" },
2110
+ windsurf: { label: "Windsurf" },
2111
+ cline: { label: "Cline" },
2112
+ aider: { label: "Aider" },
2113
+ firebase: { label: "Firebase Studio" },
2114
+ "gemini-cli": { label: "Gemini CLI" },
2115
+ junie: { label: "Junie" },
2116
+ kilocode: { label: "Kilo Code" },
2117
+ opencode: { label: "OpenCode" },
2118
+ crush: { label: "Crush" },
2119
+ zed: { label: "Zed" },
2120
+ qwen: { label: "Qwen" }
2121
+ }).map(([key, v]) => ({
2136
2122
  value: key,
2137
2123
  label: v.label
2138
2124
  })),
@@ -2145,8 +2131,7 @@ async function setupRuler(config) {
2145
2131
  return;
2146
2132
  }
2147
2133
  const configFile = path.join(rulerDir, "ruler.toml");
2148
- const currentConfig = await fs.readFile(configFile, "utf-8");
2149
- let updatedConfig = currentConfig;
2134
+ let updatedConfig = await fs.readFile(configFile, "utf-8");
2150
2135
  const defaultAgentsLine = `default_agents = [${selectedEditors.map((editor) => `"${editor}"`).join(", ")}]`;
2151
2136
  updatedConfig = updatedConfig.replace(/default_agents = \[\]/, defaultAgentsLine);
2152
2137
  await fs.writeFile(configFile, updatedConfig);
@@ -2189,7 +2174,7 @@ async function setupStarlight(config) {
2189
2174
  const s = spinner();
2190
2175
  try {
2191
2176
  s.start("Setting up Starlight docs...");
2192
- const starlightArgs = [
2177
+ const commandWithArgs = `create-astro@latest ${[
2193
2178
  "docs",
2194
2179
  "--template",
2195
2180
  "starlight",
@@ -2198,9 +2183,7 @@ async function setupStarlight(config) {
2198
2183
  "tailwind",
2199
2184
  "--no-git",
2200
2185
  "--skip-houston"
2201
- ];
2202
- const starlightArgsString = starlightArgs.join(" ");
2203
- const commandWithArgs = `create-astro@latest ${starlightArgsString}`;
2186
+ ].join(" ")}`;
2204
2187
  const starlightInitCommand = getPackageExecutionCommand(packageManager, commandWithArgs);
2205
2188
  await execa(starlightInitCommand, {
2206
2189
  cwd: path.join(projectDir, "apps"),
@@ -2246,7 +2229,7 @@ async function setupTauri(config) {
2246
2229
  const hasNext = frontend.includes("next");
2247
2230
  const devUrl = hasReactRouter || hasSvelte ? "http://localhost:5173" : hasNext ? "http://localhost:3001" : "http://localhost:3001";
2248
2231
  const frontendDist = hasNuxt ? "../.output/public" : hasSvelte ? "../build" : hasNext ? "../.next" : hasReactRouter ? "../build/client" : "../dist";
2249
- const tauriArgs = [
2232
+ const commandWithArgs = `@tauri-apps/cli@latest ${[
2250
2233
  "init",
2251
2234
  `--app-name=${path.basename(projectDir)}`,
2252
2235
  `--window-title=${path.basename(projectDir)}`,
@@ -2254,9 +2237,7 @@ async function setupTauri(config) {
2254
2237
  `--dev-url=${devUrl}`,
2255
2238
  `--before-dev-command="${packageManager} run dev"`,
2256
2239
  `--before-build-command="${packageManager} run build"`
2257
- ];
2258
- const tauriArgsString = tauriArgs.join(" ");
2259
- const commandWithArgs = `@tauri-apps/cli@latest ${tauriArgsString}`;
2240
+ ].join(" ")}`;
2260
2241
  const tauriInitCommand = getPackageExecutionCommand(packageManager, commandWithArgs);
2261
2242
  await execa(tauriInitCommand, {
2262
2243
  cwd: clientPackageDir,
@@ -2330,8 +2311,7 @@ async function setupUltracite(config, hasHusky) {
2330
2311
  if (editors.length > 0) ultraciteArgs.push("--editors", ...editors);
2331
2312
  if (rules.length > 0) ultraciteArgs.push("--rules", ...rules);
2332
2313
  if (hasHusky) ultraciteArgs.push("--integrations", "husky", "lint-staged");
2333
- const ultraciteArgsString = ultraciteArgs.join(" ");
2334
- const commandWithArgs = `ultracite@latest ${ultraciteArgsString} --skip-install`;
2314
+ const commandWithArgs = `ultracite@latest ${ultraciteArgs.join(" ")} --skip-install`;
2335
2315
  const ultraciteInitCommand = getPackageExecutionCommand(packageManager, commandWithArgs);
2336
2316
  await execa(ultraciteInitCommand, {
2337
2317
  cwd: projectDir,
@@ -2371,8 +2351,7 @@ function ensureArrayProperty(obj, name) {
2371
2351
  async function addPwaToViteConfig(viteConfigPath, projectName) {
2372
2352
  const sourceFile = tsProject.addSourceFileAtPathIfExists(viteConfigPath);
2373
2353
  if (!sourceFile) throw new Error("vite config not found");
2374
- const hasImport = sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === "vite-plugin-pwa");
2375
- if (!hasImport) sourceFile.insertImportDeclaration(0, {
2354
+ if (!sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === "vite-plugin-pwa")) sourceFile.insertImportDeclaration(0, {
2376
2355
  namedImports: ["VitePWA"],
2377
2356
  moduleSpecifier: "vite-plugin-pwa"
2378
2357
  });
@@ -2381,12 +2360,10 @@ async function addPwaToViteConfig(viteConfigPath, projectName) {
2381
2360
  return Node.isIdentifier(expression) && expression.getText() === "defineConfig";
2382
2361
  });
2383
2362
  if (!defineCall) throw new Error("Could not find defineConfig call in vite config");
2384
- const callExpr = defineCall;
2385
- const configObject = callExpr.getArguments()[0];
2363
+ const configObject = defineCall.getArguments()[0];
2386
2364
  if (!configObject) throw new Error("defineConfig argument is not an object literal");
2387
2365
  const pluginsArray = ensureArrayProperty(configObject, "plugins");
2388
- const alreadyPresent = pluginsArray.getElements().some((el) => el.getText().startsWith("VitePWA("));
2389
- if (!alreadyPresent) pluginsArray.addElement(`VitePWA({
2366
+ if (!pluginsArray.getElements().some((el) => el.getText().startsWith("VitePWA("))) pluginsArray.addElement(`VitePWA({
2390
2367
  registerType: "autoUpdate",
2391
2368
  manifest: {
2392
2369
  name: "${projectName}",
@@ -2487,12 +2464,11 @@ async function setupHusky(projectDir, linter) {
2487
2464
  }
2488
2465
  }
2489
2466
  async function setupPwa(projectDir, frontends) {
2490
- const isCompatibleFrontend = frontends.some((f) => [
2467
+ if (!frontends.some((f) => [
2491
2468
  "react-router",
2492
2469
  "tanstack-router",
2493
2470
  "solid"
2494
- ].includes(f));
2495
- if (!isCompatibleFrontend) return;
2471
+ ].includes(f))) return;
2496
2472
  const clientPackageDir = getWebAppDir(projectDir, frontends);
2497
2473
  if (!await fs.pathExists(clientPackageDir)) return;
2498
2474
  await addPackageDependency({
@@ -2592,8 +2568,7 @@ async function installDependencies({ projectDir, packageManager }) {
2592
2568
  function initializeBiome() {
2593
2569
  try {
2594
2570
  const biome = new Biome();
2595
- const result = biome.openProject("./");
2596
- const projectKey = result.projectKey;
2571
+ const projectKey = biome.openProject("./").projectKey;
2597
2572
  biome.applyConfiguration(projectKey, {
2598
2573
  formatter: {
2599
2574
  enabled: true,
@@ -2615,27 +2590,25 @@ function initializeBiome() {
2615
2590
  }
2616
2591
  function isSupportedFile(filePath) {
2617
2592
  const ext = path.extname(filePath).toLowerCase();
2618
- const supportedExtensions = [
2593
+ return [
2619
2594
  ".js",
2620
2595
  ".jsx",
2621
2596
  ".ts",
2622
2597
  ".tsx",
2623
2598
  ".json",
2624
2599
  ".jsonc"
2625
- ];
2626
- return supportedExtensions.includes(ext);
2600
+ ].includes(ext);
2627
2601
  }
2628
2602
  function shouldSkipFile(filePath) {
2629
2603
  const basename = path.basename(filePath);
2630
- const skipPatterns = [
2604
+ return [
2631
2605
  ".hbs",
2632
2606
  "package-lock.json",
2633
2607
  "yarn.lock",
2634
2608
  "pnpm-lock.yaml",
2635
2609
  "bun.lock",
2636
2610
  ".d.ts"
2637
- ];
2638
- return skipPatterns.some((pattern) => basename.includes(pattern));
2611
+ ].some((pattern) => basename.includes(pattern));
2639
2612
  }
2640
2613
  function formatFileWithBiome(filePath, content) {
2641
2614
  if (!isSupportedFile(filePath) || shouldSkipFile(filePath)) return null;
@@ -2672,8 +2645,7 @@ async function processTemplate(srcPath, destPath, context) {
2672
2645
  let content;
2673
2646
  if (srcPath.endsWith(".hbs")) {
2674
2647
  const templateContent = await fs.readFile(srcPath, "utf-8");
2675
- const template = handlebars.compile(templateContent);
2676
- content = template(context);
2648
+ content = handlebars.compile(templateContent)(context);
2677
2649
  } else content = await fs.readFile(srcPath, "utf-8");
2678
2650
  try {
2679
2651
  const formattedContent = await formatFileWithBiome(destPath, content);
@@ -2804,23 +2776,28 @@ async function setupBackendFramework(projectDir, context) {
2804
2776
  return;
2805
2777
  }
2806
2778
  await fs.ensureDir(serverAppDir);
2807
- const serverBaseDir = path.join(PKG_ROOT, "templates/backend/server/server-base");
2779
+ const serverBaseDir = path.join(PKG_ROOT, "templates/backend/server/base");
2808
2780
  if (await fs.pathExists(serverBaseDir)) await processAndCopyFiles("**/*", serverBaseDir, serverAppDir, context);
2809
2781
  const frameworkSrcDir = path.join(PKG_ROOT, `templates/backend/server/${context.backend}`);
2810
2782
  if (await fs.pathExists(frameworkSrcDir)) await processAndCopyFiles("**/*", frameworkSrcDir, serverAppDir, context, true);
2811
2783
  if (context.api !== "none") {
2784
+ const apiPackageDir = path.join(projectDir, "packages/api");
2785
+ await fs.ensureDir(apiPackageDir);
2812
2786
  const apiServerBaseDir = path.join(PKG_ROOT, `templates/api/${context.api}/server/base`);
2813
- if (await fs.pathExists(apiServerBaseDir)) await processAndCopyFiles("**/*", apiServerBaseDir, serverAppDir, context, true);
2814
- const apiServerFrameworkDir = path.join(PKG_ROOT, `templates/api/${context.api}/server/${context.backend}`);
2815
- if (await fs.pathExists(apiServerFrameworkDir)) await processAndCopyFiles("**/*", apiServerFrameworkDir, serverAppDir, context, true);
2787
+ if (await fs.pathExists(apiServerBaseDir)) await processAndCopyFiles("**/*", apiServerBaseDir, apiPackageDir, context);
2788
+ let apiServerFrameworkDir = "";
2789
+ if (context.backend === "next") apiServerFrameworkDir = path.join(PKG_ROOT, `templates/api/${context.api}/server/${context.backend}`);
2790
+ else apiServerFrameworkDir = path.join(PKG_ROOT, `templates/api/${context.api}/server/rest`);
2791
+ if (await fs.pathExists(apiServerFrameworkDir)) await processAndCopyFiles("**/*", apiServerFrameworkDir, apiPackageDir, context, true);
2792
+ }
2793
+ if (context.database !== "none" && context.orm !== "none") {
2794
+ const dbPackageDir = path.join(projectDir, "packages/db");
2795
+ await fs.ensureDir(dbPackageDir);
2796
+ const dbBaseDir = path.join(PKG_ROOT, "templates/db/base");
2797
+ if (await fs.pathExists(dbBaseDir)) await processAndCopyFiles("**/*", dbBaseDir, dbPackageDir, context);
2798
+ const dbOrmSrcDir = path.join(PKG_ROOT, `templates/db/${context.orm}/${context.database}`);
2799
+ if (await fs.pathExists(dbOrmSrcDir)) await processAndCopyFiles("**/*", dbOrmSrcDir, dbPackageDir, context);
2816
2800
  }
2817
- }
2818
- async function setupDbOrmTemplates(projectDir, context) {
2819
- if (context.backend === "convex" || context.orm === "none" || context.database === "none") return;
2820
- const serverAppDir = path.join(projectDir, "apps/server");
2821
- await fs.ensureDir(serverAppDir);
2822
- const dbOrmSrcDir = path.join(PKG_ROOT, `templates/db/${context.orm}/${context.database}`);
2823
- if (await fs.pathExists(dbOrmSrcDir)) await processAndCopyFiles("**/*", dbOrmSrcDir, serverAppDir, context);
2824
2801
  }
2825
2802
  async function setupAuthTemplate(projectDir, context) {
2826
2803
  if (!context.auth || context.auth === "none") return;
@@ -2901,20 +2878,24 @@ async function setupAuthTemplate(projectDir, context) {
2901
2878
  return;
2902
2879
  }
2903
2880
  if (serverAppDirExists && context.backend !== "convex") {
2881
+ const authPackageDir = path.join(projectDir, "packages/auth");
2882
+ await fs.ensureDir(authPackageDir);
2904
2883
  const authServerBaseSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/base`);
2905
- if (await fs.pathExists(authServerBaseSrc)) await processAndCopyFiles("**/*", authServerBaseSrc, serverAppDir, context);
2884
+ if (await fs.pathExists(authServerBaseSrc)) await processAndCopyFiles("**/*", authServerBaseSrc, authPackageDir, context);
2906
2885
  if (context.backend === "next") {
2907
2886
  const authServerNextSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/next`);
2908
- if (await fs.pathExists(authServerNextSrc)) await processAndCopyFiles("**/*", authServerNextSrc, serverAppDir, context);
2887
+ if (await fs.pathExists(authServerNextSrc)) await processAndCopyFiles("**/*", authServerNextSrc, authPackageDir, context);
2909
2888
  }
2910
2889
  if (context.orm !== "none" && context.database !== "none") {
2890
+ const dbPackageDir = path.join(projectDir, "packages/db");
2891
+ await fs.ensureDir(dbPackageDir);
2911
2892
  const orm = context.orm;
2912
2893
  const db = context.database;
2913
2894
  let authDbSrc = "";
2914
2895
  if (orm === "drizzle") authDbSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/db/drizzle/${db}`);
2915
2896
  else if (orm === "prisma") authDbSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/db/prisma/${db}`);
2916
2897
  else if (orm === "mongoose") authDbSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/db/mongoose/${db}`);
2917
- if (authDbSrc && await fs.pathExists(authDbSrc)) await processAndCopyFiles("**/*", authDbSrc, serverAppDir, context);
2898
+ if (authDbSrc && await fs.pathExists(authDbSrc)) await processAndCopyFiles("**/*", authDbSrc, dbPackageDir, context);
2918
2899
  }
2919
2900
  }
2920
2901
  if ((hasReactWeb || hasNuxtWeb || hasSvelteWeb || hasSolidWeb) && webAppDirExists) {
@@ -2961,8 +2942,10 @@ async function setupPaymentsTemplate(projectDir, context) {
2961
2942
  const serverAppDirExists = await fs.pathExists(serverAppDir);
2962
2943
  const webAppDirExists = await fs.pathExists(webAppDir);
2963
2944
  if (serverAppDirExists && context.backend !== "convex") {
2945
+ const authPackageDir = path.join(projectDir, "packages/auth");
2946
+ await fs.ensureDir(authPackageDir);
2964
2947
  const paymentsServerSrc = path.join(PKG_ROOT, `templates/payments/${context.payments}/server/base`);
2965
- if (await fs.pathExists(paymentsServerSrc)) await processAndCopyFiles("**/*", paymentsServerSrc, serverAppDir, context);
2948
+ if (await fs.pathExists(paymentsServerSrc)) await processAndCopyFiles("**/*", paymentsServerSrc, authPackageDir, context);
2966
2949
  }
2967
2950
  const hasReactWeb = context.frontend.some((f) => [
2968
2951
  "tanstack-router",
@@ -3040,15 +3023,21 @@ async function setupExamplesTemplate(projectDir, context) {
3040
3023
  const exampleBaseDir = path.join(PKG_ROOT, `templates/examples/${example}`);
3041
3024
  if (serverAppDirExists && context.backend !== "convex" && context.backend !== "none") {
3042
3025
  const exampleServerSrc = path.join(exampleBaseDir, "server");
3043
- if (example === "ai" && context.backend === "next") {
3044
- const aiNextServerSrc = path.join(exampleServerSrc, "next");
3045
- if (await fs.pathExists(aiNextServerSrc)) await processAndCopyFiles("**/*", aiNextServerSrc, serverAppDir, context, false);
3026
+ if (context.api !== "none") {
3027
+ const apiPackageDir = path.join(projectDir, "packages/api");
3028
+ await fs.ensureDir(apiPackageDir);
3029
+ const exampleOrmBaseSrc = path.join(exampleServerSrc, context.orm, "base");
3030
+ if (await fs.pathExists(exampleOrmBaseSrc)) await processAndCopyFiles("**/*", exampleOrmBaseSrc, apiPackageDir, context, false);
3046
3031
  }
3047
3032
  if (context.orm !== "none" && context.database !== "none") {
3048
- const exampleOrmBaseSrc = path.join(exampleServerSrc, context.orm, "base");
3049
- if (await fs.pathExists(exampleOrmBaseSrc)) await processAndCopyFiles("**/*", exampleOrmBaseSrc, serverAppDir, context, false);
3033
+ const dbPackageDir = path.join(projectDir, "packages/db");
3034
+ await fs.ensureDir(dbPackageDir);
3050
3035
  const exampleDbSchemaSrc = path.join(exampleServerSrc, context.orm, context.database);
3051
- if (await fs.pathExists(exampleDbSchemaSrc)) await processAndCopyFiles("**/*", exampleDbSchemaSrc, serverAppDir, context, false);
3036
+ if (await fs.pathExists(exampleDbSchemaSrc)) await processAndCopyFiles("**/*", exampleDbSchemaSrc, dbPackageDir, context, false);
3037
+ }
3038
+ if (example === "ai" && context.backend === "next") {
3039
+ const aiNextServerSrc = path.join(exampleServerSrc, "next");
3040
+ if (await fs.pathExists(aiNextServerSrc)) await processAndCopyFiles("**/*", aiNextServerSrc, serverAppDir, context, false);
3052
3041
  }
3053
3042
  }
3054
3043
  if (webAppDirExists) {
@@ -3115,9 +3104,9 @@ async function handleExtras(projectDir, context) {
3115
3104
  }
3116
3105
  async function setupDockerComposeTemplates(projectDir, context) {
3117
3106
  if (context.dbSetup !== "docker" || context.database === "none") return;
3118
- const serverAppDir = path.join(projectDir, "apps/server");
3107
+ const dbPackageDir = path.join(projectDir, "packages/db");
3119
3108
  const dockerSrcDir = path.join(PKG_ROOT, `templates/db-setup/docker-compose/${context.database}`);
3120
- if (await fs.pathExists(dockerSrcDir)) await processAndCopyFiles("**/*", dockerSrcDir, serverAppDir, context);
3109
+ if (await fs.pathExists(dockerSrcDir)) await processAndCopyFiles("**/*", dockerSrcDir, dbPackageDir, context);
3121
3110
  }
3122
3111
  async function setupDeploymentTemplates(projectDir, context) {
3123
3112
  if (context.webDeploy === "alchemy" || context.serverDeploy === "alchemy") if (context.webDeploy === "alchemy" && context.serverDeploy === "alchemy") {
@@ -3175,8 +3164,7 @@ async function setupDeploymentTemplates(projectDir, context) {
3175
3164
  async function addAddonsToProject(input) {
3176
3165
  try {
3177
3166
  const projectDir = input.projectDir || process.cwd();
3178
- const isBetterTStack = await isBetterTStackProject(projectDir);
3179
- if (!isBetterTStack) exitWithError("This doesn't appear to be a Better-T-Stack project. Please run this command from the root of a Better-T-Stack project.");
3167
+ if (!await isBetterTStackProject(projectDir)) exitWithError("This doesn't appear to be a Better-T-Stack project. Please run this command from the root of a Better-T-Stack project.");
3180
3168
  const detectedConfig = await detectProjectConfig(projectDir);
3181
3169
  if (!detectedConfig) exitWithError("Could not detect the project configuration. Please ensure this is a valid Better-T-Stack project.");
3182
3170
  const config = {
@@ -3323,15 +3311,13 @@ async function setupNextAlchemyDeploy(projectDir, _packageManager, options) {
3323
3311
  await fs.writeJson(pkgPath, pkg, { spaces: 2 });
3324
3312
  }
3325
3313
  const openNextConfigPath = path.join(webAppDir, "open-next.config.ts");
3326
- const openNextConfigContent = `import { defineCloudflareConfig } from "@opennextjs/cloudflare";
3314
+ await fs.writeFile(openNextConfigPath, `import { defineCloudflareConfig } from "@opennextjs/cloudflare";
3327
3315
 
3328
3316
  export default defineCloudflareConfig({});
3329
- `;
3330
- await fs.writeFile(openNextConfigPath, openNextConfigContent);
3317
+ `);
3331
3318
  const gitignorePath = path.join(webAppDir, ".gitignore");
3332
3319
  if (await fs.pathExists(gitignorePath)) {
3333
- const gitignoreContent = await fs.readFile(gitignorePath, "utf-8");
3334
- if (!gitignoreContent.includes("wrangler.jsonc")) await fs.appendFile(gitignorePath, "\nwrangler.jsonc\n");
3320
+ if (!(await fs.readFile(gitignorePath, "utf-8")).includes("wrangler.jsonc")) await fs.appendFile(gitignorePath, "\nwrangler.jsonc\n");
3335
3321
  } else await fs.writeFile(gitignorePath, "wrangler.jsonc\n");
3336
3322
  }
3337
3323
 
@@ -3367,8 +3353,7 @@ async function setupNuxtAlchemyDeploy(projectDir, _packageManager, options) {
3367
3353
  quoteKind: QuoteKind.Double
3368
3354
  } });
3369
3355
  project.addSourceFileAtPath(nuxtConfigPath);
3370
- const sourceFile = project.getSourceFileOrThrow(nuxtConfigPath);
3371
- const exportAssignment = sourceFile.getExportAssignment((d) => !d.isExportEquals());
3356
+ const exportAssignment = project.getSourceFileOrThrow(nuxtConfigPath).getExportAssignment((d) => !d.isExportEquals());
3372
3357
  if (!exportAssignment) return;
3373
3358
  const defineConfigCall = exportAssignment.getExpression();
3374
3359
  if (!Node.isCallExpression(defineConfigCall) || defineConfigCall.getExpression().getText() !== "defineNuxtConfig") return;
@@ -3389,8 +3374,7 @@ async function setupNuxtAlchemyDeploy(projectDir, _packageManager, options) {
3389
3374
  if (modulesProperty && Node.isPropertyAssignment(modulesProperty)) {
3390
3375
  const initializer = modulesProperty.getInitializer();
3391
3376
  if (Node.isArrayLiteralExpression(initializer)) {
3392
- const hasModule = initializer.getElements().some((el) => el.getText() === "\"nitro-cloudflare-dev\"" || el.getText() === "'nitro-cloudflare-dev'");
3393
- if (!hasModule) initializer.addElement("\"nitro-cloudflare-dev\"");
3377
+ if (!initializer.getElements().some((el) => el.getText() === "\"nitro-cloudflare-dev\"" || el.getText() === "'nitro-cloudflare-dev'")) initializer.addElement("\"nitro-cloudflare-dev\"");
3394
3378
  }
3395
3379
  } else if (!modulesProperty) configObject.addPropertyAssignment({
3396
3380
  name: "modules",
@@ -3477,8 +3461,7 @@ async function setupSvelteAlchemyDeploy(projectDir, _packageManager, options) {
3477
3461
  } });
3478
3462
  project.addSourceFileAtPath(svelteConfigPath);
3479
3463
  const sourceFile = project.getSourceFileOrThrow(svelteConfigPath);
3480
- const importDeclarations = sourceFile.getImportDeclarations();
3481
- const adapterImport = importDeclarations.find((imp) => imp.getModuleSpecifierValue().includes("@sveltejs/adapter"));
3464
+ const adapterImport = sourceFile.getImportDeclarations().find((imp) => imp.getModuleSpecifierValue().includes("@sveltejs/adapter"));
3482
3465
  if (adapterImport) {
3483
3466
  adapterImport.setModuleSpecifier("alchemy/cloudflare/sveltekit");
3484
3467
  adapterImport.removeDefaultImport();
@@ -3604,8 +3587,7 @@ async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, opti
3604
3587
  if (pluginsProperty && Node.isPropertyAssignment(pluginsProperty)) {
3605
3588
  const initializer = pluginsProperty.getInitializer();
3606
3589
  if (Node.isArrayLiteralExpression(initializer)) {
3607
- const hasShim = initializer.getElements().some((el) => el.getText().includes("alchemy"));
3608
- if (!hasShim) initializer.addElement("alchemy()");
3590
+ if (!initializer.getElements().some((el) => el.getText().includes("alchemy"))) initializer.addElement("alchemy()");
3609
3591
  const tanstackElements = initializer.getElements().filter((el) => el.getText().includes("tanstackStart"));
3610
3592
  let needsReactPlugin = false;
3611
3593
  tanstackElements.forEach((element) => {
@@ -3623,8 +3605,7 @@ async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, opti
3623
3605
  name: "target",
3624
3606
  initializer: "\"cloudflare-module\""
3625
3607
  });
3626
- const hasCustomViteReactPlugin = !!configObj.getProperty("customViteReactPlugin");
3627
- if (!hasCustomViteReactPlugin) configObj.addPropertyAssignment({
3608
+ if (!!!configObj.getProperty("customViteReactPlugin")) configObj.addPropertyAssignment({
3628
3609
  name: "customViteReactPlugin",
3629
3610
  initializer: "true"
3630
3611
  });
@@ -3645,7 +3626,7 @@ async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, opti
3645
3626
  console.warn("Failed to update vite.config.ts:", error);
3646
3627
  }
3647
3628
  const nitroConfigPath = path.join(webAppDir, "nitro.config.ts");
3648
- const nitroConfigContent = `import { defineNitroConfig } from "nitropack/config";
3629
+ await fs.writeFile(nitroConfigPath, `import { defineNitroConfig } from "nitropack/config";
3649
3630
 
3650
3631
  export default defineNitroConfig({
3651
3632
  preset: "cloudflare-module",
@@ -3653,8 +3634,7 @@ export default defineNitroConfig({
3653
3634
  nodeCompat: true,
3654
3635
  },
3655
3636
  });
3656
- `;
3657
- await fs.writeFile(nitroConfigPath, nitroConfigContent, "utf-8");
3637
+ `, "utf-8");
3658
3638
  }
3659
3639
 
3660
3640
  //#endregion
@@ -3772,8 +3752,7 @@ async function setupNuxtWorkersDeploy(projectDir, packageManager) {
3772
3752
  if (modulesProp && modulesProp.getKind() === SyntaxKind.PropertyAssignment) {
3773
3753
  const arrayExpr = modulesProp.getFirstDescendantByKind(SyntaxKind.ArrayLiteralExpression);
3774
3754
  if (arrayExpr) {
3775
- const alreadyHas = arrayExpr.getElements().some((el) => el.getText().replace(/['"`]/g, "") === "nitro-cloudflare-dev");
3776
- if (!alreadyHas) arrayExpr.addElement("'nitro-cloudflare-dev'");
3755
+ if (!arrayExpr.getElements().some((el) => el.getText().replace(/['"`]/g, "") === "nitro-cloudflare-dev")) arrayExpr.addElement("'nitro-cloudflare-dev'");
3777
3756
  }
3778
3757
  } else configObj.addPropertyAssignment({
3779
3758
  name: "modules",
@@ -3808,13 +3787,10 @@ async function setupSvelteWorkersDeploy(projectDir, packageManager) {
3808
3787
  if (!sourceFile) return;
3809
3788
  const adapterImport = sourceFile.getImportDeclarations().find((imp) => ["@sveltejs/adapter-auto", "@sveltejs/adapter-node"].includes(imp.getModuleSpecifierValue()));
3810
3789
  if (adapterImport) adapterImport.setModuleSpecifier("@sveltejs/adapter-cloudflare");
3811
- else {
3812
- const alreadyHasCloudflare = sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === "@sveltejs/adapter-cloudflare");
3813
- if (!alreadyHasCloudflare) sourceFile.insertImportDeclaration(0, {
3814
- defaultImport: "adapter",
3815
- moduleSpecifier: "@sveltejs/adapter-cloudflare"
3816
- });
3817
- }
3790
+ else if (!sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === "@sveltejs/adapter-cloudflare")) sourceFile.insertImportDeclaration(0, {
3791
+ defaultImport: "adapter",
3792
+ moduleSpecifier: "@sveltejs/adapter-cloudflare"
3793
+ });
3818
3794
  await tsProject.save();
3819
3795
  }
3820
3796
  }
@@ -3865,8 +3841,7 @@ async function setupTanstackStartWorkersDeploy(projectDir, packageManager) {
3865
3841
  const tanstackPluginText = "tanstackStart({ target: \"cloudflare-module\", customViteReactPlugin: true })";
3866
3842
  if (tanstackPluginIndex === -1) pluginsArray.addElement(tanstackPluginText);
3867
3843
  else pluginsArray.getElements()[tanstackPluginIndex].replaceWithText(tanstackPluginText);
3868
- const hasReactPlugin = pluginsArray.getElements().some((el) => Node.isCallExpression(el) && el.getExpression().getText() === reactPluginIdentifier);
3869
- if (!hasReactPlugin) {
3844
+ if (!pluginsArray.getElements().some((el) => Node.isCallExpression(el) && el.getExpression().getText() === reactPluginIdentifier)) {
3870
3845
  const nextIndex = pluginsArray.getElements().findIndex((el) => el.getText().includes("tanstackStart(")) + 1;
3871
3846
  if (nextIndex > 0) pluginsArray.insertElement(nextIndex, `${reactPluginIdentifier}()`);
3872
3847
  else pluginsArray.addElement(`${reactPluginIdentifier}()`);
@@ -3886,8 +3861,7 @@ async function setupWorkersVitePlugin(projectDir) {
3886
3861
  });
3887
3862
  const sourceFile = tsProject.addSourceFileAtPathIfExists(viteConfigPath);
3888
3863
  if (!sourceFile) throw new Error("vite.config.ts not found in web app directory");
3889
- const hasCloudflareImport = sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === "@cloudflare/vite-plugin");
3890
- if (!hasCloudflareImport) sourceFile.insertImportDeclaration(0, {
3864
+ if (!sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === "@cloudflare/vite-plugin")) sourceFile.insertImportDeclaration(0, {
3891
3865
  namedImports: ["cloudflare"],
3892
3866
  moduleSpecifier: "@cloudflare/vite-plugin"
3893
3867
  });
@@ -3896,12 +3870,10 @@ async function setupWorkersVitePlugin(projectDir) {
3896
3870
  return Node.isIdentifier(expression) && expression.getText() === "defineConfig";
3897
3871
  });
3898
3872
  if (!defineCall) throw new Error("Could not find defineConfig call in vite config");
3899
- const callExpr = defineCall;
3900
- const configObject = callExpr.getArguments()[0];
3873
+ const configObject = defineCall.getArguments()[0];
3901
3874
  if (!configObject) throw new Error("defineConfig argument is not an object literal");
3902
3875
  const pluginsArray = ensureArrayProperty(configObject, "plugins");
3903
- const hasCloudflarePlugin = pluginsArray.getElements().some((el) => el.getText().includes("cloudflare("));
3904
- if (!hasCloudflarePlugin) pluginsArray.addElement("cloudflare()");
3876
+ if (!pluginsArray.getElements().some((el) => el.getText().includes("cloudflare("))) pluginsArray.addElement("cloudflare()");
3905
3877
  await tsProject.save();
3906
3878
  }
3907
3879
 
@@ -3960,8 +3932,7 @@ async function setupWorkersWebDeploy(projectDir, pkgManager) {
3960
3932
  async function addDeploymentToProject(input) {
3961
3933
  try {
3962
3934
  const projectDir = input.projectDir || process.cwd();
3963
- const isBetterTStack = await isBetterTStackProject(projectDir);
3964
- if (!isBetterTStack) exitWithError("This doesn't appear to be a Better-T-Stack project. Please run this command from the root of a Better-T-Stack project.");
3935
+ if (!await isBetterTStackProject(projectDir)) exitWithError("This doesn't appear to be a Better-T-Stack project. Please run this command from the root of a Better-T-Stack project.");
3965
3936
  const detectedConfig = await detectProjectConfig(projectDir);
3966
3937
  if (!detectedConfig) exitWithError("Could not detect the project configuration. Please ensure this is a valid Better-T-Stack project.");
3967
3938
  if (input.webDeploy && detectedConfig.webDeploy === input.webDeploy) exitWithError(`${input.webDeploy} web deployment is already configured for this project.`);
@@ -4007,6 +3978,110 @@ async function addDeploymentToProject(input) {
4007
3978
  }
4008
3979
  }
4009
3980
 
3981
+ //#endregion
3982
+ //#region src/utils/setup-catalogs.ts
3983
+ async function setupCatalogs(projectDir, options) {
3984
+ if (options.packageManager === "npm") return;
3985
+ const packagePaths = [
3986
+ "apps/server",
3987
+ "apps/web",
3988
+ "packages/api",
3989
+ "packages/db",
3990
+ "packages/auth",
3991
+ "packages/backend"
3992
+ ];
3993
+ const packagesInfo = [];
3994
+ for (const pkgPath of packagePaths) {
3995
+ const fullPath = path.join(projectDir, pkgPath);
3996
+ const pkgJsonPath = path.join(fullPath, "package.json");
3997
+ if (await fs.pathExists(pkgJsonPath)) {
3998
+ const pkgJson = await fs.readJson(pkgJsonPath);
3999
+ packagesInfo.push({
4000
+ path: fullPath,
4001
+ dependencies: pkgJson.dependencies || {},
4002
+ devDependencies: pkgJson.devDependencies || {}
4003
+ });
4004
+ }
4005
+ }
4006
+ const catalog = findDuplicateDependencies(packagesInfo, options.projectName);
4007
+ if (Object.keys(catalog).length === 0) return;
4008
+ if (options.packageManager === "bun") await setupBunCatalogs(projectDir, catalog);
4009
+ else if (options.packageManager === "pnpm") await setupPnpmCatalogs(projectDir, catalog);
4010
+ await updatePackageJsonsWithCatalogs(packagesInfo, catalog);
4011
+ }
4012
+ function findDuplicateDependencies(packagesInfo, projectName) {
4013
+ const depCount = /* @__PURE__ */ new Map();
4014
+ const projectScope = `@${projectName}/`;
4015
+ for (const pkg of packagesInfo) {
4016
+ const allDeps = {
4017
+ ...pkg.dependencies,
4018
+ ...pkg.devDependencies
4019
+ };
4020
+ for (const [depName, version] of Object.entries(allDeps)) {
4021
+ if (depName.startsWith(projectScope)) continue;
4022
+ if (version.startsWith("workspace:")) continue;
4023
+ const existing = depCount.get(depName);
4024
+ if (existing) existing.packages.push(pkg.path);
4025
+ else depCount.set(depName, {
4026
+ version,
4027
+ packages: [pkg.path]
4028
+ });
4029
+ }
4030
+ }
4031
+ const catalog = {};
4032
+ for (const [depName, info] of depCount.entries()) if (info.packages.length > 1) catalog[depName] = info.version;
4033
+ return catalog;
4034
+ }
4035
+ async function setupBunCatalogs(projectDir, catalog) {
4036
+ const rootPkgJsonPath = path.join(projectDir, "package.json");
4037
+ const rootPkgJson = await fs.readJson(rootPkgJsonPath);
4038
+ if (!rootPkgJson.workspaces) rootPkgJson.workspaces = {};
4039
+ if (Array.isArray(rootPkgJson.workspaces)) rootPkgJson.workspaces = {
4040
+ packages: rootPkgJson.workspaces,
4041
+ catalog
4042
+ };
4043
+ else if (typeof rootPkgJson.workspaces === "object") {
4044
+ if (!rootPkgJson.workspaces.catalog) rootPkgJson.workspaces.catalog = {};
4045
+ rootPkgJson.workspaces.catalog = {
4046
+ ...rootPkgJson.workspaces.catalog,
4047
+ ...catalog
4048
+ };
4049
+ }
4050
+ await fs.writeJson(rootPkgJsonPath, rootPkgJson, { spaces: 2 });
4051
+ }
4052
+ async function setupPnpmCatalogs(projectDir, catalog) {
4053
+ const workspaceYamlPath = path.join(projectDir, "pnpm-workspace.yaml");
4054
+ if (!await fs.pathExists(workspaceYamlPath)) return;
4055
+ const workspaceContent = await fs.readFile(workspaceYamlPath, "utf-8");
4056
+ const workspaceYaml = yaml.parse(workspaceContent);
4057
+ if (!workspaceYaml.catalog) workspaceYaml.catalog = {};
4058
+ workspaceYaml.catalog = {
4059
+ ...workspaceYaml.catalog,
4060
+ ...catalog
4061
+ };
4062
+ await fs.writeFile(workspaceYamlPath, yaml.stringify(workspaceYaml));
4063
+ }
4064
+ async function updatePackageJsonsWithCatalogs(packagesInfo, catalog) {
4065
+ for (const pkg of packagesInfo) {
4066
+ const pkgJsonPath = path.join(pkg.path, "package.json");
4067
+ const pkgJson = await fs.readJson(pkgJsonPath);
4068
+ let updated = false;
4069
+ if (pkgJson.dependencies) {
4070
+ for (const depName of Object.keys(pkgJson.dependencies)) if (catalog[depName]) {
4071
+ pkgJson.dependencies[depName] = "catalog:";
4072
+ updated = true;
4073
+ }
4074
+ }
4075
+ if (pkgJson.devDependencies) {
4076
+ for (const depName of Object.keys(pkgJson.devDependencies)) if (catalog[depName]) {
4077
+ pkgJson.devDependencies[depName] = "catalog:";
4078
+ updated = true;
4079
+ }
4080
+ }
4081
+ if (updated) await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
4082
+ }
4083
+ }
4084
+
4010
4085
  //#endregion
4011
4086
  //#region src/helpers/addons/examples-setup.ts
4012
4087
  async function setupExamples(config) {
@@ -4015,10 +4090,10 @@ async function setupExamples(config) {
4015
4090
  if (examples.includes("ai")) {
4016
4091
  const webClientDir = path.join(projectDir, "apps/web");
4017
4092
  const nativeClientDir = path.join(projectDir, "apps/native");
4018
- const serverDir = path.join(projectDir, "apps/server");
4093
+ const apiDir = path.join(projectDir, "packages/api");
4019
4094
  const webClientDirExists = await fs.pathExists(webClientDir);
4020
4095
  const nativeClientDirExists = await fs.pathExists(nativeClientDir);
4021
- const serverDirExists = await fs.pathExists(serverDir);
4096
+ const apiDirExists = await fs.pathExists(apiDir);
4022
4097
  const hasNuxt = frontend.includes("nuxt");
4023
4098
  const hasSvelte = frontend.includes("svelte");
4024
4099
  const hasReactWeb = frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("next") || frontend.includes("tanstack-start");
@@ -4039,9 +4114,9 @@ async function setupExamples(config) {
4039
4114
  dependencies: ["ai", "@ai-sdk/react"],
4040
4115
  projectDir: nativeClientDir
4041
4116
  });
4042
- if (serverDirExists && backend !== "none") await addPackageDependency({
4117
+ if (apiDirExists && backend !== "none") await addPackageDependency({
4043
4118
  dependencies: ["ai", "@ai-sdk/google"],
4044
- projectDir: serverDir
4119
+ projectDir: apiDir
4045
4120
  });
4046
4121
  }
4047
4122
  }
@@ -4131,8 +4206,7 @@ function getQueryDependencies(frontend) {
4131
4206
  "native-unistyles"
4132
4207
  ];
4133
4208
  const deps = {};
4134
- const needsReactQuery = frontend.some((f) => reactBasedFrontends.includes(f));
4135
- if (needsReactQuery) {
4209
+ if (frontend.some((f) => reactBasedFrontends.includes(f))) {
4136
4210
  const hasReactWeb = frontend.some((f) => f !== "native-nativewind" && f !== "native-unistyles" && reactBasedFrontends.includes(f));
4137
4211
  const hasNative = frontend.includes("native-nativewind") || frontend.includes("native-unistyles");
4138
4212
  if (hasReactWeb) deps.web = {
@@ -4158,32 +4232,54 @@ function getConvexDependencies(frontend) {
4158
4232
  return deps;
4159
4233
  }
4160
4234
  async function setupApi(config) {
4161
- const { api, projectName, frontend, backend, packageManager, projectDir } = config;
4235
+ const { api, projectName, frontend, backend, packageManager, projectDir, auth } = config;
4162
4236
  const isConvex = backend === "convex";
4163
4237
  const webDir = path.join(projectDir, "apps/web");
4164
4238
  const nativeDir = path.join(projectDir, "apps/native");
4165
4239
  const serverDir = path.join(projectDir, "apps/server");
4166
4240
  const webDirExists = await fs.pathExists(webDir);
4167
4241
  const nativeDirExists = await fs.pathExists(nativeDir);
4168
- const serverDirExists = await fs.pathExists(serverDir);
4242
+ await fs.pathExists(serverDir);
4169
4243
  const frontendType = getFrontendType(frontend);
4170
4244
  if (!isConvex && api !== "none") {
4171
4245
  const apiDeps = getApiDependencies(api, frontendType);
4172
- if (serverDirExists && apiDeps.server) {
4246
+ const apiPackageDir = path.join(projectDir, "packages/api");
4247
+ if (apiDeps.server) {
4173
4248
  await addPackageDependency({
4174
4249
  dependencies: apiDeps.server.dependencies,
4175
- projectDir: serverDir
4250
+ projectDir: apiPackageDir
4251
+ });
4252
+ const frameworkDeps = [];
4253
+ if (backend === "hono") frameworkDeps.push("hono");
4254
+ else if (backend === "elysia") frameworkDeps.push("elysia");
4255
+ else if (backend === "express") frameworkDeps.push("express", "@types/express");
4256
+ else if (backend === "fastify") frameworkDeps.push("fastify");
4257
+ if (frameworkDeps.length > 0) await addPackageDependency({
4258
+ dependencies: frameworkDeps,
4259
+ projectDir: apiPackageDir
4176
4260
  });
4177
4261
  if (api === "trpc") {
4178
4262
  if (backend === "hono") await addPackageDependency({
4179
4263
  dependencies: ["@hono/trpc-server"],
4180
- projectDir: serverDir
4264
+ projectDir: apiPackageDir
4181
4265
  });
4182
4266
  else if (backend === "elysia") await addPackageDependency({
4183
4267
  dependencies: ["@elysiajs/trpc"],
4184
- projectDir: serverDir
4268
+ projectDir: apiPackageDir
4269
+ });
4270
+ else if (backend === "express") await addPackageDependency({
4271
+ dependencies: ["@trpc/server"],
4272
+ projectDir: apiPackageDir
4273
+ });
4274
+ else if (backend === "fastify") await addPackageDependency({
4275
+ dependencies: ["@trpc/server"],
4276
+ projectDir: apiPackageDir
4185
4277
  });
4186
4278
  }
4279
+ if (auth === "better-auth") await addPackageDependency({
4280
+ dependencies: ["better-auth"],
4281
+ projectDir: apiPackageDir
4282
+ });
4187
4283
  }
4188
4284
  if (webDirExists && apiDeps.web) await addPackageDependency({
4189
4285
  dependencies: apiDeps.web.dependencies,
@@ -4227,7 +4323,7 @@ async function setupApi(config) {
4227
4323
  //#endregion
4228
4324
  //#region src/helpers/core/backend-setup.ts
4229
4325
  async function setupBackendDependencies(config) {
4230
- const { backend, runtime, api, projectDir } = config;
4326
+ const { backend, runtime, api, auth, examples, projectDir } = config;
4231
4327
  if (backend === "convex") return;
4232
4328
  const framework = backend;
4233
4329
  const serverDir = path.join(projectDir, "apps/server");
@@ -4255,6 +4351,13 @@ async function setupBackendDependencies(config) {
4255
4351
  dependencies.push("fastify", "@fastify/cors");
4256
4352
  if (runtime === "node") devDependencies.push("tsx", "@types/node");
4257
4353
  }
4354
+ if (api === "trpc") {
4355
+ if (framework === "express") dependencies.push("@trpc/server");
4356
+ else if (framework === "fastify") dependencies.push("@trpc/server");
4357
+ else if (runtime === "workers") dependencies.push("@trpc/server");
4358
+ } else if (api === "orpc") dependencies.push("@orpc/server", "@orpc/openapi", "@orpc/zod");
4359
+ if (auth === "better-auth") dependencies.push("better-auth");
4360
+ if (examples.includes("ai")) dependencies.push("ai", "@ai-sdk/google");
4258
4361
  if (runtime === "bun") devDependencies.push("@types/bun");
4259
4362
  if (dependencies.length > 0 || devDependencies.length > 0) await addPackageDependency({
4260
4363
  dependencies,
@@ -4273,7 +4376,7 @@ async function setupAuth(config) {
4273
4376
  const nativeDir = path.join(projectDir, "apps/native");
4274
4377
  const clientDirExists = await fs.pathExists(clientDir);
4275
4378
  const nativeDirExists = await fs.pathExists(nativeDir);
4276
- const serverDirExists = await fs.pathExists(serverDir);
4379
+ await fs.pathExists(serverDir);
4277
4380
  try {
4278
4381
  if (backend === "convex") {
4279
4382
  if (auth === "clerk" && clientDirExists) {
@@ -4295,8 +4398,7 @@ async function setupAuth(config) {
4295
4398
  }
4296
4399
  if (auth === "better-auth") {
4297
4400
  const convexBackendDir = path.join(projectDir, "packages/backend");
4298
- const convexBackendDirExists = await fs.pathExists(convexBackendDir);
4299
- if (convexBackendDirExists) await addPackageDependency({
4401
+ if (await fs.pathExists(convexBackendDir)) await addPackageDependency({
4300
4402
  dependencies: ["better-auth", "@convex-dev/better-auth"],
4301
4403
  customDependencies: { "better-auth": "1.3.8" },
4302
4404
  projectDir: convexBackendDir
@@ -4330,11 +4432,13 @@ async function setupAuth(config) {
4330
4432
  });
4331
4433
  return;
4332
4434
  }
4333
- if (serverDirExists && auth === "better-auth") await addPackageDependency({
4435
+ const authPackageDir = path.join(projectDir, "packages/auth");
4436
+ const authPackageDirExists = await fs.pathExists(authPackageDir);
4437
+ if (authPackageDirExists && auth === "better-auth") await addPackageDependency({
4334
4438
  dependencies: ["better-auth"],
4335
- projectDir: serverDir
4439
+ projectDir: authPackageDir
4336
4440
  });
4337
- const hasWebFrontend$1 = frontend.some((f) => [
4441
+ if (frontend.some((f) => [
4338
4442
  "react-router",
4339
4443
  "tanstack-router",
4340
4444
  "tanstack-start",
@@ -4342,8 +4446,7 @@ async function setupAuth(config) {
4342
4446
  "nuxt",
4343
4447
  "svelte",
4344
4448
  "solid"
4345
- ].includes(f));
4346
- if (hasWebFrontend$1 && clientDirExists) {
4449
+ ].includes(f)) && clientDirExists) {
4347
4450
  if (auth === "better-auth") await addPackageDependency({
4348
4451
  dependencies: ["better-auth"],
4349
4452
  projectDir: clientDir
@@ -4355,9 +4458,9 @@ async function setupAuth(config) {
4355
4458
  dependencies: ["better-auth", "@better-auth/expo"],
4356
4459
  projectDir: nativeDir
4357
4460
  });
4358
- if (serverDirExists) await addPackageDependency({
4461
+ if (authPackageDirExists) await addPackageDependency({
4359
4462
  dependencies: ["@better-auth/expo"],
4360
- projectDir: serverDir
4463
+ projectDir: authPackageDir
4361
4464
  });
4362
4465
  }
4363
4466
  }
@@ -4411,8 +4514,7 @@ async function addEnvVariablesToFile(filePath, variables) {
4411
4514
  let exampleContentToAdd = "";
4412
4515
  for (const exampleVar of exampleVariables) {
4413
4516
  const key = exampleVar.split("=")[0];
4414
- const regex = new RegExp(`^${key}=.*$`, "m");
4415
- if (!regex.test(exampleEnvContent)) {
4517
+ if (!new RegExp(`^${key}=.*$`, "m").test(exampleEnvContent)) {
4416
4518
  exampleContentToAdd += `${exampleVar}\n`;
4417
4519
  exampleModified = true;
4418
4520
  }
@@ -4432,8 +4534,7 @@ async function setupEnvironmentVariables(config) {
4432
4534
  const hasNuxt = frontend.includes("nuxt");
4433
4535
  const hasSvelte = frontend.includes("svelte");
4434
4536
  const hasSolid = frontend.includes("solid");
4435
- const hasWebFrontend$1 = hasReactRouter || hasTanStackRouter || hasTanStackStart || hasNextJs || hasNuxt || hasSolid || hasSvelte;
4436
- if (hasWebFrontend$1) {
4537
+ if (hasReactRouter || hasTanStackRouter || hasTanStackStart || hasNextJs || hasNuxt || hasSolid || hasSvelte) {
4437
4538
  const clientDir = path.join(projectDir, "apps/web");
4438
4539
  if (await fs.pathExists(clientDir)) {
4439
4540
  let envVarName = "VITE_SERVER_URL";
@@ -4522,15 +4623,12 @@ async function setupEnvironmentVariables(config) {
4522
4623
  const convexBackendDir = path.join(projectDir, "packages/backend");
4523
4624
  if (await fs.pathExists(convexBackendDir)) {
4524
4625
  const envLocalPath = path.join(convexBackendDir, ".env.local");
4525
- if (!await fs.pathExists(envLocalPath) || !(await fs.readFile(envLocalPath, "utf8")).includes("npx convex env set")) {
4526
- const convexCommands = `# Set Convex environment variables
4626
+ if (!await fs.pathExists(envLocalPath) || !(await fs.readFile(envLocalPath, "utf8")).includes("npx convex env set")) await fs.appendFile(envLocalPath, `# Set Convex environment variables
4527
4627
  # npx convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)
4528
4628
  # npx convex env set SITE_URL http://localhost:3001
4529
4629
 
4530
- `;
4531
- await fs.appendFile(envLocalPath, convexCommands);
4532
- }
4533
- const convexBackendVars = [{
4630
+ `);
4631
+ await addEnvVariablesToFile(envLocalPath, [{
4534
4632
  key: hasNextJs ? "NEXT_PUBLIC_CONVEX_SITE_URL" : "VITE_CONVEX_SITE_URL",
4535
4633
  value: "",
4536
4634
  condition: true,
@@ -4539,15 +4637,12 @@ async function setupEnvironmentVariables(config) {
4539
4637
  key: "SITE_URL",
4540
4638
  value: "http://localhost:3001",
4541
4639
  condition: true
4542
- }];
4543
- await addEnvVariablesToFile(envLocalPath, convexBackendVars);
4640
+ }]);
4544
4641
  }
4545
4642
  }
4546
4643
  return;
4547
4644
  }
4548
4645
  const serverDir = path.join(projectDir, "apps/server");
4549
- if (!await fs.pathExists(serverDir)) return;
4550
- const envPath = path.join(serverDir, ".env");
4551
4646
  let corsOrigin = "http://localhost:3001";
4552
4647
  if (hasReactRouter || hasSvelte) corsOrigin = "http://localhost:5173";
4553
4648
  let databaseUrl = null;
@@ -4563,80 +4658,73 @@ async function setupEnvironmentVariables(config) {
4563
4658
  break;
4564
4659
  case "sqlite":
4565
4660
  if (config.runtime === "workers") databaseUrl = "http://127.0.0.1:8080";
4566
- else databaseUrl = "file:./local.db";
4661
+ else databaseUrl = `file:${path.join(config.projectDir, "apps/server", "local.db")}`;
4567
4662
  break;
4568
4663
  }
4569
- const serverVars = [
4570
- {
4571
- key: "CORS_ORIGIN",
4572
- value: corsOrigin,
4573
- condition: true
4574
- },
4575
- {
4576
- key: "BETTER_AUTH_SECRET",
4577
- value: generateAuthSecret(),
4578
- condition: !!auth
4579
- },
4580
- {
4581
- key: "BETTER_AUTH_URL",
4582
- value: "http://localhost:3000",
4583
- condition: !!auth
4584
- },
4585
- {
4586
- key: "DATABASE_URL",
4587
- value: databaseUrl,
4588
- condition: database !== "none" && dbSetup === "none"
4589
- },
4590
- {
4591
- key: "GOOGLE_GENERATIVE_AI_API_KEY",
4592
- value: "",
4593
- condition: examples?.includes("ai") || false
4594
- },
4595
- {
4596
- key: "POLAR_ACCESS_TOKEN",
4597
- value: "",
4598
- condition: config.payments === "polar"
4599
- },
4600
- {
4601
- key: "POLAR_SUCCESS_URL",
4602
- value: `${corsOrigin}/success?checkout_id={CHECKOUT_ID}`,
4603
- condition: config.payments === "polar"
4604
- }
4605
- ];
4606
- await addEnvVariablesToFile(envPath, serverVars);
4664
+ if (await fs.pathExists(serverDir)) {
4665
+ const serverEnvPath = path.join(serverDir, ".env");
4666
+ const serverVars = [
4667
+ {
4668
+ key: "BETTER_AUTH_SECRET",
4669
+ value: generateAuthSecret(),
4670
+ condition: !!auth
4671
+ },
4672
+ {
4673
+ key: "BETTER_AUTH_URL",
4674
+ value: "http://localhost:3000",
4675
+ condition: !!auth
4676
+ },
4677
+ {
4678
+ key: "POLAR_ACCESS_TOKEN",
4679
+ value: "",
4680
+ condition: config.payments === "polar"
4681
+ },
4682
+ {
4683
+ key: "POLAR_SUCCESS_URL",
4684
+ value: `${corsOrigin}/success?checkout_id={CHECKOUT_ID}`,
4685
+ condition: config.payments === "polar"
4686
+ },
4687
+ {
4688
+ key: "CORS_ORIGIN",
4689
+ value: corsOrigin,
4690
+ condition: true
4691
+ },
4692
+ {
4693
+ key: "GOOGLE_GENERATIVE_AI_API_KEY",
4694
+ value: "",
4695
+ condition: examples?.includes("ai") || false
4696
+ },
4697
+ {
4698
+ key: "DATABASE_URL",
4699
+ value: databaseUrl,
4700
+ condition: database !== "none" && dbSetup === "none"
4701
+ }
4702
+ ];
4703
+ await addEnvVariablesToFile(serverEnvPath, serverVars);
4704
+ }
4607
4705
  const isUnifiedAlchemy = webDeploy === "alchemy" && serverDeploy === "alchemy";
4608
4706
  const isIndividualAlchemy = webDeploy === "alchemy" || serverDeploy === "alchemy";
4609
4707
  if (isUnifiedAlchemy) {
4610
4708
  const rootEnvPath = path.join(projectDir, ".env");
4611
- const rootAlchemyVars = [{
4709
+ await addEnvVariablesToFile(rootEnvPath, [{
4612
4710
  key: "ALCHEMY_PASSWORD",
4613
4711
  value: "please-change-this",
4614
4712
  condition: true
4615
- }];
4616
- await addEnvVariablesToFile(rootEnvPath, rootAlchemyVars);
4713
+ }]);
4617
4714
  } else if (isIndividualAlchemy) {
4618
4715
  if (webDeploy === "alchemy") {
4619
4716
  const webDir = path.join(projectDir, "apps/web");
4620
- if (await fs.pathExists(webDir)) {
4621
- const webAlchemyVars = [{
4622
- key: "ALCHEMY_PASSWORD",
4623
- value: "please-change-this",
4624
- condition: true
4625
- }];
4626
- await addEnvVariablesToFile(path.join(webDir, ".env"), webAlchemyVars);
4627
- }
4628
- }
4629
- if (serverDeploy === "alchemy") {
4630
- const serverDir$1 = path.join(projectDir, "apps/server");
4631
- if (await fs.pathExists(serverDir$1)) {
4632
- const serverAlchemyVars = [{
4633
- key: "ALCHEMY_PASSWORD",
4634
- value: "please-change-this",
4635
- condition: true
4636
- }];
4637
- await addEnvVariablesToFile(path.join(serverDir$1, ".env"), serverAlchemyVars);
4638
- }
4717
+ if (await fs.pathExists(webDir)) await addEnvVariablesToFile(path.join(webDir, ".env"), [{
4718
+ key: "ALCHEMY_PASSWORD",
4719
+ value: "please-change-this",
4720
+ condition: true
4721
+ }]);
4639
4722
  }
4723
+ if (serverDeploy === "alchemy") await addEnvVariablesToFile(path.join(serverDir, ".env"), [{
4724
+ key: "ALCHEMY_PASSWORD",
4725
+ value: "please-change-this",
4726
+ condition: true
4727
+ }]);
4640
4728
  }
4641
4729
  }
4642
4730
 
@@ -4671,7 +4759,7 @@ async function setupCloudflareD1(config) {
4671
4759
  const envPath = path.join(projectDir, "apps/server", ".env");
4672
4760
  const variables = [{
4673
4761
  key: "DATABASE_URL",
4674
- value: "file:./local.db",
4762
+ value: `file:${path.join(projectDir, "apps/server", "local.db")}`,
4675
4763
  condition: true
4676
4764
  }];
4677
4765
  try {
@@ -4718,13 +4806,8 @@ function getDatabaseUrl(database, projectName) {
4718
4806
  //#region src/utils/command-exists.ts
4719
4807
  async function commandExists(command) {
4720
4808
  try {
4721
- const isWindows = process.platform === "win32";
4722
- if (isWindows) {
4723
- const result$1 = await execa("where", [command]);
4724
- return result$1.exitCode === 0;
4725
- }
4726
- const result = await execa("which", [command]);
4727
- return result.exitCode === 0;
4809
+ if (process.platform === "win32") return (await execa("where", [command])).exitCode === 0;
4810
+ return (await execa("which", [command])).exitCode === 0;
4728
4811
  } catch {
4729
4812
  return false;
4730
4813
  }
@@ -4746,8 +4829,7 @@ async function checkAtlasCLI() {
4746
4829
  }
4747
4830
  async function initMongoDBAtlas(serverDir) {
4748
4831
  try {
4749
- const hasAtlas = await checkAtlasCLI();
4750
- if (!hasAtlas) {
4832
+ if (!await checkAtlasCLI()) {
4751
4833
  consola.error(pc.red("MongoDB Atlas CLI not found."));
4752
4834
  log.info(pc.yellow("Please install it from: https://www.mongodb.com/docs/atlas/cli/current/install-atlas-cli/"));
4753
4835
  return null;
@@ -5152,9 +5234,8 @@ async function setupWithCreateDb(serverDir, packageManager, orm) {
5152
5234
  consola$1.error("Failed to parse create-db response");
5153
5235
  return null;
5154
5236
  }
5155
- const databaseUrl = orm === "drizzle" ? createDbResponse.directConnectionString : createDbResponse.connectionString;
5156
5237
  return {
5157
- databaseUrl,
5238
+ databaseUrl: orm === "drizzle" ? createDbResponse.directConnectionString : createDbResponse.connectionString,
5158
5239
  claimUrl: createDbResponse.claimUrl
5159
5240
  };
5160
5241
  } catch (error) {
@@ -5208,9 +5289,9 @@ async function writeEnvFile$1(projectDir, config) {
5208
5289
  }
5209
5290
  async function addDotenvImportToPrismaConfig(projectDir) {
5210
5291
  try {
5211
- const prismaConfigPath = path.join(projectDir, "apps/server/prisma.config.ts");
5292
+ const prismaConfigPath = path.join(projectDir, "packages/db/prisma.config.ts");
5212
5293
  let content = await fs.readFile(prismaConfigPath, "utf8");
5213
- content = `import "dotenv/config";\n${content}`;
5294
+ content = `import dotenv from "dotenv";\ndotenv.config({ path: "../../apps/server/.env" });\n${content}`;
5214
5295
  await fs.writeFile(prismaConfigPath, content);
5215
5296
  } catch (_error) {
5216
5297
  consola$1.error("Failed to update prisma.config.ts");
@@ -5226,11 +5307,12 @@ function displayManualSetupInstructions$1() {
5226
5307
 
5227
5308
  DATABASE_URL="your_database_url"`);
5228
5309
  }
5229
- async function addPrismaAccelerateExtension(serverDir) {
5310
+ async function addPrismaAccelerateExtension(projectDir) {
5230
5311
  try {
5312
+ const dbPackageDir = path.join(projectDir, "packages/db");
5231
5313
  await addPackageDependency({
5232
5314
  dependencies: ["@prisma/extension-accelerate"],
5233
- projectDir: serverDir
5315
+ projectDir: dbPackageDir
5234
5316
  });
5235
5317
  return true;
5236
5318
  } catch (_error) {
@@ -5291,7 +5373,7 @@ async function setupPrismaPostgres(config, cliInput) {
5291
5373
  await writeEnvFile$1(projectDir, prismaConfig);
5292
5374
  if (orm === "prisma") {
5293
5375
  await addDotenvImportToPrismaConfig(projectDir);
5294
- await addPrismaAccelerateExtension(serverDir);
5376
+ await addPrismaAccelerateExtension(projectDir);
5295
5377
  }
5296
5378
  const connectionType = orm === "drizzle" ? "direct connection" : "Prisma Accelerate";
5297
5379
  log.success(pc.green(`Prisma Postgres database configured successfully with ${connectionType}!`));
@@ -5316,7 +5398,7 @@ async function writeSupabaseEnvFile(projectDir, databaseUrl) {
5316
5398
  try {
5317
5399
  const envPath = path.join(projectDir, "apps/server", ".env");
5318
5400
  const dbUrlToUse = databaseUrl || "postgresql://postgres:postgres@127.0.0.1:54322/postgres";
5319
- const variables = [{
5401
+ await addEnvVariablesToFile(envPath, [{
5320
5402
  key: "DATABASE_URL",
5321
5403
  value: dbUrlToUse,
5322
5404
  condition: true
@@ -5324,8 +5406,7 @@ async function writeSupabaseEnvFile(projectDir, databaseUrl) {
5324
5406
  key: "DIRECT_URL",
5325
5407
  value: dbUrlToUse,
5326
5408
  condition: true
5327
- }];
5328
- await addEnvVariablesToFile(envPath, variables);
5409
+ }]);
5329
5410
  return true;
5330
5411
  } catch (error) {
5331
5412
  consola$1.error(pc.red("Failed to update .env file for Supabase."));
@@ -5334,8 +5415,7 @@ async function writeSupabaseEnvFile(projectDir, databaseUrl) {
5334
5415
  }
5335
5416
  }
5336
5417
  function extractDbUrl(output) {
5337
- const dbUrlMatch = output.match(/DB URL:\s*(postgresql:\/\/[^\s]+)/);
5338
- const url = dbUrlMatch?.[1];
5418
+ const url = output.match(/DB URL:\s*(postgresql:\/\/[^\s]+)/)?.[1];
5339
5419
  if (url) return url;
5340
5420
  return null;
5341
5421
  }
@@ -5393,18 +5473,18 @@ function displayManualSupabaseInstructions(output) {
5393
5473
  log.info(`"Manual Supabase Setup Instructions:"
5394
5474
  1. Ensure Docker is installed and running.
5395
5475
  2. Install the Supabase CLI (e.g., \`npm install -g supabase\`).
5396
- 3. Run \`supabase init\` in your project's \`apps/server\` directory.
5397
- 4. Run \`supabase start\` in your project's \`apps/server\` directory.
5476
+ 3. Run \`supabase init\` in your project's \`packages/db\` directory.
5477
+ 4. Run \`supabase start\` in your project's \`packages/db\` directory.
5398
5478
  5. Copy the 'DB URL' from the output.${output ? `
5399
5479
  ${pc.bold("Relevant output from `supabase start`:")}
5400
5480
  ${pc.dim(output)}` : ""}
5401
- 6. Add the DB URL to the .env file in \`apps/server/.env\` as \`DATABASE_URL\`:
5481
+ 6. Add the DB URL to the .env file in \`packages/db/.env\` as \`DATABASE_URL\`:
5402
5482
  ${pc.gray("DATABASE_URL=\"your_supabase_db_url\"")}`);
5403
5483
  }
5404
5484
  async function setupSupabase(config, cliInput) {
5405
5485
  const { projectDir, packageManager } = config;
5406
5486
  const manualDb = cliInput?.manualDb ?? false;
5407
- const serverDir = path.join(projectDir, "apps", "server");
5487
+ const serverDir = path.join(projectDir, "packages", "db");
5408
5488
  try {
5409
5489
  await fs.ensureDir(serverDir);
5410
5490
  if (manualDb) {
@@ -5431,8 +5511,7 @@ async function setupSupabase(config, cliInput) {
5431
5511
  await writeSupabaseEnvFile(projectDir, "");
5432
5512
  return;
5433
5513
  }
5434
- const initialized = await initializeSupabase(serverDir, packageManager);
5435
- if (!initialized) {
5514
+ if (!await initializeSupabase(serverDir, packageManager)) {
5436
5515
  displayManualSupabaseInstructions();
5437
5516
  return;
5438
5517
  }
@@ -5442,14 +5521,12 @@ async function setupSupabase(config, cliInput) {
5442
5521
  return;
5443
5522
  }
5444
5523
  const dbUrl = extractDbUrl(supabaseOutput);
5445
- if (dbUrl) {
5446
- const envUpdated = await writeSupabaseEnvFile(projectDir, dbUrl);
5447
- if (envUpdated) log.success(pc.green("Supabase local development setup ready!"));
5448
- else {
5449
- log.error(pc.red("Supabase setup completed, but failed to update .env automatically."));
5450
- displayManualSupabaseInstructions(supabaseOutput);
5451
- }
5452
- } else {
5524
+ if (dbUrl) if (await writeSupabaseEnvFile(projectDir, dbUrl)) log.success(pc.green("Supabase local development setup ready!"));
5525
+ else {
5526
+ log.error(pc.red("Supabase setup completed, but failed to update .env automatically."));
5527
+ displayManualSupabaseInstructions(supabaseOutput);
5528
+ }
5529
+ else {
5453
5530
  log.error(pc.yellow("Supabase started, but could not extract DB URL automatically."));
5454
5531
  displayManualSupabaseInstructions(supabaseOutput);
5455
5532
  }
@@ -5467,8 +5544,7 @@ async function isTursoInstalled() {
5467
5544
  }
5468
5545
  async function isTursoLoggedIn() {
5469
5546
  try {
5470
- const output = await $`turso auth whoami`;
5471
- return !output.stdout.includes("You are not logged in");
5547
+ return !(await $`turso auth whoami`).stdout.includes("You are not logged in");
5472
5548
  } catch {
5473
5549
  return false;
5474
5550
  }
@@ -5627,10 +5703,9 @@ async function setupTurso(config, cliInput) {
5627
5703
  return;
5628
5704
  }
5629
5705
  setupSpinner.start("Checking Turso CLI availability...");
5630
- const platform = os.platform();
5706
+ const platform = os$1.platform();
5631
5707
  const isMac = platform === "darwin";
5632
- const isWindows = platform === "win32";
5633
- if (isWindows) {
5708
+ if (platform === "win32") {
5634
5709
  if (setupSpinner) setupSpinner.stop(pc.yellow("Turso setup not supported on Windows"));
5635
5710
  log.warn(pc.yellow("Automatic Turso setup is not supported on Windows."));
5636
5711
  await writeEnvFile(projectDir);
@@ -5638,8 +5713,7 @@ async function setupTurso(config, cliInput) {
5638
5713
  return;
5639
5714
  }
5640
5715
  if (setupSpinner) setupSpinner.stop("Turso CLI availability checked");
5641
- const isCliInstalled = await isTursoInstalled();
5642
- if (!isCliInstalled) {
5716
+ if (!await isTursoInstalled()) {
5643
5717
  const shouldInstall = await confirm({
5644
5718
  message: "Would you like to install Turso CLI?",
5645
5719
  initialValue: true
@@ -5652,8 +5726,7 @@ async function setupTurso(config, cliInput) {
5652
5726
  }
5653
5727
  await installTursoCLI(isMac);
5654
5728
  }
5655
- const isLoggedIn = await isTursoLoggedIn();
5656
- if (!isLoggedIn) await loginToTurso();
5729
+ if (!await isTursoLoggedIn()) await loginToTurso();
5657
5730
  const selectedGroup = await selectTursoGroup();
5658
5731
  let success = false;
5659
5732
  let dbName = "";
@@ -5694,15 +5767,15 @@ async function setupDatabase(config, cliInput) {
5694
5767
  const { database, orm, dbSetup, backend, projectDir } = config;
5695
5768
  if (backend === "convex" || database === "none") {
5696
5769
  if (backend !== "convex") {
5697
- const serverDir$1 = path.join(projectDir, "apps/server");
5698
- const serverDbDir = path.join(serverDir$1, "src/db");
5770
+ const serverDir = path.join(projectDir, "apps/server");
5771
+ const serverDbDir = path.join(serverDir, "src/db");
5699
5772
  if (await fs.pathExists(serverDbDir)) await fs.remove(serverDbDir);
5700
5773
  }
5701
5774
  return;
5702
5775
  }
5703
5776
  const s = spinner();
5704
- const serverDir = path.join(projectDir, "apps/server");
5705
- if (!await fs.pathExists(serverDir)) return;
5777
+ const dbPackageDir = path.join(projectDir, "packages/db");
5778
+ if (!await fs.pathExists(dbPackageDir)) return;
5706
5779
  try {
5707
5780
  if (orm === "prisma") if (database === "mysql" && dbSetup === "planetscale") await addPackageDependency({
5708
5781
  dependencies: [
@@ -5711,23 +5784,27 @@ async function setupDatabase(config, cliInput) {
5711
5784
  "@planetscale/database"
5712
5785
  ],
5713
5786
  devDependencies: ["prisma"],
5714
- projectDir: serverDir
5787
+ projectDir: dbPackageDir
5715
5788
  });
5716
5789
  else if (database === "sqlite" && dbSetup === "turso") await addPackageDependency({
5717
5790
  dependencies: ["@prisma/client", "@prisma/adapter-libsql"],
5718
5791
  devDependencies: ["prisma"],
5719
- projectDir: serverDir
5792
+ projectDir: dbPackageDir
5720
5793
  });
5721
5794
  else await addPackageDependency({
5722
5795
  dependencies: ["@prisma/client"],
5723
5796
  devDependencies: ["prisma"],
5724
- projectDir: serverDir
5797
+ projectDir: dbPackageDir
5725
5798
  });
5726
5799
  else if (orm === "drizzle") {
5727
5800
  if (database === "sqlite") await addPackageDependency({
5728
- dependencies: ["drizzle-orm", "@libsql/client"],
5801
+ dependencies: [
5802
+ "drizzle-orm",
5803
+ "@libsql/client",
5804
+ "libsql"
5805
+ ],
5729
5806
  devDependencies: ["drizzle-kit"],
5730
- projectDir: serverDir
5807
+ projectDir: dbPackageDir
5731
5808
  });
5732
5809
  else if (database === "postgres") if (dbSetup === "neon") await addPackageDependency({
5733
5810
  dependencies: [
@@ -5736,32 +5813,32 @@ async function setupDatabase(config, cliInput) {
5736
5813
  "ws"
5737
5814
  ],
5738
5815
  devDependencies: ["drizzle-kit", "@types/ws"],
5739
- projectDir: serverDir
5816
+ projectDir: dbPackageDir
5740
5817
  });
5741
5818
  else if (dbSetup === "planetscale") await addPackageDependency({
5742
5819
  dependencies: ["drizzle-orm", "pg"],
5743
5820
  devDependencies: ["drizzle-kit", "@types/pg"],
5744
- projectDir: serverDir
5821
+ projectDir: dbPackageDir
5745
5822
  });
5746
5823
  else await addPackageDependency({
5747
5824
  dependencies: ["drizzle-orm", "pg"],
5748
5825
  devDependencies: ["drizzle-kit", "@types/pg"],
5749
- projectDir: serverDir
5826
+ projectDir: dbPackageDir
5750
5827
  });
5751
5828
  else if (database === "mysql") if (dbSetup === "planetscale") await addPackageDependency({
5752
5829
  dependencies: ["drizzle-orm", "@planetscale/database"],
5753
5830
  devDependencies: ["drizzle-kit"],
5754
- projectDir: serverDir
5831
+ projectDir: dbPackageDir
5755
5832
  });
5756
5833
  else await addPackageDependency({
5757
5834
  dependencies: ["drizzle-orm", "mysql2"],
5758
5835
  devDependencies: ["drizzle-kit"],
5759
- projectDir: serverDir
5836
+ projectDir: dbPackageDir
5760
5837
  });
5761
5838
  } else if (orm === "mongoose") await addPackageDependency({
5762
5839
  dependencies: ["mongoose"],
5763
5840
  devDependencies: [],
5764
- projectDir: serverDir
5841
+ projectDir: dbPackageDir
5765
5842
  });
5766
5843
  if (dbSetup === "docker") await setupDockerCompose(config);
5767
5844
  else if (database === "sqlite" && dbSetup === "turso") await setupTurso(config, cliInput);
@@ -5910,8 +5987,7 @@ function generateStackDescription(frontend, backend, api, isConvex) {
5910
5987
  const hasSvelte = frontend.includes("svelte");
5911
5988
  const hasNuxt = frontend.includes("nuxt");
5912
5989
  const hasSolid = frontend.includes("solid");
5913
- const hasFrontendNone = frontend.length === 0 || frontend.includes("none");
5914
- if (!hasFrontendNone) {
5990
+ if (!(frontend.length === 0 || frontend.includes("none"))) {
5915
5991
  if (hasTanstackRouter) parts.push("React, TanStack Router");
5916
5992
  else if (hasReactRouter) parts.push("React, React Router");
5917
5993
  else if (hasNext) parts.push("Next.js");
@@ -5967,8 +6043,7 @@ function generateProjectStructure(projectName, frontend, backend, addons, isConv
5967
6043
  structure.push(`│ ├── web/ # Frontend application (${frontendType})`);
5968
6044
  }
5969
6045
  }
5970
- const hasNative = frontend.includes("native-nativewind") || frontend.includes("native-unistyles");
5971
- if (hasNative) structure.push("│ ├── native/ # Mobile application (React Native, Expo)");
6046
+ if (frontend.includes("native-nativewind") || frontend.includes("native-unistyles")) structure.push("│ ├── native/ # Mobile application (React Native, Expo)");
5972
6047
  if (addons.includes("starlight")) structure.push("│ ├── docs/ # Documentation site (Astro Starlight)");
5973
6048
  if (isConvex) {
5974
6049
  structure.push("├── packages/");
@@ -6129,7 +6204,7 @@ SERVER_URL={your-production-server-domain}
6129
6204
  CORS_ORIGIN={your-production-web-domain}
6130
6205
  BETTER_AUTH_URL={your-production-server-domain}
6131
6206
  \`\`\`
6132
- - In \`apps/server/lib/auth.ts\`, uncomment the \`session.cookieCache\` and \`advanced.crossSubDomainCookies\` sections and replace \`<your-workers-subdomain>\` with your actual workers subdomain. These settings are required to ensure cookies are transferred properly between your web and server domains.
6207
+ - In \`apps/server/src/lib/auth.ts\`, uncomment the \`session.cookieCache\` and \`advanced.crossSubDomainCookies\` sections and replace \`<your-workers-subdomain>\` with your actual workers subdomain. These settings are required to ensure cookies are transferred properly between your web and server domains.
6133
6208
  `;
6134
6209
  }
6135
6210
  function generateDeploymentCommands(packageManagerRunCmd, webDeploy, serverDeploy) {
@@ -6152,12 +6227,11 @@ function generateDeploymentCommands(packageManagerRunCmd, webDeploy, serverDeplo
6152
6227
  //#region src/helpers/core/git.ts
6153
6228
  async function initializeGit(projectDir, useGit) {
6154
6229
  if (!useGit) return;
6155
- const gitVersionResult = await $({
6230
+ if ((await $({
6156
6231
  cwd: projectDir,
6157
6232
  reject: false,
6158
6233
  stderr: "pipe"
6159
- })`git --version`;
6160
- if (gitVersionResult.exitCode !== 0) {
6234
+ })`git --version`).exitCode !== 0) {
6161
6235
  log.warn(pc.yellow("Git is not installed"));
6162
6236
  return;
6163
6237
  }
@@ -6187,7 +6261,7 @@ async function setupPayments(config) {
6187
6261
  projectDir: serverDir
6188
6262
  });
6189
6263
  if (clientDirExists) {
6190
- const hasWebFrontend$1 = frontend.some((f) => [
6264
+ if (frontend.some((f) => [
6191
6265
  "react-router",
6192
6266
  "tanstack-router",
6193
6267
  "tanstack-start",
@@ -6195,8 +6269,7 @@ async function setupPayments(config) {
6195
6269
  "nuxt",
6196
6270
  "svelte",
6197
6271
  "solid"
6198
- ].includes(f));
6199
- if (hasWebFrontend$1) await addPackageDependency({
6272
+ ].includes(f))) await addPackageDependency({
6200
6273
  dependencies: ["@polar-sh/better-auth"],
6201
6274
  projectDir: clientDir
6202
6275
  });
@@ -6237,15 +6310,13 @@ function getDockerInstallInstructions(platform, database) {
6237
6310
  return `${pc.yellow("IMPORTANT:")} Docker required for ${databaseName}. Install for ${platformName}:\n${pc.blue(installUrl)}`;
6238
6311
  }
6239
6312
  async function getDockerStatus(database) {
6240
- const platform = os.platform();
6241
- const installed = await isDockerInstalled();
6242
- if (!installed) return {
6313
+ const platform = os$1.platform();
6314
+ if (!await isDockerInstalled()) return {
6243
6315
  installed: false,
6244
6316
  running: false,
6245
6317
  message: getDockerInstallInstructions(platform, database)
6246
6318
  };
6247
- const running = await isDockerRunning();
6248
- if (!running) return {
6319
+ if (!await isDockerRunning()) return {
6249
6320
  installed: true,
6250
6321
  running: false,
6251
6322
  message: `${pc.yellow("IMPORTANT:")} Docker is installed but not running.`
@@ -6424,12 +6495,66 @@ function getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy) {
6424
6495
  return instructions.length ? `\n${instructions.join("\n")}` : "";
6425
6496
  }
6426
6497
 
6498
+ //#endregion
6499
+ //#region src/helpers/core/workspace-setup.ts
6500
+ async function setupWorkspaceDependencies(projectDir, options) {
6501
+ const projectName = options.projectName;
6502
+ const workspaceVersion = options.packageManager === "npm" ? "*" : "workspace:*";
6503
+ const commonDeps = ["dotenv", "zod"];
6504
+ const commonDevDeps = ["tsdown"];
6505
+ const dbPackageDir = path.join(projectDir, "packages/db");
6506
+ if (await fs.pathExists(dbPackageDir)) await addPackageDependency({
6507
+ dependencies: commonDeps,
6508
+ devDependencies: commonDevDeps,
6509
+ projectDir: dbPackageDir
6510
+ });
6511
+ const authPackageDir = path.join(projectDir, "packages/auth");
6512
+ if (await fs.pathExists(authPackageDir)) await addPackageDependency({
6513
+ dependencies: commonDeps,
6514
+ devDependencies: commonDevDeps,
6515
+ customDependencies: { [`@${projectName}/db`]: workspaceVersion },
6516
+ projectDir: authPackageDir
6517
+ });
6518
+ const apiPackageDir = path.join(projectDir, "packages/api");
6519
+ if (await fs.pathExists(apiPackageDir)) await addPackageDependency({
6520
+ dependencies: commonDeps,
6521
+ devDependencies: commonDevDeps,
6522
+ customDependencies: {
6523
+ [`@${projectName}/auth`]: workspaceVersion,
6524
+ [`@${projectName}/db`]: workspaceVersion
6525
+ },
6526
+ projectDir: apiPackageDir
6527
+ });
6528
+ const serverPackageDir = path.join(projectDir, "apps/server");
6529
+ if (await fs.pathExists(serverPackageDir)) await addPackageDependency({
6530
+ dependencies: commonDeps,
6531
+ devDependencies: commonDevDeps,
6532
+ customDependencies: {
6533
+ [`@${projectName}/api`]: workspaceVersion,
6534
+ [`@${projectName}/auth`]: workspaceVersion,
6535
+ [`@${projectName}/db`]: workspaceVersion
6536
+ },
6537
+ projectDir: serverPackageDir
6538
+ });
6539
+ if (options.api && options.api !== "none") {
6540
+ const webPackageDir = path.join(projectDir, "apps/web");
6541
+ if (await fs.pathExists(webPackageDir)) await addPackageDependency({
6542
+ customDependencies: { [`@${projectName}/api`]: workspaceVersion },
6543
+ projectDir: webPackageDir
6544
+ });
6545
+ }
6546
+ }
6547
+
6427
6548
  //#endregion
6428
6549
  //#region src/helpers/core/project-config.ts
6429
6550
  async function updatePackageConfigurations(projectDir, options) {
6430
6551
  await updateRootPackageJson(projectDir, options);
6431
- if (options.backend !== "convex") await updateServerPackageJson(projectDir, options);
6432
- else await updateConvexPackageJson(projectDir, options);
6552
+ if (options.backend !== "convex") {
6553
+ await updateServerPackageJson(projectDir, options);
6554
+ await updateAuthPackageJson(projectDir, options);
6555
+ await updateApiPackageJson(projectDir, options);
6556
+ await setupWorkspaceDependencies(projectDir, options);
6557
+ } else await updateConvexPackageJson(projectDir, options);
6433
6558
  }
6434
6559
  async function updateRootPackageJson(projectDir, options) {
6435
6560
  const rootPackageJsonPath = path.join(projectDir, "package.json");
@@ -6439,6 +6564,7 @@ async function updateRootPackageJson(projectDir, options) {
6439
6564
  if (!packageJson.scripts) packageJson.scripts = {};
6440
6565
  const scripts = packageJson.scripts;
6441
6566
  const backendPackageName = options.backend === "convex" ? `@${options.projectName}/backend` : "server";
6567
+ const dbPackageName = `@${options.projectName}/db`;
6442
6568
  let serverDevScript = "";
6443
6569
  if (options.addons.includes("turborepo")) serverDevScript = `turbo -F ${backendPackageName} dev`;
6444
6570
  else if (options.packageManager === "bun") serverDevScript = `bun run --filter ${backendPackageName} dev`;
@@ -6458,14 +6584,14 @@ async function updateRootPackageJson(projectDir, options) {
6458
6584
  scripts["dev:server"] = serverDevScript;
6459
6585
  if (options.backend === "convex") scripts["dev:setup"] = `turbo -F ${backendPackageName} dev:setup`;
6460
6586
  if (needsDbScripts) {
6461
- scripts["db:push"] = `turbo -F ${backendPackageName} db:push`;
6462
- if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `turbo -F ${backendPackageName} db:studio`;
6587
+ scripts["db:push"] = `turbo -F ${dbPackageName} db:push`;
6588
+ if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `turbo -F ${dbPackageName} db:studio`;
6463
6589
  if (options.orm === "prisma") {
6464
- scripts["db:generate"] = `turbo -F ${backendPackageName} db:generate`;
6465
- scripts["db:migrate"] = `turbo -F ${backendPackageName} db:migrate`;
6590
+ scripts["db:generate"] = `turbo -F ${dbPackageName} db:generate`;
6591
+ scripts["db:migrate"] = `turbo -F ${dbPackageName} db:migrate`;
6466
6592
  } else if (options.orm === "drizzle") {
6467
- scripts["db:generate"] = `turbo -F ${backendPackageName} db:generate`;
6468
- if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `turbo -F ${backendPackageName} db:migrate`;
6593
+ scripts["db:generate"] = `turbo -F ${dbPackageName} db:generate`;
6594
+ if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `turbo -F ${dbPackageName} db:migrate`;
6469
6595
  }
6470
6596
  }
6471
6597
  if (options.dbSetup === "docker") {
@@ -6483,14 +6609,14 @@ async function updateRootPackageJson(projectDir, options) {
6483
6609
  scripts["dev:server"] = serverDevScript;
6484
6610
  if (options.backend === "convex") scripts["dev:setup"] = `pnpm --filter ${backendPackageName} dev:setup`;
6485
6611
  if (needsDbScripts) {
6486
- scripts["db:push"] = `pnpm --filter ${backendPackageName} db:push`;
6487
- if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `pnpm --filter ${backendPackageName} db:studio`;
6612
+ scripts["db:push"] = `pnpm --filter ${dbPackageName} db:push`;
6613
+ if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `pnpm --filter ${dbPackageName} db:studio`;
6488
6614
  if (options.orm === "prisma") {
6489
- scripts["db:generate"] = `pnpm --filter ${backendPackageName} db:generate`;
6490
- scripts["db:migrate"] = `pnpm --filter ${backendPackageName} db:migrate`;
6615
+ scripts["db:generate"] = `pnpm --filter ${dbPackageName} db:generate`;
6616
+ scripts["db:migrate"] = `pnpm --filter ${dbPackageName} db:migrate`;
6491
6617
  } else if (options.orm === "drizzle") {
6492
- scripts["db:generate"] = `pnpm --filter ${backendPackageName} db:generate`;
6493
- if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `pnpm --filter ${backendPackageName} db:migrate`;
6618
+ scripts["db:generate"] = `pnpm --filter ${dbPackageName} db:generate`;
6619
+ if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `pnpm --filter ${dbPackageName} db:migrate`;
6494
6620
  }
6495
6621
  }
6496
6622
  if (options.dbSetup === "docker") {
@@ -6508,14 +6634,14 @@ async function updateRootPackageJson(projectDir, options) {
6508
6634
  scripts["dev:server"] = serverDevScript;
6509
6635
  if (options.backend === "convex") scripts["dev:setup"] = `npm run dev:setup --workspace ${backendPackageName}`;
6510
6636
  if (needsDbScripts) {
6511
- scripts["db:push"] = `npm run db:push --workspace ${backendPackageName}`;
6512
- if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `npm run db:studio --workspace ${backendPackageName}`;
6637
+ scripts["db:push"] = `npm run db:push --workspace ${dbPackageName}`;
6638
+ if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `npm run db:studio --workspace ${dbPackageName}`;
6513
6639
  if (options.orm === "prisma") {
6514
- scripts["db:generate"] = `npm run db:generate --workspace ${backendPackageName}`;
6515
- scripts["db:migrate"] = `npm run db:migrate --workspace ${backendPackageName}`;
6640
+ scripts["db:generate"] = `npm run db:generate --workspace ${dbPackageName}`;
6641
+ scripts["db:migrate"] = `npm run db:migrate --workspace ${dbPackageName}`;
6516
6642
  } else if (options.orm === "drizzle") {
6517
- scripts["db:generate"] = `npm run db:generate --workspace ${backendPackageName}`;
6518
- if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `npm run db:migrate --workspace ${backendPackageName}`;
6643
+ scripts["db:generate"] = `npm run db:generate --workspace ${dbPackageName}`;
6644
+ if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `npm run db:migrate --workspace ${dbPackageName}`;
6519
6645
  }
6520
6646
  }
6521
6647
  if (options.dbSetup === "docker") {
@@ -6533,14 +6659,14 @@ async function updateRootPackageJson(projectDir, options) {
6533
6659
  scripts["dev:server"] = serverDevScript;
6534
6660
  if (options.backend === "convex") scripts["dev:setup"] = `bun run --filter ${backendPackageName} dev:setup`;
6535
6661
  if (needsDbScripts) {
6536
- scripts["db:push"] = `bun run --filter ${backendPackageName} db:push`;
6537
- if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `bun run --filter ${backendPackageName} db:studio`;
6662
+ scripts["db:push"] = `bun run --filter ${dbPackageName} db:push`;
6663
+ if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `bun run --filter ${dbPackageName} db:studio`;
6538
6664
  if (options.orm === "prisma") {
6539
- scripts["db:generate"] = `bun run --filter ${backendPackageName} db:generate`;
6540
- scripts["db:migrate"] = `bun run --filter ${backendPackageName} db:migrate`;
6665
+ scripts["db:generate"] = `bun run --filter ${dbPackageName} db:generate`;
6666
+ scripts["db:migrate"] = `bun run --filter ${dbPackageName} db:migrate`;
6541
6667
  } else if (options.orm === "drizzle") {
6542
- scripts["db:generate"] = `bun run --filter ${backendPackageName} db:generate`;
6543
- if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `bun run --filter ${backendPackageName} db:migrate`;
6668
+ scripts["db:generate"] = `bun run --filter ${dbPackageName} db:generate`;
6669
+ if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `bun run --filter ${dbPackageName} db:migrate`;
6544
6670
  }
6545
6671
  }
6546
6672
  if (options.dbSetup === "docker") {
@@ -6560,8 +6686,7 @@ async function updateRootPackageJson(projectDir, options) {
6560
6686
  const workspaces = packageJson.workspaces;
6561
6687
  if (options.backend === "convex") {
6562
6688
  if (!workspaces.includes("packages/*")) workspaces.push("packages/*");
6563
- const needsAppsDir = options.frontend.length > 0 || options.addons.includes("starlight");
6564
- if (needsAppsDir && !workspaces.includes("apps/*")) workspaces.push("apps/*");
6689
+ if ((options.frontend.length > 0 || options.addons.includes("starlight")) && !workspaces.includes("apps/*")) workspaces.push("apps/*");
6565
6690
  } else {
6566
6691
  if (!workspaces.includes("apps/*")) workspaces.push("apps/*");
6567
6692
  if (!workspaces.includes("packages/*")) workspaces.push("packages/*");
@@ -6574,6 +6699,22 @@ async function updateServerPackageJson(projectDir, options) {
6574
6699
  const serverPackageJson = await fs.readJson(serverPackageJsonPath);
6575
6700
  if (!serverPackageJson.scripts) serverPackageJson.scripts = {};
6576
6701
  const scripts = serverPackageJson.scripts;
6702
+ if (options.dbSetup === "docker") {
6703
+ scripts["db:start"] = "docker compose up -d";
6704
+ scripts["db:watch"] = "docker compose up";
6705
+ scripts["db:stop"] = "docker compose stop";
6706
+ scripts["db:down"] = "docker compose down";
6707
+ }
6708
+ await fs.writeJson(serverPackageJsonPath, serverPackageJson, { spaces: 2 });
6709
+ await updateDbPackageJson(projectDir, options);
6710
+ }
6711
+ async function updateDbPackageJson(projectDir, options) {
6712
+ const dbPackageJsonPath = path.join(projectDir, "packages/db/package.json");
6713
+ if (!await fs.pathExists(dbPackageJsonPath)) return;
6714
+ const dbPackageJson = await fs.readJson(dbPackageJsonPath);
6715
+ dbPackageJson.name = `@${options.projectName}/db`;
6716
+ if (!dbPackageJson.scripts) dbPackageJson.scripts = {};
6717
+ const scripts = dbPackageJson.scripts;
6577
6718
  if (options.database !== "none") {
6578
6719
  if (options.database === "sqlite" && options.orm === "drizzle" && options.dbSetup !== "d1") scripts["db:local"] = "turso dev --db-file local.db";
6579
6720
  if (options.orm === "prisma") {
@@ -6588,13 +6729,21 @@ async function updateServerPackageJson(projectDir, options) {
6588
6729
  if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = "drizzle-kit migrate";
6589
6730
  }
6590
6731
  }
6591
- if (options.dbSetup === "docker") {
6592
- scripts["db:start"] = "docker compose up -d";
6593
- scripts["db:watch"] = "docker compose up";
6594
- scripts["db:stop"] = "docker compose stop";
6595
- scripts["db:down"] = "docker compose down";
6596
- }
6597
- await fs.writeJson(serverPackageJsonPath, serverPackageJson, { spaces: 2 });
6732
+ await fs.writeJson(dbPackageJsonPath, dbPackageJson, { spaces: 2 });
6733
+ }
6734
+ async function updateAuthPackageJson(projectDir, options) {
6735
+ const authPackageJsonPath = path.join(projectDir, "packages/auth/package.json");
6736
+ if (!await fs.pathExists(authPackageJsonPath)) return;
6737
+ const authPackageJson = await fs.readJson(authPackageJsonPath);
6738
+ authPackageJson.name = `@${options.projectName}/auth`;
6739
+ await fs.writeJson(authPackageJsonPath, authPackageJson, { spaces: 2 });
6740
+ }
6741
+ async function updateApiPackageJson(projectDir, options) {
6742
+ const apiPackageJsonPath = path.join(projectDir, "packages/api/package.json");
6743
+ if (!await fs.pathExists(apiPackageJsonPath)) return;
6744
+ const apiPackageJson = await fs.readJson(apiPackageJsonPath);
6745
+ apiPackageJson.name = `@${options.projectName}/api`;
6746
+ await fs.writeJson(apiPackageJsonPath, apiPackageJson, { spaces: 2 });
6598
6747
  }
6599
6748
  async function updateConvexPackageJson(projectDir, options) {
6600
6749
  const convexPackageJsonPath = path.join(projectDir, "packages/backend/package.json");
@@ -6615,10 +6764,7 @@ async function createProject(options, cliInput) {
6615
6764
  await copyBaseTemplate(projectDir, options);
6616
6765
  await setupFrontendTemplates(projectDir, options);
6617
6766
  await setupBackendFramework(projectDir, options);
6618
- if (!isConvex) {
6619
- await setupDbOrmTemplates(projectDir, options);
6620
- await setupDockerComposeTemplates(projectDir, options);
6621
- }
6767
+ if (!isConvex) await setupDockerComposeTemplates(projectDir, options);
6622
6768
  await setupAuthTemplate(projectDir, options);
6623
6769
  if (options.payments && options.payments !== "none") await setupPaymentsTemplate(projectDir, options);
6624
6770
  if (options.examples.length > 0 && options.examples[0] !== "none") await setupExamplesTemplate(projectDir, options);
@@ -6637,6 +6783,7 @@ async function createProject(options, cliInput) {
6637
6783
  await handleExtras(projectDir, options);
6638
6784
  await setupEnvironmentVariables(options);
6639
6785
  await updatePackageConfigurations(projectDir, options);
6786
+ await setupCatalogs(projectDir, options);
6640
6787
  await setupWebDeploy(options);
6641
6788
  await setupServerDeploy(options);
6642
6789
  await createReadme(projectDir, options);
@@ -6781,9 +6928,7 @@ async function handleDirectoryConflictProgrammatically(currentPathInput, strateg
6781
6928
  finalPathInput: currentPathInput,
6782
6929
  shouldClearDirectory: false
6783
6930
  };
6784
- const dirContents = await fs.readdir(currentPath);
6785
- const isNotEmpty = dirContents.length > 0;
6786
- if (!isNotEmpty) return {
6931
+ if (!((await fs.readdir(currentPath)).length > 0)) return {
6787
6932
  finalPathInput: currentPathInput,
6788
6933
  shouldClearDirectory: false
6789
6934
  };
@@ -6945,26 +7090,25 @@ function displaySponsorsBox(sponsors$1) {
6945
7090
 
6946
7091
  //#endregion
6947
7092
  //#region src/index.ts
6948
- const t = trpcServer.initTRPC.create();
6949
- const router = t.router({
6950
- init: t.procedure.meta({
7093
+ const router = os.router({
7094
+ init: os.meta({
6951
7095
  description: "Create a new Better-T-Stack project",
6952
7096
  default: true,
6953
7097
  negateBooleans: true
6954
- }).input(z.tuple([ProjectNameSchema.optional(), z.object({
6955
- yes: z.boolean().optional().default(false).describe("Use default configuration"),
6956
- yolo: z.boolean().optional().default(false).describe("(WARNING - NOT RECOMMENDED) Bypass validations and compatibility checks"),
6957
- verbose: z.boolean().optional().default(false).describe("Show detailed result information"),
7098
+ }).input(z$1.tuple([ProjectNameSchema.optional(), z$1.object({
7099
+ yes: z$1.boolean().optional().default(false).describe("Use default configuration"),
7100
+ yolo: z$1.boolean().optional().default(false).describe("(WARNING - NOT RECOMMENDED) Bypass validations and compatibility checks"),
7101
+ verbose: z$1.boolean().optional().default(false).describe("Show detailed result information"),
6958
7102
  database: DatabaseSchema.optional(),
6959
7103
  orm: ORMSchema.optional(),
6960
7104
  auth: AuthSchema.optional(),
6961
7105
  payments: PaymentsSchema.optional(),
6962
- frontend: z.array(FrontendSchema).optional(),
6963
- addons: z.array(AddonsSchema).optional(),
6964
- examples: z.array(ExamplesSchema).optional(),
6965
- git: z.boolean().optional(),
7106
+ frontend: z$1.array(FrontendSchema).optional(),
7107
+ addons: z$1.array(AddonsSchema).optional(),
7108
+ examples: z$1.array(ExamplesSchema).optional(),
7109
+ git: z$1.boolean().optional(),
6966
7110
  packageManager: PackageManagerSchema.optional(),
6967
- install: z.boolean().optional(),
7111
+ install: z$1.boolean().optional(),
6968
7112
  dbSetup: DatabaseSetupSchema.optional(),
6969
7113
  backend: BackendSchema.optional(),
6970
7114
  runtime: RuntimeSchema.optional(),
@@ -6972,10 +7116,10 @@ const router = t.router({
6972
7116
  webDeploy: WebDeploySchema.optional(),
6973
7117
  serverDeploy: ServerDeploySchema.optional(),
6974
7118
  directoryConflict: DirectoryConflictSchema.optional(),
6975
- renderTitle: z.boolean().optional(),
6976
- disableAnalytics: z.boolean().optional().default(false).describe("Disable analytics"),
6977
- manualDb: z.boolean().optional().default(false).describe("Skip automatic/manual database setup prompt and use manual setup")
6978
- })])).mutation(async ({ input }) => {
7119
+ renderTitle: z$1.boolean().optional(),
7120
+ disableAnalytics: z$1.boolean().optional().default(false).describe("Disable analytics"),
7121
+ manualDb: z$1.boolean().optional().default(false).describe("Skip automatic/manual database setup prompt and use manual setup")
7122
+ })])).handler(async ({ input }) => {
6979
7123
  const [projectName, options] = input;
6980
7124
  const combinedInput = {
6981
7125
  projectName,
@@ -6984,18 +7128,18 @@ const router = t.router({
6984
7128
  const result = await createProjectHandler(combinedInput);
6985
7129
  if (options.verbose) return result;
6986
7130
  }),
6987
- add: t.procedure.meta({ description: "Add addons or deployment configurations to an existing Better-T-Stack project" }).input(z.tuple([z.object({
6988
- addons: z.array(AddonsSchema).optional().default([]),
7131
+ add: os.meta({ description: "Add addons or deployment configurations to an existing Better-T-Stack project" }).input(z$1.tuple([z$1.object({
7132
+ addons: z$1.array(AddonsSchema).optional().default([]),
6989
7133
  webDeploy: WebDeploySchema.optional(),
6990
7134
  serverDeploy: ServerDeploySchema.optional(),
6991
- projectDir: z.string().optional(),
6992
- install: z.boolean().optional().default(false).describe("Install dependencies after adding addons or deployment"),
7135
+ projectDir: z$1.string().optional(),
7136
+ install: z$1.boolean().optional().default(false).describe("Install dependencies after adding addons or deployment"),
6993
7137
  packageManager: PackageManagerSchema.optional()
6994
- })])).mutation(async ({ input }) => {
7138
+ })])).handler(async ({ input }) => {
6995
7139
  const [options] = input;
6996
7140
  await addAddonsHandler(options);
6997
7141
  }),
6998
- sponsors: t.procedure.meta({ description: "Show Better-T-Stack sponsors" }).mutation(async () => {
7142
+ sponsors: os.meta({ description: "Show Better-T-Stack sponsors" }).handler(async () => {
6999
7143
  try {
7000
7144
  renderTitle();
7001
7145
  intro(pc.magenta("Better-T-Stack Sponsors"));
@@ -7005,7 +7149,7 @@ const router = t.router({
7005
7149
  handleError(error, "Failed to display sponsors");
7006
7150
  }
7007
7151
  }),
7008
- docs: t.procedure.meta({ description: "Open Better-T-Stack documentation" }).mutation(async () => {
7152
+ docs: os.meta({ description: "Open Better-T-Stack documentation" }).handler(async () => {
7009
7153
  const DOCS_URL = "https://better-t-stack.dev/docs";
7010
7154
  try {
7011
7155
  await openUrl(DOCS_URL);
@@ -7014,7 +7158,7 @@ const router = t.router({
7014
7158
  log.message(`Please visit ${DOCS_URL}`);
7015
7159
  }
7016
7160
  }),
7017
- builder: t.procedure.meta({ description: "Open the web-based stack builder" }).mutation(async () => {
7161
+ builder: os.meta({ description: "Open the web-based stack builder" }).handler(async () => {
7018
7162
  const BUILDER_URL = "https://better-t-stack.dev/new";
7019
7163
  try {
7020
7164
  await openUrl(BUILDER_URL);
@@ -7024,7 +7168,7 @@ const router = t.router({
7024
7168
  }
7025
7169
  })
7026
7170
  });
7027
- const caller = t.createCallerFactory(router)({});
7171
+ const caller = createRouterClient(router, { context: {} });
7028
7172
  function createBtsCli() {
7029
7173
  return createCli({
7030
7174
  router,
@@ -7066,9 +7210,8 @@ function createBtsCli() {
7066
7210
  * ```
7067
7211
  */
7068
7212
  async function init(projectName, options) {
7069
- const opts = options ?? {};
7070
7213
  const programmaticOpts = {
7071
- ...opts,
7214
+ ...options ?? {},
7072
7215
  verbose: true
7073
7216
  };
7074
7217
  const prev = process.env.BTS_PROGRAMMATIC;