create-better-t-stack 2.33.8 → 2.33.9-canary.88769d6d

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.
Files changed (32) hide show
  1. package/README.md +2 -1
  2. package/dist/cli.js +1 -1
  3. package/dist/index.d.ts +17 -4
  4. package/dist/index.js +1 -1
  5. package/dist/{src-jDxvJPRx.js → src-Bm5FOLMd.js} +1323 -517
  6. package/package.json +7 -4
  7. package/templates/addons/biome/biome.json.hbs +1 -0
  8. package/templates/addons/ruler/.ruler/mcp.json.hbs +1 -1
  9. package/templates/addons/ultracite/biome.json.hbs +1 -0
  10. package/templates/auth/server/base/src/lib/auth.ts.hbs +37 -4
  11. package/templates/backend/server/server-base/_gitignore +1 -0
  12. package/templates/backend/server/server-base/tsconfig.json.hbs +1 -1
  13. package/templates/base/_gitignore +2 -0
  14. package/templates/deploy/alchemy/alchemy.run.ts.hbs +200 -0
  15. package/templates/deploy/alchemy/env.d.ts.hbs +20 -0
  16. package/templates/deploy/{web → wrangler/web}/nuxt/wrangler.jsonc.hbs +1 -1
  17. package/templates/deploy/{web → wrangler/web}/react/next/wrangler.jsonc.hbs +1 -1
  18. package/templates/deploy/{web → wrangler/web}/react/react-router/wrangler.jsonc.hbs +1 -1
  19. package/templates/deploy/{web → wrangler/web}/react/tanstack-router/wrangler.jsonc.hbs +1 -1
  20. package/templates/deploy/{web → wrangler/web}/react/tanstack-start/wrangler.jsonc.hbs +1 -1
  21. package/templates/deploy/{web → wrangler/web}/solid/wrangler.jsonc.hbs +1 -1
  22. package/templates/deploy/{web → wrangler/web}/svelte/wrangler.jsonc.hbs +1 -1
  23. package/templates/frontend/nuxt/_gitignore +3 -0
  24. package/templates/frontend/nuxt/tsconfig.json.hbs +4 -4
  25. package/templates/frontend/react/web-base/_gitignore +1 -0
  26. package/templates/frontend/react/web-base/src/components/header.tsx.hbs +0 -1
  27. package/templates/frontend/solid/_gitignore +1 -0
  28. package/templates/frontend/solid/package.json.hbs +0 -1
  29. package/templates/frontend/svelte/_gitignore +1 -0
  30. package/templates/frontend/svelte/package.json.hbs +11 -13
  31. /package/templates/{runtime/workers/apps → deploy/wrangler}/server/wrangler.jsonc.hbs +0 -0
  32. /package/templates/deploy/{web → wrangler/web}/react/next/open-next.config.ts +0 -0
@@ -10,9 +10,10 @@ import { fileURLToPath } from "node:url";
10
10
  import gradient from "gradient-string";
11
11
  import * as JSONC from "jsonc-parser";
12
12
  import { $, execa } from "execa";
13
- import { globby } from "globby";
13
+ import { glob } from "tinyglobby";
14
14
  import handlebars from "handlebars";
15
15
  import { IndentationText, Node, Project, QuoteKind, SyntaxKind } from "ts-morph";
16
+ import { Biome } from "@biomejs/js-api/nodejs";
16
17
  import os from "node:os";
17
18
 
18
19
  //#region src/utils/get-package-manager.ts
@@ -45,7 +46,8 @@ const DEFAULT_CONFIG = {
45
46
  backend: "hono",
46
47
  runtime: "bun",
47
48
  api: "trpc",
48
- webDeploy: "none"
49
+ webDeploy: "none",
50
+ serverDeploy: "none"
49
51
  };
50
52
  const dependencyVersionMap = {
51
53
  "better-auth": "^1.3.4",
@@ -103,18 +105,24 @@ const dependencyVersionMap = {
103
105
  "convex-svelte": "^0.0.11",
104
106
  "convex-nuxt": "0.1.5",
105
107
  "convex-vue": "^0.1.5",
106
- "@tanstack/svelte-query": "^5.74.4",
108
+ "@tanstack/svelte-query": "^5.85.3",
109
+ "@tanstack/svelte-query-devtools": "^5.85.3",
107
110
  "@tanstack/vue-query-devtools": "^5.83.0",
108
111
  "@tanstack/vue-query": "^5.83.0",
109
112
  "@tanstack/react-query-devtools": "^5.80.5",
110
113
  "@tanstack/react-query": "^5.80.5",
111
114
  "@tanstack/solid-query": "^5.75.0",
112
115
  "@tanstack/solid-query-devtools": "^5.75.0",
116
+ "@tanstack/solid-router-devtools": "^1.131.25",
113
117
  wrangler: "^4.23.0",
114
118
  "@cloudflare/vite-plugin": "^1.9.0",
115
119
  "@opennextjs/cloudflare": "^1.3.0",
116
120
  "nitro-cloudflare-dev": "^0.2.2",
117
- "@sveltejs/adapter-cloudflare": "^7.0.4"
121
+ "@sveltejs/adapter-cloudflare": "^7.2.1",
122
+ "@cloudflare/workers-types": "^4.20250813.0",
123
+ alchemy: "^0.62.1",
124
+ nitropack: "^2.12.4",
125
+ dotenv: "^17.2.1"
118
126
  };
119
127
  const ADDON_COMPATIBILITY = {
120
128
  pwa: [
@@ -128,7 +136,8 @@ const ADDON_COMPATIBILITY = {
128
136
  "react-router",
129
137
  "nuxt",
130
138
  "svelte",
131
- "solid"
139
+ "solid",
140
+ "next"
132
141
  ],
133
142
  biome: [],
134
143
  husky: [],
@@ -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(["workers", "none"]).describe("Web deployment");
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,23 @@ 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
+ }
568
+ function validateAddonsAgainstFrontends(addons = [], frontends = []) {
569
+ for (const addon of addons) {
570
+ if (addon === "none") continue;
571
+ const { isCompatible, reason } = validateAddonCompatibility(addon, frontends);
572
+ if (!isCompatible) exitWithError(`Incompatible addon/frontend combination: ${reason}`);
573
+ }
574
+ }
575
+ function validateExamplesCompatibility(examples, backend, database, frontend) {
576
+ const examplesArr = examples ?? [];
577
+ if (examplesArr.length === 0 || examplesArr.includes("none")) return;
578
+ if (examplesArr.includes("todo") && backend !== "convex" && backend !== "none" && database === "none") exitWithError("The 'todo' example requires a database if a backend (other than Convex) is present. Cannot use --examples todo when database is 'none' and a backend is selected.");
579
+ if (examplesArr.includes("ai") && backend === "elysia") exitWithError("The 'ai' example is not compatible with the Elysia backend.");
580
+ if (examplesArr.includes("ai") && (frontend ?? []).includes("solid")) exitWithError("The 'ai' example is not compatible with the Solid frontend.");
581
+ }
547
582
 
548
583
  //#endregion
549
584
  //#region src/prompts/api.ts
@@ -1004,16 +1039,97 @@ async function getRuntimeChoice(runtime, backend) {
1004
1039
  return response;
1005
1040
  }
1006
1041
 
1042
+ //#endregion
1043
+ //#region src/prompts/server-deploy.ts
1044
+ function getDeploymentDisplay$1(deployment) {
1045
+ if (deployment === "wrangler") return {
1046
+ label: "Wrangler",
1047
+ hint: "Deploy to Cloudflare Workers using Wrangler"
1048
+ };
1049
+ if (deployment === "alchemy") return {
1050
+ label: "Alchemy",
1051
+ hint: "Deploy to Cloudflare Workers using Alchemy"
1052
+ };
1053
+ return {
1054
+ label: deployment,
1055
+ hint: `Add ${deployment} deployment`
1056
+ };
1057
+ }
1058
+ async function getServerDeploymentChoice(deployment, runtime, backend, webDeploy) {
1059
+ if (deployment !== void 0) return deployment;
1060
+ if (backend === "none" || backend === "convex") return "none";
1061
+ const options = [];
1062
+ if (runtime === "workers") ["alchemy", "wrangler"].forEach((deploy) => {
1063
+ const { label, hint } = getDeploymentDisplay$1(deploy);
1064
+ options.unshift({
1065
+ value: deploy,
1066
+ label,
1067
+ hint
1068
+ });
1069
+ });
1070
+ else options.push({
1071
+ value: "none",
1072
+ label: "None",
1073
+ hint: "Manual setup"
1074
+ });
1075
+ const response = await select({
1076
+ message: "Select server deployment",
1077
+ options,
1078
+ initialValue: webDeploy === "alchemy" ? "alchemy" : runtime === "workers" ? "wrangler" : DEFAULT_CONFIG.serverDeploy
1079
+ });
1080
+ if (isCancel(response)) return exitCancelled("Operation cancelled");
1081
+ return response;
1082
+ }
1083
+ async function getServerDeploymentToAdd(runtime, existingDeployment) {
1084
+ const options = [];
1085
+ if (runtime === "workers") {
1086
+ if (existingDeployment !== "wrangler") {
1087
+ const { label, hint } = getDeploymentDisplay$1("wrangler");
1088
+ options.push({
1089
+ value: "wrangler",
1090
+ label,
1091
+ hint
1092
+ });
1093
+ }
1094
+ if (existingDeployment !== "alchemy") {
1095
+ const { label, hint } = getDeploymentDisplay$1("alchemy");
1096
+ options.push({
1097
+ value: "alchemy",
1098
+ label,
1099
+ hint
1100
+ });
1101
+ }
1102
+ }
1103
+ if (existingDeployment && existingDeployment !== "none") return "none";
1104
+ if (options.length > 0) options.push({
1105
+ value: "none",
1106
+ label: "None",
1107
+ hint: "Skip deployment setup"
1108
+ });
1109
+ if (options.length === 0) return "none";
1110
+ const response = await select({
1111
+ message: "Select server deployment",
1112
+ options,
1113
+ initialValue: runtime === "workers" ? "wrangler" : DEFAULT_CONFIG.serverDeploy
1114
+ });
1115
+ if (isCancel(response)) return exitCancelled("Operation cancelled");
1116
+ return response;
1117
+ }
1118
+
1007
1119
  //#endregion
1008
1120
  //#region src/prompts/web-deploy.ts
1009
1121
  function hasWebFrontend(frontends) {
1010
1122
  return frontends.some((f) => WEB_FRAMEWORKS.includes(f));
1011
1123
  }
1012
1124
  function getDeploymentDisplay(deployment) {
1013
- if (deployment === "workers") return {
1014
- label: "Cloudflare Workers",
1125
+ if (deployment === "wrangler") return {
1126
+ label: "Wrangler",
1015
1127
  hint: "Deploy to Cloudflare Workers using Wrangler"
1016
1128
  };
1129
+ if (deployment === "alchemy") return {
1130
+ label: "Alchemy",
1131
+ hint: "Deploy to Cloudflare Workers using Alchemy"
1132
+ };
1017
1133
  return {
1018
1134
  label: deployment,
1019
1135
  hint: `Add ${deployment} deployment`
@@ -1022,15 +1138,18 @@ function getDeploymentDisplay(deployment) {
1022
1138
  async function getDeploymentChoice(deployment, _runtime, _backend, frontend = []) {
1023
1139
  if (deployment !== void 0) return deployment;
1024
1140
  if (!hasWebFrontend(frontend)) return "none";
1025
- const options = [{
1026
- value: "workers",
1027
- label: "Cloudflare Workers",
1028
- hint: "Deploy to Cloudflare Workers using Wrangler"
1029
- }, {
1030
- value: "none",
1031
- label: "None",
1032
- hint: "Manual setup"
1033
- }];
1141
+ const options = [
1142
+ "wrangler",
1143
+ "alchemy",
1144
+ "none"
1145
+ ].map((deploy) => {
1146
+ const { label, hint } = getDeploymentDisplay(deploy);
1147
+ return {
1148
+ value: deploy,
1149
+ label,
1150
+ hint
1151
+ };
1152
+ });
1034
1153
  const response = await select({
1035
1154
  message: "Select web deployment",
1036
1155
  options,
@@ -1042,10 +1161,18 @@ async function getDeploymentChoice(deployment, _runtime, _backend, frontend = []
1042
1161
  async function getDeploymentToAdd(frontend, existingDeployment) {
1043
1162
  if (!hasWebFrontend(frontend)) return "none";
1044
1163
  const options = [];
1045
- if (existingDeployment !== "workers") {
1046
- const { label, hint } = getDeploymentDisplay("workers");
1164
+ if (existingDeployment !== "wrangler") {
1165
+ const { label, hint } = getDeploymentDisplay("wrangler");
1166
+ options.push({
1167
+ value: "wrangler",
1168
+ label,
1169
+ hint
1170
+ });
1171
+ }
1172
+ if (existingDeployment !== "alchemy") {
1173
+ const { label, hint } = getDeploymentDisplay("alchemy");
1047
1174
  options.push({
1048
- value: "workers",
1175
+ value: "alchemy",
1049
1176
  label,
1050
1177
  hint
1051
1178
  });
@@ -1081,6 +1208,7 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
1081
1208
  examples: ({ results }) => getExamplesChoice(flags.examples, results.database, results.frontend, results.backend, results.api),
1082
1209
  dbSetup: ({ results }) => getDBSetupChoice(results.database ?? "none", flags.dbSetup, results.orm, results.backend, results.runtime),
1083
1210
  webDeploy: ({ results }) => getDeploymentChoice(flags.webDeploy, results.runtime, results.backend, results.frontend),
1211
+ serverDeploy: ({ results }) => getServerDeploymentChoice(flags.serverDeploy, results.runtime, results.backend, results.webDeploy),
1084
1212
  git: () => getGitChoice(flags.git),
1085
1213
  packageManager: () => getPackageManagerChoice(flags.packageManager),
1086
1214
  install: () => getinstallChoice(flags.install)
@@ -1120,7 +1248,8 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
1120
1248
  install: result.install,
1121
1249
  dbSetup: result.dbSetup,
1122
1250
  api: result.api,
1123
- webDeploy: result.webDeploy
1251
+ webDeploy: result.webDeploy,
1252
+ serverDeploy: result.serverDeploy
1124
1253
  };
1125
1254
  }
1126
1255
 
@@ -1152,7 +1281,7 @@ async function getProjectName(initialName) {
1152
1281
  let projectPath = "";
1153
1282
  let defaultName = DEFAULT_CONFIG.projectName;
1154
1283
  let counter = 1;
1155
- while (fs.pathExistsSync(path.resolve(process.cwd(), defaultName)) && fs.readdirSync(path.resolve(process.cwd(), defaultName)).length > 0) {
1284
+ while (await fs.pathExists(path.resolve(process.cwd(), defaultName)) && (await fs.readdir(path.resolve(process.cwd(), defaultName))).length > 0) {
1156
1285
  defaultName = `${DEFAULT_CONFIG.projectName}-${counter}`;
1157
1286
  counter++;
1158
1287
  }
@@ -1199,7 +1328,7 @@ const getLatestCLIVersion = () => {
1199
1328
  */
1200
1329
  function isTelemetryEnabled() {
1201
1330
  const BTS_TELEMETRY_DISABLED = process.env.BTS_TELEMETRY_DISABLED;
1202
- const BTS_TELEMETRY = "1";
1331
+ const BTS_TELEMETRY = "0";
1203
1332
  if (BTS_TELEMETRY_DISABLED !== void 0) return BTS_TELEMETRY_DISABLED !== "1";
1204
1333
  if (BTS_TELEMETRY !== void 0) return BTS_TELEMETRY === "1";
1205
1334
  return true;
@@ -1207,11 +1336,16 @@ function isTelemetryEnabled() {
1207
1336
 
1208
1337
  //#endregion
1209
1338
  //#region src/utils/analytics.ts
1210
- const POSTHOG_API_KEY = "phc_8ZUxEwwfKMajJLvxz1daGd931dYbQrwKNficBmsdIrs";
1211
- const POSTHOG_HOST = "https://us.i.posthog.com";
1339
+ const POSTHOG_API_KEY = "random";
1340
+ const POSTHOG_HOST = "random";
1341
+ function generateSessionId() {
1342
+ const rand = Math.random().toString(36).slice(2);
1343
+ const now = Date.now().toString(36);
1344
+ return `cli_${now}${rand}`;
1345
+ }
1212
1346
  async function trackProjectCreation(config, disableAnalytics = false) {
1213
1347
  if (!isTelemetryEnabled() || disableAnalytics) return;
1214
- const sessionId = `cli_${crypto.randomUUID().replace(/-/g, "")}`;
1348
+ const sessionId = generateSessionId();
1215
1349
  const { projectName, projectDir, relativePath,...safeConfig } = config;
1216
1350
  const payload = {
1217
1351
  api_key: POSTHOG_API_KEY,
@@ -1219,8 +1353,8 @@ async function trackProjectCreation(config, disableAnalytics = false) {
1219
1353
  properties: {
1220
1354
  ...safeConfig,
1221
1355
  cli_version: getLatestCLIVersion(),
1222
- node_version: process.version,
1223
- platform: process.platform,
1356
+ node_version: typeof process !== "undefined" ? process.version : "",
1357
+ platform: typeof process !== "undefined" ? process.platform : "",
1224
1358
  $ip: null
1225
1359
  },
1226
1360
  distinct_id: sessionId
@@ -1274,6 +1408,7 @@ function displayConfig(config) {
1274
1408
  }
1275
1409
  if (config.dbSetup !== void 0) configDisplay.push(`${pc.blue("Database Setup:")} ${String(config.dbSetup)}`);
1276
1410
  if (config.webDeploy !== void 0) configDisplay.push(`${pc.blue("Web Deployment:")} ${String(config.webDeploy)}`);
1411
+ if (config.serverDeploy !== void 0) configDisplay.push(`${pc.blue("Server Deployment:")} ${String(config.serverDeploy)}`);
1277
1412
  if (configDisplay.length === 0) return pc.yellow("No configuration selected.");
1278
1413
  return configDisplay.join("\n");
1279
1414
  }
@@ -1296,6 +1431,7 @@ function generateReproducibleCommand(config) {
1296
1431
  else flags.push("--examples none");
1297
1432
  flags.push(`--db-setup ${config.dbSetup}`);
1298
1433
  flags.push(`--web-deploy ${config.webDeploy}`);
1434
+ flags.push(`--server-deploy ${config.serverDeploy}`);
1299
1435
  flags.push(config.git ? "--git" : "--no-git");
1300
1436
  flags.push(`--package-manager ${config.packageManager}`);
1301
1437
  flags.push(config.install ? "--install" : "--no-install");
@@ -1313,8 +1449,8 @@ function generateReproducibleCommand(config) {
1313
1449
  async function handleDirectoryConflict(currentPathInput, silent = false) {
1314
1450
  while (true) {
1315
1451
  const resolvedPath = path.resolve(process.cwd(), currentPathInput);
1316
- const dirExists = fs.pathExistsSync(resolvedPath);
1317
- const dirIsNotEmpty = dirExists && fs.readdirSync(resolvedPath).length > 0;
1452
+ const dirExists = await fs.pathExists(resolvedPath);
1453
+ const dirIsNotEmpty = dirExists && (await fs.readdir(resolvedPath)).length > 0;
1318
1454
  if (!dirIsNotEmpty) return {
1319
1455
  finalPathInput: currentPathInput,
1320
1456
  shouldClearDirectory: false
@@ -1476,6 +1612,7 @@ function processAndValidateFlags(options, providedFlags, projectName) {
1476
1612
  if (options.dbSetup) config.dbSetup = options.dbSetup;
1477
1613
  if (options.packageManager) config.packageManager = options.packageManager;
1478
1614
  if (options.webDeploy) config.webDeploy = options.webDeploy;
1615
+ if (options.serverDeploy) config.serverDeploy = options.serverDeploy;
1479
1616
  const derivedName = deriveProjectName(projectName, options.projectDirectory);
1480
1617
  if (derivedName) {
1481
1618
  const nameToValidate = projectName ? path.basename(projectName) : derivedName;
@@ -1536,6 +1673,44 @@ function processAndValidateFlags(options, providedFlags, projectName) {
1536
1673
  validateWorkersCompatibility(providedFlags, options, config);
1537
1674
  const hasWebFrontendFlag = (config.frontend ?? []).some((f) => isWebFrontend(f));
1538
1675
  validateWebDeployRequiresWebFrontend(config.webDeploy, hasWebFrontendFlag);
1676
+ validateServerDeployRequiresBackend(config.serverDeploy, config.backend);
1677
+ return config;
1678
+ }
1679
+ function validateConfigCompatibility(config) {
1680
+ const effectiveDatabase = config.database;
1681
+ const effectiveBackend = config.backend;
1682
+ const effectiveFrontend = config.frontend;
1683
+ const effectiveApi = config.api;
1684
+ validateApiFrontendCompatibility(effectiveApi, effectiveFrontend);
1685
+ if (config.addons && config.addons.length > 0) {
1686
+ validateAddonsAgainstFrontends(config.addons, effectiveFrontend);
1687
+ config.addons = [...new Set(config.addons)];
1688
+ }
1689
+ validateExamplesCompatibility(config.examples ?? [], effectiveBackend, effectiveDatabase, effectiveFrontend ?? []);
1690
+ }
1691
+ function processProvidedFlagsWithoutValidation(options, projectName) {
1692
+ const config = {};
1693
+ if (options.api) config.api = options.api;
1694
+ if (options.backend) config.backend = options.backend;
1695
+ if (options.database) config.database = options.database;
1696
+ if (options.orm) config.orm = options.orm;
1697
+ if (options.auth !== void 0) config.auth = options.auth;
1698
+ if (options.git !== void 0) config.git = options.git;
1699
+ if (options.install !== void 0) config.install = options.install;
1700
+ if (options.runtime) config.runtime = options.runtime;
1701
+ if (options.dbSetup) config.dbSetup = options.dbSetup;
1702
+ if (options.packageManager) config.packageManager = options.packageManager;
1703
+ if (options.webDeploy) config.webDeploy = options.webDeploy;
1704
+ const derivedName = deriveProjectName(projectName, options.projectDirectory);
1705
+ if (derivedName) {
1706
+ const nameToValidate = projectName ? path.basename(projectName) : derivedName;
1707
+ const result = ProjectNameSchema.safeParse(nameToValidate);
1708
+ if (!result.success) throw new Error(`Invalid project name: ${result.error.issues[0]?.message}`);
1709
+ config.projectName = projectName || derivedName;
1710
+ }
1711
+ if (options.frontend && options.frontend.length > 0) config.frontend = processArrayOption(options.frontend);
1712
+ if (options.addons && options.addons.length > 0) config.addons = processArrayOption(options.addons);
1713
+ if (options.examples && options.examples.length > 0) config.examples = processArrayOption(options.examples);
1539
1714
  return config;
1540
1715
  }
1541
1716
  function getProvidedFlags(options) {
@@ -1560,7 +1735,8 @@ async function writeBtsConfig(projectConfig) {
1560
1735
  packageManager: projectConfig.packageManager,
1561
1736
  dbSetup: projectConfig.dbSetup,
1562
1737
  api: projectConfig.api,
1563
- webDeploy: projectConfig.webDeploy
1738
+ webDeploy: projectConfig.webDeploy,
1739
+ serverDeploy: projectConfig.serverDeploy
1564
1740
  };
1565
1741
  const baseContent = {
1566
1742
  $schema: "https://r2.better-t-stack.dev/schema.json",
@@ -1577,7 +1753,8 @@ async function writeBtsConfig(projectConfig) {
1577
1753
  packageManager: btsConfig.packageManager,
1578
1754
  dbSetup: btsConfig.dbSetup,
1579
1755
  api: btsConfig.api,
1580
- webDeploy: btsConfig.webDeploy
1756
+ webDeploy: btsConfig.webDeploy,
1757
+ serverDeploy: btsConfig.serverDeploy
1581
1758
  };
1582
1759
  let configContent = JSON.stringify(baseContent);
1583
1760
  const formatResult = JSONC.format(configContent, void 0, {
@@ -1670,7 +1847,7 @@ function getPackageExecutionCommand(packageManager, commandWithArgs) {
1670
1847
  }
1671
1848
 
1672
1849
  //#endregion
1673
- //#region src/helpers/setup/fumadocs-setup.ts
1850
+ //#region src/helpers/addons/fumadocs-setup.ts
1674
1851
  const TEMPLATES = {
1675
1852
  "next-mdx": {
1676
1853
  label: "Next.js: Fumadocs MDX",
@@ -1757,9 +1934,9 @@ handlebars.registerHelper("or", (a, b) => a || b);
1757
1934
  handlebars.registerHelper("includes", (array, value) => Array.isArray(array) && array.includes(value));
1758
1935
 
1759
1936
  //#endregion
1760
- //#region src/helpers/project-generation/template-manager.ts
1937
+ //#region src/helpers/core/template-manager.ts
1761
1938
  async function processAndCopyFiles(sourcePattern, baseSourceDir, destDir, context, overwrite = true, ignorePatterns) {
1762
- const sourceFiles = await globby(sourcePattern, {
1939
+ const sourceFiles = await glob(sourcePattern, {
1763
1940
  cwd: baseSourceDir,
1764
1941
  dot: true,
1765
1942
  onlyFiles: true,
@@ -2073,10 +2250,6 @@ async function handleExtras(projectDir, context) {
2073
2250
  const npmrcTemplateSrc = path.join(extrasDir, "_npmrc.hbs");
2074
2251
  if (await fs.pathExists(npmrcTemplateSrc)) await processAndCopyFiles("_npmrc.hbs", extrasDir, projectDir, context);
2075
2252
  }
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
2253
  }
2081
2254
  async function setupDockerComposeTemplates(projectDir, context) {
2082
2255
  if (context.dbSetup !== "docker" || context.database === "none") return;
@@ -2085,29 +2258,58 @@ async function setupDockerComposeTemplates(projectDir, context) {
2085
2258
  if (await fs.pathExists(dockerSrcDir)) await processAndCopyFiles("**/*", dockerSrcDir, serverAppDir, context);
2086
2259
  }
2087
2260
  async function setupDeploymentTemplates(projectDir, context) {
2088
- if (context.webDeploy === "none") return;
2089
- if (context.webDeploy === "workers") {
2261
+ if (context.webDeploy === "alchemy" || context.serverDeploy === "alchemy") if (context.webDeploy === "alchemy" && context.serverDeploy === "alchemy") {
2262
+ const alchemyTemplateSrc = path.join(PKG_ROOT, "templates/deploy/alchemy");
2263
+ if (await fs.pathExists(alchemyTemplateSrc)) {
2264
+ await processAndCopyFiles("alchemy.run.ts.hbs", alchemyTemplateSrc, projectDir, context);
2265
+ const serverAppDir = path.join(projectDir, "apps/server");
2266
+ if (await fs.pathExists(serverAppDir)) await processAndCopyFiles("env.d.ts.hbs", alchemyTemplateSrc, serverAppDir, context);
2267
+ }
2268
+ } else {
2269
+ if (context.webDeploy === "alchemy") {
2270
+ const alchemyTemplateSrc = path.join(PKG_ROOT, "templates/deploy/alchemy");
2271
+ const webAppDir = path.join(projectDir, "apps/web");
2272
+ if (await fs.pathExists(alchemyTemplateSrc) && await fs.pathExists(webAppDir)) await processAndCopyFiles("alchemy.run.ts.hbs", alchemyTemplateSrc, webAppDir, context);
2273
+ }
2274
+ if (context.serverDeploy === "alchemy") {
2275
+ const alchemyTemplateSrc = path.join(PKG_ROOT, "templates/deploy/alchemy");
2276
+ const serverAppDir = path.join(projectDir, "apps/server");
2277
+ if (await fs.pathExists(alchemyTemplateSrc) && await fs.pathExists(serverAppDir)) {
2278
+ await processAndCopyFiles("alchemy.run.ts.hbs", alchemyTemplateSrc, serverAppDir, context);
2279
+ await processAndCopyFiles("env.d.ts.hbs", alchemyTemplateSrc, serverAppDir, context);
2280
+ }
2281
+ }
2282
+ }
2283
+ if (context.webDeploy !== "none" && context.webDeploy !== "alchemy") {
2090
2284
  const webAppDir = path.join(projectDir, "apps/web");
2091
- if (!await fs.pathExists(webAppDir)) return;
2092
- const frontends = context.frontend;
2093
- const templateMap = {
2094
- "tanstack-router": "react/tanstack-router",
2095
- "tanstack-start": "react/tanstack-start",
2096
- "react-router": "react/react-router",
2097
- solid: "solid",
2098
- next: "react/next",
2099
- nuxt: "nuxt",
2100
- svelte: "svelte"
2101
- };
2102
- for (const f of frontends) if (templateMap[f]) {
2103
- const deployTemplateSrc = path.join(PKG_ROOT, `templates/deploy/web/${templateMap[f]}`);
2104
- if (await fs.pathExists(deployTemplateSrc)) await processAndCopyFiles("**/*", deployTemplateSrc, webAppDir, context);
2285
+ if (await fs.pathExists(webAppDir)) {
2286
+ const frontends = context.frontend;
2287
+ const templateMap = {
2288
+ "tanstack-router": "react/tanstack-router",
2289
+ "tanstack-start": "react/tanstack-start",
2290
+ "react-router": "react/react-router",
2291
+ solid: "solid",
2292
+ next: "react/next",
2293
+ nuxt: "nuxt",
2294
+ svelte: "svelte"
2295
+ };
2296
+ for (const f of frontends) if (templateMap[f]) {
2297
+ const deployTemplateSrc = path.join(PKG_ROOT, `templates/deploy/${context.webDeploy}/web/${templateMap[f]}`);
2298
+ if (await fs.pathExists(deployTemplateSrc)) await processAndCopyFiles("**/*", deployTemplateSrc, webAppDir, context);
2299
+ }
2300
+ }
2301
+ }
2302
+ if (context.serverDeploy !== "none" && context.serverDeploy !== "alchemy") {
2303
+ const serverAppDir = path.join(projectDir, "apps/server");
2304
+ if (await fs.pathExists(serverAppDir)) {
2305
+ const deployTemplateSrc = path.join(PKG_ROOT, `templates/deploy/${context.serverDeploy}/server`);
2306
+ if (await fs.pathExists(deployTemplateSrc)) await processAndCopyFiles("**/*", deployTemplateSrc, serverAppDir, context);
2105
2307
  }
2106
2308
  }
2107
2309
  }
2108
2310
 
2109
2311
  //#endregion
2110
- //#region src/helpers/setup/ruler-setup.ts
2312
+ //#region src/helpers/addons/ruler-setup.ts
2111
2313
  async function setupVibeRules(config) {
2112
2314
  const { packageManager, projectDir } = config;
2113
2315
  try {
@@ -2189,7 +2391,7 @@ async function addRulerScriptToPackageJson(projectDir, packageManager) {
2189
2391
  }
2190
2392
 
2191
2393
  //#endregion
2192
- //#region src/helpers/setup/starlight-setup.ts
2394
+ //#region src/helpers/addons/starlight-setup.ts
2193
2395
  async function setupStarlight(config) {
2194
2396
  const { packageManager, projectDir } = config;
2195
2397
  const s = spinner();
@@ -2221,7 +2423,7 @@ async function setupStarlight(config) {
2221
2423
  }
2222
2424
 
2223
2425
  //#endregion
2224
- //#region src/helpers/setup/tauri-setup.ts
2426
+ //#region src/helpers/addons/tauri-setup.ts
2225
2427
  async function setupTauri(config) {
2226
2428
  const { packageManager, frontend, projectDir } = config;
2227
2429
  const s = spinner();
@@ -2258,8 +2460,8 @@ async function setupTauri(config) {
2258
2460
  `--window-title=${path.basename(projectDir)}`,
2259
2461
  `--frontend-dist=${frontendDist}`,
2260
2462
  `--dev-url=${devUrl}`,
2261
- `--before-dev-command=\"${packageManager} run dev\"`,
2262
- `--before-build-command=\"${packageManager} run build\"`
2463
+ `--before-dev-command="${packageManager} run dev"`,
2464
+ `--before-build-command="${packageManager} run build"`
2263
2465
  ];
2264
2466
  const tauriArgsString = tauriArgs.join(" ");
2265
2467
  const commandWithArgs = `@tauri-apps/cli@latest ${tauriArgsString}`;
@@ -2277,7 +2479,7 @@ async function setupTauri(config) {
2277
2479
  }
2278
2480
 
2279
2481
  //#endregion
2280
- //#region src/helpers/setup/ultracite-setup.ts
2482
+ //#region src/helpers/addons/ultracite-setup.ts
2281
2483
  const EDITORS = {
2282
2484
  vscode: {
2283
2485
  label: "VSCode / Cursor / Windsurf",
@@ -2346,7 +2548,7 @@ async function setupUltracite(config, hasHusky) {
2346
2548
  ];
2347
2549
  if (editors.length > 0) ultraciteArgs.push("--editors", ...editors);
2348
2550
  if (rules.length > 0) ultraciteArgs.push("--rules", ...rules);
2349
- if (hasHusky) ultraciteArgs.push("--features", "husky", "lint-staged");
2551
+ if (hasHusky) ultraciteArgs.push("--integrations", "husky", "lint-staged");
2350
2552
  const ultraciteArgsString = ultraciteArgs.join(" ");
2351
2553
  const commandWithArgs = `ultracite@latest ${ultraciteArgsString} --skip-install`;
2352
2554
  const ultraciteInitCommand = getPackageExecutionCommand(packageManager, commandWithArgs);
@@ -2384,7 +2586,7 @@ function ensureArrayProperty(obj, name) {
2384
2586
  }
2385
2587
 
2386
2588
  //#endregion
2387
- //#region src/helpers/setup/vite-pwa-setup.ts
2589
+ //#region src/helpers/addons/vite-pwa-setup.ts
2388
2590
  async function addPwaToViteConfig(viteConfigPath, projectName) {
2389
2591
  const sourceFile = tsProject.addSourceFileAtPathIfExists(viteConfigPath);
2390
2592
  if (!sourceFile) throw new Error("vite config not found");
@@ -2418,7 +2620,7 @@ async function addPwaToViteConfig(viteConfigPath, projectName) {
2418
2620
  }
2419
2621
 
2420
2622
  //#endregion
2421
- //#region src/helpers/setup/addons-setup.ts
2623
+ //#region src/helpers/addons/addons-setup.ts
2422
2624
  async function setupAddons(config, isAddCommand = false) {
2423
2625
  const { addons, frontend, projectDir, packageManager } = config;
2424
2626
  const hasReactWebFrontend = frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("next");
@@ -2552,7 +2754,7 @@ async function setupOxlint(projectDir, packageManager) {
2552
2754
  }
2553
2755
 
2554
2756
  //#endregion
2555
- //#region src/helpers/project-generation/detect-project-config.ts
2757
+ //#region src/helpers/core/detect-project-config.ts
2556
2758
  async function detectProjectConfig(projectDir) {
2557
2759
  try {
2558
2760
  const btsConfig = await readBtsConfig(projectDir);
@@ -2570,7 +2772,8 @@ async function detectProjectConfig(projectDir) {
2570
2772
  packageManager: btsConfig.packageManager,
2571
2773
  dbSetup: btsConfig.dbSetup,
2572
2774
  api: btsConfig.api,
2573
- webDeploy: btsConfig.webDeploy
2775
+ webDeploy: btsConfig.webDeploy,
2776
+ serverDeploy: btsConfig.serverDeploy
2574
2777
  };
2575
2778
  return null;
2576
2779
  } catch (_error) {
@@ -2586,7 +2789,7 @@ async function isBetterTStackProject(projectDir) {
2586
2789
  }
2587
2790
 
2588
2791
  //#endregion
2589
- //#region src/helpers/project-generation/install-dependencies.ts
2792
+ //#region src/helpers/core/install-dependencies.ts
2590
2793
  async function installDependencies({ projectDir, packageManager }) {
2591
2794
  const s = spinner();
2592
2795
  try {
@@ -2603,7 +2806,7 @@ async function installDependencies({ projectDir, packageManager }) {
2603
2806
  }
2604
2807
 
2605
2808
  //#endregion
2606
- //#region src/helpers/project-generation/add-addons.ts
2809
+ //#region src/helpers/core/add-addons.ts
2607
2810
  async function addAddonsToProject(input) {
2608
2811
  try {
2609
2812
  const projectDir = input.projectDir || process.cwd();
@@ -2628,7 +2831,8 @@ async function addAddonsToProject(input) {
2628
2831
  install: input.install || false,
2629
2832
  dbSetup: detectedConfig.dbSetup || "none",
2630
2833
  api: detectedConfig.api || "none",
2631
- webDeploy: detectedConfig.webDeploy || "none"
2834
+ webDeploy: detectedConfig.webDeploy || "none",
2835
+ serverDeploy: detectedConfig.serverDeploy || "none"
2632
2836
  };
2633
2837
  for (const addon of input.addons) {
2634
2838
  const { isCompatible, reason } = validateAddonCompatibility(addon, config.frontend);
@@ -2651,12 +2855,92 @@ async function addAddonsToProject(input) {
2651
2855
  }
2652
2856
 
2653
2857
  //#endregion
2654
- //#region src/helpers/setup/workers-nuxt-setup.ts
2655
- async function setupNuxtWorkersDeploy(projectDir, packageManager) {
2858
+ //#region src/helpers/deployment/server-deploy-setup.ts
2859
+ async function setupServerDeploy(config) {
2860
+ const { serverDeploy, webDeploy, projectDir } = config;
2861
+ const { packageManager } = config;
2862
+ if (serverDeploy === "none") return;
2863
+ if (serverDeploy === "alchemy" && webDeploy === "alchemy") return;
2864
+ const serverDir = path.join(projectDir, "apps/server");
2865
+ if (!await fs.pathExists(serverDir)) return;
2866
+ if (serverDeploy === "wrangler") {
2867
+ await setupWorkersServerDeploy(serverDir, packageManager);
2868
+ await generateCloudflareWorkerTypes({
2869
+ serverDir,
2870
+ packageManager
2871
+ });
2872
+ } else if (serverDeploy === "alchemy") await setupAlchemyServerDeploy(serverDir, packageManager);
2873
+ }
2874
+ async function setupWorkersServerDeploy(serverDir, _packageManager) {
2875
+ const packageJsonPath = path.join(serverDir, "package.json");
2876
+ if (!await fs.pathExists(packageJsonPath)) return;
2877
+ const packageJson = await fs.readJson(packageJsonPath);
2878
+ packageJson.scripts = {
2879
+ ...packageJson.scripts,
2880
+ dev: "wrangler dev --port=3000",
2881
+ start: "wrangler dev",
2882
+ deploy: "wrangler deploy",
2883
+ build: "wrangler deploy --dry-run",
2884
+ "cf-typegen": "wrangler types --env-interface CloudflareBindings"
2885
+ };
2886
+ await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
2887
+ await addPackageDependency({
2888
+ devDependencies: [
2889
+ "wrangler",
2890
+ "@types/node",
2891
+ "@cloudflare/workers-types"
2892
+ ],
2893
+ projectDir: serverDir
2894
+ });
2895
+ }
2896
+ async function generateCloudflareWorkerTypes({ serverDir, packageManager }) {
2897
+ if (!await fs.pathExists(serverDir)) return;
2898
+ const s = spinner();
2899
+ try {
2900
+ s.start("Generating Cloudflare Workers types...");
2901
+ const runCmd = packageManager === "npm" ? "npm" : packageManager;
2902
+ await execa(runCmd, ["run", "cf-typegen"], { cwd: serverDir });
2903
+ s.stop("Cloudflare Workers types generated successfully!");
2904
+ } catch {
2905
+ s.stop(pc.yellow("Failed to generate Cloudflare Workers types"));
2906
+ const managerCmd = `${packageManager} run`;
2907
+ log.warn(`Note: You can manually run 'cd apps/server && ${managerCmd} cf-typegen' in the project directory later`);
2908
+ }
2909
+ }
2910
+ async function setupAlchemyServerDeploy(serverDir, _packageManager) {
2911
+ if (!await fs.pathExists(serverDir)) return;
2912
+ await addPackageDependency({
2913
+ devDependencies: [
2914
+ "alchemy",
2915
+ "wrangler",
2916
+ "@types/node",
2917
+ "@cloudflare/workers-types",
2918
+ "dotenv"
2919
+ ],
2920
+ projectDir: serverDir
2921
+ });
2922
+ const packageJsonPath = path.join(serverDir, "package.json");
2923
+ if (await fs.pathExists(packageJsonPath)) {
2924
+ const packageJson = await fs.readJson(packageJsonPath);
2925
+ packageJson.scripts = {
2926
+ ...packageJson.scripts,
2927
+ dev: "wrangler dev --port=3000",
2928
+ build: "wrangler deploy --dry-run",
2929
+ deploy: "alchemy deploy",
2930
+ destroy: "alchemy destroy",
2931
+ "alchemy:dev": "alchemy dev"
2932
+ };
2933
+ await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
2934
+ }
2935
+ }
2936
+
2937
+ //#endregion
2938
+ //#region src/helpers/deployment/alchemy/alchemy-next-setup.ts
2939
+ async function setupNextAlchemyDeploy(projectDir, _packageManager) {
2656
2940
  const webAppDir = path.join(projectDir, "apps/web");
2657
2941
  if (!await fs.pathExists(webAppDir)) return;
2658
2942
  await addPackageDependency({
2659
- devDependencies: ["nitro-cloudflare-dev", "wrangler"],
2943
+ devDependencies: ["alchemy", "dotenv"],
2660
2944
  projectDir: webAppDir
2661
2945
  });
2662
2946
  const pkgPath = path.join(webAppDir, "package.json");
@@ -2664,63 +2948,93 @@ async function setupNuxtWorkersDeploy(projectDir, packageManager) {
2664
2948
  const pkg = await fs.readJson(pkgPath);
2665
2949
  pkg.scripts = {
2666
2950
  ...pkg.scripts,
2667
- deploy: `${packageManager} run build && wrangler deploy`,
2668
- "cf-typegen": "wrangler types"
2951
+ deploy: "alchemy deploy",
2952
+ destroy: "alchemy destroy",
2953
+ "alchemy:dev": "alchemy dev"
2954
+ };
2955
+ await fs.writeJson(pkgPath, pkg, { spaces: 2 });
2956
+ }
2957
+ }
2958
+
2959
+ //#endregion
2960
+ //#region src/helpers/deployment/alchemy/alchemy-nuxt-setup.ts
2961
+ async function setupNuxtAlchemyDeploy(projectDir, _packageManager) {
2962
+ const webAppDir = path.join(projectDir, "apps/web");
2963
+ if (!await fs.pathExists(webAppDir)) return;
2964
+ await addPackageDependency({
2965
+ devDependencies: [
2966
+ "alchemy",
2967
+ "nitro-cloudflare-dev",
2968
+ "dotenv"
2969
+ ],
2970
+ projectDir: webAppDir
2971
+ });
2972
+ const pkgPath = path.join(webAppDir, "package.json");
2973
+ if (await fs.pathExists(pkgPath)) {
2974
+ const pkg = await fs.readJson(pkgPath);
2975
+ pkg.scripts = {
2976
+ ...pkg.scripts,
2977
+ deploy: "alchemy deploy",
2978
+ destroy: "alchemy destroy",
2979
+ "alchemy:dev": "alchemy dev"
2669
2980
  };
2670
2981
  await fs.writeJson(pkgPath, pkg, { spaces: 2 });
2671
2982
  }
2672
2983
  const nuxtConfigPath = path.join(webAppDir, "nuxt.config.ts");
2673
2984
  if (!await fs.pathExists(nuxtConfigPath)) return;
2674
- const sourceFile = tsProject.addSourceFileAtPathIfExists(nuxtConfigPath);
2675
- if (!sourceFile) return;
2676
- const defineCall = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression).find((expr) => {
2677
- const expression = expr.getExpression();
2678
- return Node.isIdentifier(expression) && expression.getText() === "defineNuxtConfig";
2679
- });
2680
- if (!defineCall) return;
2681
- const configObj = defineCall.getArguments()[0];
2682
- if (!configObj) return;
2683
- const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2684
- const compatProp = configObj.getProperty("compatibilityDate");
2685
- if (compatProp && compatProp.getKind() === SyntaxKind.PropertyAssignment) compatProp.setInitializer(`'${today}'`);
2686
- else configObj.addPropertyAssignment({
2687
- name: "compatibilityDate",
2688
- initializer: `'${today}'`
2689
- });
2690
- const nitroInitializer = `{
2985
+ try {
2986
+ const project = new Project({ manipulationSettings: {
2987
+ indentationText: IndentationText.TwoSpaces,
2988
+ quoteKind: QuoteKind.Double
2989
+ } });
2990
+ project.addSourceFileAtPath(nuxtConfigPath);
2991
+ const sourceFile = project.getSourceFileOrThrow(nuxtConfigPath);
2992
+ const exportAssignment = sourceFile.getExportAssignment((d) => !d.isExportEquals());
2993
+ if (!exportAssignment) return;
2994
+ const defineConfigCall = exportAssignment.getExpression();
2995
+ if (!Node.isCallExpression(defineConfigCall) || defineConfigCall.getExpression().getText() !== "defineNuxtConfig") return;
2996
+ let configObject = defineConfigCall.getArguments()[0];
2997
+ if (!configObject) configObject = defineConfigCall.addArgument("{}");
2998
+ if (Node.isObjectLiteralExpression(configObject)) {
2999
+ if (!configObject.getProperty("nitro")) configObject.addPropertyAssignment({
3000
+ name: "nitro",
3001
+ initializer: `{
2691
3002
  preset: "cloudflare_module",
2692
3003
  cloudflare: {
2693
3004
  deployConfig: true,
2694
3005
  nodeCompat: true
2695
3006
  }
2696
- }`;
2697
- const nitroProp = configObj.getProperty("nitro");
2698
- if (nitroProp && nitroProp.getKind() === SyntaxKind.PropertyAssignment) nitroProp.setInitializer(nitroInitializer);
2699
- else configObj.addPropertyAssignment({
2700
- name: "nitro",
2701
- initializer: nitroInitializer
2702
- });
2703
- const modulesProp = configObj.getProperty("modules");
2704
- if (modulesProp && modulesProp.getKind() === SyntaxKind.PropertyAssignment) {
2705
- const arrayExpr = modulesProp.getFirstDescendantByKind(SyntaxKind.ArrayLiteralExpression);
2706
- if (arrayExpr) {
2707
- const alreadyHas = arrayExpr.getElements().some((el) => el.getText().replace(/['"`]/g, "") === "nitro-cloudflare-dev");
2708
- if (!alreadyHas) arrayExpr.addElement("'nitro-cloudflare-dev'");
3007
+ }`
3008
+ });
3009
+ const modulesProperty = configObject.getProperty("modules");
3010
+ if (modulesProperty && Node.isPropertyAssignment(modulesProperty)) {
3011
+ const initializer = modulesProperty.getInitializer();
3012
+ if (Node.isArrayLiteralExpression(initializer)) {
3013
+ const hasModule = initializer.getElements().some((el) => el.getText() === "\"nitro-cloudflare-dev\"" || el.getText() === "'nitro-cloudflare-dev'");
3014
+ if (!hasModule) initializer.addElement("\"nitro-cloudflare-dev\"");
3015
+ }
3016
+ } else if (!modulesProperty) configObject.addPropertyAssignment({
3017
+ name: "modules",
3018
+ initializer: "[\"nitro-cloudflare-dev\"]"
3019
+ });
2709
3020
  }
2710
- } else configObj.addPropertyAssignment({
2711
- name: "modules",
2712
- initializer: "['nitro-cloudflare-dev']"
2713
- });
2714
- await tsProject.save();
3021
+ await project.save();
3022
+ } catch (error) {
3023
+ console.warn("Failed to update nuxt.config.ts:", error);
3024
+ }
2715
3025
  }
2716
3026
 
2717
3027
  //#endregion
2718
- //#region src/helpers/setup/workers-svelte-setup.ts
2719
- async function setupSvelteWorkersDeploy(projectDir, packageManager) {
3028
+ //#region src/helpers/deployment/alchemy/alchemy-react-router-setup.ts
3029
+ async function setupReactRouterAlchemyDeploy(projectDir, _packageManager) {
2720
3030
  const webAppDir = path.join(projectDir, "apps/web");
2721
3031
  if (!await fs.pathExists(webAppDir)) return;
2722
3032
  await addPackageDependency({
2723
- devDependencies: ["@sveltejs/adapter-cloudflare", "wrangler"],
3033
+ devDependencies: [
3034
+ "alchemy",
3035
+ "@cloudflare/vite-plugin",
3036
+ "dotenv"
3037
+ ],
2724
3038
  projectDir: webAppDir
2725
3039
  });
2726
3040
  const pkgPath = path.join(webAppDir, "package.json");
@@ -2728,36 +3042,100 @@ async function setupSvelteWorkersDeploy(projectDir, packageManager) {
2728
3042
  const pkg = await fs.readJson(pkgPath);
2729
3043
  pkg.scripts = {
2730
3044
  ...pkg.scripts,
2731
- deploy: `${packageManager} run build && wrangler deploy`,
2732
- "cf-typegen": "wrangler types ./src/worker-configuration.d.ts"
3045
+ deploy: "alchemy deploy",
3046
+ destroy: "alchemy destroy",
3047
+ "alchemy:dev": "alchemy dev"
2733
3048
  };
2734
3049
  await fs.writeJson(pkgPath, pkg, { spaces: 2 });
2735
3050
  }
2736
- const possibleConfigFiles = [path.join(webAppDir, "svelte.config.js"), path.join(webAppDir, "svelte.config.ts")];
2737
- const existingConfigPath = (await Promise.all(possibleConfigFiles.map(async (p) => await fs.pathExists(p) ? p : ""))).find((p) => p);
2738
- if (existingConfigPath) {
2739
- const sourceFile = tsProject.addSourceFileAtPathIfExists(existingConfigPath);
2740
- if (!sourceFile) return;
2741
- const adapterImport = sourceFile.getImportDeclarations().find((imp) => ["@sveltejs/adapter-auto", "@sveltejs/adapter-node"].includes(imp.getModuleSpecifierValue()));
2742
- if (adapterImport) adapterImport.setModuleSpecifier("@sveltejs/adapter-cloudflare");
2743
- else {
2744
- const alreadyHasCloudflare = sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === "@sveltejs/adapter-cloudflare");
2745
- if (!alreadyHasCloudflare) sourceFile.insertImportDeclaration(0, {
2746
- defaultImport: "adapter",
2747
- moduleSpecifier: "@sveltejs/adapter-cloudflare"
3051
+ const viteConfigPath = path.join(webAppDir, "vite.config.ts");
3052
+ if (await fs.pathExists(viteConfigPath)) try {
3053
+ const project = new Project({ manipulationSettings: {
3054
+ indentationText: IndentationText.TwoSpaces,
3055
+ quoteKind: QuoteKind.Double
3056
+ } });
3057
+ project.addSourceFileAtPath(viteConfigPath);
3058
+ const sourceFile = project.getSourceFileOrThrow(viteConfigPath);
3059
+ const alchemyImport = sourceFile.getImportDeclaration("alchemy/cloudflare/react-router");
3060
+ if (!alchemyImport) sourceFile.addImportDeclaration({
3061
+ moduleSpecifier: "alchemy/cloudflare/react-router",
3062
+ defaultImport: "alchemy"
3063
+ });
3064
+ const exportAssignment = sourceFile.getExportAssignment((d) => !d.isExportEquals());
3065
+ if (!exportAssignment) return;
3066
+ const defineConfigCall = exportAssignment.getExpression();
3067
+ if (!Node.isCallExpression(defineConfigCall) || defineConfigCall.getExpression().getText() !== "defineConfig") return;
3068
+ let configObject = defineConfigCall.getArguments()[0];
3069
+ if (!configObject) configObject = defineConfigCall.addArgument("{}");
3070
+ if (Node.isObjectLiteralExpression(configObject)) {
3071
+ const pluginsProperty = configObject.getProperty("plugins");
3072
+ if (pluginsProperty && Node.isPropertyAssignment(pluginsProperty)) {
3073
+ const initializer = pluginsProperty.getInitializer();
3074
+ if (Node.isArrayLiteralExpression(initializer)) {
3075
+ const hasCloudflarePlugin = initializer.getElements().some((el) => el.getText().includes("cloudflare("));
3076
+ if (!hasCloudflarePlugin) initializer.addElement("alchemy()");
3077
+ }
3078
+ } else if (!pluginsProperty) configObject.addPropertyAssignment({
3079
+ name: "plugins",
3080
+ initializer: "[alchemy()]"
2748
3081
  });
2749
3082
  }
2750
- await tsProject.save();
3083
+ await project.save();
3084
+ } catch (error) {
3085
+ console.warn("Failed to update vite.config.ts:", error);
3086
+ }
3087
+ const reactRouterConfigPath = path.join(webAppDir, "react-router.config.ts");
3088
+ if (await fs.pathExists(reactRouterConfigPath)) try {
3089
+ const project = new Project({ manipulationSettings: {
3090
+ indentationText: IndentationText.TwoSpaces,
3091
+ quoteKind: QuoteKind.Double
3092
+ } });
3093
+ project.addSourceFileAtPath(reactRouterConfigPath);
3094
+ const sourceFile = project.getSourceFileOrThrow(reactRouterConfigPath);
3095
+ const exportAssignment = sourceFile.getExportAssignment((d) => !d.isExportEquals());
3096
+ if (!exportAssignment) return;
3097
+ const configExpression = exportAssignment.getExpression();
3098
+ let configObject;
3099
+ if (Node.isObjectLiteralExpression(configExpression)) configObject = configExpression;
3100
+ else if (Node.isSatisfiesExpression(configExpression)) {
3101
+ const expression = configExpression.getExpression();
3102
+ if (Node.isObjectLiteralExpression(expression)) configObject = expression;
3103
+ }
3104
+ if (!configObject || !Node.isObjectLiteralExpression(configObject)) return;
3105
+ const futureProperty = configObject.getProperty("future");
3106
+ if (!futureProperty) configObject.addPropertyAssignment({
3107
+ name: "future",
3108
+ initializer: `{
3109
+ unstable_viteEnvironmentApi: true,
3110
+ }`
3111
+ });
3112
+ else if (Node.isPropertyAssignment(futureProperty)) {
3113
+ const futureInitializer = futureProperty.getInitializer();
3114
+ if (Node.isObjectLiteralExpression(futureInitializer)) {
3115
+ const viteEnvApiProp = futureInitializer.getProperty("unstable_viteEnvironmentApi");
3116
+ if (!viteEnvApiProp) futureInitializer.addPropertyAssignment({
3117
+ name: "unstable_viteEnvironmentApi",
3118
+ initializer: "true"
3119
+ });
3120
+ else if (Node.isPropertyAssignment(viteEnvApiProp)) {
3121
+ const value = viteEnvApiProp.getInitializer()?.getText();
3122
+ if (value === "false") viteEnvApiProp.setInitializer("true");
3123
+ }
3124
+ }
3125
+ }
3126
+ await project.save();
3127
+ } catch (error) {
3128
+ console.warn("Failed to update react-router.config.ts:", error);
2751
3129
  }
2752
3130
  }
2753
3131
 
2754
3132
  //#endregion
2755
- //#region src/helpers/setup/workers-tanstack-start-setup.ts
2756
- async function setupTanstackStartWorkersDeploy(projectDir, packageManager) {
3133
+ //#region src/helpers/deployment/alchemy/alchemy-solid-setup.ts
3134
+ async function setupSolidAlchemyDeploy(projectDir, _packageManager) {
2757
3135
  const webAppDir = path.join(projectDir, "apps/web");
2758
3136
  if (!await fs.pathExists(webAppDir)) return;
2759
3137
  await addPackageDependency({
2760
- devDependencies: ["wrangler"],
3138
+ devDependencies: ["alchemy", "dotenv"],
2761
3139
  projectDir: webAppDir
2762
3140
  });
2763
3141
  const pkgPath = path.join(webAppDir, "package.json");
@@ -2765,13 +3143,394 @@ async function setupTanstackStartWorkersDeploy(projectDir, packageManager) {
2765
3143
  const pkg = await fs.readJson(pkgPath);
2766
3144
  pkg.scripts = {
2767
3145
  ...pkg.scripts,
2768
- deploy: `${packageManager} run build && wrangler deploy`,
2769
- "cf-typegen": "wrangler types --env-interface Env"
3146
+ deploy: "alchemy deploy",
3147
+ destroy: "alchemy destroy",
3148
+ "alchemy:dev": "alchemy dev"
2770
3149
  };
2771
3150
  await fs.writeJson(pkgPath, pkg, { spaces: 2 });
2772
3151
  }
2773
- const viteConfigPath = path.join(webAppDir, "vite.config.ts");
2774
- if (!await fs.pathExists(viteConfigPath)) return;
3152
+ }
3153
+
3154
+ //#endregion
3155
+ //#region src/helpers/deployment/alchemy/alchemy-svelte-setup.ts
3156
+ async function setupSvelteAlchemyDeploy(projectDir, _packageManager) {
3157
+ const webAppDir = path.join(projectDir, "apps/web");
3158
+ if (!await fs.pathExists(webAppDir)) return;
3159
+ await addPackageDependency({
3160
+ devDependencies: [
3161
+ "alchemy",
3162
+ "@sveltejs/adapter-cloudflare",
3163
+ "dotenv"
3164
+ ],
3165
+ projectDir: webAppDir
3166
+ });
3167
+ const pkgPath = path.join(webAppDir, "package.json");
3168
+ if (await fs.pathExists(pkgPath)) {
3169
+ const pkg = await fs.readJson(pkgPath);
3170
+ pkg.scripts = {
3171
+ ...pkg.scripts,
3172
+ deploy: "alchemy deploy",
3173
+ destroy: "alchemy destroy",
3174
+ "alchemy:dev": "alchemy dev"
3175
+ };
3176
+ await fs.writeJson(pkgPath, pkg, { spaces: 2 });
3177
+ }
3178
+ const svelteConfigPath = path.join(webAppDir, "svelte.config.js");
3179
+ if (!await fs.pathExists(svelteConfigPath)) return;
3180
+ try {
3181
+ const project = new Project({ manipulationSettings: {
3182
+ indentationText: IndentationText.TwoSpaces,
3183
+ quoteKind: QuoteKind.Single
3184
+ } });
3185
+ project.addSourceFileAtPath(svelteConfigPath);
3186
+ const sourceFile = project.getSourceFileOrThrow(svelteConfigPath);
3187
+ const importDeclarations = sourceFile.getImportDeclarations();
3188
+ const adapterImport = importDeclarations.find((imp) => imp.getModuleSpecifierValue().includes("@sveltejs/adapter"));
3189
+ if (adapterImport) {
3190
+ adapterImport.setModuleSpecifier("alchemy/cloudflare/sveltekit");
3191
+ adapterImport.removeDefaultImport();
3192
+ adapterImport.setDefaultImport("alchemy");
3193
+ } else sourceFile.insertImportDeclaration(0, {
3194
+ moduleSpecifier: "alchemy/cloudflare/sveltekit",
3195
+ defaultImport: "alchemy"
3196
+ });
3197
+ const configVariable = sourceFile.getVariableDeclaration("config");
3198
+ if (configVariable) {
3199
+ const initializer = configVariable.getInitializer();
3200
+ if (Node.isObjectLiteralExpression(initializer)) updateAdapterInConfig(initializer);
3201
+ }
3202
+ await project.save();
3203
+ } catch (error) {
3204
+ console.warn("Failed to update svelte.config.js:", error);
3205
+ }
3206
+ }
3207
+ function updateAdapterInConfig(configObject) {
3208
+ if (!Node.isObjectLiteralExpression(configObject)) return;
3209
+ const kitProperty = configObject.getProperty("kit");
3210
+ if (kitProperty && Node.isPropertyAssignment(kitProperty)) {
3211
+ const kitInitializer = kitProperty.getInitializer();
3212
+ if (Node.isObjectLiteralExpression(kitInitializer)) {
3213
+ const adapterProperty = kitInitializer.getProperty("adapter");
3214
+ if (adapterProperty && Node.isPropertyAssignment(adapterProperty)) {
3215
+ const initializer = adapterProperty.getInitializer();
3216
+ if (Node.isCallExpression(initializer)) {
3217
+ const expression = initializer.getExpression();
3218
+ if (Node.isIdentifier(expression) && expression.getText() === "adapter") expression.replaceWithText("alchemy");
3219
+ }
3220
+ }
3221
+ }
3222
+ }
3223
+ }
3224
+
3225
+ //#endregion
3226
+ //#region src/helpers/deployment/alchemy/alchemy-tanstack-router-setup.ts
3227
+ async function setupTanStackRouterAlchemyDeploy(projectDir, _packageManager) {
3228
+ const webAppDir = path.join(projectDir, "apps/web");
3229
+ if (!await fs.pathExists(webAppDir)) return;
3230
+ await addPackageDependency({
3231
+ devDependencies: ["alchemy", "dotenv"],
3232
+ projectDir: webAppDir
3233
+ });
3234
+ const pkgPath = path.join(webAppDir, "package.json");
3235
+ if (await fs.pathExists(pkgPath)) {
3236
+ const pkg = await fs.readJson(pkgPath);
3237
+ pkg.scripts = {
3238
+ ...pkg.scripts,
3239
+ deploy: "alchemy deploy",
3240
+ destroy: "alchemy destroy",
3241
+ "alchemy:dev": "alchemy dev"
3242
+ };
3243
+ await fs.writeJson(pkgPath, pkg, { spaces: 2 });
3244
+ }
3245
+ }
3246
+
3247
+ //#endregion
3248
+ //#region src/helpers/deployment/alchemy/alchemy-tanstack-start-setup.ts
3249
+ async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager) {
3250
+ const webAppDir = path.join(projectDir, "apps/web");
3251
+ if (!await fs.pathExists(webAppDir)) return;
3252
+ await addPackageDependency({
3253
+ devDependencies: [
3254
+ "alchemy",
3255
+ "nitropack",
3256
+ "dotenv"
3257
+ ],
3258
+ projectDir: webAppDir
3259
+ });
3260
+ const pkgPath = path.join(webAppDir, "package.json");
3261
+ if (await fs.pathExists(pkgPath)) {
3262
+ const pkg = await fs.readJson(pkgPath);
3263
+ pkg.scripts = {
3264
+ ...pkg.scripts,
3265
+ deploy: "alchemy deploy",
3266
+ destroy: "alchemy destroy",
3267
+ "alchemy:dev": "alchemy dev"
3268
+ };
3269
+ await fs.writeJson(pkgPath, pkg, { spaces: 2 });
3270
+ }
3271
+ const viteConfigPath = path.join(webAppDir, "vite.config.ts");
3272
+ if (await fs.pathExists(viteConfigPath)) try {
3273
+ const project = new Project({ manipulationSettings: {
3274
+ indentationText: IndentationText.TwoSpaces,
3275
+ quoteKind: QuoteKind.Double
3276
+ } });
3277
+ project.addSourceFileAtPath(viteConfigPath);
3278
+ const sourceFile = project.getSourceFileOrThrow(viteConfigPath);
3279
+ const alchemyImport = sourceFile.getImportDeclaration("alchemy/cloudflare/tanstack-start");
3280
+ if (!alchemyImport) sourceFile.addImportDeclaration({
3281
+ moduleSpecifier: "alchemy/cloudflare/tanstack-start",
3282
+ defaultImport: "alchemy"
3283
+ });
3284
+ else alchemyImport.setModuleSpecifier("alchemy/cloudflare/tanstack-start");
3285
+ const exportAssignment = sourceFile.getExportAssignment((d) => !d.isExportEquals());
3286
+ if (!exportAssignment) return;
3287
+ const defineConfigCall = exportAssignment.getExpression();
3288
+ if (!Node.isCallExpression(defineConfigCall) || defineConfigCall.getExpression().getText() !== "defineConfig") return;
3289
+ let configObject = defineConfigCall.getArguments()[0];
3290
+ if (!configObject) configObject = defineConfigCall.addArgument("{}");
3291
+ if (Node.isObjectLiteralExpression(configObject)) {
3292
+ if (!configObject.getProperty("build")) configObject.addPropertyAssignment({
3293
+ name: "build",
3294
+ initializer: `{
3295
+ target: "esnext",
3296
+ rollupOptions: {
3297
+ external: ["node:async_hooks", "cloudflare:workers"],
3298
+ },
3299
+ }`
3300
+ });
3301
+ const pluginsProperty = configObject.getProperty("plugins");
3302
+ if (pluginsProperty && Node.isPropertyAssignment(pluginsProperty)) {
3303
+ const initializer = pluginsProperty.getInitializer();
3304
+ if (Node.isArrayLiteralExpression(initializer)) {
3305
+ const hasShim = initializer.getElements().some((el) => el.getText().includes("alchemy"));
3306
+ if (!hasShim) initializer.addElement("alchemy()");
3307
+ const tanstackElements = initializer.getElements().filter((el) => el.getText().includes("tanstackStart"));
3308
+ tanstackElements.forEach((element) => {
3309
+ if (Node.isCallExpression(element)) {
3310
+ const args = element.getArguments();
3311
+ if (args.length === 0) element.addArgument(`{
3312
+ target: "cloudflare-module",
3313
+ customViteReactPlugin: true,
3314
+ }`);
3315
+ else if (args.length === 1 && Node.isObjectLiteralExpression(args[0])) {
3316
+ const configObj = args[0];
3317
+ if (!configObj.getProperty("target")) configObj.addPropertyAssignment({
3318
+ name: "target",
3319
+ initializer: "\"cloudflare-module\""
3320
+ });
3321
+ if (!configObj.getProperty("customViteReactPlugin")) configObj.addPropertyAssignment({
3322
+ name: "customViteReactPlugin",
3323
+ initializer: "true"
3324
+ });
3325
+ }
3326
+ }
3327
+ });
3328
+ }
3329
+ } else configObject.addPropertyAssignment({
3330
+ name: "plugins",
3331
+ initializer: "[alchemy()]"
3332
+ });
3333
+ }
3334
+ await project.save();
3335
+ } catch (error) {
3336
+ console.warn("Failed to update vite.config.ts:", error);
3337
+ }
3338
+ const nitroConfigPath = path.join(webAppDir, "nitro.config.ts");
3339
+ const nitroConfigContent = `import { defineNitroConfig } from "nitropack/config";
3340
+
3341
+ export default defineNitroConfig({
3342
+ preset: "cloudflare-module",
3343
+ cloudflare: {
3344
+ nodeCompat: true,
3345
+ },
3346
+ });
3347
+ `;
3348
+ await fs.writeFile(nitroConfigPath, nitroConfigContent, "utf-8");
3349
+ }
3350
+
3351
+ //#endregion
3352
+ //#region src/helpers/deployment/alchemy/alchemy-combined-setup.ts
3353
+ async function setupCombinedAlchemyDeploy(projectDir, packageManager, config) {
3354
+ await addPackageDependency({
3355
+ devDependencies: ["alchemy", "dotenv"],
3356
+ projectDir
3357
+ });
3358
+ const rootPkgPath = path.join(projectDir, "package.json");
3359
+ if (await fs.pathExists(rootPkgPath)) {
3360
+ const pkg = await fs.readJson(rootPkgPath);
3361
+ pkg.scripts = {
3362
+ ...pkg.scripts,
3363
+ deploy: "alchemy deploy",
3364
+ destroy: "alchemy destroy",
3365
+ "alchemy:dev": "alchemy dev"
3366
+ };
3367
+ await fs.writeJson(rootPkgPath, pkg, { spaces: 2 });
3368
+ }
3369
+ const serverDir = path.join(projectDir, "apps/server");
3370
+ if (await fs.pathExists(serverDir)) await setupAlchemyServerDeploy(serverDir, packageManager);
3371
+ const frontend = config.frontend;
3372
+ const isNext = frontend.includes("next");
3373
+ const isNuxt = frontend.includes("nuxt");
3374
+ const isSvelte = frontend.includes("svelte");
3375
+ const isTanstackRouter = frontend.includes("tanstack-router");
3376
+ const isTanstackStart = frontend.includes("tanstack-start");
3377
+ const isReactRouter = frontend.includes("react-router");
3378
+ const isSolid = frontend.includes("solid");
3379
+ if (isNext) await setupNextAlchemyDeploy(projectDir, packageManager);
3380
+ else if (isNuxt) await setupNuxtAlchemyDeploy(projectDir, packageManager);
3381
+ else if (isSvelte) await setupSvelteAlchemyDeploy(projectDir, packageManager);
3382
+ else if (isTanstackStart) await setupTanStackStartAlchemyDeploy(projectDir, packageManager);
3383
+ else if (isTanstackRouter) await setupTanStackRouterAlchemyDeploy(projectDir, packageManager);
3384
+ else if (isReactRouter) await setupReactRouterAlchemyDeploy(projectDir, packageManager);
3385
+ else if (isSolid) await setupSolidAlchemyDeploy(projectDir, packageManager);
3386
+ }
3387
+
3388
+ //#endregion
3389
+ //#region src/helpers/deployment/workers/workers-next-setup.ts
3390
+ async function setupNextWorkersDeploy(projectDir, _packageManager) {
3391
+ const webAppDir = path.join(projectDir, "apps/web");
3392
+ if (!await fs.pathExists(webAppDir)) return;
3393
+ await addPackageDependency({
3394
+ dependencies: ["@opennextjs/cloudflare"],
3395
+ devDependencies: ["wrangler"],
3396
+ projectDir: webAppDir
3397
+ });
3398
+ const packageJsonPath = path.join(webAppDir, "package.json");
3399
+ if (await fs.pathExists(packageJsonPath)) {
3400
+ const pkg = await fs.readJson(packageJsonPath);
3401
+ pkg.scripts = {
3402
+ ...pkg.scripts,
3403
+ preview: "opennextjs-cloudflare build && opennextjs-cloudflare preview",
3404
+ deploy: "opennextjs-cloudflare build && opennextjs-cloudflare deploy",
3405
+ upload: "opennextjs-cloudflare build && opennextjs-cloudflare upload",
3406
+ "cf-typegen": "wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts"
3407
+ };
3408
+ await fs.writeJson(packageJsonPath, pkg, { spaces: 2 });
3409
+ }
3410
+ }
3411
+
3412
+ //#endregion
3413
+ //#region src/helpers/deployment/workers/workers-nuxt-setup.ts
3414
+ async function setupNuxtWorkersDeploy(projectDir, packageManager) {
3415
+ const webAppDir = path.join(projectDir, "apps/web");
3416
+ if (!await fs.pathExists(webAppDir)) return;
3417
+ await addPackageDependency({
3418
+ devDependencies: ["nitro-cloudflare-dev", "wrangler"],
3419
+ projectDir: webAppDir
3420
+ });
3421
+ const pkgPath = path.join(webAppDir, "package.json");
3422
+ if (await fs.pathExists(pkgPath)) {
3423
+ const pkg = await fs.readJson(pkgPath);
3424
+ pkg.scripts = {
3425
+ ...pkg.scripts,
3426
+ deploy: `${packageManager} run build && wrangler deploy`,
3427
+ "cf-typegen": "wrangler types"
3428
+ };
3429
+ await fs.writeJson(pkgPath, pkg, { spaces: 2 });
3430
+ }
3431
+ const nuxtConfigPath = path.join(webAppDir, "nuxt.config.ts");
3432
+ if (!await fs.pathExists(nuxtConfigPath)) return;
3433
+ const sourceFile = tsProject.addSourceFileAtPathIfExists(nuxtConfigPath);
3434
+ if (!sourceFile) return;
3435
+ const defineCall = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression).find((expr) => {
3436
+ const expression = expr.getExpression();
3437
+ return Node.isIdentifier(expression) && expression.getText() === "defineNuxtConfig";
3438
+ });
3439
+ if (!defineCall) return;
3440
+ const configObj = defineCall.getArguments()[0];
3441
+ if (!configObj) return;
3442
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3443
+ const compatProp = configObj.getProperty("compatibilityDate");
3444
+ if (compatProp && compatProp.getKind() === SyntaxKind.PropertyAssignment) compatProp.setInitializer(`'${today}'`);
3445
+ else configObj.addPropertyAssignment({
3446
+ name: "compatibilityDate",
3447
+ initializer: `'${today}'`
3448
+ });
3449
+ const nitroInitializer = `{
3450
+ preset: "cloudflare_module",
3451
+ cloudflare: {
3452
+ deployConfig: true,
3453
+ nodeCompat: true
3454
+ }
3455
+ }`;
3456
+ const nitroProp = configObj.getProperty("nitro");
3457
+ if (nitroProp && nitroProp.getKind() === SyntaxKind.PropertyAssignment) nitroProp.setInitializer(nitroInitializer);
3458
+ else configObj.addPropertyAssignment({
3459
+ name: "nitro",
3460
+ initializer: nitroInitializer
3461
+ });
3462
+ const modulesProp = configObj.getProperty("modules");
3463
+ if (modulesProp && modulesProp.getKind() === SyntaxKind.PropertyAssignment) {
3464
+ const arrayExpr = modulesProp.getFirstDescendantByKind(SyntaxKind.ArrayLiteralExpression);
3465
+ if (arrayExpr) {
3466
+ const alreadyHas = arrayExpr.getElements().some((el) => el.getText().replace(/['"`]/g, "") === "nitro-cloudflare-dev");
3467
+ if (!alreadyHas) arrayExpr.addElement("'nitro-cloudflare-dev'");
3468
+ }
3469
+ } else configObj.addPropertyAssignment({
3470
+ name: "modules",
3471
+ initializer: "['nitro-cloudflare-dev']"
3472
+ });
3473
+ await tsProject.save();
3474
+ }
3475
+
3476
+ //#endregion
3477
+ //#region src/helpers/deployment/workers/workers-svelte-setup.ts
3478
+ async function setupSvelteWorkersDeploy(projectDir, packageManager) {
3479
+ const webAppDir = path.join(projectDir, "apps/web");
3480
+ if (!await fs.pathExists(webAppDir)) return;
3481
+ await addPackageDependency({
3482
+ devDependencies: ["@sveltejs/adapter-cloudflare", "wrangler"],
3483
+ projectDir: webAppDir
3484
+ });
3485
+ const pkgPath = path.join(webAppDir, "package.json");
3486
+ if (await fs.pathExists(pkgPath)) {
3487
+ const pkg = await fs.readJson(pkgPath);
3488
+ pkg.scripts = {
3489
+ ...pkg.scripts,
3490
+ deploy: `${packageManager} run build && wrangler deploy`,
3491
+ "cf-typegen": "wrangler types ./src/worker-configuration.d.ts"
3492
+ };
3493
+ await fs.writeJson(pkgPath, pkg, { spaces: 2 });
3494
+ }
3495
+ const possibleConfigFiles = [path.join(webAppDir, "svelte.config.js"), path.join(webAppDir, "svelte.config.ts")];
3496
+ const existingConfigPath = (await Promise.all(possibleConfigFiles.map(async (p) => await fs.pathExists(p) ? p : ""))).find((p) => p);
3497
+ if (existingConfigPath) {
3498
+ const sourceFile = tsProject.addSourceFileAtPathIfExists(existingConfigPath);
3499
+ if (!sourceFile) return;
3500
+ const adapterImport = sourceFile.getImportDeclarations().find((imp) => ["@sveltejs/adapter-auto", "@sveltejs/adapter-node"].includes(imp.getModuleSpecifierValue()));
3501
+ if (adapterImport) adapterImport.setModuleSpecifier("@sveltejs/adapter-cloudflare");
3502
+ else {
3503
+ const alreadyHasCloudflare = sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === "@sveltejs/adapter-cloudflare");
3504
+ if (!alreadyHasCloudflare) sourceFile.insertImportDeclaration(0, {
3505
+ defaultImport: "adapter",
3506
+ moduleSpecifier: "@sveltejs/adapter-cloudflare"
3507
+ });
3508
+ }
3509
+ await tsProject.save();
3510
+ }
3511
+ }
3512
+
3513
+ //#endregion
3514
+ //#region src/helpers/deployment/workers/workers-tanstack-start-setup.ts
3515
+ async function setupTanstackStartWorkersDeploy(projectDir, packageManager) {
3516
+ const webAppDir = path.join(projectDir, "apps/web");
3517
+ if (!await fs.pathExists(webAppDir)) return;
3518
+ await addPackageDependency({
3519
+ devDependencies: ["wrangler"],
3520
+ projectDir: webAppDir
3521
+ });
3522
+ const pkgPath = path.join(webAppDir, "package.json");
3523
+ if (await fs.pathExists(pkgPath)) {
3524
+ const pkg = await fs.readJson(pkgPath);
3525
+ pkg.scripts = {
3526
+ ...pkg.scripts,
3527
+ deploy: `${packageManager} run build && wrangler deploy`,
3528
+ "cf-typegen": "wrangler types --env-interface Env"
3529
+ };
3530
+ await fs.writeJson(pkgPath, pkg, { spaces: 2 });
3531
+ }
3532
+ const viteConfigPath = path.join(webAppDir, "vite.config.ts");
3533
+ if (!await fs.pathExists(viteConfigPath)) return;
2775
3534
  const sourceFile = tsProject.addSourceFileAtPathIfExists(viteConfigPath);
2776
3535
  if (!sourceFile) return;
2777
3536
  const defineCall = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression).find((expr) => {
@@ -2790,7 +3549,7 @@ async function setupTanstackStartWorkersDeploy(projectDir, packageManager) {
2790
3549
  }
2791
3550
 
2792
3551
  //#endregion
2793
- //#region src/helpers/setup/workers-vite-setup.ts
3552
+ //#region src/helpers/deployment/workers/workers-vite-setup.ts
2794
3553
  async function setupWorkersVitePlugin(projectDir) {
2795
3554
  const webAppDir = path.join(projectDir, "apps/web");
2796
3555
  const viteConfigPath = path.join(webAppDir, "vite.config.ts");
@@ -2821,12 +3580,16 @@ async function setupWorkersVitePlugin(projectDir) {
2821
3580
  }
2822
3581
 
2823
3582
  //#endregion
2824
- //#region src/helpers/setup/web-deploy-setup.ts
3583
+ //#region src/helpers/deployment/web-deploy-setup.ts
2825
3584
  async function setupWebDeploy(config) {
2826
- const { webDeploy, frontend, projectDir } = config;
3585
+ const { webDeploy, serverDeploy, frontend, projectDir } = config;
2827
3586
  const { packageManager } = config;
2828
3587
  if (webDeploy === "none") return;
2829
- if (webDeploy !== "workers") return;
3588
+ if (webDeploy !== "wrangler" && webDeploy !== "alchemy") return;
3589
+ if (webDeploy === "alchemy" && serverDeploy === "alchemy") {
3590
+ await setupCombinedAlchemyDeploy(projectDir, packageManager, config);
3591
+ return;
3592
+ }
2830
3593
  const isNext = frontend.includes("next");
2831
3594
  const isNuxt = frontend.includes("nuxt");
2832
3595
  const isSvelte = frontend.includes("svelte");
@@ -2834,11 +3597,21 @@ async function setupWebDeploy(config) {
2834
3597
  const isTanstackStart = frontend.includes("tanstack-start");
2835
3598
  const isReactRouter = frontend.includes("react-router");
2836
3599
  const isSolid = frontend.includes("solid");
2837
- if (isNext) await setupNextWorkersDeploy(projectDir, packageManager);
2838
- else if (isNuxt) await setupNuxtWorkersDeploy(projectDir, packageManager);
2839
- else if (isSvelte) await setupSvelteWorkersDeploy(projectDir, packageManager);
2840
- else if (isTanstackStart) await setupTanstackStartWorkersDeploy(projectDir, packageManager);
2841
- else if (isTanstackRouter || isReactRouter || isSolid) await setupWorkersWebDeploy(projectDir, packageManager);
3600
+ if (webDeploy === "wrangler") {
3601
+ if (isNext) await setupNextWorkersDeploy(projectDir, packageManager);
3602
+ else if (isNuxt) await setupNuxtWorkersDeploy(projectDir, packageManager);
3603
+ else if (isSvelte) await setupSvelteWorkersDeploy(projectDir, packageManager);
3604
+ else if (isTanstackStart) await setupTanstackStartWorkersDeploy(projectDir, packageManager);
3605
+ else if (isTanstackRouter || isReactRouter || isSolid) await setupWorkersWebDeploy(projectDir, packageManager);
3606
+ } else if (webDeploy === "alchemy") {
3607
+ if (isNext) await setupNextAlchemyDeploy(projectDir, packageManager);
3608
+ else if (isNuxt) await setupNuxtAlchemyDeploy(projectDir, packageManager);
3609
+ else if (isSvelte) await setupSvelteAlchemyDeploy(projectDir, packageManager);
3610
+ else if (isTanstackStart) await setupTanStackStartAlchemyDeploy(projectDir, packageManager);
3611
+ else if (isTanstackRouter) await setupTanStackRouterAlchemyDeploy(projectDir, packageManager);
3612
+ else if (isReactRouter) await setupReactRouterAlchemyDeploy(projectDir, packageManager);
3613
+ else if (isSolid) await setupSolidAlchemyDeploy(projectDir, packageManager);
3614
+ }
2842
3615
  }
2843
3616
  async function setupWorkersWebDeploy(projectDir, pkgManager) {
2844
3617
  const webAppDir = path.join(projectDir, "apps/web");
@@ -2855,30 +3628,9 @@ async function setupWorkersWebDeploy(projectDir, pkgManager) {
2855
3628
  }
2856
3629
  await setupWorkersVitePlugin(projectDir);
2857
3630
  }
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
3631
 
2880
3632
  //#endregion
2881
- //#region src/helpers/project-generation/add-deployment.ts
3633
+ //#region src/helpers/core/add-deployment.ts
2882
3634
  async function addDeploymentToProject(input) {
2883
3635
  try {
2884
3636
  const projectDir = input.projectDir || process.cwd();
@@ -2886,7 +3638,8 @@ async function addDeploymentToProject(input) {
2886
3638
  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
3639
  const detectedConfig = await detectProjectConfig(projectDir);
2888
3640
  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.`);
3641
+ if (input.webDeploy && detectedConfig.webDeploy === input.webDeploy) exitWithError(`${input.webDeploy} web deployment is already configured for this project.`);
3642
+ if (input.serverDeploy && detectedConfig.serverDeploy === input.serverDeploy) exitWithError(`${input.serverDeploy} server deployment is already configured for this project.`);
2890
3643
  const config = {
2891
3644
  projectName: detectedConfig.projectName || path.basename(projectDir),
2892
3645
  projectDir,
@@ -2896,231 +3649,86 @@ async function addDeploymentToProject(input) {
2896
3649
  backend: detectedConfig.backend || "none",
2897
3650
  runtime: detectedConfig.runtime || "none",
2898
3651
  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: ["@orpc/tanstack-query", "@orpc/client"],
2969
- projectDir: webDir
2970
- });
2971
- else if (api === "trpc") await addPackageDependency({
2972
- dependencies: [
2973
- "@trpc/tanstack-react-query",
2974
- "@trpc/client",
2975
- "@trpc/server"
2976
- ],
2977
- projectDir: webDir
2978
- });
2979
- } else if (hasNuxtWeb) {
2980
- if (api === "orpc") await addPackageDependency({
2981
- dependencies: [
2982
- "@tanstack/vue-query",
2983
- "@tanstack/vue-query-devtools",
2984
- "@orpc/tanstack-query",
2985
- "@orpc/client"
2986
- ],
2987
- projectDir: webDir
2988
- });
2989
- } else if (hasSvelteWeb) {
2990
- if (api === "orpc") await addPackageDependency({
2991
- dependencies: [
2992
- "@orpc/tanstack-query",
2993
- "@orpc/client",
2994
- "@tanstack/svelte-query"
2995
- ],
2996
- projectDir: webDir
2997
- });
2998
- } else if (hasSolidWeb) {
2999
- if (api === "orpc") await addPackageDependency({
3000
- dependencies: [
3001
- "@orpc/tanstack-query",
3002
- "@orpc/client",
3003
- "@tanstack/solid-query"
3004
- ],
3005
- projectDir: webDir
3006
- });
3007
- }
3008
- }
3009
- if (nativeDirExists) {
3010
- if (api === "trpc") await addPackageDependency({
3011
- dependencies: [
3012
- "@trpc/tanstack-react-query",
3013
- "@trpc/client",
3014
- "@trpc/server"
3015
- ],
3016
- projectDir: nativeDir
3017
- });
3018
- else if (api === "orpc") await addPackageDependency({
3019
- dependencies: ["@orpc/tanstack-query", "@orpc/client"],
3020
- projectDir: nativeDir
3021
- });
3022
- }
3023
- }
3024
- const reactBasedFrontends = [
3025
- "react-router",
3026
- "tanstack-router",
3027
- "tanstack-start",
3028
- "next",
3029
- "native-nativewind",
3030
- "native-unistyles"
3031
- ];
3032
- const needsSolidQuery = frontend.includes("solid");
3033
- const needsReactQuery = frontend.some((f) => reactBasedFrontends.includes(f));
3034
- if (needsReactQuery && !isConvex) {
3035
- const reactQueryDeps = ["@tanstack/react-query"];
3036
- const reactQueryDevDeps = ["@tanstack/react-query-devtools"];
3037
- const hasReactWeb$1 = frontend.some((f) => f !== "native-nativewind" && f !== "native-unistyles" && reactBasedFrontends.includes(f));
3038
- const hasNative = frontend.includes("native-nativewind") || frontend.includes("native-unistyles");
3039
- if (hasReactWeb$1 && webDirExists) {
3040
- const webPkgJsonPath = path.join(webDir, "package.json");
3041
- if (await fs.pathExists(webPkgJsonPath)) try {
3042
- await addPackageDependency({
3043
- dependencies: reactQueryDeps,
3044
- devDependencies: reactQueryDevDeps,
3045
- projectDir: webDir
3046
- });
3047
- } catch (_error) {}
3048
- }
3049
- if (hasNative && nativeDirExists) {
3050
- const nativePkgJsonPath = path.join(nativeDir, "package.json");
3051
- if (await fs.pathExists(nativePkgJsonPath)) try {
3052
- await addPackageDependency({
3053
- dependencies: reactQueryDeps,
3054
- projectDir: nativeDir
3055
- });
3056
- } catch (_error) {}
3057
- }
3058
- }
3059
- if (needsSolidQuery && !isConvex) {
3060
- const solidQueryDeps = ["@tanstack/solid-query"];
3061
- const solidQueryDevDeps = ["@tanstack/solid-query-devtools"];
3062
- if (webDirExists) {
3063
- const webPkgJsonPath = path.join(webDir, "package.json");
3064
- if (await fs.pathExists(webPkgJsonPath)) try {
3065
- await addPackageDependency({
3066
- dependencies: solidQueryDeps,
3067
- devDependencies: solidQueryDevDeps,
3068
- projectDir: webDir
3069
- });
3070
- } catch (_error) {}
3071
- }
3072
- }
3073
- if (isConvex) {
3074
- if (webDirExists) {
3075
- const webPkgJsonPath = path.join(webDir, "package.json");
3076
- if (await fs.pathExists(webPkgJsonPath)) try {
3077
- const webDepsToAdd = ["convex"];
3078
- if (frontend.includes("tanstack-start")) webDepsToAdd.push("@convex-dev/react-query");
3079
- if (hasSvelteWeb) webDepsToAdd.push("convex-svelte");
3080
- if (hasNuxtWeb) {
3081
- webDepsToAdd.push("convex-nuxt");
3082
- webDepsToAdd.push("convex-vue");
3083
- }
3084
- await addPackageDependency({
3085
- dependencies: webDepsToAdd,
3086
- projectDir: webDir
3087
- });
3088
- } catch (_error) {}
3089
- }
3090
- if (nativeDirExists) {
3091
- const nativePkgJsonPath = path.join(nativeDir, "package.json");
3092
- if (await fs.pathExists(nativePkgJsonPath)) try {
3093
- await addPackageDependency({
3094
- dependencies: ["convex"],
3095
- projectDir: nativeDir
3096
- });
3097
- } catch (_error) {}
3098
- }
3099
- const backendPackageName = `@${projectName}/backend`;
3100
- const backendWorkspaceVersion = packageManager === "npm" ? "*" : "workspace:*";
3101
- const addWorkspaceDepManually = async (pkgJsonPath, depName, depVersion) => {
3102
- try {
3103
- const pkgJson = await fs.readJson(pkgJsonPath);
3104
- if (!pkgJson.dependencies) pkgJson.dependencies = {};
3105
- if (pkgJson.dependencies[depName] !== depVersion) {
3106
- pkgJson.dependencies[depName] = depVersion;
3107
- await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
3108
- }
3109
- } catch (_error) {}
3652
+ addons: detectedConfig.addons || [],
3653
+ examples: detectedConfig.examples || [],
3654
+ auth: detectedConfig.auth || false,
3655
+ git: false,
3656
+ packageManager: input.packageManager || detectedConfig.packageManager || "npm",
3657
+ install: input.install || false,
3658
+ dbSetup: detectedConfig.dbSetup || "none",
3659
+ api: detectedConfig.api || "none",
3660
+ webDeploy: input.webDeploy || detectedConfig.webDeploy || "none",
3661
+ serverDeploy: input.serverDeploy || detectedConfig.serverDeploy || "none"
3110
3662
  };
3111
- if (webDirExists) {
3112
- const webPkgJsonPath = path.join(webDir, "package.json");
3113
- if (await fs.pathExists(webPkgJsonPath)) await addWorkspaceDepManually(webPkgJsonPath, backendPackageName, backendWorkspaceVersion);
3114
- }
3115
- if (nativeDirExists) {
3116
- const nativePkgJsonPath = path.join(nativeDir, "package.json");
3117
- if (await fs.pathExists(nativePkgJsonPath)) await addWorkspaceDepManually(nativePkgJsonPath, backendPackageName, backendWorkspaceVersion);
3118
- }
3663
+ if (input.webDeploy && input.webDeploy !== "none") log.info(pc.green(`Adding ${input.webDeploy} web deployment to ${config.frontend.join("/")}`));
3664
+ if (input.serverDeploy && input.serverDeploy !== "none") log.info(pc.green(`Adding ${input.serverDeploy} server deployment`));
3665
+ await setupDeploymentTemplates(projectDir, config);
3666
+ await setupWebDeploy(config);
3667
+ await setupServerDeploy(config);
3668
+ await updateBtsConfig(projectDir, {
3669
+ webDeploy: input.webDeploy || config.webDeploy,
3670
+ serverDeploy: input.serverDeploy || config.serverDeploy
3671
+ });
3672
+ if (config.install) await installDependencies({
3673
+ projectDir,
3674
+ packageManager: config.packageManager
3675
+ });
3676
+ else if (!input.suppressInstallMessage) log.info(pc.yellow(`Run ${pc.bold(`${config.packageManager} install`)} to install dependencies`));
3677
+ } catch (error) {
3678
+ const message = error instanceof Error ? error.message : String(error);
3679
+ exitWithError(`Error adding deployment: ${message}`);
3119
3680
  }
3120
3681
  }
3121
3682
 
3122
3683
  //#endregion
3123
- //#region src/helpers/setup/auth-setup.ts
3684
+ //#region src/utils/format-with-biome.ts
3685
+ async function formatProjectWithBiome(projectDir) {
3686
+ const biome = new Biome();
3687
+ const { projectKey } = biome.openProject(projectDir);
3688
+ biome.applyConfiguration(projectKey, {
3689
+ formatter: {
3690
+ enabled: true,
3691
+ indentStyle: "tab"
3692
+ },
3693
+ javascript: { formatter: { quoteStyle: "double" } }
3694
+ });
3695
+ const files = await glob("**/*", {
3696
+ cwd: projectDir,
3697
+ dot: true,
3698
+ absolute: true,
3699
+ onlyFiles: true
3700
+ });
3701
+ for (const filePath of files) try {
3702
+ const ext = path.extname(filePath).toLowerCase();
3703
+ const supported = new Set([
3704
+ ".ts",
3705
+ ".tsx",
3706
+ ".js",
3707
+ ".jsx",
3708
+ ".cjs",
3709
+ ".mjs",
3710
+ ".cts",
3711
+ ".mts",
3712
+ ".json",
3713
+ ".jsonc",
3714
+ ".md",
3715
+ ".mdx",
3716
+ ".css",
3717
+ ".scss",
3718
+ ".html"
3719
+ ]);
3720
+ if (!supported.has(ext)) continue;
3721
+ const original = await fs.readFile(filePath, "utf8");
3722
+ const result = biome.formatContent(projectKey, original, { filePath });
3723
+ const content = result?.content;
3724
+ if (typeof content !== "string") continue;
3725
+ if (content.length === 0 && original.length > 0) continue;
3726
+ if (content !== original) await fs.writeFile(filePath, content);
3727
+ } catch {}
3728
+ }
3729
+
3730
+ //#endregion
3731
+ //#region src/helpers/addons/auth-setup.ts
3124
3732
  async function setupAuth(config) {
3125
3733
  const { auth, frontend, backend, projectDir } = config;
3126
3734
  if (backend === "convex" || !auth) return;
@@ -3172,7 +3780,217 @@ function generateAuthSecret(length = 32) {
3172
3780
  }
3173
3781
 
3174
3782
  //#endregion
3175
- //#region src/helpers/setup/backend-setup.ts
3783
+ //#region src/helpers/addons/examples-setup.ts
3784
+ async function setupExamples(config) {
3785
+ const { examples, frontend, backend, projectDir } = config;
3786
+ if (backend === "convex" || !examples || examples.length === 0 || examples[0] === "none") return;
3787
+ if (examples.includes("ai")) {
3788
+ const webClientDir = path.join(projectDir, "apps/web");
3789
+ const nativeClientDir = path.join(projectDir, "apps/native");
3790
+ const serverDir = path.join(projectDir, "apps/server");
3791
+ const webClientDirExists = await fs.pathExists(webClientDir);
3792
+ const nativeClientDirExists = await fs.pathExists(nativeClientDir);
3793
+ const serverDirExists = await fs.pathExists(serverDir);
3794
+ const hasNuxt = frontend.includes("nuxt");
3795
+ const hasSvelte = frontend.includes("svelte");
3796
+ const hasReactWeb = frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("next") || frontend.includes("tanstack-start");
3797
+ const hasReactNative = frontend.includes("native-nativewind") || frontend.includes("native-unistyles");
3798
+ if (webClientDirExists) {
3799
+ const dependencies = ["ai"];
3800
+ if (hasNuxt) dependencies.push("@ai-sdk/vue");
3801
+ else if (hasSvelte) dependencies.push("@ai-sdk/svelte");
3802
+ else if (hasReactWeb) dependencies.push("@ai-sdk/react");
3803
+ await addPackageDependency({
3804
+ dependencies,
3805
+ projectDir: webClientDir
3806
+ });
3807
+ }
3808
+ if (nativeClientDirExists && hasReactNative) await addPackageDependency({
3809
+ dependencies: ["ai", "@ai-sdk/react"],
3810
+ projectDir: nativeClientDir
3811
+ });
3812
+ if (serverDirExists && backend !== "none") await addPackageDependency({
3813
+ dependencies: ["ai", "@ai-sdk/google"],
3814
+ projectDir: serverDir
3815
+ });
3816
+ }
3817
+ }
3818
+
3819
+ //#endregion
3820
+ //#region src/helpers/core/api-setup.ts
3821
+ async function addBackendWorkspaceDependency(projectDir, backendPackageName, workspaceVersion) {
3822
+ const pkgJsonPath = path.join(projectDir, "package.json");
3823
+ try {
3824
+ const pkgJson = await fs.readJson(pkgJsonPath);
3825
+ if (!pkgJson.dependencies) pkgJson.dependencies = {};
3826
+ pkgJson.dependencies[backendPackageName] = workspaceVersion;
3827
+ await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
3828
+ } catch (_error) {}
3829
+ }
3830
+ function getFrontendType(frontend) {
3831
+ const reactBasedFrontends = [
3832
+ "tanstack-router",
3833
+ "react-router",
3834
+ "tanstack-start",
3835
+ "next"
3836
+ ];
3837
+ const nativeFrontends = ["native-nativewind", "native-unistyles"];
3838
+ return {
3839
+ hasReactWeb: frontend.some((f) => reactBasedFrontends.includes(f)),
3840
+ hasNuxtWeb: frontend.includes("nuxt"),
3841
+ hasSvelteWeb: frontend.includes("svelte"),
3842
+ hasSolidWeb: frontend.includes("solid"),
3843
+ hasNative: frontend.some((f) => nativeFrontends.includes(f))
3844
+ };
3845
+ }
3846
+ function getApiDependencies(api, frontendType) {
3847
+ const deps = {};
3848
+ if (api === "orpc") deps.server = { dependencies: ["@orpc/server", "@orpc/client"] };
3849
+ else if (api === "trpc") deps.server = { dependencies: ["@trpc/server", "@trpc/client"] };
3850
+ if (frontendType.hasReactWeb) {
3851
+ if (api === "orpc") deps.web = { dependencies: ["@orpc/tanstack-query", "@orpc/client"] };
3852
+ else if (api === "trpc") deps.web = { dependencies: [
3853
+ "@trpc/tanstack-react-query",
3854
+ "@trpc/client",
3855
+ "@trpc/server"
3856
+ ] };
3857
+ } else if (frontendType.hasNuxtWeb && api === "orpc") deps.web = {
3858
+ dependencies: [
3859
+ "@tanstack/vue-query",
3860
+ "@orpc/tanstack-query",
3861
+ "@orpc/client"
3862
+ ],
3863
+ devDependencies: ["@tanstack/vue-query-devtools"]
3864
+ };
3865
+ else if (frontendType.hasSvelteWeb && api === "orpc") deps.web = {
3866
+ dependencies: [
3867
+ "@orpc/tanstack-query",
3868
+ "@orpc/client",
3869
+ "@tanstack/svelte-query"
3870
+ ],
3871
+ devDependencies: ["@tanstack/svelte-query-devtools"]
3872
+ };
3873
+ else if (frontendType.hasSolidWeb && api === "orpc") deps.web = {
3874
+ dependencies: [
3875
+ "@orpc/tanstack-query",
3876
+ "@orpc/client",
3877
+ "@tanstack/solid-query"
3878
+ ],
3879
+ devDependencies: ["@tanstack/solid-query-devtools", "@tanstack/solid-router-devtools"]
3880
+ };
3881
+ if (api === "trpc") deps.native = { dependencies: [
3882
+ "@trpc/tanstack-react-query",
3883
+ "@trpc/client",
3884
+ "@trpc/server"
3885
+ ] };
3886
+ else if (api === "orpc") deps.native = { dependencies: ["@orpc/tanstack-query", "@orpc/client"] };
3887
+ return deps;
3888
+ }
3889
+ function getQueryDependencies(frontend) {
3890
+ const reactBasedFrontends = [
3891
+ "react-router",
3892
+ "tanstack-router",
3893
+ "tanstack-start",
3894
+ "next",
3895
+ "native-nativewind",
3896
+ "native-unistyles"
3897
+ ];
3898
+ const deps = {};
3899
+ const needsReactQuery = frontend.some((f) => reactBasedFrontends.includes(f));
3900
+ if (needsReactQuery) {
3901
+ const hasReactWeb = frontend.some((f) => f !== "native-nativewind" && f !== "native-unistyles" && reactBasedFrontends.includes(f));
3902
+ const hasNative = frontend.includes("native-nativewind") || frontend.includes("native-unistyles");
3903
+ if (hasReactWeb) deps.web = {
3904
+ dependencies: ["@tanstack/react-query"],
3905
+ devDependencies: ["@tanstack/react-query-devtools"]
3906
+ };
3907
+ if (hasNative) deps.native = { dependencies: ["@tanstack/react-query"] };
3908
+ }
3909
+ if (frontend.includes("solid")) deps.web = {
3910
+ dependencies: ["@tanstack/solid-query"],
3911
+ devDependencies: ["@tanstack/solid-query-devtools", "@tanstack/solid-router-devtools"]
3912
+ };
3913
+ return deps;
3914
+ }
3915
+ function getConvexDependencies(frontend) {
3916
+ const deps = {
3917
+ web: { dependencies: ["convex"] },
3918
+ native: { dependencies: ["convex"] }
3919
+ };
3920
+ if (frontend.includes("tanstack-start")) deps.web.dependencies.push("@convex-dev/react-query");
3921
+ if (frontend.includes("svelte")) deps.web.dependencies.push("convex-svelte");
3922
+ if (frontend.includes("nuxt")) deps.web.dependencies.push("convex-nuxt", "convex-vue");
3923
+ return deps;
3924
+ }
3925
+ async function setupApi(config) {
3926
+ const { api, projectName, frontend, backend, packageManager, projectDir } = config;
3927
+ const isConvex = backend === "convex";
3928
+ const webDir = path.join(projectDir, "apps/web");
3929
+ const nativeDir = path.join(projectDir, "apps/native");
3930
+ const serverDir = path.join(projectDir, "apps/server");
3931
+ const webDirExists = await fs.pathExists(webDir);
3932
+ const nativeDirExists = await fs.pathExists(nativeDir);
3933
+ const serverDirExists = await fs.pathExists(serverDir);
3934
+ const frontendType = getFrontendType(frontend);
3935
+ if (!isConvex && api !== "none") {
3936
+ const apiDeps = getApiDependencies(api, frontendType);
3937
+ if (serverDirExists && apiDeps.server) {
3938
+ await addPackageDependency({
3939
+ dependencies: apiDeps.server.dependencies,
3940
+ projectDir: serverDir
3941
+ });
3942
+ if (api === "trpc") {
3943
+ if (backend === "hono") await addPackageDependency({
3944
+ dependencies: ["@hono/trpc-server"],
3945
+ projectDir: serverDir
3946
+ });
3947
+ else if (backend === "elysia") await addPackageDependency({
3948
+ dependencies: ["@elysiajs/trpc"],
3949
+ projectDir: serverDir
3950
+ });
3951
+ }
3952
+ }
3953
+ if (webDirExists && apiDeps.web) await addPackageDependency({
3954
+ dependencies: apiDeps.web.dependencies,
3955
+ devDependencies: apiDeps.web.devDependencies,
3956
+ projectDir: webDir
3957
+ });
3958
+ if (nativeDirExists && apiDeps.native) await addPackageDependency({
3959
+ dependencies: apiDeps.native.dependencies,
3960
+ projectDir: nativeDir
3961
+ });
3962
+ }
3963
+ if (!isConvex) {
3964
+ const queryDeps = getQueryDependencies(frontend);
3965
+ if (webDirExists && queryDeps.web) await addPackageDependency({
3966
+ dependencies: queryDeps.web.dependencies,
3967
+ devDependencies: queryDeps.web.devDependencies,
3968
+ projectDir: webDir
3969
+ });
3970
+ if (nativeDirExists && queryDeps.native) await addPackageDependency({
3971
+ dependencies: queryDeps.native.dependencies,
3972
+ projectDir: nativeDir
3973
+ });
3974
+ }
3975
+ if (isConvex) {
3976
+ const convexDeps = getConvexDependencies(frontend);
3977
+ if (webDirExists) await addPackageDependency({
3978
+ dependencies: convexDeps.web.dependencies,
3979
+ projectDir: webDir
3980
+ });
3981
+ if (nativeDirExists) await addPackageDependency({
3982
+ dependencies: convexDeps.native.dependencies,
3983
+ projectDir: nativeDir
3984
+ });
3985
+ const backendPackageName = `@${projectName}/backend`;
3986
+ const backendWorkspaceVersion = packageManager === "npm" ? "*" : "workspace:*";
3987
+ if (webDirExists) await addBackendWorkspaceDependency(webDir, backendPackageName, backendWorkspaceVersion);
3988
+ if (nativeDirExists) await addBackendWorkspaceDependency(nativeDir, backendPackageName, backendWorkspaceVersion);
3989
+ }
3990
+ }
3991
+
3992
+ //#endregion
3993
+ //#region src/helpers/core/backend-setup.ts
3176
3994
  async function setupBackendDependencies(config) {
3177
3995
  const { backend, runtime, api, projectDir } = config;
3178
3996
  if (backend === "convex") return;
@@ -3211,7 +4029,7 @@ async function setupBackendDependencies(config) {
3211
4029
  }
3212
4030
 
3213
4031
  //#endregion
3214
- //#region src/helpers/project-generation/env-setup.ts
4032
+ //#region src/helpers/core/env-setup.ts
3215
4033
  async function addEnvVariablesToFile(filePath, variables) {
3216
4034
  await fs.ensureDir(path.dirname(filePath));
3217
4035
  let envContent = "";
@@ -3259,7 +4077,7 @@ async function addEnvVariablesToFile(filePath, variables) {
3259
4077
  if (exampleModified || !await fs.pathExists(exampleFilePath)) await fs.writeFile(exampleFilePath, exampleEnvContent.trimEnd());
3260
4078
  }
3261
4079
  async function setupEnvironmentVariables(config) {
3262
- const { backend, frontend, database, auth, examples, dbSetup, projectDir } = config;
4080
+ const { backend, frontend, database, auth, examples, dbSetup, projectDir, webDeploy, serverDeploy } = config;
3263
4081
  const hasReactRouter = frontend.includes("react-router");
3264
4082
  const hasTanStackRouter = frontend.includes("tanstack-router");
3265
4083
  const hasTanStackStart = frontend.includes("tanstack-start");
@@ -3359,39 +4177,69 @@ async function setupEnvironmentVariables(config) {
3359
4177
  }
3360
4178
  ];
3361
4179
  await addEnvVariablesToFile(envPath, serverVars);
3362
- if (config.runtime === "workers") {
3363
- const devVarsPath = path.join(serverDir, ".dev.vars");
3364
- try {
3365
- await fs.copy(envPath, devVarsPath);
3366
- } catch (_err) {}
4180
+ const isUnifiedAlchemy = webDeploy === "alchemy" && serverDeploy === "alchemy";
4181
+ const isIndividualAlchemy = webDeploy === "alchemy" || serverDeploy === "alchemy";
4182
+ if (isUnifiedAlchemy) {
4183
+ const rootEnvPath = path.join(projectDir, ".env");
4184
+ const rootAlchemyVars = [{
4185
+ key: "ALCHEMY_PASSWORD",
4186
+ value: "please-change-this",
4187
+ condition: true
4188
+ }];
4189
+ await addEnvVariablesToFile(rootEnvPath, rootAlchemyVars);
4190
+ } else if (isIndividualAlchemy) {
4191
+ if (webDeploy === "alchemy") {
4192
+ const webDir = path.join(projectDir, "apps/web");
4193
+ if (await fs.pathExists(webDir)) {
4194
+ const webAlchemyVars = [{
4195
+ key: "ALCHEMY_PASSWORD",
4196
+ value: "please-change-this",
4197
+ condition: true
4198
+ }];
4199
+ await addEnvVariablesToFile(path.join(webDir, ".env"), webAlchemyVars);
4200
+ }
4201
+ }
4202
+ if (serverDeploy === "alchemy") {
4203
+ const serverDir$1 = path.join(projectDir, "apps/server");
4204
+ if (await fs.pathExists(serverDir$1)) {
4205
+ const serverAlchemyVars = [{
4206
+ key: "ALCHEMY_PASSWORD",
4207
+ value: "please-change-this",
4208
+ condition: true
4209
+ }];
4210
+ await addEnvVariablesToFile(path.join(serverDir$1, ".env"), serverAlchemyVars);
4211
+ }
4212
+ }
3367
4213
  }
3368
4214
  }
3369
4215
 
3370
4216
  //#endregion
3371
4217
  //#region src/helpers/database-providers/d1-setup.ts
3372
4218
  async function setupCloudflareD1(config) {
3373
- const { projectDir } = config;
3374
- const envPath = path.join(projectDir, "apps/server", ".env");
3375
- const variables = [
3376
- {
3377
- key: "CLOUDFLARE_ACCOUNT_ID",
3378
- value: "",
3379
- condition: true
3380
- },
3381
- {
3382
- key: "CLOUDFLARE_DATABASE_ID",
3383
- value: "",
3384
- condition: true
3385
- },
3386
- {
3387
- key: "CLOUDFLARE_D1_TOKEN",
3388
- value: "",
3389
- condition: true
3390
- }
3391
- ];
3392
- try {
3393
- await addEnvVariablesToFile(envPath, variables);
3394
- } catch (_err) {}
4219
+ const { projectDir, serverDeploy } = config;
4220
+ if (serverDeploy === "wrangler") {
4221
+ const envPath = path.join(projectDir, "apps/server", ".env");
4222
+ const variables = [
4223
+ {
4224
+ key: "CLOUDFLARE_ACCOUNT_ID",
4225
+ value: "",
4226
+ condition: true
4227
+ },
4228
+ {
4229
+ key: "CLOUDFLARE_DATABASE_ID",
4230
+ value: "",
4231
+ condition: true
4232
+ },
4233
+ {
4234
+ key: "CLOUDFLARE_D1_TOKEN",
4235
+ value: "",
4236
+ condition: true
4237
+ }
4238
+ ];
4239
+ try {
4240
+ await addEnvVariablesToFile(envPath, variables);
4241
+ } catch (_err) {}
4242
+ }
3395
4243
  }
3396
4244
 
3397
4245
  //#endregion
@@ -4185,7 +5033,7 @@ async function setupTurso(config) {
4185
5033
  }
4186
5034
 
4187
5035
  //#endregion
4188
- //#region src/helpers/setup/db-setup.ts
5036
+ //#region src/helpers/core/db-setup.ts
4189
5037
  async function setupDatabase(config) {
4190
5038
  const { database, orm, dbSetup, backend, projectDir } = config;
4191
5039
  if (backend === "convex" || database === "none") {
@@ -4250,44 +5098,7 @@ async function setupDatabase(config) {
4250
5098
  }
4251
5099
 
4252
5100
  //#endregion
4253
- //#region src/helpers/setup/examples-setup.ts
4254
- async function setupExamples(config) {
4255
- const { examples, frontend, backend, projectDir } = config;
4256
- if (backend === "convex" || !examples || examples.length === 0 || examples[0] === "none") return;
4257
- if (examples.includes("ai")) {
4258
- const webClientDir = path.join(projectDir, "apps/web");
4259
- const nativeClientDir = path.join(projectDir, "apps/native");
4260
- const serverDir = path.join(projectDir, "apps/server");
4261
- const webClientDirExists = await fs.pathExists(webClientDir);
4262
- const nativeClientDirExists = await fs.pathExists(nativeClientDir);
4263
- const serverDirExists = await fs.pathExists(serverDir);
4264
- const hasNuxt = frontend.includes("nuxt");
4265
- const hasSvelte = frontend.includes("svelte");
4266
- const hasReactWeb = frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("next") || frontend.includes("tanstack-start");
4267
- const hasReactNative = frontend.includes("native-nativewind") || frontend.includes("native-unistyles");
4268
- if (webClientDirExists) {
4269
- const dependencies = ["ai"];
4270
- if (hasNuxt) dependencies.push("@ai-sdk/vue");
4271
- else if (hasSvelte) dependencies.push("@ai-sdk/svelte");
4272
- else if (hasReactWeb) dependencies.push("@ai-sdk/react");
4273
- await addPackageDependency({
4274
- dependencies,
4275
- projectDir: webClientDir
4276
- });
4277
- }
4278
- if (nativeClientDirExists && hasReactNative) await addPackageDependency({
4279
- dependencies: ["ai", "@ai-sdk/react"],
4280
- projectDir: nativeClientDir
4281
- });
4282
- if (serverDirExists && backend !== "none") await addPackageDependency({
4283
- dependencies: ["ai", "@ai-sdk/google"],
4284
- projectDir: serverDir
4285
- });
4286
- }
4287
- }
4288
-
4289
- //#endregion
4290
- //#region src/helpers/setup/runtime-setup.ts
5101
+ //#region src/helpers/core/runtime-setup.ts
4291
5102
  async function setupRuntime(config) {
4292
5103
  const { runtime, backend, projectDir } = config;
4293
5104
  if (backend === "convex" || backend === "next" || runtime === "none") return;
@@ -4295,23 +5106,6 @@ async function setupRuntime(config) {
4295
5106
  if (!await fs.pathExists(serverDir)) return;
4296
5107
  if (runtime === "bun") await setupBunRuntime(serverDir, backend);
4297
5108
  else if (runtime === "node") await setupNodeRuntime(serverDir, backend);
4298
- else if (runtime === "workers") await setupWorkersRuntime(serverDir);
4299
- }
4300
- async function generateCloudflareWorkerTypes(config) {
4301
- if (config.runtime !== "workers") return;
4302
- const serverDir = path.join(config.projectDir, "apps/server");
4303
- if (!await fs.pathExists(serverDir)) return;
4304
- const s = spinner();
4305
- try {
4306
- s.start("Generating Cloudflare Workers types...");
4307
- const runCmd = config.packageManager === "npm" ? "npm" : config.packageManager;
4308
- await execa(runCmd, ["run", "cf-typegen"], { cwd: serverDir });
4309
- s.stop("Cloudflare Workers types generated successfully!");
4310
- } catch {
4311
- s.stop(pc.yellow("Failed to generate Cloudflare Workers types"));
4312
- const managerCmd = config.packageManager === "npm" ? "npm run" : `${config.packageManager} run`;
4313
- console.warn(`Note: You can manually run 'cd apps/server && ${managerCmd} cf-typegen' in the project directory later`);
4314
- }
4315
5109
  }
4316
5110
  async function setupBunRuntime(serverDir, _backend) {
4317
5111
  const packageJsonPath = path.join(serverDir, "package.json");
@@ -4351,27 +5145,20 @@ async function setupNodeRuntime(serverDir, backend) {
4351
5145
  projectDir: serverDir
4352
5146
  });
4353
5147
  }
4354
- async function setupWorkersRuntime(serverDir) {
4355
- const packageJsonPath = path.join(serverDir, "package.json");
4356
- if (!await fs.pathExists(packageJsonPath)) return;
4357
- const packageJson = await fs.readJson(packageJsonPath);
4358
- packageJson.scripts = {
4359
- ...packageJson.scripts,
4360
- dev: "wrangler dev --port=3000",
4361
- start: "wrangler dev",
4362
- deploy: "wrangler deploy",
4363
- build: "wrangler deploy --dry-run",
4364
- "cf-typegen": "wrangler types --env-interface CloudflareBindings"
4365
- };
4366
- await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
4367
- await addPackageDependency({
4368
- devDependencies: ["wrangler", "@types/node"],
4369
- projectDir: serverDir
5148
+
5149
+ //#endregion
5150
+ //#region src/helpers/core/convex-codegen.ts
5151
+ async function runConvexCodegen(projectDir, packageManager) {
5152
+ const backendDir = path.join(projectDir, "packages/backend");
5153
+ const cmd = getPackageExecutionCommand(packageManager, "convex codegen");
5154
+ await execa(cmd, {
5155
+ cwd: backendDir,
5156
+ shell: true
4370
5157
  });
4371
5158
  }
4372
5159
 
4373
5160
  //#endregion
4374
- //#region src/helpers/project-generation/create-readme.ts
5161
+ //#region src/helpers/core/create-readme.ts
4375
5162
  async function createReadme(projectDir, options) {
4376
5163
  const readmePath = path.join(projectDir, "README.md");
4377
5164
  const content = generateReadmeContent(options);
@@ -4648,7 +5435,7 @@ function generateScriptsList(packageManagerRunCmd, database, orm, _auth, hasNati
4648
5435
  }
4649
5436
 
4650
5437
  //#endregion
4651
- //#region src/helpers/project-generation/git.ts
5438
+ //#region src/helpers/core/git.ts
4652
5439
  async function initializeGit(projectDir, useGit) {
4653
5440
  if (!useGit) return;
4654
5441
  const gitVersionResult = await $({
@@ -4724,9 +5511,9 @@ async function getDockerStatus(database) {
4724
5511
  }
4725
5512
 
4726
5513
  //#endregion
4727
- //#region src/helpers/project-generation/post-installation.ts
5514
+ //#region src/helpers/core/post-installation.ts
4728
5515
  async function displayPostInstallInstructions(config) {
4729
- const { database, relativePath, packageManager, depsInstalled, orm, addons, runtime, frontend, backend, dbSetup, webDeploy } = config;
5516
+ const { database, relativePath, packageManager, depsInstalled, orm, addons, runtime, frontend, backend, dbSetup, webDeploy, serverDeploy } = config;
4730
5517
  const isConvex = backend === "convex";
4731
5518
  const runCmd = packageManager === "npm" ? "npm run" : packageManager;
4732
5519
  const cdCmd = `cd ${relativePath}`;
@@ -4737,7 +5524,7 @@ async function displayPostInstallInstructions(config) {
4737
5524
  const nativeInstructions = frontend?.includes("native-nativewind") || frontend?.includes("native-unistyles") ? getNativeInstructions(isConvex) : "";
4738
5525
  const pwaInstructions = addons?.includes("pwa") && frontend?.includes("react-router") ? getPwaInstructions() : "";
4739
5526
  const starlightInstructions = addons?.includes("starlight") ? getStarlightInstructions(runCmd) : "";
4740
- const workersDeployInstructions = webDeploy === "workers" ? getWorkersDeployInstructions(runCmd) : "";
5527
+ const workersDeployInstructions = webDeploy === "wrangler" ? getWorkersDeployInstructions(runCmd) : "";
4741
5528
  const hasWeb = frontend?.some((f) => [
4742
5529
  "tanstack-router",
4743
5530
  "react-router",
@@ -4766,7 +5553,7 @@ async function displayPostInstallInstructions(config) {
4766
5553
  if (runtime === "workers") {
4767
5554
  if (dbSetup === "d1") output += `${pc.yellow("IMPORTANT:")} Complete D1 database setup first\n (see Database commands below)\n`;
4768
5555
  output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev\n`;
4769
- output += `${pc.cyan(`${stepCounter++}.`)} cd apps/server && ${runCmd} run cf-typegen\n\n`;
5556
+ if (serverDeploy === "wrangler") output += `${pc.cyan(`${stepCounter++}.`)} cd apps/server && ${runCmd} cf-typegen\n\n`;
4770
5557
  } else output += "\n";
4771
5558
  }
4772
5559
  output += `${pc.bold("Your project will be available at:")}\n`;
@@ -4856,7 +5643,7 @@ function getWorkersDeployInstructions(runCmd) {
4856
5643
  }
4857
5644
 
4858
5645
  //#endregion
4859
- //#region src/helpers/project-generation/project-config.ts
5646
+ //#region src/helpers/core/project-config.ts
4860
5647
  async function updatePackageConfigurations(projectDir, options) {
4861
5648
  await updateRootPackageJson(projectDir, options);
4862
5649
  if (options.backend !== "convex") await updateServerPackageJson(projectDir, options);
@@ -5037,7 +5824,7 @@ async function updateConvexPackageJson(projectDir, options) {
5037
5824
  }
5038
5825
 
5039
5826
  //#endregion
5040
- //#region src/helpers/project-generation/create-project.ts
5827
+ //#region src/helpers/core/create-project.ts
5041
5828
  async function createProject(options) {
5042
5829
  const projectDir = options.projectDir;
5043
5830
  const isConvex = options.backend === "convex";
@@ -5069,14 +5856,13 @@ async function createProject(options) {
5069
5856
  await updatePackageConfigurations(projectDir, options);
5070
5857
  await createReadme(projectDir, options);
5071
5858
  await writeBtsConfig(options);
5859
+ await formatProjectWithBiome(projectDir);
5860
+ if (isConvex) await runConvexCodegen(projectDir, options.packageManager);
5072
5861
  log.success("Project template successfully scaffolded!");
5073
- if (options.install) {
5074
- await installDependencies({
5075
- projectDir,
5076
- packageManager: options.packageManager
5077
- });
5078
- await generateCloudflareWorkerTypes(options);
5079
- }
5862
+ if (options.install) await installDependencies({
5863
+ projectDir,
5864
+ packageManager: options.packageManager
5865
+ });
5080
5866
  await initializeGit(projectDir, options.git);
5081
5867
  await displayPostInstallInstructions({
5082
5868
  ...options,
@@ -5095,7 +5881,7 @@ async function createProject(options) {
5095
5881
  }
5096
5882
 
5097
5883
  //#endregion
5098
- //#region src/helpers/project-generation/command-handlers.ts
5884
+ //#region src/helpers/core/command-handlers.ts
5099
5885
  async function createProjectHandler(input) {
5100
5886
  const startTime = Date.now();
5101
5887
  const timeScaffolded = (/* @__PURE__ */ new Date()).toISOString();
@@ -5107,7 +5893,7 @@ async function createProjectHandler(input) {
5107
5893
  else if (input.yes) {
5108
5894
  let defaultName = DEFAULT_CONFIG.relativePath;
5109
5895
  let counter = 1;
5110
- while (fs.pathExistsSync(path.resolve(process.cwd(), defaultName)) && fs.readdirSync(path.resolve(process.cwd(), defaultName)).length > 0) {
5896
+ while (await fs.pathExists(path.resolve(process.cwd(), defaultName)) && (await fs.readdir(path.resolve(process.cwd(), defaultName))).length > 0) {
5111
5897
  defaultName = `${DEFAULT_CONFIG.projectName}-${counter}`;
5112
5898
  counter++;
5113
5899
  }
@@ -5146,7 +5932,8 @@ async function createProjectHandler(input) {
5146
5932
  install: false,
5147
5933
  dbSetup: "none",
5148
5934
  api: "none",
5149
- webDeploy: "none"
5935
+ webDeploy: "none",
5936
+ serverDeploy: "none"
5150
5937
  },
5151
5938
  reproducibleCommand: "",
5152
5939
  timeScaffolded,
@@ -5162,15 +5949,9 @@ async function createProjectHandler(input) {
5162
5949
  projectDirectory: input.projectName
5163
5950
  };
5164
5951
  const providedFlags = getProvidedFlags(cliInput);
5165
- const flagConfig = processAndValidateFlags(cliInput, providedFlags, finalBaseName);
5166
- const { projectName: _projectNameFromFlags,...otherFlags } = flagConfig;
5167
- if (!input.yes && Object.keys(otherFlags).length > 0) {
5168
- log.info(pc.yellow("Using these pre-selected options:"));
5169
- log.message(displayConfig(otherFlags));
5170
- log.message("");
5171
- }
5172
5952
  let config;
5173
5953
  if (input.yes) {
5954
+ const flagConfig = processProvidedFlagsWithoutValidation(cliInput, finalBaseName);
5174
5955
  config = {
5175
5956
  ...DEFAULT_CONFIG,
5176
5957
  ...flagConfig,
@@ -5178,12 +5959,22 @@ async function createProjectHandler(input) {
5178
5959
  projectDir: finalResolvedPath,
5179
5960
  relativePath: finalPathInput
5180
5961
  };
5962
+ validateConfigCompatibility(config);
5181
5963
  if (config.backend === "convex") log.info("Due to '--backend convex' flag, the following options have been automatically set: auth=false, database=none, orm=none, api=none, runtime=none, dbSetup=none, examples=todo");
5182
5964
  else if (config.backend === "none") log.info("Due to '--backend none', the following options have been automatically set: --auth=false, --database=none, --orm=none, --api=none, --runtime=none, --db-setup=none, --examples=none");
5183
5965
  log.info(pc.yellow("Using default/flag options (config prompts skipped):"));
5184
5966
  log.message(displayConfig(config));
5185
5967
  log.message("");
5186
- } else config = await gatherConfig(flagConfig, finalBaseName, finalResolvedPath, finalPathInput);
5968
+ } else {
5969
+ const flagConfig = processAndValidateFlags(cliInput, providedFlags, finalBaseName);
5970
+ const { projectName: _projectNameFromFlags,...otherFlags } = flagConfig;
5971
+ if (Object.keys(otherFlags).length > 0) {
5972
+ log.info(pc.yellow("Using these pre-selected options:"));
5973
+ log.message(displayConfig(otherFlags));
5974
+ log.message("");
5975
+ }
5976
+ config = await gatherConfig(flagConfig, finalBaseName, finalResolvedPath, finalPathInput);
5977
+ }
5187
5978
  await createProject(config);
5188
5979
  const reproducibleCommand = generateReproducibleCommand(config);
5189
5980
  log.success(pc.blue(`You can reproduce this setup with the following command:\n${reproducibleCommand}`));
@@ -5203,11 +5994,11 @@ async function createProjectHandler(input) {
5203
5994
  }
5204
5995
  async function handleDirectoryConflictProgrammatically(currentPathInput, strategy) {
5205
5996
  const currentPath = path.resolve(process.cwd(), currentPathInput);
5206
- if (!fs.pathExistsSync(currentPath)) return {
5997
+ if (!await fs.pathExists(currentPath)) return {
5207
5998
  finalPathInput: currentPathInput,
5208
5999
  shouldClearDirectory: false
5209
6000
  };
5210
- const dirContents = fs.readdirSync(currentPath);
6001
+ const dirContents = await fs.readdir(currentPath);
5211
6002
  const isNotEmpty = dirContents.length > 0;
5212
6003
  if (!isNotEmpty) return {
5213
6004
  finalPathInput: currentPathInput,
@@ -5226,7 +6017,7 @@ async function handleDirectoryConflictProgrammatically(currentPathInput, strateg
5226
6017
  let counter = 1;
5227
6018
  const baseName = currentPathInput;
5228
6019
  let finalPathInput = `${baseName}-${counter}`;
5229
- while (fs.pathExistsSync(path.resolve(process.cwd(), finalPathInput)) && fs.readdirSync(path.resolve(process.cwd(), finalPathInput)).length > 0) {
6020
+ while (await fs.pathExists(path.resolve(process.cwd(), finalPathInput)) && (await fs.readdir(path.resolve(process.cwd(), finalPathInput))).length > 0) {
5230
6021
  counter++;
5231
6022
  finalPathInput = `${baseName}-${counter}`;
5232
6023
  }
@@ -5252,6 +6043,10 @@ async function addAddonsHandler(input) {
5252
6043
  const deploymentPrompt = await getDeploymentToAdd(detectedConfig.frontend || [], detectedConfig.webDeploy);
5253
6044
  if (deploymentPrompt !== "none") input.webDeploy = deploymentPrompt;
5254
6045
  }
6046
+ if (!input.serverDeploy) {
6047
+ const serverDeploymentPrompt = await getServerDeploymentToAdd(detectedConfig.runtime, detectedConfig.serverDeploy);
6048
+ if (serverDeploymentPrompt !== "none") input.serverDeploy = serverDeploymentPrompt;
6049
+ }
5255
6050
  const packageManager = input.packageManager || detectedConfig.packageManager || "npm";
5256
6051
  let somethingAdded = false;
5257
6052
  if (input.addons && input.addons.length > 0) {
@@ -5272,6 +6067,15 @@ async function addAddonsHandler(input) {
5272
6067
  });
5273
6068
  somethingAdded = true;
5274
6069
  }
6070
+ if (input.serverDeploy && input.serverDeploy !== "none") {
6071
+ await addDeploymentToProject({
6072
+ ...input,
6073
+ install: false,
6074
+ suppressInstallMessage: true,
6075
+ serverDeploy: input.serverDeploy
6076
+ });
6077
+ somethingAdded = true;
6078
+ }
5275
6079
  if (!somethingAdded) {
5276
6080
  outro(pc.yellow("No addons or deployment configurations to add."));
5277
6081
  return;
@@ -5375,6 +6179,7 @@ const router = t.router({
5375
6179
  runtime: RuntimeSchema.optional(),
5376
6180
  api: APISchema.optional(),
5377
6181
  webDeploy: WebDeploySchema.optional(),
6182
+ serverDeploy: ServerDeploySchema.optional(),
5378
6183
  directoryConflict: DirectoryConflictSchema.optional(),
5379
6184
  renderTitle: z.boolean().optional(),
5380
6185
  disableAnalytics: z.boolean().optional().default(false).describe("Disable analytics")
@@ -5390,6 +6195,7 @@ const router = t.router({
5390
6195
  add: t.procedure.meta({ description: "Add addons or deployment configurations to an existing Better-T Stack project" }).input(z.tuple([z.object({
5391
6196
  addons: z.array(AddonsSchema).optional().default([]),
5392
6197
  webDeploy: WebDeploySchema.optional(),
6198
+ serverDeploy: ServerDeploySchema.optional(),
5393
6199
  projectDir: z.string().optional(),
5394
6200
  install: z.boolean().optional().default(false).describe("Install dependencies after adding addons or deployment"),
5395
6201
  packageManager: PackageManagerSchema.optional()