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 +286 -209
- package/package.json +1 -1
- package/templates/addons/biome/biome.json.hbs +83 -0
- package/templates/auth/web/solid/src/components/{user-menu.tsx → user-menu.tsx.hbs} +1 -0
- package/templates/db/drizzle/sqlite/drizzle.config.ts.hbs +11 -0
- package/templates/db/drizzle/sqlite/src/db/index.ts.hbs +7 -0
- package/templates/runtime/workers/apps/server/wrangler.jsonc.hbs +25 -9
- package/templates/addons/biome/biome.json +0 -52
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": "
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
1177
|
+
await writeEnvFile$1(projectDir);
|
|
994
1178
|
fallbackSpinner.stop("Fallback configuration ready");
|
|
995
|
-
displayManualSetupInstructions$
|
|
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$
|
|
1002
|
-
displayManualSetupInstructions$
|
|
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
|
|
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
|
|
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
|
|
1287
|
-
displayManualSetupInstructions
|
|
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
|
|
1303
|
-
displayManualSetupInstructions
|
|
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
|
|
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)
|
|
2543
|
-
|
|
2544
|
-
|
|
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
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
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.
|
|
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
|
-
//
|
|
9
|
-
// "CORS_ORIGIN": "https://your-
|
|
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
|
-
//
|
|
13
|
-
//
|
|
14
|
-
// -
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
//
|
|
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
|
-
}
|