create-better-t-stack 2.40.4 → 2.41.0

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/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { createBtsCli } from "./src-BTNbBm4L.js";
2
+ import { createBtsCli } from "./src-BWT41_aE.js";
3
3
 
4
4
  //#region src/cli.ts
5
5
  createBtsCli().run();
package/dist/index.d.ts CHANGED
@@ -80,6 +80,7 @@ declare const DatabaseSetupSchema: z.ZodEnum<{
80
80
  turso: "turso";
81
81
  neon: "neon";
82
82
  "prisma-postgres": "prisma-postgres";
83
+ planetscale: "planetscale";
83
84
  "mongodb-atlas": "mongodb-atlas";
84
85
  supabase: "supabase";
85
86
  d1: "d1";
@@ -218,7 +219,7 @@ declare const router: trpcServer.TRPCBuiltRouter<{
218
219
  git?: boolean | undefined;
219
220
  packageManager?: "npm" | "pnpm" | "bun" | undefined;
220
221
  install?: boolean | undefined;
221
- dbSetup?: "none" | "turso" | "neon" | "prisma-postgres" | "mongodb-atlas" | "supabase" | "d1" | "docker" | undefined;
222
+ dbSetup?: "none" | "turso" | "neon" | "prisma-postgres" | "planetscale" | "mongodb-atlas" | "supabase" | "d1" | "docker" | undefined;
222
223
  backend?: "none" | "next" | "hono" | "express" | "fastify" | "elysia" | "convex" | undefined;
223
224
  runtime?: "none" | "bun" | "node" | "workers" | undefined;
224
225
  api?: "none" | "trpc" | "orpc" | undefined;
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { builder, createBtsCli, docs, init, router, sponsors } from "./src-BTNbBm4L.js";
2
+ import { builder, createBtsCli, docs, init, router, sponsors } from "./src-BWT41_aE.js";
3
3
 
4
4
  export { builder, createBtsCli, docs, init, router, sponsors };
@@ -13,7 +13,6 @@ import { $, execa } from "execa";
13
13
  import { glob } from "tinyglobby";
14
14
  import handlebars from "handlebars";
15
15
  import { IndentationText, Node, Project, QuoteKind, SyntaxKind } from "ts-morph";
16
- import { Biome } from "@biomejs/js-api/nodejs";
17
16
  import os from "node:os";
18
17
 
19
18
  //#region src/utils/get-package-manager.ts
@@ -67,6 +66,7 @@ const dependencyVersionMap = {
67
66
  "@clerk/clerk-expo": "^2.14.25",
68
67
  "drizzle-orm": "^0.44.2",
69
68
  "drizzle-kit": "^0.31.2",
69
+ "@planetscale/database": "^1.19.0",
70
70
  "@libsql/client": "^0.15.9",
71
71
  "@neondatabase/serverless": "^1.0.1",
72
72
  pg: "^8.14.1",
@@ -76,7 +76,10 @@ const dependencyVersionMap = {
76
76
  mysql2: "^3.14.0",
77
77
  "@prisma/client": "^6.15.0",
78
78
  prisma: "^6.15.0",
79
+ "@prisma/adapter-d1": "^6.15.0",
79
80
  "@prisma/extension-accelerate": "^2.0.2",
81
+ "@prisma/adapter-libsql": "^6.15.0",
82
+ "@prisma/adapter-planetscale": "^6.15.0",
80
83
  mongoose: "^8.14.0",
81
84
  "vite-plugin-pwa": "^1.0.1",
82
85
  "@vite-pwa/assets-generator": "^1.0.0",
@@ -132,11 +135,11 @@ const dependencyVersionMap = {
132
135
  "@tanstack/solid-router-devtools": "^1.131.25",
133
136
  wrangler: "^4.23.0",
134
137
  "@cloudflare/vite-plugin": "^1.9.0",
135
- "@opennextjs/cloudflare": "^1.3.0",
138
+ "@opennextjs/cloudflare": "^1.6.5",
136
139
  "nitro-cloudflare-dev": "^0.2.2",
137
140
  "@sveltejs/adapter-cloudflare": "^7.2.1",
138
141
  "@cloudflare/workers-types": "^4.20250822.0",
139
- alchemy: "^0.63.0",
142
+ alchemy: "^0.65.0",
140
143
  nitropack: "^2.12.4",
141
144
  dotenv: "^17.2.1"
142
145
  };
@@ -235,6 +238,7 @@ const DatabaseSetupSchema = z.enum([
235
238
  "turso",
236
239
  "neon",
237
240
  "prisma-postgres",
241
+ "planetscale",
238
242
  "mongodb-atlas",
239
243
  "supabase",
240
244
  "d1",
@@ -498,11 +502,9 @@ function ensureSingleWebAndNative(frontends) {
498
502
  function validateWorkersCompatibility(providedFlags, options, config) {
499
503
  if (providedFlags.has("runtime") && options.runtime === "workers" && config.backend && config.backend !== "hono") exitWithError(`Cloudflare Workers runtime (--runtime workers) is only supported with Hono backend (--backend hono). Current backend: ${config.backend}. Please use '--backend hono' or choose a different runtime.`);
500
504
  if (providedFlags.has("backend") && config.backend && config.backend !== "hono" && config.runtime === "workers") exitWithError(`Backend '${config.backend}' is not compatible with Cloudflare Workers runtime. Cloudflare Workers runtime is only supported with Hono backend. Please use '--backend hono' or choose a different runtime.`);
501
- if (providedFlags.has("runtime") && options.runtime === "workers" && config.orm && config.orm !== "drizzle" && config.orm !== "none") exitWithError(`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.`);
502
- if (providedFlags.has("orm") && config.orm && config.orm !== "drizzle" && config.orm !== "none" && config.runtime === "workers") exitWithError(`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.`);
503
- if (providedFlags.has("runtime") && options.runtime === "workers" && config.database === "mongodb") exitWithError("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.");
505
+ if (providedFlags.has("runtime") && options.runtime === "workers" && config.database === "mongodb") exitWithError("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.");
504
506
  if (providedFlags.has("runtime") && options.runtime === "workers" && config.dbSetup === "docker") exitWithError("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.");
505
- if (providedFlags.has("database") && config.database === "mongodb" && config.runtime === "workers") exitWithError("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.");
507
+ if (providedFlags.has("database") && config.database === "mongodb" && config.runtime === "workers") exitWithError("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.");
506
508
  if (providedFlags.has("dbSetup") && options.dbSetup === "docker" && config.runtime === "workers") exitWithError("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.");
507
509
  }
508
510
  function validateApiFrontendCompatibility(api, frontends = []) {
@@ -564,17 +566,6 @@ function validateExamplesCompatibility(examples, backend, database, frontend) {
564
566
  if (examplesArr.includes("ai") && backend === "elysia") exitWithError("The 'ai' example is not compatible with the Elysia backend.");
565
567
  if (examplesArr.includes("ai") && (frontend ?? []).includes("solid")) exitWithError("The 'ai' example is not compatible with the Solid frontend.");
566
568
  }
567
- function validateAlchemyCompatibility(webDeploy, serverDeploy, frontends = []) {
568
- const isAlchemyWebDeploy = webDeploy === "alchemy";
569
- const isAlchemyServerDeploy = serverDeploy === "alchemy";
570
- if (isAlchemyWebDeploy || isAlchemyServerDeploy) {
571
- const incompatibleFrontends = frontends.filter((f) => f === "next");
572
- if (incompatibleFrontends.length > 0) {
573
- const deployType = isAlchemyWebDeploy && isAlchemyServerDeploy ? "web and server deployment" : isAlchemyWebDeploy ? "web deployment" : "server deployment";
574
- exitWithError(`Alchemy ${deployType} is temporarily not compatible with ${incompatibleFrontends.join(" and ")} frontend(s). Please choose a different frontend or deployment option.`);
575
- }
576
- }
577
- }
578
569
 
579
570
  //#endregion
580
571
  //#region src/prompts/api.ts
@@ -741,11 +732,10 @@ async function getDatabaseChoice(database, backend, runtime) {
741
732
 
742
733
  //#endregion
743
734
  //#region src/prompts/database-setup.ts
744
- async function getDBSetupChoice(databaseType, dbSetup, orm, backend, runtime) {
735
+ async function getDBSetupChoice(databaseType, dbSetup, _orm, backend, runtime) {
745
736
  if (backend === "convex") return "none";
746
737
  if (dbSetup !== void 0) return dbSetup;
747
738
  if (databaseType === "none") return "none";
748
- if (databaseType === "sqlite" && orm === "prisma") return "none";
749
739
  let options = [];
750
740
  if (databaseType === "sqlite") options = [
751
741
  {
@@ -770,6 +760,11 @@ async function getDBSetupChoice(databaseType, dbSetup, orm, backend, runtime) {
770
760
  label: "Neon Postgres",
771
761
  hint: "Serverless Postgres with branching capability"
772
762
  },
763
+ {
764
+ value: "planetscale",
765
+ label: "PlanetScale",
766
+ hint: "Serverless MySQL platform with branching (Postgres compatible)"
767
+ },
773
768
  {
774
769
  value: "supabase",
775
770
  label: "Supabase",
@@ -791,15 +786,23 @@ async function getDBSetupChoice(databaseType, dbSetup, orm, backend, runtime) {
791
786
  hint: "Manual setup"
792
787
  }
793
788
  ];
794
- else if (databaseType === "mysql") options = [{
795
- value: "docker",
796
- label: "Docker",
797
- hint: "Run locally with docker compose"
798
- }, {
799
- value: "none",
800
- label: "None",
801
- hint: "Manual setup"
802
- }];
789
+ else if (databaseType === "mysql") options = [
790
+ {
791
+ value: "planetscale",
792
+ label: "PlanetScale",
793
+ hint: "Serverless MySQL platform with branching"
794
+ },
795
+ {
796
+ value: "docker",
797
+ label: "Docker",
798
+ hint: "Run locally with docker compose"
799
+ },
800
+ {
801
+ value: "none",
802
+ label: "None",
803
+ hint: "Manual setup"
804
+ }
805
+ ];
803
806
  else if (databaseType === "mongodb") options = [
804
807
  {
805
808
  value: "mongodb-atlas",
@@ -997,12 +1000,11 @@ async function getORMChoice(orm, hasDatabase, database, backend, runtime) {
997
1000
  if (backend === "convex") return "none";
998
1001
  if (!hasDatabase) return "none";
999
1002
  if (orm !== void 0) return orm;
1000
- if (runtime === "workers") return "drizzle";
1001
1003
  const options = [...database === "mongodb" ? [ormOptions.prisma, ormOptions.mongoose] : [ormOptions.drizzle, ormOptions.prisma]];
1002
1004
  const response = await select({
1003
1005
  message: "Select ORM",
1004
1006
  options,
1005
- initialValue: database === "mongodb" ? "prisma" : DEFAULT_CONFIG.orm
1007
+ initialValue: database === "mongodb" ? "prisma" : runtime === "workers" ? "drizzle" : DEFAULT_CONFIG.orm
1006
1008
  });
1007
1009
  if (isCancel(response)) return exitCancelled("Operation cancelled");
1008
1010
  return response;
@@ -1160,8 +1162,7 @@ function getDeploymentDisplay(deployment) {
1160
1162
  async function getDeploymentChoice(deployment, _runtime, _backend, frontend = []) {
1161
1163
  if (deployment !== void 0) return deployment;
1162
1164
  if (!hasWebFrontend(frontend)) return "none";
1163
- const hasIncompatibleFrontend = frontend.some((f) => f === "next");
1164
- const availableDeployments = hasIncompatibleFrontend ? ["wrangler", "none"] : [
1165
+ const availableDeployments = [
1165
1166
  "wrangler",
1166
1167
  "alchemy",
1167
1168
  "none"
@@ -1177,14 +1178,13 @@ async function getDeploymentChoice(deployment, _runtime, _backend, frontend = []
1177
1178
  const response = await select({
1178
1179
  message: "Select web deployment",
1179
1180
  options,
1180
- initialValue: hasIncompatibleFrontend ? "wrangler" : DEFAULT_CONFIG.webDeploy
1181
+ initialValue: DEFAULT_CONFIG.webDeploy
1181
1182
  });
1182
1183
  if (isCancel(response)) return exitCancelled("Operation cancelled");
1183
1184
  return response;
1184
1185
  }
1185
1186
  async function getDeploymentToAdd(frontend, existingDeployment) {
1186
1187
  if (!hasWebFrontend(frontend)) return "none";
1187
- const hasIncompatibleFrontend = frontend.some((f) => f === "next");
1188
1188
  const options = [];
1189
1189
  if (existingDeployment !== "wrangler") {
1190
1190
  const { label, hint } = getDeploymentDisplay("wrangler");
@@ -1194,7 +1194,7 @@ async function getDeploymentToAdd(frontend, existingDeployment) {
1194
1194
  hint
1195
1195
  });
1196
1196
  }
1197
- if (existingDeployment !== "alchemy" && !hasIncompatibleFrontend) {
1197
+ if (existingDeployment !== "alchemy") {
1198
1198
  const { label, hint } = getDeploymentDisplay("alchemy");
1199
1199
  options.push({
1200
1200
  value: "alchemy",
@@ -1212,7 +1212,7 @@ async function getDeploymentToAdd(frontend, existingDeployment) {
1212
1212
  const response = await select({
1213
1213
  message: "Select web deployment",
1214
1214
  options,
1215
- initialValue: hasIncompatibleFrontend ? "wrangler" : DEFAULT_CONFIG.webDeploy
1215
+ initialValue: DEFAULT_CONFIG.webDeploy
1216
1216
  });
1217
1217
  if (isCancel(response)) return exitCancelled("Operation cancelled");
1218
1218
  return response;
@@ -1656,6 +1656,7 @@ function validateDatabaseSetup(config, providedFlags) {
1656
1656
  database: "postgres",
1657
1657
  errorMessage: "Prisma PostgreSQL setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup."
1658
1658
  },
1659
+ planetscale: { errorMessage: "PlanetScale setup requires PostgreSQL or MySQL database. Please use '--database postgres' or '--database mysql' or choose a different setup." },
1659
1660
  "mongodb-atlas": {
1660
1661
  database: "mongodb",
1661
1662
  errorMessage: "MongoDB Atlas setup requires MongoDB database. Please use '--database mongodb' or choose a different setup."
@@ -1674,7 +1675,9 @@ function validateDatabaseSetup(config, providedFlags) {
1674
1675
  };
1675
1676
  if (dbSetup && dbSetup !== "none") {
1676
1677
  const validation = setupValidations[dbSetup];
1677
- if (validation.database && database !== validation.database) exitWithError(validation.errorMessage);
1678
+ if (dbSetup === "planetscale") {
1679
+ if (database !== "postgres" && database !== "mysql") exitWithError(validation.errorMessage);
1680
+ } else if (validation.database && database !== validation.database) exitWithError(validation.errorMessage);
1678
1681
  if (validation.runtime && runtime !== validation.runtime) exitWithError(validation.errorMessage);
1679
1682
  if (dbSetup === "docker") {
1680
1683
  if (database === "sqlite") exitWithError("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.");
@@ -1755,7 +1758,6 @@ function validateFullConfig(config, providedFlags, options) {
1755
1758
  config.addons = [...new Set(config.addons)];
1756
1759
  }
1757
1760
  validateExamplesCompatibility(config.examples ?? [], config.backend, config.database, config.frontend ?? []);
1758
- validateAlchemyCompatibility(config.webDeploy, config.serverDeploy, config.frontend ?? []);
1759
1761
  }
1760
1762
  function validateConfigForProgrammaticUse(config) {
1761
1763
  try {
@@ -3049,8 +3051,11 @@ async function generateCloudflareWorkerTypes({ serverDir, packageManager }) {
3049
3051
  const s = spinner();
3050
3052
  try {
3051
3053
  s.start("Generating Cloudflare Workers types...");
3052
- const runCmd = packageManager === "npm" ? "npm" : packageManager;
3053
- await execa(runCmd, ["run", "cf-typegen"], { cwd: serverDir });
3054
+ const runCmd = getPackageExecutionCommand(packageManager, "wrangler types --env-interface CloudflareBindings");
3055
+ await execa(runCmd, {
3056
+ cwd: serverDir,
3057
+ shell: true
3058
+ });
3054
3059
  s.stop("Cloudflare Workers types generated successfully!");
3055
3060
  } catch {
3056
3061
  s.stop(pc.yellow("Failed to generate Cloudflare Workers types"));
@@ -3089,7 +3094,12 @@ async function setupNextAlchemyDeploy(projectDir, _packageManager, options) {
3089
3094
  const webAppDir = path.join(projectDir, "apps/web");
3090
3095
  if (!await fs.pathExists(webAppDir)) return;
3091
3096
  await addPackageDependency({
3092
- devDependencies: ["alchemy", "dotenv"],
3097
+ dependencies: ["@opennextjs/cloudflare"],
3098
+ devDependencies: [
3099
+ "alchemy",
3100
+ "dotenv",
3101
+ "wrangler"
3102
+ ],
3093
3103
  projectDir: webAppDir
3094
3104
  });
3095
3105
  const pkgPath = path.join(webAppDir, "package.json");
@@ -3102,6 +3112,17 @@ async function setupNextAlchemyDeploy(projectDir, _packageManager, options) {
3102
3112
  };
3103
3113
  await fs.writeJson(pkgPath, pkg, { spaces: 2 });
3104
3114
  }
3115
+ const openNextConfigPath = path.join(webAppDir, "open-next.config.ts");
3116
+ const openNextConfigContent = `import { defineCloudflareConfig } from "@opennextjs/cloudflare";
3117
+
3118
+ export default defineCloudflareConfig({});
3119
+ `;
3120
+ await fs.writeFile(openNextConfigPath, openNextConfigContent);
3121
+ const gitignorePath = path.join(webAppDir, ".gitignore");
3122
+ if (await fs.pathExists(gitignorePath)) {
3123
+ const gitignoreContent = await fs.readFile(gitignorePath, "utf-8");
3124
+ if (!gitignoreContent.includes("wrangler.jsonc")) await fs.appendFile(gitignorePath, "\nwrangler.jsonc\n");
3125
+ } else await fs.writeFile(gitignorePath, "wrangler.jsonc\n");
3105
3126
  }
3106
3127
 
3107
3128
  //#endregion
@@ -3774,53 +3795,6 @@ async function addDeploymentToProject(input) {
3774
3795
  }
3775
3796
  }
3776
3797
 
3777
- //#endregion
3778
- //#region src/utils/format-with-biome.ts
3779
- async function formatProjectWithBiome(projectDir) {
3780
- const biome = new Biome();
3781
- const { projectKey } = biome.openProject(projectDir);
3782
- biome.applyConfiguration(projectKey, {
3783
- formatter: {
3784
- enabled: true,
3785
- indentStyle: "tab"
3786
- },
3787
- javascript: { formatter: { quoteStyle: "double" } }
3788
- });
3789
- const files = await glob("**/*", {
3790
- cwd: projectDir,
3791
- dot: true,
3792
- absolute: true,
3793
- onlyFiles: true
3794
- });
3795
- for (const filePath of files) try {
3796
- const ext = path.extname(filePath).toLowerCase();
3797
- const supported = new Set([
3798
- ".ts",
3799
- ".tsx",
3800
- ".js",
3801
- ".jsx",
3802
- ".cjs",
3803
- ".mjs",
3804
- ".cts",
3805
- ".mts",
3806
- ".json",
3807
- ".jsonc",
3808
- ".md",
3809
- ".mdx",
3810
- ".css",
3811
- ".scss",
3812
- ".html"
3813
- ]);
3814
- if (!supported.has(ext)) continue;
3815
- const original = await fs.readFile(filePath, "utf8");
3816
- const result = biome.formatContent(projectKey, original, { filePath });
3817
- const content = result?.content;
3818
- if (typeof content !== "string") continue;
3819
- if (content.length === 0 && original.length > 0) continue;
3820
- if (content !== original) await fs.writeFile(filePath, content);
3821
- } catch {}
3822
- }
3823
-
3824
3798
  //#endregion
3825
3799
  //#region src/helpers/addons/examples-setup.ts
3826
3800
  async function setupExamples(config) {
@@ -4294,8 +4268,7 @@ async function setupEnvironmentVariables(config) {
4294
4268
  let corsOrigin = "http://localhost:3001";
4295
4269
  if (hasReactRouter || hasSvelte) corsOrigin = "http://localhost:5173";
4296
4270
  let databaseUrl = null;
4297
- const specializedSetup = dbSetup === "turso" || dbSetup === "prisma-postgres" || dbSetup === "mongodb-atlas" || dbSetup === "neon" || dbSetup === "supabase" || dbSetup === "d1" || dbSetup === "docker";
4298
- if (database !== "none" && !specializedSetup) switch (database) {
4271
+ if (database !== "none" && dbSetup === "none") switch (database) {
4299
4272
  case "postgres":
4300
4273
  databaseUrl = "postgresql://postgres:password@localhost:5432/postgres";
4301
4274
  break;
@@ -4329,7 +4302,7 @@ async function setupEnvironmentVariables(config) {
4329
4302
  {
4330
4303
  key: "DATABASE_URL",
4331
4304
  value: databaseUrl,
4332
- condition: database !== "none" && !specializedSetup
4305
+ condition: database !== "none" && dbSetup === "none"
4333
4306
  },
4334
4307
  {
4335
4308
  key: "GOOGLE_GENERATIVE_AI_API_KEY",
@@ -4377,7 +4350,7 @@ async function setupEnvironmentVariables(config) {
4377
4350
  //#endregion
4378
4351
  //#region src/helpers/database-providers/d1-setup.ts
4379
4352
  async function setupCloudflareD1(config) {
4380
- const { projectDir, serverDeploy } = config;
4353
+ const { projectDir, serverDeploy, orm } = config;
4381
4354
  if (serverDeploy === "wrangler") {
4382
4355
  const envPath = path.join(projectDir, "apps/server", ".env");
4383
4356
  const variables = [
@@ -4401,6 +4374,22 @@ async function setupCloudflareD1(config) {
4401
4374
  await addEnvVariablesToFile(envPath, variables);
4402
4375
  } catch (_err) {}
4403
4376
  }
4377
+ if ((serverDeploy === "wrangler" || serverDeploy === "alchemy") && orm === "prisma") {
4378
+ const envPath = path.join(projectDir, "apps/server", ".env");
4379
+ const variables = [{
4380
+ key: "DATABASE_URL",
4381
+ value: "file:./local.db",
4382
+ condition: true
4383
+ }];
4384
+ try {
4385
+ await addEnvVariablesToFile(envPath, variables);
4386
+ } catch (_err) {}
4387
+ const serverDir = path.join(projectDir, "apps/server");
4388
+ await addPackageDependency({
4389
+ dependencies: ["@prisma/adapter-d1"],
4390
+ projectDir: serverDir
4391
+ });
4392
+ }
4404
4393
  }
4405
4394
 
4406
4395
  //#endregion
@@ -4531,6 +4520,26 @@ async function setupMongoDBAtlas(config) {
4531
4520
  const serverDir = path.join(projectDir, "apps/server");
4532
4521
  try {
4533
4522
  await fs.ensureDir(serverDir);
4523
+ const mode = await select({
4524
+ message: "MongoDB Atlas setup: choose mode",
4525
+ options: [{
4526
+ label: "Automatic",
4527
+ value: "auto",
4528
+ hint: "Automated setup with provider CLI, sets .env"
4529
+ }, {
4530
+ label: "Manual",
4531
+ value: "manual",
4532
+ hint: "Manual setup, add env vars yourself"
4533
+ }],
4534
+ initialValue: "auto"
4535
+ });
4536
+ if (isCancel(mode)) return exitCancelled("Operation cancelled");
4537
+ if (mode === "manual") {
4538
+ mainSpinner.stop("MongoDB Atlas manual setup selected");
4539
+ await writeEnvFile$3(projectDir);
4540
+ displayManualSetupInstructions$3();
4541
+ return;
4542
+ }
4534
4543
  mainSpinner.stop("MongoDB Atlas setup ready");
4535
4544
  const config$1 = await initMongoDBAtlas(serverDir);
4536
4545
  if (config$1) {
@@ -4663,6 +4672,25 @@ DATABASE_URL="your_connection_string"`);
4663
4672
  async function setupNeonPostgres(config) {
4664
4673
  const { packageManager, projectDir } = config;
4665
4674
  try {
4675
+ const mode = await select({
4676
+ message: "Neon setup: choose mode",
4677
+ options: [{
4678
+ label: "Automatic",
4679
+ value: "auto",
4680
+ hint: "Automated setup with provider CLI, sets .env"
4681
+ }, {
4682
+ label: "Manual",
4683
+ value: "manual",
4684
+ hint: "Manual setup, add env vars yourself"
4685
+ }],
4686
+ initialValue: "auto"
4687
+ });
4688
+ if (isCancel(mode)) return exitCancelled("Operation cancelled");
4689
+ if (mode === "manual") {
4690
+ await writeEnvFile$2(projectDir);
4691
+ displayManualSetupInstructions$2();
4692
+ return;
4693
+ }
4666
4694
  const setupMethod = await select({
4667
4695
  message: "Choose your Neon setup method:",
4668
4696
  options: [{
@@ -4706,6 +4734,66 @@ async function setupNeonPostgres(config) {
4706
4734
  }
4707
4735
  }
4708
4736
 
4737
+ //#endregion
4738
+ //#region src/helpers/database-providers/planetscale-setup.ts
4739
+ async function setupPlanetScale(config) {
4740
+ const { projectDir, database, orm } = config;
4741
+ const envPath = path.join(projectDir, "apps/server", ".env");
4742
+ if (database === "mysql" && orm === "drizzle") {
4743
+ const variables = [
4744
+ {
4745
+ key: "DATABASE_URL",
4746
+ value: "mysql://username:password@host/database?ssl={\"rejectUnauthorized\":true}",
4747
+ condition: true
4748
+ },
4749
+ {
4750
+ key: "DATABASE_HOST",
4751
+ value: "",
4752
+ condition: true
4753
+ },
4754
+ {
4755
+ key: "DATABASE_USERNAME",
4756
+ value: "",
4757
+ condition: true
4758
+ },
4759
+ {
4760
+ key: "DATABASE_PASSWORD",
4761
+ value: "",
4762
+ condition: true
4763
+ }
4764
+ ];
4765
+ await fs.ensureDir(path.join(projectDir, "apps/server"));
4766
+ await addEnvVariablesToFile(envPath, variables);
4767
+ }
4768
+ if (database === "postgres" && orm === "prisma") {
4769
+ const variables = [{
4770
+ key: "DATABASE_URL",
4771
+ value: "postgresql://username:password@host/database?sslaccept=strict",
4772
+ condition: true
4773
+ }];
4774
+ await fs.ensureDir(path.join(projectDir, "apps/server"));
4775
+ await addEnvVariablesToFile(envPath, variables);
4776
+ }
4777
+ if (database === "postgres" && orm === "drizzle") {
4778
+ const variables = [{
4779
+ key: "DATABASE_URL",
4780
+ value: "postgresql://username:password@host/database?sslmode=verify-full",
4781
+ condition: true
4782
+ }];
4783
+ await fs.ensureDir(path.join(projectDir, "apps/server"));
4784
+ await addEnvVariablesToFile(envPath, variables);
4785
+ }
4786
+ if (database === "mysql" && orm === "prisma") {
4787
+ const variables = [{
4788
+ key: "DATABASE_URL",
4789
+ value: "mysql://username:password@host/database?sslaccept=strict",
4790
+ condition: true
4791
+ }];
4792
+ await fs.ensureDir(path.join(projectDir, "apps/server"));
4793
+ await addEnvVariablesToFile(envPath, variables);
4794
+ }
4795
+ }
4796
+
4709
4797
  //#endregion
4710
4798
  //#region src/helpers/database-providers/prisma-postgres-setup.ts
4711
4799
  const AVAILABLE_REGIONS = [
@@ -4868,6 +4956,25 @@ async function setupPrismaPostgres(config) {
4868
4956
  const serverDir = path.join(projectDir, "apps/server");
4869
4957
  try {
4870
4958
  await fs.ensureDir(serverDir);
4959
+ const mode = await select({
4960
+ message: "Prisma Postgres setup: choose mode",
4961
+ options: [{
4962
+ label: "Automatic",
4963
+ value: "auto",
4964
+ hint: "Automated setup with provider CLI, sets .env"
4965
+ }, {
4966
+ label: "Manual",
4967
+ value: "manual",
4968
+ hint: "Manual setup, add env vars yourself"
4969
+ }],
4970
+ initialValue: "auto"
4971
+ });
4972
+ if (isCancel(mode)) return exitCancelled("Operation cancelled");
4973
+ if (mode === "manual") {
4974
+ await writeEnvFile$1(projectDir);
4975
+ displayManualSetupInstructions$1();
4976
+ return;
4977
+ }
4871
4978
  const setupOptions = [{
4872
4979
  label: "Quick setup with create-db",
4873
4980
  value: "create-db",
@@ -5006,6 +5113,25 @@ async function setupSupabase(config) {
5006
5113
  const serverDir = path.join(projectDir, "apps", "server");
5007
5114
  try {
5008
5115
  await fs.ensureDir(serverDir);
5116
+ const mode = await select({
5117
+ message: "Supabase setup: choose mode",
5118
+ options: [{
5119
+ label: "Automatic",
5120
+ value: "auto",
5121
+ hint: "Automated setup with provider CLI, sets .env"
5122
+ }, {
5123
+ label: "Manual",
5124
+ value: "manual",
5125
+ hint: "Manual setup, add env vars yourself"
5126
+ }],
5127
+ initialValue: "auto"
5128
+ });
5129
+ if (isCancel(mode)) return exitCancelled("Operation cancelled");
5130
+ if (mode === "manual") {
5131
+ displayManualSupabaseInstructions();
5132
+ await writeSupabaseEnvFile(projectDir, "");
5133
+ return;
5134
+ }
5009
5135
  const initialized = await initializeSupabase(serverDir, packageManager);
5010
5136
  if (!initialized) {
5011
5137
  displayManualSupabaseInstructions();
@@ -5175,19 +5301,38 @@ DATABASE_AUTH_TOKEN=your_auth_token`);
5175
5301
  async function setupTurso(config) {
5176
5302
  const { orm, projectDir } = config;
5177
5303
  const setupSpinner = spinner();
5178
- setupSpinner.start("Checking Turso CLI availability...");
5179
5304
  try {
5305
+ const mode = await select({
5306
+ message: "Turso setup: choose mode",
5307
+ options: [{
5308
+ label: "Automatic",
5309
+ value: "auto",
5310
+ hint: "Automated setup with provider CLI, sets .env"
5311
+ }, {
5312
+ label: "Manual",
5313
+ value: "manual",
5314
+ hint: "Manual setup, add env vars yourself"
5315
+ }],
5316
+ initialValue: "auto"
5317
+ });
5318
+ if (isCancel(mode)) return exitCancelled("Operation cancelled");
5319
+ if (mode === "manual") {
5320
+ await writeEnvFile(projectDir);
5321
+ displayManualSetupInstructions();
5322
+ return;
5323
+ }
5324
+ setupSpinner.start("Checking Turso CLI availability...");
5180
5325
  const platform = os.platform();
5181
5326
  const isMac = platform === "darwin";
5182
5327
  const isWindows = platform === "win32";
5183
5328
  if (isWindows) {
5184
- setupSpinner.stop(pc.yellow("Turso setup not supported on Windows"));
5329
+ if (setupSpinner) setupSpinner.stop(pc.yellow("Turso setup not supported on Windows"));
5185
5330
  log.warn(pc.yellow("Automatic Turso setup is not supported on Windows."));
5186
5331
  await writeEnvFile(projectDir);
5187
5332
  displayManualSetupInstructions();
5188
5333
  return;
5189
5334
  }
5190
- setupSpinner.stop("Turso CLI availability checked");
5335
+ if (setupSpinner) setupSpinner.stop("Turso CLI availability checked");
5191
5336
  const isCliInstalled = await isTursoInstalled();
5192
5337
  if (!isCliInstalled) {
5193
5338
  const shouldInstall = await confirm({
@@ -5230,7 +5375,7 @@ async function setupTurso(config) {
5230
5375
  }
5231
5376
  log.success("Turso database setup completed successfully!");
5232
5377
  } catch (error) {
5233
- setupSpinner.stop(pc.red("Turso CLI availability check failed"));
5378
+ if (setupSpinner) setupSpinner.stop(pc.red("Turso CLI availability check failed"));
5234
5379
  consola.error(pc.red(`Error during Turso setup: ${error instanceof Error ? error.message : String(error)}`));
5235
5380
  await writeEnvFile(projectDir);
5236
5381
  displayManualSetupInstructions();
@@ -5254,7 +5399,21 @@ async function setupDatabase(config) {
5254
5399
  const serverDir = path.join(projectDir, "apps/server");
5255
5400
  if (!await fs.pathExists(serverDir)) return;
5256
5401
  try {
5257
- if (orm === "prisma") await addPackageDependency({
5402
+ if (orm === "prisma") if (database === "mysql" && dbSetup === "planetscale") await addPackageDependency({
5403
+ dependencies: [
5404
+ "@prisma/client",
5405
+ "@prisma/adapter-planetscale",
5406
+ "@planetscale/database"
5407
+ ],
5408
+ devDependencies: ["prisma"],
5409
+ projectDir: serverDir
5410
+ });
5411
+ else if (database === "sqlite" && dbSetup === "turso") await addPackageDependency({
5412
+ dependencies: ["@prisma/client", "@prisma/adapter-libsql"],
5413
+ devDependencies: ["prisma"],
5414
+ projectDir: serverDir
5415
+ });
5416
+ else await addPackageDependency({
5258
5417
  dependencies: ["@prisma/client"],
5259
5418
  devDependencies: ["prisma"],
5260
5419
  projectDir: serverDir
@@ -5274,12 +5433,22 @@ async function setupDatabase(config) {
5274
5433
  devDependencies: ["drizzle-kit", "@types/ws"],
5275
5434
  projectDir: serverDir
5276
5435
  });
5436
+ else if (dbSetup === "planetscale") await addPackageDependency({
5437
+ dependencies: ["drizzle-orm", "pg"],
5438
+ devDependencies: ["drizzle-kit", "@types/pg"],
5439
+ projectDir: serverDir
5440
+ });
5277
5441
  else await addPackageDependency({
5278
5442
  dependencies: ["drizzle-orm", "pg"],
5279
5443
  devDependencies: ["drizzle-kit", "@types/pg"],
5280
5444
  projectDir: serverDir
5281
5445
  });
5282
- else if (database === "mysql") await addPackageDependency({
5446
+ else if (database === "mysql") if (dbSetup === "planetscale") await addPackageDependency({
5447
+ dependencies: ["drizzle-orm", "@planetscale/database"],
5448
+ devDependencies: ["drizzle-kit"],
5449
+ projectDir: serverDir
5450
+ });
5451
+ else await addPackageDependency({
5283
5452
  dependencies: ["drizzle-orm", "mysql2"],
5284
5453
  devDependencies: ["drizzle-kit"],
5285
5454
  projectDir: serverDir
@@ -5295,7 +5464,10 @@ async function setupDatabase(config) {
5295
5464
  else if (database === "postgres") {
5296
5465
  if (dbSetup === "prisma-postgres") await setupPrismaPostgres(config);
5297
5466
  else if (dbSetup === "neon") await setupNeonPostgres(config);
5467
+ else if (dbSetup === "planetscale") await setupPlanetScale(config);
5298
5468
  else if (dbSetup === "supabase") await setupSupabase(config);
5469
+ } else if (database === "mysql") {
5470
+ if (dbSetup === "planetscale") await setupPlanetScale(config);
5299
5471
  } else if (database === "mongodb" && dbSetup === "mongodb-atlas") await setupMongoDBAtlas(config);
5300
5472
  } catch (error) {
5301
5473
  s.stop(pc.red("Failed to set up database"));
@@ -5841,9 +6013,18 @@ async function getDatabaseInstructions(database, orm, runCmd, _runtime, dbSetup,
5841
6013
  instructions.push(`${pc.cyan("5.")} Apply migrations locally: ${pc.white(`${packageManager} wrangler d1 migrations apply YOUR_DB_NAME --local`)}`);
5842
6014
  instructions.push(`${pc.cyan("6.")} Apply migrations to production: ${pc.white(`${packageManager} wrangler d1 migrations apply YOUR_DB_NAME`)}`);
5843
6015
  }
5844
- if (dbSetup === "d1" && serverDeploy === "alchemy") instructions.push(`${pc.yellow("NOTE:")} D1 migrations are automatically handled by Alchemy`);
6016
+ if (dbSetup === "d1" && serverDeploy === "alchemy") {
6017
+ if (orm === "drizzle") instructions.push(`${pc.yellow("NOTE:")} D1 migrations are automatically handled by Alchemy`);
6018
+ else if (orm === "prisma") {
6019
+ instructions.push(`${pc.cyan("•")} Generate migrations: ${`${runCmd} db:generate`}`);
6020
+ instructions.push(`${pc.cyan("•")} Apply migrations: ${`${runCmd} db:migrate`}`);
6021
+ }
6022
+ }
6023
+ if (dbSetup === "planetscale") {
6024
+ if (database === "mysql" && orm === "drizzle") instructions.push(`${pc.yellow("NOTE:")} Enable foreign key constraints in PlanetScale database settings`);
6025
+ if (database === "mysql" && orm === "prisma") instructions.push(`${pc.yellow("NOTE:")} How to handle Prisma migrations with PlanetScale:\n https://github.com/prisma/prisma/issues/7292`);
6026
+ }
5845
6027
  if (orm === "prisma") {
5846
- if (dbSetup === "turso") instructions.push(`${pc.yellow("NOTE:")} Turso support with Prisma is in Early Access and requires\n additional setup. Learn more at:\n https://www.prisma.io/docs/orm/overview/databases/turso`);
5847
6028
  if (database === "mongodb" && dbSetup === "docker") instructions.push(`${pc.yellow("WARNING:")} Prisma + MongoDB + Docker combination\n may not work.`);
5848
6029
  if (dbSetup === "docker") instructions.push(`${pc.cyan("•")} Start docker container: ${`${runCmd} db:start`}`);
5849
6030
  instructions.push(`${pc.cyan("•")} Apply schema: ${`${runCmd} db:push`}`);
@@ -5928,7 +6109,7 @@ async function updateRootPackageJson(projectDir, options) {
5928
6109
  if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `turbo -F ${backendPackageName} db:studio`;
5929
6110
  if (options.orm === "prisma") {
5930
6111
  scripts["db:generate"] = `turbo -F ${backendPackageName} db:generate`;
5931
- if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `turbo -F ${backendPackageName} db:migrate`;
6112
+ scripts["db:migrate"] = `turbo -F ${backendPackageName} db:migrate`;
5932
6113
  } else if (options.orm === "drizzle") {
5933
6114
  scripts["db:generate"] = `turbo -F ${backendPackageName} db:generate`;
5934
6115
  if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `turbo -F ${backendPackageName} db:migrate`;
@@ -5953,7 +6134,7 @@ async function updateRootPackageJson(projectDir, options) {
5953
6134
  if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `pnpm --filter ${backendPackageName} db:studio`;
5954
6135
  if (options.orm === "prisma") {
5955
6136
  scripts["db:generate"] = `pnpm --filter ${backendPackageName} db:generate`;
5956
- if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `pnpm --filter ${backendPackageName} db:migrate`;
6137
+ scripts["db:migrate"] = `pnpm --filter ${backendPackageName} db:migrate`;
5957
6138
  } else if (options.orm === "drizzle") {
5958
6139
  scripts["db:generate"] = `pnpm --filter ${backendPackageName} db:generate`;
5959
6140
  if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `pnpm --filter ${backendPackageName} db:migrate`;
@@ -5978,7 +6159,7 @@ async function updateRootPackageJson(projectDir, options) {
5978
6159
  if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `npm run db:studio --workspace ${backendPackageName}`;
5979
6160
  if (options.orm === "prisma") {
5980
6161
  scripts["db:generate"] = `npm run db:generate --workspace ${backendPackageName}`;
5981
- if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `npm run db:migrate --workspace ${backendPackageName}`;
6162
+ scripts["db:migrate"] = `npm run db:migrate --workspace ${backendPackageName}`;
5982
6163
  } else if (options.orm === "drizzle") {
5983
6164
  scripts["db:generate"] = `npm run db:generate --workspace ${backendPackageName}`;
5984
6165
  if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `npm run db:migrate --workspace ${backendPackageName}`;
@@ -6003,7 +6184,7 @@ async function updateRootPackageJson(projectDir, options) {
6003
6184
  if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `bun run --filter ${backendPackageName} db:studio`;
6004
6185
  if (options.orm === "prisma") {
6005
6186
  scripts["db:generate"] = `bun run --filter ${backendPackageName} db:generate`;
6006
- if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `bun run --filter ${backendPackageName} db:migrate`;
6187
+ scripts["db:migrate"] = `bun run --filter ${backendPackageName} db:migrate`;
6007
6188
  } else if (options.orm === "drizzle") {
6008
6189
  scripts["db:generate"] = `bun run --filter ${backendPackageName} db:generate`;
6009
6190
  if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `bun run --filter ${backendPackageName} db:migrate`;
@@ -6046,7 +6227,7 @@ async function updateServerPackageJson(projectDir, options) {
6046
6227
  scripts["db:push"] = "prisma db push";
6047
6228
  if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = "prisma studio";
6048
6229
  scripts["db:generate"] = "prisma generate";
6049
- if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = "prisma migrate dev";
6230
+ scripts["db:migrate"] = "prisma migrate dev";
6050
6231
  } else if (options.orm === "drizzle") {
6051
6232
  scripts["db:push"] = "drizzle-kit push";
6052
6233
  if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = "drizzle-kit studio";
@@ -6105,7 +6286,6 @@ async function createProject(options) {
6105
6286
  await setupServerDeploy(options);
6106
6287
  await createReadme(projectDir, options);
6107
6288
  await writeBtsConfig(options);
6108
- await formatProjectWithBiome(projectDir);
6109
6289
  if (isConvex) await runConvexCodegen(projectDir, options.packageManager);
6110
6290
  log.success("Project template successfully scaffolded!");
6111
6291
  if (options.install) await installDependencies({
@@ -6369,7 +6549,7 @@ async function openUrl(url) {
6369
6549
 
6370
6550
  //#endregion
6371
6551
  //#region src/utils/sponsors.ts
6372
- const SPONSORS_JSON_URL = "https://sponsors.amanv.dev/sponsors.json";
6552
+ const SPONSORS_JSON_URL = "https://sponsors.better-t-stack.dev/sponsors.json";
6373
6553
  async function fetchSponsors(url = SPONSORS_JSON_URL) {
6374
6554
  const s = spinner();
6375
6555
  s.start("Fetching sponsors…");
@@ -6383,23 +6563,30 @@ async function fetchSponsors(url = SPONSORS_JSON_URL) {
6383
6563
  return sponsors$1;
6384
6564
  }
6385
6565
  function displaySponsors(sponsors$1) {
6386
- if (sponsors$1.length === 0) {
6566
+ const { total_sponsors } = sponsors$1.summary;
6567
+ if (total_sponsors === 0) {
6387
6568
  log.info("No sponsors found. You can be the first one! ✨");
6388
6569
  outro(pc.cyan("Visit https://github.com/sponsors/AmanVarshney01 to become a sponsor."));
6389
6570
  return;
6390
6571
  }
6391
- sponsors$1.forEach((entry, idx) => {
6392
- const sponsor = entry.sponsor;
6393
- const displayName = sponsor.name ?? sponsor.login;
6394
- const tier = entry.tierName ? ` (${entry.tierName})` : "";
6395
- log.step(`${idx + 1}. ${pc.green(displayName)}${pc.yellow(tier)}`);
6396
- log.message(` ${pc.dim("GitHub:")} https://github.com/${sponsor.login}`);
6397
- const website = sponsor.websiteUrl ?? sponsor.linkUrl;
6398
- if (website) log.message(` ${pc.dim("Website:")} ${website}`);
6399
- });
6400
- log.message("");
6572
+ displaySponsorsBox(sponsors$1);
6573
+ if (total_sponsors - sponsors$1.specialSponsors.length > 0) log.message(pc.blue(`+${total_sponsors - sponsors$1.specialSponsors.length} more amazing sponsors.\n`));
6401
6574
  outro(pc.magenta("Visit https://github.com/sponsors/AmanVarshney01 to become a sponsor."));
6402
6575
  }
6576
+ function displaySponsorsBox(sponsors$1) {
6577
+ if (sponsors$1.specialSponsors.length === 0) return;
6578
+ let output = `${pc.bold(pc.cyan("-> Special Sponsors"))}\n\n`;
6579
+ sponsors$1.specialSponsors.forEach((sponsor, idx) => {
6580
+ const displayName = sponsor.name ?? sponsor.githubId;
6581
+ const tier = sponsor.tierName ? ` ${pc.yellow(`(${sponsor.tierName})`)}` : "";
6582
+ output += `${pc.green(`• ${displayName}`)}${tier}\n`;
6583
+ output += ` ${pc.dim("GitHub:")} https://github.com/${sponsor.githubId}\n`;
6584
+ const website = sponsor.websiteUrl ?? sponsor.githubUrl;
6585
+ if (website) output += ` ${pc.dim("Website:")} ${website}\n`;
6586
+ if (idx < sponsors$1.specialSponsors.length - 1) output += "\n";
6587
+ });
6588
+ consola$1.box(output);
6589
+ }
6403
6590
 
6404
6591
  //#endregion
6405
6592
  //#region src/index.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-better-t-stack",
3
- "version": "2.40.4",
3
+ "version": "2.41.0",
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",
@@ -64,8 +64,6 @@
64
64
  }
65
65
  },
66
66
  "dependencies": {
67
- "@biomejs/js-api": "^3.0.0",
68
- "@biomejs/wasm-nodejs": "^2.2.0",
69
67
  "@clack/prompts": "^1.0.0-alpha.4",
70
68
  "consola": "^3.4.2",
71
69
  "execa": "^9.6.0",
@@ -74,16 +72,16 @@
74
72
  "handlebars": "^4.7.8",
75
73
  "jsonc-parser": "^3.3.1",
76
74
  "picocolors": "^1.1.1",
77
- "tinyglobby": "^0.2.14",
75
+ "tinyglobby": "^0.2.15",
78
76
  "trpc-cli": "^0.10.2",
79
- "ts-morph": "^26.0.0",
80
- "zod": "^4.0.17"
77
+ "ts-morph": "^27.0.0",
78
+ "zod": "^4.1.5"
81
79
  },
82
80
  "devDependencies": {
83
81
  "@types/fs-extra": "^11.0.4",
84
- "@types/node": "^24.3.0",
82
+ "@types/node": "^24.3.1",
85
83
  "@vitest/ui": "^3.2.4",
86
- "tsdown": "^0.14.1",
84
+ "tsdown": "^0.14.2",
87
85
  "typescript": "^5.9.2",
88
86
  "vitest": "^3.2.4"
89
87
  }
@@ -4,7 +4,7 @@ import { prismaAdapter } from "better-auth/adapters/prisma";
4
4
  {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
5
5
  import { expo } from "@better-auth/expo";
6
6
  {{/if}}
7
- import prisma from "../../prisma";
7
+ import prisma from "../db";
8
8
 
9
9
  export const auth = betterAuth({
10
10
  database: prismaAdapter(prisma, {
@@ -1,4 +1,15 @@
1
1
  {{#if (or (eq runtime "bun") (eq runtime "node"))}}
2
+ {{#if (eq dbSetup "planetscale")}}
3
+ import { drizzle } from "drizzle-orm/planetscale-serverless";
4
+
5
+ export const db = drizzle({
6
+ connection: {
7
+ host: process.env.DATABASE_HOST,
8
+ username: process.env.DATABASE_USERNAME,
9
+ password: process.env.DATABASE_PASSWORD,
10
+ },
11
+ });
12
+ {{else}}
2
13
  import { drizzle } from "drizzle-orm/mysql2";
3
14
 
4
15
  export const db = drizzle({
@@ -7,8 +18,21 @@ export const db = drizzle({
7
18
  },
8
19
  });
9
20
  {{/if}}
21
+ {{/if}}
10
22
 
11
23
  {{#if (eq runtime "workers")}}
24
+ {{#if (eq dbSetup "planetscale")}}
25
+ import { drizzle } from "drizzle-orm/planetscale-serverless";
26
+ import { env } from "cloudflare:workers";
27
+
28
+ export const db = drizzle({
29
+ connection: {
30
+ host: env.DATABASE_HOST,
31
+ username: env.DATABASE_USERNAME,
32
+ password: env.DATABASE_PASSWORD,
33
+ },
34
+ });
35
+ {{else}}
12
36
  import { drizzle } from "drizzle-orm/mysql2";
13
37
  import { env } from "cloudflare:workers";
14
38
 
@@ -18,3 +42,4 @@ export const db = drizzle({
18
42
  },
19
43
  });
20
44
  {{/if}}
45
+ {{/if}}
@@ -0,0 +1,5 @@
1
+ import { PrismaClient } from "../../prisma/generated/client";
2
+
3
+ const prisma = new PrismaClient();
4
+
5
+ export default prisma;
@@ -11,9 +11,15 @@ generator client {
11
11
  {{#if (eq runtime "workers")}}
12
12
  runtime = "workerd"
13
13
  {{/if}}
14
+ {{#if (eq dbSetup "planetscale")}}
15
+ previewFeatures = ["driverAdapters"]
16
+ {{/if}}
14
17
  }
15
18
 
16
19
  datasource db {
17
20
  provider = "mysql"
18
21
  url = env("DATABASE_URL")
22
+ {{#if (eq dbSetup "planetscale")}}
23
+ relationMode = "prisma"
24
+ {{/if}}
19
25
  }
@@ -0,0 +1,12 @@
1
+ import { PrismaClient } from "../../prisma/generated/client";
2
+ {{#if (eq dbSetup "planetscale")}}
3
+ import { PrismaPlanetScale } from '@prisma/adapter-planetscale'
4
+
5
+ const adapter = new PrismaPlanetScale({ url: process.env.DATABASE_URL })
6
+
7
+ const prisma = new PrismaClient({adapter});
8
+ {{else}}
9
+ const prisma = new PrismaClient();
10
+ {{/if}}
11
+
12
+ export default prisma;
@@ -19,4 +19,7 @@ datasource db {
19
19
  {{#if (eq dbSetup "supabase")}}
20
20
  directUrl = env("DIRECT_URL")
21
21
  {{/if}}
22
+ {{#if (eq dbSetup "planetscale")}}
23
+ relationMode = "prisma"
24
+ {{/if}}
22
25
  }
@@ -0,0 +1,5 @@
1
+ import { PrismaClient } from "../../prisma/generated/client";
2
+
3
+ const prisma = new PrismaClient();
4
+
5
+ export default prisma;
@@ -10,10 +10,20 @@ generator client {
10
10
  {{/if}}
11
11
  {{#if (eq runtime "workers")}}
12
12
  runtime = "workerd"
13
+ {{#if (eq dbSetup "d1")}}
14
+ previewFeatures = ["driverAdapters"]
15
+ {{/if}}
16
+ {{/if}}
17
+ {{#if (eq dbSetup "turso")}}
18
+ previewFeatures = ["driverAdapters"]
13
19
  {{/if}}
14
20
  }
15
21
 
16
22
  datasource db {
17
23
  provider = "sqlite"
24
+ {{#if (eq dbSetup "turso")}}
25
+ url = "file:./local.db"
26
+ {{else}}
18
27
  url = env("DATABASE_URL")
28
+ {{/if}}
19
29
  }
@@ -1,10 +1,38 @@
1
1
  import "dotenv/config";
2
2
  import path from "node:path";
3
3
  import type { PrismaConfig } from "prisma";
4
+ {{#if (eq dbSetup "d1")}}
5
+ import { PrismaD1 } from "@prisma/adapter-d1";
6
+ {{/if}}
7
+ {{#if (eq dbSetup "turso")}}
8
+ import { PrismaLibSQL } from "@prisma/adapter-libsql";
9
+ {{/if}}
4
10
 
5
11
  export default {
12
+ {{#if (or (eq dbSetup "d1") (eq dbSetup "turso"))}}
13
+ experimental: {
14
+ adapter: true
15
+ },
16
+ {{/if}}
6
17
  schema: path.join("prisma", "schema"),
7
18
  migrations: {
8
19
  path: path.join("prisma", "migrations"),
9
- }
20
+ },
21
+ {{#if (eq dbSetup "d1")}}
22
+ async adapter() {
23
+ return new PrismaD1({
24
+ CLOUDFLARE_D1_TOKEN: process.env.CLOUDFLARE_D1_TOKEN,
25
+ CLOUDFLARE_ACCOUNT_ID: process.env.CLOUDFLARE_ACCOUNT_ID,
26
+ CLOUDFLARE_DATABASE_ID: process.env.CLOUDFLARE_DATABASE_ID,
27
+ });
28
+ },
29
+ {{/if}}
30
+ {{#if (eq dbSetup "turso")}}
31
+ async adapter() {
32
+ return new PrismaLibSQL({
33
+ url: process.env.DATABASE_URL || "",
34
+ authToken: process.env.DATABASE_AUTH_TOKEN,
35
+ });
36
+ },
37
+ {{/if}}
10
38
  } satisfies PrismaConfig;
@@ -0,0 +1,28 @@
1
+ {{#if (eq dbSetup "d1")}}
2
+ import { env } from "cloudflare:workers";
3
+ import { PrismaD1 } from "@prisma/adapter-d1";
4
+ import { PrismaClient } from "../../prisma/generated/client";
5
+
6
+ const adapter = new PrismaD1(env.DB);
7
+ const prisma = new PrismaClient({ adapter });
8
+
9
+ export default prisma;
10
+ {{else if (eq dbSetup "turso")}}
11
+ import { PrismaLibSQL } from "@prisma/adapter-libsql";
12
+ import { PrismaClient } from "../../prisma/generated/client";
13
+
14
+ const adapter = new PrismaLibSQL({
15
+ url: process.env.DATABASE_URL || "",
16
+ authToken: process.env.DATABASE_AUTH_TOKEN,
17
+ });
18
+
19
+ const prisma = new PrismaClient({ adapter });
20
+
21
+ export default prisma;
22
+ {{else}}
23
+ import { PrismaClient } from "../../prisma/generated/client";
24
+
25
+ const prisma = new PrismaClient();
26
+
27
+ export default prisma;
28
+ {{/if}}
@@ -1,7 +1,7 @@
1
1
  import alchemy from "alchemy";
2
2
  {{#if (eq webDeploy "alchemy")}}
3
3
  {{#if (includes frontend "next")}}
4
- import { Next } from "alchemy/cloudflare";
4
+ import { Nextjs } from "alchemy/cloudflare";
5
5
  {{else if (includes frontend "nuxt")}}
6
6
  import { Nuxt } from "alchemy/cloudflare";
7
7
  {{else if (includes frontend "svelte")}}
@@ -44,13 +44,17 @@ await Exec("db-generate", {
44
44
  });
45
45
 
46
46
  const db = await D1Database("database", {
47
+ {{#if (eq orm "prisma")}}
48
+ migrationsDir: "apps/server/prisma/migrations",
49
+ {{else if (eq orm "drizzle")}}
47
50
  migrationsDir: "apps/server/src/db/migrations",
51
+ {{/if}}
48
52
  });
49
53
  {{/if}}
50
54
 
51
55
  {{#if (eq webDeploy "alchemy")}}
52
56
  {{#if (includes frontend "next")}}
53
- export const web = await Next("web", {
57
+ export const web = await Nextjs("web", {
54
58
  {{#if (eq serverDeploy "alchemy")}}cwd: "apps/web",{{/if}}
55
59
  bindings: {
56
60
  {{#if (eq backend "convex")}}
@@ -27,7 +27,12 @@
27
27
  "database_name": "YOUR_DB_NAME",
28
28
  "database_id": "YOUR_DB_ID",
29
29
  "preview_database_id": "local-test-db",
30
+ {{#if (eq orm "drizzle")}}
30
31
  "migrations_dir": "./src/db/migrations"
32
+ {{/if}}
33
+ {{#if (eq orm "prisma")}}
34
+ "migrations_dir": "./prisma/migrations"
35
+ {{/if}}
31
36
  }
32
37
  ]
33
38
  {{/if}}
@@ -1,6 +1,6 @@
1
1
  {{#if (eq api "orpc")}}
2
2
  import z from "zod";
3
- import prisma from "../../prisma";
3
+ import prisma from "../db";
4
4
  import { publicProcedure } from "../lib/orpc";
5
5
 
6
6
  export const todoRouter = {
@@ -52,7 +52,7 @@ export const todoRouter = {
52
52
  {{#if (eq api "trpc")}}
53
53
  import { TRPCError } from "@trpc/server";
54
54
  import z from "zod";
55
- import prisma from "../../prisma";
55
+ import prisma from "../db";
56
56
  import { publicProcedure, router } from "../lib/trpc";
57
57
 
58
58
  export const todoRouter = router({
@@ -1,7 +1,2 @@
1
1
  [install]
2
- {{#if (or (or (includes frontend "nuxt") (includes frontend "native-nativewind")) (includes frontend
3
- "native-unistyles"))}}
4
2
  # linker = "isolated"
5
- {{else}}
6
- linker = "isolated"
7
- {{/if}}
@@ -1,3 +1,6 @@
1
+ {{#if (or (eq webDeploy "alchemy") (eq webDeploy "wrangler"))}}
2
+ import { initOpenNextCloudflareForDev } from "@opennextjs/cloudflare";
3
+ {{/if}}
1
4
  import type { NextConfig } from "next";
2
5
 
3
6
  const nextConfig: NextConfig = {
@@ -5,3 +8,7 @@ const nextConfig: NextConfig = {
5
8
  };
6
9
 
7
10
  export default nextConfig;
11
+
12
+ {{#if (or (eq webDeploy "alchemy") (eq webDeploy "wrangler"))}}
13
+ initOpenNextCloudflareForDev();
14
+ {{/if}}
@@ -1,5 +0,0 @@
1
- import { PrismaClient } from "./generated/client";
2
-
3
- const prisma = new PrismaClient();
4
-
5
- export default prisma;
@@ -1,5 +0,0 @@
1
- import { PrismaClient } from "./generated/client";
2
-
3
- const prisma = new PrismaClient();
4
-
5
- export default prisma;
@@ -1,5 +0,0 @@
1
- import { PrismaClient } from "./generated/client";
2
-
3
- const prisma = new PrismaClient();
4
-
5
- export default prisma;
@@ -1,5 +0,0 @@
1
- import { PrismaClient } from "./generated/client";
2
-
3
- const prisma = new PrismaClient();
4
-
5
- export default prisma;