create-better-t-stack 2.40.4-canary.49f23d48 → 2.40.4-canary.f07be855

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-D32Mc1TH.js";
2
+ import { createBtsCli } from "./src-BMkTBm89.js";
3
3
 
4
4
  //#region src/cli.ts
5
5
  createBtsCli().run();
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-D32Mc1TH.js";
2
+ import { builder, createBtsCli, docs, init, router, sponsors } from "./src-BMkTBm89.js";
3
3
 
4
4
  export { builder, createBtsCli, docs, init, router, sponsors };
@@ -79,6 +79,7 @@ const dependencyVersionMap = {
79
79
  prisma: "^6.15.0",
80
80
  "@prisma/adapter-d1": "^6.15.0",
81
81
  "@prisma/extension-accelerate": "^2.0.2",
82
+ "@prisma/adapter-libsql": "^6.15.0",
82
83
  "@prisma/adapter-planetscale": "^6.15.0",
83
84
  undici: "^7.15.0",
84
85
  mongoose: "^8.14.0",
@@ -136,7 +137,7 @@ const dependencyVersionMap = {
136
137
  "@tanstack/solid-router-devtools": "^1.131.25",
137
138
  wrangler: "^4.23.0",
138
139
  "@cloudflare/vite-plugin": "^1.9.0",
139
- "@opennextjs/cloudflare": "^1.3.0",
140
+ "@opennextjs/cloudflare": "^1.6.5",
140
141
  "nitro-cloudflare-dev": "^0.2.2",
141
142
  "@sveltejs/adapter-cloudflare": "^7.2.1",
142
143
  "@cloudflare/workers-types": "^4.20250822.0",
@@ -567,17 +568,6 @@ function validateExamplesCompatibility(examples, backend, database, frontend) {
567
568
  if (examplesArr.includes("ai") && backend === "elysia") exitWithError("The 'ai' example is not compatible with the Elysia backend.");
568
569
  if (examplesArr.includes("ai") && (frontend ?? []).includes("solid")) exitWithError("The 'ai' example is not compatible with the Solid frontend.");
569
570
  }
570
- function validateAlchemyCompatibility(webDeploy, serverDeploy, frontends = []) {
571
- const isAlchemyWebDeploy = webDeploy === "alchemy";
572
- const isAlchemyServerDeploy = serverDeploy === "alchemy";
573
- if (isAlchemyWebDeploy || isAlchemyServerDeploy) {
574
- const incompatibleFrontends = frontends.filter((f) => f === "next");
575
- if (incompatibleFrontends.length > 0) {
576
- const deployType = isAlchemyWebDeploy && isAlchemyServerDeploy ? "web and server deployment" : isAlchemyWebDeploy ? "web deployment" : "server deployment";
577
- exitWithError(`Alchemy ${deployType} is temporarily not compatible with ${incompatibleFrontends.join(" and ")} frontend(s). Please choose a different frontend or deployment option.`);
578
- }
579
- }
580
- }
581
571
 
582
572
  //#endregion
583
573
  //#region src/prompts/api.ts
@@ -1174,8 +1164,7 @@ function getDeploymentDisplay(deployment) {
1174
1164
  async function getDeploymentChoice(deployment, _runtime, _backend, frontend = []) {
1175
1165
  if (deployment !== void 0) return deployment;
1176
1166
  if (!hasWebFrontend(frontend)) return "none";
1177
- const hasIncompatibleFrontend = frontend.some((f) => f === "next");
1178
- const availableDeployments = hasIncompatibleFrontend ? ["wrangler", "none"] : [
1167
+ const availableDeployments = [
1179
1168
  "wrangler",
1180
1169
  "alchemy",
1181
1170
  "none"
@@ -1191,14 +1180,13 @@ async function getDeploymentChoice(deployment, _runtime, _backend, frontend = []
1191
1180
  const response = await select({
1192
1181
  message: "Select web deployment",
1193
1182
  options,
1194
- initialValue: hasIncompatibleFrontend ? "wrangler" : DEFAULT_CONFIG.webDeploy
1183
+ initialValue: DEFAULT_CONFIG.webDeploy
1195
1184
  });
1196
1185
  if (isCancel(response)) return exitCancelled("Operation cancelled");
1197
1186
  return response;
1198
1187
  }
1199
1188
  async function getDeploymentToAdd(frontend, existingDeployment) {
1200
1189
  if (!hasWebFrontend(frontend)) return "none";
1201
- const hasIncompatibleFrontend = frontend.some((f) => f === "next");
1202
1190
  const options = [];
1203
1191
  if (existingDeployment !== "wrangler") {
1204
1192
  const { label, hint } = getDeploymentDisplay("wrangler");
@@ -1208,7 +1196,7 @@ async function getDeploymentToAdd(frontend, existingDeployment) {
1208
1196
  hint
1209
1197
  });
1210
1198
  }
1211
- if (existingDeployment !== "alchemy" && !hasIncompatibleFrontend) {
1199
+ if (existingDeployment !== "alchemy") {
1212
1200
  const { label, hint } = getDeploymentDisplay("alchemy");
1213
1201
  options.push({
1214
1202
  value: "alchemy",
@@ -1226,7 +1214,7 @@ async function getDeploymentToAdd(frontend, existingDeployment) {
1226
1214
  const response = await select({
1227
1215
  message: "Select web deployment",
1228
1216
  options,
1229
- initialValue: hasIncompatibleFrontend ? "wrangler" : DEFAULT_CONFIG.webDeploy
1217
+ initialValue: DEFAULT_CONFIG.webDeploy
1230
1218
  });
1231
1219
  if (isCancel(response)) return exitCancelled("Operation cancelled");
1232
1220
  return response;
@@ -1772,7 +1760,6 @@ function validateFullConfig(config, providedFlags, options) {
1772
1760
  config.addons = [...new Set(config.addons)];
1773
1761
  }
1774
1762
  validateExamplesCompatibility(config.examples ?? [], config.backend, config.database, config.frontend ?? []);
1775
- validateAlchemyCompatibility(config.webDeploy, config.serverDeploy, config.frontend ?? []);
1776
1763
  }
1777
1764
  function validateConfigForProgrammaticUse(config) {
1778
1765
  try {
@@ -3109,7 +3096,12 @@ async function setupNextAlchemyDeploy(projectDir, _packageManager, options) {
3109
3096
  const webAppDir = path.join(projectDir, "apps/web");
3110
3097
  if (!await fs.pathExists(webAppDir)) return;
3111
3098
  await addPackageDependency({
3112
- devDependencies: ["alchemy", "dotenv"],
3099
+ dependencies: ["@opennextjs/cloudflare"],
3100
+ devDependencies: [
3101
+ "alchemy",
3102
+ "dotenv",
3103
+ "wrangler"
3104
+ ],
3113
3105
  projectDir: webAppDir
3114
3106
  });
3115
3107
  const pkgPath = path.join(webAppDir, "package.json");
@@ -3122,6 +3114,17 @@ async function setupNextAlchemyDeploy(projectDir, _packageManager, options) {
3122
3114
  };
3123
3115
  await fs.writeJson(pkgPath, pkg, { spaces: 2 });
3124
3116
  }
3117
+ const openNextConfigPath = path.join(webAppDir, "open-next.config.ts");
3118
+ const openNextConfigContent = `import { defineCloudflareConfig } from "@opennextjs/cloudflare";
3119
+
3120
+ export default defineCloudflareConfig({});
3121
+ `;
3122
+ await fs.writeFile(openNextConfigPath, openNextConfigContent);
3123
+ const gitignorePath = path.join(webAppDir, ".gitignore");
3124
+ if (await fs.pathExists(gitignorePath)) {
3125
+ const gitignoreContent = await fs.readFile(gitignorePath, "utf-8");
3126
+ if (!gitignoreContent.includes("wrangler.jsonc")) await fs.appendFile(gitignorePath, "\nwrangler.jsonc\n");
3127
+ } else await fs.writeFile(gitignorePath, "wrangler.jsonc\n");
3125
3128
  }
3126
3129
 
3127
3130
  //#endregion
@@ -4567,6 +4570,26 @@ async function setupMongoDBAtlas(config) {
4567
4570
  const serverDir = path.join(projectDir, "apps/server");
4568
4571
  try {
4569
4572
  await fs.ensureDir(serverDir);
4573
+ const mode = await select({
4574
+ message: "MongoDB Atlas setup: choose mode",
4575
+ options: [{
4576
+ label: "Automatic",
4577
+ value: "auto",
4578
+ hint: "Automated setup with provider CLI, sets .env"
4579
+ }, {
4580
+ label: "Manual",
4581
+ value: "manual",
4582
+ hint: "Manual setup, add env vars yourself"
4583
+ }],
4584
+ initialValue: "auto"
4585
+ });
4586
+ if (isCancel(mode)) return exitCancelled("Operation cancelled");
4587
+ if (mode === "manual") {
4588
+ mainSpinner.stop("MongoDB Atlas manual setup selected");
4589
+ await writeEnvFile$3(projectDir);
4590
+ displayManualSetupInstructions$3();
4591
+ return;
4592
+ }
4570
4593
  mainSpinner.stop("MongoDB Atlas setup ready");
4571
4594
  const config$1 = await initMongoDBAtlas(serverDir);
4572
4595
  if (config$1) {
@@ -4699,6 +4722,25 @@ DATABASE_URL="your_connection_string"`);
4699
4722
  async function setupNeonPostgres(config) {
4700
4723
  const { packageManager, projectDir } = config;
4701
4724
  try {
4725
+ const mode = await select({
4726
+ message: "Neon setup: choose mode",
4727
+ options: [{
4728
+ label: "Automatic",
4729
+ value: "auto",
4730
+ hint: "Automated setup with provider CLI, sets .env"
4731
+ }, {
4732
+ label: "Manual",
4733
+ value: "manual",
4734
+ hint: "Manual setup, add env vars yourself"
4735
+ }],
4736
+ initialValue: "auto"
4737
+ });
4738
+ if (isCancel(mode)) return exitCancelled("Operation cancelled");
4739
+ if (mode === "manual") {
4740
+ await writeEnvFile$2(projectDir);
4741
+ displayManualSetupInstructions$2();
4742
+ return;
4743
+ }
4702
4744
  const setupMethod = await select({
4703
4745
  message: "Choose your Neon setup method:",
4704
4746
  options: [{
@@ -4969,6 +5011,25 @@ async function setupPrismaPostgres(config) {
4969
5011
  const serverDir = path.join(projectDir, "apps/server");
4970
5012
  try {
4971
5013
  await fs.ensureDir(serverDir);
5014
+ const mode = await select({
5015
+ message: "Prisma Postgres setup: choose mode",
5016
+ options: [{
5017
+ label: "Automatic",
5018
+ value: "auto",
5019
+ hint: "Automated setup with provider CLI, sets .env"
5020
+ }, {
5021
+ label: "Manual",
5022
+ value: "manual",
5023
+ hint: "Manual setup, add env vars yourself"
5024
+ }],
5025
+ initialValue: "auto"
5026
+ });
5027
+ if (isCancel(mode)) return exitCancelled("Operation cancelled");
5028
+ if (mode === "manual") {
5029
+ await writeEnvFile$1(projectDir);
5030
+ displayManualSetupInstructions$1();
5031
+ return;
5032
+ }
4972
5033
  const setupOptions = [{
4973
5034
  label: "Quick setup with create-db",
4974
5035
  value: "create-db",
@@ -5107,6 +5168,24 @@ async function setupSupabase(config) {
5107
5168
  const serverDir = path.join(projectDir, "apps", "server");
5108
5169
  try {
5109
5170
  await fs.ensureDir(serverDir);
5171
+ const mode = await select({
5172
+ message: "Supabase setup: choose mode",
5173
+ options: [{
5174
+ label: "Automatic",
5175
+ value: "auto",
5176
+ hint: "Automated setup with provider CLI, sets .env"
5177
+ }, {
5178
+ label: "Manual",
5179
+ value: "manual",
5180
+ hint: "Manual setup, add env vars yourself"
5181
+ }],
5182
+ initialValue: "auto"
5183
+ });
5184
+ if (isCancel(mode)) return exitCancelled("Operation cancelled");
5185
+ if (mode === "manual") {
5186
+ displayManualSupabaseInstructions();
5187
+ return;
5188
+ }
5110
5189
  const initialized = await initializeSupabase(serverDir, packageManager);
5111
5190
  if (!initialized) {
5112
5191
  displayManualSupabaseInstructions();
@@ -5276,19 +5355,38 @@ DATABASE_AUTH_TOKEN=your_auth_token`);
5276
5355
  async function setupTurso(config) {
5277
5356
  const { orm, projectDir } = config;
5278
5357
  const setupSpinner = spinner();
5279
- setupSpinner.start("Checking Turso CLI availability...");
5280
5358
  try {
5359
+ const mode = await select({
5360
+ message: "Turso setup: choose mode",
5361
+ options: [{
5362
+ label: "Automatic",
5363
+ value: "auto",
5364
+ hint: "Automated setup with provider CLI, sets .env"
5365
+ }, {
5366
+ label: "Manual",
5367
+ value: "manual",
5368
+ hint: "Manual setup, add env vars yourself"
5369
+ }],
5370
+ initialValue: "auto"
5371
+ });
5372
+ if (isCancel(mode)) return exitCancelled("Operation cancelled");
5373
+ if (mode === "manual") {
5374
+ await writeEnvFile(projectDir);
5375
+ displayManualSetupInstructions();
5376
+ return;
5377
+ }
5378
+ setupSpinner.start("Checking Turso CLI availability...");
5281
5379
  const platform = os.platform();
5282
5380
  const isMac = platform === "darwin";
5283
5381
  const isWindows = platform === "win32";
5284
5382
  if (isWindows) {
5285
- setupSpinner.stop(pc.yellow("Turso setup not supported on Windows"));
5383
+ if (setupSpinner) setupSpinner.stop(pc.yellow("Turso setup not supported on Windows"));
5286
5384
  log.warn(pc.yellow("Automatic Turso setup is not supported on Windows."));
5287
5385
  await writeEnvFile(projectDir);
5288
5386
  displayManualSetupInstructions();
5289
5387
  return;
5290
5388
  }
5291
- setupSpinner.stop("Turso CLI availability checked");
5389
+ if (setupSpinner) setupSpinner.stop("Turso CLI availability checked");
5292
5390
  const isCliInstalled = await isTursoInstalled();
5293
5391
  if (!isCliInstalled) {
5294
5392
  const shouldInstall = await confirm({
@@ -5331,7 +5429,7 @@ async function setupTurso(config) {
5331
5429
  }
5332
5430
  log.success("Turso database setup completed successfully!");
5333
5431
  } catch (error) {
5334
- setupSpinner.stop(pc.red("Turso CLI availability check failed"));
5432
+ if (setupSpinner) setupSpinner.stop(pc.red("Turso CLI availability check failed"));
5335
5433
  consola.error(pc.red(`Error during Turso setup: ${error instanceof Error ? error.message : String(error)}`));
5336
5434
  await writeEnvFile(projectDir);
5337
5435
  displayManualSetupInstructions();
@@ -5359,11 +5457,17 @@ async function setupDatabase(config) {
5359
5457
  dependencies: [
5360
5458
  "@prisma/client",
5361
5459
  "@prisma/adapter-planetscale",
5460
+ "@planetscale/database",
5362
5461
  "undici"
5363
5462
  ],
5364
5463
  devDependencies: ["prisma"],
5365
5464
  projectDir: serverDir
5366
5465
  });
5466
+ else if (database === "sqlite" && dbSetup === "turso") await addPackageDependency({
5467
+ dependencies: ["@prisma/client", "@prisma/adapter-libsql"],
5468
+ devDependencies: ["prisma"],
5469
+ projectDir: serverDir
5470
+ });
5367
5471
  else await addPackageDependency({
5368
5472
  dependencies: ["@prisma/client"],
5369
5473
  devDependencies: ["prisma"],
@@ -5972,7 +6076,6 @@ async function getDatabaseInstructions(database, orm, runCmd, _runtime, dbSetup,
5972
6076
  }
5973
6077
  }
5974
6078
  if (orm === "prisma") {
5975
- 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`);
5976
6079
  if (database === "mongodb" && dbSetup === "docker") instructions.push(`${pc.yellow("WARNING:")} Prisma + MongoDB + Docker combination\n may not work.`);
5977
6080
  if (dbSetup === "docker") instructions.push(`${pc.cyan("•")} Start docker container: ${`${runCmd} db:start`}`);
5978
6081
  instructions.push(`${pc.cyan("•")} Apply schema: ${`${runCmd} db:push`}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-better-t-stack",
3
- "version": "2.40.4-canary.49f23d48",
3
+ "version": "2.40.4-canary.f07be855",
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",
@@ -14,9 +14,16 @@ generator client {
14
14
  previewFeatures = ["driverAdapters"]
15
15
  {{/if}}
16
16
  {{/if}}
17
+ {{#if (eq dbSetup "turso")}}
18
+ previewFeatures = ["driverAdapters"]
19
+ {{/if}}
17
20
  }
18
21
 
19
22
  datasource db {
20
23
  provider = "sqlite"
24
+ {{#if (eq dbSetup "turso")}}
25
+ url = "file:./local.db"
26
+ {{else}}
21
27
  url = env("DATABASE_URL")
28
+ {{/if}}
22
29
  }
@@ -1,12 +1,15 @@
1
1
  import "dotenv/config";
2
2
  import path from "node:path";
3
3
  import type { PrismaConfig } from "prisma";
4
- {{#if (eq serverDeploy "wrangler")}}
4
+ {{#if (eq dbSetup "d1")}}
5
5
  import { PrismaD1 } from "@prisma/adapter-d1";
6
6
  {{/if}}
7
+ {{#if (eq dbSetup "turso")}}
8
+ import { PrismaLibSQL } from "@prisma/adapter-libsql";
9
+ {{/if}}
7
10
 
8
11
  export default {
9
- {{#if (eq serverDeploy "wrangler")}}
12
+ {{#if (or (eq dbSetup "d1") (eq dbSetup "turso"))}}
10
13
  experimental: {
11
14
  adapter: true
12
15
  },
@@ -15,7 +18,7 @@ export default {
15
18
  migrations: {
16
19
  path: path.join("prisma", "migrations"),
17
20
  },
18
- {{#if (eq serverDeploy "wrangler")}}
21
+ {{#if (eq dbSetup "d1")}}
19
22
  async adapter() {
20
23
  return new PrismaD1({
21
24
  CLOUDFLARE_D1_TOKEN: process.env.CLOUDFLARE_D1_TOKEN!,
@@ -24,4 +27,12 @@ export default {
24
27
  });
25
28
  },
26
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}}
27
38
  } satisfies PrismaConfig;
@@ -1,4 +1,4 @@
1
- {{#if (and (eq dbSetup "d1") (eq runtime "workers"))}}
1
+ {{#if (eq dbSetup "d1")}}
2
2
  import { env } from "cloudflare:workers";
3
3
  import { PrismaD1 } from "@prisma/adapter-d1";
4
4
  import { PrismaClient } from "../../prisma/generated/client";
@@ -6,6 +6,18 @@ import { PrismaClient } from "../../prisma/generated/client";
6
6
  const adapter = new PrismaD1(env.DB);
7
7
  const prisma = new PrismaClient({ adapter });
8
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
+
9
21
  export default prisma;
10
22
  {{else}}
11
23
  import { PrismaClient } from "../../prisma/generated/client";
@@ -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")}}
@@ -54,7 +54,7 @@ const db = await D1Database("database", {
54
54
 
55
55
  {{#if (eq webDeploy "alchemy")}}
56
56
  {{#if (includes frontend "next")}}
57
- export const web = await Next("web", {
57
+ export const web = await Nextjs("web", {
58
58
  {{#if (eq serverDeploy "alchemy")}}cwd: "apps/web",{{/if}}
59
59
  bindings: {
60
60
  {{#if (eq backend "convex")}}
@@ -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}}