create-better-t-stack 2.29.2 → 2.29.4

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 (3) hide show
  1. package/README.md +10 -13
  2. package/dist/index.js +224 -158
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -15,14 +15,14 @@ A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with
15
15
  Run without installing globally:
16
16
 
17
17
  ```bash
18
- # Using npm
19
- npx create-better-t-stack@latest
20
-
21
- # Using bun
18
+ # Using bun (recommended)
22
19
  bun create better-t-stack@latest
23
20
 
24
21
  # Using pnpm
25
22
  pnpm create better-t-stack@latest
23
+
24
+ # Using npm
25
+ npx create-better-t-stack@latest
26
26
  ```
27
27
 
28
28
  Follow the prompts to configure your project or use the `--yes` flag for defaults.
@@ -58,14 +58,15 @@ Options:
58
58
  --auth Include authentication
59
59
  --no-auth Exclude authentication
60
60
  --frontend <types...> Frontend types (tanstack-router, react-router, tanstack-start, next, nuxt, svelte, solid, native-nativewind, native-unistyles, none)
61
- --addons <types...> Additional addons (pwa, tauri, starlight, biome, husky, turborepo, none)
61
+ --addons <types...> Additional addons (pwa, tauri, starlight, biome, husky, turborepo, fumadocs, ultracite, oxlint, none)
62
62
  --examples <types...> Examples to include (todo, ai, none)
63
63
  --git Initialize git repository
64
64
  --no-git Skip git initialization
65
65
  --package-manager <pm> Package manager (npm, pnpm, bun)
66
66
  --install Install dependencies
67
67
  --no-install Skip installing dependencies
68
- --db-setup <setup> Database setup (turso, d1, neon, supabase, prisma-postgres, mongodb-atlas, none)
68
+ --db-setup <setup> Database setup (turso, d1, neon, supabase, prisma-postgres, mongodb-atlas, docker, none)
69
+ --web-deploy <setup> Web deployment (workers, none)
69
70
  --backend <framework> Backend framework (hono, express, elysia, next, convex, fastify, none)
70
71
  --runtime <runtime> Runtime (bun, node, workers, none)
71
72
  --api <type> API type (trpc, orpc, none)
@@ -84,7 +85,7 @@ This CLI collects anonymous usage data to help improve the tool. The data collec
84
85
 
85
86
  ### Disabling Telemetry
86
87
 
87
- You can disable telemetry by setting the `BTS_TELEMETRY` environment variable:
88
+ You can disable telemetry by setting the `BTS_TELEMETRY_DISABLED` environment variable:
88
89
 
89
90
  ```bash
90
91
  # Disable telemetry for a single run
@@ -94,10 +95,6 @@ BTS_TELEMETRY_DISABLED=1 npx create-better-t-stack my-app
94
95
  export BTS_TELEMETRY_DISABLED=1
95
96
  ```
96
97
 
97
- ### Development
98
-
99
- During development, telemetry is automatically disabled when `NODE_ENV=development`.
100
-
101
98
  ## Examples
102
99
 
103
100
  Create a project with default configuration:
@@ -118,10 +115,10 @@ Create a project with Elysia backend and Node.js runtime:
118
115
  npx create-better-t-stack my-app --backend elysia --runtime node
119
116
  ```
120
117
 
121
- Create a project with multiple frontend options:
118
+ Create a project with multiple frontend options (one web + one native):
122
119
 
123
120
  ```bash
124
- npx create-better-t-stack my-app --frontend tanstack-router native
121
+ npx create-better-t-stack my-app --frontend tanstack-router native-nativewind
125
122
  ```
126
123
 
127
124
  Create a project with examples:
package/dist/index.js CHANGED
@@ -140,15 +140,6 @@ const ADDON_COMPATIBILITY = {
140
140
  fumadocs: [],
141
141
  none: []
142
142
  };
143
- const WEB_FRAMEWORKS = [
144
- "tanstack-router",
145
- "react-router",
146
- "tanstack-start",
147
- "next",
148
- "nuxt",
149
- "svelte",
150
- "solid"
151
- ];
152
143
 
153
144
  //#endregion
154
145
  //#region src/types.ts
@@ -179,7 +170,7 @@ const RuntimeSchema = z.enum([
179
170
  "node",
180
171
  "workers",
181
172
  "none"
182
- ]).describe("Runtime environment (workers only available with hono backend and drizzle orm)");
173
+ ]).describe("Runtime environment");
183
174
  const FrontendSchema = z.enum([
184
175
  "tanstack-router",
185
176
  "react-router",
@@ -407,40 +398,180 @@ async function getAddonsToAdd(frontend, existingAddons = []) {
407
398
  return response;
408
399
  }
409
400
 
401
+ //#endregion
402
+ //#region src/utils/compatibility.ts
403
+ const WEB_FRAMEWORKS = [
404
+ "tanstack-router",
405
+ "react-router",
406
+ "tanstack-start",
407
+ "next",
408
+ "nuxt",
409
+ "svelte",
410
+ "solid"
411
+ ];
412
+
413
+ //#endregion
414
+ //#region src/utils/compatibility-rules.ts
415
+ function isWebFrontend(value) {
416
+ return WEB_FRAMEWORKS.includes(value);
417
+ }
418
+ function splitFrontends(values = []) {
419
+ const web = values.filter((f) => isWebFrontend(f));
420
+ const native = values.filter((f) => f === "native-nativewind" || f === "native-unistyles");
421
+ return {
422
+ web,
423
+ native
424
+ };
425
+ }
426
+ function ensureSingleWebAndNative(frontends) {
427
+ const { web, native } = splitFrontends(frontends);
428
+ if (web.length > 1) {
429
+ consola$1.fatal("Cannot select multiple web frameworks. Choose only one of: tanstack-router, tanstack-start, react-router, next, nuxt, svelte, solid");
430
+ process.exit(1);
431
+ }
432
+ if (native.length > 1) {
433
+ consola$1.fatal("Cannot select multiple native frameworks. Choose only one of: native-nativewind, native-unistyles");
434
+ process.exit(1);
435
+ }
436
+ }
437
+ function validateWorkersCompatibility(providedFlags, options, config) {
438
+ if (providedFlags.has("runtime") && options.runtime === "workers" && config.backend && config.backend !== "hono") {
439
+ consola$1.fatal(`Cloudflare Workers runtime (--runtime workers) is only supported with Hono backend (--backend hono). Current backend: ${config.backend}. Please use '--backend hono' or choose a different runtime.`);
440
+ process.exit(1);
441
+ }
442
+ if (providedFlags.has("backend") && config.backend && config.backend !== "hono" && config.runtime === "workers") {
443
+ consola$1.fatal(`Backend '${config.backend}' is not compatible with Cloudflare Workers runtime. Cloudflare Workers runtime is only supported with Hono backend. Please use '--backend hono' or choose a different runtime.`);
444
+ process.exit(1);
445
+ }
446
+ if (providedFlags.has("runtime") && options.runtime === "workers" && config.orm && config.orm !== "drizzle" && config.orm !== "none") {
447
+ consola$1.fatal(`Cloudflare Workers runtime (--runtime workers) is only supported with Drizzle ORM (--orm drizzle) or no ORM (--orm none). Current ORM: ${config.orm}. Please use '--orm drizzle', '--orm none', or choose a different runtime.`);
448
+ process.exit(1);
449
+ }
450
+ if (providedFlags.has("orm") && config.orm && config.orm !== "drizzle" && config.orm !== "none" && config.runtime === "workers") {
451
+ consola$1.fatal(`ORM '${config.orm}' is not compatible with Cloudflare Workers runtime. Cloudflare Workers runtime is only supported with Drizzle ORM or no ORM. Please use '--orm drizzle', '--orm none', or choose a different runtime.`);
452
+ process.exit(1);
453
+ }
454
+ if (providedFlags.has("runtime") && options.runtime === "workers" && config.database === "mongodb") {
455
+ consola$1.fatal("Cloudflare Workers runtime (--runtime workers) is not compatible with MongoDB database. MongoDB requires Prisma or Mongoose ORM, but Workers runtime only supports Drizzle ORM. Please use a different database or runtime.");
456
+ process.exit(1);
457
+ }
458
+ if (providedFlags.has("runtime") && options.runtime === "workers" && config.dbSetup === "docker") {
459
+ consola$1.fatal("Cloudflare Workers runtime (--runtime workers) is not compatible with Docker setup. Workers runtime uses serverless databases (D1) and doesn't support local Docker containers. Please use '--db-setup d1' for SQLite or choose a different runtime.");
460
+ process.exit(1);
461
+ }
462
+ if (providedFlags.has("database") && config.database === "mongodb" && config.runtime === "workers") {
463
+ consola$1.fatal("MongoDB database is not compatible with Cloudflare Workers runtime. MongoDB requires Prisma or Mongoose ORM, but Workers runtime only supports Drizzle ORM. Please use a different database or runtime.");
464
+ process.exit(1);
465
+ }
466
+ if (providedFlags.has("dbSetup") && options.dbSetup === "docker" && config.runtime === "workers") {
467
+ consola$1.fatal("Docker setup (--db-setup docker) is not compatible with Cloudflare Workers runtime. Workers runtime uses serverless databases (D1) and doesn't support local Docker containers. Please use '--db-setup d1' for SQLite or choose a different runtime.");
468
+ process.exit(1);
469
+ }
470
+ }
471
+ function coerceBackendPresets(config) {
472
+ if (config.backend === "convex") {
473
+ config.auth = false;
474
+ config.database = "none";
475
+ config.orm = "none";
476
+ config.api = "none";
477
+ config.runtime = "none";
478
+ config.dbSetup = "none";
479
+ config.examples = ["todo"];
480
+ }
481
+ if (config.backend === "none") {
482
+ config.auth = false;
483
+ config.database = "none";
484
+ config.orm = "none";
485
+ config.api = "none";
486
+ config.runtime = "none";
487
+ config.dbSetup = "none";
488
+ config.examples = [];
489
+ }
490
+ }
491
+ function incompatibleFlagsForBackend(backend, providedFlags, options) {
492
+ const list = [];
493
+ if (backend === "convex") {
494
+ if (providedFlags.has("auth") && options.auth === true) list.push("--auth");
495
+ if (providedFlags.has("database") && options.database !== "none") list.push(`--database ${options.database}`);
496
+ if (providedFlags.has("orm") && options.orm !== "none") list.push(`--orm ${options.orm}`);
497
+ if (providedFlags.has("api") && options.api !== "none") list.push(`--api ${options.api}`);
498
+ if (providedFlags.has("runtime") && options.runtime !== "none") list.push(`--runtime ${options.runtime}`);
499
+ if (providedFlags.has("dbSetup") && options.dbSetup !== "none") list.push(`--db-setup ${options.dbSetup}`);
500
+ }
501
+ if (backend === "none") {
502
+ if (providedFlags.has("auth") && options.auth === true) list.push("--auth");
503
+ if (providedFlags.has("database") && options.database !== "none") list.push(`--database ${options.database}`);
504
+ if (providedFlags.has("orm") && options.orm !== "none") list.push(`--orm ${options.orm}`);
505
+ if (providedFlags.has("api") && options.api !== "none") list.push(`--api ${options.api}`);
506
+ if (providedFlags.has("runtime") && options.runtime !== "none") list.push(`--runtime ${options.runtime}`);
507
+ if (providedFlags.has("dbSetup") && options.dbSetup !== "none") list.push(`--db-setup ${options.dbSetup}`);
508
+ if (providedFlags.has("examples") && options.examples) {
509
+ const hasNonNoneExamples = options.examples.some((ex) => ex !== "none");
510
+ if (hasNonNoneExamples) list.push("--examples");
511
+ }
512
+ }
513
+ return list;
514
+ }
515
+ function validateApiFrontendCompatibility(api, frontends = []) {
516
+ const includesNuxt = frontends.includes("nuxt");
517
+ const includesSvelte = frontends.includes("svelte");
518
+ const includesSolid = frontends.includes("solid");
519
+ if ((includesNuxt || includesSvelte || includesSolid) && api === "trpc") {
520
+ consola$1.fatal(`tRPC API is not supported with '${includesNuxt ? "nuxt" : includesSvelte ? "svelte" : "solid"}' frontend. Please use --api orpc or --api none or remove '${includesNuxt ? "nuxt" : includesSvelte ? "svelte" : "solid"}' from --frontend.`);
521
+ process.exit(1);
522
+ }
523
+ }
524
+ function isFrontendAllowedWithBackend(frontend, backend) {
525
+ if (backend === "convex" && frontend === "solid") return false;
526
+ return true;
527
+ }
528
+ function allowedApisForFrontends(frontends = []) {
529
+ const includesNuxt = frontends.includes("nuxt");
530
+ const includesSvelte = frontends.includes("svelte");
531
+ const includesSolid = frontends.includes("solid");
532
+ const base = [
533
+ "trpc",
534
+ "orpc",
535
+ "none"
536
+ ];
537
+ if (includesNuxt || includesSvelte || includesSolid) return ["orpc", "none"];
538
+ return base;
539
+ }
540
+ function isExampleTodoAllowed(backend, database) {
541
+ return !(backend !== "convex" && backend !== "none" && database === "none");
542
+ }
543
+ function isExampleAIAllowed(backend, frontends = []) {
544
+ const includesSolid = frontends.includes("solid");
545
+ if (backend === "elysia") return false;
546
+ if (includesSolid) return false;
547
+ return true;
548
+ }
549
+ function validateWebDeployRequiresWebFrontend(webDeploy, hasWebFrontendFlag) {
550
+ if (webDeploy && webDeploy !== "none" && !hasWebFrontendFlag) {
551
+ consola$1.fatal("'--web-deploy' requires a web frontend. Please select a web frontend or set '--web-deploy none'.");
552
+ process.exit(1);
553
+ }
554
+ }
555
+
410
556
  //#endregion
411
557
  //#region src/prompts/api.ts
412
558
  async function getApiChoice(Api, frontend, backend) {
413
559
  if (backend === "convex" || backend === "none") return "none";
414
- if (Api) return Api;
415
- const includesNuxt = frontend?.includes("nuxt");
416
- const includesSvelte = frontend?.includes("svelte");
417
- const includesSolid = frontend?.includes("solid");
418
- let apiOptions = [
419
- {
420
- value: "trpc",
421
- label: "tRPC",
422
- hint: "End-to-end typesafe APIs made easy"
423
- },
424
- {
425
- value: "orpc",
426
- label: "oRPC",
427
- hint: "End-to-end type-safe APIs that adhere to OpenAPI standards"
428
- },
429
- {
430
- value: "none",
431
- label: "None",
432
- hint: "No API layer (e.g. for full-stack frameworks like Next.js with Route Handlers)"
433
- }
434
- ];
435
- if (includesNuxt || includesSvelte || includesSolid) apiOptions = [{
560
+ const allowed = allowedApisForFrontends(frontend ?? []);
561
+ if (Api) return allowed.includes(Api) ? Api : allowed[0];
562
+ const apiOptions = allowed.map((a) => a === "trpc" ? {
563
+ value: "trpc",
564
+ label: "tRPC",
565
+ hint: "End-to-end typesafe APIs made easy"
566
+ } : a === "orpc" ? {
436
567
  value: "orpc",
437
568
  label: "oRPC",
438
- hint: `End-to-end type-safe APIs (Recommended for ${includesNuxt ? "Nuxt" : includesSvelte ? "Svelte" : "Solid"} frontend)`
439
- }, {
569
+ hint: "End-to-end type-safe APIs that adhere to OpenAPI standards"
570
+ } : {
440
571
  value: "none",
441
572
  label: "None",
442
- hint: "No API layer"
443
- }];
573
+ hint: "No API layer (e.g. for full-stack frameworks like Next.js with Route Handlers)"
574
+ });
444
575
  const apiType = await select({
445
576
  message: "Select API type",
446
577
  options: apiOptions,
@@ -670,21 +801,23 @@ async function getExamplesChoice(examples, database, frontends, backend, api) {
670
801
  const noFrontendSelected = !frontends || frontends.length === 0;
671
802
  if (noFrontendSelected) return [];
672
803
  let response = [];
673
- const options = [{
804
+ const options = [];
805
+ if (isExampleTodoAllowed(backend, database)) options.push({
674
806
  value: "todo",
675
807
  label: "Todo App",
676
808
  hint: "A simple CRUD example app"
677
- }];
678
- if (backend !== "elysia" && !frontends?.includes("solid")) options.push({
809
+ });
810
+ if (isExampleAIAllowed(backend, frontends ?? [])) options.push({
679
811
  value: "ai",
680
812
  label: "AI Chat",
681
813
  hint: "A simple AI chat interface using AI SDK"
682
814
  });
815
+ if (options.length === 0) return [];
683
816
  response = await multiselect({
684
817
  message: "Include examples",
685
818
  options,
686
819
  required: false,
687
- initialValues: DEFAULT_CONFIG.examples
820
+ initialValues: DEFAULT_CONFIG.examples?.filter((ex) => options.some((o) => o.value === ex))
688
821
  });
689
822
  if (isCancel(response)) {
690
823
  cancel(pc.red("Operation cancelled"));
@@ -754,10 +887,7 @@ async function getFrontendChoice(frontendOptions, backend) {
754
887
  hint: "SSR, Server Functions, API Routes and more with TanStack Router"
755
888
  }
756
889
  ];
757
- const webOptions = allWebOptions.filter((option) => {
758
- if (backend === "convex") return option.value !== "solid";
759
- return true;
760
- });
890
+ const webOptions = allWebOptions.filter((option) => isFrontendAllowedWithBackend(option.value, backend));
761
891
  const webFramework = await select({
762
892
  message: "Choose web",
763
893
  options: webOptions,
@@ -1057,6 +1187,11 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
1057
1187
 
1058
1188
  //#endregion
1059
1189
  //#region src/prompts/project-name.ts
1190
+ function isPathWithinCwd(targetPath) {
1191
+ const resolved = path.resolve(targetPath);
1192
+ const rel = path.relative(process.cwd(), resolved);
1193
+ return !rel.startsWith("..") && !path.isAbsolute(rel);
1194
+ }
1060
1195
  function validateDirectoryName(name) {
1061
1196
  if (name === ".") return void 0;
1062
1197
  const result = ProjectNameSchema.safeParse(name);
@@ -1068,7 +1203,11 @@ async function getProjectName(initialName) {
1068
1203
  if (initialName === ".") return initialName;
1069
1204
  const finalDirName = path.basename(initialName);
1070
1205
  const validationError = validateDirectoryName(finalDirName);
1071
- if (!validationError) return initialName;
1206
+ if (!validationError) {
1207
+ const projectDir = path.resolve(process.cwd(), initialName);
1208
+ if (isPathWithinCwd(projectDir)) return initialName;
1209
+ consola.error(pc.red("Project path must be within current directory"));
1210
+ }
1072
1211
  }
1073
1212
  let isValid = false;
1074
1213
  let projectPath = "";
@@ -1091,7 +1230,7 @@ async function getProjectName(initialName) {
1091
1230
  if (validationError) return validationError;
1092
1231
  if (nameToUse !== ".") {
1093
1232
  const projectDir = path.resolve(process.cwd(), nameToUse);
1094
- if (!projectDir.startsWith(process.cwd())) return "Project path must be within current directory";
1233
+ if (!isPathWithinCwd(projectDir)) return "Project path must be within current directory";
1095
1234
  }
1096
1235
  return void 0;
1097
1236
  }
@@ -1224,11 +1363,11 @@ function generateReproducibleCommand(config) {
1224
1363
  flags.push(config.git ? "--git" : "--no-git");
1225
1364
  flags.push(`--package-manager ${config.packageManager}`);
1226
1365
  flags.push(config.install ? "--install" : "--no-install");
1227
- let baseCommand = "";
1366
+ let baseCommand = "npx create-better-t-stack@latest";
1228
1367
  const pkgManager = config.packageManager;
1229
- if (pkgManager === "npm") baseCommand = "npx create-better-t-stack@latest";
1368
+ if (pkgManager === "bun") baseCommand = "bun create better-t-stack@latest";
1230
1369
  else if (pkgManager === "pnpm") baseCommand = "pnpm create better-t-stack@latest";
1231
- else if (pkgManager === "bun") baseCommand = "bun create better-t-stack@latest";
1370
+ else if (pkgManager === "npm") baseCommand = "npx create-better-t-stack@latest";
1232
1371
  const projectPathArg = config.relativePath ? ` ${config.relativePath}` : "";
1233
1372
  return `${baseCommand}${projectPathArg} ${flags.join(" ")}`;
1234
1373
  }
@@ -1422,18 +1561,10 @@ function processAndValidateFlags(options, providedFlags, projectName) {
1422
1561
  config.frontend = [];
1423
1562
  } else {
1424
1563
  const validOptions = options.frontend.filter((f) => f !== "none");
1425
- const webFrontends = validOptions.filter((f) => WEB_FRAMEWORKS.includes(f));
1426
- const nativeFrontends = validOptions.filter((f) => f === "native-nativewind" || f === "native-unistyles");
1427
- if (webFrontends.length > 1) {
1428
- consola$1.fatal("Cannot select multiple web frameworks. Choose only one of: tanstack-router, tanstack-start, react-router, next, nuxt, svelte, solid");
1429
- process.exit(1);
1430
- }
1431
- if (nativeFrontends.length > 1) {
1432
- consola$1.fatal("Cannot select multiple native frameworks. Choose only one of: native-nativewind, native-unistyles");
1433
- process.exit(1);
1434
- }
1564
+ ensureSingleWebAndNative(validOptions);
1435
1565
  config.frontend = validOptions;
1436
1566
  }
1567
+ if (providedFlags.has("api") && providedFlags.has("frontend") && config.api && config.frontend && config.frontend.length > 0) validateApiFrontendCompatibility(config.api, config.frontend);
1437
1568
  if (options.addons && options.addons.length > 0) if (options.addons.includes("none")) {
1438
1569
  if (options.addons.length > 1) {
1439
1570
  consola$1.fatal(`Cannot combine 'none' with other addons.`);
@@ -1451,159 +1582,94 @@ function processAndValidateFlags(options, providedFlags, projectName) {
1451
1582
  config.examples = options.examples.filter((ex) => ex !== "none");
1452
1583
  if (options.examples.includes("none") && config.backend !== "convex") config.examples = [];
1453
1584
  }
1454
- if (config.backend === "convex") {
1455
- const incompatibleFlags = [];
1456
- if (providedFlags.has("auth") && options.auth === true) incompatibleFlags.push("--auth");
1457
- if (providedFlags.has("database") && options.database !== "none") incompatibleFlags.push(`--database ${options.database}`);
1458
- if (providedFlags.has("orm") && options.orm !== "none") incompatibleFlags.push(`--orm ${options.orm}`);
1459
- if (providedFlags.has("api") && options.api !== "none") incompatibleFlags.push(`--api ${options.api}`);
1460
- if (providedFlags.has("runtime") && options.runtime !== "none") incompatibleFlags.push(`--runtime ${options.runtime}`);
1461
- if (providedFlags.has("dbSetup") && options.dbSetup !== "none") incompatibleFlags.push(`--db-setup ${options.dbSetup}`);
1585
+ if (config.backend === "convex" || config.backend === "none") {
1586
+ const incompatibleFlags = incompatibleFlagsForBackend(config.backend, providedFlags, options);
1462
1587
  if (incompatibleFlags.length > 0) {
1463
- consola$1.fatal(`The following flags are incompatible with '--backend convex': ${incompatibleFlags.join(", ")}. Please remove them.`);
1588
+ consola$1.fatal(`The following flags are incompatible with '--backend ${config.backend}': ${incompatibleFlags.join(", ")}. Please remove them.`);
1464
1589
  process.exit(1);
1465
1590
  }
1466
- if (providedFlags.has("frontend") && options.frontend) {
1591
+ if (config.backend === "convex" && providedFlags.has("frontend") && options.frontend) {
1467
1592
  const incompatibleFrontends = options.frontend.filter((f) => f === "solid");
1468
1593
  if (incompatibleFrontends.length > 0) {
1469
1594
  consola$1.fatal(`The following frontends are not compatible with '--backend convex': ${incompatibleFrontends.join(", ")}. Please choose a different frontend or backend.`);
1470
1595
  process.exit(1);
1471
1596
  }
1472
1597
  }
1473
- config.auth = false;
1474
- config.database = "none";
1475
- config.orm = "none";
1476
- config.api = "none";
1477
- config.runtime = "none";
1478
- config.dbSetup = "none";
1479
- config.examples = ["todo"];
1480
- } else if (config.backend === "none") {
1481
- const incompatibleFlags = [];
1482
- if (providedFlags.has("auth") && options.auth === true) incompatibleFlags.push("--auth");
1483
- if (providedFlags.has("database") && options.database !== "none") incompatibleFlags.push(`--database ${options.database}`);
1484
- if (providedFlags.has("orm") && options.orm !== "none") incompatibleFlags.push(`--orm ${options.orm}`);
1485
- if (providedFlags.has("api") && options.api !== "none") incompatibleFlags.push(`--api ${options.api}`);
1486
- if (providedFlags.has("runtime") && options.runtime !== "none") incompatibleFlags.push(`--runtime ${options.runtime}`);
1487
- if (providedFlags.has("dbSetup") && options.dbSetup !== "none") incompatibleFlags.push(`--db-setup ${options.dbSetup}`);
1488
- if (providedFlags.has("examples") && options.examples) {
1489
- const hasNonNoneExamples = options.examples.some((ex) => ex !== "none");
1490
- if (hasNonNoneExamples) incompatibleFlags.push("--examples");
1491
- }
1492
- if (incompatibleFlags.length > 0) {
1493
- consola$1.fatal(`The following flags are incompatible with '--backend none': ${incompatibleFlags.join(", ")}. Please remove them.`);
1494
- process.exit(1);
1495
- }
1496
- config.auth = false;
1497
- config.database = "none";
1498
- config.orm = "none";
1499
- config.api = "none";
1500
- config.runtime = "none";
1501
- config.dbSetup = "none";
1502
- config.examples = [];
1598
+ coerceBackendPresets(config);
1503
1599
  }
1504
- if (config.orm === "mongoose" && config.database !== "mongodb") {
1600
+ if (providedFlags.has("orm") && providedFlags.has("database") && config.orm === "mongoose" && config.database !== "mongodb") {
1505
1601
  consola$1.fatal("Mongoose ORM requires MongoDB database. Please use '--database mongodb' or choose a different ORM.");
1506
1602
  process.exit(1);
1507
1603
  }
1508
- if (config.database === "mongodb" && config.orm && config.orm !== "mongoose" && config.orm !== "prisma") {
1604
+ if (providedFlags.has("database") && providedFlags.has("orm") && config.database === "mongodb" && config.orm && config.orm !== "mongoose" && config.orm !== "prisma") {
1509
1605
  consola$1.fatal("MongoDB database requires Mongoose or Prisma ORM. Please use '--orm mongoose' or '--orm prisma' or choose a different database.");
1510
1606
  process.exit(1);
1511
1607
  }
1512
- if (config.orm === "drizzle" && config.database === "mongodb") {
1608
+ if (providedFlags.has("orm") && providedFlags.has("database") && config.orm === "drizzle" && config.database === "mongodb") {
1513
1609
  consola$1.fatal("Drizzle ORM does not support MongoDB. Please use '--orm mongoose' or '--orm prisma' or choose a different database.");
1514
1610
  process.exit(1);
1515
1611
  }
1516
- if (config.database && config.database !== "none" && config.orm === "none") {
1612
+ if (providedFlags.has("database") && providedFlags.has("orm") && config.database && config.database !== "none" && config.orm === "none") {
1517
1613
  consola$1.fatal("Database selection requires an ORM. Please choose '--orm drizzle', '--orm prisma', or '--orm mongoose'.");
1518
1614
  process.exit(1);
1519
1615
  }
1520
- if (config.orm && config.orm !== "none" && config.database === "none") {
1616
+ if (providedFlags.has("orm") && providedFlags.has("database") && config.orm && config.orm !== "none" && config.database === "none") {
1521
1617
  consola$1.fatal("ORM selection requires a database. Please choose a database or set '--orm none'.");
1522
1618
  process.exit(1);
1523
1619
  }
1524
- if (config.auth && config.database === "none") {
1620
+ if (providedFlags.has("auth") && providedFlags.has("database") && config.auth && config.database === "none") {
1525
1621
  consola$1.fatal("Authentication requires a database. Please choose a database or set '--no-auth'.");
1526
1622
  process.exit(1);
1527
1623
  }
1528
- if (config.dbSetup && config.dbSetup !== "none" && config.database === "none") {
1624
+ if (providedFlags.has("dbSetup") && providedFlags.has("database") && config.dbSetup && config.dbSetup !== "none" && config.database === "none") {
1529
1625
  consola$1.fatal("Database setup requires a database. Please choose a database or set '--db-setup none'.");
1530
1626
  process.exit(1);
1531
1627
  }
1532
- if (config.dbSetup === "turso" && config.database !== "sqlite") {
1628
+ if (providedFlags.has("dbSetup") && (config.database ? providedFlags.has("database") : true) && config.dbSetup === "turso" && config.database !== "sqlite") {
1533
1629
  consola$1.fatal("Turso setup requires SQLite database. Please use '--database sqlite' or choose a different setup.");
1534
1630
  process.exit(1);
1535
1631
  }
1536
- if (config.dbSetup === "neon" && config.database !== "postgres") {
1632
+ if (providedFlags.has("dbSetup") && (config.database ? providedFlags.has("database") : true) && config.dbSetup === "neon" && config.database !== "postgres") {
1537
1633
  consola$1.fatal("Neon setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup.");
1538
1634
  process.exit(1);
1539
1635
  }
1540
- if (config.dbSetup === "prisma-postgres" && config.database !== "postgres") {
1636
+ if (providedFlags.has("dbSetup") && (config.database ? providedFlags.has("database") : true) && config.dbSetup === "prisma-postgres" && config.database !== "postgres") {
1541
1637
  consola$1.fatal("Prisma PostgreSQL setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup.");
1542
1638
  process.exit(1);
1543
1639
  }
1544
- if (config.dbSetup === "mongodb-atlas" && config.database !== "mongodb") {
1640
+ if (providedFlags.has("dbSetup") && (config.database ? providedFlags.has("database") : true) && config.dbSetup === "mongodb-atlas" && config.database !== "mongodb") {
1545
1641
  consola$1.fatal("MongoDB Atlas setup requires MongoDB database. Please use '--database mongodb' or choose a different setup.");
1546
1642
  process.exit(1);
1547
1643
  }
1548
- if (config.dbSetup === "supabase" && config.database !== "postgres") {
1644
+ if (providedFlags.has("dbSetup") && (config.database ? providedFlags.has("database") : true) && config.dbSetup === "supabase" && config.database !== "postgres") {
1549
1645
  consola$1.fatal("Supabase setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup.");
1550
1646
  process.exit(1);
1551
1647
  }
1552
1648
  if (config.dbSetup === "d1") {
1553
- if (config.database !== "sqlite") {
1554
- consola$1.fatal("Cloudflare D1 setup requires SQLite database. Please use '--database sqlite' or choose a different setup.");
1555
- process.exit(1);
1649
+ if (providedFlags.has("dbSetup") && providedFlags.has("database") || providedFlags.has("dbSetup") && !config.database) {
1650
+ if (config.database !== "sqlite") {
1651
+ consola$1.fatal("Cloudflare D1 setup requires SQLite database. Please use '--database sqlite' or choose a different setup.");
1652
+ process.exit(1);
1653
+ }
1556
1654
  }
1557
- if (config.runtime !== "workers") {
1558
- consola$1.fatal("Cloudflare D1 setup requires the Cloudflare Workers runtime. Please use '--runtime workers' or choose a different setup.");
1559
- process.exit(1);
1655
+ if (providedFlags.has("dbSetup") && providedFlags.has("runtime") || providedFlags.has("dbSetup") && !config.runtime) {
1656
+ if (config.runtime !== "workers") {
1657
+ consola$1.fatal("Cloudflare D1 setup requires the Cloudflare Workers runtime. Please use '--runtime workers' or choose a different setup.");
1658
+ process.exit(1);
1659
+ }
1560
1660
  }
1561
1661
  }
1562
- if (config.dbSetup === "docker" && config.database === "sqlite") {
1662
+ if (providedFlags.has("dbSetup") && providedFlags.has("database") && config.dbSetup === "docker" && config.database === "sqlite") {
1563
1663
  consola$1.fatal("Docker setup is not compatible with SQLite database. SQLite is file-based and doesn't require Docker. Please use '--database postgres', '--database mysql', '--database mongodb', or choose a different setup.");
1564
1664
  process.exit(1);
1565
1665
  }
1566
- if (config.dbSetup === "docker" && config.runtime === "workers") {
1666
+ if (providedFlags.has("dbSetup") && providedFlags.has("runtime") && config.dbSetup === "docker" && config.runtime === "workers") {
1567
1667
  consola$1.fatal("Docker setup is not compatible with Cloudflare Workers runtime. Workers runtime uses serverless databases (D1) and doesn't support local Docker containers. Please use '--db-setup d1' for SQLite or choose a different runtime.");
1568
1668
  process.exit(1);
1569
1669
  }
1570
- if (providedFlags.has("runtime") && options.runtime === "workers" && config.backend && config.backend !== "hono") {
1571
- consola$1.fatal(`Cloudflare Workers runtime (--runtime workers) is only supported with Hono backend (--backend hono). Current backend: ${config.backend}. Please use '--backend hono' or choose a different runtime.`);
1572
- process.exit(1);
1573
- }
1574
- if (providedFlags.has("backend") && config.backend && config.backend !== "hono" && config.runtime === "workers") {
1575
- consola$1.fatal(`Backend '${config.backend}' is not compatible with Cloudflare Workers runtime. Cloudflare Workers runtime is only supported with Hono backend. Please use '--backend hono' or choose a different runtime.`);
1576
- process.exit(1);
1577
- }
1578
- if (providedFlags.has("runtime") && options.runtime === "workers" && config.orm && config.orm !== "drizzle" && config.orm !== "none") {
1579
- consola$1.fatal(`Cloudflare Workers runtime (--runtime workers) is only supported with Drizzle ORM (--orm drizzle) or no ORM (--orm none). Current ORM: ${config.orm}. Please use '--orm drizzle', '--orm none', or choose a different runtime.`);
1580
- process.exit(1);
1581
- }
1582
- if (providedFlags.has("orm") && config.orm && config.orm !== "drizzle" && config.orm !== "none" && config.runtime === "workers") {
1583
- consola$1.fatal(`ORM '${config.orm}' is not compatible with Cloudflare Workers runtime. Cloudflare Workers runtime is only supported with Drizzle ORM or no ORM. Please use '--orm drizzle', '--orm none', or choose a different runtime.`);
1584
- process.exit(1);
1585
- }
1586
- if (providedFlags.has("runtime") && options.runtime === "workers" && config.database === "mongodb") {
1587
- consola$1.fatal("Cloudflare Workers runtime (--runtime workers) is not compatible with MongoDB database. MongoDB requires Prisma or Mongoose ORM, but Workers runtime only supports Drizzle ORM. Please use a different database or runtime.");
1588
- process.exit(1);
1589
- }
1590
- if (providedFlags.has("runtime") && options.runtime === "workers" && config.dbSetup === "docker") {
1591
- consola$1.fatal("Cloudflare Workers runtime (--runtime workers) is not compatible with Docker setup. Workers runtime uses serverless databases (D1) and doesn't support local Docker containers. Please use '--db-setup d1' for SQLite or choose a different runtime.");
1592
- process.exit(1);
1593
- }
1594
- if (providedFlags.has("database") && config.database === "mongodb" && config.runtime === "workers") {
1595
- consola$1.fatal("MongoDB database is not compatible with Cloudflare Workers runtime. MongoDB requires Prisma or Mongoose ORM, but Workers runtime only supports Drizzle ORM. Please use a different database or runtime.");
1596
- process.exit(1);
1597
- }
1598
- if (providedFlags.has("db-setup") && options.dbSetup === "docker" && config.runtime === "workers") {
1599
- consola$1.fatal("Docker setup (--db-setup docker) is not compatible with Cloudflare Workers runtime. Workers runtime uses serverless databases (D1) and doesn't support local Docker containers. Please use '--db-setup d1' for SQLite or choose a different runtime.");
1600
- process.exit(1);
1601
- }
1602
- const hasWebFrontendFlag = (config.frontend ?? []).some((f) => WEB_FRAMEWORKS.includes(f));
1603
- if (config.webDeploy && config.webDeploy !== "none" && !hasWebFrontendFlag && providedFlags.has("frontend")) {
1604
- consola$1.fatal("'--web-deploy' requires a web frontend. Please select a web frontend or set '--web-deploy none'.");
1605
- process.exit(1);
1606
- }
1670
+ validateWorkersCompatibility(providedFlags, options, config);
1671
+ const hasWebFrontendFlag = (config.frontend ?? []).some((f) => isWebFrontend(f));
1672
+ validateWebDeployRequiresWebFrontend(config.webDeploy, hasWebFrontendFlag);
1607
1673
  return config;
1608
1674
  }
1609
1675
  function getProvidedFlags(options) {
@@ -1779,7 +1845,7 @@ async function setupFumadocs(config) {
1779
1845
  process.exit(0);
1780
1846
  }
1781
1847
  const templateArg = TEMPLATES[template].value;
1782
- const commandWithArgs = `create-fumadocs-app@latest fumadocs --template ${templateArg} --src --no-install --pm ${packageManager} --no-eslint`;
1848
+ const commandWithArgs = `create-fumadocs-app@latest fumadocs --template ${templateArg} --src --no-install --pm ${packageManager} --no-eslint --no-git`;
1783
1849
  const fumadocsInitCommand = getPackageExecutionCommand(packageManager, commandWithArgs);
1784
1850
  await execa(fumadocsInitCommand, {
1785
1851
  cwd: path.join(projectDir, "apps"),
@@ -4417,7 +4483,7 @@ async function setupWorkersRuntime(serverDir) {
4417
4483
  };
4418
4484
  await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
4419
4485
  await addPackageDependency({
4420
- devDependencies: ["wrangler"],
4486
+ devDependencies: ["wrangler", "@types/node"],
4421
4487
  projectDir: serverDir
4422
4488
  });
4423
4489
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-better-t-stack",
3
- "version": "2.29.2",
3
+ "version": "2.29.4",
4
4
  "description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -56,7 +56,7 @@
56
56
  "@clack/prompts": "^0.11.0",
57
57
  "consola": "^3.4.2",
58
58
  "execa": "^9.6.0",
59
- "fs-extra": "^11.3.0",
59
+ "fs-extra": "^11.3.1",
60
60
  "globby": "^14.1.0",
61
61
  "gradient-string": "^3.0.0",
62
62
  "handlebars": "^4.7.8",
@@ -64,7 +64,7 @@
64
64
  "picocolors": "^1.1.1",
65
65
  "trpc-cli": "^0.10.2",
66
66
  "ts-morph": "^26.0.0",
67
- "zod": "^4.0.14"
67
+ "zod": "^4.0.15"
68
68
  },
69
69
  "devDependencies": {
70
70
  "@types/fs-extra": "^11.0.4",