create-better-t-stack 2.20.0 → 2.21.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/index.js CHANGED
@@ -609,22 +609,6 @@ async function setupBackendDependencies(config) {
609
609
  });
610
610
  }
611
611
 
612
- //#endregion
613
- //#region src/utils/command-exists.ts
614
- async function commandExists(command) {
615
- try {
616
- const isWindows = process.platform === "win32";
617
- if (isWindows) {
618
- const result$1 = await execa("where", [command]);
619
- return result$1.exitCode === 0;
620
- }
621
- const result = await execa("which", [command]);
622
- return result.exitCode === 0;
623
- } catch {
624
- return false;
625
- }
626
- }
627
-
628
612
  //#endregion
629
613
  //#region src/helpers/project-generation/env-setup.ts
630
614
  async function addEnvVariablesToFile(filePath, variables) {
@@ -730,7 +714,7 @@ async function setupEnvironmentVariables(config) {
730
714
  let corsOrigin = "http://localhost:3001";
731
715
  if (hasReactRouter || hasSvelte) corsOrigin = "http://localhost:5173";
732
716
  let databaseUrl = null;
733
- const specializedSetup = dbSetup === "turso" || dbSetup === "prisma-postgres" || dbSetup === "mongodb-atlas" || dbSetup === "neon" || dbSetup === "supabase";
717
+ const specializedSetup = dbSetup === "turso" || dbSetup === "prisma-postgres" || dbSetup === "mongodb-atlas" || dbSetup === "neon" || dbSetup === "supabase" || dbSetup === "d1";
734
718
  if (database !== "none" && !specializedSetup) switch (database) {
735
719
  case "postgres":
736
720
  databaseUrl = "postgresql://postgres:password@localhost:5432/postgres";
@@ -782,6 +766,49 @@ async function setupEnvironmentVariables(config) {
782
766
  }
783
767
  }
784
768
 
769
+ //#endregion
770
+ //#region src/helpers/database-providers/d1-setup.ts
771
+ async function setupCloudflareD1(config) {
772
+ const { projectDir } = config;
773
+ const envPath = path.join(projectDir, "apps/server", ".env");
774
+ const variables = [
775
+ {
776
+ key: "CLOUDFLARE_ACCOUNT_ID",
777
+ value: "",
778
+ condition: true
779
+ },
780
+ {
781
+ key: "CLOUDFLARE_DATABASE_ID",
782
+ value: "",
783
+ condition: true
784
+ },
785
+ {
786
+ key: "CLOUDFLARE_D1_TOKEN",
787
+ value: "",
788
+ condition: true
789
+ }
790
+ ];
791
+ try {
792
+ await addEnvVariablesToFile(envPath, variables);
793
+ } catch (_err) {}
794
+ }
795
+
796
+ //#endregion
797
+ //#region src/utils/command-exists.ts
798
+ async function commandExists(command) {
799
+ try {
800
+ const isWindows = process.platform === "win32";
801
+ if (isWindows) {
802
+ const result$1 = await execa("where", [command]);
803
+ return result$1.exitCode === 0;
804
+ }
805
+ const result = await execa("which", [command]);
806
+ return result.exitCode === 0;
807
+ } catch {
808
+ return false;
809
+ }
810
+ }
811
+
785
812
  //#endregion
786
813
  //#region src/helpers/database-providers/mongodb-atlas-setup.ts
787
814
  async function checkAtlasCLI() {
@@ -1544,6 +1571,7 @@ async function setupDatabase(config) {
1544
1571
  projectDir: serverDir
1545
1572
  });
1546
1573
  if (database === "sqlite" && dbSetup === "turso") await setupTurso(config);
1574
+ else if (database === "sqlite" && dbSetup === "d1") await setupCloudflareD1(config);
1547
1575
  else if (database === "postgres") {
1548
1576
  if (orm === "prisma" && dbSetup === "prisma-postgres") await setupPrismaPostgres(config);
1549
1577
  else if (dbSetup === "neon") await setupNeonPostgres(config);
@@ -1596,6 +1624,22 @@ async function setupRuntime(config) {
1596
1624
  else if (runtime === "node") await setupNodeRuntime(serverDir, backend);
1597
1625
  else if (runtime === "workers") await setupWorkersRuntime(serverDir);
1598
1626
  }
1627
+ async function generateCloudflareWorkerTypes(config) {
1628
+ if (config.runtime !== "workers") return;
1629
+ const serverDir = path.join(config.projectDir, "apps/server");
1630
+ if (!await fs.pathExists(serverDir)) return;
1631
+ const s = spinner();
1632
+ try {
1633
+ s.start("Generating Cloudflare Workers types...");
1634
+ const runCmd = config.packageManager === "npm" ? "npm" : config.packageManager;
1635
+ await execa(runCmd, ["run", "cf-typegen"], { cwd: serverDir });
1636
+ s.stop("Cloudflare Workers types generated successfully!");
1637
+ } catch {
1638
+ s.stop(pc.yellow("Failed to generate Cloudflare Workers types"));
1639
+ const managerCmd = config.packageManager === "npm" ? "npm run" : `${config.packageManager} run`;
1640
+ console.warn(`Note: You can manually run 'cd apps/server && ${managerCmd} cf-typegen' in the project directory later`);
1641
+ }
1642
+ }
1599
1643
  async function setupBunRuntime(serverDir, _backend) {
1600
1644
  const packageJsonPath = path.join(serverDir, "package.json");
1601
1645
  if (!await fs.pathExists(packageJsonPath)) return;
@@ -1867,12 +1911,12 @@ async function installDependencies({ projectDir, packageManager }) {
1867
1911
  //#endregion
1868
1912
  //#region src/helpers/project-generation/post-installation.ts
1869
1913
  function displayPostInstallInstructions(config) {
1870
- const { database, relativePath, packageManager, depsInstalled, orm, addons, runtime, frontend, backend } = config;
1914
+ const { database, relativePath, packageManager, depsInstalled, orm, addons, runtime, frontend, backend, dbSetup } = config;
1871
1915
  const isConvex = backend === "convex";
1872
1916
  const runCmd = packageManager === "npm" ? "npm run" : packageManager;
1873
1917
  const cdCmd = `cd ${relativePath}`;
1874
1918
  const hasHuskyOrBiome = addons?.includes("husky") || addons?.includes("biome");
1875
- const databaseInstructions = !isConvex && database !== "none" ? getDatabaseInstructions(database, orm, runCmd, runtime) : "";
1919
+ const databaseInstructions = !isConvex && database !== "none" ? getDatabaseInstructions(database, orm, runCmd, runtime, dbSetup) : "";
1876
1920
  const tauriInstructions = addons?.includes("tauri") ? getTauriInstructions(runCmd) : "";
1877
1921
  const lintingInstructions = hasHuskyOrBiome ? getLintingInstructions(runCmd) : "";
1878
1922
  const nativeInstructions = frontend?.includes("native-nativewind") || frontend?.includes("native-unistyles") ? getNativeInstructions(isConvex) : "";
@@ -1903,6 +1947,7 @@ function displayPostInstallInstructions(config) {
1903
1947
  } else {
1904
1948
  if (runtime !== "workers") output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev\n`;
1905
1949
  if (runtime === "workers") {
1950
+ if (dbSetup === "d1") output += `${pc.yellow("IMPORTANT:")} Complete D1 database setup first (see Database commands below)\n`;
1906
1951
  output += `${pc.cyan(`${stepCounter++}.`)} bun dev\n`;
1907
1952
  output += `${pc.cyan(`${stepCounter++}.`)} cd apps/server && bun run cf-typegen\n\n`;
1908
1953
  } else output += "\n";
@@ -1937,8 +1982,18 @@ function getNativeInstructions(isConvex) {
1937
1982
  function getLintingInstructions(runCmd) {
1938
1983
  return `${pc.bold("Linting and formatting:")}\n${pc.cyan("•")} Format and lint fix: ${`${runCmd} check`}\n`;
1939
1984
  }
1940
- function getDatabaseInstructions(database, orm, runCmd, runtime) {
1985
+ function getDatabaseInstructions(database, orm, runCmd, runtime, dbSetup) {
1941
1986
  const instructions = [];
1987
+ if (runtime === "workers" && dbSetup === "d1") {
1988
+ const packageManager = runCmd === "npm run" ? "npm" : runCmd || "npm";
1989
+ instructions.push(`${pc.cyan("1.")} Login to Cloudflare: ${pc.white(`${packageManager} wrangler login`)}`);
1990
+ instructions.push(`${pc.cyan("2.")} Create D1 database: ${pc.white(`${packageManager} wrangler d1 create your-database-name`)}`);
1991
+ instructions.push(`${pc.cyan("3.")} Update apps/server/wrangler.jsonc with database_id and database_name`);
1992
+ instructions.push(`${pc.cyan("4.")} Generate migrations: ${pc.white("cd apps/server && bun db:generate")}`);
1993
+ instructions.push(`${pc.cyan("5.")} Apply migrations locally: ${pc.white(`${packageManager} wrangler d1 migrations apply YOUR_DB_NAME --local`)}`);
1994
+ instructions.push(`${pc.cyan("6.")} Apply migrations to production: ${pc.white(`${packageManager} wrangler d1 migrations apply YOUR_DB_NAME`)}`);
1995
+ instructions.push("");
1996
+ }
1942
1997
  if (orm === "prisma") {
1943
1998
  if (database === "sqlite") instructions.push(`${pc.yellow("NOTE:")} Turso support with Prisma is in Early Access and requires additional setup.`, `Learn more at: https://www.prisma.io/docs/orm/overview/databases/turso`);
1944
1999
  if (runtime === "bun") instructions.push(`${pc.yellow("NOTE:")} Prisma with Bun may require additional configuration. If you encounter errors,\nfollow the guidance provided in the error messages`);
@@ -1947,7 +2002,7 @@ function getDatabaseInstructions(database, orm, runCmd, runtime) {
1947
2002
  } else if (orm === "drizzle") {
1948
2003
  instructions.push(`${pc.cyan("•")} Apply schema: ${`${runCmd} db:push`}`);
1949
2004
  instructions.push(`${pc.cyan("•")} Database UI: ${`${runCmd} db:studio`}`);
1950
- if (database === "sqlite") instructions.push(`${pc.cyan("•")} Start local DB (if needed): ${`cd apps/server && ${runCmd} db:local`}`);
2005
+ if (database === "sqlite" && dbSetup !== "d1") instructions.push(`${pc.cyan("•")} Start local DB (if needed): ${`cd apps/server && ${runCmd} db:local`}`);
1951
2006
  } else if (orm === "none") instructions.push(`${pc.yellow("NOTE:")} Manual database schema setup required.`);
1952
2007
  return instructions.length ? `${pc.bold("Database commands:")}\n${instructions.join("\n")}` : "";
1953
2008
  }
@@ -2539,10 +2594,13 @@ async function createProject(options) {
2539
2594
  await createReadme(projectDir, options);
2540
2595
  await initializeGit(projectDir, options.git);
2541
2596
  log.success("Project template successfully scaffolded!");
2542
- if (options.install) await installDependencies({
2543
- projectDir,
2544
- packageManager: options.packageManager
2545
- });
2597
+ if (options.install) {
2598
+ await installDependencies({
2599
+ projectDir,
2600
+ packageManager: options.packageManager
2601
+ });
2602
+ await generateCloudflareWorkerTypes(options);
2603
+ }
2546
2604
  displayPostInstallInstructions({
2547
2605
  ...options,
2548
2606
  depsInstalled: options.install
@@ -2784,21 +2842,29 @@ async function getDatabaseChoice(database, backend, runtime) {
2784
2842
 
2785
2843
  //#endregion
2786
2844
  //#region src/prompts/database-setup.ts
2787
- async function getDBSetupChoice(databaseType, dbSetup, orm, backend) {
2845
+ async function getDBSetupChoice(databaseType, dbSetup, orm, backend, runtime) {
2788
2846
  if (backend === "convex") return "none";
2789
2847
  if (dbSetup !== void 0) return dbSetup;
2790
2848
  if (databaseType === "none") return "none";
2791
2849
  if (databaseType === "sqlite" && orm === "prisma") return "none";
2792
2850
  let options = [];
2793
- if (databaseType === "sqlite") options = [{
2794
- value: "turso",
2795
- label: "Turso",
2796
- hint: "SQLite for Production. Powered by libSQL"
2797
- }, {
2798
- value: "none",
2799
- label: "None",
2800
- hint: "Manual setup"
2801
- }];
2851
+ if (databaseType === "sqlite") options = [
2852
+ {
2853
+ value: "turso",
2854
+ label: "Turso",
2855
+ hint: "SQLite for Production. Powered by libSQL"
2856
+ },
2857
+ ...runtime === "workers" ? [{
2858
+ value: "d1",
2859
+ label: "Cloudflare D1",
2860
+ hint: "Cloudflare's managed, serverless database with SQLite's SQL semantics"
2861
+ }] : [],
2862
+ {
2863
+ value: "none",
2864
+ label: "None",
2865
+ hint: "Manual setup"
2866
+ }
2867
+ ];
2802
2868
  else if (databaseType === "postgres") options = [
2803
2869
  {
2804
2870
  value: "neon",
@@ -3121,7 +3187,7 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
3121
3187
  auth: ({ results }) => getAuthChoice(flags.auth, results.database !== "none", results.backend),
3122
3188
  addons: ({ results }) => getAddonsChoice(flags.addons, results.frontend),
3123
3189
  examples: ({ results }) => getExamplesChoice(flags.examples, results.database, results.frontend, results.backend, results.api),
3124
- dbSetup: ({ results }) => getDBSetupChoice(results.database ?? "none", flags.dbSetup, results.orm, results.backend),
3190
+ dbSetup: ({ results }) => getDBSetupChoice(results.database ?? "none", flags.dbSetup, results.orm, results.backend, results.runtime),
3125
3191
  git: () => getGitChoice(flags.git),
3126
3192
  packageManager: () => getPackageManagerChoice(flags.packageManager),
3127
3193
  install: () => getinstallChoice(flags.install)
@@ -3234,6 +3300,7 @@ const DatabaseSetupSchema = z.enum([
3234
3300
  "prisma-postgres",
3235
3301
  "mongodb-atlas",
3236
3302
  "supabase",
3303
+ "d1",
3237
3304
  "none"
3238
3305
  ]).describe("Database hosting setup");
3239
3306
  const APISchema = z.enum([
@@ -3702,6 +3769,16 @@ function processAndValidateFlags(options, providedFlags, projectName) {
3702
3769
  consola$1.fatal("Supabase setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup.");
3703
3770
  process.exit(1);
3704
3771
  }
3772
+ if (config.dbSetup === "d1") {
3773
+ if (config.database !== "sqlite") {
3774
+ consola$1.fatal("Cloudflare D1 setup requires SQLite database. Please use '--database sqlite' or choose a different setup.");
3775
+ process.exit(1);
3776
+ }
3777
+ if (config.runtime !== "workers") {
3778
+ consola$1.fatal("Cloudflare D1 setup requires the Cloudflare Workers runtime. Please use '--runtime workers' or choose a different setup.");
3779
+ process.exit(1);
3780
+ }
3781
+ }
3705
3782
  if (providedFlags.has("runtime") && options.runtime === "workers" && config.backend && config.backend !== "hono") {
3706
3783
  consola$1.fatal(`Cloudflare Workers runtime (--runtime workers) is only supported with Hono backend (--backend hono). Current backend: ${config.backend}. Please use '--backend hono' or choose a different runtime.`);
3707
3784
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-better-t-stack",
3
- "version": "2.20.0",
3
+ "version": "2.21.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",
@@ -3,6 +3,16 @@ import { defineConfig } from "drizzle-kit";
3
3
  export default defineConfig({
4
4
  schema: "./src/db/schema",
5
5
  out: "./src/db/migrations",
6
+ {{#if (eq dbSetup "d1")}}
7
+ // DOCS: https://orm.drizzle.team/docs/guides/d1-http-with-drizzle-kit
8
+ dialect: "sqlite",
9
+ driver: "d1-http",
10
+ dbCredentials: {
11
+ accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,
12
+ databaseId: process.env.CLOUDFLARE_DATABASE_ID!,
13
+ token: process.env.CLOUDFLARE_D1_TOKEN!,
14
+ },
15
+ {{else}}
6
16
  dialect: "turso",
7
17
  dbCredentials: {
8
18
  url: process.env.DATABASE_URL || "",
@@ -10,4 +20,5 @@ export default defineConfig({
10
20
  authToken: process.env.DATABASE_AUTH_TOKEN,
11
21
  {{/if}}
12
22
  },
23
+ {{/if}}
13
24
  });
@@ -13,6 +13,12 @@ export const db = drizzle({ client });
13
13
  {{/if}}
14
14
 
15
15
  {{#if (eq runtime "workers")}}
16
+ {{#if (eq dbSetup "d1")}}
17
+ import { drizzle } from "drizzle-orm/d1";
18
+ import { env } from "cloudflare:workers";
19
+
20
+ export const db = drizzle(env.DB);
21
+ {{else}}
16
22
  import { drizzle } from "drizzle-orm/libsql";
17
23
  import { env } from "cloudflare:workers";
18
24
  import { createClient } from "@libsql/client";
@@ -26,3 +32,4 @@ const client = createClient({
26
32
 
27
33
  export const db = drizzle({ client });
28
34
  {{/if}}
35
+ {{/if}}
@@ -5,14 +5,30 @@
5
5
  "compatibility_flags": ["nodejs_compat"],
6
6
  "vars": {
7
7
  "NODE_ENV": "production"
8
- // Non-sensitive environment variables (visible in dashboard)
9
- // "CORS_ORIGIN": "https://your-frontend-domain.com",
10
- // "BETTER_AUTH_URL": "https://your-worker-domain.workers.dev"
8
+ // Add public environment variables here
9
+ // Example: "CORS_ORIGIN": "https://your-domain.com"
11
10
  }
12
- // ⚠️ SENSITIVE DATA: Use `wrangler secret put` instead of adding here
13
- // Don't put these in "vars" - they'll be visible in the dashboard!
14
- // - DATABASE_URL
15
- // - DATABASE_AUTH_TOKEN
16
- // - GOOGLE_GENERATIVE_AI_API_KEY
17
- // - BETTER_AUTH_SECRET
11
+ // For sensitive data, use:
12
+ // wrangler secret put SECRET_NAME
13
+ // Don't add secrets to "vars" - they're visible in the dashboard!
14
+
15
+ {{#if (eq dbSetup "d1")}},
16
+ // To set up D1 database:
17
+ // 1. Run: wrangler login
18
+ // 2. Run: wrangler d1 create your-database-name
19
+ // 3. Copy the output and paste below
20
+ // Then run migrations:
21
+ // bun db:generate
22
+ // To apply migrations locally, run:
23
+ // wrangler d1 migrations apply YOUR_DB_NAME --local
24
+ "d1_databases": [
25
+ {
26
+ "binding": "DB",
27
+ "database_name": "YOUR_DB_NAME",
28
+ "database_id": "YOUR_DB_ID",
29
+ "preview_database_id": "local-test-db",
30
+ "migrations_dir": "./src/db/migrations"
31
+ }
32
+ ]
33
+ {{/if}}
18
34
  }