create-better-t-stack 3.26.1 → 3.27.1

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.
package/README.md CHANGED
@@ -217,6 +217,12 @@ Create a Cloudflare Workers project:
217
217
  npx create-better-t-stack --backend hono --runtime workers --database sqlite --orm drizzle --db-setup d1
218
218
  ```
219
219
 
220
+ Create a self-hosted fullstack project on Cloudflare with D1:
221
+
222
+ ```bash
223
+ npx create-better-t-stack --backend self --frontend next --api trpc --database sqlite --orm drizzle --db-setup d1 --web-deploy cloudflare
224
+ ```
225
+
220
226
  Create a minimal API-only project:
221
227
 
222
228
  ```bash
@@ -232,7 +238,8 @@ npx create-better-t-stack --frontend none --backend hono --api trpc --database n
232
238
  - **Database 'none'**: Disables database setup and requires ORM to be `none`.
233
239
  - **ORM 'none'**: Can be used when you want to handle database operations manually or use a different ORM.
234
240
  - **Runtime 'none'**: Only available with Convex backend, backend `none`, or backend `self`.
235
- - **Cloudflare Workers runtime**: Only compatible with Hono backend, Drizzle ORM (or no ORM), and SQLite database (with D1 setup). Not compatible with MongoDB.
241
+ - **Cloudflare Workers runtime**: Only compatible with Hono backend. If a database is used, MongoDB is not supported.
242
+ - **Cloudflare D1 setup**: Requires `sqlite` and either `--runtime workers --server-deploy cloudflare` or `--backend self --web-deploy cloudflare`. For `backend self`, D1 is supported on `next`, `tanstack-start`, `nuxt`, and `astro`.
236
243
  - **Addons 'none'**: Skips all addons.
237
244
  - **Examples 'none'**: Skips all example implementations (todo, AI chat).
238
245
  - **Nuxt, Svelte, SolidJS, and Astro** frontends are only compatible with oRPC API layer
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { _ as types_exports, i as SchemaNameSchema, l as create, m as getSchemaResult, s as add, u as createBtsCli, v as getLatestCLIVersion } from "./src-Kus6HC8q.mjs";
2
+ import { _ as types_exports, i as SchemaNameSchema, l as create, m as getSchemaResult, s as add, u as createBtsCli, v as getLatestCLIVersion } from "./src-nmBsmday.mjs";
3
3
  import z from "zod";
4
4
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
5
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import { C as ProjectCreationError, S as DirectoryConflictError, T as ValidationError, a as TEMPLATE_COUNT, b as CompatibilityError, c as builder, d as createVirtual, f as docs, g as sponsors, h as router, i as SchemaNameSchema, l as create, m as getSchemaResult, n as GeneratorError, o as VirtualFileSystem, p as generate, r as Result, s as add, t as EMBEDDED_TEMPLATES, u as createBtsCli, w as UserCancelledError, x as DatabaseSetupError, y as CLIError } from "./src-Kus6HC8q.mjs";
2
+ import { C as ProjectCreationError, S as DirectoryConflictError, T as ValidationError, a as TEMPLATE_COUNT, b as CompatibilityError, c as builder, d as createVirtual, f as docs, g as sponsors, h as router, i as SchemaNameSchema, l as create, m as getSchemaResult, n as GeneratorError, o as VirtualFileSystem, p as generate, r as Result, s as add, t as EMBEDDED_TEMPLATES, u as createBtsCli, w as UserCancelledError, x as DatabaseSetupError, y as CLIError } from "./src-nmBsmday.mjs";
3
3
  export { CLIError, CompatibilityError, DatabaseSetupError, DirectoryConflictError, EMBEDDED_TEMPLATES, GeneratorError, ProjectCreationError, Result, SchemaNameSchema, TEMPLATE_COUNT, UserCancelledError, ValidationError, VirtualFileSystem, add, builder, create, createBtsCli, createVirtual, docs, generate, getSchemaResult, router, sponsors };
@@ -783,7 +783,6 @@ function validateWorkersCompatibility(providedFlags, options, config) {
783
783
  if (providedFlags.has("runtime") && options.runtime === "workers" && config.backend && config.backend !== "hono") return validationErr$1(`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.`);
784
784
  if (providedFlags.has("backend") && config.backend && config.backend !== "hono" && config.runtime === "workers") return validationErr$1(`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.`);
785
785
  if (providedFlags.has("runtime") && options.runtime === "workers" && config.database === "mongodb") return validationErr$1("Cloudflare Workers runtime (--runtime workers) is not compatible with MongoDB database. MongoDB requires Prisma or Mongoose ORM, but Workers runtime only supports Drizzle or Prisma ORM. Please use a different database or runtime.");
786
- if (providedFlags.has("runtime") && options.runtime === "workers" && config.dbSetup === "docker") return validationErr$1("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.");
787
786
  if (providedFlags.has("database") && config.database === "mongodb" && config.runtime === "workers") return validationErr$1("MongoDB database is not compatible with Cloudflare Workers runtime. MongoDB requires Prisma or Mongoose ORM, but Workers runtime only supports Drizzle or Prisma ORM. Please use a different database or runtime.");
788
787
  return Result.ok(void 0);
789
788
  }
@@ -3212,7 +3211,11 @@ async function addHandlerInternal(input) {
3212
3211
  async function getApiChoice(Api, frontend, backend) {
3213
3212
  if (backend === "convex" || backend === "none") return "none";
3214
3213
  const allowed = allowedApisForFrontends(frontend ?? []);
3215
- if (Api) return allowed.includes(Api) ? Api : allowed[0];
3214
+ if (Api) {
3215
+ const compat = validateApiFrontendCompatibility(Api, frontend ?? []);
3216
+ if (compat.isErr()) throw compat.error;
3217
+ return Api;
3218
+ }
3216
3219
  const apiOptions = allowed.map((a) => a === "trpc" ? {
3217
3220
  value: "trpc",
3218
3221
  label: "tRPC",
@@ -3392,7 +3395,7 @@ async function getDBSetupChoice(databaseType, dbSetup, _orm, backend, runtime) {
3392
3395
  label: "Turso",
3393
3396
  hint: "SQLite for Production. Powered by libSQL"
3394
3397
  },
3395
- ...runtime === "workers" ? [{
3398
+ ...runtime === "workers" || backend === "self" ? [{
3396
3399
  value: "d1",
3397
3400
  label: "Cloudflare D1",
3398
3401
  hint: "Cloudflare's managed, serverless database with SQLite's SQL semantics"
@@ -3701,6 +3704,209 @@ async function navigableGroup(prompts, opts) {
3701
3704
  return results;
3702
3705
  }
3703
3706
  //#endregion
3707
+ //#region src/utils/config-validation.ts
3708
+ function validationErr(message) {
3709
+ return Result.err(new ValidationError({ message }));
3710
+ }
3711
+ function hasResolvedWorkersD1Target(config) {
3712
+ return config.backend === "hono" && config.runtime === "workers" && config.serverDeploy === "cloudflare";
3713
+ }
3714
+ function hasResolvedSelfCloudflareD1Target(config) {
3715
+ return config.backend === "self" && config.runtime === "none" && config.webDeploy === "cloudflare";
3716
+ }
3717
+ function canResolveWorkersD1Target(config) {
3718
+ return (config.backend === void 0 || config.backend === "hono") && (config.runtime === void 0 || config.runtime === "workers") && (config.serverDeploy === void 0 || config.serverDeploy === "cloudflare");
3719
+ }
3720
+ function canResolveSelfCloudflareD1Target(config) {
3721
+ return (config.backend === void 0 || config.backend === "self") && (config.runtime === void 0 || config.runtime === "none") && (config.webDeploy === void 0 || config.webDeploy === "cloudflare");
3722
+ }
3723
+ /**
3724
+ * Pure ORM + database compatibility check. Used by the flag-path
3725
+ * validator below and by the orm prompt directly (for flag+prompt
3726
+ * combos where one value came from a flag and the other from a
3727
+ * prompt).
3728
+ */
3729
+ function validateOrmDatabaseCompat(orm, database) {
3730
+ if (orm === "mongoose" && database && database !== "mongodb") return validationErr("Mongoose ORM requires MongoDB database. Please use '--database mongodb' or choose a different ORM.");
3731
+ if (orm === "drizzle" && database === "mongodb") return validationErr("Drizzle ORM does not support MongoDB. Please use '--orm mongoose' or '--orm prisma' or choose a different database.");
3732
+ if (database === "mongodb" && orm && orm !== "mongoose" && orm !== "prisma" && orm !== "none") return validationErr("MongoDB database requires Mongoose or Prisma ORM. Please use '--orm mongoose' or '--orm prisma' or choose a different database.");
3733
+ if (database && database !== "none" && orm === "none") return validationErr("Database selection requires an ORM. Please choose '--orm drizzle', '--orm prisma', or '--orm mongoose'.");
3734
+ if (orm && orm !== "none" && database === "none") return validationErr("ORM selection requires a database. Please choose a database or set '--orm none'.");
3735
+ return Result.ok(void 0);
3736
+ }
3737
+ function validateDatabaseOrmAuth(cfg, flags) {
3738
+ const has = (k) => flags ? flags.has(k) : true;
3739
+ if (!has("orm") || !has("database")) return Result.ok(void 0);
3740
+ return validateOrmDatabaseCompat(cfg.orm, cfg.database);
3741
+ }
3742
+ function validateDatabaseSetup(config, providedFlags) {
3743
+ const { dbSetup, database, runtime } = config;
3744
+ if (providedFlags.has("dbSetup") && providedFlags.has("database") && dbSetup && dbSetup !== "none" && database === "none") return validationErr("Database setup requires a database. Please choose a database or set '--db-setup none'.");
3745
+ const setupValidations = {
3746
+ turso: {
3747
+ database: "sqlite",
3748
+ errorMessage: "Turso setup requires SQLite database. Please use '--database sqlite' or choose a different setup."
3749
+ },
3750
+ neon: {
3751
+ database: "postgres",
3752
+ errorMessage: "Neon setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup."
3753
+ },
3754
+ "prisma-postgres": {
3755
+ database: "postgres",
3756
+ errorMessage: "Prisma PostgreSQL setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup."
3757
+ },
3758
+ planetscale: { errorMessage: "PlanetScale setup requires PostgreSQL or MySQL database. Please use '--database postgres' or '--database mysql' or choose a different setup." },
3759
+ "mongodb-atlas": {
3760
+ database: "mongodb",
3761
+ errorMessage: "MongoDB Atlas setup requires MongoDB database. Please use '--database mongodb' or choose a different setup."
3762
+ },
3763
+ supabase: {
3764
+ database: "postgres",
3765
+ errorMessage: "Supabase setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup."
3766
+ },
3767
+ d1: {
3768
+ database: "sqlite",
3769
+ errorMessage: "Cloudflare D1 setup requires SQLite database."
3770
+ },
3771
+ docker: { errorMessage: "Docker setup is not compatible with SQLite database or Cloudflare Workers runtime." },
3772
+ none: { errorMessage: "" }
3773
+ };
3774
+ if (dbSetup && dbSetup !== "none") {
3775
+ const validation = setupValidations[dbSetup];
3776
+ if (dbSetup === "planetscale") {
3777
+ if (database !== "postgres" && database !== "mysql") return validationErr(validation.errorMessage);
3778
+ } else if (validation.database && database !== validation.database) return validationErr(validation.errorMessage);
3779
+ if (validation.runtime && runtime !== validation.runtime) return validationErr(validation.errorMessage);
3780
+ if (dbSetup === "d1") {
3781
+ const isWorkersTarget = hasResolvedWorkersD1Target(config);
3782
+ const isSelfCloudflareTarget = hasResolvedSelfCloudflareD1Target(config);
3783
+ const canResolveWorkersTarget = canResolveWorkersD1Target(config);
3784
+ const canResolveSelfCloudflareTarget = canResolveSelfCloudflareD1Target(config);
3785
+ if (!isWorkersTarget && !isSelfCloudflareTarget && !canResolveWorkersTarget && !canResolveSelfCloudflareTarget) return validationErr("Cloudflare D1 setup requires SQLite database and either Cloudflare Workers runtime with server deployment or backend 'self' with Cloudflare web deployment.");
3786
+ }
3787
+ if (dbSetup === "docker") {
3788
+ if (database === "sqlite") return validationErr("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.");
3789
+ if (runtime === "workers") return validationErr("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.");
3790
+ }
3791
+ }
3792
+ return Result.ok(void 0);
3793
+ }
3794
+ function validateConvexConstraints(config, providedFlags) {
3795
+ const { backend } = config;
3796
+ if (backend !== "convex") return Result.ok(void 0);
3797
+ const has = (k) => providedFlags.has(k);
3798
+ if (has("runtime") && config.runtime !== "none") return validationErr("Convex backend requires '--runtime none'. Please remove the --runtime flag or set it to 'none'.");
3799
+ if (has("database") && config.database !== "none") return validationErr("Convex backend requires '--database none'. Please remove the --database flag or set it to 'none'.");
3800
+ if (has("orm") && config.orm !== "none") return validationErr("Convex backend requires '--orm none'. Please remove the --orm flag or set it to 'none'.");
3801
+ if (has("api") && config.api !== "none") return validationErr("Convex backend requires '--api none'. Please remove the --api flag or set it to 'none'.");
3802
+ if (has("dbSetup") && config.dbSetup !== "none") return validationErr("Convex backend requires '--db-setup none'. Please remove the --db-setup flag or set it to 'none'.");
3803
+ if (has("serverDeploy") && config.serverDeploy !== "none") return validationErr("Convex backend requires '--server-deploy none'. Please remove the --server-deploy flag or set it to 'none'.");
3804
+ if (has("auth") && config.auth === "better-auth") {
3805
+ const incompatibleFrontends = config.frontend?.filter((f) => CONVEX_BETTER_AUTH_INCOMPATIBLE_FRONTENDS.includes(f)) ?? [];
3806
+ const hasSupportedFrontend = supportsConvexBetterAuth(config.frontend);
3807
+ if (incompatibleFrontends.length > 0) return validationErr(`Better Auth with '--backend convex' is not compatible with the following frontends: ${incompatibleFrontends.join(", ")}. Please use a React-based web frontend (next, tanstack-start, tanstack-router, react-router), a supported native frontend, or choose a different auth provider.`);
3808
+ if (!hasSupportedFrontend) return validationErr(`Better Auth with '--backend convex' requires a supported frontend (${CONVEX_BETTER_AUTH_SUPPORTED_FRONTENDS.join(", ")}).`);
3809
+ }
3810
+ return Result.ok(void 0);
3811
+ }
3812
+ function validateBackendNoneConstraints(config, providedFlags) {
3813
+ const { backend } = config;
3814
+ if (backend !== "none") return Result.ok(void 0);
3815
+ const has = (k) => providedFlags.has(k);
3816
+ if (has("runtime") && config.runtime !== "none") return validationErr("Backend 'none' requires '--runtime none'. Please remove the --runtime flag or set it to 'none'.");
3817
+ if (has("database") && config.database !== "none") return validationErr("Backend 'none' requires '--database none'. Please remove the --database flag or set it to 'none'.");
3818
+ if (has("orm") && config.orm !== "none") return validationErr("Backend 'none' requires '--orm none'. Please remove the --orm flag or set it to 'none'.");
3819
+ if (has("api") && config.api !== "none") return validationErr("Backend 'none' requires '--api none'. Please remove the --api flag or set it to 'none'.");
3820
+ if (has("auth") && config.auth !== "none") return validationErr("Backend 'none' requires '--auth none'. Please remove the --auth flag or set it to 'none'.");
3821
+ if (has("payments") && config.payments !== "none") return validationErr("Backend 'none' requires '--payments none'. Please remove the --payments flag or set it to 'none'.");
3822
+ if (has("dbSetup") && config.dbSetup !== "none") return validationErr("Backend 'none' requires '--db-setup none'. Please remove the --db-setup flag or set it to 'none'.");
3823
+ if (has("serverDeploy") && config.serverDeploy !== "none") return validationErr("Backend 'none' requires '--server-deploy none'. Please remove the --server-deploy flag or set it to 'none'.");
3824
+ return Result.ok(void 0);
3825
+ }
3826
+ function validateSelfBackendConstraints(config, providedFlags) {
3827
+ const { backend } = config;
3828
+ if (backend !== "self") return Result.ok(void 0);
3829
+ const has = (k) => providedFlags.has(k);
3830
+ if (has("runtime") && config.runtime !== "none") return validationErr("Backend 'self' (fullstack) requires '--runtime none'. Please remove the --runtime flag or set it to 'none'.");
3831
+ return Result.ok(void 0);
3832
+ }
3833
+ function validateBackendConstraints(config, providedFlags, options) {
3834
+ const { backend } = config;
3835
+ if (config.auth === "clerk" && config.frontend) {
3836
+ const incompatibleFrontends = config.frontend.filter((f) => [
3837
+ "nuxt",
3838
+ "svelte",
3839
+ "solid",
3840
+ "astro"
3841
+ ].includes(f));
3842
+ if (incompatibleFrontends.length > 0) return validationErr(`Clerk authentication is not compatible with the following frontends: ${incompatibleFrontends.join(", ")}. Please choose a different frontend or auth provider.`);
3843
+ }
3844
+ if (providedFlags.has("backend") && backend && backend !== "convex" && backend !== "none" && backend !== "self") {
3845
+ if (providedFlags.has("runtime") && options.runtime === "none") return validationErr("'--runtime none' is only supported with '--backend convex', '--backend none', or '--backend self'. Please choose 'bun', 'node', or remove the --runtime flag.");
3846
+ }
3847
+ if (backend === "convex" && providedFlags.has("frontend") && options.frontend) {
3848
+ const incompatibleFrontends = options.frontend.filter((f) => f === "solid" || f === "astro");
3849
+ if (incompatibleFrontends.length > 0) return validationErr(`The following frontends are not compatible with '--backend convex': ${incompatibleFrontends.join(", ")}. Please choose a different frontend or backend.`);
3850
+ }
3851
+ return Result.ok(void 0);
3852
+ }
3853
+ function validateFrontendConstraints(config, providedFlags) {
3854
+ const { frontend } = config;
3855
+ if (frontend && frontend.length > 0) {
3856
+ const singleWebNativeResult = ensureSingleWebAndNative(frontend);
3857
+ if (singleWebNativeResult.isErr()) return singleWebNativeResult;
3858
+ if (providedFlags.has("api") && providedFlags.has("frontend") && config.api) {
3859
+ const apiResult = validateApiFrontendCompatibility(config.api, frontend);
3860
+ if (apiResult.isErr()) return apiResult;
3861
+ }
3862
+ }
3863
+ const hasWebFrontendFlag = (frontend ?? []).some((f) => isWebFrontend(f));
3864
+ const webDeployResult = validateWebDeployRequiresWebFrontend(config.webDeploy, hasWebFrontendFlag);
3865
+ if (webDeployResult.isErr()) return webDeployResult;
3866
+ return Result.ok(void 0);
3867
+ }
3868
+ function validateApiConstraints(config, options) {
3869
+ if (config.api === "none") {
3870
+ if (options.examples?.includes("todo") && options.backend !== "convex" && options.backend !== "none") return validationErr("Cannot use '--examples todo' when '--api' is set to 'none'. The todo example requires an API layer. Please remove 'todo' from --examples or choose an API type.");
3871
+ }
3872
+ return Result.ok(void 0);
3873
+ }
3874
+ function validateFullConfig(config, providedFlags, options) {
3875
+ return Result.gen(function* () {
3876
+ yield* validateDatabaseOrmAuth(config, providedFlags);
3877
+ yield* validateDatabaseSetup(config, providedFlags);
3878
+ yield* validateConvexConstraints(config, providedFlags);
3879
+ yield* validateBackendNoneConstraints(config, providedFlags);
3880
+ yield* validateSelfBackendConstraints(config, providedFlags);
3881
+ yield* validateBackendConstraints(config, providedFlags, options);
3882
+ yield* validateFrontendConstraints(config, providedFlags);
3883
+ yield* validateApiConstraints(config, options);
3884
+ yield* validateServerDeployRequiresBackend(config.serverDeploy, config.backend);
3885
+ yield* validateSelfBackendCompatibility(providedFlags, options, config);
3886
+ yield* validateWorkersCompatibility(providedFlags, options, config);
3887
+ if (config.runtime === "workers" && config.serverDeploy === "none") yield* validationErr("Cloudflare Workers runtime requires a server deployment. Please choose 'cloudflare' for --server-deploy.");
3888
+ if (providedFlags.has("serverDeploy") && config.serverDeploy === "cloudflare" && config.runtime !== "workers") yield* validationErr(`Server deployment '${config.serverDeploy}' requires '--runtime workers'. Please use '--runtime workers' or choose a different server deployment.`);
3889
+ if (config.addons && config.addons.length > 0) {
3890
+ yield* validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth);
3891
+ config.addons = [...new Set(config.addons)];
3892
+ }
3893
+ yield* validateExamplesCompatibility(config.examples ?? [], config.backend, config.database, config.frontend ?? [], config.api);
3894
+ yield* validatePaymentsCompatibility(config.payments, config.auth, config.backend, config.frontend ?? []);
3895
+ return Result.ok(void 0);
3896
+ });
3897
+ }
3898
+ function validateConfigForProgrammaticUse(config) {
3899
+ return Result.gen(function* () {
3900
+ yield* validateDatabaseOrmAuth(config);
3901
+ if (config.frontend && config.frontend.length > 0) yield* ensureSingleWebAndNative(config.frontend);
3902
+ yield* validateApiFrontendCompatibility(config.api, config.frontend);
3903
+ yield* validatePaymentsCompatibility(config.payments, config.auth, config.backend, config.frontend);
3904
+ if (config.addons && config.addons.length > 0) yield* validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth);
3905
+ yield* validateExamplesCompatibility(config.examples ?? [], config.backend, config.database, config.frontend ?? [], config.api);
3906
+ return Result.ok(void 0);
3907
+ });
3908
+ }
3909
+ //#endregion
3704
3910
  //#region src/prompts/orm.ts
3705
3911
  const ormOptions = {
3706
3912
  prisma: {
@@ -3722,7 +3928,11 @@ const ormOptions = {
3722
3928
  async function getORMChoice(orm, hasDatabase, database, backend, runtime) {
3723
3929
  if (backend === "convex") return "none";
3724
3930
  if (!hasDatabase) return "none";
3725
- if (orm !== void 0) return orm;
3931
+ if (orm !== void 0) {
3932
+ const compat = validateOrmDatabaseCompat(orm, database);
3933
+ if (compat.isErr()) throw compat.error;
3934
+ return orm;
3935
+ }
3726
3936
  const response = await navigableSelect({
3727
3937
  message: "Select ORM",
3728
3938
  options: database === "mongodb" ? [ormOptions.prisma, ormOptions.mongoose] : [ormOptions.drizzle, ormOptions.prisma],
@@ -3832,9 +4042,10 @@ function getDeploymentDisplay(deployment) {
3832
4042
  hint: `Add ${deployment} deployment`
3833
4043
  };
3834
4044
  }
3835
- async function getDeploymentChoice(deployment, _runtime, _backend, frontend = []) {
4045
+ async function getDeploymentChoice(deployment, _runtime, backend, frontend = [], dbSetup) {
3836
4046
  if (deployment !== void 0) return deployment;
3837
4047
  if (!hasWebFrontend(frontend)) return "none";
4048
+ if (backend === "self" && dbSetup === "d1") return "cloudflare";
3838
4049
  const response = await navigableSelect({
3839
4050
  message: "Select web deployment",
3840
4051
  options: ["cloudflare", "none"].map((deploy) => {
@@ -3888,7 +4099,7 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
3888
4099
  addons: ({ results }) => getAddonsChoice(flags.addons, results.frontend, results.auth),
3889
4100
  examples: ({ results }) => getExamplesChoice(flags.examples, results.database, results.frontend, results.backend, results.api),
3890
4101
  dbSetup: ({ results }) => getDBSetupChoice(results.database ?? "none", flags.dbSetup, results.orm, results.backend, results.runtime),
3891
- webDeploy: ({ results }) => getDeploymentChoice(flags.webDeploy, results.runtime, results.backend, results.frontend),
4102
+ webDeploy: ({ results }) => getDeploymentChoice(flags.webDeploy, results.runtime, results.backend, results.frontend, results.dbSetup),
3892
4103
  serverDeploy: ({ results }) => getServerDeploymentChoice(flags.serverDeploy, results.runtime, results.backend, results.webDeploy),
3893
4104
  git: () => getGitChoice(flags.git),
3894
4105
  packageManager: () => getPackageManagerChoice(flags.packageManager),
@@ -4301,183 +4512,6 @@ function validateArrayOptions(options) {
4301
4512
  return Result.ok(void 0);
4302
4513
  }
4303
4514
  //#endregion
4304
- //#region src/utils/config-validation.ts
4305
- function validationErr(message) {
4306
- return Result.err(new ValidationError({ message }));
4307
- }
4308
- function validateDatabaseOrmAuth(cfg, flags) {
4309
- const db = cfg.database;
4310
- const orm = cfg.orm;
4311
- const has = (k) => flags ? flags.has(k) : true;
4312
- if (has("orm") && has("database") && orm === "mongoose" && db !== "mongodb") return validationErr("Mongoose ORM requires MongoDB database. Please use '--database mongodb' or choose a different ORM.");
4313
- if (has("orm") && has("database") && orm === "drizzle" && db === "mongodb") return validationErr("Drizzle ORM does not support MongoDB. Please use '--orm mongoose' or '--orm prisma' or choose a different database.");
4314
- if (has("database") && has("orm") && db === "mongodb" && orm && orm !== "mongoose" && orm !== "prisma" && orm !== "none") return validationErr("MongoDB database requires Mongoose or Prisma ORM. Please use '--orm mongoose' or '--orm prisma' or choose a different database.");
4315
- if (has("database") && has("orm") && db && db !== "none" && orm === "none") return validationErr("Database selection requires an ORM. Please choose '--orm drizzle', '--orm prisma', or '--orm mongoose'.");
4316
- if (has("orm") && has("database") && orm && orm !== "none" && db === "none") return validationErr("ORM selection requires a database. Please choose a database or set '--orm none'.");
4317
- return Result.ok(void 0);
4318
- }
4319
- function validateDatabaseSetup(config, providedFlags) {
4320
- const { dbSetup, database, runtime } = config;
4321
- if (providedFlags.has("dbSetup") && providedFlags.has("database") && dbSetup && dbSetup !== "none" && database === "none") return validationErr("Database setup requires a database. Please choose a database or set '--db-setup none'.");
4322
- const setupValidations = {
4323
- turso: {
4324
- database: "sqlite",
4325
- errorMessage: "Turso setup requires SQLite database. Please use '--database sqlite' or choose a different setup."
4326
- },
4327
- neon: {
4328
- database: "postgres",
4329
- errorMessage: "Neon setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup."
4330
- },
4331
- "prisma-postgres": {
4332
- database: "postgres",
4333
- errorMessage: "Prisma PostgreSQL setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup."
4334
- },
4335
- planetscale: { errorMessage: "PlanetScale setup requires PostgreSQL or MySQL database. Please use '--database postgres' or '--database mysql' or choose a different setup." },
4336
- "mongodb-atlas": {
4337
- database: "mongodb",
4338
- errorMessage: "MongoDB Atlas setup requires MongoDB database. Please use '--database mongodb' or choose a different setup."
4339
- },
4340
- supabase: {
4341
- database: "postgres",
4342
- errorMessage: "Supabase setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup."
4343
- },
4344
- d1: {
4345
- database: "sqlite",
4346
- runtime: "workers",
4347
- errorMessage: "Cloudflare D1 setup requires SQLite database and Cloudflare Workers runtime."
4348
- },
4349
- docker: { errorMessage: "Docker setup is not compatible with SQLite database or Cloudflare Workers runtime." },
4350
- none: { errorMessage: "" }
4351
- };
4352
- if (dbSetup && dbSetup !== "none") {
4353
- const validation = setupValidations[dbSetup];
4354
- if (dbSetup === "planetscale") {
4355
- if (database !== "postgres" && database !== "mysql") return validationErr(validation.errorMessage);
4356
- } else if (validation.database && database !== validation.database) return validationErr(validation.errorMessage);
4357
- if (validation.runtime && runtime !== validation.runtime) return validationErr(validation.errorMessage);
4358
- if (dbSetup === "docker") {
4359
- if (database === "sqlite") return validationErr("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.");
4360
- if (runtime === "workers") return validationErr("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.");
4361
- }
4362
- }
4363
- return Result.ok(void 0);
4364
- }
4365
- function validateConvexConstraints(config, providedFlags) {
4366
- const { backend } = config;
4367
- if (backend !== "convex") return Result.ok(void 0);
4368
- const has = (k) => providedFlags.has(k);
4369
- if (has("runtime") && config.runtime !== "none") return validationErr("Convex backend requires '--runtime none'. Please remove the --runtime flag or set it to 'none'.");
4370
- if (has("database") && config.database !== "none") return validationErr("Convex backend requires '--database none'. Please remove the --database flag or set it to 'none'.");
4371
- if (has("orm") && config.orm !== "none") return validationErr("Convex backend requires '--orm none'. Please remove the --orm flag or set it to 'none'.");
4372
- if (has("api") && config.api !== "none") return validationErr("Convex backend requires '--api none'. Please remove the --api flag or set it to 'none'.");
4373
- if (has("dbSetup") && config.dbSetup !== "none") return validationErr("Convex backend requires '--db-setup none'. Please remove the --db-setup flag or set it to 'none'.");
4374
- if (has("serverDeploy") && config.serverDeploy !== "none") return validationErr("Convex backend requires '--server-deploy none'. Please remove the --server-deploy flag or set it to 'none'.");
4375
- if (has("auth") && config.auth === "better-auth") {
4376
- const incompatibleFrontends = config.frontend?.filter((f) => CONVEX_BETTER_AUTH_INCOMPATIBLE_FRONTENDS.includes(f)) ?? [];
4377
- const hasSupportedFrontend = supportsConvexBetterAuth(config.frontend);
4378
- if (incompatibleFrontends.length > 0) return validationErr(`Better Auth with '--backend convex' is not compatible with the following frontends: ${incompatibleFrontends.join(", ")}. Please use a React-based web frontend (next, tanstack-start, tanstack-router, react-router), a supported native frontend, or choose a different auth provider.`);
4379
- if (!hasSupportedFrontend) return validationErr(`Better Auth with '--backend convex' requires a supported frontend (${CONVEX_BETTER_AUTH_SUPPORTED_FRONTENDS.join(", ")}).`);
4380
- }
4381
- return Result.ok(void 0);
4382
- }
4383
- function validateBackendNoneConstraints(config, providedFlags) {
4384
- const { backend } = config;
4385
- if (backend !== "none") return Result.ok(void 0);
4386
- const has = (k) => providedFlags.has(k);
4387
- if (has("runtime") && config.runtime !== "none") return validationErr("Backend 'none' requires '--runtime none'. Please remove the --runtime flag or set it to 'none'.");
4388
- if (has("database") && config.database !== "none") return validationErr("Backend 'none' requires '--database none'. Please remove the --database flag or set it to 'none'.");
4389
- if (has("orm") && config.orm !== "none") return validationErr("Backend 'none' requires '--orm none'. Please remove the --orm flag or set it to 'none'.");
4390
- if (has("api") && config.api !== "none") return validationErr("Backend 'none' requires '--api none'. Please remove the --api flag or set it to 'none'.");
4391
- if (has("auth") && config.auth !== "none") return validationErr("Backend 'none' requires '--auth none'. Please remove the --auth flag or set it to 'none'.");
4392
- if (has("payments") && config.payments !== "none") return validationErr("Backend 'none' requires '--payments none'. Please remove the --payments flag or set it to 'none'.");
4393
- if (has("dbSetup") && config.dbSetup !== "none") return validationErr("Backend 'none' requires '--db-setup none'. Please remove the --db-setup flag or set it to 'none'.");
4394
- if (has("serverDeploy") && config.serverDeploy !== "none") return validationErr("Backend 'none' requires '--server-deploy none'. Please remove the --server-deploy flag or set it to 'none'.");
4395
- return Result.ok(void 0);
4396
- }
4397
- function validateSelfBackendConstraints(config, providedFlags) {
4398
- const { backend } = config;
4399
- if (backend !== "self") return Result.ok(void 0);
4400
- const has = (k) => providedFlags.has(k);
4401
- if (has("runtime") && config.runtime !== "none") return validationErr("Backend 'self' (fullstack) requires '--runtime none'. Please remove the --runtime flag or set it to 'none'.");
4402
- return Result.ok(void 0);
4403
- }
4404
- function validateBackendConstraints(config, providedFlags, options) {
4405
- const { backend } = config;
4406
- if (config.auth === "clerk" && config.frontend) {
4407
- const incompatibleFrontends = config.frontend.filter((f) => [
4408
- "nuxt",
4409
- "svelte",
4410
- "solid",
4411
- "astro"
4412
- ].includes(f));
4413
- if (incompatibleFrontends.length > 0) return validationErr(`Clerk authentication is not compatible with the following frontends: ${incompatibleFrontends.join(", ")}. Please choose a different frontend or auth provider.`);
4414
- }
4415
- if (providedFlags.has("backend") && backend && backend !== "convex" && backend !== "none" && backend !== "self") {
4416
- if (providedFlags.has("runtime") && options.runtime === "none") return validationErr("'--runtime none' is only supported with '--backend convex', '--backend none', or '--backend self'. Please choose 'bun', 'node', or remove the --runtime flag.");
4417
- }
4418
- if (backend === "convex" && providedFlags.has("frontend") && options.frontend) {
4419
- const incompatibleFrontends = options.frontend.filter((f) => f === "solid" || f === "astro");
4420
- if (incompatibleFrontends.length > 0) return validationErr(`The following frontends are not compatible with '--backend convex': ${incompatibleFrontends.join(", ")}. Please choose a different frontend or backend.`);
4421
- }
4422
- return Result.ok(void 0);
4423
- }
4424
- function validateFrontendConstraints(config, providedFlags) {
4425
- const { frontend } = config;
4426
- if (frontend && frontend.length > 0) {
4427
- const singleWebNativeResult = ensureSingleWebAndNative(frontend);
4428
- if (singleWebNativeResult.isErr()) return singleWebNativeResult;
4429
- if (providedFlags.has("api") && providedFlags.has("frontend") && config.api) {
4430
- const apiResult = validateApiFrontendCompatibility(config.api, frontend);
4431
- if (apiResult.isErr()) return apiResult;
4432
- }
4433
- }
4434
- const hasWebFrontendFlag = (frontend ?? []).some((f) => isWebFrontend(f));
4435
- const webDeployResult = validateWebDeployRequiresWebFrontend(config.webDeploy, hasWebFrontendFlag);
4436
- if (webDeployResult.isErr()) return webDeployResult;
4437
- return Result.ok(void 0);
4438
- }
4439
- function validateApiConstraints(config, options) {
4440
- if (config.api === "none") {
4441
- if (options.examples?.includes("todo") && options.backend !== "convex" && options.backend !== "none") return validationErr("Cannot use '--examples todo' when '--api' is set to 'none'. The todo example requires an API layer. Please remove 'todo' from --examples or choose an API type.");
4442
- }
4443
- return Result.ok(void 0);
4444
- }
4445
- function validateFullConfig(config, providedFlags, options) {
4446
- return Result.gen(function* () {
4447
- yield* validateDatabaseOrmAuth(config, providedFlags);
4448
- yield* validateDatabaseSetup(config, providedFlags);
4449
- yield* validateConvexConstraints(config, providedFlags);
4450
- yield* validateBackendNoneConstraints(config, providedFlags);
4451
- yield* validateSelfBackendConstraints(config, providedFlags);
4452
- yield* validateBackendConstraints(config, providedFlags, options);
4453
- yield* validateFrontendConstraints(config, providedFlags);
4454
- yield* validateApiConstraints(config, options);
4455
- yield* validateServerDeployRequiresBackend(config.serverDeploy, config.backend);
4456
- yield* validateSelfBackendCompatibility(providedFlags, options, config);
4457
- yield* validateWorkersCompatibility(providedFlags, options, config);
4458
- if (config.runtime === "workers" && config.serverDeploy === "none") yield* validationErr("Cloudflare Workers runtime requires a server deployment. Please choose 'cloudflare' for --server-deploy.");
4459
- if (providedFlags.has("serverDeploy") && config.serverDeploy === "cloudflare" && config.runtime !== "workers") yield* validationErr(`Server deployment '${config.serverDeploy}' requires '--runtime workers'. Please use '--runtime workers' or choose a different server deployment.`);
4460
- if (config.addons && config.addons.length > 0) {
4461
- yield* validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth);
4462
- config.addons = [...new Set(config.addons)];
4463
- }
4464
- yield* validateExamplesCompatibility(config.examples ?? [], config.backend, config.database, config.frontend ?? [], config.api);
4465
- yield* validatePaymentsCompatibility(config.payments, config.auth, config.backend, config.frontend ?? []);
4466
- return Result.ok(void 0);
4467
- });
4468
- }
4469
- function validateConfigForProgrammaticUse(config) {
4470
- return Result.gen(function* () {
4471
- yield* validateDatabaseOrmAuth(config);
4472
- if (config.frontend && config.frontend.length > 0) yield* ensureSingleWebAndNative(config.frontend);
4473
- yield* validateApiFrontendCompatibility(config.api, config.frontend);
4474
- yield* validatePaymentsCompatibility(config.payments, config.auth, config.backend, config.frontend);
4475
- if (config.addons && config.addons.length > 0) yield* validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth);
4476
- yield* validateExamplesCompatibility(config.examples ?? [], config.backend, config.database, config.frontend ?? [], config.api);
4477
- return Result.ok(void 0);
4478
- });
4479
- }
4480
- //#endregion
4481
4515
  //#region src/validation.ts
4482
4516
  const CORE_STACK_FLAGS = new Set([
4483
4517
  "database",
@@ -4612,8 +4646,8 @@ async function addEnvVariablesToFile(envPath, variables) {
4612
4646
  //#endregion
4613
4647
  //#region src/helpers/database-providers/d1-setup.ts
4614
4648
  async function setupCloudflareD1(config) {
4615
- const { projectDir, serverDeploy, orm, backend } = config;
4616
- if (!(serverDeploy === "cloudflare" && orm === "prisma")) return Result.ok(void 0);
4649
+ const { projectDir, serverDeploy, webDeploy, orm, backend } = config;
4650
+ if (!(orm === "prisma" && (serverDeploy === "cloudflare" || backend === "self" && webDeploy === "cloudflare"))) return Result.ok(void 0);
4617
4651
  return Result.tryPromise({
4618
4652
  try: async () => {
4619
4653
  const targetApp = backend === "self" ? "apps/web" : "apps/server";
@@ -5995,7 +6029,7 @@ async function displayPostInstallInstructions(config) {
5995
6029
  const hasHusky = addons?.includes("husky");
5996
6030
  const hasLefthook = addons?.includes("lefthook");
5997
6031
  const hasGitHooksOrLinting = addons?.includes("husky") || addons?.includes("biome") || addons?.includes("lefthook") || addons?.includes("oxlint");
5998
- const databaseInstructions = !isConvex && database !== "none" ? await getDatabaseInstructions(database, orm, runCmd, runtime, dbSetup, serverDeploy, backend) : "";
6032
+ const databaseInstructions = !isConvex && database !== "none" ? await getDatabaseInstructions(database, orm, runCmd, runtime, dbSetup, webDeploy, serverDeploy, backend) : "";
5999
6033
  const tauriInstructions = addons?.includes("tauri") ? getTauriInstructions(runCmd, frontend) : "";
6000
6034
  const electrobunInstructions = addons?.includes("electrobun") ? getElectrobunInstructions(runCmd, frontend) : "";
6001
6035
  const huskyInstructions = hasHusky ? getHuskyInstructions(runCmd) : "";
@@ -6088,8 +6122,9 @@ function getLefthookInstructions(packageManager) {
6088
6122
  const cmd = packageManager === "npm" ? "npx" : packageManager;
6089
6123
  return `${pc.bold("Git hooks with Lefthook:")}\n${pc.cyan("•")} Install hooks: ${cmd} lefthook install\n`;
6090
6124
  }
6091
- async function getDatabaseInstructions(database, orm, runCmd, _runtime, dbSetup, serverDeploy, _backend) {
6125
+ async function getDatabaseInstructions(database, orm, runCmd, _runtime, dbSetup, webDeploy, serverDeploy, backend) {
6092
6126
  const instructions = [];
6127
+ const isD1Alchemy = dbSetup === "d1" && (serverDeploy === "cloudflare" || backend === "self" && webDeploy === "cloudflare");
6093
6128
  if (dbSetup === "docker") {
6094
6129
  const dockerStatus = await getDockerStatus(database);
6095
6130
  if (dockerStatus.message) {
@@ -6097,7 +6132,7 @@ async function getDatabaseInstructions(database, orm, runCmd, _runtime, dbSetup,
6097
6132
  instructions.push("");
6098
6133
  }
6099
6134
  }
6100
- if (dbSetup === "d1" && serverDeploy === "cloudflare") {
6135
+ if (isD1Alchemy) {
6101
6136
  if (orm === "drizzle") instructions.push(`${pc.cyan("•")} Generate migrations: ${`${runCmd} db:generate`}`);
6102
6137
  else if (orm === "prisma") {
6103
6138
  instructions.push(`${pc.cyan("•")} Generate Prisma client: ${`${runCmd} db:generate`}`);
@@ -6112,15 +6147,15 @@ async function getDatabaseInstructions(database, orm, runCmd, _runtime, dbSetup,
6112
6147
  if (orm === "prisma") {
6113
6148
  if (database === "mongodb" && dbSetup === "docker") instructions.push(`${pc.yellow("WARNING:")} Prisma + MongoDB + Docker combination\n may not work.`);
6114
6149
  if (dbSetup === "docker") instructions.push(`${pc.cyan("•")} Start docker container: ${`${runCmd} db:start`}`);
6115
- if (!(dbSetup === "d1" && serverDeploy === "cloudflare")) {
6150
+ if (!isD1Alchemy) {
6116
6151
  instructions.push(`${pc.cyan("•")} Generate Prisma Client: ${`${runCmd} db:generate`}`);
6117
6152
  instructions.push(`${pc.cyan("•")} Apply schema: ${`${runCmd} db:push`}`);
6118
6153
  }
6119
- if (!(dbSetup === "d1" && serverDeploy === "cloudflare")) instructions.push(`${pc.cyan("•")} Database UI: ${`${runCmd} db:studio`}`);
6154
+ if (!isD1Alchemy) instructions.push(`${pc.cyan("•")} Database UI: ${`${runCmd} db:studio`}`);
6120
6155
  } else if (orm === "drizzle") {
6121
6156
  if (dbSetup === "docker") instructions.push(`${pc.cyan("•")} Start docker container: ${`${runCmd} db:start`}`);
6122
- if (dbSetup !== "d1") instructions.push(`${pc.cyan("•")} Apply schema: ${`${runCmd} db:push`}`);
6123
- if (!(dbSetup === "d1" && serverDeploy === "cloudflare")) instructions.push(`${pc.cyan("•")} Database UI: ${`${runCmd} db:studio`}`);
6157
+ if (!isD1Alchemy) instructions.push(`${pc.cyan("•")} Apply schema: ${`${runCmd} db:push`}`);
6158
+ if (!isD1Alchemy) instructions.push(`${pc.cyan("•")} Database UI: ${`${runCmd} db:studio`}`);
6124
6159
  } else if (orm === "mongoose") {
6125
6160
  if (dbSetup === "docker") instructions.push(`${pc.cyan("•")} Start docker container: ${`${runCmd} db:start`}`);
6126
6161
  } else if (orm === "none") instructions.push(`${pc.yellow("NOTE:")} Manual database schema setup\n required.`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-better-t-stack",
3
- "version": "3.26.1",
3
+ "version": "3.27.1",
4
4
  "description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations",
5
5
  "keywords": [
6
6
  "better-auth",
@@ -70,8 +70,8 @@
70
70
  "prepublishOnly": "npm run build"
71
71
  },
72
72
  "dependencies": {
73
- "@better-t-stack/template-generator": "^3.26.1",
74
- "@better-t-stack/types": "^3.26.1",
73
+ "@better-t-stack/template-generator": "^3.27.1",
74
+ "@better-t-stack/types": "^3.27.1",
75
75
  "@clack/core": "^1.1.0",
76
76
  "@clack/prompts": "^1.1.0",
77
77
  "@modelcontextprotocol/sdk": "1.27.1",