create-better-t-stack 2.19.1 → 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
@@ -60,7 +60,7 @@ const dependencyVersionMap = {
60
60
  "vite-plugin-pwa": "^0.21.2",
61
61
  "@vite-pwa/assets-generator": "^0.2.6",
62
62
  "@tauri-apps/cli": "^2.4.0",
63
- "@biomejs/biome": "1.9.4",
63
+ "@biomejs/biome": "^2.0.0",
64
64
  husky: "^9.1.7",
65
65
  "lint-staged": "^15.5.0",
66
66
  "@hono/node-server": "^1.14.0",
@@ -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() {
@@ -885,6 +912,163 @@ async function setupMongoDBAtlas(config) {
885
912
  }
886
913
  }
887
914
 
915
+ //#endregion
916
+ //#region src/helpers/database-providers/neon-setup.ts
917
+ const NEON_REGIONS = [
918
+ {
919
+ label: "AWS US East (N. Virginia)",
920
+ value: "aws-us-east-1"
921
+ },
922
+ {
923
+ label: "AWS US East (Ohio)",
924
+ value: "aws-us-east-2"
925
+ },
926
+ {
927
+ label: "AWS US West (Oregon)",
928
+ value: "aws-us-west-2"
929
+ },
930
+ {
931
+ label: "AWS Europe (Frankfurt)",
932
+ value: "aws-eu-central-1"
933
+ },
934
+ {
935
+ label: "AWS Asia Pacific (Singapore)",
936
+ value: "aws-ap-southeast-1"
937
+ },
938
+ {
939
+ label: "AWS Asia Pacific (Sydney)",
940
+ value: "aws-ap-southeast-2"
941
+ },
942
+ {
943
+ label: "Azure East US 2 region (Virginia)",
944
+ value: "azure-eastus2"
945
+ }
946
+ ];
947
+ async function executeNeonCommand(packageManager, commandArgsString, spinnerText) {
948
+ const s = spinner();
949
+ try {
950
+ const fullCommand = getPackageExecutionCommand(packageManager, commandArgsString);
951
+ if (spinnerText) s.start(spinnerText);
952
+ const result = await execa(fullCommand, { shell: true });
953
+ if (spinnerText) s.stop(pc.green(spinnerText.replace("...", "").replace("ing ", "ed ").trim()));
954
+ return result;
955
+ } catch (error) {
956
+ if (s) s.stop(pc.red(`Failed: ${spinnerText || "Command execution"}`));
957
+ throw error;
958
+ }
959
+ }
960
+ async function createNeonProject(projectName, regionId, packageManager) {
961
+ try {
962
+ const commandArgsString = `neonctl projects create --name ${projectName} --region-id ${regionId} --output json`;
963
+ const { stdout } = await executeNeonCommand(packageManager, commandArgsString, `Creating Neon project "${projectName}"...`);
964
+ const response = JSON.parse(stdout);
965
+ if (response.project && response.connection_uris && response.connection_uris.length > 0) {
966
+ const projectId = response.project.id;
967
+ const connectionUri = response.connection_uris[0].connection_uri;
968
+ const params = response.connection_uris[0].connection_parameters;
969
+ return {
970
+ connectionString: connectionUri,
971
+ projectId,
972
+ dbName: params.database,
973
+ roleName: params.role
974
+ };
975
+ }
976
+ consola$1.error(pc.red("Failed to extract connection information from response"));
977
+ return null;
978
+ } catch (_error) {
979
+ consola$1.error(pc.red("Failed to create Neon project"));
980
+ }
981
+ }
982
+ async function writeEnvFile$2(projectDir, config) {
983
+ const envPath = path.join(projectDir, "apps/server", ".env");
984
+ const variables = [{
985
+ key: "DATABASE_URL",
986
+ value: config?.connectionString ?? "postgresql://postgres:postgres@localhost:5432/mydb?schema=public",
987
+ condition: true
988
+ }];
989
+ await addEnvVariablesToFile(envPath, variables);
990
+ return true;
991
+ }
992
+ async function setupWithNeonDb(projectDir, packageManager) {
993
+ try {
994
+ const s = spinner();
995
+ s.start("Creating Neon database using neondb...");
996
+ const serverDir = path.join(projectDir, "apps/server");
997
+ await fs.ensureDir(serverDir);
998
+ const packageCmd = getPackageExecutionCommand(packageManager, "neondb --yes");
999
+ await execa(packageCmd, {
1000
+ shell: true,
1001
+ cwd: serverDir
1002
+ });
1003
+ s.stop(pc.green("Neon database created successfully!"));
1004
+ return true;
1005
+ } catch (error) {
1006
+ consola$1.error(pc.red("Failed to create database with neondb"));
1007
+ throw error;
1008
+ }
1009
+ }
1010
+ function displayManualSetupInstructions$2() {
1011
+ log.info(`Manual Neon PostgreSQL Setup Instructions:
1012
+
1013
+ 1. Visit https://neon.tech and create an account
1014
+ 2. Create a new project from the dashboard
1015
+ 3. Get your connection string
1016
+ 4. Add the database URL to the .env file in apps/server/.env
1017
+
1018
+ DATABASE_URL="your_connection_string"`);
1019
+ }
1020
+ async function setupNeonPostgres(config) {
1021
+ const { packageManager, projectDir } = config;
1022
+ try {
1023
+ const setupMethod = await select({
1024
+ message: "Choose your Neon setup method:",
1025
+ options: [{
1026
+ label: "Quick setup with neondb",
1027
+ value: "neondb",
1028
+ hint: "fastest, no auth required"
1029
+ }, {
1030
+ label: "Custom setup with neonctl",
1031
+ value: "neonctl",
1032
+ hint: "More control - choose project name and region"
1033
+ }],
1034
+ initialValue: "neondb"
1035
+ });
1036
+ if (isCancel(setupMethod)) {
1037
+ cancel(pc.red("Operation cancelled"));
1038
+ process.exit(0);
1039
+ }
1040
+ if (setupMethod === "neondb") await setupWithNeonDb(projectDir, packageManager);
1041
+ else {
1042
+ const suggestedProjectName = path.basename(projectDir);
1043
+ const projectName = await text({
1044
+ message: "Enter a name for your Neon project:",
1045
+ defaultValue: suggestedProjectName,
1046
+ initialValue: suggestedProjectName
1047
+ });
1048
+ const regionId = await select({
1049
+ message: "Select a region for your Neon project:",
1050
+ options: NEON_REGIONS,
1051
+ initialValue: NEON_REGIONS[0].value
1052
+ });
1053
+ if (isCancel(projectName) || isCancel(regionId)) {
1054
+ cancel(pc.red("Operation cancelled"));
1055
+ process.exit(0);
1056
+ }
1057
+ const neonConfig = await createNeonProject(projectName, regionId, packageManager);
1058
+ if (!neonConfig) throw new Error("Failed to create project - couldn't get connection information");
1059
+ const finalSpinner = spinner();
1060
+ finalSpinner.start("Configuring database connection");
1061
+ await fs.ensureDir(path.join(projectDir, "apps/server"));
1062
+ await writeEnvFile$2(projectDir, neonConfig);
1063
+ finalSpinner.stop("Neon database configured!");
1064
+ }
1065
+ } catch (error) {
1066
+ if (error instanceof Error) consola$1.error(pc.red(error.message));
1067
+ await writeEnvFile$2(projectDir);
1068
+ displayManualSetupInstructions$2();
1069
+ }
1070
+ }
1071
+
888
1072
  //#endregion
889
1073
  //#region src/helpers/database-providers/prisma-postgres-setup.ts
890
1074
  async function initPrismaDatabase(serverDir, packageManager) {
@@ -919,7 +1103,7 @@ async function initPrismaDatabase(serverDir, packageManager) {
919
1103
  return null;
920
1104
  }
921
1105
  }
922
- async function writeEnvFile$2(projectDir, config) {
1106
+ async function writeEnvFile$1(projectDir, config) {
923
1107
  try {
924
1108
  const envPath = path.join(projectDir, "apps/server", ".env");
925
1109
  const variables = [{
@@ -932,7 +1116,7 @@ async function writeEnvFile$2(projectDir, config) {
932
1116
  consola$1.error("Failed to update environment configuration");
933
1117
  }
934
1118
  }
935
- function displayManualSetupInstructions$2() {
1119
+ function displayManualSetupInstructions$1() {
936
1120
  log.info(`Manual Prisma PostgreSQL Setup Instructions:
937
1121
 
938
1122
  1. Visit https://console.prisma.io and create an account
@@ -983,23 +1167,23 @@ async function setupPrismaPostgres(config) {
983
1167
  s.stop("Prisma PostgreSQL setup ready");
984
1168
  const config$1 = await initPrismaDatabase(serverDir, packageManager);
985
1169
  if (config$1) {
986
- await writeEnvFile$2(projectDir, config$1);
1170
+ await writeEnvFile$1(projectDir, config$1);
987
1171
  await addPrismaAccelerateExtension(serverDir);
988
1172
  log.success(pc.green("Prisma PostgreSQL database configured successfully!"));
989
1173
  log.info(pc.cyan("NOTE: Make sure to uncomment `import \"dotenv/config\";` in `apps/server/src/prisma.config.ts` to load environment variables."));
990
1174
  } else {
991
1175
  const fallbackSpinner = spinner();
992
1176
  fallbackSpinner.start("Setting up fallback configuration...");
993
- await writeEnvFile$2(projectDir);
1177
+ await writeEnvFile$1(projectDir);
994
1178
  fallbackSpinner.stop("Fallback configuration ready");
995
- displayManualSetupInstructions$2();
1179
+ displayManualSetupInstructions$1();
996
1180
  }
997
1181
  } catch (error) {
998
1182
  s.stop(pc.red("Prisma PostgreSQL setup failed"));
999
1183
  consola$1.error(pc.red(`Error during Prisma PostgreSQL setup: ${error instanceof Error ? error.message : String(error)}`));
1000
1184
  try {
1001
- await writeEnvFile$2(projectDir);
1002
- displayManualSetupInstructions$2();
1185
+ await writeEnvFile$1(projectDir);
1186
+ displayManualSetupInstructions$1();
1003
1187
  } catch {}
1004
1188
  log.info("Setup completed with manual configuration required.");
1005
1189
  }
@@ -1246,7 +1430,7 @@ async function createTursoDatabase(dbName, groupName) {
1246
1430
  s.stop(pc.red("Failed to retrieve database connection details"));
1247
1431
  }
1248
1432
  }
1249
- async function writeEnvFile$1(projectDir, config) {
1433
+ async function writeEnvFile(projectDir, config) {
1250
1434
  const envPath = path.join(projectDir, "apps/server", ".env");
1251
1435
  const variables = [{
1252
1436
  key: "DATABASE_URL",
@@ -1259,7 +1443,7 @@ async function writeEnvFile$1(projectDir, config) {
1259
1443
  }];
1260
1444
  await addEnvVariablesToFile(envPath, variables);
1261
1445
  }
1262
- function displayManualSetupInstructions$1() {
1446
+ function displayManualSetupInstructions() {
1263
1447
  log.info(`Manual Turso Setup Instructions:
1264
1448
 
1265
1449
  1. Visit https://turso.tech and create an account
@@ -1283,8 +1467,8 @@ async function setupTurso(config) {
1283
1467
  if (isWindows) {
1284
1468
  setupSpinner.stop(pc.yellow("Turso setup not supported on Windows"));
1285
1469
  log.warn(pc.yellow("Automatic Turso setup is not supported on Windows."));
1286
- await writeEnvFile$1(projectDir);
1287
- displayManualSetupInstructions$1();
1470
+ await writeEnvFile(projectDir);
1471
+ displayManualSetupInstructions();
1288
1472
  return;
1289
1473
  }
1290
1474
  setupSpinner.stop("Turso CLI availability checked");
@@ -1299,8 +1483,8 @@ async function setupTurso(config) {
1299
1483
  process.exit(0);
1300
1484
  }
1301
1485
  if (!shouldInstall) {
1302
- await writeEnvFile$1(projectDir);
1303
- displayManualSetupInstructions$1();
1486
+ await writeEnvFile(projectDir);
1487
+ displayManualSetupInstructions();
1304
1488
  return;
1305
1489
  }
1306
1490
  await installTursoCLI(isMac);
@@ -1325,7 +1509,7 @@ async function setupTurso(config) {
1325
1509
  dbName = dbNameResponse;
1326
1510
  try {
1327
1511
  const config$1 = await createTursoDatabase(dbName, selectedGroup);
1328
- await writeEnvFile$1(projectDir, config$1);
1512
+ await writeEnvFile(projectDir, config$1);
1329
1513
  success = true;
1330
1514
  } catch (error) {
1331
1515
  if (error instanceof Error && error.message === "DATABASE_EXISTS") {
@@ -1338,166 +1522,9 @@ async function setupTurso(config) {
1338
1522
  } catch (error) {
1339
1523
  setupSpinner.stop(pc.red("Turso CLI availability check failed"));
1340
1524
  consola.error(pc.red(`Error during Turso setup: ${error instanceof Error ? error.message : String(error)}`));
1341
- await writeEnvFile$1(projectDir);
1342
- displayManualSetupInstructions$1();
1343
- log.success("Setup completed with manual configuration required.");
1344
- }
1345
- }
1346
-
1347
- //#endregion
1348
- //#region src/helpers/database-providers/neon-setup.ts
1349
- const NEON_REGIONS = [
1350
- {
1351
- label: "AWS US East (N. Virginia)",
1352
- value: "aws-us-east-1"
1353
- },
1354
- {
1355
- label: "AWS US East (Ohio)",
1356
- value: "aws-us-east-2"
1357
- },
1358
- {
1359
- label: "AWS US West (Oregon)",
1360
- value: "aws-us-west-2"
1361
- },
1362
- {
1363
- label: "AWS Europe (Frankfurt)",
1364
- value: "aws-eu-central-1"
1365
- },
1366
- {
1367
- label: "AWS Asia Pacific (Singapore)",
1368
- value: "aws-ap-southeast-1"
1369
- },
1370
- {
1371
- label: "AWS Asia Pacific (Sydney)",
1372
- value: "aws-ap-southeast-2"
1373
- },
1374
- {
1375
- label: "Azure East US 2 region (Virginia)",
1376
- value: "azure-eastus2"
1377
- }
1378
- ];
1379
- async function executeNeonCommand(packageManager, commandArgsString, spinnerText) {
1380
- const s = spinner();
1381
- try {
1382
- const fullCommand = getPackageExecutionCommand(packageManager, commandArgsString);
1383
- if (spinnerText) s.start(spinnerText);
1384
- const result = await execa(fullCommand, { shell: true });
1385
- if (spinnerText) s.stop(pc.green(spinnerText.replace("...", "").replace("ing ", "ed ").trim()));
1386
- return result;
1387
- } catch (error) {
1388
- if (s) s.stop(pc.red(`Failed: ${spinnerText || "Command execution"}`));
1389
- throw error;
1390
- }
1391
- }
1392
- async function createNeonProject(projectName, regionId, packageManager) {
1393
- try {
1394
- const commandArgsString = `neonctl projects create --name ${projectName} --region-id ${regionId} --output json`;
1395
- const { stdout } = await executeNeonCommand(packageManager, commandArgsString, `Creating Neon project "${projectName}"...`);
1396
- const response = JSON.parse(stdout);
1397
- if (response.project && response.connection_uris && response.connection_uris.length > 0) {
1398
- const projectId = response.project.id;
1399
- const connectionUri = response.connection_uris[0].connection_uri;
1400
- const params = response.connection_uris[0].connection_parameters;
1401
- return {
1402
- connectionString: connectionUri,
1403
- projectId,
1404
- dbName: params.database,
1405
- roleName: params.role
1406
- };
1407
- }
1408
- consola$1.error(pc.red("Failed to extract connection information from response"));
1409
- return null;
1410
- } catch (_error) {
1411
- consola$1.error(pc.red("Failed to create Neon project"));
1412
- }
1413
- }
1414
- async function writeEnvFile(projectDir, config) {
1415
- const envPath = path.join(projectDir, "apps/server", ".env");
1416
- const variables = [{
1417
- key: "DATABASE_URL",
1418
- value: config?.connectionString ?? "postgresql://postgres:postgres@localhost:5432/mydb?schema=public",
1419
- condition: true
1420
- }];
1421
- await addEnvVariablesToFile(envPath, variables);
1422
- return true;
1423
- }
1424
- async function setupWithNeonDb(projectDir, packageManager) {
1425
- try {
1426
- const s = spinner();
1427
- s.start("Creating Neon database using neondb...");
1428
- const serverDir = path.join(projectDir, "apps/server");
1429
- await fs.ensureDir(serverDir);
1430
- const packageCmd = getPackageExecutionCommand(packageManager, "neondb --yes");
1431
- await execa(packageCmd, {
1432
- shell: true,
1433
- cwd: serverDir
1434
- });
1435
- s.stop(pc.green("Neon database created successfully!"));
1436
- return true;
1437
- } catch (error) {
1438
- consola$1.error(pc.red("Failed to create database with neondb"));
1439
- throw error;
1440
- }
1441
- }
1442
- function displayManualSetupInstructions() {
1443
- log.info(`Manual Neon PostgreSQL Setup Instructions:
1444
-
1445
- 1. Visit https://neon.tech and create an account
1446
- 2. Create a new project from the dashboard
1447
- 3. Get your connection string
1448
- 4. Add the database URL to the .env file in apps/server/.env
1449
-
1450
- DATABASE_URL="your_connection_string"`);
1451
- }
1452
- async function setupNeonPostgres(config) {
1453
- const { packageManager, projectDir } = config;
1454
- try {
1455
- const setupMethod = await select({
1456
- message: "Choose your Neon setup method:",
1457
- options: [{
1458
- label: "Quick setup with neondb",
1459
- value: "neondb",
1460
- hint: "fastest, no auth required"
1461
- }, {
1462
- label: "Custom setup with neonctl",
1463
- value: "neonctl",
1464
- hint: "More control - choose project name and region"
1465
- }],
1466
- initialValue: "neondb"
1467
- });
1468
- if (isCancel(setupMethod)) {
1469
- cancel(pc.red("Operation cancelled"));
1470
- process.exit(0);
1471
- }
1472
- if (setupMethod === "neondb") await setupWithNeonDb(projectDir, packageManager);
1473
- else {
1474
- const suggestedProjectName = path.basename(projectDir);
1475
- const projectName = await text({
1476
- message: "Enter a name for your Neon project:",
1477
- defaultValue: suggestedProjectName,
1478
- initialValue: suggestedProjectName
1479
- });
1480
- const regionId = await select({
1481
- message: "Select a region for your Neon project:",
1482
- options: NEON_REGIONS,
1483
- initialValue: NEON_REGIONS[0].value
1484
- });
1485
- if (isCancel(projectName) || isCancel(regionId)) {
1486
- cancel(pc.red("Operation cancelled"));
1487
- process.exit(0);
1488
- }
1489
- const neonConfig = await createNeonProject(projectName, regionId, packageManager);
1490
- if (!neonConfig) throw new Error("Failed to create project - couldn't get connection information");
1491
- const finalSpinner = spinner();
1492
- finalSpinner.start("Configuring database connection");
1493
- await fs.ensureDir(path.join(projectDir, "apps/server"));
1494
- await writeEnvFile(projectDir, neonConfig);
1495
- finalSpinner.stop("Neon database configured!");
1496
- }
1497
- } catch (error) {
1498
- if (error instanceof Error) consola$1.error(pc.red(error.message));
1499
1525
  await writeEnvFile(projectDir);
1500
1526
  displayManualSetupInstructions();
1527
+ log.success("Setup completed with manual configuration required.");
1501
1528
  }
1502
1529
  }
1503
1530
 
@@ -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.19.1",
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",
@@ -0,0 +1,83 @@
1
+ {
2
+ "$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
3
+ "vcs": {
4
+ "enabled": false,
5
+ "clientKind": "git",
6
+ "useIgnoreFile": false
7
+ },
8
+ "files": {
9
+ "ignoreUnknown": false,
10
+ "includes": [
11
+ "**",
12
+ "!**/.next",
13
+ "!**/dist",
14
+ "!**/.turbo",
15
+ "!**/dev-dist",
16
+ "!**/.zed",
17
+ "!**/.vscode",
18
+ "!**/routeTree.gen.ts",
19
+ "!**/src-tauri",
20
+ "!**/.nuxt"
21
+ ]
22
+ },
23
+ "formatter": {
24
+ "enabled": true,
25
+ "indentStyle": "tab"
26
+ },
27
+ "assist": { "actions": { "source": { "organizeImports": "on" } } },
28
+ "linter": {
29
+ "enabled": true,
30
+ "rules": {
31
+ "recommended": true,
32
+ "correctness": {
33
+ "useExhaustiveDependencies": "info"
34
+ },
35
+ "nursery": {
36
+ "useSortedClasses": {
37
+ "level": "warn",
38
+ "fix": "safe",
39
+ "options": {
40
+ "functions": ["clsx", "cva", "cn"]
41
+ }
42
+ }
43
+ },
44
+ "style": {
45
+ "noParameterAssign": "error",
46
+ "useAsConstAssertion": "error",
47
+ "useDefaultParameterLast": "error",
48
+ "useEnumInitializers": "error",
49
+ "useSelfClosingElements": "error",
50
+ "useSingleVarDeclarator": "error",
51
+ "noUnusedTemplateLiteral": "error",
52
+ "useNumberNamespace": "error",
53
+ "noInferrableTypes": "error",
54
+ "noUselessElse": "error"
55
+ }
56
+ }
57
+ },
58
+ "javascript": {
59
+ "formatter": {
60
+ "quoteStyle": "double"
61
+ }
62
+ }
63
+ {{#if (or (eq frontend "svelte") (eq frontend "nuxt"))}}
64
+ ,
65
+ "overrides": [
66
+ {
67
+ "includes": ["**/*.svelte", "**/*.vue"],
68
+ "linter": {
69
+ "rules": {
70
+ "style": {
71
+ "useConst": "off",
72
+ "useImportType": "off"
73
+ },
74
+ "correctness": {
75
+ "noUnusedVariables": "off",
76
+ "noUnusedImports": "off"
77
+ }
78
+ }
79
+ }
80
+ }
81
+ ]
82
+ {{/if}}
83
+ }
@@ -32,6 +32,7 @@ export default function UserMenu() {
32
32
  <div class="absolute right-0 mt-2 w-56 rounded p-1 shadow-sm">
33
33
  <div class="px-4 text-sm">{session().data?.user.email}</div>
34
34
  <button
35
+ type="button"
35
36
  class="mt-1 w-full border rounded px-4 text-center text-sm"
36
37
  onClick={() => {
37
38
  setIsMenuOpen(false);
@@ -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
  }
@@ -1,52 +0,0 @@
1
- {
2
- "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
3
- "vcs": {
4
- "enabled": false,
5
- "clientKind": "git",
6
- "useIgnoreFile": false
7
- },
8
- "files": {
9
- "ignoreUnknown": false,
10
- "ignore": [
11
- ".next",
12
- "dist",
13
- ".turbo",
14
- "dev-dist",
15
- ".zed",
16
- ".vscode",
17
- "routeTree.gen.ts",
18
- "src-tauri",
19
- ".nuxt"
20
- ]
21
- },
22
- "formatter": {
23
- "enabled": true,
24
- "indentStyle": "tab"
25
- },
26
- "organizeImports": {
27
- "enabled": true
28
- },
29
- "linter": {
30
- "enabled": true,
31
- "rules": {
32
- "recommended": true,
33
- "correctness": {
34
- "useExhaustiveDependencies": "info"
35
- },
36
- "nursery": {
37
- "useSortedClasses": {
38
- "level": "warn",
39
- "fix": "safe",
40
- "options": {
41
- "functions": ["clsx", "cva", "cn"]
42
- }
43
- }
44
- }
45
- }
46
- },
47
- "javascript": {
48
- "formatter": {
49
- "quoteStyle": "double"
50
- }
51
- }
52
- }