create-better-t-stack 3.4.2 → 3.5.1
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 +0 -6
- package/dist/index.js +1 -1
- package/dist/{src-BmqgKPgS.js → src-C4e8E70x.js} +139 -441
- package/package.json +1 -1
- package/templates/api/orpc/server/package.json.hbs +1 -5
- package/templates/api/orpc/server/tsconfig.json.hbs +1 -4
- package/templates/api/trpc/server/package.json.hbs +1 -5
- package/templates/api/trpc/server/tsconfig.json.hbs +1 -4
- package/templates/auth/better-auth/server/base/package.json.hbs +1 -5
- package/templates/auth/better-auth/server/base/tsconfig.json.hbs +1 -4
- package/templates/db/base/package.json.hbs +1 -5
- package/templates/db/base/tsconfig.json.hbs +1 -4
- package/templates/db/drizzle/sqlite/drizzle.config.ts.hbs +0 -7
- package/templates/db/prisma/mongodb/prisma/schema/schema.prisma.hbs +1 -1
- package/templates/db/prisma/mysql/prisma/schema/schema.prisma.hbs +8 -12
- package/templates/db/prisma/mysql/prisma.config.ts.hbs +17 -14
- package/templates/db/prisma/mysql/src/index.ts.hbs +46 -4
- package/templates/db/prisma/postgres/prisma/schema/schema.prisma.hbs +1 -5
- package/templates/db/prisma/postgres/prisma.config.ts.hbs +9 -6
- package/templates/db/prisma/postgres/src/index.ts.hbs +46 -1
- package/templates/db/prisma/sqlite/prisma/schema/schema.prisma.hbs +1 -12
- package/templates/db/prisma/sqlite/prisma.config.ts.hbs +16 -9
- package/templates/db/prisma/sqlite/src/index.ts.hbs +15 -15
- package/templates/deploy/alchemy/alchemy.run.ts.hbs +67 -11
- package/templates/deploy/alchemy/env.d.ts.hbs +0 -6
- package/templates/examples/ai/web/react/next/src/app/ai/page.tsx.hbs +2 -2
- package/templates/extras/_npmrc.hbs +2 -2
- package/templates/frontend/native/uniwind/app/(drawer)/_layout.tsx.hbs +21 -13
- package/templates/frontend/react/next/next.config.ts.hbs +5 -2
- package/templates/frontend/react/next/tsconfig.json.hbs +0 -3
- package/templates/packages/config/tsconfig.base.json.hbs +1 -1
- package/templates/api/orpc/server/tsdown.config.ts.hbs +0 -7
- package/templates/api/trpc/server/tsdown.config.ts.hbs +0 -7
- package/templates/auth/better-auth/server/base/tsdown.config.ts.hbs +0 -7
- package/templates/db/base/tsdown.config.ts.hbs +0 -7
- package/templates/deploy/wrangler/server/wrangler.jsonc.hbs +0 -39
- package/templates/deploy/wrangler/web/nuxt/wrangler.jsonc.hbs +0 -51
- package/templates/deploy/wrangler/web/react/next/open-next.config.ts +0 -6
- package/templates/deploy/wrangler/web/react/next/wrangler.jsonc.hbs +0 -22
- package/templates/deploy/wrangler/web/react/react-router/wrangler.jsonc.hbs +0 -8
- package/templates/deploy/wrangler/web/react/tanstack-router/wrangler.jsonc.hbs +0 -8
- package/templates/deploy/wrangler/web/react/tanstack-start/wrangler.jsonc.hbs +0 -20
- package/templates/deploy/wrangler/web/solid/wrangler.jsonc.hbs +0 -8
- package/templates/deploy/wrangler/web/svelte/wrangler.jsonc.hbs +0 -51
|
@@ -72,17 +72,22 @@ const dependencyVersionMap = {
|
|
|
72
72
|
"drizzle-kit": "^0.31.2",
|
|
73
73
|
"@planetscale/database": "^1.19.0",
|
|
74
74
|
"@libsql/client": "^0.14.0",
|
|
75
|
-
|
|
75
|
+
libsql: "^0.5.22",
|
|
76
|
+
"@neondatabase/serverless": "^1.0.2",
|
|
76
77
|
pg: "^8.14.1",
|
|
77
78
|
"@types/pg": "^8.11.11",
|
|
78
79
|
"@types/ws": "^8.18.1",
|
|
79
80
|
ws: "^8.18.3",
|
|
80
81
|
mysql2: "^3.14.0",
|
|
81
|
-
"@prisma/client": "^
|
|
82
|
-
prisma: "^
|
|
83
|
-
"@prisma/adapter-d1": "^
|
|
84
|
-
"@prisma/adapter-
|
|
85
|
-
"@prisma/adapter-
|
|
82
|
+
"@prisma/client": "^7.0.0",
|
|
83
|
+
prisma: "^7.0.0",
|
|
84
|
+
"@prisma/adapter-d1": "^7.0.0",
|
|
85
|
+
"@prisma/adapter-neon": "^7.0.0",
|
|
86
|
+
"@prisma/adapter-mariadb": "^7.0.0",
|
|
87
|
+
"@prisma/adapter-libsql": "^7.0.0",
|
|
88
|
+
"@prisma/adapter-better-sqlite3": "^7.0.0",
|
|
89
|
+
"@prisma/adapter-pg": "^7.0.0",
|
|
90
|
+
"@prisma/adapter-planetscale": "^7.0.0",
|
|
86
91
|
mongoose: "^8.14.0",
|
|
87
92
|
"vite-plugin-pwa": "^1.0.1",
|
|
88
93
|
"@vite-pwa/assets-generator": "^1.0.0",
|
|
@@ -147,7 +152,7 @@ const dependencyVersionMap = {
|
|
|
147
152
|
"@cloudflare/workers-types": "^4.20250822.0",
|
|
148
153
|
alchemy: "^0.77.0",
|
|
149
154
|
dotenv: "^17.2.2",
|
|
150
|
-
tsdown: "^0.
|
|
155
|
+
tsdown: "^0.16.5",
|
|
151
156
|
zod: "^4.1.11",
|
|
152
157
|
srvx: "0.8.15",
|
|
153
158
|
"@polar-sh/better-auth": "^1.1.3",
|
|
@@ -278,16 +283,8 @@ const ProjectNameSchema = z.string().min(1, "Project name cannot be empty").max(
|
|
|
278
283
|
"*"
|
|
279
284
|
].some((char) => name.includes(char));
|
|
280
285
|
}, "Project name contains invalid characters").refine((name) => name.toLowerCase() !== "node_modules", "Project name is reserved").describe("Project name or path");
|
|
281
|
-
const WebDeploySchema = z.enum([
|
|
282
|
-
|
|
283
|
-
"alchemy",
|
|
284
|
-
"none"
|
|
285
|
-
]).describe("Web deployment");
|
|
286
|
-
const ServerDeploySchema = z.enum([
|
|
287
|
-
"wrangler",
|
|
288
|
-
"alchemy",
|
|
289
|
-
"none"
|
|
290
|
-
]).describe("Server deployment");
|
|
286
|
+
const WebDeploySchema = z.enum(["alchemy", "none"]).describe("Web deployment");
|
|
287
|
+
const ServerDeploySchema = z.enum(["alchemy", "none"]).describe("Server deployment");
|
|
291
288
|
const DirectoryConflictSchema = z.enum([
|
|
292
289
|
"merge",
|
|
293
290
|
"overwrite",
|
|
@@ -1131,10 +1128,6 @@ async function getRuntimeChoice(runtime, backend) {
|
|
|
1131
1128
|
//#endregion
|
|
1132
1129
|
//#region src/prompts/server-deploy.ts
|
|
1133
1130
|
function getDeploymentDisplay$1(deployment) {
|
|
1134
|
-
if (deployment === "wrangler") return {
|
|
1135
|
-
label: "Wrangler",
|
|
1136
|
-
hint: "Deploy to Cloudflare Workers using Wrangler"
|
|
1137
|
-
};
|
|
1138
1131
|
if (deployment === "alchemy") return {
|
|
1139
1132
|
label: "Alchemy",
|
|
1140
1133
|
hint: "Deploy to Cloudflare Workers using Alchemy"
|
|
@@ -1144,40 +1137,17 @@ function getDeploymentDisplay$1(deployment) {
|
|
|
1144
1137
|
hint: `Add ${deployment} deployment`
|
|
1145
1138
|
};
|
|
1146
1139
|
}
|
|
1147
|
-
async function getServerDeploymentChoice(deployment, runtime, backend,
|
|
1140
|
+
async function getServerDeploymentChoice(deployment, runtime, backend, _webDeploy) {
|
|
1148
1141
|
if (deployment !== void 0) return deployment;
|
|
1149
1142
|
if (backend === "none" || backend === "convex") return "none";
|
|
1150
1143
|
if (backend !== "hono") return "none";
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
["alchemy", "wrangler"].forEach((deploy) => {
|
|
1154
|
-
const { label, hint } = getDeploymentDisplay$1(deploy);
|
|
1155
|
-
options.unshift({
|
|
1156
|
-
value: deploy,
|
|
1157
|
-
label,
|
|
1158
|
-
hint
|
|
1159
|
-
});
|
|
1160
|
-
});
|
|
1161
|
-
const response = await select({
|
|
1162
|
-
message: "Select server deployment",
|
|
1163
|
-
options,
|
|
1164
|
-
initialValue: webDeploy === "alchemy" ? "alchemy" : runtime === "workers" ? "wrangler" : DEFAULT_CONFIG.serverDeploy
|
|
1165
|
-
});
|
|
1166
|
-
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
1167
|
-
return response;
|
|
1144
|
+
if (runtime === "workers") return "alchemy";
|
|
1145
|
+
return "none";
|
|
1168
1146
|
}
|
|
1169
1147
|
async function getServerDeploymentToAdd(runtime, existingDeployment, backend) {
|
|
1170
1148
|
if (backend !== "hono") return "none";
|
|
1171
1149
|
const options = [];
|
|
1172
1150
|
if (runtime === "workers") {
|
|
1173
|
-
if (existingDeployment !== "wrangler") {
|
|
1174
|
-
const { label, hint } = getDeploymentDisplay$1("wrangler");
|
|
1175
|
-
options.push({
|
|
1176
|
-
value: "wrangler",
|
|
1177
|
-
label,
|
|
1178
|
-
hint
|
|
1179
|
-
});
|
|
1180
|
-
}
|
|
1181
1151
|
if (existingDeployment !== "alchemy") {
|
|
1182
1152
|
const { label, hint } = getDeploymentDisplay$1("alchemy");
|
|
1183
1153
|
options.push({
|
|
@@ -1193,7 +1163,7 @@ async function getServerDeploymentToAdd(runtime, existingDeployment, backend) {
|
|
|
1193
1163
|
const response = await select({
|
|
1194
1164
|
message: "Select server deployment",
|
|
1195
1165
|
options,
|
|
1196
|
-
initialValue:
|
|
1166
|
+
initialValue: DEFAULT_CONFIG.serverDeploy
|
|
1197
1167
|
});
|
|
1198
1168
|
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
1199
1169
|
return response;
|
|
@@ -1205,10 +1175,6 @@ function hasWebFrontend(frontends) {
|
|
|
1205
1175
|
return frontends.some((f) => WEB_FRAMEWORKS.includes(f));
|
|
1206
1176
|
}
|
|
1207
1177
|
function getDeploymentDisplay(deployment) {
|
|
1208
|
-
if (deployment === "wrangler") return {
|
|
1209
|
-
label: "Wrangler",
|
|
1210
|
-
hint: "Deploy to Cloudflare Workers using Wrangler"
|
|
1211
|
-
};
|
|
1212
1178
|
if (deployment === "alchemy") return {
|
|
1213
1179
|
label: "Alchemy",
|
|
1214
1180
|
hint: "Deploy to Cloudflare Workers using Alchemy"
|
|
@@ -1223,11 +1189,7 @@ async function getDeploymentChoice(deployment, _runtime, _backend, frontend = []
|
|
|
1223
1189
|
if (!hasWebFrontend(frontend)) return "none";
|
|
1224
1190
|
const response = await select({
|
|
1225
1191
|
message: "Select web deployment",
|
|
1226
|
-
options: [
|
|
1227
|
-
"wrangler",
|
|
1228
|
-
"alchemy",
|
|
1229
|
-
"none"
|
|
1230
|
-
].map((deploy) => {
|
|
1192
|
+
options: ["alchemy", "none"].map((deploy) => {
|
|
1231
1193
|
const { label, hint } = getDeploymentDisplay(deploy);
|
|
1232
1194
|
return {
|
|
1233
1195
|
value: deploy,
|
|
@@ -1243,14 +1205,6 @@ async function getDeploymentChoice(deployment, _runtime, _backend, frontend = []
|
|
|
1243
1205
|
async function getDeploymentToAdd(frontend, existingDeployment) {
|
|
1244
1206
|
if (!hasWebFrontend(frontend)) return "none";
|
|
1245
1207
|
const options = [];
|
|
1246
|
-
if (existingDeployment !== "wrangler") {
|
|
1247
|
-
const { label, hint } = getDeploymentDisplay("wrangler");
|
|
1248
|
-
options.push({
|
|
1249
|
-
value: "wrangler",
|
|
1250
|
-
label,
|
|
1251
|
-
hint
|
|
1252
|
-
});
|
|
1253
|
-
}
|
|
1254
1208
|
if (existingDeployment !== "alchemy") {
|
|
1255
1209
|
const { label, hint } = getDeploymentDisplay("alchemy");
|
|
1256
1210
|
options.push({
|
|
@@ -1814,8 +1768,8 @@ function validateFullConfig(config, providedFlags, options) {
|
|
|
1814
1768
|
validateServerDeployRequiresBackend(config.serverDeploy, config.backend);
|
|
1815
1769
|
validateSelfBackendCompatibility(providedFlags, options, config);
|
|
1816
1770
|
validateWorkersCompatibility(providedFlags, options, config);
|
|
1817
|
-
if (config.runtime === "workers" && config.serverDeploy === "none") exitWithError("Cloudflare Workers runtime requires a server deployment. Please choose '
|
|
1818
|
-
if (providedFlags.has("serverDeploy") &&
|
|
1771
|
+
if (config.runtime === "workers" && config.serverDeploy === "none") exitWithError("Cloudflare Workers runtime requires a server deployment. Please choose 'alchemy' for --server-deploy.");
|
|
1772
|
+
if (providedFlags.has("serverDeploy") && config.serverDeploy === "alchemy" && config.runtime !== "workers") exitWithError(`Server deployment '${config.serverDeploy}' requires '--runtime workers'. Please use '--runtime workers' or choose a different server deployment.`);
|
|
1819
1773
|
if (config.addons && config.addons.length > 0) {
|
|
1820
1774
|
validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth);
|
|
1821
1775
|
config.addons = [...new Set(config.addons)];
|
|
@@ -2735,6 +2689,38 @@ handlebars.registerHelper("or", (...args) => {
|
|
|
2735
2689
|
});
|
|
2736
2690
|
handlebars.registerHelper("includes", (array, value) => Array.isArray(array) && array.includes(value));
|
|
2737
2691
|
|
|
2692
|
+
//#endregion
|
|
2693
|
+
//#region src/helpers/deployment/alchemy/env-dts-setup.ts
|
|
2694
|
+
const tsProject$1 = new Project({
|
|
2695
|
+
useInMemoryFileSystem: false,
|
|
2696
|
+
skipAddingFilesFromTsConfig: true
|
|
2697
|
+
});
|
|
2698
|
+
function determineImportPath(envDtsPath, projectDir, config) {
|
|
2699
|
+
const { webDeploy, serverDeploy, backend } = config;
|
|
2700
|
+
const isBackendSelf = backend === "self";
|
|
2701
|
+
let alchemyRunPath;
|
|
2702
|
+
if (webDeploy === "alchemy" && (serverDeploy === "alchemy" || isBackendSelf)) if (isBackendSelf) alchemyRunPath = path.join(projectDir, "apps/web/alchemy.run.ts");
|
|
2703
|
+
else alchemyRunPath = path.join(projectDir, "alchemy.run.ts");
|
|
2704
|
+
else if (webDeploy === "alchemy") alchemyRunPath = path.join(projectDir, "apps/web/alchemy.run.ts");
|
|
2705
|
+
else if (serverDeploy === "alchemy") alchemyRunPath = path.join(projectDir, "apps/server/alchemy.run.ts");
|
|
2706
|
+
else alchemyRunPath = path.join(projectDir, "alchemy.run.ts");
|
|
2707
|
+
const relativePath = path.relative(path.dirname(envDtsPath), alchemyRunPath.replace(/\.ts$/, ""));
|
|
2708
|
+
return (relativePath.startsWith(".") ? relativePath : `./${relativePath}`).replace(/\\/g, "/");
|
|
2709
|
+
}
|
|
2710
|
+
async function setupEnvDtsImport(envDtsPath, projectDir, config) {
|
|
2711
|
+
if (!await fs.pathExists(envDtsPath)) return;
|
|
2712
|
+
const importPath = determineImportPath(envDtsPath, projectDir, config);
|
|
2713
|
+
const sourceFile = tsProject$1.addSourceFileAtPath(envDtsPath);
|
|
2714
|
+
if (!sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === importPath && imp.getNamedImports().some((named) => named.getName() === "server"))) sourceFile.insertImportDeclaration(0, {
|
|
2715
|
+
moduleSpecifier: importPath,
|
|
2716
|
+
namedImports: [{
|
|
2717
|
+
name: "server",
|
|
2718
|
+
isTypeOnly: true
|
|
2719
|
+
}]
|
|
2720
|
+
});
|
|
2721
|
+
await sourceFile.save();
|
|
2722
|
+
}
|
|
2723
|
+
|
|
2738
2724
|
//#endregion
|
|
2739
2725
|
//#region src/helpers/core/template-manager.ts
|
|
2740
2726
|
async function processAndCopyFiles(sourcePattern, baseSourceDir, destDir, context, overwrite = true, ignorePatterns) {
|
|
@@ -3226,21 +3212,23 @@ async function setupDeploymentTemplates(projectDir, context) {
|
|
|
3226
3212
|
if (await fs.pathExists(alchemyTemplateSrc)) {
|
|
3227
3213
|
const webAppDir = path.join(projectDir, "apps/web");
|
|
3228
3214
|
await processAndCopyFiles("alchemy.run.ts.hbs", alchemyTemplateSrc, isBackendSelf && await fs.pathExists(webAppDir) ? webAppDir : projectDir, context);
|
|
3229
|
-
await addEnvDtsToPackages(projectDir, context, alchemyTemplateSrc);
|
|
3215
|
+
if (!isBackendSelf) await addEnvDtsToPackages(projectDir, context, alchemyTemplateSrc);
|
|
3230
3216
|
}
|
|
3231
3217
|
} else {
|
|
3232
3218
|
if (context.webDeploy === "alchemy") {
|
|
3233
3219
|
const webAppDir = path.join(projectDir, "apps/web");
|
|
3234
3220
|
if (await fs.pathExists(alchemyTemplateSrc) && await fs.pathExists(webAppDir)) {
|
|
3235
3221
|
await processAndCopyFiles("alchemy.run.ts.hbs", alchemyTemplateSrc, webAppDir, context);
|
|
3236
|
-
await addEnvDtsToPackages(projectDir, context, alchemyTemplateSrc);
|
|
3222
|
+
if (!isBackendSelf) await addEnvDtsToPackages(projectDir, context, alchemyTemplateSrc);
|
|
3237
3223
|
}
|
|
3238
3224
|
}
|
|
3239
3225
|
if (context.serverDeploy === "alchemy" && !isBackendSelf) {
|
|
3240
3226
|
const serverAppDir = path.join(projectDir, "apps/server");
|
|
3241
3227
|
if (await fs.pathExists(alchemyTemplateSrc) && await fs.pathExists(serverAppDir)) {
|
|
3242
3228
|
await processAndCopyFiles("alchemy.run.ts.hbs", alchemyTemplateSrc, serverAppDir, context);
|
|
3243
|
-
|
|
3229
|
+
const envDtsPath = path.join(serverAppDir, "env.d.ts");
|
|
3230
|
+
await processTemplate(path.join(alchemyTemplateSrc, "env.d.ts.hbs"), envDtsPath, context);
|
|
3231
|
+
await setupEnvDtsImport(envDtsPath, projectDir, context);
|
|
3244
3232
|
await addEnvDtsToPackages(projectDir, context, alchemyTemplateSrc);
|
|
3245
3233
|
}
|
|
3246
3234
|
}
|
|
@@ -3280,10 +3268,18 @@ async function addEnvDtsToPackages(projectDir, context, alchemyTemplateSrc) {
|
|
|
3280
3268
|
"packages/db"
|
|
3281
3269
|
]) {
|
|
3282
3270
|
const packageDir = path.join(projectDir, packageName);
|
|
3283
|
-
if (await fs.pathExists(packageDir))
|
|
3271
|
+
if (await fs.pathExists(packageDir)) {
|
|
3272
|
+
const envDtsPath = path.join(packageDir, "env.d.ts");
|
|
3273
|
+
await processTemplate(path.join(alchemyTemplateSrc, "env.d.ts.hbs"), envDtsPath, context);
|
|
3274
|
+
await setupEnvDtsImport(envDtsPath, projectDir, context);
|
|
3275
|
+
}
|
|
3284
3276
|
}
|
|
3285
3277
|
const serverAppDir = path.join(projectDir, "apps/server");
|
|
3286
|
-
if (await fs.pathExists(serverAppDir))
|
|
3278
|
+
if (await fs.pathExists(serverAppDir)) {
|
|
3279
|
+
const envDtsPath = path.join(serverAppDir, "env.d.ts");
|
|
3280
|
+
await processTemplate(path.join(alchemyTemplateSrc, "env.d.ts.hbs"), envDtsPath, context);
|
|
3281
|
+
await setupEnvDtsImport(envDtsPath, projectDir, context);
|
|
3282
|
+
}
|
|
3287
3283
|
}
|
|
3288
3284
|
|
|
3289
3285
|
//#endregion
|
|
@@ -3337,54 +3333,13 @@ async function addAddonsToProject(input) {
|
|
|
3337
3333
|
//#region src/helpers/deployment/server-deploy-setup.ts
|
|
3338
3334
|
async function setupServerDeploy(config) {
|
|
3339
3335
|
const { serverDeploy, webDeploy, projectDir } = config;
|
|
3340
|
-
const { packageManager } = config;
|
|
3341
3336
|
if (serverDeploy === "none") return;
|
|
3342
3337
|
if (serverDeploy === "alchemy" && webDeploy === "alchemy") return;
|
|
3343
3338
|
const serverDir = path.join(projectDir, "apps/server");
|
|
3344
3339
|
if (!await fs.pathExists(serverDir)) return;
|
|
3345
|
-
if (serverDeploy === "
|
|
3346
|
-
await setupWorkersServerDeploy(serverDir, packageManager);
|
|
3347
|
-
await generateCloudflareWorkerTypes({
|
|
3348
|
-
serverDir,
|
|
3349
|
-
packageManager
|
|
3350
|
-
});
|
|
3351
|
-
} else if (serverDeploy === "alchemy") await setupAlchemyServerDeploy(serverDir, packageManager, projectDir);
|
|
3340
|
+
if (serverDeploy === "alchemy") await setupAlchemyServerDeploy(serverDir, projectDir);
|
|
3352
3341
|
}
|
|
3353
|
-
async function
|
|
3354
|
-
const packageJsonPath = path.join(serverDir, "package.json");
|
|
3355
|
-
if (!await fs.pathExists(packageJsonPath)) return;
|
|
3356
|
-
const packageJson = await fs.readJson(packageJsonPath);
|
|
3357
|
-
packageJson.scripts = {
|
|
3358
|
-
...packageJson.scripts,
|
|
3359
|
-
dev: "wrangler dev --port=3000",
|
|
3360
|
-
start: "wrangler dev",
|
|
3361
|
-
deploy: "wrangler deploy",
|
|
3362
|
-
build: "wrangler deploy --dry-run",
|
|
3363
|
-
"cf-typegen": "wrangler types --env-interface CloudflareBindings"
|
|
3364
|
-
};
|
|
3365
|
-
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
3366
|
-
await addPackageDependency({
|
|
3367
|
-
devDependencies: ["wrangler", "@types/node"],
|
|
3368
|
-
projectDir: serverDir
|
|
3369
|
-
});
|
|
3370
|
-
}
|
|
3371
|
-
async function generateCloudflareWorkerTypes({ serverDir, packageManager }) {
|
|
3372
|
-
if (!await fs.pathExists(serverDir)) return;
|
|
3373
|
-
const s = spinner();
|
|
3374
|
-
try {
|
|
3375
|
-
s.start("Generating Cloudflare Workers types...");
|
|
3376
|
-
await execa(getPackageExecutionCommand(packageManager, "wrangler types --env-interface CloudflareBindings"), {
|
|
3377
|
-
cwd: serverDir,
|
|
3378
|
-
shell: true
|
|
3379
|
-
});
|
|
3380
|
-
s.stop("Cloudflare Workers types generated successfully!");
|
|
3381
|
-
} catch {
|
|
3382
|
-
s.stop(pc.yellow("Failed to generate Cloudflare Workers types"));
|
|
3383
|
-
const managerCmd = `${packageManager} run`;
|
|
3384
|
-
log.warn(`Note: You can manually run 'cd apps/server && ${managerCmd} cf-typegen' in the project directory later`);
|
|
3385
|
-
}
|
|
3386
|
-
}
|
|
3387
|
-
async function setupAlchemyServerDeploy(serverDir, _packageManager, projectDir) {
|
|
3342
|
+
async function setupAlchemyServerDeploy(serverDir, projectDir) {
|
|
3388
3343
|
if (!await fs.pathExists(serverDir)) return;
|
|
3389
3344
|
await addPackageDependency({
|
|
3390
3345
|
devDependencies: [
|
|
@@ -3719,7 +3674,7 @@ async function setupCombinedAlchemyDeploy(projectDir, packageManager, config) {
|
|
|
3719
3674
|
await fs.writeJson(rootPkgPath, pkg, { spaces: 2 });
|
|
3720
3675
|
}
|
|
3721
3676
|
const serverDir = path.join(projectDir, "apps/server");
|
|
3722
|
-
if (await fs.pathExists(serverDir)) await setupAlchemyServerDeploy(serverDir,
|
|
3677
|
+
if (await fs.pathExists(serverDir)) await setupAlchemyServerDeploy(serverDir, projectDir);
|
|
3723
3678
|
const frontend = config.frontend;
|
|
3724
3679
|
const isNext = frontend.includes("next");
|
|
3725
3680
|
const isNuxt = frontend.includes("nuxt");
|
|
@@ -3737,219 +3692,13 @@ async function setupCombinedAlchemyDeploy(projectDir, packageManager, config) {
|
|
|
3737
3692
|
else if (isSolid) await setupSolidAlchemyDeploy(projectDir, packageManager, { skipAppScripts: true });
|
|
3738
3693
|
}
|
|
3739
3694
|
|
|
3740
|
-
//#endregion
|
|
3741
|
-
//#region src/helpers/deployment/workers/workers-next-setup.ts
|
|
3742
|
-
async function setupNextWorkersDeploy(projectDir, _packageManager) {
|
|
3743
|
-
const webAppDir = path.join(projectDir, "apps/web");
|
|
3744
|
-
if (!await fs.pathExists(webAppDir)) return;
|
|
3745
|
-
await addPackageDependency({
|
|
3746
|
-
dependencies: ["@opennextjs/cloudflare"],
|
|
3747
|
-
devDependencies: ["wrangler"],
|
|
3748
|
-
projectDir: webAppDir
|
|
3749
|
-
});
|
|
3750
|
-
const packageJsonPath = path.join(webAppDir, "package.json");
|
|
3751
|
-
if (await fs.pathExists(packageJsonPath)) {
|
|
3752
|
-
const pkg = await fs.readJson(packageJsonPath);
|
|
3753
|
-
pkg.scripts = {
|
|
3754
|
-
...pkg.scripts,
|
|
3755
|
-
preview: "opennextjs-cloudflare build && opennextjs-cloudflare preview",
|
|
3756
|
-
deploy: "opennextjs-cloudflare build && opennextjs-cloudflare deploy",
|
|
3757
|
-
upload: "opennextjs-cloudflare build && opennextjs-cloudflare upload",
|
|
3758
|
-
"cf-typegen": "wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts"
|
|
3759
|
-
};
|
|
3760
|
-
await fs.writeJson(packageJsonPath, pkg, { spaces: 2 });
|
|
3761
|
-
}
|
|
3762
|
-
}
|
|
3763
|
-
|
|
3764
|
-
//#endregion
|
|
3765
|
-
//#region src/helpers/deployment/workers/workers-nuxt-setup.ts
|
|
3766
|
-
async function setupNuxtWorkersDeploy(projectDir, packageManager) {
|
|
3767
|
-
const webAppDir = path.join(projectDir, "apps/web");
|
|
3768
|
-
if (!await fs.pathExists(webAppDir)) return;
|
|
3769
|
-
await addPackageDependency({
|
|
3770
|
-
devDependencies: ["nitro-cloudflare-dev", "wrangler"],
|
|
3771
|
-
projectDir: webAppDir
|
|
3772
|
-
});
|
|
3773
|
-
const pkgPath = path.join(webAppDir, "package.json");
|
|
3774
|
-
if (await fs.pathExists(pkgPath)) {
|
|
3775
|
-
const pkg = await fs.readJson(pkgPath);
|
|
3776
|
-
pkg.scripts = {
|
|
3777
|
-
...pkg.scripts,
|
|
3778
|
-
deploy: `${packageManager} run build && wrangler deploy`,
|
|
3779
|
-
"cf-typegen": "wrangler types"
|
|
3780
|
-
};
|
|
3781
|
-
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
3782
|
-
}
|
|
3783
|
-
const nuxtConfigPath = path.join(webAppDir, "nuxt.config.ts");
|
|
3784
|
-
if (!await fs.pathExists(nuxtConfigPath)) return;
|
|
3785
|
-
const sourceFile = tsProject.addSourceFileAtPathIfExists(nuxtConfigPath);
|
|
3786
|
-
if (!sourceFile) return;
|
|
3787
|
-
const defineCall = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression).find((expr) => {
|
|
3788
|
-
const expression = expr.getExpression();
|
|
3789
|
-
return Node.isIdentifier(expression) && expression.getText() === "defineNuxtConfig";
|
|
3790
|
-
});
|
|
3791
|
-
if (!defineCall) return;
|
|
3792
|
-
const configObj = defineCall.getArguments()[0];
|
|
3793
|
-
if (!configObj) return;
|
|
3794
|
-
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
3795
|
-
const compatProp = configObj.getProperty("compatibilityDate");
|
|
3796
|
-
if (compatProp && compatProp.getKind() === SyntaxKind.PropertyAssignment) compatProp.setInitializer(`'${today}'`);
|
|
3797
|
-
else configObj.addPropertyAssignment({
|
|
3798
|
-
name: "compatibilityDate",
|
|
3799
|
-
initializer: `'${today}'`
|
|
3800
|
-
});
|
|
3801
|
-
const nitroInitializer = `{
|
|
3802
|
-
preset: "cloudflare_module",
|
|
3803
|
-
cloudflare: {
|
|
3804
|
-
deployConfig: true,
|
|
3805
|
-
nodeCompat: true
|
|
3806
|
-
}
|
|
3807
|
-
}`;
|
|
3808
|
-
const nitroProp = configObj.getProperty("nitro");
|
|
3809
|
-
if (nitroProp && nitroProp.getKind() === SyntaxKind.PropertyAssignment) nitroProp.setInitializer(nitroInitializer);
|
|
3810
|
-
else configObj.addPropertyAssignment({
|
|
3811
|
-
name: "nitro",
|
|
3812
|
-
initializer: nitroInitializer
|
|
3813
|
-
});
|
|
3814
|
-
const modulesProp = configObj.getProperty("modules");
|
|
3815
|
-
if (modulesProp && modulesProp.getKind() === SyntaxKind.PropertyAssignment) {
|
|
3816
|
-
const arrayExpr = modulesProp.getFirstDescendantByKind(SyntaxKind.ArrayLiteralExpression);
|
|
3817
|
-
if (arrayExpr) {
|
|
3818
|
-
if (!arrayExpr.getElements().some((el) => el.getText().replace(/['"`]/g, "") === "nitro-cloudflare-dev")) arrayExpr.addElement("'nitro-cloudflare-dev'");
|
|
3819
|
-
}
|
|
3820
|
-
} else configObj.addPropertyAssignment({
|
|
3821
|
-
name: "modules",
|
|
3822
|
-
initializer: "['nitro-cloudflare-dev']"
|
|
3823
|
-
});
|
|
3824
|
-
await tsProject.save();
|
|
3825
|
-
}
|
|
3826
|
-
|
|
3827
|
-
//#endregion
|
|
3828
|
-
//#region src/helpers/deployment/workers/workers-svelte-setup.ts
|
|
3829
|
-
async function setupSvelteWorkersDeploy(projectDir, packageManager) {
|
|
3830
|
-
const webAppDir = path.join(projectDir, "apps/web");
|
|
3831
|
-
if (!await fs.pathExists(webAppDir)) return;
|
|
3832
|
-
await addPackageDependency({
|
|
3833
|
-
devDependencies: ["@sveltejs/adapter-cloudflare", "wrangler"],
|
|
3834
|
-
projectDir: webAppDir
|
|
3835
|
-
});
|
|
3836
|
-
const pkgPath = path.join(webAppDir, "package.json");
|
|
3837
|
-
if (await fs.pathExists(pkgPath)) {
|
|
3838
|
-
const pkg = await fs.readJson(pkgPath);
|
|
3839
|
-
pkg.scripts = {
|
|
3840
|
-
...pkg.scripts,
|
|
3841
|
-
deploy: `${packageManager} run build && wrangler deploy`,
|
|
3842
|
-
"cf-typegen": "wrangler types ./src/worker-configuration.d.ts"
|
|
3843
|
-
};
|
|
3844
|
-
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
3845
|
-
}
|
|
3846
|
-
const possibleConfigFiles = [path.join(webAppDir, "svelte.config.js"), path.join(webAppDir, "svelte.config.ts")];
|
|
3847
|
-
const existingConfigPath = (await Promise.all(possibleConfigFiles.map(async (p) => await fs.pathExists(p) ? p : ""))).find((p) => p);
|
|
3848
|
-
if (existingConfigPath) {
|
|
3849
|
-
const sourceFile = tsProject.addSourceFileAtPathIfExists(existingConfigPath);
|
|
3850
|
-
if (!sourceFile) return;
|
|
3851
|
-
const adapterImport = sourceFile.getImportDeclarations().find((imp) => ["@sveltejs/adapter-auto", "@sveltejs/adapter-node"].includes(imp.getModuleSpecifierValue()));
|
|
3852
|
-
if (adapterImport) adapterImport.setModuleSpecifier("@sveltejs/adapter-cloudflare");
|
|
3853
|
-
else if (!sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === "@sveltejs/adapter-cloudflare")) sourceFile.insertImportDeclaration(0, {
|
|
3854
|
-
defaultImport: "adapter",
|
|
3855
|
-
moduleSpecifier: "@sveltejs/adapter-cloudflare"
|
|
3856
|
-
});
|
|
3857
|
-
await tsProject.save();
|
|
3858
|
-
}
|
|
3859
|
-
}
|
|
3860
|
-
|
|
3861
|
-
//#endregion
|
|
3862
|
-
//#region src/helpers/deployment/workers/workers-tanstack-start-setup.ts
|
|
3863
|
-
async function setupTanstackStartWorkersDeploy(projectDir, packageManager) {
|
|
3864
|
-
const webAppDir = path.join(projectDir, "apps/web");
|
|
3865
|
-
if (!await fs.pathExists(webAppDir)) return;
|
|
3866
|
-
await addPackageDependency({
|
|
3867
|
-
devDependencies: ["wrangler", "@cloudflare/vite-plugin"],
|
|
3868
|
-
projectDir: webAppDir
|
|
3869
|
-
});
|
|
3870
|
-
const pkgPath = path.join(webAppDir, "package.json");
|
|
3871
|
-
if (await fs.pathExists(pkgPath)) {
|
|
3872
|
-
const pkg = await fs.readJson(pkgPath);
|
|
3873
|
-
pkg.scripts = {
|
|
3874
|
-
...pkg.scripts,
|
|
3875
|
-
deploy: `${packageManager} run build && wrangler deploy`,
|
|
3876
|
-
"cf-typegen": "wrangler types --env-interface Env"
|
|
3877
|
-
};
|
|
3878
|
-
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
3879
|
-
}
|
|
3880
|
-
const viteConfigPath = path.join(webAppDir, "vite.config.ts");
|
|
3881
|
-
if (!await fs.pathExists(viteConfigPath)) return;
|
|
3882
|
-
const sourceFile = tsProject.addSourceFileAtPathIfExists(viteConfigPath);
|
|
3883
|
-
if (!sourceFile) return;
|
|
3884
|
-
const cfImport = sourceFile.getImportDeclaration("@cloudflare/vite-plugin");
|
|
3885
|
-
if (!cfImport) sourceFile.addImportDeclaration({
|
|
3886
|
-
moduleSpecifier: "@cloudflare/vite-plugin",
|
|
3887
|
-
namedImports: [{ name: "cloudflare" }]
|
|
3888
|
-
});
|
|
3889
|
-
else if (!cfImport.getNamedImports().some((ni) => ni.getName() === "cloudflare")) cfImport.addNamedImport({ name: "cloudflare" });
|
|
3890
|
-
const reactImport = sourceFile.getImportDeclaration("@vitejs/plugin-react");
|
|
3891
|
-
let reactPluginIdentifier = "viteReact";
|
|
3892
|
-
if (!reactImport) sourceFile.addImportDeclaration({
|
|
3893
|
-
moduleSpecifier: "@vitejs/plugin-react",
|
|
3894
|
-
defaultImport: "viteReact"
|
|
3895
|
-
});
|
|
3896
|
-
else {
|
|
3897
|
-
const defaultImport = reactImport.getDefaultImport();
|
|
3898
|
-
if (defaultImport) reactPluginIdentifier = defaultImport.getText();
|
|
3899
|
-
else reactImport.setDefaultImport("viteReact");
|
|
3900
|
-
}
|
|
3901
|
-
const defineCall = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression).find((expr) => {
|
|
3902
|
-
const expression = expr.getExpression();
|
|
3903
|
-
return Node.isIdentifier(expression) && expression.getText() === "defineConfig";
|
|
3904
|
-
});
|
|
3905
|
-
if (!defineCall) return;
|
|
3906
|
-
const configObj = defineCall.getArguments()[0];
|
|
3907
|
-
if (!configObj) return;
|
|
3908
|
-
const pluginsArray = ensureArrayProperty(configObj, "plugins");
|
|
3909
|
-
if (!pluginsArray.getElements().some((el) => el.getText().includes("cloudflare("))) pluginsArray.insertElement(0, "cloudflare({ viteEnvironment: { name: 'ssr' } })");
|
|
3910
|
-
if (!pluginsArray.getElements().some((el) => Node.isCallExpression(el) && el.getExpression().getText() === reactPluginIdentifier)) {
|
|
3911
|
-
const nextIndex = pluginsArray.getElements().findIndex((el) => el.getText().includes("tanstackStart(")) + 1;
|
|
3912
|
-
if (nextIndex > 0) pluginsArray.insertElement(nextIndex, `${reactPluginIdentifier}()`);
|
|
3913
|
-
else pluginsArray.addElement(`${reactPluginIdentifier}()`);
|
|
3914
|
-
}
|
|
3915
|
-
await tsProject.save();
|
|
3916
|
-
}
|
|
3917
|
-
|
|
3918
|
-
//#endregion
|
|
3919
|
-
//#region src/helpers/deployment/workers/workers-vite-setup.ts
|
|
3920
|
-
async function setupWorkersVitePlugin(projectDir) {
|
|
3921
|
-
const webAppDir = path.join(projectDir, "apps/web");
|
|
3922
|
-
const viteConfigPath = path.join(webAppDir, "vite.config.ts");
|
|
3923
|
-
if (!await fs.pathExists(viteConfigPath)) throw new Error("vite.config.ts not found in web app directory");
|
|
3924
|
-
await addPackageDependency({
|
|
3925
|
-
devDependencies: ["@cloudflare/vite-plugin", "wrangler"],
|
|
3926
|
-
projectDir: webAppDir
|
|
3927
|
-
});
|
|
3928
|
-
const sourceFile = tsProject.addSourceFileAtPathIfExists(viteConfigPath);
|
|
3929
|
-
if (!sourceFile) throw new Error("vite.config.ts not found in web app directory");
|
|
3930
|
-
if (!sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === "@cloudflare/vite-plugin")) sourceFile.insertImportDeclaration(0, {
|
|
3931
|
-
namedImports: ["cloudflare"],
|
|
3932
|
-
moduleSpecifier: "@cloudflare/vite-plugin"
|
|
3933
|
-
});
|
|
3934
|
-
const defineCall = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression).find((expr) => {
|
|
3935
|
-
const expression = expr.getExpression();
|
|
3936
|
-
return Node.isIdentifier(expression) && expression.getText() === "defineConfig";
|
|
3937
|
-
});
|
|
3938
|
-
if (!defineCall) throw new Error("Could not find defineConfig call in vite config");
|
|
3939
|
-
const configObject = defineCall.getArguments()[0];
|
|
3940
|
-
if (!configObject) throw new Error("defineConfig argument is not an object literal");
|
|
3941
|
-
const pluginsArray = ensureArrayProperty(configObject, "plugins");
|
|
3942
|
-
if (!pluginsArray.getElements().some((el) => el.getText().includes("cloudflare("))) pluginsArray.addElement("cloudflare()");
|
|
3943
|
-
await tsProject.save();
|
|
3944
|
-
}
|
|
3945
|
-
|
|
3946
3695
|
//#endregion
|
|
3947
3696
|
//#region src/helpers/deployment/web-deploy-setup.ts
|
|
3948
3697
|
async function setupWebDeploy(config) {
|
|
3949
3698
|
const { webDeploy, serverDeploy, frontend, projectDir } = config;
|
|
3950
3699
|
const { packageManager } = config;
|
|
3951
3700
|
if (webDeploy === "none") return;
|
|
3952
|
-
if (webDeploy !== "
|
|
3701
|
+
if (webDeploy !== "alchemy") return;
|
|
3953
3702
|
if (webDeploy === "alchemy" && serverDeploy === "alchemy") {
|
|
3954
3703
|
await setupCombinedAlchemyDeploy(projectDir, packageManager, config);
|
|
3955
3704
|
await addAlchemyPackagesDependencies(projectDir);
|
|
@@ -3962,37 +3711,14 @@ async function setupWebDeploy(config) {
|
|
|
3962
3711
|
const isTanstackStart = frontend.includes("tanstack-start");
|
|
3963
3712
|
const isReactRouter = frontend.includes("react-router");
|
|
3964
3713
|
const isSolid = frontend.includes("solid");
|
|
3965
|
-
if (
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
else if (isNuxt) await setupNuxtAlchemyDeploy(projectDir, packageManager);
|
|
3974
|
-
else if (isSvelte) await setupSvelteAlchemyDeploy(projectDir, packageManager);
|
|
3975
|
-
else if (isTanstackStart) await setupTanStackStartAlchemyDeploy(projectDir, packageManager);
|
|
3976
|
-
else if (isTanstackRouter) await setupTanStackRouterAlchemyDeploy(projectDir, packageManager);
|
|
3977
|
-
else if (isReactRouter) await setupReactRouterAlchemyDeploy(projectDir, packageManager);
|
|
3978
|
-
else if (isSolid) await setupSolidAlchemyDeploy(projectDir, packageManager);
|
|
3979
|
-
await addAlchemyPackagesDependencies(projectDir);
|
|
3980
|
-
}
|
|
3981
|
-
}
|
|
3982
|
-
async function setupWorkersWebDeploy(projectDir, pkgManager) {
|
|
3983
|
-
const webAppDir = path.join(projectDir, "apps/web");
|
|
3984
|
-
if (!await fs.pathExists(webAppDir)) return;
|
|
3985
|
-
const packageJsonPath = path.join(webAppDir, "package.json");
|
|
3986
|
-
if (await fs.pathExists(packageJsonPath)) {
|
|
3987
|
-
const packageJson = await fs.readJson(packageJsonPath);
|
|
3988
|
-
packageJson.scripts = {
|
|
3989
|
-
...packageJson.scripts,
|
|
3990
|
-
"wrangler:dev": "wrangler dev --port=3001",
|
|
3991
|
-
deploy: `${pkgManager} run build && wrangler deploy`
|
|
3992
|
-
};
|
|
3993
|
-
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
3994
|
-
}
|
|
3995
|
-
await setupWorkersVitePlugin(projectDir);
|
|
3714
|
+
if (isNext) await setupNextAlchemyDeploy(projectDir, packageManager);
|
|
3715
|
+
else if (isNuxt) await setupNuxtAlchemyDeploy(projectDir, packageManager);
|
|
3716
|
+
else if (isSvelte) await setupSvelteAlchemyDeploy(projectDir, packageManager);
|
|
3717
|
+
else if (isTanstackStart) await setupTanStackStartAlchemyDeploy(projectDir, packageManager);
|
|
3718
|
+
else if (isTanstackRouter) await setupTanStackRouterAlchemyDeploy(projectDir, packageManager);
|
|
3719
|
+
else if (isReactRouter) await setupReactRouterAlchemyDeploy(projectDir, packageManager);
|
|
3720
|
+
else if (isSolid) await setupSolidAlchemyDeploy(projectDir, packageManager);
|
|
3721
|
+
await addAlchemyPackagesDependencies(projectDir);
|
|
3996
3722
|
}
|
|
3997
3723
|
async function addAlchemyPackagesDependencies(projectDir) {
|
|
3998
3724
|
await addPackageDependency({
|
|
@@ -4853,7 +4579,7 @@ ${hasWeb ? "# npx convex env set SITE_URL http://localhost:3001\n" : ""}
|
|
|
4853
4579
|
databaseUrl = "mongodb://localhost:27017/mydatabase";
|
|
4854
4580
|
break;
|
|
4855
4581
|
case "sqlite":
|
|
4856
|
-
if (config.runtime === "workers" || webDeploy === "
|
|
4582
|
+
if (config.runtime === "workers" || webDeploy === "alchemy" || serverDeploy === "alchemy") databaseUrl = "http://127.0.0.1:8080";
|
|
4857
4583
|
else {
|
|
4858
4584
|
const dbAppDir = backend === "self" ? "apps/web" : "apps/server";
|
|
4859
4585
|
databaseUrl = `file:${path.join(config.projectDir, dbAppDir, "local.db")}`;
|
|
@@ -4935,31 +4661,7 @@ ${hasWeb ? "# npx convex env set SITE_URL http://localhost:3001\n" : ""}
|
|
|
4935
4661
|
//#region src/helpers/database-providers/d1-setup.ts
|
|
4936
4662
|
async function setupCloudflareD1(config) {
|
|
4937
4663
|
const { projectDir, serverDeploy, orm, backend } = config;
|
|
4938
|
-
if (serverDeploy === "
|
|
4939
|
-
const targetApp = backend === "self" ? "apps/web" : "apps/server";
|
|
4940
|
-
const envPath = path.join(projectDir, targetApp, ".env");
|
|
4941
|
-
const variables = [
|
|
4942
|
-
{
|
|
4943
|
-
key: "CLOUDFLARE_ACCOUNT_ID",
|
|
4944
|
-
value: "",
|
|
4945
|
-
condition: true
|
|
4946
|
-
},
|
|
4947
|
-
{
|
|
4948
|
-
key: "CLOUDFLARE_DATABASE_ID",
|
|
4949
|
-
value: "",
|
|
4950
|
-
condition: true
|
|
4951
|
-
},
|
|
4952
|
-
{
|
|
4953
|
-
key: "CLOUDFLARE_D1_TOKEN",
|
|
4954
|
-
value: "",
|
|
4955
|
-
condition: true
|
|
4956
|
-
}
|
|
4957
|
-
];
|
|
4958
|
-
try {
|
|
4959
|
-
await addEnvVariablesToFile(envPath, variables);
|
|
4960
|
-
} catch (_err) {}
|
|
4961
|
-
}
|
|
4962
|
-
if ((serverDeploy === "wrangler" || serverDeploy === "alchemy") && orm === "prisma") {
|
|
4664
|
+
if (serverDeploy === "alchemy" && orm === "prisma") {
|
|
4963
4665
|
const targetApp2 = backend === "self" ? "apps/web" : "apps/server";
|
|
4964
4666
|
const envPath = path.join(projectDir, targetApp2, ".env");
|
|
4965
4667
|
const variables = [{
|
|
@@ -5951,40 +5653,60 @@ async function setupDatabase(config, cliInput) {
|
|
|
5951
5653
|
}
|
|
5952
5654
|
const s = spinner();
|
|
5953
5655
|
const dbPackageDir = path.join(projectDir, "packages/db");
|
|
5656
|
+
const webDir = path.join(projectDir, "apps/web");
|
|
5954
5657
|
if (!await fs.pathExists(dbPackageDir)) return;
|
|
5955
5658
|
try {
|
|
5956
5659
|
if (orm === "prisma") {
|
|
5957
|
-
if (database === "
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
"@prisma/adapter-planetscale",
|
|
5961
|
-
"@planetscale/database"
|
|
5962
|
-
],
|
|
5963
|
-
devDependencies: ["prisma"],
|
|
5660
|
+
if (database === "mongodb") await addPackageDependency({
|
|
5661
|
+
customDependencies: { "@prisma/client": "6.19.0" },
|
|
5662
|
+
customDevDependencies: { prisma: "6.19.0" },
|
|
5964
5663
|
projectDir: dbPackageDir
|
|
5965
5664
|
});
|
|
5966
|
-
else
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5665
|
+
else {
|
|
5666
|
+
const prismaDependencies = ["@prisma/client"];
|
|
5667
|
+
const prismaDevDependencies = ["prisma"];
|
|
5668
|
+
if (database === "mysql" && dbSetup === "planetscale") prismaDependencies.push("@prisma/adapter-planetscale", "@planetscale/database");
|
|
5669
|
+
else if (database === "mysql") prismaDependencies.push("@prisma/adapter-mariadb");
|
|
5670
|
+
else if (database === "sqlite") if (dbSetup === "d1") prismaDependencies.push("@prisma/adapter-d1");
|
|
5671
|
+
else prismaDependencies.push("@prisma/adapter-libsql");
|
|
5672
|
+
else if (database === "postgres") if (dbSetup === "neon") {
|
|
5673
|
+
prismaDependencies.push("@prisma/adapter-neon", "@neondatabase/serverless", "ws");
|
|
5674
|
+
prismaDevDependencies.push("@types/ws");
|
|
5675
|
+
} else {
|
|
5676
|
+
prismaDependencies.push("@prisma/adapter-pg");
|
|
5677
|
+
prismaDependencies.push("pg");
|
|
5678
|
+
prismaDevDependencies.push("@types/pg");
|
|
5679
|
+
}
|
|
5680
|
+
await addPackageDependency({
|
|
5681
|
+
dependencies: prismaDependencies,
|
|
5682
|
+
devDependencies: prismaDevDependencies,
|
|
5683
|
+
projectDir: dbPackageDir
|
|
5684
|
+
});
|
|
5685
|
+
}
|
|
5686
|
+
if (await fs.pathExists(webDir)) if (database === "mongodb") await addPackageDependency({
|
|
5687
|
+
customDependencies: { "@prisma/client": "6.19.0" },
|
|
5688
|
+
projectDir: webDir
|
|
5970
5689
|
});
|
|
5971
5690
|
else await addPackageDependency({
|
|
5972
|
-
dependencies: ["@prisma/client"],
|
|
5973
|
-
devDependencies: ["prisma"],
|
|
5974
|
-
projectDir: dbPackageDir
|
|
5975
|
-
});
|
|
5976
|
-
const webDir = path.join(projectDir, "apps/web");
|
|
5977
|
-
if (await fs.pathExists(webDir)) await addPackageDependency({
|
|
5978
5691
|
dependencies: ["@prisma/client"],
|
|
5979
5692
|
projectDir: webDir
|
|
5980
5693
|
});
|
|
5981
5694
|
} else if (orm === "drizzle") {
|
|
5982
|
-
if (database === "sqlite")
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
|
|
5695
|
+
if (database === "sqlite") {
|
|
5696
|
+
await addPackageDependency({
|
|
5697
|
+
dependencies: [
|
|
5698
|
+
"drizzle-orm",
|
|
5699
|
+
"@libsql/client",
|
|
5700
|
+
"libsql"
|
|
5701
|
+
],
|
|
5702
|
+
devDependencies: ["drizzle-kit"],
|
|
5703
|
+
projectDir: dbPackageDir
|
|
5704
|
+
});
|
|
5705
|
+
await addPackageDependency({
|
|
5706
|
+
dependencies: ["@libsql/client", "libsql"],
|
|
5707
|
+
projectDir: webDir
|
|
5708
|
+
});
|
|
5709
|
+
} else if (database === "postgres") if (dbSetup === "neon") await addPackageDependency({
|
|
5988
5710
|
dependencies: [
|
|
5989
5711
|
"drizzle-orm",
|
|
5990
5712
|
"@neondatabase/serverless",
|
|
@@ -6303,7 +6025,7 @@ function generateFeaturesList(database, auth, addons, orm, runtime, frontend, ba
|
|
|
6303
6025
|
else if (addon === "turborepo") addonsList.push("- **Turborepo** - Optimized monorepo build system");
|
|
6304
6026
|
return addonsList.join("\n");
|
|
6305
6027
|
}
|
|
6306
|
-
function generateDatabaseSetup(database, _auth, packageManagerRunCmd, orm, dbSetup,
|
|
6028
|
+
function generateDatabaseSetup(database, _auth, packageManagerRunCmd, orm, dbSetup, _serverDeploy, backend) {
|
|
6307
6029
|
if (database === "none") return "";
|
|
6308
6030
|
const isBackendSelf = backend === "self";
|
|
6309
6031
|
const envPath = isBackendSelf ? "apps/web/.env" : "apps/server/.env";
|
|
@@ -6312,7 +6034,7 @@ function generateDatabaseSetup(database, _auth, packageManagerRunCmd, orm, dbSet
|
|
|
6312
6034
|
if (database === "sqlite") setup += `This project uses SQLite${orm === "drizzle" ? " with Drizzle ORM" : orm === "prisma" ? " with Prisma" : ` with ${orm}`}.
|
|
6313
6035
|
|
|
6314
6036
|
1. Start the local SQLite database:
|
|
6315
|
-
${dbSetup === "d1" ?
|
|
6037
|
+
${dbSetup === "d1" ? "D1 local development and migrations are handled automatically by Alchemy during dev and deploy." : `\`\`\`bash
|
|
6316
6038
|
cd ${dbLocalPath} && ${packageManagerRunCmd} db:local
|
|
6317
6039
|
\`\`\`
|
|
6318
6040
|
`}
|
|
@@ -6410,11 +6132,6 @@ function generateDeploymentCommands(packageManagerRunCmd, webDeploy, serverDeplo
|
|
|
6410
6132
|
if (serverDeploy === "alchemy" && webDeploy !== "alchemy") lines.push(`- Server dev: cd apps/server && ${packageManagerRunCmd} dev`, `- Server deploy: cd apps/server && ${packageManagerRunCmd} deploy`, `- Server destroy: cd apps/server && ${packageManagerRunCmd} destroy`);
|
|
6411
6133
|
if (webDeploy === "alchemy" && serverDeploy === "alchemy") lines.push(`- Dev: ${packageManagerRunCmd} dev`, `- Deploy: ${packageManagerRunCmd} deploy`, `- Destroy: ${packageManagerRunCmd} destroy`);
|
|
6412
6134
|
}
|
|
6413
|
-
if (webDeploy === "wrangler" || serverDeploy === "wrangler") {
|
|
6414
|
-
lines.push("\n## Deployment (Cloudflare Wrangler)");
|
|
6415
|
-
if (webDeploy === "wrangler") lines.push(`- Web deploy: cd apps/web && ${packageManagerRunCmd} deploy`);
|
|
6416
|
-
if (serverDeploy === "wrangler") lines.push(`- Server dev: cd apps/server && ${packageManagerRunCmd} dev`, `- Server deploy: cd apps/server && ${packageManagerRunCmd} deploy`);
|
|
6417
|
-
}
|
|
6418
6135
|
return lines.length ? `\n${lines.join("\n")}\n` : "";
|
|
6419
6136
|
}
|
|
6420
6137
|
|
|
@@ -6538,7 +6255,6 @@ async function displayPostInstallInstructions(config) {
|
|
|
6538
6255
|
const starlightInstructions = addons?.includes("starlight") ? getStarlightInstructions(runCmd) : "";
|
|
6539
6256
|
const clerkInstructions = isConvex && config.auth === "clerk" ? getClerkInstructions() : "";
|
|
6540
6257
|
const polarInstructions = config.payments === "polar" && config.auth === "better-auth" ? getPolarInstructions(backend) : "";
|
|
6541
|
-
const wranglerDeployInstructions = getWranglerDeployInstructions(runCmd, webDeploy, serverDeploy, backend);
|
|
6542
6258
|
const alchemyDeployInstructions = getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy, backend);
|
|
6543
6259
|
const hasWeb = frontend?.some((f) => [
|
|
6544
6260
|
"tanstack-router",
|
|
@@ -6558,7 +6274,7 @@ async function displayPostInstallInstructions(config) {
|
|
|
6558
6274
|
let output = `${pc.bold("Next steps")}\n${pc.cyan("1.")} ${cdCmd}\n`;
|
|
6559
6275
|
let stepCounter = 2;
|
|
6560
6276
|
if (!depsInstalled) output += `${pc.cyan(`${stepCounter++}.`)} ${packageManager} install\n`;
|
|
6561
|
-
if (database === "sqlite" && dbSetup === "none" && (serverDeploy === "
|
|
6277
|
+
if (database === "sqlite" && dbSetup === "none" && (serverDeploy === "alchemy" || webDeploy === "alchemy")) output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} db:local\n${pc.dim(" (starts local SQLite server for Workers compatibility)")}\n`;
|
|
6562
6278
|
if (isConvex) {
|
|
6563
6279
|
output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev:setup\n${pc.dim(" (this will guide you through Convex project setup)")}\n`;
|
|
6564
6280
|
output += `${pc.cyan(`${stepCounter++}.`)} Copy environment variables from\n${pc.white(" packages/backend/.env.local")} to ${pc.white("apps/*/.env")}\n`;
|
|
@@ -6569,7 +6285,6 @@ async function displayPostInstallInstructions(config) {
|
|
|
6569
6285
|
if (runtime === "workers") {
|
|
6570
6286
|
if (dbSetup === "d1") output += `${pc.yellow("IMPORTANT:")} Complete D1 database setup first\n (see Database commands below)\n`;
|
|
6571
6287
|
output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev\n`;
|
|
6572
|
-
if (serverDeploy === "wrangler") output += `${pc.cyan(`${stepCounter++}.`)} cd apps/server && ${runCmd} cf-typegen\n`;
|
|
6573
6288
|
}
|
|
6574
6289
|
}
|
|
6575
6290
|
output += `${pc.bold("Your project will be available at:")}\n`;
|
|
@@ -6587,7 +6302,6 @@ async function displayPostInstallInstructions(config) {
|
|
|
6587
6302
|
if (tauriInstructions) output += `\n${tauriInstructions.trim()}\n`;
|
|
6588
6303
|
if (lintingInstructions) output += `\n${lintingInstructions.trim()}\n`;
|
|
6589
6304
|
if (pwaInstructions) output += `\n${pwaInstructions.trim()}\n`;
|
|
6590
|
-
if (wranglerDeployInstructions) output += `\n${wranglerDeployInstructions.trim()}\n`;
|
|
6591
6305
|
if (alchemyDeployInstructions) output += `\n${alchemyDeployInstructions.trim()}\n`;
|
|
6592
6306
|
if (starlightInstructions) output += `\n${starlightInstructions.trim()}\n`;
|
|
6593
6307
|
if (clerkInstructions) output += `\n${clerkInstructions.trim()}\n`;
|
|
@@ -6610,7 +6324,7 @@ function getNativeInstructions(isConvex, isBackendSelf, _frontend) {
|
|
|
6610
6324
|
function getLintingInstructions(runCmd) {
|
|
6611
6325
|
return `${pc.bold("Linting and formatting:")}\n${pc.cyan("•")} Format and lint fix: ${`${runCmd} check`}\n`;
|
|
6612
6326
|
}
|
|
6613
|
-
async function getDatabaseInstructions(database, orm, runCmd,
|
|
6327
|
+
async function getDatabaseInstructions(database, orm, runCmd, _runtime, dbSetup, serverDeploy, _backend) {
|
|
6614
6328
|
const instructions = [];
|
|
6615
6329
|
if (dbSetup === "docker") {
|
|
6616
6330
|
const dockerStatus = await getDockerStatus(database);
|
|
@@ -6619,21 +6333,10 @@ async function getDatabaseInstructions(database, orm, runCmd, runtime, dbSetup,
|
|
|
6619
6333
|
instructions.push("");
|
|
6620
6334
|
}
|
|
6621
6335
|
}
|
|
6622
|
-
if (serverDeploy === "wrangler" && dbSetup === "d1") {
|
|
6623
|
-
if (orm === "prisma" && runtime === "workers") instructions.push(`\n${pc.yellow("WARNING:")} Prisma + D1 on Workers with Wrangler has migration issues.\n Consider using Alchemy deploy instead of Wrangler for D1 projects.\n`);
|
|
6624
|
-
const packageManager = runCmd === "npm run" ? "npm" : runCmd || "npm";
|
|
6625
|
-
instructions.push(`${pc.cyan("1.")} Login to Cloudflare: ${pc.white(`${packageManager} wrangler login`)}`);
|
|
6626
|
-
instructions.push(`${pc.cyan("2.")} Create D1 database: ${pc.white(`${packageManager} wrangler d1 create your-database-name`)}`);
|
|
6627
|
-
const wranglerPath = backend === "self" ? "apps/web" : "apps/server";
|
|
6628
|
-
instructions.push(`${pc.cyan("3.")} Update ${wranglerPath}/wrangler.jsonc with database_id and database_name`);
|
|
6629
|
-
instructions.push(`${pc.cyan("4.")} Generate migrations: ${pc.white(`cd ${wranglerPath} && ${runCmd} db:generate`)}`);
|
|
6630
|
-
instructions.push(`${pc.cyan("5.")} Apply migrations locally: ${pc.white(`${packageManager} wrangler d1 migrations apply YOUR_DB_NAME --local`)}`);
|
|
6631
|
-
instructions.push(`${pc.cyan("6.")} Apply migrations to production: ${pc.white(`${packageManager} wrangler d1 migrations apply YOUR_DB_NAME`)}`);
|
|
6632
|
-
}
|
|
6633
6336
|
if (dbSetup === "d1" && serverDeploy === "alchemy") {
|
|
6634
|
-
if (orm === "drizzle") instructions.push(`${pc.
|
|
6337
|
+
if (orm === "drizzle") instructions.push(`${pc.cyan("•")} Generate migrations: ${`${runCmd} db:generate`}`);
|
|
6635
6338
|
else if (orm === "prisma") {
|
|
6636
|
-
instructions.push(`${pc.cyan("•")} Generate
|
|
6339
|
+
instructions.push(`${pc.cyan("•")} Generate Prisma client: ${`${runCmd} db:generate`}`);
|
|
6637
6340
|
instructions.push(`${pc.cyan("•")} Apply migrations: ${`${runCmd} db:migrate`}`);
|
|
6638
6341
|
}
|
|
6639
6342
|
}
|
|
@@ -6641,10 +6344,14 @@ async function getDatabaseInstructions(database, orm, runCmd, runtime, dbSetup,
|
|
|
6641
6344
|
if (database === "mysql" && orm === "drizzle") instructions.push(`${pc.yellow("NOTE:")} Enable foreign key constraints in PlanetScale database settings`);
|
|
6642
6345
|
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`);
|
|
6643
6346
|
}
|
|
6347
|
+
if (dbSetup === "turso" && orm === "prisma") instructions.push(`${pc.yellow("NOTE:")} Follow Turso's Prisma guide for migrations via the Turso CLI:\n https://docs.turso.tech/sdk/ts/orm/prisma`);
|
|
6644
6348
|
if (orm === "prisma") {
|
|
6645
6349
|
if (database === "mongodb" && dbSetup === "docker") instructions.push(`${pc.yellow("WARNING:")} Prisma + MongoDB + Docker combination\n may not work.`);
|
|
6646
6350
|
if (dbSetup === "docker") instructions.push(`${pc.cyan("•")} Start docker container: ${`${runCmd} db:start`}`);
|
|
6647
|
-
if (!(dbSetup === "d1" && serverDeploy === "alchemy"))
|
|
6351
|
+
if (!(dbSetup === "d1" && serverDeploy === "alchemy")) {
|
|
6352
|
+
instructions.push(`${pc.cyan("•")} Generate Prisma Client: ${`${runCmd} db:generate`}`);
|
|
6353
|
+
instructions.push(`${pc.cyan("•")} Apply schema: ${`${runCmd} db:push`}`);
|
|
6354
|
+
}
|
|
6648
6355
|
if (!(dbSetup === "d1" && serverDeploy === "alchemy")) instructions.push(`${pc.cyan("•")} Database UI: ${`${runCmd} db:studio`}`);
|
|
6649
6356
|
} else if (orm === "drizzle") {
|
|
6650
6357
|
if (dbSetup === "docker") instructions.push(`${pc.cyan("•")} Start docker container: ${`${runCmd} db:start`}`);
|
|
@@ -6670,15 +6377,6 @@ function getNoOrmWarning() {
|
|
|
6670
6377
|
function getBunWebNativeWarning() {
|
|
6671
6378
|
return `\n${pc.yellow("WARNING:")} 'bun' might cause issues with web + native apps in a monorepo.\n Use 'pnpm' if problems arise.`;
|
|
6672
6379
|
}
|
|
6673
|
-
function getWranglerDeployInstructions(runCmd, webDeploy, serverDeploy, backend) {
|
|
6674
|
-
const instructions = [];
|
|
6675
|
-
if (webDeploy === "wrangler") {
|
|
6676
|
-
const deployPath = backend === "self" ? "apps/web" : "apps/web";
|
|
6677
|
-
instructions.push(`${pc.bold("Deploy web to Cloudflare Workers:")}\n${pc.cyan("•")} Deploy: ${`cd ${deployPath} && ${runCmd} deploy`}`);
|
|
6678
|
-
}
|
|
6679
|
-
if (serverDeploy === "wrangler" && backend !== "self") instructions.push(`${pc.bold("Deploy server to Cloudflare Workers:")}\n${pc.cyan("•")} Deploy: ${`cd apps/server && ${runCmd} deploy`}`);
|
|
6680
|
-
return instructions.length ? `\n${instructions.join("\n")}` : "";
|
|
6681
|
-
}
|
|
6682
6380
|
function getClerkInstructions() {
|
|
6683
6381
|
return `${pc.bold("Clerk Authentication Setup:")}\n${pc.cyan("•")} Follow the guide: ${pc.underline("https://docs.convex.dev/auth/clerk")}\n${pc.cyan("•")} Set CLERK_JWT_ISSUER_DOMAIN in Convex Dashboard\n${pc.cyan("•")} Set CLERK_PUBLISHABLE_KEY in apps/*/.env`;
|
|
6684
6382
|
}
|
|
@@ -6689,7 +6387,7 @@ function getPolarInstructions(backend) {
|
|
|
6689
6387
|
function getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy, backend) {
|
|
6690
6388
|
const instructions = [];
|
|
6691
6389
|
const isBackendSelf = backend === "self";
|
|
6692
|
-
if (webDeploy === "alchemy" && serverDeploy !== "alchemy") instructions.push(`${pc.bold("Deploy web with Alchemy:")}\n${pc.cyan("•")} Dev: ${`cd apps/web && ${runCmd} dev`}\n${pc.cyan("•")} Deploy: ${`cd apps/web && ${runCmd} deploy`}\n${pc.cyan("•")} Destroy: ${`cd apps/web && ${runCmd} destroy`}`);
|
|
6390
|
+
if (webDeploy === "alchemy" && serverDeploy !== "alchemy") instructions.push(`${pc.bold("Deploy web with Alchemy:")}\n${pc.cyan("•")} Dev: ${`cd apps/web && ${runCmd} alchemy dev`}\n${pc.cyan("•")} Deploy: ${`cd apps/web && ${runCmd} deploy`}\n${pc.cyan("•")} Destroy: ${`cd apps/web && ${runCmd} destroy`}`);
|
|
6693
6391
|
else if (serverDeploy === "alchemy" && webDeploy !== "alchemy" && !isBackendSelf) instructions.push(`${pc.bold("Deploy server with Alchemy:")}\n${pc.cyan("•")} Dev: ${`cd apps/server && ${runCmd} dev`}\n${pc.cyan("•")} Deploy: ${`cd apps/server && ${runCmd} deploy`}\n${pc.cyan("•")} Destroy: ${`cd apps/server && ${runCmd} destroy`}`);
|
|
6694
6392
|
else if (webDeploy === "alchemy" && (serverDeploy === "alchemy" || isBackendSelf)) instructions.push(`${pc.bold("Deploy with Alchemy:")}\n${pc.cyan("•")} Dev: ${`${runCmd} dev`}\n${pc.cyan("•")} Deploy: ${`${runCmd} deploy`}\n${pc.cyan("•")} Destroy: ${`${runCmd} destroy`}`);
|
|
6695
6393
|
return instructions.length ? `\n${instructions.join("\n")}` : "";
|
|
@@ -6701,7 +6399,7 @@ async function setupWorkspaceDependencies(projectDir, options) {
|
|
|
6701
6399
|
const projectName = options.projectName;
|
|
6702
6400
|
const workspaceVersion = options.packageManager === "npm" ? "*" : "workspace:*";
|
|
6703
6401
|
const commonDeps = ["dotenv", "zod"];
|
|
6704
|
-
const commonDevDeps = [
|
|
6402
|
+
const commonDevDeps = [];
|
|
6705
6403
|
const configPackageDir = path.join(projectDir, "packages/config");
|
|
6706
6404
|
const configDep = {};
|
|
6707
6405
|
if (await fs.pathExists(configPackageDir)) configDep[`@${projectName}/config`] = workspaceVersion;
|
|
@@ -6745,7 +6443,7 @@ async function setupWorkspaceDependencies(projectDir, options) {
|
|
|
6745
6443
|
if (options.database !== "none" && await fs.pathExists(dbPackageDir)) serverDeps[`@${projectName}/db`] = workspaceVersion;
|
|
6746
6444
|
await addPackageDependency({
|
|
6747
6445
|
dependencies: commonDeps,
|
|
6748
|
-
devDependencies: commonDevDeps,
|
|
6446
|
+
devDependencies: [...commonDevDeps, "tsdown"],
|
|
6749
6447
|
customDependencies: serverDeps,
|
|
6750
6448
|
customDevDependencies: configDep,
|
|
6751
6449
|
projectDir: serverPackageDir
|