create-better-t-stack 2.33.6 → 2.33.8-canary.70c2a51a

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