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