create-better-t-stack 2.33.6 → 2.33.8-canary.98a850ad-canary.98a850ad-canary.98a850ad-canary.98a850ad-canary.98a850ad
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/README.md +2 -1
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +17 -4
- package/dist/index.js +1 -1
- package/dist/{src-CcycH-Mi.js → src-D0MPVT22.js} +1303 -559
- package/package.json +10 -7
- package/templates/addons/biome/biome.json.hbs +1 -0
- package/templates/addons/ruler/.ruler/mcp.json.hbs +1 -1
- package/templates/addons/ultracite/biome.json.hbs +1 -0
- package/templates/api/orpc/native/utils/orpc.ts.hbs +2 -3
- package/templates/api/orpc/web/nuxt/app/plugins/orpc.ts.hbs +2 -3
- package/templates/api/orpc/web/react/base/src/utils/orpc.ts.hbs +2 -3
- package/templates/api/orpc/web/solid/src/utils/orpc.ts.hbs +2 -3
- package/templates/api/orpc/web/svelte/src/lib/orpc.ts.hbs +2 -3
- package/templates/auth/server/base/src/lib/auth.ts.hbs +37 -4
- package/templates/backend/server/server-base/_gitignore +1 -0
- package/templates/backend/server/server-base/src/routers/index.ts.hbs +2 -0
- package/templates/backend/server/server-base/tsconfig.json.hbs +1 -1
- package/templates/base/_gitignore +2 -0
- package/templates/deploy/alchemy/alchemy.run.ts.hbs +200 -0
- package/templates/deploy/alchemy/env.d.ts.hbs +20 -0
- package/templates/deploy/{web → wrangler/web}/nuxt/wrangler.jsonc.hbs +1 -1
- package/templates/deploy/{web → wrangler/web}/react/next/wrangler.jsonc.hbs +1 -1
- package/templates/deploy/{web → wrangler/web}/react/react-router/wrangler.jsonc.hbs +1 -1
- package/templates/deploy/{web → wrangler/web}/react/tanstack-router/wrangler.jsonc.hbs +1 -1
- package/templates/deploy/{web → wrangler/web}/react/tanstack-start/wrangler.jsonc.hbs +1 -1
- package/templates/deploy/{web → wrangler/web}/solid/wrangler.jsonc.hbs +1 -1
- package/templates/deploy/{web → wrangler/web}/svelte/wrangler.jsonc.hbs +1 -1
- package/templates/frontend/nuxt/_gitignore +3 -0
- package/templates/frontend/nuxt/tsconfig.json.hbs +4 -4
- package/templates/frontend/react/tanstack-router/src/routes/__root.tsx.hbs +2 -3
- package/templates/frontend/react/web-base/_gitignore +1 -0
- package/templates/frontend/react/web-base/src/components/header.tsx.hbs +0 -1
- package/templates/frontend/solid/_gitignore +1 -0
- package/templates/frontend/solid/package.json.hbs +0 -1
- package/templates/frontend/svelte/_gitignore +1 -0
- package/templates/frontend/svelte/package.json.hbs +11 -13
- package/LICENSE +0 -21
- /package/templates/{runtime/workers/apps → deploy/wrangler}/server/wrangler.jsonc.hbs +0 -0
- /package/templates/deploy/{web → wrangler/web}/react/next/open-next.config.ts +0 -0
|
@@ -5,14 +5,16 @@ import { createCli, trpcServer } from "trpc-cli";
|
|
|
5
5
|
import z from "zod";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import consola, { consola as consola$1 } from "consola";
|
|
8
|
+
import * as fs$1 from "fs-extra";
|
|
8
9
|
import fs from "fs-extra";
|
|
9
10
|
import { fileURLToPath } from "node:url";
|
|
10
11
|
import gradient from "gradient-string";
|
|
11
12
|
import * as JSONC from "jsonc-parser";
|
|
12
13
|
import { $, execa } from "execa";
|
|
13
|
-
import {
|
|
14
|
+
import { glob } from "tinyglobby";
|
|
14
15
|
import handlebars from "handlebars";
|
|
15
16
|
import { IndentationText, Node, Project, QuoteKind, SyntaxKind } from "ts-morph";
|
|
17
|
+
import { Biome } from "@biomejs/js-api/nodejs";
|
|
16
18
|
import os from "node:os";
|
|
17
19
|
|
|
18
20
|
//#region src/utils/get-package-manager.ts
|
|
@@ -45,7 +47,8 @@ const DEFAULT_CONFIG = {
|
|
|
45
47
|
backend: "hono",
|
|
46
48
|
runtime: "bun",
|
|
47
49
|
api: "trpc",
|
|
48
|
-
webDeploy: "none"
|
|
50
|
+
webDeploy: "none",
|
|
51
|
+
serverDeploy: "none"
|
|
49
52
|
};
|
|
50
53
|
const dependencyVersionMap = {
|
|
51
54
|
"better-auth": "^1.3.4",
|
|
@@ -103,18 +106,24 @@ const dependencyVersionMap = {
|
|
|
103
106
|
"convex-svelte": "^0.0.11",
|
|
104
107
|
"convex-nuxt": "0.1.5",
|
|
105
108
|
"convex-vue": "^0.1.5",
|
|
106
|
-
"@tanstack/svelte-query": "^5.
|
|
109
|
+
"@tanstack/svelte-query": "^5.85.3",
|
|
110
|
+
"@tanstack/svelte-query-devtools": "^5.85.3",
|
|
107
111
|
"@tanstack/vue-query-devtools": "^5.83.0",
|
|
108
112
|
"@tanstack/vue-query": "^5.83.0",
|
|
109
113
|
"@tanstack/react-query-devtools": "^5.80.5",
|
|
110
114
|
"@tanstack/react-query": "^5.80.5",
|
|
111
115
|
"@tanstack/solid-query": "^5.75.0",
|
|
112
116
|
"@tanstack/solid-query-devtools": "^5.75.0",
|
|
117
|
+
"@tanstack/solid-router-devtools": "^1.131.25",
|
|
113
118
|
wrangler: "^4.23.0",
|
|
114
119
|
"@cloudflare/vite-plugin": "^1.9.0",
|
|
115
120
|
"@opennextjs/cloudflare": "^1.3.0",
|
|
116
121
|
"nitro-cloudflare-dev": "^0.2.2",
|
|
117
|
-
"@sveltejs/adapter-cloudflare": "^7.
|
|
122
|
+
"@sveltejs/adapter-cloudflare": "^7.2.1",
|
|
123
|
+
"@cloudflare/workers-types": "^4.20250813.0",
|
|
124
|
+
alchemy: "^0.62.1",
|
|
125
|
+
nitropack: "^2.12.4",
|
|
126
|
+
dotenv: "^17.2.1"
|
|
118
127
|
};
|
|
119
128
|
const ADDON_COMPATIBILITY = {
|
|
120
129
|
pwa: [
|
|
@@ -233,7 +242,16 @@ const ProjectNameSchema = z.string().min(1, "Project name cannot be empty").max(
|
|
|
233
242
|
];
|
|
234
243
|
return !invalidChars.some((char) => name.includes(char));
|
|
235
244
|
}, "Project name contains invalid characters").refine((name) => name.toLowerCase() !== "node_modules", "Project name is reserved").describe("Project name or path");
|
|
236
|
-
const WebDeploySchema = z.enum([
|
|
245
|
+
const WebDeploySchema = z.enum([
|
|
246
|
+
"wrangler",
|
|
247
|
+
"alchemy",
|
|
248
|
+
"none"
|
|
249
|
+
]).describe("Web deployment");
|
|
250
|
+
const ServerDeploySchema = z.enum([
|
|
251
|
+
"wrangler",
|
|
252
|
+
"alchemy",
|
|
253
|
+
"none"
|
|
254
|
+
]).describe("Server deployment");
|
|
237
255
|
const DirectoryConflictSchema = z.enum([
|
|
238
256
|
"merge",
|
|
239
257
|
"overwrite",
|
|
@@ -544,6 +562,9 @@ function isExampleAIAllowed(backend, frontends = []) {
|
|
|
544
562
|
function validateWebDeployRequiresWebFrontend(webDeploy, hasWebFrontendFlag) {
|
|
545
563
|
if (webDeploy && webDeploy !== "none" && !hasWebFrontendFlag) exitWithError("'--web-deploy' requires a web frontend. Please select a web frontend or set '--web-deploy none'.");
|
|
546
564
|
}
|
|
565
|
+
function validateServerDeployRequiresBackend(serverDeploy, backend) {
|
|
566
|
+
if (serverDeploy && serverDeploy !== "none" && (!backend || backend === "none")) exitWithError("'--server-deploy' requires a backend. Please select a backend or set '--server-deploy none'.");
|
|
567
|
+
}
|
|
547
568
|
|
|
548
569
|
//#endregion
|
|
549
570
|
//#region src/prompts/api.ts
|
|
@@ -1004,16 +1025,97 @@ async function getRuntimeChoice(runtime, backend) {
|
|
|
1004
1025
|
return response;
|
|
1005
1026
|
}
|
|
1006
1027
|
|
|
1028
|
+
//#endregion
|
|
1029
|
+
//#region src/prompts/server-deploy.ts
|
|
1030
|
+
function getDeploymentDisplay$1(deployment) {
|
|
1031
|
+
if (deployment === "wrangler") return {
|
|
1032
|
+
label: "Wrangler",
|
|
1033
|
+
hint: "Deploy to Cloudflare Workers using Wrangler"
|
|
1034
|
+
};
|
|
1035
|
+
if (deployment === "alchemy") return {
|
|
1036
|
+
label: "Alchemy",
|
|
1037
|
+
hint: "Deploy to Cloudflare Workers using Alchemy"
|
|
1038
|
+
};
|
|
1039
|
+
return {
|
|
1040
|
+
label: deployment,
|
|
1041
|
+
hint: `Add ${deployment} deployment`
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
async function getServerDeploymentChoice(deployment, runtime, backend, webDeploy) {
|
|
1045
|
+
if (deployment !== void 0) return deployment;
|
|
1046
|
+
if (backend === "none" || backend === "convex") return "none";
|
|
1047
|
+
const options = [];
|
|
1048
|
+
if (runtime === "workers") ["alchemy", "wrangler"].forEach((deploy) => {
|
|
1049
|
+
const { label, hint } = getDeploymentDisplay$1(deploy);
|
|
1050
|
+
options.unshift({
|
|
1051
|
+
value: deploy,
|
|
1052
|
+
label,
|
|
1053
|
+
hint
|
|
1054
|
+
});
|
|
1055
|
+
});
|
|
1056
|
+
else options.push({
|
|
1057
|
+
value: "none",
|
|
1058
|
+
label: "None",
|
|
1059
|
+
hint: "Manual setup"
|
|
1060
|
+
});
|
|
1061
|
+
const response = await select({
|
|
1062
|
+
message: "Select server deployment",
|
|
1063
|
+
options,
|
|
1064
|
+
initialValue: webDeploy === "alchemy" ? "alchemy" : runtime === "workers" ? "wrangler" : DEFAULT_CONFIG.serverDeploy
|
|
1065
|
+
});
|
|
1066
|
+
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
1067
|
+
return response;
|
|
1068
|
+
}
|
|
1069
|
+
async function getServerDeploymentToAdd(runtime, existingDeployment) {
|
|
1070
|
+
const options = [];
|
|
1071
|
+
if (runtime === "workers") {
|
|
1072
|
+
if (existingDeployment !== "wrangler") {
|
|
1073
|
+
const { label, hint } = getDeploymentDisplay$1("wrangler");
|
|
1074
|
+
options.push({
|
|
1075
|
+
value: "wrangler",
|
|
1076
|
+
label,
|
|
1077
|
+
hint
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
1080
|
+
if (existingDeployment !== "alchemy") {
|
|
1081
|
+
const { label, hint } = getDeploymentDisplay$1("alchemy");
|
|
1082
|
+
options.push({
|
|
1083
|
+
value: "alchemy",
|
|
1084
|
+
label,
|
|
1085
|
+
hint
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
if (existingDeployment && existingDeployment !== "none") return "none";
|
|
1090
|
+
if (options.length > 0) options.push({
|
|
1091
|
+
value: "none",
|
|
1092
|
+
label: "None",
|
|
1093
|
+
hint: "Skip deployment setup"
|
|
1094
|
+
});
|
|
1095
|
+
if (options.length === 0) return "none";
|
|
1096
|
+
const response = await select({
|
|
1097
|
+
message: "Select server deployment",
|
|
1098
|
+
options,
|
|
1099
|
+
initialValue: runtime === "workers" ? "wrangler" : DEFAULT_CONFIG.serverDeploy
|
|
1100
|
+
});
|
|
1101
|
+
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
1102
|
+
return response;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1007
1105
|
//#endregion
|
|
1008
1106
|
//#region src/prompts/web-deploy.ts
|
|
1009
1107
|
function hasWebFrontend(frontends) {
|
|
1010
1108
|
return frontends.some((f) => WEB_FRAMEWORKS.includes(f));
|
|
1011
1109
|
}
|
|
1012
1110
|
function getDeploymentDisplay(deployment) {
|
|
1013
|
-
if (deployment === "
|
|
1014
|
-
label: "
|
|
1111
|
+
if (deployment === "wrangler") return {
|
|
1112
|
+
label: "Wrangler",
|
|
1015
1113
|
hint: "Deploy to Cloudflare Workers using Wrangler"
|
|
1016
1114
|
};
|
|
1115
|
+
if (deployment === "alchemy") return {
|
|
1116
|
+
label: "Alchemy",
|
|
1117
|
+
hint: "Deploy to Cloudflare Workers using Alchemy"
|
|
1118
|
+
};
|
|
1017
1119
|
return {
|
|
1018
1120
|
label: deployment,
|
|
1019
1121
|
hint: `Add ${deployment} deployment`
|
|
@@ -1022,15 +1124,18 @@ function getDeploymentDisplay(deployment) {
|
|
|
1022
1124
|
async function getDeploymentChoice(deployment, _runtime, _backend, frontend = []) {
|
|
1023
1125
|
if (deployment !== void 0) return deployment;
|
|
1024
1126
|
if (!hasWebFrontend(frontend)) return "none";
|
|
1025
|
-
const options = [
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1127
|
+
const options = [
|
|
1128
|
+
"wrangler",
|
|
1129
|
+
"alchemy",
|
|
1130
|
+
"none"
|
|
1131
|
+
].map((deploy) => {
|
|
1132
|
+
const { label, hint } = getDeploymentDisplay(deploy);
|
|
1133
|
+
return {
|
|
1134
|
+
value: deploy,
|
|
1135
|
+
label,
|
|
1136
|
+
hint
|
|
1137
|
+
};
|
|
1138
|
+
});
|
|
1034
1139
|
const response = await select({
|
|
1035
1140
|
message: "Select web deployment",
|
|
1036
1141
|
options,
|
|
@@ -1042,10 +1147,18 @@ async function getDeploymentChoice(deployment, _runtime, _backend, frontend = []
|
|
|
1042
1147
|
async function getDeploymentToAdd(frontend, existingDeployment) {
|
|
1043
1148
|
if (!hasWebFrontend(frontend)) return "none";
|
|
1044
1149
|
const options = [];
|
|
1045
|
-
if (existingDeployment !== "
|
|
1046
|
-
const { label, hint } = getDeploymentDisplay("
|
|
1150
|
+
if (existingDeployment !== "wrangler") {
|
|
1151
|
+
const { label, hint } = getDeploymentDisplay("wrangler");
|
|
1047
1152
|
options.push({
|
|
1048
|
-
value: "
|
|
1153
|
+
value: "wrangler",
|
|
1154
|
+
label,
|
|
1155
|
+
hint
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1158
|
+
if (existingDeployment !== "alchemy") {
|
|
1159
|
+
const { label, hint } = getDeploymentDisplay("alchemy");
|
|
1160
|
+
options.push({
|
|
1161
|
+
value: "alchemy",
|
|
1049
1162
|
label,
|
|
1050
1163
|
hint
|
|
1051
1164
|
});
|
|
@@ -1081,6 +1194,7 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
|
|
|
1081
1194
|
examples: ({ results }) => getExamplesChoice(flags.examples, results.database, results.frontend, results.backend, results.api),
|
|
1082
1195
|
dbSetup: ({ results }) => getDBSetupChoice(results.database ?? "none", flags.dbSetup, results.orm, results.backend, results.runtime),
|
|
1083
1196
|
webDeploy: ({ results }) => getDeploymentChoice(flags.webDeploy, results.runtime, results.backend, results.frontend),
|
|
1197
|
+
serverDeploy: ({ results }) => getServerDeploymentChoice(flags.serverDeploy, results.runtime, results.backend, results.webDeploy),
|
|
1084
1198
|
git: () => getGitChoice(flags.git),
|
|
1085
1199
|
packageManager: () => getPackageManagerChoice(flags.packageManager),
|
|
1086
1200
|
install: () => getinstallChoice(flags.install)
|
|
@@ -1120,7 +1234,8 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
|
|
|
1120
1234
|
install: result.install,
|
|
1121
1235
|
dbSetup: result.dbSetup,
|
|
1122
1236
|
api: result.api,
|
|
1123
|
-
webDeploy: result.webDeploy
|
|
1237
|
+
webDeploy: result.webDeploy,
|
|
1238
|
+
serverDeploy: result.serverDeploy
|
|
1124
1239
|
};
|
|
1125
1240
|
}
|
|
1126
1241
|
|
|
@@ -1152,7 +1267,7 @@ async function getProjectName(initialName) {
|
|
|
1152
1267
|
let projectPath = "";
|
|
1153
1268
|
let defaultName = DEFAULT_CONFIG.projectName;
|
|
1154
1269
|
let counter = 1;
|
|
1155
|
-
while (fs.
|
|
1270
|
+
while (await fs.pathExists(path.resolve(process.cwd(), defaultName)) && (await fs.readdir(path.resolve(process.cwd(), defaultName))).length > 0) {
|
|
1156
1271
|
defaultName = `${DEFAULT_CONFIG.projectName}-${counter}`;
|
|
1157
1272
|
counter++;
|
|
1158
1273
|
}
|
|
@@ -1183,9 +1298,9 @@ async function getProjectName(initialName) {
|
|
|
1183
1298
|
|
|
1184
1299
|
//#endregion
|
|
1185
1300
|
//#region src/utils/get-latest-cli-version.ts
|
|
1186
|
-
const getLatestCLIVersion = () => {
|
|
1301
|
+
const getLatestCLIVersion = async () => {
|
|
1187
1302
|
const packageJsonPath = path.join(PKG_ROOT, "package.json");
|
|
1188
|
-
const packageJsonContent = fs.
|
|
1303
|
+
const packageJsonContent = await fs.readJSON(packageJsonPath);
|
|
1189
1304
|
return packageJsonContent.version ?? "1.0.0";
|
|
1190
1305
|
};
|
|
1191
1306
|
|
|
@@ -1209,9 +1324,14 @@ function isTelemetryEnabled() {
|
|
|
1209
1324
|
//#region src/utils/analytics.ts
|
|
1210
1325
|
const POSTHOG_API_KEY = "random";
|
|
1211
1326
|
const POSTHOG_HOST = "random";
|
|
1327
|
+
function generateSessionId() {
|
|
1328
|
+
const rand = Math.random().toString(36).slice(2);
|
|
1329
|
+
const now = Date.now().toString(36);
|
|
1330
|
+
return `cli_${now}${rand}`;
|
|
1331
|
+
}
|
|
1212
1332
|
async function trackProjectCreation(config, disableAnalytics = false) {
|
|
1213
1333
|
if (!isTelemetryEnabled() || disableAnalytics) return;
|
|
1214
|
-
const sessionId =
|
|
1334
|
+
const sessionId = generateSessionId();
|
|
1215
1335
|
const { projectName, projectDir, relativePath,...safeConfig } = config;
|
|
1216
1336
|
const payload = {
|
|
1217
1337
|
api_key: POSTHOG_API_KEY,
|
|
@@ -1219,8 +1339,8 @@ async function trackProjectCreation(config, disableAnalytics = false) {
|
|
|
1219
1339
|
properties: {
|
|
1220
1340
|
...safeConfig,
|
|
1221
1341
|
cli_version: getLatestCLIVersion(),
|
|
1222
|
-
node_version: process.version,
|
|
1223
|
-
platform: process.platform,
|
|
1342
|
+
node_version: typeof process !== "undefined" ? process.version : "",
|
|
1343
|
+
platform: typeof process !== "undefined" ? process.platform : "",
|
|
1224
1344
|
$ip: null
|
|
1225
1345
|
},
|
|
1226
1346
|
distinct_id: sessionId
|
|
@@ -1274,6 +1394,7 @@ function displayConfig(config) {
|
|
|
1274
1394
|
}
|
|
1275
1395
|
if (config.dbSetup !== void 0) configDisplay.push(`${pc.blue("Database Setup:")} ${String(config.dbSetup)}`);
|
|
1276
1396
|
if (config.webDeploy !== void 0) configDisplay.push(`${pc.blue("Web Deployment:")} ${String(config.webDeploy)}`);
|
|
1397
|
+
if (config.serverDeploy !== void 0) configDisplay.push(`${pc.blue("Server Deployment:")} ${String(config.serverDeploy)}`);
|
|
1277
1398
|
if (configDisplay.length === 0) return pc.yellow("No configuration selected.");
|
|
1278
1399
|
return configDisplay.join("\n");
|
|
1279
1400
|
}
|
|
@@ -1296,6 +1417,7 @@ function generateReproducibleCommand(config) {
|
|
|
1296
1417
|
else flags.push("--examples none");
|
|
1297
1418
|
flags.push(`--db-setup ${config.dbSetup}`);
|
|
1298
1419
|
flags.push(`--web-deploy ${config.webDeploy}`);
|
|
1420
|
+
flags.push(`--server-deploy ${config.serverDeploy}`);
|
|
1299
1421
|
flags.push(config.git ? "--git" : "--no-git");
|
|
1300
1422
|
flags.push(`--package-manager ${config.packageManager}`);
|
|
1301
1423
|
flags.push(config.install ? "--install" : "--no-install");
|
|
@@ -1313,8 +1435,8 @@ function generateReproducibleCommand(config) {
|
|
|
1313
1435
|
async function handleDirectoryConflict(currentPathInput, silent = false) {
|
|
1314
1436
|
while (true) {
|
|
1315
1437
|
const resolvedPath = path.resolve(process.cwd(), currentPathInput);
|
|
1316
|
-
const dirExists = fs.
|
|
1317
|
-
const dirIsNotEmpty = dirExists && fs.
|
|
1438
|
+
const dirExists = await fs.pathExists(resolvedPath);
|
|
1439
|
+
const dirIsNotEmpty = dirExists && (await fs.readdir(resolvedPath)).length > 0;
|
|
1318
1440
|
if (!dirIsNotEmpty) return {
|
|
1319
1441
|
finalPathInput: currentPathInput,
|
|
1320
1442
|
shouldClearDirectory: false
|
|
@@ -1476,6 +1598,7 @@ function processAndValidateFlags(options, providedFlags, projectName) {
|
|
|
1476
1598
|
if (options.dbSetup) config.dbSetup = options.dbSetup;
|
|
1477
1599
|
if (options.packageManager) config.packageManager = options.packageManager;
|
|
1478
1600
|
if (options.webDeploy) config.webDeploy = options.webDeploy;
|
|
1601
|
+
if (options.serverDeploy) config.serverDeploy = options.serverDeploy;
|
|
1479
1602
|
const derivedName = deriveProjectName(projectName, options.projectDirectory);
|
|
1480
1603
|
if (derivedName) {
|
|
1481
1604
|
const nameToValidate = projectName ? path.basename(projectName) : derivedName;
|
|
@@ -1536,6 +1659,7 @@ function processAndValidateFlags(options, providedFlags, projectName) {
|
|
|
1536
1659
|
validateWorkersCompatibility(providedFlags, options, config);
|
|
1537
1660
|
const hasWebFrontendFlag = (config.frontend ?? []).some((f) => isWebFrontend(f));
|
|
1538
1661
|
validateWebDeployRequiresWebFrontend(config.webDeploy, hasWebFrontendFlag);
|
|
1662
|
+
validateServerDeployRequiresBackend(config.serverDeploy, config.backend);
|
|
1539
1663
|
return config;
|
|
1540
1664
|
}
|
|
1541
1665
|
function getProvidedFlags(options) {
|
|
@@ -1560,7 +1684,8 @@ async function writeBtsConfig(projectConfig) {
|
|
|
1560
1684
|
packageManager: projectConfig.packageManager,
|
|
1561
1685
|
dbSetup: projectConfig.dbSetup,
|
|
1562
1686
|
api: projectConfig.api,
|
|
1563
|
-
webDeploy: projectConfig.webDeploy
|
|
1687
|
+
webDeploy: projectConfig.webDeploy,
|
|
1688
|
+
serverDeploy: projectConfig.serverDeploy
|
|
1564
1689
|
};
|
|
1565
1690
|
const baseContent = {
|
|
1566
1691
|
$schema: "https://r2.better-t-stack.dev/schema.json",
|
|
@@ -1577,7 +1702,8 @@ async function writeBtsConfig(projectConfig) {
|
|
|
1577
1702
|
packageManager: btsConfig.packageManager,
|
|
1578
1703
|
dbSetup: btsConfig.dbSetup,
|
|
1579
1704
|
api: btsConfig.api,
|
|
1580
|
-
webDeploy: btsConfig.webDeploy
|
|
1705
|
+
webDeploy: btsConfig.webDeploy,
|
|
1706
|
+
serverDeploy: btsConfig.serverDeploy
|
|
1581
1707
|
};
|
|
1582
1708
|
let configContent = JSON.stringify(baseContent);
|
|
1583
1709
|
const formatResult = JSONC.format(configContent, void 0, {
|
|
@@ -1670,7 +1796,7 @@ function getPackageExecutionCommand(packageManager, commandWithArgs) {
|
|
|
1670
1796
|
}
|
|
1671
1797
|
|
|
1672
1798
|
//#endregion
|
|
1673
|
-
//#region src/helpers/
|
|
1799
|
+
//#region src/helpers/addons/fumadocs-setup.ts
|
|
1674
1800
|
const TEMPLATES = {
|
|
1675
1801
|
"next-mdx": {
|
|
1676
1802
|
label: "Next.js: Fumadocs MDX",
|
|
@@ -1757,9 +1883,9 @@ handlebars.registerHelper("or", (a, b) => a || b);
|
|
|
1757
1883
|
handlebars.registerHelper("includes", (array, value) => Array.isArray(array) && array.includes(value));
|
|
1758
1884
|
|
|
1759
1885
|
//#endregion
|
|
1760
|
-
//#region src/helpers/
|
|
1886
|
+
//#region src/helpers/core/template-manager.ts
|
|
1761
1887
|
async function processAndCopyFiles(sourcePattern, baseSourceDir, destDir, context, overwrite = true, ignorePatterns) {
|
|
1762
|
-
const sourceFiles = await
|
|
1888
|
+
const sourceFiles = await glob(sourcePattern, {
|
|
1763
1889
|
cwd: baseSourceDir,
|
|
1764
1890
|
dot: true,
|
|
1765
1891
|
onlyFiles: true,
|
|
@@ -2073,10 +2199,6 @@ async function handleExtras(projectDir, context) {
|
|
|
2073
2199
|
const npmrcTemplateSrc = path.join(extrasDir, "_npmrc.hbs");
|
|
2074
2200
|
if (await fs.pathExists(npmrcTemplateSrc)) await processAndCopyFiles("_npmrc.hbs", extrasDir, projectDir, context);
|
|
2075
2201
|
}
|
|
2076
|
-
if (context.runtime === "workers") {
|
|
2077
|
-
const runtimeWorkersDir = path.join(PKG_ROOT, "templates/runtime/workers");
|
|
2078
|
-
if (await fs.pathExists(runtimeWorkersDir)) await processAndCopyFiles("**/*", runtimeWorkersDir, projectDir, context, false);
|
|
2079
|
-
}
|
|
2080
2202
|
}
|
|
2081
2203
|
async function setupDockerComposeTemplates(projectDir, context) {
|
|
2082
2204
|
if (context.dbSetup !== "docker" || context.database === "none") return;
|
|
@@ -2085,29 +2207,62 @@ async function setupDockerComposeTemplates(projectDir, context) {
|
|
|
2085
2207
|
if (await fs.pathExists(dockerSrcDir)) await processAndCopyFiles("**/*", dockerSrcDir, serverAppDir, context);
|
|
2086
2208
|
}
|
|
2087
2209
|
async function setupDeploymentTemplates(projectDir, context) {
|
|
2088
|
-
if (context.webDeploy === "
|
|
2089
|
-
|
|
2210
|
+
if (context.webDeploy === "alchemy" || context.serverDeploy === "alchemy") if (context.webDeploy === "alchemy" && context.serverDeploy === "alchemy") {
|
|
2211
|
+
const alchemyTemplateSrc = path.join(PKG_ROOT, "templates/deploy/alchemy");
|
|
2212
|
+
if (await fs.pathExists(alchemyTemplateSrc)) {
|
|
2213
|
+
await processAndCopyFiles("alchemy.run.ts.hbs", alchemyTemplateSrc, projectDir, context);
|
|
2214
|
+
const serverAppDir = path.join(projectDir, "apps/server");
|
|
2215
|
+
if (await fs.pathExists(serverAppDir)) await processAndCopyFiles("env.d.ts.hbs", alchemyTemplateSrc, serverAppDir, context);
|
|
2216
|
+
}
|
|
2217
|
+
} else {
|
|
2218
|
+
if (context.webDeploy === "alchemy") {
|
|
2219
|
+
const webAppDir = path.join(projectDir, "apps/web");
|
|
2220
|
+
if (await fs.pathExists(webAppDir)) {
|
|
2221
|
+
const alchemyTemplateSrc = path.join(PKG_ROOT, "templates/deploy/alchemy");
|
|
2222
|
+
if (await fs.pathExists(alchemyTemplateSrc)) await processAndCopyFiles("**/*", alchemyTemplateSrc, webAppDir, context);
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
if (context.serverDeploy === "alchemy") {
|
|
2226
|
+
const serverAppDir = path.join(projectDir, "apps/server");
|
|
2227
|
+
if (await fs.pathExists(serverAppDir)) {
|
|
2228
|
+
const alchemyTemplateSrc = path.join(PKG_ROOT, "templates/deploy/alchemy");
|
|
2229
|
+
if (await fs.pathExists(alchemyTemplateSrc)) {
|
|
2230
|
+
await processAndCopyFiles("alchemy.run.ts.hbs", alchemyTemplateSrc, serverAppDir, context);
|
|
2231
|
+
await processAndCopyFiles("env.d.ts.hbs", alchemyTemplateSrc, serverAppDir, context);
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
if (context.webDeploy !== "none" && context.webDeploy !== "alchemy") {
|
|
2090
2237
|
const webAppDir = path.join(projectDir, "apps/web");
|
|
2091
|
-
if (
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2238
|
+
if (await fs.pathExists(webAppDir)) {
|
|
2239
|
+
const frontends = context.frontend;
|
|
2240
|
+
const templateMap = {
|
|
2241
|
+
"tanstack-router": "react/tanstack-router",
|
|
2242
|
+
"tanstack-start": "react/tanstack-start",
|
|
2243
|
+
"react-router": "react/react-router",
|
|
2244
|
+
solid: "solid",
|
|
2245
|
+
next: "react/next",
|
|
2246
|
+
nuxt: "nuxt",
|
|
2247
|
+
svelte: "svelte"
|
|
2248
|
+
};
|
|
2249
|
+
for (const f of frontends) if (templateMap[f]) {
|
|
2250
|
+
const deployTemplateSrc = path.join(PKG_ROOT, `templates/deploy/${context.webDeploy}/web/${templateMap[f]}`);
|
|
2251
|
+
if (await fs.pathExists(deployTemplateSrc)) await processAndCopyFiles("**/*", deployTemplateSrc, webAppDir, context);
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
if (context.serverDeploy !== "none" && context.serverDeploy !== "alchemy") {
|
|
2256
|
+
const serverAppDir = path.join(projectDir, "apps/server");
|
|
2257
|
+
if (await fs.pathExists(serverAppDir)) {
|
|
2258
|
+
const deployTemplateSrc = path.join(PKG_ROOT, `templates/deploy/${context.serverDeploy}/server`);
|
|
2259
|
+
if (await fs.pathExists(deployTemplateSrc)) await processAndCopyFiles("**/*", deployTemplateSrc, serverAppDir, context);
|
|
2105
2260
|
}
|
|
2106
2261
|
}
|
|
2107
2262
|
}
|
|
2108
2263
|
|
|
2109
2264
|
//#endregion
|
|
2110
|
-
//#region src/helpers/
|
|
2265
|
+
//#region src/helpers/addons/ruler-setup.ts
|
|
2111
2266
|
async function setupVibeRules(config) {
|
|
2112
2267
|
const { packageManager, projectDir } = config;
|
|
2113
2268
|
try {
|
|
@@ -2189,7 +2344,7 @@ async function addRulerScriptToPackageJson(projectDir, packageManager) {
|
|
|
2189
2344
|
}
|
|
2190
2345
|
|
|
2191
2346
|
//#endregion
|
|
2192
|
-
//#region src/helpers/
|
|
2347
|
+
//#region src/helpers/addons/starlight-setup.ts
|
|
2193
2348
|
async function setupStarlight(config) {
|
|
2194
2349
|
const { packageManager, projectDir } = config;
|
|
2195
2350
|
const s = spinner();
|
|
@@ -2221,7 +2376,7 @@ async function setupStarlight(config) {
|
|
|
2221
2376
|
}
|
|
2222
2377
|
|
|
2223
2378
|
//#endregion
|
|
2224
|
-
//#region src/helpers/
|
|
2379
|
+
//#region src/helpers/addons/tauri-setup.ts
|
|
2225
2380
|
async function setupTauri(config) {
|
|
2226
2381
|
const { packageManager, frontend, projectDir } = config;
|
|
2227
2382
|
const s = spinner();
|
|
@@ -2258,8 +2413,8 @@ async function setupTauri(config) {
|
|
|
2258
2413
|
`--window-title=${path.basename(projectDir)}`,
|
|
2259
2414
|
`--frontend-dist=${frontendDist}`,
|
|
2260
2415
|
`--dev-url=${devUrl}`,
|
|
2261
|
-
`--before-dev-command
|
|
2262
|
-
`--before-build-command
|
|
2416
|
+
`--before-dev-command="${packageManager} run dev"`,
|
|
2417
|
+
`--before-build-command="${packageManager} run build"`
|
|
2263
2418
|
];
|
|
2264
2419
|
const tauriArgsString = tauriArgs.join(" ");
|
|
2265
2420
|
const commandWithArgs = `@tauri-apps/cli@latest ${tauriArgsString}`;
|
|
@@ -2277,7 +2432,7 @@ async function setupTauri(config) {
|
|
|
2277
2432
|
}
|
|
2278
2433
|
|
|
2279
2434
|
//#endregion
|
|
2280
|
-
//#region src/helpers/
|
|
2435
|
+
//#region src/helpers/addons/ultracite-setup.ts
|
|
2281
2436
|
const EDITORS = {
|
|
2282
2437
|
vscode: {
|
|
2283
2438
|
label: "VSCode / Cursor / Windsurf",
|
|
@@ -2384,7 +2539,7 @@ function ensureArrayProperty(obj, name) {
|
|
|
2384
2539
|
}
|
|
2385
2540
|
|
|
2386
2541
|
//#endregion
|
|
2387
|
-
//#region src/helpers/
|
|
2542
|
+
//#region src/helpers/addons/vite-pwa-setup.ts
|
|
2388
2543
|
async function addPwaToViteConfig(viteConfigPath, projectName) {
|
|
2389
2544
|
const sourceFile = tsProject.addSourceFileAtPathIfExists(viteConfigPath);
|
|
2390
2545
|
if (!sourceFile) throw new Error("vite config not found");
|
|
@@ -2418,7 +2573,7 @@ async function addPwaToViteConfig(viteConfigPath, projectName) {
|
|
|
2418
2573
|
}
|
|
2419
2574
|
|
|
2420
2575
|
//#endregion
|
|
2421
|
-
//#region src/helpers/
|
|
2576
|
+
//#region src/helpers/addons/addons-setup.ts
|
|
2422
2577
|
async function setupAddons(config, isAddCommand = false) {
|
|
2423
2578
|
const { addons, frontend, projectDir, packageManager } = config;
|
|
2424
2579
|
const hasReactWebFrontend = frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("next");
|
|
@@ -2552,7 +2707,7 @@ async function setupOxlint(projectDir, packageManager) {
|
|
|
2552
2707
|
}
|
|
2553
2708
|
|
|
2554
2709
|
//#endregion
|
|
2555
|
-
//#region src/helpers/
|
|
2710
|
+
//#region src/helpers/core/detect-project-config.ts
|
|
2556
2711
|
async function detectProjectConfig(projectDir) {
|
|
2557
2712
|
try {
|
|
2558
2713
|
const btsConfig = await readBtsConfig(projectDir);
|
|
@@ -2570,7 +2725,8 @@ async function detectProjectConfig(projectDir) {
|
|
|
2570
2725
|
packageManager: btsConfig.packageManager,
|
|
2571
2726
|
dbSetup: btsConfig.dbSetup,
|
|
2572
2727
|
api: btsConfig.api,
|
|
2573
|
-
webDeploy: btsConfig.webDeploy
|
|
2728
|
+
webDeploy: btsConfig.webDeploy,
|
|
2729
|
+
serverDeploy: btsConfig.serverDeploy
|
|
2574
2730
|
};
|
|
2575
2731
|
return null;
|
|
2576
2732
|
} catch (_error) {
|
|
@@ -2586,7 +2742,7 @@ async function isBetterTStackProject(projectDir) {
|
|
|
2586
2742
|
}
|
|
2587
2743
|
|
|
2588
2744
|
//#endregion
|
|
2589
|
-
//#region src/helpers/
|
|
2745
|
+
//#region src/helpers/core/install-dependencies.ts
|
|
2590
2746
|
async function installDependencies({ projectDir, packageManager }) {
|
|
2591
2747
|
const s = spinner();
|
|
2592
2748
|
try {
|
|
@@ -2603,7 +2759,7 @@ async function installDependencies({ projectDir, packageManager }) {
|
|
|
2603
2759
|
}
|
|
2604
2760
|
|
|
2605
2761
|
//#endregion
|
|
2606
|
-
//#region src/helpers/
|
|
2762
|
+
//#region src/helpers/core/add-addons.ts
|
|
2607
2763
|
async function addAddonsToProject(input) {
|
|
2608
2764
|
try {
|
|
2609
2765
|
const projectDir = input.projectDir || process.cwd();
|
|
@@ -2628,7 +2784,8 @@ async function addAddonsToProject(input) {
|
|
|
2628
2784
|
install: input.install || false,
|
|
2629
2785
|
dbSetup: detectedConfig.dbSetup || "none",
|
|
2630
2786
|
api: detectedConfig.api || "none",
|
|
2631
|
-
webDeploy: detectedConfig.webDeploy || "none"
|
|
2787
|
+
webDeploy: detectedConfig.webDeploy || "none",
|
|
2788
|
+
serverDeploy: detectedConfig.serverDeploy || "none"
|
|
2632
2789
|
};
|
|
2633
2790
|
for (const addon of input.addons) {
|
|
2634
2791
|
const { isCompatible, reason } = validateAddonCompatibility(addon, config.frontend);
|
|
@@ -2651,12 +2808,92 @@ async function addAddonsToProject(input) {
|
|
|
2651
2808
|
}
|
|
2652
2809
|
|
|
2653
2810
|
//#endregion
|
|
2654
|
-
//#region src/helpers/
|
|
2655
|
-
async function
|
|
2811
|
+
//#region src/helpers/deployment/server-deploy-setup.ts
|
|
2812
|
+
async function setupServerDeploy(config) {
|
|
2813
|
+
const { serverDeploy, webDeploy, projectDir } = config;
|
|
2814
|
+
const { packageManager } = config;
|
|
2815
|
+
if (serverDeploy === "none") return;
|
|
2816
|
+
if (serverDeploy === "alchemy" && webDeploy === "alchemy") return;
|
|
2817
|
+
const serverDir = path.join(projectDir, "apps/server");
|
|
2818
|
+
if (!await fs.pathExists(serverDir)) return;
|
|
2819
|
+
if (serverDeploy === "wrangler") {
|
|
2820
|
+
await setupWorkersServerDeploy(serverDir, packageManager);
|
|
2821
|
+
await generateCloudflareWorkerTypes({
|
|
2822
|
+
serverDir,
|
|
2823
|
+
packageManager
|
|
2824
|
+
});
|
|
2825
|
+
} else if (serverDeploy === "alchemy") await setupAlchemyServerDeploy(serverDir, packageManager);
|
|
2826
|
+
}
|
|
2827
|
+
async function setupWorkersServerDeploy(serverDir, _packageManager) {
|
|
2828
|
+
const packageJsonPath = path.join(serverDir, "package.json");
|
|
2829
|
+
if (!await fs.pathExists(packageJsonPath)) return;
|
|
2830
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
2831
|
+
packageJson.scripts = {
|
|
2832
|
+
...packageJson.scripts,
|
|
2833
|
+
dev: "wrangler dev --port=3000",
|
|
2834
|
+
start: "wrangler dev",
|
|
2835
|
+
deploy: "wrangler deploy",
|
|
2836
|
+
build: "wrangler deploy --dry-run",
|
|
2837
|
+
"cf-typegen": "wrangler types --env-interface CloudflareBindings"
|
|
2838
|
+
};
|
|
2839
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
2840
|
+
await addPackageDependency({
|
|
2841
|
+
devDependencies: [
|
|
2842
|
+
"wrangler",
|
|
2843
|
+
"@types/node",
|
|
2844
|
+
"@cloudflare/workers-types"
|
|
2845
|
+
],
|
|
2846
|
+
projectDir: serverDir
|
|
2847
|
+
});
|
|
2848
|
+
}
|
|
2849
|
+
async function generateCloudflareWorkerTypes({ serverDir, packageManager }) {
|
|
2850
|
+
if (!await fs.pathExists(serverDir)) return;
|
|
2851
|
+
const s = spinner();
|
|
2852
|
+
try {
|
|
2853
|
+
s.start("Generating Cloudflare Workers types...");
|
|
2854
|
+
const runCmd = packageManager === "npm" ? "npm" : packageManager;
|
|
2855
|
+
await execa(runCmd, ["run", "cf-typegen"], { cwd: serverDir });
|
|
2856
|
+
s.stop("Cloudflare Workers types generated successfully!");
|
|
2857
|
+
} catch {
|
|
2858
|
+
s.stop(pc.yellow("Failed to generate Cloudflare Workers types"));
|
|
2859
|
+
const managerCmd = `${packageManager} run`;
|
|
2860
|
+
log.warn(`Note: You can manually run 'cd apps/server && ${managerCmd} cf-typegen' in the project directory later`);
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
async function setupAlchemyServerDeploy(serverDir, _packageManager) {
|
|
2864
|
+
if (!await fs.pathExists(serverDir)) return;
|
|
2865
|
+
await addPackageDependency({
|
|
2866
|
+
devDependencies: [
|
|
2867
|
+
"alchemy",
|
|
2868
|
+
"wrangler",
|
|
2869
|
+
"@types/node",
|
|
2870
|
+
"@cloudflare/workers-types",
|
|
2871
|
+
"dotenv"
|
|
2872
|
+
],
|
|
2873
|
+
projectDir: serverDir
|
|
2874
|
+
});
|
|
2875
|
+
const packageJsonPath = path.join(serverDir, "package.json");
|
|
2876
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
2877
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
2878
|
+
packageJson.scripts = {
|
|
2879
|
+
...packageJson.scripts,
|
|
2880
|
+
dev: "wrangler dev --port=3000",
|
|
2881
|
+
build: "wrangler deploy --dry-run",
|
|
2882
|
+
deploy: "alchemy deploy",
|
|
2883
|
+
destroy: "alchemy destroy",
|
|
2884
|
+
"alchemy:dev": "alchemy dev"
|
|
2885
|
+
};
|
|
2886
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
//#endregion
|
|
2891
|
+
//#region src/helpers/deployment/alchemy/alchemy-next-setup.ts
|
|
2892
|
+
async function setupNextAlchemyDeploy(projectDir, _packageManager) {
|
|
2656
2893
|
const webAppDir = path.join(projectDir, "apps/web");
|
|
2657
2894
|
if (!await fs.pathExists(webAppDir)) return;
|
|
2658
2895
|
await addPackageDependency({
|
|
2659
|
-
devDependencies: ["
|
|
2896
|
+
devDependencies: ["alchemy", "dotenv"],
|
|
2660
2897
|
projectDir: webAppDir
|
|
2661
2898
|
});
|
|
2662
2899
|
const pkgPath = path.join(webAppDir, "package.json");
|
|
@@ -2664,63 +2901,93 @@ async function setupNuxtWorkersDeploy(projectDir, packageManager) {
|
|
|
2664
2901
|
const pkg = await fs.readJson(pkgPath);
|
|
2665
2902
|
pkg.scripts = {
|
|
2666
2903
|
...pkg.scripts,
|
|
2667
|
-
deploy:
|
|
2668
|
-
|
|
2904
|
+
deploy: "alchemy deploy",
|
|
2905
|
+
destroy: "alchemy destroy",
|
|
2906
|
+
"alchemy:dev": "alchemy dev"
|
|
2907
|
+
};
|
|
2908
|
+
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
2909
|
+
}
|
|
2910
|
+
}
|
|
2911
|
+
|
|
2912
|
+
//#endregion
|
|
2913
|
+
//#region src/helpers/deployment/alchemy/alchemy-nuxt-setup.ts
|
|
2914
|
+
async function setupNuxtAlchemyDeploy(projectDir, _packageManager) {
|
|
2915
|
+
const webAppDir = path.join(projectDir, "apps/web");
|
|
2916
|
+
if (!await fs.pathExists(webAppDir)) return;
|
|
2917
|
+
await addPackageDependency({
|
|
2918
|
+
devDependencies: [
|
|
2919
|
+
"alchemy",
|
|
2920
|
+
"nitro-cloudflare-dev",
|
|
2921
|
+
"dotenv"
|
|
2922
|
+
],
|
|
2923
|
+
projectDir: webAppDir
|
|
2924
|
+
});
|
|
2925
|
+
const pkgPath = path.join(webAppDir, "package.json");
|
|
2926
|
+
if (await fs.pathExists(pkgPath)) {
|
|
2927
|
+
const pkg = await fs.readJson(pkgPath);
|
|
2928
|
+
pkg.scripts = {
|
|
2929
|
+
...pkg.scripts,
|
|
2930
|
+
deploy: "alchemy deploy",
|
|
2931
|
+
destroy: "alchemy destroy",
|
|
2932
|
+
"alchemy:dev": "alchemy dev"
|
|
2669
2933
|
};
|
|
2670
2934
|
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
2671
2935
|
}
|
|
2672
2936
|
const nuxtConfigPath = path.join(webAppDir, "nuxt.config.ts");
|
|
2673
2937
|
if (!await fs.pathExists(nuxtConfigPath)) return;
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2938
|
+
try {
|
|
2939
|
+
const project = new Project({ manipulationSettings: {
|
|
2940
|
+
indentationText: IndentationText.TwoSpaces,
|
|
2941
|
+
quoteKind: QuoteKind.Double
|
|
2942
|
+
} });
|
|
2943
|
+
project.addSourceFileAtPath(nuxtConfigPath);
|
|
2944
|
+
const sourceFile = project.getSourceFileOrThrow(nuxtConfigPath);
|
|
2945
|
+
const exportAssignment = sourceFile.getExportAssignment((d) => !d.isExportEquals());
|
|
2946
|
+
if (!exportAssignment) return;
|
|
2947
|
+
const defineConfigCall = exportAssignment.getExpression();
|
|
2948
|
+
if (!Node.isCallExpression(defineConfigCall) || defineConfigCall.getExpression().getText() !== "defineNuxtConfig") return;
|
|
2949
|
+
let configObject = defineConfigCall.getArguments()[0];
|
|
2950
|
+
if (!configObject) configObject = defineConfigCall.addArgument("{}");
|
|
2951
|
+
if (Node.isObjectLiteralExpression(configObject)) {
|
|
2952
|
+
if (!configObject.getProperty("nitro")) configObject.addPropertyAssignment({
|
|
2953
|
+
name: "nitro",
|
|
2954
|
+
initializer: `{
|
|
2691
2955
|
preset: "cloudflare_module",
|
|
2692
2956
|
cloudflare: {
|
|
2693
2957
|
deployConfig: true,
|
|
2694
2958
|
nodeCompat: true
|
|
2695
2959
|
}
|
|
2696
|
-
}
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2960
|
+
}`
|
|
2961
|
+
});
|
|
2962
|
+
const modulesProperty = configObject.getProperty("modules");
|
|
2963
|
+
if (modulesProperty && Node.isPropertyAssignment(modulesProperty)) {
|
|
2964
|
+
const initializer = modulesProperty.getInitializer();
|
|
2965
|
+
if (Node.isArrayLiteralExpression(initializer)) {
|
|
2966
|
+
const hasModule = initializer.getElements().some((el) => el.getText() === "\"nitro-cloudflare-dev\"" || el.getText() === "'nitro-cloudflare-dev'");
|
|
2967
|
+
if (!hasModule) initializer.addElement("\"nitro-cloudflare-dev\"");
|
|
2968
|
+
}
|
|
2969
|
+
} else if (!modulesProperty) configObject.addPropertyAssignment({
|
|
2970
|
+
name: "modules",
|
|
2971
|
+
initializer: "[\"nitro-cloudflare-dev\"]"
|
|
2972
|
+
});
|
|
2709
2973
|
}
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
}
|
|
2714
|
-
await tsProject.save();
|
|
2974
|
+
await project.save();
|
|
2975
|
+
} catch (error) {
|
|
2976
|
+
console.warn("Failed to update nuxt.config.ts:", error);
|
|
2977
|
+
}
|
|
2715
2978
|
}
|
|
2716
2979
|
|
|
2717
2980
|
//#endregion
|
|
2718
|
-
//#region src/helpers/
|
|
2719
|
-
async function
|
|
2981
|
+
//#region src/helpers/deployment/alchemy/alchemy-react-router-setup.ts
|
|
2982
|
+
async function setupReactRouterAlchemyDeploy(projectDir, _packageManager) {
|
|
2720
2983
|
const webAppDir = path.join(projectDir, "apps/web");
|
|
2721
2984
|
if (!await fs.pathExists(webAppDir)) return;
|
|
2722
2985
|
await addPackageDependency({
|
|
2723
|
-
devDependencies: [
|
|
2986
|
+
devDependencies: [
|
|
2987
|
+
"alchemy",
|
|
2988
|
+
"@cloudflare/vite-plugin",
|
|
2989
|
+
"dotenv"
|
|
2990
|
+
],
|
|
2724
2991
|
projectDir: webAppDir
|
|
2725
2992
|
});
|
|
2726
2993
|
const pkgPath = path.join(webAppDir, "package.json");
|
|
@@ -2728,36 +2995,100 @@ async function setupSvelteWorkersDeploy(projectDir, packageManager) {
|
|
|
2728
2995
|
const pkg = await fs.readJson(pkgPath);
|
|
2729
2996
|
pkg.scripts = {
|
|
2730
2997
|
...pkg.scripts,
|
|
2731
|
-
deploy:
|
|
2732
|
-
|
|
2998
|
+
deploy: "alchemy deploy",
|
|
2999
|
+
destroy: "alchemy destroy",
|
|
3000
|
+
"alchemy:dev": "alchemy dev"
|
|
2733
3001
|
};
|
|
2734
3002
|
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
2735
3003
|
}
|
|
2736
|
-
const
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
3004
|
+
const viteConfigPath = path.join(webAppDir, "vite.config.ts");
|
|
3005
|
+
if (await fs.pathExists(viteConfigPath)) try {
|
|
3006
|
+
const project = new Project({ manipulationSettings: {
|
|
3007
|
+
indentationText: IndentationText.TwoSpaces,
|
|
3008
|
+
quoteKind: QuoteKind.Double
|
|
3009
|
+
} });
|
|
3010
|
+
project.addSourceFileAtPath(viteConfigPath);
|
|
3011
|
+
const sourceFile = project.getSourceFileOrThrow(viteConfigPath);
|
|
3012
|
+
const alchemyImport = sourceFile.getImportDeclaration("alchemy/cloudflare/react-router");
|
|
3013
|
+
if (!alchemyImport) sourceFile.addImportDeclaration({
|
|
3014
|
+
moduleSpecifier: "alchemy/cloudflare/react-router",
|
|
3015
|
+
defaultImport: "alchemy"
|
|
3016
|
+
});
|
|
3017
|
+
const exportAssignment = sourceFile.getExportAssignment((d) => !d.isExportEquals());
|
|
3018
|
+
if (!exportAssignment) return;
|
|
3019
|
+
const defineConfigCall = exportAssignment.getExpression();
|
|
3020
|
+
if (!Node.isCallExpression(defineConfigCall) || defineConfigCall.getExpression().getText() !== "defineConfig") return;
|
|
3021
|
+
let configObject = defineConfigCall.getArguments()[0];
|
|
3022
|
+
if (!configObject) configObject = defineConfigCall.addArgument("{}");
|
|
3023
|
+
if (Node.isObjectLiteralExpression(configObject)) {
|
|
3024
|
+
const pluginsProperty = configObject.getProperty("plugins");
|
|
3025
|
+
if (pluginsProperty && Node.isPropertyAssignment(pluginsProperty)) {
|
|
3026
|
+
const initializer = pluginsProperty.getInitializer();
|
|
3027
|
+
if (Node.isArrayLiteralExpression(initializer)) {
|
|
3028
|
+
const hasCloudflarePlugin = initializer.getElements().some((el) => el.getText().includes("cloudflare("));
|
|
3029
|
+
if (!hasCloudflarePlugin) initializer.addElement("alchemy()");
|
|
3030
|
+
}
|
|
3031
|
+
} else if (!pluginsProperty) configObject.addPropertyAssignment({
|
|
3032
|
+
name: "plugins",
|
|
3033
|
+
initializer: "[alchemy()]"
|
|
2748
3034
|
});
|
|
2749
3035
|
}
|
|
2750
|
-
await
|
|
3036
|
+
await project.save();
|
|
3037
|
+
} catch (error) {
|
|
3038
|
+
console.warn("Failed to update vite.config.ts:", error);
|
|
3039
|
+
}
|
|
3040
|
+
const reactRouterConfigPath = path.join(webAppDir, "react-router.config.ts");
|
|
3041
|
+
if (await fs.pathExists(reactRouterConfigPath)) try {
|
|
3042
|
+
const project = new Project({ manipulationSettings: {
|
|
3043
|
+
indentationText: IndentationText.TwoSpaces,
|
|
3044
|
+
quoteKind: QuoteKind.Double
|
|
3045
|
+
} });
|
|
3046
|
+
project.addSourceFileAtPath(reactRouterConfigPath);
|
|
3047
|
+
const sourceFile = project.getSourceFileOrThrow(reactRouterConfigPath);
|
|
3048
|
+
const exportAssignment = sourceFile.getExportAssignment((d) => !d.isExportEquals());
|
|
3049
|
+
if (!exportAssignment) return;
|
|
3050
|
+
const configExpression = exportAssignment.getExpression();
|
|
3051
|
+
let configObject;
|
|
3052
|
+
if (Node.isObjectLiteralExpression(configExpression)) configObject = configExpression;
|
|
3053
|
+
else if (Node.isSatisfiesExpression(configExpression)) {
|
|
3054
|
+
const expression = configExpression.getExpression();
|
|
3055
|
+
if (Node.isObjectLiteralExpression(expression)) configObject = expression;
|
|
3056
|
+
}
|
|
3057
|
+
if (!configObject || !Node.isObjectLiteralExpression(configObject)) return;
|
|
3058
|
+
const futureProperty = configObject.getProperty("future");
|
|
3059
|
+
if (!futureProperty) configObject.addPropertyAssignment({
|
|
3060
|
+
name: "future",
|
|
3061
|
+
initializer: `{
|
|
3062
|
+
unstable_viteEnvironmentApi: true,
|
|
3063
|
+
}`
|
|
3064
|
+
});
|
|
3065
|
+
else if (Node.isPropertyAssignment(futureProperty)) {
|
|
3066
|
+
const futureInitializer = futureProperty.getInitializer();
|
|
3067
|
+
if (Node.isObjectLiteralExpression(futureInitializer)) {
|
|
3068
|
+
const viteEnvApiProp = futureInitializer.getProperty("unstable_viteEnvironmentApi");
|
|
3069
|
+
if (!viteEnvApiProp) futureInitializer.addPropertyAssignment({
|
|
3070
|
+
name: "unstable_viteEnvironmentApi",
|
|
3071
|
+
initializer: "true"
|
|
3072
|
+
});
|
|
3073
|
+
else if (Node.isPropertyAssignment(viteEnvApiProp)) {
|
|
3074
|
+
const value = viteEnvApiProp.getInitializer()?.getText();
|
|
3075
|
+
if (value === "false") viteEnvApiProp.setInitializer("true");
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
3078
|
+
}
|
|
3079
|
+
await project.save();
|
|
3080
|
+
} catch (error) {
|
|
3081
|
+
console.warn("Failed to update react-router.config.ts:", error);
|
|
2751
3082
|
}
|
|
2752
3083
|
}
|
|
2753
3084
|
|
|
2754
3085
|
//#endregion
|
|
2755
|
-
//#region src/helpers/
|
|
2756
|
-
async function
|
|
3086
|
+
//#region src/helpers/deployment/alchemy/alchemy-solid-setup.ts
|
|
3087
|
+
async function setupSolidAlchemyDeploy(projectDir, _packageManager) {
|
|
2757
3088
|
const webAppDir = path.join(projectDir, "apps/web");
|
|
2758
3089
|
if (!await fs.pathExists(webAppDir)) return;
|
|
2759
3090
|
await addPackageDependency({
|
|
2760
|
-
devDependencies: ["
|
|
3091
|
+
devDependencies: ["alchemy", "dotenv"],
|
|
2761
3092
|
projectDir: webAppDir
|
|
2762
3093
|
});
|
|
2763
3094
|
const pkgPath = path.join(webAppDir, "package.json");
|
|
@@ -2765,44 +3096,425 @@ async function setupTanstackStartWorkersDeploy(projectDir, packageManager) {
|
|
|
2765
3096
|
const pkg = await fs.readJson(pkgPath);
|
|
2766
3097
|
pkg.scripts = {
|
|
2767
3098
|
...pkg.scripts,
|
|
2768
|
-
deploy:
|
|
2769
|
-
|
|
3099
|
+
deploy: "alchemy deploy",
|
|
3100
|
+
destroy: "alchemy destroy",
|
|
3101
|
+
"alchemy:dev": "alchemy dev"
|
|
2770
3102
|
};
|
|
2771
3103
|
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
2772
3104
|
}
|
|
2773
|
-
const viteConfigPath = path.join(webAppDir, "vite.config.ts");
|
|
2774
|
-
if (!await fs.pathExists(viteConfigPath)) return;
|
|
2775
|
-
const sourceFile = tsProject.addSourceFileAtPathIfExists(viteConfigPath);
|
|
2776
|
-
if (!sourceFile) return;
|
|
2777
|
-
const defineCall = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression).find((expr) => {
|
|
2778
|
-
const expression = expr.getExpression();
|
|
2779
|
-
return Node.isIdentifier(expression) && expression.getText() === "defineConfig";
|
|
2780
|
-
});
|
|
2781
|
-
if (!defineCall) return;
|
|
2782
|
-
const configObj = defineCall.getArguments()[0];
|
|
2783
|
-
if (!configObj) return;
|
|
2784
|
-
const pluginsArray = ensureArrayProperty(configObj, "plugins");
|
|
2785
|
-
const tanstackPluginIndex = pluginsArray.getElements().findIndex((el) => el.getText().includes("tanstackStart("));
|
|
2786
|
-
const tanstackPluginText = "tanstackStart({ target: \"cloudflare-module\" })";
|
|
2787
|
-
if (tanstackPluginIndex === -1) pluginsArray.addElement(tanstackPluginText);
|
|
2788
|
-
else pluginsArray.getElements()[tanstackPluginIndex].replaceWithText(tanstackPluginText);
|
|
2789
|
-
await tsProject.save();
|
|
2790
3105
|
}
|
|
2791
3106
|
|
|
2792
3107
|
//#endregion
|
|
2793
|
-
//#region src/helpers/
|
|
2794
|
-
async function
|
|
3108
|
+
//#region src/helpers/deployment/alchemy/alchemy-svelte-setup.ts
|
|
3109
|
+
async function setupSvelteAlchemyDeploy(projectDir, _packageManager) {
|
|
2795
3110
|
const webAppDir = path.join(projectDir, "apps/web");
|
|
2796
|
-
|
|
2797
|
-
if (!await fs.pathExists(viteConfigPath)) throw new Error("vite.config.ts not found in web app directory");
|
|
3111
|
+
if (!await fs.pathExists(webAppDir)) return;
|
|
2798
3112
|
await addPackageDependency({
|
|
2799
|
-
devDependencies: [
|
|
3113
|
+
devDependencies: [
|
|
3114
|
+
"alchemy",
|
|
3115
|
+
"@sveltejs/adapter-cloudflare",
|
|
3116
|
+
"dotenv"
|
|
3117
|
+
],
|
|
2800
3118
|
projectDir: webAppDir
|
|
2801
3119
|
});
|
|
2802
|
-
const
|
|
2803
|
-
if (
|
|
2804
|
-
|
|
2805
|
-
|
|
3120
|
+
const pkgPath = path.join(webAppDir, "package.json");
|
|
3121
|
+
if (await fs.pathExists(pkgPath)) {
|
|
3122
|
+
const pkg = await fs.readJson(pkgPath);
|
|
3123
|
+
pkg.scripts = {
|
|
3124
|
+
...pkg.scripts,
|
|
3125
|
+
deploy: "alchemy deploy",
|
|
3126
|
+
destroy: "alchemy destroy",
|
|
3127
|
+
"alchemy:dev": "alchemy dev"
|
|
3128
|
+
};
|
|
3129
|
+
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
3130
|
+
}
|
|
3131
|
+
const svelteConfigPath = path.join(webAppDir, "svelte.config.js");
|
|
3132
|
+
if (!await fs.pathExists(svelteConfigPath)) return;
|
|
3133
|
+
try {
|
|
3134
|
+
const project = new Project({ manipulationSettings: {
|
|
3135
|
+
indentationText: IndentationText.TwoSpaces,
|
|
3136
|
+
quoteKind: QuoteKind.Single
|
|
3137
|
+
} });
|
|
3138
|
+
project.addSourceFileAtPath(svelteConfigPath);
|
|
3139
|
+
const sourceFile = project.getSourceFileOrThrow(svelteConfigPath);
|
|
3140
|
+
const importDeclarations = sourceFile.getImportDeclarations();
|
|
3141
|
+
const adapterImport = importDeclarations.find((imp) => imp.getModuleSpecifierValue().includes("@sveltejs/adapter"));
|
|
3142
|
+
if (adapterImport) {
|
|
3143
|
+
adapterImport.setModuleSpecifier("alchemy/cloudflare/sveltekit");
|
|
3144
|
+
adapterImport.removeDefaultImport();
|
|
3145
|
+
adapterImport.setDefaultImport("alchemy");
|
|
3146
|
+
} else sourceFile.insertImportDeclaration(0, {
|
|
3147
|
+
moduleSpecifier: "alchemy/cloudflare/sveltekit",
|
|
3148
|
+
defaultImport: "alchemy"
|
|
3149
|
+
});
|
|
3150
|
+
const configVariable = sourceFile.getVariableDeclaration("config");
|
|
3151
|
+
if (configVariable) {
|
|
3152
|
+
const initializer = configVariable.getInitializer();
|
|
3153
|
+
if (Node.isObjectLiteralExpression(initializer)) updateAdapterInConfig(initializer);
|
|
3154
|
+
}
|
|
3155
|
+
await project.save();
|
|
3156
|
+
} catch (error) {
|
|
3157
|
+
console.warn("Failed to update svelte.config.js:", error);
|
|
3158
|
+
}
|
|
3159
|
+
}
|
|
3160
|
+
function updateAdapterInConfig(configObject) {
|
|
3161
|
+
if (!Node.isObjectLiteralExpression(configObject)) return;
|
|
3162
|
+
const kitProperty = configObject.getProperty("kit");
|
|
3163
|
+
if (kitProperty && Node.isPropertyAssignment(kitProperty)) {
|
|
3164
|
+
const kitInitializer = kitProperty.getInitializer();
|
|
3165
|
+
if (Node.isObjectLiteralExpression(kitInitializer)) {
|
|
3166
|
+
const adapterProperty = kitInitializer.getProperty("adapter");
|
|
3167
|
+
if (adapterProperty && Node.isPropertyAssignment(adapterProperty)) {
|
|
3168
|
+
const initializer = adapterProperty.getInitializer();
|
|
3169
|
+
if (Node.isCallExpression(initializer)) {
|
|
3170
|
+
const expression = initializer.getExpression();
|
|
3171
|
+
if (Node.isIdentifier(expression) && expression.getText() === "adapter") expression.replaceWithText("alchemy");
|
|
3172
|
+
}
|
|
3173
|
+
}
|
|
3174
|
+
}
|
|
3175
|
+
}
|
|
3176
|
+
}
|
|
3177
|
+
|
|
3178
|
+
//#endregion
|
|
3179
|
+
//#region src/helpers/deployment/alchemy/alchemy-tanstack-router-setup.ts
|
|
3180
|
+
async function setupTanStackRouterAlchemyDeploy(projectDir, _packageManager) {
|
|
3181
|
+
const webAppDir = path.join(projectDir, "apps/web");
|
|
3182
|
+
if (!await fs.pathExists(webAppDir)) return;
|
|
3183
|
+
await addPackageDependency({
|
|
3184
|
+
devDependencies: ["alchemy", "dotenv"],
|
|
3185
|
+
projectDir: webAppDir
|
|
3186
|
+
});
|
|
3187
|
+
const pkgPath = path.join(webAppDir, "package.json");
|
|
3188
|
+
if (await fs.pathExists(pkgPath)) {
|
|
3189
|
+
const pkg = await fs.readJson(pkgPath);
|
|
3190
|
+
pkg.scripts = {
|
|
3191
|
+
...pkg.scripts,
|
|
3192
|
+
deploy: "alchemy deploy",
|
|
3193
|
+
destroy: "alchemy destroy",
|
|
3194
|
+
"alchemy:dev": "alchemy dev"
|
|
3195
|
+
};
|
|
3196
|
+
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
3197
|
+
}
|
|
3198
|
+
}
|
|
3199
|
+
|
|
3200
|
+
//#endregion
|
|
3201
|
+
//#region src/helpers/deployment/alchemy/alchemy-tanstack-start-setup.ts
|
|
3202
|
+
async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager) {
|
|
3203
|
+
const webAppDir = path.join(projectDir, "apps/web");
|
|
3204
|
+
if (!await fs.pathExists(webAppDir)) return;
|
|
3205
|
+
await addPackageDependency({
|
|
3206
|
+
devDependencies: [
|
|
3207
|
+
"alchemy",
|
|
3208
|
+
"nitropack",
|
|
3209
|
+
"dotenv"
|
|
3210
|
+
],
|
|
3211
|
+
projectDir: webAppDir
|
|
3212
|
+
});
|
|
3213
|
+
const pkgPath = path.join(webAppDir, "package.json");
|
|
3214
|
+
if (await fs.pathExists(pkgPath)) {
|
|
3215
|
+
const pkg = await fs.readJson(pkgPath);
|
|
3216
|
+
pkg.scripts = {
|
|
3217
|
+
...pkg.scripts,
|
|
3218
|
+
deploy: "alchemy deploy",
|
|
3219
|
+
destroy: "alchemy destroy",
|
|
3220
|
+
"alchemy:dev": "alchemy dev"
|
|
3221
|
+
};
|
|
3222
|
+
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
3223
|
+
}
|
|
3224
|
+
const viteConfigPath = path.join(webAppDir, "vite.config.ts");
|
|
3225
|
+
if (await fs.pathExists(viteConfigPath)) try {
|
|
3226
|
+
const project = new Project({ manipulationSettings: {
|
|
3227
|
+
indentationText: IndentationText.TwoSpaces,
|
|
3228
|
+
quoteKind: QuoteKind.Double
|
|
3229
|
+
} });
|
|
3230
|
+
project.addSourceFileAtPath(viteConfigPath);
|
|
3231
|
+
const sourceFile = project.getSourceFileOrThrow(viteConfigPath);
|
|
3232
|
+
const alchemyImport = sourceFile.getImportDeclaration("alchemy/cloudflare/tanstack-start");
|
|
3233
|
+
if (!alchemyImport) sourceFile.addImportDeclaration({
|
|
3234
|
+
moduleSpecifier: "alchemy/cloudflare/tanstack-start",
|
|
3235
|
+
defaultImport: "alchemy"
|
|
3236
|
+
});
|
|
3237
|
+
else alchemyImport.setModuleSpecifier("alchemy/cloudflare/tanstack-start");
|
|
3238
|
+
const exportAssignment = sourceFile.getExportAssignment((d) => !d.isExportEquals());
|
|
3239
|
+
if (!exportAssignment) return;
|
|
3240
|
+
const defineConfigCall = exportAssignment.getExpression();
|
|
3241
|
+
if (!Node.isCallExpression(defineConfigCall) || defineConfigCall.getExpression().getText() !== "defineConfig") return;
|
|
3242
|
+
let configObject = defineConfigCall.getArguments()[0];
|
|
3243
|
+
if (!configObject) configObject = defineConfigCall.addArgument("{}");
|
|
3244
|
+
if (Node.isObjectLiteralExpression(configObject)) {
|
|
3245
|
+
if (!configObject.getProperty("build")) configObject.addPropertyAssignment({
|
|
3246
|
+
name: "build",
|
|
3247
|
+
initializer: `{
|
|
3248
|
+
target: "esnext",
|
|
3249
|
+
rollupOptions: {
|
|
3250
|
+
external: ["node:async_hooks", "cloudflare:workers"],
|
|
3251
|
+
},
|
|
3252
|
+
}`
|
|
3253
|
+
});
|
|
3254
|
+
const pluginsProperty = configObject.getProperty("plugins");
|
|
3255
|
+
if (pluginsProperty && Node.isPropertyAssignment(pluginsProperty)) {
|
|
3256
|
+
const initializer = pluginsProperty.getInitializer();
|
|
3257
|
+
if (Node.isArrayLiteralExpression(initializer)) {
|
|
3258
|
+
const hasShim = initializer.getElements().some((el) => el.getText().includes("alchemy"));
|
|
3259
|
+
if (!hasShim) initializer.addElement("alchemy()");
|
|
3260
|
+
const tanstackElements = initializer.getElements().filter((el) => el.getText().includes("tanstackStart"));
|
|
3261
|
+
tanstackElements.forEach((element) => {
|
|
3262
|
+
if (Node.isCallExpression(element)) {
|
|
3263
|
+
const args = element.getArguments();
|
|
3264
|
+
if (args.length === 0) element.addArgument(`{
|
|
3265
|
+
target: "cloudflare-module",
|
|
3266
|
+
customViteReactPlugin: true,
|
|
3267
|
+
}`);
|
|
3268
|
+
else if (args.length === 1 && Node.isObjectLiteralExpression(args[0])) {
|
|
3269
|
+
const configObj = args[0];
|
|
3270
|
+
if (!configObj.getProperty("target")) configObj.addPropertyAssignment({
|
|
3271
|
+
name: "target",
|
|
3272
|
+
initializer: "\"cloudflare-module\""
|
|
3273
|
+
});
|
|
3274
|
+
if (!configObj.getProperty("customViteReactPlugin")) configObj.addPropertyAssignment({
|
|
3275
|
+
name: "customViteReactPlugin",
|
|
3276
|
+
initializer: "true"
|
|
3277
|
+
});
|
|
3278
|
+
}
|
|
3279
|
+
}
|
|
3280
|
+
});
|
|
3281
|
+
}
|
|
3282
|
+
} else configObject.addPropertyAssignment({
|
|
3283
|
+
name: "plugins",
|
|
3284
|
+
initializer: "[alchemy()]"
|
|
3285
|
+
});
|
|
3286
|
+
}
|
|
3287
|
+
await project.save();
|
|
3288
|
+
} catch (error) {
|
|
3289
|
+
console.warn("Failed to update vite.config.ts:", error);
|
|
3290
|
+
}
|
|
3291
|
+
const nitroConfigPath = path.join(webAppDir, "nitro.config.ts");
|
|
3292
|
+
const nitroConfigContent = `import { defineNitroConfig } from "nitropack/config";
|
|
3293
|
+
|
|
3294
|
+
export default defineNitroConfig({
|
|
3295
|
+
preset: "cloudflare-module",
|
|
3296
|
+
cloudflare: {
|
|
3297
|
+
nodeCompat: true,
|
|
3298
|
+
},
|
|
3299
|
+
});
|
|
3300
|
+
`;
|
|
3301
|
+
await fs.writeFile(nitroConfigPath, nitroConfigContent, "utf-8");
|
|
3302
|
+
}
|
|
3303
|
+
|
|
3304
|
+
//#endregion
|
|
3305
|
+
//#region src/helpers/deployment/alchemy/alchemy-combined-setup.ts
|
|
3306
|
+
async function setupCombinedAlchemyDeploy(projectDir, packageManager, config) {
|
|
3307
|
+
await addPackageDependency({
|
|
3308
|
+
devDependencies: ["alchemy", "dotenv"],
|
|
3309
|
+
projectDir
|
|
3310
|
+
});
|
|
3311
|
+
const rootPkgPath = path.join(projectDir, "package.json");
|
|
3312
|
+
if (await fs.pathExists(rootPkgPath)) {
|
|
3313
|
+
const pkg = await fs.readJson(rootPkgPath);
|
|
3314
|
+
pkg.scripts = {
|
|
3315
|
+
...pkg.scripts,
|
|
3316
|
+
deploy: "alchemy deploy",
|
|
3317
|
+
destroy: "alchemy destroy",
|
|
3318
|
+
"alchemy:dev": "alchemy dev"
|
|
3319
|
+
};
|
|
3320
|
+
await fs.writeJson(rootPkgPath, pkg, { spaces: 2 });
|
|
3321
|
+
}
|
|
3322
|
+
const serverDir = path.join(projectDir, "apps/server");
|
|
3323
|
+
if (await fs.pathExists(serverDir)) await setupAlchemyServerDeploy(serverDir, packageManager);
|
|
3324
|
+
const frontend = config.frontend;
|
|
3325
|
+
const isNext = frontend.includes("next");
|
|
3326
|
+
const isNuxt = frontend.includes("nuxt");
|
|
3327
|
+
const isSvelte = frontend.includes("svelte");
|
|
3328
|
+
const isTanstackRouter = frontend.includes("tanstack-router");
|
|
3329
|
+
const isTanstackStart = frontend.includes("tanstack-start");
|
|
3330
|
+
const isReactRouter = frontend.includes("react-router");
|
|
3331
|
+
const isSolid = frontend.includes("solid");
|
|
3332
|
+
if (isNext) await setupNextAlchemyDeploy(projectDir, packageManager);
|
|
3333
|
+
else if (isNuxt) await setupNuxtAlchemyDeploy(projectDir, packageManager);
|
|
3334
|
+
else if (isSvelte) await setupSvelteAlchemyDeploy(projectDir, packageManager);
|
|
3335
|
+
else if (isTanstackStart) await setupTanStackStartAlchemyDeploy(projectDir, packageManager);
|
|
3336
|
+
else if (isTanstackRouter) await setupTanStackRouterAlchemyDeploy(projectDir, packageManager);
|
|
3337
|
+
else if (isReactRouter) await setupReactRouterAlchemyDeploy(projectDir, packageManager);
|
|
3338
|
+
else if (isSolid) await setupSolidAlchemyDeploy(projectDir, packageManager);
|
|
3339
|
+
}
|
|
3340
|
+
|
|
3341
|
+
//#endregion
|
|
3342
|
+
//#region src/helpers/deployment/workers/workers-next-setup.ts
|
|
3343
|
+
async function setupNextWorkersDeploy(projectDir, _packageManager) {
|
|
3344
|
+
const webAppDir = path.join(projectDir, "apps/web");
|
|
3345
|
+
if (!await fs$1.pathExists(webAppDir)) return;
|
|
3346
|
+
await addPackageDependency({
|
|
3347
|
+
dependencies: ["@opennextjs/cloudflare"],
|
|
3348
|
+
devDependencies: ["wrangler"],
|
|
3349
|
+
projectDir: webAppDir
|
|
3350
|
+
});
|
|
3351
|
+
const packageJsonPath = path.join(webAppDir, "package.json");
|
|
3352
|
+
if (await fs$1.pathExists(packageJsonPath)) {
|
|
3353
|
+
const pkg = await fs$1.readJson(packageJsonPath);
|
|
3354
|
+
pkg.scripts = {
|
|
3355
|
+
...pkg.scripts,
|
|
3356
|
+
preview: "opennextjs-cloudflare build && opennextjs-cloudflare preview",
|
|
3357
|
+
deploy: "opennextjs-cloudflare build && opennextjs-cloudflare deploy",
|
|
3358
|
+
upload: "opennextjs-cloudflare build && opennextjs-cloudflare upload",
|
|
3359
|
+
"cf-typegen": "wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts"
|
|
3360
|
+
};
|
|
3361
|
+
await fs$1.writeJson(packageJsonPath, pkg, { spaces: 2 });
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
3364
|
+
|
|
3365
|
+
//#endregion
|
|
3366
|
+
//#region src/helpers/deployment/workers/workers-nuxt-setup.ts
|
|
3367
|
+
async function setupNuxtWorkersDeploy(projectDir, packageManager) {
|
|
3368
|
+
const webAppDir = path.join(projectDir, "apps/web");
|
|
3369
|
+
if (!await fs.pathExists(webAppDir)) return;
|
|
3370
|
+
await addPackageDependency({
|
|
3371
|
+
devDependencies: ["nitro-cloudflare-dev", "wrangler"],
|
|
3372
|
+
projectDir: webAppDir
|
|
3373
|
+
});
|
|
3374
|
+
const pkgPath = path.join(webAppDir, "package.json");
|
|
3375
|
+
if (await fs.pathExists(pkgPath)) {
|
|
3376
|
+
const pkg = await fs.readJson(pkgPath);
|
|
3377
|
+
pkg.scripts = {
|
|
3378
|
+
...pkg.scripts,
|
|
3379
|
+
deploy: `${packageManager} run build && wrangler deploy`,
|
|
3380
|
+
"cf-typegen": "wrangler types"
|
|
3381
|
+
};
|
|
3382
|
+
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
3383
|
+
}
|
|
3384
|
+
const nuxtConfigPath = path.join(webAppDir, "nuxt.config.ts");
|
|
3385
|
+
if (!await fs.pathExists(nuxtConfigPath)) return;
|
|
3386
|
+
const sourceFile = tsProject.addSourceFileAtPathIfExists(nuxtConfigPath);
|
|
3387
|
+
if (!sourceFile) return;
|
|
3388
|
+
const defineCall = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression).find((expr) => {
|
|
3389
|
+
const expression = expr.getExpression();
|
|
3390
|
+
return Node.isIdentifier(expression) && expression.getText() === "defineNuxtConfig";
|
|
3391
|
+
});
|
|
3392
|
+
if (!defineCall) return;
|
|
3393
|
+
const configObj = defineCall.getArguments()[0];
|
|
3394
|
+
if (!configObj) return;
|
|
3395
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
3396
|
+
const compatProp = configObj.getProperty("compatibilityDate");
|
|
3397
|
+
if (compatProp && compatProp.getKind() === SyntaxKind.PropertyAssignment) compatProp.setInitializer(`'${today}'`);
|
|
3398
|
+
else configObj.addPropertyAssignment({
|
|
3399
|
+
name: "compatibilityDate",
|
|
3400
|
+
initializer: `'${today}'`
|
|
3401
|
+
});
|
|
3402
|
+
const nitroInitializer = `{
|
|
3403
|
+
preset: "cloudflare_module",
|
|
3404
|
+
cloudflare: {
|
|
3405
|
+
deployConfig: true,
|
|
3406
|
+
nodeCompat: true
|
|
3407
|
+
}
|
|
3408
|
+
}`;
|
|
3409
|
+
const nitroProp = configObj.getProperty("nitro");
|
|
3410
|
+
if (nitroProp && nitroProp.getKind() === SyntaxKind.PropertyAssignment) nitroProp.setInitializer(nitroInitializer);
|
|
3411
|
+
else configObj.addPropertyAssignment({
|
|
3412
|
+
name: "nitro",
|
|
3413
|
+
initializer: nitroInitializer
|
|
3414
|
+
});
|
|
3415
|
+
const modulesProp = configObj.getProperty("modules");
|
|
3416
|
+
if (modulesProp && modulesProp.getKind() === SyntaxKind.PropertyAssignment) {
|
|
3417
|
+
const arrayExpr = modulesProp.getFirstDescendantByKind(SyntaxKind.ArrayLiteralExpression);
|
|
3418
|
+
if (arrayExpr) {
|
|
3419
|
+
const alreadyHas = arrayExpr.getElements().some((el) => el.getText().replace(/['"`]/g, "") === "nitro-cloudflare-dev");
|
|
3420
|
+
if (!alreadyHas) arrayExpr.addElement("'nitro-cloudflare-dev'");
|
|
3421
|
+
}
|
|
3422
|
+
} else configObj.addPropertyAssignment({
|
|
3423
|
+
name: "modules",
|
|
3424
|
+
initializer: "['nitro-cloudflare-dev']"
|
|
3425
|
+
});
|
|
3426
|
+
await tsProject.save();
|
|
3427
|
+
}
|
|
3428
|
+
|
|
3429
|
+
//#endregion
|
|
3430
|
+
//#region src/helpers/deployment/workers/workers-svelte-setup.ts
|
|
3431
|
+
async function setupSvelteWorkersDeploy(projectDir, packageManager) {
|
|
3432
|
+
const webAppDir = path.join(projectDir, "apps/web");
|
|
3433
|
+
if (!await fs.pathExists(webAppDir)) return;
|
|
3434
|
+
await addPackageDependency({
|
|
3435
|
+
devDependencies: ["@sveltejs/adapter-cloudflare", "wrangler"],
|
|
3436
|
+
projectDir: webAppDir
|
|
3437
|
+
});
|
|
3438
|
+
const pkgPath = path.join(webAppDir, "package.json");
|
|
3439
|
+
if (await fs.pathExists(pkgPath)) {
|
|
3440
|
+
const pkg = await fs.readJson(pkgPath);
|
|
3441
|
+
pkg.scripts = {
|
|
3442
|
+
...pkg.scripts,
|
|
3443
|
+
deploy: `${packageManager} run build && wrangler deploy`,
|
|
3444
|
+
"cf-typegen": "wrangler types ./src/worker-configuration.d.ts"
|
|
3445
|
+
};
|
|
3446
|
+
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
3447
|
+
}
|
|
3448
|
+
const possibleConfigFiles = [path.join(webAppDir, "svelte.config.js"), path.join(webAppDir, "svelte.config.ts")];
|
|
3449
|
+
const existingConfigPath = (await Promise.all(possibleConfigFiles.map(async (p) => await fs.pathExists(p) ? p : ""))).find((p) => p);
|
|
3450
|
+
if (existingConfigPath) {
|
|
3451
|
+
const sourceFile = tsProject.addSourceFileAtPathIfExists(existingConfigPath);
|
|
3452
|
+
if (!sourceFile) return;
|
|
3453
|
+
const adapterImport = sourceFile.getImportDeclarations().find((imp) => ["@sveltejs/adapter-auto", "@sveltejs/adapter-node"].includes(imp.getModuleSpecifierValue()));
|
|
3454
|
+
if (adapterImport) adapterImport.setModuleSpecifier("@sveltejs/adapter-cloudflare");
|
|
3455
|
+
else {
|
|
3456
|
+
const alreadyHasCloudflare = sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === "@sveltejs/adapter-cloudflare");
|
|
3457
|
+
if (!alreadyHasCloudflare) sourceFile.insertImportDeclaration(0, {
|
|
3458
|
+
defaultImport: "adapter",
|
|
3459
|
+
moduleSpecifier: "@sveltejs/adapter-cloudflare"
|
|
3460
|
+
});
|
|
3461
|
+
}
|
|
3462
|
+
await tsProject.save();
|
|
3463
|
+
}
|
|
3464
|
+
}
|
|
3465
|
+
|
|
3466
|
+
//#endregion
|
|
3467
|
+
//#region src/helpers/deployment/workers/workers-tanstack-start-setup.ts
|
|
3468
|
+
async function setupTanstackStartWorkersDeploy(projectDir, packageManager) {
|
|
3469
|
+
const webAppDir = path.join(projectDir, "apps/web");
|
|
3470
|
+
if (!await fs.pathExists(webAppDir)) return;
|
|
3471
|
+
await addPackageDependency({
|
|
3472
|
+
devDependencies: ["wrangler"],
|
|
3473
|
+
projectDir: webAppDir
|
|
3474
|
+
});
|
|
3475
|
+
const pkgPath = path.join(webAppDir, "package.json");
|
|
3476
|
+
if (await fs.pathExists(pkgPath)) {
|
|
3477
|
+
const pkg = await fs.readJson(pkgPath);
|
|
3478
|
+
pkg.scripts = {
|
|
3479
|
+
...pkg.scripts,
|
|
3480
|
+
deploy: `${packageManager} run build && wrangler deploy`,
|
|
3481
|
+
"cf-typegen": "wrangler types --env-interface Env"
|
|
3482
|
+
};
|
|
3483
|
+
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
3484
|
+
}
|
|
3485
|
+
const viteConfigPath = path.join(webAppDir, "vite.config.ts");
|
|
3486
|
+
if (!await fs.pathExists(viteConfigPath)) return;
|
|
3487
|
+
const sourceFile = tsProject.addSourceFileAtPathIfExists(viteConfigPath);
|
|
3488
|
+
if (!sourceFile) return;
|
|
3489
|
+
const defineCall = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression).find((expr) => {
|
|
3490
|
+
const expression = expr.getExpression();
|
|
3491
|
+
return Node.isIdentifier(expression) && expression.getText() === "defineConfig";
|
|
3492
|
+
});
|
|
3493
|
+
if (!defineCall) return;
|
|
3494
|
+
const configObj = defineCall.getArguments()[0];
|
|
3495
|
+
if (!configObj) return;
|
|
3496
|
+
const pluginsArray = ensureArrayProperty(configObj, "plugins");
|
|
3497
|
+
const tanstackPluginIndex = pluginsArray.getElements().findIndex((el) => el.getText().includes("tanstackStart("));
|
|
3498
|
+
const tanstackPluginText = "tanstackStart({ target: \"cloudflare-module\" })";
|
|
3499
|
+
if (tanstackPluginIndex === -1) pluginsArray.addElement(tanstackPluginText);
|
|
3500
|
+
else pluginsArray.getElements()[tanstackPluginIndex].replaceWithText(tanstackPluginText);
|
|
3501
|
+
await tsProject.save();
|
|
3502
|
+
}
|
|
3503
|
+
|
|
3504
|
+
//#endregion
|
|
3505
|
+
//#region src/helpers/deployment/workers/workers-vite-setup.ts
|
|
3506
|
+
async function setupWorkersVitePlugin(projectDir) {
|
|
3507
|
+
const webAppDir = path.join(projectDir, "apps/web");
|
|
3508
|
+
const viteConfigPath = path.join(webAppDir, "vite.config.ts");
|
|
3509
|
+
if (!await fs.pathExists(viteConfigPath)) throw new Error("vite.config.ts not found in web app directory");
|
|
3510
|
+
await addPackageDependency({
|
|
3511
|
+
devDependencies: ["@cloudflare/vite-plugin", "wrangler"],
|
|
3512
|
+
projectDir: webAppDir
|
|
3513
|
+
});
|
|
3514
|
+
const sourceFile = tsProject.addSourceFileAtPathIfExists(viteConfigPath);
|
|
3515
|
+
if (!sourceFile) throw new Error("vite.config.ts not found in web app directory");
|
|
3516
|
+
const hasCloudflareImport = sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === "@cloudflare/vite-plugin");
|
|
3517
|
+
if (!hasCloudflareImport) sourceFile.insertImportDeclaration(0, {
|
|
2806
3518
|
namedImports: ["cloudflare"],
|
|
2807
3519
|
moduleSpecifier: "@cloudflare/vite-plugin"
|
|
2808
3520
|
});
|
|
@@ -2821,12 +3533,16 @@ async function setupWorkersVitePlugin(projectDir) {
|
|
|
2821
3533
|
}
|
|
2822
3534
|
|
|
2823
3535
|
//#endregion
|
|
2824
|
-
//#region src/helpers/
|
|
3536
|
+
//#region src/helpers/deployment/web-deploy-setup.ts
|
|
2825
3537
|
async function setupWebDeploy(config) {
|
|
2826
|
-
const { webDeploy, frontend, projectDir } = config;
|
|
3538
|
+
const { webDeploy, serverDeploy, frontend, projectDir } = config;
|
|
2827
3539
|
const { packageManager } = config;
|
|
2828
3540
|
if (webDeploy === "none") return;
|
|
2829
|
-
if (webDeploy !== "
|
|
3541
|
+
if (webDeploy !== "wrangler" && webDeploy !== "alchemy") return;
|
|
3542
|
+
if (webDeploy === "alchemy" && serverDeploy === "alchemy") {
|
|
3543
|
+
await setupCombinedAlchemyDeploy(projectDir, packageManager, config);
|
|
3544
|
+
return;
|
|
3545
|
+
}
|
|
2830
3546
|
const isNext = frontend.includes("next");
|
|
2831
3547
|
const isNuxt = frontend.includes("nuxt");
|
|
2832
3548
|
const isSvelte = frontend.includes("svelte");
|
|
@@ -2834,11 +3550,21 @@ async function setupWebDeploy(config) {
|
|
|
2834
3550
|
const isTanstackStart = frontend.includes("tanstack-start");
|
|
2835
3551
|
const isReactRouter = frontend.includes("react-router");
|
|
2836
3552
|
const isSolid = frontend.includes("solid");
|
|
2837
|
-
if (
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
3553
|
+
if (webDeploy === "wrangler") {
|
|
3554
|
+
if (isNext) await setupNextWorkersDeploy(projectDir, packageManager);
|
|
3555
|
+
else if (isNuxt) await setupNuxtWorkersDeploy(projectDir, packageManager);
|
|
3556
|
+
else if (isSvelte) await setupSvelteWorkersDeploy(projectDir, packageManager);
|
|
3557
|
+
else if (isTanstackStart) await setupTanstackStartWorkersDeploy(projectDir, packageManager);
|
|
3558
|
+
else if (isTanstackRouter || isReactRouter || isSolid) await setupWorkersWebDeploy(projectDir, packageManager);
|
|
3559
|
+
} else if (webDeploy === "alchemy") {
|
|
3560
|
+
if (isNext) await setupNextAlchemyDeploy(projectDir, packageManager);
|
|
3561
|
+
else if (isNuxt) await setupNuxtAlchemyDeploy(projectDir, packageManager);
|
|
3562
|
+
else if (isSvelte) await setupSvelteAlchemyDeploy(projectDir, packageManager);
|
|
3563
|
+
else if (isTanstackStart) await setupTanStackStartAlchemyDeploy(projectDir, packageManager);
|
|
3564
|
+
else if (isTanstackRouter) await setupTanStackRouterAlchemyDeploy(projectDir, packageManager);
|
|
3565
|
+
else if (isReactRouter) await setupReactRouterAlchemyDeploy(projectDir, packageManager);
|
|
3566
|
+
else if (isSolid) await setupSolidAlchemyDeploy(projectDir, packageManager);
|
|
3567
|
+
}
|
|
2842
3568
|
}
|
|
2843
3569
|
async function setupWorkersWebDeploy(projectDir, pkgManager) {
|
|
2844
3570
|
const webAppDir = path.join(projectDir, "apps/web");
|
|
@@ -2855,30 +3581,9 @@ async function setupWorkersWebDeploy(projectDir, pkgManager) {
|
|
|
2855
3581
|
}
|
|
2856
3582
|
await setupWorkersVitePlugin(projectDir);
|
|
2857
3583
|
}
|
|
2858
|
-
async function setupNextWorkersDeploy(projectDir, _packageManager) {
|
|
2859
|
-
const webAppDir = path.join(projectDir, "apps/web");
|
|
2860
|
-
if (!await fs.pathExists(webAppDir)) return;
|
|
2861
|
-
await addPackageDependency({
|
|
2862
|
-
dependencies: ["@opennextjs/cloudflare"],
|
|
2863
|
-
devDependencies: ["wrangler"],
|
|
2864
|
-
projectDir: webAppDir
|
|
2865
|
-
});
|
|
2866
|
-
const packageJsonPath = path.join(webAppDir, "package.json");
|
|
2867
|
-
if (await fs.pathExists(packageJsonPath)) {
|
|
2868
|
-
const pkg = await fs.readJson(packageJsonPath);
|
|
2869
|
-
pkg.scripts = {
|
|
2870
|
-
...pkg.scripts,
|
|
2871
|
-
preview: "opennextjs-cloudflare build && opennextjs-cloudflare preview",
|
|
2872
|
-
deploy: "opennextjs-cloudflare build && opennextjs-cloudflare deploy",
|
|
2873
|
-
upload: "opennextjs-cloudflare build && opennextjs-cloudflare upload",
|
|
2874
|
-
"cf-typegen": "wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts"
|
|
2875
|
-
};
|
|
2876
|
-
await fs.writeJson(packageJsonPath, pkg, { spaces: 2 });
|
|
2877
|
-
}
|
|
2878
|
-
}
|
|
2879
3584
|
|
|
2880
3585
|
//#endregion
|
|
2881
|
-
//#region src/helpers/
|
|
3586
|
+
//#region src/helpers/core/add-deployment.ts
|
|
2882
3587
|
async function addDeploymentToProject(input) {
|
|
2883
3588
|
try {
|
|
2884
3589
|
const projectDir = input.projectDir || process.cwd();
|
|
@@ -2886,252 +3591,97 @@ async function addDeploymentToProject(input) {
|
|
|
2886
3591
|
if (!isBetterTStack) exitWithError("This doesn't appear to be a Better-T Stack project. Please run this command from the root of a Better-T Stack project.");
|
|
2887
3592
|
const detectedConfig = await detectProjectConfig(projectDir);
|
|
2888
3593
|
if (!detectedConfig) exitWithError("Could not detect the project configuration. Please ensure this is a valid Better-T Stack project.");
|
|
2889
|
-
if (detectedConfig.webDeploy === input.webDeploy) exitWithError(`${input.webDeploy} deployment is already configured for this project.`);
|
|
3594
|
+
if (input.webDeploy && detectedConfig.webDeploy === input.webDeploy) exitWithError(`${input.webDeploy} web deployment is already configured for this project.`);
|
|
3595
|
+
if (input.serverDeploy && detectedConfig.serverDeploy === input.serverDeploy) exitWithError(`${input.serverDeploy} server deployment is already configured for this project.`);
|
|
2890
3596
|
const config = {
|
|
2891
3597
|
projectName: detectedConfig.projectName || path.basename(projectDir),
|
|
2892
3598
|
projectDir,
|
|
2893
3599
|
relativePath: ".",
|
|
2894
|
-
database: detectedConfig.database || "none",
|
|
2895
|
-
orm: detectedConfig.orm || "none",
|
|
2896
|
-
backend: detectedConfig.backend || "none",
|
|
2897
|
-
runtime: detectedConfig.runtime || "none",
|
|
2898
|
-
frontend: detectedConfig.frontend || [],
|
|
2899
|
-
addons: detectedConfig.addons || [],
|
|
2900
|
-
examples: detectedConfig.examples || [],
|
|
2901
|
-
auth: detectedConfig.auth || false,
|
|
2902
|
-
git: false,
|
|
2903
|
-
packageManager: input.packageManager || detectedConfig.packageManager || "npm",
|
|
2904
|
-
install: input.install || false,
|
|
2905
|
-
dbSetup: detectedConfig.dbSetup || "none",
|
|
2906
|
-
api: detectedConfig.api || "none",
|
|
2907
|
-
webDeploy: input.webDeploy
|
|
2908
|
-
|
|
2909
|
-
log.info(pc.green(`Adding ${input.webDeploy} deployment to ${config.frontend.join("/")}`));
|
|
2910
|
-
await setupDeploymentTemplates(projectDir, config);
|
|
2911
|
-
await setupWebDeploy(config);
|
|
2912
|
-
await updateBtsConfig(projectDir, { webDeploy: input.webDeploy });
|
|
2913
|
-
if (config.install) await installDependencies({
|
|
2914
|
-
projectDir,
|
|
2915
|
-
packageManager: config.packageManager
|
|
2916
|
-
});
|
|
2917
|
-
else if (!input.suppressInstallMessage) log.info(pc.yellow(`Run ${pc.bold(`${config.packageManager} install`)} to install dependencies`));
|
|
2918
|
-
} catch (error) {
|
|
2919
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2920
|
-
exitWithError(`Error adding deployment: ${message}`);
|
|
2921
|
-
}
|
|
2922
|
-
}
|
|
2923
|
-
|
|
2924
|
-
//#endregion
|
|
2925
|
-
//#region src/helpers/setup/api-setup.ts
|
|
2926
|
-
async function setupApi(config) {
|
|
2927
|
-
const { api, projectName, frontend, backend, packageManager, projectDir } = config;
|
|
2928
|
-
const isConvex = backend === "convex";
|
|
2929
|
-
const webDir = path.join(projectDir, "apps/web");
|
|
2930
|
-
const nativeDir = path.join(projectDir, "apps/native");
|
|
2931
|
-
const webDirExists = await fs.pathExists(webDir);
|
|
2932
|
-
const nativeDirExists = await fs.pathExists(nativeDir);
|
|
2933
|
-
const hasReactWeb = frontend.some((f) => [
|
|
2934
|
-
"tanstack-router",
|
|
2935
|
-
"react-router",
|
|
2936
|
-
"tanstack-start",
|
|
2937
|
-
"next"
|
|
2938
|
-
].includes(f));
|
|
2939
|
-
const hasNuxtWeb = frontend.includes("nuxt");
|
|
2940
|
-
const hasSvelteWeb = frontend.includes("svelte");
|
|
2941
|
-
const hasSolidWeb = frontend.includes("solid");
|
|
2942
|
-
if (!isConvex && api !== "none") {
|
|
2943
|
-
const serverDir = path.join(projectDir, "apps/server");
|
|
2944
|
-
const serverDirExists = await fs.pathExists(serverDir);
|
|
2945
|
-
if (serverDirExists) {
|
|
2946
|
-
if (api === "orpc") await addPackageDependency({
|
|
2947
|
-
dependencies: ["@orpc/server", "@orpc/client"],
|
|
2948
|
-
projectDir: serverDir
|
|
2949
|
-
});
|
|
2950
|
-
else if (api === "trpc") {
|
|
2951
|
-
await addPackageDependency({
|
|
2952
|
-
dependencies: ["@trpc/server", "@trpc/client"],
|
|
2953
|
-
projectDir: serverDir
|
|
2954
|
-
});
|
|
2955
|
-
if (config.backend === "hono") await addPackageDependency({
|
|
2956
|
-
dependencies: ["@hono/trpc-server"],
|
|
2957
|
-
projectDir: serverDir
|
|
2958
|
-
});
|
|
2959
|
-
else if (config.backend === "elysia") await addPackageDependency({
|
|
2960
|
-
dependencies: ["@elysiajs/trpc"],
|
|
2961
|
-
projectDir: serverDir
|
|
2962
|
-
});
|
|
2963
|
-
}
|
|
2964
|
-
}
|
|
2965
|
-
if (webDirExists) {
|
|
2966
|
-
if (hasReactWeb) {
|
|
2967
|
-
if (api === "orpc") await addPackageDependency({
|
|
2968
|
-
dependencies: [
|
|
2969
|
-
"@orpc/tanstack-query",
|
|
2970
|
-
"@orpc/client",
|
|
2971
|
-
"@orpc/server"
|
|
2972
|
-
],
|
|
2973
|
-
projectDir: webDir
|
|
2974
|
-
});
|
|
2975
|
-
else if (api === "trpc") await addPackageDependency({
|
|
2976
|
-
dependencies: [
|
|
2977
|
-
"@trpc/tanstack-react-query",
|
|
2978
|
-
"@trpc/client",
|
|
2979
|
-
"@trpc/server"
|
|
2980
|
-
],
|
|
2981
|
-
projectDir: webDir
|
|
2982
|
-
});
|
|
2983
|
-
} else if (hasNuxtWeb) {
|
|
2984
|
-
if (api === "orpc") await addPackageDependency({
|
|
2985
|
-
dependencies: [
|
|
2986
|
-
"@tanstack/vue-query",
|
|
2987
|
-
"@tanstack/vue-query-devtools",
|
|
2988
|
-
"@orpc/tanstack-query",
|
|
2989
|
-
"@orpc/client",
|
|
2990
|
-
"@orpc/server"
|
|
2991
|
-
],
|
|
2992
|
-
projectDir: webDir
|
|
2993
|
-
});
|
|
2994
|
-
} else if (hasSvelteWeb) {
|
|
2995
|
-
if (api === "orpc") await addPackageDependency({
|
|
2996
|
-
dependencies: [
|
|
2997
|
-
"@orpc/tanstack-query",
|
|
2998
|
-
"@orpc/client",
|
|
2999
|
-
"@orpc/server",
|
|
3000
|
-
"@tanstack/svelte-query"
|
|
3001
|
-
],
|
|
3002
|
-
projectDir: webDir
|
|
3003
|
-
});
|
|
3004
|
-
} else if (hasSolidWeb) {
|
|
3005
|
-
if (api === "orpc") await addPackageDependency({
|
|
3006
|
-
dependencies: [
|
|
3007
|
-
"@orpc/tanstack-query",
|
|
3008
|
-
"@orpc/client",
|
|
3009
|
-
"@orpc/server",
|
|
3010
|
-
"@tanstack/solid-query"
|
|
3011
|
-
],
|
|
3012
|
-
projectDir: webDir
|
|
3013
|
-
});
|
|
3014
|
-
}
|
|
3015
|
-
}
|
|
3016
|
-
if (nativeDirExists) {
|
|
3017
|
-
if (api === "trpc") await addPackageDependency({
|
|
3018
|
-
dependencies: [
|
|
3019
|
-
"@trpc/tanstack-react-query",
|
|
3020
|
-
"@trpc/client",
|
|
3021
|
-
"@trpc/server"
|
|
3022
|
-
],
|
|
3023
|
-
projectDir: nativeDir
|
|
3024
|
-
});
|
|
3025
|
-
else if (api === "orpc") await addPackageDependency({
|
|
3026
|
-
dependencies: [
|
|
3027
|
-
"@orpc/tanstack-query",
|
|
3028
|
-
"@orpc/client",
|
|
3029
|
-
"@orpc/server"
|
|
3030
|
-
],
|
|
3031
|
-
projectDir: nativeDir
|
|
3032
|
-
});
|
|
3033
|
-
}
|
|
3034
|
-
}
|
|
3035
|
-
const reactBasedFrontends = [
|
|
3036
|
-
"react-router",
|
|
3037
|
-
"tanstack-router",
|
|
3038
|
-
"tanstack-start",
|
|
3039
|
-
"next",
|
|
3040
|
-
"native-nativewind",
|
|
3041
|
-
"native-unistyles"
|
|
3042
|
-
];
|
|
3043
|
-
const needsSolidQuery = frontend.includes("solid");
|
|
3044
|
-
const needsReactQuery = frontend.some((f) => reactBasedFrontends.includes(f));
|
|
3045
|
-
if (needsReactQuery && !isConvex) {
|
|
3046
|
-
const reactQueryDeps = ["@tanstack/react-query"];
|
|
3047
|
-
const reactQueryDevDeps = ["@tanstack/react-query-devtools"];
|
|
3048
|
-
const hasReactWeb$1 = frontend.some((f) => f !== "native-nativewind" && f !== "native-unistyles" && reactBasedFrontends.includes(f));
|
|
3049
|
-
const hasNative = frontend.includes("native-nativewind") || frontend.includes("native-unistyles");
|
|
3050
|
-
if (hasReactWeb$1 && webDirExists) {
|
|
3051
|
-
const webPkgJsonPath = path.join(webDir, "package.json");
|
|
3052
|
-
if (await fs.pathExists(webPkgJsonPath)) try {
|
|
3053
|
-
await addPackageDependency({
|
|
3054
|
-
dependencies: reactQueryDeps,
|
|
3055
|
-
devDependencies: reactQueryDevDeps,
|
|
3056
|
-
projectDir: webDir
|
|
3057
|
-
});
|
|
3058
|
-
} catch (_error) {}
|
|
3059
|
-
}
|
|
3060
|
-
if (hasNative && nativeDirExists) {
|
|
3061
|
-
const nativePkgJsonPath = path.join(nativeDir, "package.json");
|
|
3062
|
-
if (await fs.pathExists(nativePkgJsonPath)) try {
|
|
3063
|
-
await addPackageDependency({
|
|
3064
|
-
dependencies: reactQueryDeps,
|
|
3065
|
-
projectDir: nativeDir
|
|
3066
|
-
});
|
|
3067
|
-
} catch (_error) {}
|
|
3068
|
-
}
|
|
3069
|
-
}
|
|
3070
|
-
if (needsSolidQuery && !isConvex) {
|
|
3071
|
-
const solidQueryDeps = ["@tanstack/solid-query"];
|
|
3072
|
-
const solidQueryDevDeps = ["@tanstack/solid-query-devtools"];
|
|
3073
|
-
if (webDirExists) {
|
|
3074
|
-
const webPkgJsonPath = path.join(webDir, "package.json");
|
|
3075
|
-
if (await fs.pathExists(webPkgJsonPath)) try {
|
|
3076
|
-
await addPackageDependency({
|
|
3077
|
-
dependencies: solidQueryDeps,
|
|
3078
|
-
devDependencies: solidQueryDevDeps,
|
|
3079
|
-
projectDir: webDir
|
|
3080
|
-
});
|
|
3081
|
-
} catch (_error) {}
|
|
3082
|
-
}
|
|
3083
|
-
}
|
|
3084
|
-
if (isConvex) {
|
|
3085
|
-
if (webDirExists) {
|
|
3086
|
-
const webPkgJsonPath = path.join(webDir, "package.json");
|
|
3087
|
-
if (await fs.pathExists(webPkgJsonPath)) try {
|
|
3088
|
-
const webDepsToAdd = ["convex"];
|
|
3089
|
-
if (frontend.includes("tanstack-start")) webDepsToAdd.push("@convex-dev/react-query");
|
|
3090
|
-
if (hasSvelteWeb) webDepsToAdd.push("convex-svelte");
|
|
3091
|
-
if (hasNuxtWeb) {
|
|
3092
|
-
webDepsToAdd.push("convex-nuxt");
|
|
3093
|
-
webDepsToAdd.push("convex-vue");
|
|
3094
|
-
}
|
|
3095
|
-
await addPackageDependency({
|
|
3096
|
-
dependencies: webDepsToAdd,
|
|
3097
|
-
projectDir: webDir
|
|
3098
|
-
});
|
|
3099
|
-
} catch (_error) {}
|
|
3100
|
-
}
|
|
3101
|
-
if (nativeDirExists) {
|
|
3102
|
-
const nativePkgJsonPath = path.join(nativeDir, "package.json");
|
|
3103
|
-
if (await fs.pathExists(nativePkgJsonPath)) try {
|
|
3104
|
-
await addPackageDependency({
|
|
3105
|
-
dependencies: ["convex"],
|
|
3106
|
-
projectDir: nativeDir
|
|
3107
|
-
});
|
|
3108
|
-
} catch (_error) {}
|
|
3109
|
-
}
|
|
3110
|
-
const backendPackageName = `@${projectName}/backend`;
|
|
3111
|
-
const backendWorkspaceVersion = packageManager === "npm" ? "*" : "workspace:*";
|
|
3112
|
-
const addWorkspaceDepManually = async (pkgJsonPath, depName, depVersion) => {
|
|
3113
|
-
try {
|
|
3114
|
-
const pkgJson = await fs.readJson(pkgJsonPath);
|
|
3115
|
-
if (!pkgJson.dependencies) pkgJson.dependencies = {};
|
|
3116
|
-
if (pkgJson.dependencies[depName] !== depVersion) {
|
|
3117
|
-
pkgJson.dependencies[depName] = depVersion;
|
|
3118
|
-
await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
|
|
3119
|
-
}
|
|
3120
|
-
} catch (_error) {}
|
|
3600
|
+
database: detectedConfig.database || "none",
|
|
3601
|
+
orm: detectedConfig.orm || "none",
|
|
3602
|
+
backend: detectedConfig.backend || "none",
|
|
3603
|
+
runtime: detectedConfig.runtime || "none",
|
|
3604
|
+
frontend: detectedConfig.frontend || [],
|
|
3605
|
+
addons: detectedConfig.addons || [],
|
|
3606
|
+
examples: detectedConfig.examples || [],
|
|
3607
|
+
auth: detectedConfig.auth || false,
|
|
3608
|
+
git: false,
|
|
3609
|
+
packageManager: input.packageManager || detectedConfig.packageManager || "npm",
|
|
3610
|
+
install: input.install || false,
|
|
3611
|
+
dbSetup: detectedConfig.dbSetup || "none",
|
|
3612
|
+
api: detectedConfig.api || "none",
|
|
3613
|
+
webDeploy: input.webDeploy || detectedConfig.webDeploy || "none",
|
|
3614
|
+
serverDeploy: input.serverDeploy || detectedConfig.serverDeploy || "none"
|
|
3121
3615
|
};
|
|
3122
|
-
if (
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3616
|
+
if (input.webDeploy && input.webDeploy !== "none") log.info(pc.green(`Adding ${input.webDeploy} web deployment to ${config.frontend.join("/")}`));
|
|
3617
|
+
if (input.serverDeploy && input.serverDeploy !== "none") log.info(pc.green(`Adding ${input.serverDeploy} server deployment`));
|
|
3618
|
+
await setupDeploymentTemplates(projectDir, config);
|
|
3619
|
+
await setupWebDeploy(config);
|
|
3620
|
+
await setupServerDeploy(config);
|
|
3621
|
+
await updateBtsConfig(projectDir, {
|
|
3622
|
+
webDeploy: input.webDeploy || config.webDeploy,
|
|
3623
|
+
serverDeploy: input.serverDeploy || config.serverDeploy
|
|
3624
|
+
});
|
|
3625
|
+
if (config.install) await installDependencies({
|
|
3626
|
+
projectDir,
|
|
3627
|
+
packageManager: config.packageManager
|
|
3628
|
+
});
|
|
3629
|
+
else if (!input.suppressInstallMessage) log.info(pc.yellow(`Run ${pc.bold(`${config.packageManager} install`)} to install dependencies`));
|
|
3630
|
+
} catch (error) {
|
|
3631
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3632
|
+
exitWithError(`Error adding deployment: ${message}`);
|
|
3130
3633
|
}
|
|
3131
3634
|
}
|
|
3132
3635
|
|
|
3133
3636
|
//#endregion
|
|
3134
|
-
//#region src/
|
|
3637
|
+
//#region src/utils/format-with-biome.ts
|
|
3638
|
+
async function formatProjectWithBiome(projectDir) {
|
|
3639
|
+
const biome = new Biome();
|
|
3640
|
+
const { projectKey } = biome.openProject(projectDir);
|
|
3641
|
+
biome.applyConfiguration(projectKey, {
|
|
3642
|
+
formatter: {
|
|
3643
|
+
enabled: true,
|
|
3644
|
+
indentStyle: "tab"
|
|
3645
|
+
},
|
|
3646
|
+
javascript: { formatter: { quoteStyle: "double" } }
|
|
3647
|
+
});
|
|
3648
|
+
const files = await glob("**/*", {
|
|
3649
|
+
cwd: projectDir,
|
|
3650
|
+
dot: true,
|
|
3651
|
+
absolute: true,
|
|
3652
|
+
onlyFiles: true
|
|
3653
|
+
});
|
|
3654
|
+
for (const filePath of files) try {
|
|
3655
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
3656
|
+
const supported = new Set([
|
|
3657
|
+
".ts",
|
|
3658
|
+
".tsx",
|
|
3659
|
+
".js",
|
|
3660
|
+
".jsx",
|
|
3661
|
+
".cjs",
|
|
3662
|
+
".mjs",
|
|
3663
|
+
".cts",
|
|
3664
|
+
".mts",
|
|
3665
|
+
".json",
|
|
3666
|
+
".jsonc",
|
|
3667
|
+
".md",
|
|
3668
|
+
".mdx",
|
|
3669
|
+
".css",
|
|
3670
|
+
".scss",
|
|
3671
|
+
".html"
|
|
3672
|
+
]);
|
|
3673
|
+
if (!supported.has(ext)) continue;
|
|
3674
|
+
const original = await fs.readFile(filePath, "utf8");
|
|
3675
|
+
const result = biome.formatContent(projectKey, original, { filePath });
|
|
3676
|
+
const content = result?.content;
|
|
3677
|
+
if (typeof content !== "string") continue;
|
|
3678
|
+
if (content.length === 0 && original.length > 0) continue;
|
|
3679
|
+
if (content !== original) await fs.writeFile(filePath, content);
|
|
3680
|
+
} catch {}
|
|
3681
|
+
}
|
|
3682
|
+
|
|
3683
|
+
//#endregion
|
|
3684
|
+
//#region src/helpers/addons/auth-setup.ts
|
|
3135
3685
|
async function setupAuth(config) {
|
|
3136
3686
|
const { auth, frontend, backend, projectDir } = config;
|
|
3137
3687
|
if (backend === "convex" || !auth) return;
|
|
@@ -3183,7 +3733,217 @@ function generateAuthSecret(length = 32) {
|
|
|
3183
3733
|
}
|
|
3184
3734
|
|
|
3185
3735
|
//#endregion
|
|
3186
|
-
//#region src/helpers/
|
|
3736
|
+
//#region src/helpers/addons/examples-setup.ts
|
|
3737
|
+
async function setupExamples(config) {
|
|
3738
|
+
const { examples, frontend, backend, projectDir } = config;
|
|
3739
|
+
if (backend === "convex" || !examples || examples.length === 0 || examples[0] === "none") return;
|
|
3740
|
+
if (examples.includes("ai")) {
|
|
3741
|
+
const webClientDir = path.join(projectDir, "apps/web");
|
|
3742
|
+
const nativeClientDir = path.join(projectDir, "apps/native");
|
|
3743
|
+
const serverDir = path.join(projectDir, "apps/server");
|
|
3744
|
+
const webClientDirExists = await fs.pathExists(webClientDir);
|
|
3745
|
+
const nativeClientDirExists = await fs.pathExists(nativeClientDir);
|
|
3746
|
+
const serverDirExists = await fs.pathExists(serverDir);
|
|
3747
|
+
const hasNuxt = frontend.includes("nuxt");
|
|
3748
|
+
const hasSvelte = frontend.includes("svelte");
|
|
3749
|
+
const hasReactWeb = frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("next") || frontend.includes("tanstack-start");
|
|
3750
|
+
const hasReactNative = frontend.includes("native-nativewind") || frontend.includes("native-unistyles");
|
|
3751
|
+
if (webClientDirExists) {
|
|
3752
|
+
const dependencies = ["ai"];
|
|
3753
|
+
if (hasNuxt) dependencies.push("@ai-sdk/vue");
|
|
3754
|
+
else if (hasSvelte) dependencies.push("@ai-sdk/svelte");
|
|
3755
|
+
else if (hasReactWeb) dependencies.push("@ai-sdk/react");
|
|
3756
|
+
await addPackageDependency({
|
|
3757
|
+
dependencies,
|
|
3758
|
+
projectDir: webClientDir
|
|
3759
|
+
});
|
|
3760
|
+
}
|
|
3761
|
+
if (nativeClientDirExists && hasReactNative) await addPackageDependency({
|
|
3762
|
+
dependencies: ["ai", "@ai-sdk/react"],
|
|
3763
|
+
projectDir: nativeClientDir
|
|
3764
|
+
});
|
|
3765
|
+
if (serverDirExists && backend !== "none") await addPackageDependency({
|
|
3766
|
+
dependencies: ["ai", "@ai-sdk/google"],
|
|
3767
|
+
projectDir: serverDir
|
|
3768
|
+
});
|
|
3769
|
+
}
|
|
3770
|
+
}
|
|
3771
|
+
|
|
3772
|
+
//#endregion
|
|
3773
|
+
//#region src/helpers/core/api-setup.ts
|
|
3774
|
+
async function addBackendWorkspaceDependency(projectDir, backendPackageName, workspaceVersion) {
|
|
3775
|
+
const pkgJsonPath = path.join(projectDir, "package.json");
|
|
3776
|
+
try {
|
|
3777
|
+
const pkgJson = await fs.readJson(pkgJsonPath);
|
|
3778
|
+
if (!pkgJson.dependencies) pkgJson.dependencies = {};
|
|
3779
|
+
pkgJson.dependencies[backendPackageName] = workspaceVersion;
|
|
3780
|
+
await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
|
|
3781
|
+
} catch (_error) {}
|
|
3782
|
+
}
|
|
3783
|
+
function getFrontendType(frontend) {
|
|
3784
|
+
const reactBasedFrontends = [
|
|
3785
|
+
"tanstack-router",
|
|
3786
|
+
"react-router",
|
|
3787
|
+
"tanstack-start",
|
|
3788
|
+
"next"
|
|
3789
|
+
];
|
|
3790
|
+
const nativeFrontends = ["native-nativewind", "native-unistyles"];
|
|
3791
|
+
return {
|
|
3792
|
+
hasReactWeb: frontend.some((f) => reactBasedFrontends.includes(f)),
|
|
3793
|
+
hasNuxtWeb: frontend.includes("nuxt"),
|
|
3794
|
+
hasSvelteWeb: frontend.includes("svelte"),
|
|
3795
|
+
hasSolidWeb: frontend.includes("solid"),
|
|
3796
|
+
hasNative: frontend.some((f) => nativeFrontends.includes(f))
|
|
3797
|
+
};
|
|
3798
|
+
}
|
|
3799
|
+
function getApiDependencies(api, frontendType) {
|
|
3800
|
+
const deps = {};
|
|
3801
|
+
if (api === "orpc") deps.server = { dependencies: ["@orpc/server", "@orpc/client"] };
|
|
3802
|
+
else if (api === "trpc") deps.server = { dependencies: ["@trpc/server", "@trpc/client"] };
|
|
3803
|
+
if (frontendType.hasReactWeb) {
|
|
3804
|
+
if (api === "orpc") deps.web = { dependencies: ["@orpc/tanstack-query", "@orpc/client"] };
|
|
3805
|
+
else if (api === "trpc") deps.web = { dependencies: [
|
|
3806
|
+
"@trpc/tanstack-react-query",
|
|
3807
|
+
"@trpc/client",
|
|
3808
|
+
"@trpc/server"
|
|
3809
|
+
] };
|
|
3810
|
+
} else if (frontendType.hasNuxtWeb && api === "orpc") deps.web = {
|
|
3811
|
+
dependencies: [
|
|
3812
|
+
"@tanstack/vue-query",
|
|
3813
|
+
"@orpc/tanstack-query",
|
|
3814
|
+
"@orpc/client"
|
|
3815
|
+
],
|
|
3816
|
+
devDependencies: ["@tanstack/vue-query-devtools"]
|
|
3817
|
+
};
|
|
3818
|
+
else if (frontendType.hasSvelteWeb && api === "orpc") deps.web = {
|
|
3819
|
+
dependencies: [
|
|
3820
|
+
"@orpc/tanstack-query",
|
|
3821
|
+
"@orpc/client",
|
|
3822
|
+
"@tanstack/svelte-query"
|
|
3823
|
+
],
|
|
3824
|
+
devDependencies: ["@tanstack/svelte-query-devtools"]
|
|
3825
|
+
};
|
|
3826
|
+
else if (frontendType.hasSolidWeb && api === "orpc") deps.web = {
|
|
3827
|
+
dependencies: [
|
|
3828
|
+
"@orpc/tanstack-query",
|
|
3829
|
+
"@orpc/client",
|
|
3830
|
+
"@tanstack/solid-query"
|
|
3831
|
+
],
|
|
3832
|
+
devDependencies: ["@tanstack/solid-query-devtools", "@tanstack/solid-router-devtools"]
|
|
3833
|
+
};
|
|
3834
|
+
if (api === "trpc") deps.native = { dependencies: [
|
|
3835
|
+
"@trpc/tanstack-react-query",
|
|
3836
|
+
"@trpc/client",
|
|
3837
|
+
"@trpc/server"
|
|
3838
|
+
] };
|
|
3839
|
+
else if (api === "orpc") deps.native = { dependencies: ["@orpc/tanstack-query", "@orpc/client"] };
|
|
3840
|
+
return deps;
|
|
3841
|
+
}
|
|
3842
|
+
function getQueryDependencies(frontend) {
|
|
3843
|
+
const reactBasedFrontends = [
|
|
3844
|
+
"react-router",
|
|
3845
|
+
"tanstack-router",
|
|
3846
|
+
"tanstack-start",
|
|
3847
|
+
"next",
|
|
3848
|
+
"native-nativewind",
|
|
3849
|
+
"native-unistyles"
|
|
3850
|
+
];
|
|
3851
|
+
const deps = {};
|
|
3852
|
+
const needsReactQuery = frontend.some((f) => reactBasedFrontends.includes(f));
|
|
3853
|
+
if (needsReactQuery) {
|
|
3854
|
+
const hasReactWeb = frontend.some((f) => f !== "native-nativewind" && f !== "native-unistyles" && reactBasedFrontends.includes(f));
|
|
3855
|
+
const hasNative = frontend.includes("native-nativewind") || frontend.includes("native-unistyles");
|
|
3856
|
+
if (hasReactWeb) deps.web = {
|
|
3857
|
+
dependencies: ["@tanstack/react-query"],
|
|
3858
|
+
devDependencies: ["@tanstack/react-query-devtools"]
|
|
3859
|
+
};
|
|
3860
|
+
if (hasNative) deps.native = { dependencies: ["@tanstack/react-query"] };
|
|
3861
|
+
}
|
|
3862
|
+
if (frontend.includes("solid")) deps.web = {
|
|
3863
|
+
dependencies: ["@tanstack/solid-query"],
|
|
3864
|
+
devDependencies: ["@tanstack/solid-query-devtools", "@tanstack/solid-router-devtools"]
|
|
3865
|
+
};
|
|
3866
|
+
return deps;
|
|
3867
|
+
}
|
|
3868
|
+
function getConvexDependencies(frontend) {
|
|
3869
|
+
const deps = {
|
|
3870
|
+
web: { dependencies: ["convex"] },
|
|
3871
|
+
native: { dependencies: ["convex"] }
|
|
3872
|
+
};
|
|
3873
|
+
if (frontend.includes("tanstack-start")) deps.web.dependencies.push("@convex-dev/react-query");
|
|
3874
|
+
if (frontend.includes("svelte")) deps.web.dependencies.push("convex-svelte");
|
|
3875
|
+
if (frontend.includes("nuxt")) deps.web.dependencies.push("convex-nuxt", "convex-vue");
|
|
3876
|
+
return deps;
|
|
3877
|
+
}
|
|
3878
|
+
async function setupApi(config) {
|
|
3879
|
+
const { api, projectName, frontend, backend, packageManager, projectDir } = config;
|
|
3880
|
+
const isConvex = backend === "convex";
|
|
3881
|
+
const webDir = path.join(projectDir, "apps/web");
|
|
3882
|
+
const nativeDir = path.join(projectDir, "apps/native");
|
|
3883
|
+
const serverDir = path.join(projectDir, "apps/server");
|
|
3884
|
+
const webDirExists = await fs.pathExists(webDir);
|
|
3885
|
+
const nativeDirExists = await fs.pathExists(nativeDir);
|
|
3886
|
+
const serverDirExists = await fs.pathExists(serverDir);
|
|
3887
|
+
const frontendType = getFrontendType(frontend);
|
|
3888
|
+
if (!isConvex && api !== "none") {
|
|
3889
|
+
const apiDeps = getApiDependencies(api, frontendType);
|
|
3890
|
+
if (serverDirExists && apiDeps.server) {
|
|
3891
|
+
await addPackageDependency({
|
|
3892
|
+
dependencies: apiDeps.server.dependencies,
|
|
3893
|
+
projectDir: serverDir
|
|
3894
|
+
});
|
|
3895
|
+
if (api === "trpc") {
|
|
3896
|
+
if (backend === "hono") await addPackageDependency({
|
|
3897
|
+
dependencies: ["@hono/trpc-server"],
|
|
3898
|
+
projectDir: serverDir
|
|
3899
|
+
});
|
|
3900
|
+
else if (backend === "elysia") await addPackageDependency({
|
|
3901
|
+
dependencies: ["@elysiajs/trpc"],
|
|
3902
|
+
projectDir: serverDir
|
|
3903
|
+
});
|
|
3904
|
+
}
|
|
3905
|
+
}
|
|
3906
|
+
if (webDirExists && apiDeps.web) await addPackageDependency({
|
|
3907
|
+
dependencies: apiDeps.web.dependencies,
|
|
3908
|
+
devDependencies: apiDeps.web.devDependencies,
|
|
3909
|
+
projectDir: webDir
|
|
3910
|
+
});
|
|
3911
|
+
if (nativeDirExists && apiDeps.native) await addPackageDependency({
|
|
3912
|
+
dependencies: apiDeps.native.dependencies,
|
|
3913
|
+
projectDir: nativeDir
|
|
3914
|
+
});
|
|
3915
|
+
}
|
|
3916
|
+
if (!isConvex) {
|
|
3917
|
+
const queryDeps = getQueryDependencies(frontend);
|
|
3918
|
+
if (webDirExists && queryDeps.web) await addPackageDependency({
|
|
3919
|
+
dependencies: queryDeps.web.dependencies,
|
|
3920
|
+
devDependencies: queryDeps.web.devDependencies,
|
|
3921
|
+
projectDir: webDir
|
|
3922
|
+
});
|
|
3923
|
+
if (nativeDirExists && queryDeps.native) await addPackageDependency({
|
|
3924
|
+
dependencies: queryDeps.native.dependencies,
|
|
3925
|
+
projectDir: nativeDir
|
|
3926
|
+
});
|
|
3927
|
+
}
|
|
3928
|
+
if (isConvex) {
|
|
3929
|
+
const convexDeps = getConvexDependencies(frontend);
|
|
3930
|
+
if (webDirExists) await addPackageDependency({
|
|
3931
|
+
dependencies: convexDeps.web.dependencies,
|
|
3932
|
+
projectDir: webDir
|
|
3933
|
+
});
|
|
3934
|
+
if (nativeDirExists) await addPackageDependency({
|
|
3935
|
+
dependencies: convexDeps.native.dependencies,
|
|
3936
|
+
projectDir: nativeDir
|
|
3937
|
+
});
|
|
3938
|
+
const backendPackageName = `@${projectName}/backend`;
|
|
3939
|
+
const backendWorkspaceVersion = packageManager === "npm" ? "*" : "workspace:*";
|
|
3940
|
+
if (webDirExists) await addBackendWorkspaceDependency(webDir, backendPackageName, backendWorkspaceVersion);
|
|
3941
|
+
if (nativeDirExists) await addBackendWorkspaceDependency(nativeDir, backendPackageName, backendWorkspaceVersion);
|
|
3942
|
+
}
|
|
3943
|
+
}
|
|
3944
|
+
|
|
3945
|
+
//#endregion
|
|
3946
|
+
//#region src/helpers/core/backend-setup.ts
|
|
3187
3947
|
async function setupBackendDependencies(config) {
|
|
3188
3948
|
const { backend, runtime, api, projectDir } = config;
|
|
3189
3949
|
if (backend === "convex") return;
|
|
@@ -3222,7 +3982,7 @@ async function setupBackendDependencies(config) {
|
|
|
3222
3982
|
}
|
|
3223
3983
|
|
|
3224
3984
|
//#endregion
|
|
3225
|
-
//#region src/helpers/
|
|
3985
|
+
//#region src/helpers/core/env-setup.ts
|
|
3226
3986
|
async function addEnvVariablesToFile(filePath, variables) {
|
|
3227
3987
|
await fs.ensureDir(path.dirname(filePath));
|
|
3228
3988
|
let envContent = "";
|
|
@@ -3270,7 +4030,7 @@ async function addEnvVariablesToFile(filePath, variables) {
|
|
|
3270
4030
|
if (exampleModified || !await fs.pathExists(exampleFilePath)) await fs.writeFile(exampleFilePath, exampleEnvContent.trimEnd());
|
|
3271
4031
|
}
|
|
3272
4032
|
async function setupEnvironmentVariables(config) {
|
|
3273
|
-
const { backend, frontend, database, auth, examples, dbSetup, projectDir } = config;
|
|
4033
|
+
const { backend, frontend, database, auth, examples, dbSetup, projectDir, webDeploy, serverDeploy } = config;
|
|
3274
4034
|
const hasReactRouter = frontend.includes("react-router");
|
|
3275
4035
|
const hasTanStackRouter = frontend.includes("tanstack-router");
|
|
3276
4036
|
const hasTanStackStart = frontend.includes("tanstack-start");
|
|
@@ -3370,39 +4130,69 @@ async function setupEnvironmentVariables(config) {
|
|
|
3370
4130
|
}
|
|
3371
4131
|
];
|
|
3372
4132
|
await addEnvVariablesToFile(envPath, serverVars);
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
4133
|
+
const isUnifiedAlchemy = webDeploy === "alchemy" && serverDeploy === "alchemy";
|
|
4134
|
+
const isIndividualAlchemy = webDeploy === "alchemy" || serverDeploy === "alchemy";
|
|
4135
|
+
if (isUnifiedAlchemy) {
|
|
4136
|
+
const rootEnvPath = path.join(projectDir, ".env");
|
|
4137
|
+
const rootAlchemyVars = [{
|
|
4138
|
+
key: "ALCHEMY_PASSWORD",
|
|
4139
|
+
value: "please-change-this",
|
|
4140
|
+
condition: true
|
|
4141
|
+
}];
|
|
4142
|
+
await addEnvVariablesToFile(rootEnvPath, rootAlchemyVars);
|
|
4143
|
+
} else if (isIndividualAlchemy) {
|
|
4144
|
+
if (webDeploy === "alchemy") {
|
|
4145
|
+
const webDir = path.join(projectDir, "apps/web");
|
|
4146
|
+
if (await fs.pathExists(webDir)) {
|
|
4147
|
+
const webAlchemyVars = [{
|
|
4148
|
+
key: "ALCHEMY_PASSWORD",
|
|
4149
|
+
value: "please-change-this",
|
|
4150
|
+
condition: true
|
|
4151
|
+
}];
|
|
4152
|
+
await addEnvVariablesToFile(path.join(webDir, ".env"), webAlchemyVars);
|
|
4153
|
+
}
|
|
4154
|
+
}
|
|
4155
|
+
if (serverDeploy === "alchemy") {
|
|
4156
|
+
const serverDir$1 = path.join(projectDir, "apps/server");
|
|
4157
|
+
if (await fs.pathExists(serverDir$1)) {
|
|
4158
|
+
const serverAlchemyVars = [{
|
|
4159
|
+
key: "ALCHEMY_PASSWORD",
|
|
4160
|
+
value: "please-change-this",
|
|
4161
|
+
condition: true
|
|
4162
|
+
}];
|
|
4163
|
+
await addEnvVariablesToFile(path.join(serverDir$1, ".env"), serverAlchemyVars);
|
|
4164
|
+
}
|
|
4165
|
+
}
|
|
3378
4166
|
}
|
|
3379
4167
|
}
|
|
3380
4168
|
|
|
3381
4169
|
//#endregion
|
|
3382
4170
|
//#region src/helpers/database-providers/d1-setup.ts
|
|
3383
4171
|
async function setupCloudflareD1(config) {
|
|
3384
|
-
const { projectDir } = config;
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
4172
|
+
const { projectDir, serverDeploy } = config;
|
|
4173
|
+
if (serverDeploy === "wrangler") {
|
|
4174
|
+
const envPath = path.join(projectDir, "apps/server", ".env");
|
|
4175
|
+
const variables = [
|
|
4176
|
+
{
|
|
4177
|
+
key: "CLOUDFLARE_ACCOUNT_ID",
|
|
4178
|
+
value: "",
|
|
4179
|
+
condition: true
|
|
4180
|
+
},
|
|
4181
|
+
{
|
|
4182
|
+
key: "CLOUDFLARE_DATABASE_ID",
|
|
4183
|
+
value: "",
|
|
4184
|
+
condition: true
|
|
4185
|
+
},
|
|
4186
|
+
{
|
|
4187
|
+
key: "CLOUDFLARE_D1_TOKEN",
|
|
4188
|
+
value: "",
|
|
4189
|
+
condition: true
|
|
4190
|
+
}
|
|
4191
|
+
];
|
|
4192
|
+
try {
|
|
4193
|
+
await addEnvVariablesToFile(envPath, variables);
|
|
4194
|
+
} catch (_err) {}
|
|
4195
|
+
}
|
|
3406
4196
|
}
|
|
3407
4197
|
|
|
3408
4198
|
//#endregion
|
|
@@ -4196,7 +4986,7 @@ async function setupTurso(config) {
|
|
|
4196
4986
|
}
|
|
4197
4987
|
|
|
4198
4988
|
//#endregion
|
|
4199
|
-
//#region src/helpers/
|
|
4989
|
+
//#region src/helpers/core/db-setup.ts
|
|
4200
4990
|
async function setupDatabase(config) {
|
|
4201
4991
|
const { database, orm, dbSetup, backend, projectDir } = config;
|
|
4202
4992
|
if (backend === "convex" || database === "none") {
|
|
@@ -4261,44 +5051,7 @@ async function setupDatabase(config) {
|
|
|
4261
5051
|
}
|
|
4262
5052
|
|
|
4263
5053
|
//#endregion
|
|
4264
|
-
//#region src/helpers/
|
|
4265
|
-
async function setupExamples(config) {
|
|
4266
|
-
const { examples, frontend, backend, projectDir } = config;
|
|
4267
|
-
if (backend === "convex" || !examples || examples.length === 0 || examples[0] === "none") return;
|
|
4268
|
-
if (examples.includes("ai")) {
|
|
4269
|
-
const webClientDir = path.join(projectDir, "apps/web");
|
|
4270
|
-
const nativeClientDir = path.join(projectDir, "apps/native");
|
|
4271
|
-
const serverDir = path.join(projectDir, "apps/server");
|
|
4272
|
-
const webClientDirExists = await fs.pathExists(webClientDir);
|
|
4273
|
-
const nativeClientDirExists = await fs.pathExists(nativeClientDir);
|
|
4274
|
-
const serverDirExists = await fs.pathExists(serverDir);
|
|
4275
|
-
const hasNuxt = frontend.includes("nuxt");
|
|
4276
|
-
const hasSvelte = frontend.includes("svelte");
|
|
4277
|
-
const hasReactWeb = frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("next") || frontend.includes("tanstack-start");
|
|
4278
|
-
const hasReactNative = frontend.includes("native-nativewind") || frontend.includes("native-unistyles");
|
|
4279
|
-
if (webClientDirExists) {
|
|
4280
|
-
const dependencies = ["ai"];
|
|
4281
|
-
if (hasNuxt) dependencies.push("@ai-sdk/vue");
|
|
4282
|
-
else if (hasSvelte) dependencies.push("@ai-sdk/svelte");
|
|
4283
|
-
else if (hasReactWeb) dependencies.push("@ai-sdk/react");
|
|
4284
|
-
await addPackageDependency({
|
|
4285
|
-
dependencies,
|
|
4286
|
-
projectDir: webClientDir
|
|
4287
|
-
});
|
|
4288
|
-
}
|
|
4289
|
-
if (nativeClientDirExists && hasReactNative) await addPackageDependency({
|
|
4290
|
-
dependencies: ["ai", "@ai-sdk/react"],
|
|
4291
|
-
projectDir: nativeClientDir
|
|
4292
|
-
});
|
|
4293
|
-
if (serverDirExists && backend !== "none") await addPackageDependency({
|
|
4294
|
-
dependencies: ["ai", "@ai-sdk/google"],
|
|
4295
|
-
projectDir: serverDir
|
|
4296
|
-
});
|
|
4297
|
-
}
|
|
4298
|
-
}
|
|
4299
|
-
|
|
4300
|
-
//#endregion
|
|
4301
|
-
//#region src/helpers/setup/runtime-setup.ts
|
|
5054
|
+
//#region src/helpers/core/runtime-setup.ts
|
|
4302
5055
|
async function setupRuntime(config) {
|
|
4303
5056
|
const { runtime, backend, projectDir } = config;
|
|
4304
5057
|
if (backend === "convex" || backend === "next" || runtime === "none") return;
|
|
@@ -4306,23 +5059,6 @@ async function setupRuntime(config) {
|
|
|
4306
5059
|
if (!await fs.pathExists(serverDir)) return;
|
|
4307
5060
|
if (runtime === "bun") await setupBunRuntime(serverDir, backend);
|
|
4308
5061
|
else if (runtime === "node") await setupNodeRuntime(serverDir, backend);
|
|
4309
|
-
else if (runtime === "workers") await setupWorkersRuntime(serverDir);
|
|
4310
|
-
}
|
|
4311
|
-
async function generateCloudflareWorkerTypes(config) {
|
|
4312
|
-
if (config.runtime !== "workers") return;
|
|
4313
|
-
const serverDir = path.join(config.projectDir, "apps/server");
|
|
4314
|
-
if (!await fs.pathExists(serverDir)) return;
|
|
4315
|
-
const s = spinner();
|
|
4316
|
-
try {
|
|
4317
|
-
s.start("Generating Cloudflare Workers types...");
|
|
4318
|
-
const runCmd = config.packageManager === "npm" ? "npm" : config.packageManager;
|
|
4319
|
-
await execa(runCmd, ["run", "cf-typegen"], { cwd: serverDir });
|
|
4320
|
-
s.stop("Cloudflare Workers types generated successfully!");
|
|
4321
|
-
} catch {
|
|
4322
|
-
s.stop(pc.yellow("Failed to generate Cloudflare Workers types"));
|
|
4323
|
-
const managerCmd = config.packageManager === "npm" ? "npm run" : `${config.packageManager} run`;
|
|
4324
|
-
console.warn(`Note: You can manually run 'cd apps/server && ${managerCmd} cf-typegen' in the project directory later`);
|
|
4325
|
-
}
|
|
4326
5062
|
}
|
|
4327
5063
|
async function setupBunRuntime(serverDir, _backend) {
|
|
4328
5064
|
const packageJsonPath = path.join(serverDir, "package.json");
|
|
@@ -4362,27 +5098,20 @@ async function setupNodeRuntime(serverDir, backend) {
|
|
|
4362
5098
|
projectDir: serverDir
|
|
4363
5099
|
});
|
|
4364
5100
|
}
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
build: "wrangler deploy --dry-run",
|
|
4375
|
-
"cf-typegen": "wrangler types --env-interface CloudflareBindings"
|
|
4376
|
-
};
|
|
4377
|
-
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
4378
|
-
await addPackageDependency({
|
|
4379
|
-
devDependencies: ["wrangler", "@types/node"],
|
|
4380
|
-
projectDir: serverDir
|
|
5101
|
+
|
|
5102
|
+
//#endregion
|
|
5103
|
+
//#region src/helpers/core/convex-codegen.ts
|
|
5104
|
+
async function runConvexCodegen(projectDir, packageManager) {
|
|
5105
|
+
const backendDir = path.join(projectDir, "packages/backend");
|
|
5106
|
+
const cmd = getPackageExecutionCommand(packageManager, "convex codegen");
|
|
5107
|
+
await execa(cmd, {
|
|
5108
|
+
cwd: backendDir,
|
|
5109
|
+
shell: true
|
|
4381
5110
|
});
|
|
4382
5111
|
}
|
|
4383
5112
|
|
|
4384
5113
|
//#endregion
|
|
4385
|
-
//#region src/helpers/
|
|
5114
|
+
//#region src/helpers/core/create-readme.ts
|
|
4386
5115
|
async function createReadme(projectDir, options) {
|
|
4387
5116
|
const readmePath = path.join(projectDir, "README.md");
|
|
4388
5117
|
const content = generateReadmeContent(options);
|
|
@@ -4659,7 +5388,7 @@ function generateScriptsList(packageManagerRunCmd, database, orm, _auth, hasNati
|
|
|
4659
5388
|
}
|
|
4660
5389
|
|
|
4661
5390
|
//#endregion
|
|
4662
|
-
//#region src/helpers/
|
|
5391
|
+
//#region src/helpers/core/git.ts
|
|
4663
5392
|
async function initializeGit(projectDir, useGit) {
|
|
4664
5393
|
if (!useGit) return;
|
|
4665
5394
|
const gitVersionResult = await $({
|
|
@@ -4735,9 +5464,9 @@ async function getDockerStatus(database) {
|
|
|
4735
5464
|
}
|
|
4736
5465
|
|
|
4737
5466
|
//#endregion
|
|
4738
|
-
//#region src/helpers/
|
|
5467
|
+
//#region src/helpers/core/post-installation.ts
|
|
4739
5468
|
async function displayPostInstallInstructions(config) {
|
|
4740
|
-
const { database, relativePath, packageManager, depsInstalled, orm, addons, runtime, frontend, backend, dbSetup, webDeploy } = config;
|
|
5469
|
+
const { database, relativePath, packageManager, depsInstalled, orm, addons, runtime, frontend, backend, dbSetup, webDeploy, serverDeploy } = config;
|
|
4741
5470
|
const isConvex = backend === "convex";
|
|
4742
5471
|
const runCmd = packageManager === "npm" ? "npm run" : packageManager;
|
|
4743
5472
|
const cdCmd = `cd ${relativePath}`;
|
|
@@ -4748,7 +5477,7 @@ async function displayPostInstallInstructions(config) {
|
|
|
4748
5477
|
const nativeInstructions = frontend?.includes("native-nativewind") || frontend?.includes("native-unistyles") ? getNativeInstructions(isConvex) : "";
|
|
4749
5478
|
const pwaInstructions = addons?.includes("pwa") && frontend?.includes("react-router") ? getPwaInstructions() : "";
|
|
4750
5479
|
const starlightInstructions = addons?.includes("starlight") ? getStarlightInstructions(runCmd) : "";
|
|
4751
|
-
const workersDeployInstructions = webDeploy === "
|
|
5480
|
+
const workersDeployInstructions = webDeploy === "wrangler" ? getWorkersDeployInstructions(runCmd) : "";
|
|
4752
5481
|
const hasWeb = frontend?.some((f) => [
|
|
4753
5482
|
"tanstack-router",
|
|
4754
5483
|
"react-router",
|
|
@@ -4769,20 +5498,20 @@ async function displayPostInstallInstructions(config) {
|
|
|
4769
5498
|
let stepCounter = 2;
|
|
4770
5499
|
if (!depsInstalled) output += `${pc.cyan(`${stepCounter++}.`)} ${packageManager} install\n`;
|
|
4771
5500
|
if (isConvex) {
|
|
4772
|
-
output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev:setup
|
|
4773
|
-
output += `${pc.cyan(`${stepCounter++}.`)} Copy environment variables from
|
|
5501
|
+
output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev:setup\n${pc.dim(" (this will guide you through Convex project setup)")}\n`;
|
|
5502
|
+
output += `${pc.cyan(`${stepCounter++}.`)} Copy environment variables from\n${pc.white(" packages/backend/.env.local")} to ${pc.white("apps/*/.env")}\n`;
|
|
4774
5503
|
output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev\n\n`;
|
|
4775
5504
|
} else {
|
|
4776
5505
|
if (runtime !== "workers") output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev\n`;
|
|
4777
5506
|
if (runtime === "workers") {
|
|
4778
|
-
if (dbSetup === "d1") output += `${pc.yellow("IMPORTANT:")} Complete D1 database setup first
|
|
5507
|
+
if (dbSetup === "d1") output += `${pc.yellow("IMPORTANT:")} Complete D1 database setup first\n (see Database commands below)\n`;
|
|
4779
5508
|
output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev\n`;
|
|
4780
|
-
output += `${pc.cyan(`${stepCounter++}.`)} cd apps/server && ${runCmd}
|
|
5509
|
+
if (serverDeploy === "wrangler") output += `${pc.cyan(`${stepCounter++}.`)} cd apps/server && ${runCmd} cf-typegen\n\n`;
|
|
4781
5510
|
} else output += "\n";
|
|
4782
5511
|
}
|
|
4783
5512
|
output += `${pc.bold("Your project will be available at:")}\n`;
|
|
4784
5513
|
if (hasWeb) output += `${pc.cyan("•")} Frontend: http://localhost:${webPort}\n`;
|
|
4785
|
-
else if (!hasNative && !addons?.includes("starlight")) output += `${pc.yellow("NOTE:")} You are creating a backend-only app
|
|
5514
|
+
else if (!hasNative && !addons?.includes("starlight")) output += `${pc.yellow("NOTE:")} You are creating a backend-only app\n (no frontend selected)\n`;
|
|
4786
5515
|
if (!isConvex) output += `${pc.cyan("•")} Backend API: http://localhost:3000\n`;
|
|
4787
5516
|
if (addons?.includes("starlight")) output += `${pc.cyan("•")} Docs: http://localhost:4321\n`;
|
|
4788
5517
|
if (addons?.includes("fumadocs")) output += `${pc.cyan("•")} Fumadocs: http://localhost:4000\n`;
|
|
@@ -4796,7 +5525,7 @@ async function displayPostInstallInstructions(config) {
|
|
|
4796
5525
|
if (noOrmWarning) output += `\n${noOrmWarning.trim()}\n`;
|
|
4797
5526
|
if (bunWebNativeWarning) output += `\n${bunWebNativeWarning.trim()}\n`;
|
|
4798
5527
|
output += `\n${pc.bold("Update all dependencies:\n")}${pc.cyan(tazeCommand)}\n\n`;
|
|
4799
|
-
output += `${pc.bold("Like Better-T Stack?")} Please consider giving us a star
|
|
5528
|
+
output += `${pc.bold("Like Better-T Stack?")} Please consider giving us a star\n on GitHub:\n`;
|
|
4800
5529
|
output += pc.cyan("https://github.com/AmanVarshney01/create-better-t-stack");
|
|
4801
5530
|
consola$1.box(output);
|
|
4802
5531
|
}
|
|
@@ -4805,8 +5534,8 @@ function getNativeInstructions(isConvex) {
|
|
|
4805
5534
|
const exampleUrl = isConvex ? "https://<YOUR_CONVEX_URL>" : "http://<YOUR_LOCAL_IP>:3000";
|
|
4806
5535
|
const envFileName = ".env";
|
|
4807
5536
|
const ipNote = isConvex ? "your Convex deployment URL (find after running 'dev:setup')" : "your local IP address";
|
|
4808
|
-
let instructions = `${pc.yellow("NOTE:")} For Expo connectivity issues, update
|
|
4809
|
-
if (isConvex) instructions += `\n${pc.yellow("IMPORTANT:")} When using local development with Convex and native apps
|
|
5537
|
+
let instructions = `${pc.yellow("NOTE:")} For Expo connectivity issues, update\n apps/native/${envFileName} with ${ipNote}:\n ${`${envVar}=${exampleUrl}`}\n`;
|
|
5538
|
+
if (isConvex) instructions += `\n${pc.yellow("IMPORTANT:")} When using local development with Convex and native apps,\n ensure you use your local IP address instead of localhost or 127.0.0.1\n for proper connectivity.\n`;
|
|
4810
5539
|
return instructions;
|
|
4811
5540
|
}
|
|
4812
5541
|
function getLintingInstructions(runCmd) {
|
|
@@ -4832,8 +5561,8 @@ async function getDatabaseInstructions(database, orm, runCmd, runtime, dbSetup)
|
|
|
4832
5561
|
instructions.push("");
|
|
4833
5562
|
}
|
|
4834
5563
|
if (orm === "prisma") {
|
|
4835
|
-
if (dbSetup === "turso") instructions.push(`${pc.yellow("NOTE:")} Turso support with Prisma is in Early Access and requires
|
|
4836
|
-
if (database === "mongodb" && dbSetup === "docker") instructions.push(`${pc.yellow("WARNING:")} Prisma + MongoDB + Docker combination
|
|
5564
|
+
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`);
|
|
5565
|
+
if (database === "mongodb" && dbSetup === "docker") instructions.push(`${pc.yellow("WARNING:")} Prisma + MongoDB + Docker combination\n may not work.`);
|
|
4837
5566
|
if (dbSetup === "docker") instructions.push(`${pc.cyan("•")} Start docker container: ${`${runCmd} db:start`}`);
|
|
4838
5567
|
instructions.push(`${pc.cyan("•")} Apply schema: ${`${runCmd} db:push`}`);
|
|
4839
5568
|
instructions.push(`${pc.cyan("•")} Database UI: ${`${runCmd} db:studio`}`);
|
|
@@ -4844,30 +5573,30 @@ async function getDatabaseInstructions(database, orm, runCmd, runtime, dbSetup)
|
|
|
4844
5573
|
if (database === "sqlite" && dbSetup !== "d1") instructions.push(`${pc.cyan("•")} Start local DB (if needed): ${`cd apps/server && ${runCmd} db:local`}`);
|
|
4845
5574
|
} else if (orm === "mongoose") {
|
|
4846
5575
|
if (dbSetup === "docker") instructions.push(`${pc.cyan("•")} Start docker container: ${`${runCmd} db:start`}`);
|
|
4847
|
-
} else if (orm === "none") instructions.push(`${pc.yellow("NOTE:")} Manual database schema setup
|
|
5576
|
+
} else if (orm === "none") instructions.push(`${pc.yellow("NOTE:")} Manual database schema setup\n required.`);
|
|
4848
5577
|
return instructions.length ? `${pc.bold("Database commands:")}\n${instructions.join("\n")}` : "";
|
|
4849
5578
|
}
|
|
4850
5579
|
function getTauriInstructions(runCmd) {
|
|
4851
|
-
return `\n${pc.bold("Desktop app with Tauri:")}\n${pc.cyan("•")} Start desktop app: ${`cd apps/web && ${runCmd} desktop:dev`}\n${pc.cyan("•")} Build desktop app: ${`cd apps/web && ${runCmd} desktop:build`}\n${pc.yellow("NOTE:")} Tauri requires Rust and platform-specific dependencies.\
|
|
5580
|
+
return `\n${pc.bold("Desktop app with Tauri:")}\n${pc.cyan("•")} Start desktop app: ${`cd apps/web && ${runCmd} desktop:dev`}\n${pc.cyan("•")} Build desktop app: ${`cd apps/web && ${runCmd} desktop:build`}\n${pc.yellow("NOTE:")} Tauri requires Rust and platform-specific dependencies.\n See: https://v2.tauri.app/start/prerequisites/`;
|
|
4852
5581
|
}
|
|
4853
5582
|
function getPwaInstructions() {
|
|
4854
|
-
return `\n${pc.bold("PWA with React Router v7:")}\n${pc.yellow("NOTE:")} There is a known compatibility issue between VitePWA
|
|
5583
|
+
return `\n${pc.bold("PWA with React Router v7:")}\n${pc.yellow("NOTE:")} There is a known compatibility issue between VitePWA\n and React Router v7. See:\n https://github.com/vite-pwa/vite-plugin-pwa/issues/809`;
|
|
4855
5584
|
}
|
|
4856
5585
|
function getStarlightInstructions(runCmd) {
|
|
4857
5586
|
return `\n${pc.bold("Documentation with Starlight:")}\n${pc.cyan("•")} Start docs site: ${`cd apps/docs && ${runCmd} dev`}\n${pc.cyan("•")} Build docs site: ${`cd apps/docs && ${runCmd} build`}`;
|
|
4858
5587
|
}
|
|
4859
5588
|
function getNoOrmWarning() {
|
|
4860
|
-
return `\n${pc.yellow("WARNING:")} Database selected without an ORM. Features requiring
|
|
5589
|
+
return `\n${pc.yellow("WARNING:")} Database selected without an ORM. Features requiring\n database access (e.g., examples, auth) need manual setup.`;
|
|
4861
5590
|
}
|
|
4862
5591
|
function getBunWebNativeWarning() {
|
|
4863
|
-
return `\n${pc.yellow("WARNING:")} 'bun' might cause issues with web + native apps in a monorepo
|
|
5592
|
+
return `\n${pc.yellow("WARNING:")} 'bun' might cause issues with web + native apps in a monorepo.\n Use 'pnpm' if problems arise.`;
|
|
4864
5593
|
}
|
|
4865
5594
|
function getWorkersDeployInstructions(runCmd) {
|
|
4866
5595
|
return `\n${pc.bold("Deploy frontend to Cloudflare Workers:")}\n${pc.cyan("•")} Deploy: ${`cd apps/web && ${runCmd} run deploy`}`;
|
|
4867
5596
|
}
|
|
4868
5597
|
|
|
4869
5598
|
//#endregion
|
|
4870
|
-
//#region src/helpers/
|
|
5599
|
+
//#region src/helpers/core/project-config.ts
|
|
4871
5600
|
async function updatePackageConfigurations(projectDir, options) {
|
|
4872
5601
|
await updateRootPackageJson(projectDir, options);
|
|
4873
5602
|
if (options.backend !== "convex") await updateServerPackageJson(projectDir, options);
|
|
@@ -5048,7 +5777,7 @@ async function updateConvexPackageJson(projectDir, options) {
|
|
|
5048
5777
|
}
|
|
5049
5778
|
|
|
5050
5779
|
//#endregion
|
|
5051
|
-
//#region src/helpers/
|
|
5780
|
+
//#region src/helpers/core/create-project.ts
|
|
5052
5781
|
async function createProject(options) {
|
|
5053
5782
|
const projectDir = options.projectDir;
|
|
5054
5783
|
const isConvex = options.backend === "convex";
|
|
@@ -5080,14 +5809,13 @@ async function createProject(options) {
|
|
|
5080
5809
|
await updatePackageConfigurations(projectDir, options);
|
|
5081
5810
|
await createReadme(projectDir, options);
|
|
5082
5811
|
await writeBtsConfig(options);
|
|
5812
|
+
await formatProjectWithBiome(projectDir);
|
|
5813
|
+
if (isConvex) await runConvexCodegen(projectDir, options.packageManager);
|
|
5083
5814
|
log.success("Project template successfully scaffolded!");
|
|
5084
|
-
if (options.install) {
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
});
|
|
5089
|
-
await generateCloudflareWorkerTypes(options);
|
|
5090
|
-
}
|
|
5815
|
+
if (options.install) await installDependencies({
|
|
5816
|
+
projectDir,
|
|
5817
|
+
packageManager: options.packageManager
|
|
5818
|
+
});
|
|
5091
5819
|
await initializeGit(projectDir, options.git);
|
|
5092
5820
|
await displayPostInstallInstructions({
|
|
5093
5821
|
...options,
|
|
@@ -5106,7 +5834,7 @@ async function createProject(options) {
|
|
|
5106
5834
|
}
|
|
5107
5835
|
|
|
5108
5836
|
//#endregion
|
|
5109
|
-
//#region src/helpers/
|
|
5837
|
+
//#region src/helpers/core/command-handlers.ts
|
|
5110
5838
|
async function createProjectHandler(input) {
|
|
5111
5839
|
const startTime = Date.now();
|
|
5112
5840
|
const timeScaffolded = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -5118,7 +5846,7 @@ async function createProjectHandler(input) {
|
|
|
5118
5846
|
else if (input.yes) {
|
|
5119
5847
|
let defaultName = DEFAULT_CONFIG.relativePath;
|
|
5120
5848
|
let counter = 1;
|
|
5121
|
-
while (fs.
|
|
5849
|
+
while (await fs.pathExists(path.resolve(process.cwd(), defaultName)) && (await fs.readdir(path.resolve(process.cwd(), defaultName))).length > 0) {
|
|
5122
5850
|
defaultName = `${DEFAULT_CONFIG.projectName}-${counter}`;
|
|
5123
5851
|
counter++;
|
|
5124
5852
|
}
|
|
@@ -5157,7 +5885,8 @@ async function createProjectHandler(input) {
|
|
|
5157
5885
|
install: false,
|
|
5158
5886
|
dbSetup: "none",
|
|
5159
5887
|
api: "none",
|
|
5160
|
-
webDeploy: "none"
|
|
5888
|
+
webDeploy: "none",
|
|
5889
|
+
serverDeploy: "none"
|
|
5161
5890
|
},
|
|
5162
5891
|
reproducibleCommand: "",
|
|
5163
5892
|
timeScaffolded,
|
|
@@ -5214,11 +5943,11 @@ async function createProjectHandler(input) {
|
|
|
5214
5943
|
}
|
|
5215
5944
|
async function handleDirectoryConflictProgrammatically(currentPathInput, strategy) {
|
|
5216
5945
|
const currentPath = path.resolve(process.cwd(), currentPathInput);
|
|
5217
|
-
if (!fs.
|
|
5946
|
+
if (!await fs.pathExists(currentPath)) return {
|
|
5218
5947
|
finalPathInput: currentPathInput,
|
|
5219
5948
|
shouldClearDirectory: false
|
|
5220
5949
|
};
|
|
5221
|
-
const dirContents = fs.
|
|
5950
|
+
const dirContents = await fs.readdir(currentPath);
|
|
5222
5951
|
const isNotEmpty = dirContents.length > 0;
|
|
5223
5952
|
if (!isNotEmpty) return {
|
|
5224
5953
|
finalPathInput: currentPathInput,
|
|
@@ -5237,7 +5966,7 @@ async function handleDirectoryConflictProgrammatically(currentPathInput, strateg
|
|
|
5237
5966
|
let counter = 1;
|
|
5238
5967
|
const baseName = currentPathInput;
|
|
5239
5968
|
let finalPathInput = `${baseName}-${counter}`;
|
|
5240
|
-
while (fs.
|
|
5969
|
+
while (await fs.pathExists(path.resolve(process.cwd(), finalPathInput)) && (await fs.readdir(path.resolve(process.cwd(), finalPathInput))).length > 0) {
|
|
5241
5970
|
counter++;
|
|
5242
5971
|
finalPathInput = `${baseName}-${counter}`;
|
|
5243
5972
|
}
|
|
@@ -5263,6 +5992,10 @@ async function addAddonsHandler(input) {
|
|
|
5263
5992
|
const deploymentPrompt = await getDeploymentToAdd(detectedConfig.frontend || [], detectedConfig.webDeploy);
|
|
5264
5993
|
if (deploymentPrompt !== "none") input.webDeploy = deploymentPrompt;
|
|
5265
5994
|
}
|
|
5995
|
+
if (!input.serverDeploy) {
|
|
5996
|
+
const serverDeploymentPrompt = await getServerDeploymentToAdd(detectedConfig.runtime, detectedConfig.serverDeploy);
|
|
5997
|
+
if (serverDeploymentPrompt !== "none") input.serverDeploy = serverDeploymentPrompt;
|
|
5998
|
+
}
|
|
5266
5999
|
const packageManager = input.packageManager || detectedConfig.packageManager || "npm";
|
|
5267
6000
|
let somethingAdded = false;
|
|
5268
6001
|
if (input.addons && input.addons.length > 0) {
|
|
@@ -5283,6 +6016,15 @@ async function addAddonsHandler(input) {
|
|
|
5283
6016
|
});
|
|
5284
6017
|
somethingAdded = true;
|
|
5285
6018
|
}
|
|
6019
|
+
if (input.serverDeploy && input.serverDeploy !== "none") {
|
|
6020
|
+
await addDeploymentToProject({
|
|
6021
|
+
...input,
|
|
6022
|
+
install: false,
|
|
6023
|
+
suppressInstallMessage: true,
|
|
6024
|
+
serverDeploy: input.serverDeploy
|
|
6025
|
+
});
|
|
6026
|
+
somethingAdded = true;
|
|
6027
|
+
}
|
|
5286
6028
|
if (!somethingAdded) {
|
|
5287
6029
|
outro(pc.yellow("No addons or deployment configurations to add."));
|
|
5288
6030
|
return;
|
|
@@ -5386,6 +6128,7 @@ const router = t.router({
|
|
|
5386
6128
|
runtime: RuntimeSchema.optional(),
|
|
5387
6129
|
api: APISchema.optional(),
|
|
5388
6130
|
webDeploy: WebDeploySchema.optional(),
|
|
6131
|
+
serverDeploy: ServerDeploySchema.optional(),
|
|
5389
6132
|
directoryConflict: DirectoryConflictSchema.optional(),
|
|
5390
6133
|
renderTitle: z.boolean().optional(),
|
|
5391
6134
|
disableAnalytics: z.boolean().optional().default(false).describe("Disable analytics")
|
|
@@ -5401,6 +6144,7 @@ const router = t.router({
|
|
|
5401
6144
|
add: t.procedure.meta({ description: "Add addons or deployment configurations to an existing Better-T Stack project" }).input(z.tuple([z.object({
|
|
5402
6145
|
addons: z.array(AddonsSchema).optional().default([]),
|
|
5403
6146
|
webDeploy: WebDeploySchema.optional(),
|
|
6147
|
+
serverDeploy: ServerDeploySchema.optional(),
|
|
5404
6148
|
projectDir: z.string().optional(),
|
|
5405
6149
|
install: z.boolean().optional().default(false).describe("Install dependencies after adding addons or deployment"),
|
|
5406
6150
|
packageManager: PackageManagerSchema.optional()
|