create-better-t-stack 3.11.1 → 3.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/dist/chunk-Dt3mZKp0.mjs +24 -0
  2. package/dist/cli.mjs +1 -1
  3. package/dist/index.d.mts +40 -60
  4. package/dist/index.mjs +2 -2
  5. package/dist/{src-Dc2OdxbP.mjs → src-DBVnwTkj.mjs} +644 -609
  6. package/package.json +2 -2
  7. package/templates/addons/turborepo/turbo.json.hbs +8 -0
  8. package/templates/api/orpc/native/utils/orpc.ts.hbs +21 -20
  9. package/templates/api/orpc/web/nuxt/app/plugins/orpc.ts.hbs +3 -5
  10. package/templates/api/orpc/web/react/base/src/utils/orpc.ts.hbs +73 -67
  11. package/templates/api/orpc/web/solid/src/utils/orpc.ts.hbs +15 -14
  12. package/templates/api/trpc/native/utils/trpc.ts.hbs +8 -7
  13. package/templates/api/trpc/web/react/base/src/utils/trpc.ts.hbs +59 -57
  14. package/templates/auth/better-auth/convex/native/base/lib/auth-client.ts.hbs +10 -9
  15. package/templates/auth/better-auth/convex/web/react/next/src/lib/auth-server.ts.hbs +10 -9
  16. package/templates/auth/better-auth/convex/web/react/tanstack-router/src/lib/auth-client.ts.hbs +5 -4
  17. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/lib/auth-server.ts.hbs +8 -7
  18. package/templates/auth/better-auth/native/base/lib/auth-client.ts.hbs +9 -8
  19. package/templates/auth/better-auth/server/base/src/index.ts.hbs +239 -235
  20. package/templates/auth/better-auth/web/nuxt/app/plugins/auth-client.ts.hbs +2 -3
  21. package/templates/auth/better-auth/web/react/base/src/lib/auth-client.ts.hbs +9 -11
  22. package/templates/auth/better-auth/web/solid/src/lib/auth-client.ts.hbs +3 -2
  23. package/templates/backend/server/elysia/src/index.ts.hbs +71 -71
  24. package/templates/backend/server/express/src/index.ts.hbs +57 -57
  25. package/templates/backend/server/fastify/src/index.ts.hbs +107 -107
  26. package/templates/backend/server/hono/src/index.ts.hbs +75 -85
  27. package/templates/base/tsconfig.json.hbs +3 -0
  28. package/templates/db/drizzle/mysql/src/index.ts.hbs +23 -30
  29. package/templates/db/drizzle/postgres/src/index.ts.hbs +6 -13
  30. package/templates/db/drizzle/sqlite/src/index.ts.hbs +11 -18
  31. package/templates/db/mongoose/mongodb/src/index.ts.hbs +3 -2
  32. package/templates/db/prisma/mongodb/prisma/schema/schema.prisma.hbs +1 -1
  33. package/templates/db/prisma/mysql/prisma/schema/schema.prisma.hbs +1 -1
  34. package/templates/db/prisma/mysql/prisma.config.ts.hbs +16 -16
  35. package/templates/db/prisma/mysql/src/index.ts.hbs +16 -15
  36. package/templates/db/prisma/postgres/prisma/schema/schema.prisma.hbs +1 -1
  37. package/templates/db/prisma/postgres/src/index.ts.hbs +10 -9
  38. package/templates/db/prisma/sqlite/prisma/schema/schema.prisma.hbs +1 -1
  39. package/templates/db/prisma/sqlite/src/index.ts.hbs +4 -7
  40. package/templates/examples/ai/native/bare/app/(drawer)/ai.tsx.hbs +2 -1
  41. package/templates/examples/ai/native/unistyles/app/(drawer)/ai.tsx.hbs +2 -1
  42. package/templates/examples/ai/native/uniwind/app/(drawer)/ai.tsx.hbs +2 -1
  43. package/templates/examples/ai/web/nuxt/app/pages/ai.vue.hbs +1 -3
  44. package/templates/examples/ai/web/react/next/src/app/ai/page.tsx.hbs +4 -3
  45. package/templates/examples/ai/web/react/react-router/src/routes/ai.tsx.hbs +2 -1
  46. package/templates/examples/ai/web/react/tanstack-router/src/routes/ai.tsx.hbs +4 -1
  47. package/templates/examples/ai/web/react/tanstack-start/src/routes/ai.tsx.hbs +4 -1
  48. package/templates/frontend/native/bare/app/_layout.tsx.hbs +4 -2
  49. package/templates/frontend/native/unistyles/app/_layout.tsx.hbs +4 -2
  50. package/templates/frontend/native/uniwind/app/_layout.tsx.hbs +4 -3
  51. package/templates/frontend/nuxt/nuxt.config.ts.hbs +6 -3
  52. package/templates/frontend/react/next/next.config.ts.hbs +9 -8
  53. package/templates/frontend/react/next/src/components/providers.tsx.hbs +4 -1
  54. package/templates/frontend/react/next/tsconfig.json.hbs +2 -2
  55. package/templates/frontend/react/react-router/src/root.tsx.hbs +3 -4
  56. package/templates/frontend/react/tanstack-router/src/main.tsx.hbs +3 -2
  57. package/templates/frontend/react/tanstack-start/src/router.tsx.hbs +97 -93
  58. package/templates/frontend/react/tanstack-start/src/routes/__root.tsx.hbs +23 -3
  59. package/templates/packages/config/tsconfig.base.json.hbs +1 -1
  60. package/templates/{deploy/alchemy → packages/env}/env.d.ts.hbs +6 -4
  61. package/templates/packages/env/package.json.hbs +7 -0
  62. package/templates/packages/env/src/native.ts.hbs +21 -0
  63. package/templates/packages/env/src/server.ts.hbs +38 -0
  64. package/templates/packages/env/src/web.ts.hbs +89 -0
  65. package/templates/packages/env/tsconfig.json.hbs +3 -0
  66. package/templates/{deploy/alchemy → packages/infra}/alchemy.run.ts.hbs +84 -80
  67. package/templates/packages/infra/package.json.hbs +10 -0
  68. package/templates/payments/polar/server/base/src/lib/payments.ts.hbs +3 -2
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
+ import { t as __reExport } from "./chunk-Dt3mZKp0.mjs";
2
3
  import { autocompleteMultiselect, cancel, confirm, group, groupMultiselect, intro, isCancel, log, multiselect, outro, select, spinner, text } from "@clack/prompts";
3
4
  import { createRouterClient, os } from "@orpc/server";
4
5
  import pc from "picocolors";
5
6
  import { createCli } from "trpc-cli";
6
7
  import z from "zod";
7
- import path from "node:path";
8
8
  import consola, { consola as consola$1 } from "consola";
9
9
  import fs from "fs-extra";
10
+ import path from "node:path";
10
11
  import { fileURLToPath } from "node:url";
11
- import { APISchema, AddonsSchema, AuthSchema, BackendSchema, DatabaseSchema, DatabaseSetupSchema, DirectoryConflictSchema, ExamplesSchema, FrontendSchema, ORMSchema, PackageManagerSchema, PaymentsSchema, ProjectNameSchema, RuntimeSchema, ServerDeploySchema, TemplateSchema, WebDeploySchema } from "@better-t-stack/types";
12
12
  import gradient from "gradient-string";
13
13
  import * as JSONC from "jsonc-parser";
14
14
  import { $, execa } from "execa";
@@ -155,10 +155,13 @@ const dependencyVersionMap = {
155
155
  "nitro-cloudflare-dev": "^0.2.2",
156
156
  "@sveltejs/adapter-cloudflare": "^7.2.4",
157
157
  "@cloudflare/workers-types": "^4.20251213.0",
158
- alchemy: "^0.81.2",
158
+ alchemy: "^0.82.1",
159
159
  dotenv: "^17.2.2",
160
160
  tsdown: "^0.16.5",
161
161
  zod: "^4.1.13",
162
+ "@t3-oss/env-core": "^0.13.1",
163
+ "@t3-oss/env-nextjs": "^0.13.1",
164
+ "@t3-oss/env-nuxt": "^0.13.1",
162
165
  srvx: "0.8.15",
163
166
  "@polar-sh/better-auth": "^1.1.3",
164
167
  "@polar-sh/sdk": "^0.34.16"
@@ -191,6 +194,12 @@ const ADDON_COMPATIBILITY = {
191
194
  none: []
192
195
  };
193
196
 
197
+ //#endregion
198
+ //#region src/types.ts
199
+ var types_exports = {};
200
+ import * as import__better_t_stack_types from "@better-t-stack/types";
201
+ __reExport(types_exports, import__better_t_stack_types);
202
+
194
203
  //#endregion
195
204
  //#region src/utils/compatibility.ts
196
205
  const WEB_FRAMEWORKS = [
@@ -205,24 +214,18 @@ const WEB_FRAMEWORKS = [
205
214
 
206
215
  //#endregion
207
216
  //#region src/utils/errors.ts
208
- function isProgrammatic() {
209
- return process.env.BTS_PROGRAMMATIC === "1";
210
- }
211
217
  function exitWithError(message) {
212
218
  consola.error(pc.red(message));
213
- if (isProgrammatic()) throw new Error(message);
214
- process.exit(1);
219
+ throw new Error(message);
215
220
  }
216
221
  function exitCancelled(message = "Operation cancelled") {
217
222
  cancel(pc.red(message));
218
- if (isProgrammatic()) throw new Error(message);
219
- process.exit(0);
223
+ throw new Error(message);
220
224
  }
221
225
  function handleError(error, fallbackMessage) {
222
226
  const message = error instanceof Error ? error.message : fallbackMessage || String(error);
223
227
  consola.error(pc.red(message));
224
- if (isProgrammatic()) throw new Error(message);
225
- process.exit(1);
228
+ throw new Error(message);
226
229
  }
227
230
 
228
231
  //#endregion
@@ -435,7 +438,7 @@ const ADDON_GROUPS = {
435
438
  };
436
439
  async function getAddonsChoice(addons, frontends, auth) {
437
440
  if (addons !== void 0) return addons;
438
- const allAddons = AddonsSchema.options.filter((addon) => addon !== "none");
441
+ const allAddons = types_exports.AddonsSchema.options.filter((addon) => addon !== "none");
439
442
  const groupedOptions = {
440
443
  Documentation: [],
441
444
  Linting: [],
@@ -481,7 +484,7 @@ async function getAddonsToAdd(frontend, existingAddons = [], auth) {
481
484
  Other: []
482
485
  };
483
486
  const frontendArray = frontend || [];
484
- const compatibleAddons = getCompatibleAddons(AddonsSchema.options.filter((addon) => addon !== "none"), frontendArray, existingAddons, auth);
487
+ const compatibleAddons = getCompatibleAddons(types_exports.AddonsSchema.options.filter((addon) => addon !== "none"), frontendArray, existingAddons, auth);
485
488
  for (const addon of compatibleAddons) {
486
489
  const { label, hint } = getAddonDisplay(addon);
487
490
  const option = {
@@ -1058,8 +1061,8 @@ async function getRuntimeChoice(runtime, backend) {
1058
1061
  //#endregion
1059
1062
  //#region src/prompts/server-deploy.ts
1060
1063
  function getDeploymentDisplay$1(deployment) {
1061
- if (deployment === "alchemy") return {
1062
- label: "Alchemy",
1064
+ if (deployment === "cloudflare") return {
1065
+ label: "Cloudflare",
1063
1066
  hint: "Deploy to Cloudflare Workers using Alchemy"
1064
1067
  };
1065
1068
  return {
@@ -1071,17 +1074,17 @@ async function getServerDeploymentChoice(deployment, runtime, backend, _webDeplo
1071
1074
  if (deployment !== void 0) return deployment;
1072
1075
  if (backend === "none" || backend === "convex") return "none";
1073
1076
  if (backend !== "hono") return "none";
1074
- if (runtime === "workers") return "alchemy";
1077
+ if (runtime === "workers") return "cloudflare";
1075
1078
  return "none";
1076
1079
  }
1077
1080
  async function getServerDeploymentToAdd(runtime, existingDeployment, backend) {
1078
1081
  if (backend !== "hono") return "none";
1079
1082
  const options = [];
1080
1083
  if (runtime === "workers") {
1081
- if (existingDeployment !== "alchemy") {
1082
- const { label, hint } = getDeploymentDisplay$1("alchemy");
1084
+ if (existingDeployment !== "cloudflare") {
1085
+ const { label, hint } = getDeploymentDisplay$1("cloudflare");
1083
1086
  options.push({
1084
- value: "alchemy",
1087
+ value: "cloudflare",
1085
1088
  label,
1086
1089
  hint
1087
1090
  });
@@ -1104,8 +1107,8 @@ function hasWebFrontend(frontends) {
1104
1107
  return frontends.some((f) => WEB_FRAMEWORKS.includes(f));
1105
1108
  }
1106
1109
  function getDeploymentDisplay(deployment) {
1107
- if (deployment === "alchemy") return {
1108
- label: "Alchemy",
1110
+ if (deployment === "cloudflare") return {
1111
+ label: "Cloudflare",
1109
1112
  hint: "Deploy to Cloudflare Workers using Alchemy"
1110
1113
  };
1111
1114
  return {
@@ -1118,7 +1121,7 @@ async function getDeploymentChoice(deployment, _runtime, _backend, frontend = []
1118
1121
  if (!hasWebFrontend(frontend)) return "none";
1119
1122
  const response = await select({
1120
1123
  message: "Select web deployment",
1121
- options: ["alchemy", "none"].map((deploy) => {
1124
+ options: ["cloudflare", "none"].map((deploy) => {
1122
1125
  const { label, hint } = getDeploymentDisplay(deploy);
1123
1126
  return {
1124
1127
  value: deploy,
@@ -1134,10 +1137,10 @@ async function getDeploymentChoice(deployment, _runtime, _backend, frontend = []
1134
1137
  async function getDeploymentToAdd(frontend, existingDeployment) {
1135
1138
  if (!hasWebFrontend(frontend)) return "none";
1136
1139
  const options = [];
1137
- if (existingDeployment !== "alchemy") {
1138
- const { label, hint } = getDeploymentDisplay("alchemy");
1140
+ if (existingDeployment !== "cloudflare") {
1141
+ const { label, hint } = getDeploymentDisplay("cloudflare");
1139
1142
  options.push({
1140
- value: "alchemy",
1143
+ value: "cloudflare",
1141
1144
  label,
1142
1145
  hint
1143
1146
  });
@@ -1211,7 +1214,7 @@ function isPathWithinCwd(targetPath) {
1211
1214
  }
1212
1215
  function validateDirectoryName(name) {
1213
1216
  if (name === ".") return void 0;
1214
- const result = ProjectNameSchema.safeParse(name);
1217
+ const result = types_exports.ProjectNameSchema.safeParse(name);
1215
1218
  if (!result.success) return result.error.issues[0]?.message || "Invalid project name";
1216
1219
  }
1217
1220
  async function getProjectName(initialName) {
@@ -1771,7 +1774,7 @@ function validateFullConfig(config, providedFlags, options) {
1771
1774
  validateSelfBackendCompatibility(providedFlags, options, config);
1772
1775
  validateWorkersCompatibility(providedFlags, options, config);
1773
1776
  if (config.runtime === "workers" && config.serverDeploy === "none") exitWithError("Cloudflare Workers runtime requires a server deployment. Please choose 'alchemy' for --server-deploy.");
1774
- if (providedFlags.has("serverDeploy") && config.serverDeploy === "alchemy" && config.runtime !== "workers") exitWithError(`Server deployment '${config.serverDeploy}' requires '--runtime workers'. Please use '--runtime workers' or choose a different server deployment.`);
1777
+ if (providedFlags.has("serverDeploy") && config.serverDeploy === "cloudflare" && config.runtime !== "workers") exitWithError(`Server deployment '${config.serverDeploy}' requires '--runtime workers'. Please use '--runtime workers' or choose a different server deployment.`);
1775
1778
  if (config.addons && config.addons.length > 0) {
1776
1779
  validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth);
1777
1780
  config.addons = [...new Set(config.addons)];
@@ -1796,11 +1799,11 @@ function validateConfigForProgrammaticUse(config) {
1796
1799
  //#endregion
1797
1800
  //#region src/utils/project-name-validation.ts
1798
1801
  function validateProjectName(name) {
1799
- const result = ProjectNameSchema.safeParse(name);
1802
+ const result = types_exports.ProjectNameSchema.safeParse(name);
1800
1803
  if (!result.success) exitWithError(`Invalid project name: ${result.error.issues[0]?.message || "Invalid project name"}`);
1801
1804
  }
1802
1805
  function validateProjectNameThrow(name) {
1803
- const result = ProjectNameSchema.safeParse(name);
1806
+ const result = types_exports.ProjectNameSchema.safeParse(name);
1804
1807
  if (!result.success) throw new Error(`Invalid project name: ${result.error.issues[0]?.message}`);
1805
1808
  }
1806
1809
  function extractAndValidateProjectName(projectName, projectDirectory, throwOnError = false) {
@@ -1998,6 +2001,45 @@ function getPackageExecutionCommand(packageManager, commandWithArgs) {
1998
2001
  default: return `npx ${commandWithArgs}`;
1999
2002
  }
2000
2003
  }
2004
+ /**
2005
+ * Returns the command and arguments as an array for use with execa's $ template syntax.
2006
+ * This avoids the need for shell: true and provides better escaping.
2007
+ *
2008
+ * @param packageManager - The selected package manager (e.g., 'npm', 'yarn', 'pnpm', 'bun').
2009
+ * @param commandWithArgs - The command to run, including arguments (e.g., "prisma generate").
2010
+ * @returns An array of [command, ...args] (e.g., ["npx", "prisma", "generate"]).
2011
+ */
2012
+ function getPackageExecutionArgs(packageManager, commandWithArgs) {
2013
+ const args = commandWithArgs.split(" ");
2014
+ switch (packageManager) {
2015
+ case "pnpm": return [
2016
+ "pnpm",
2017
+ "dlx",
2018
+ ...args
2019
+ ];
2020
+ case "bun": return ["bunx", ...args];
2021
+ default: return ["npx", ...args];
2022
+ }
2023
+ }
2024
+ /**
2025
+ * Returns just the runner prefix as an array, for when you already have args built.
2026
+ * Use this when you have complex arguments that shouldn't be split by spaces.
2027
+ *
2028
+ * @param packageManager - The selected package manager.
2029
+ * @returns The runner prefix as an array (e.g., ["npx"] or ["pnpm", "dlx"]).
2030
+ *
2031
+ * @example
2032
+ * const prefix = getPackageRunnerPrefix("bun");
2033
+ * const args = ["@tauri-apps/cli@latest", "init", "--app-name=foo"];
2034
+ * await $`${[...prefix, ...args]}`;
2035
+ */
2036
+ function getPackageRunnerPrefix(packageManager) {
2037
+ switch (packageManager) {
2038
+ case "pnpm": return ["pnpm", "dlx"];
2039
+ case "bun": return ["bunx"];
2040
+ default: return ["npx"];
2041
+ }
2042
+ }
2001
2043
 
2002
2044
  //#endregion
2003
2045
  //#region src/helpers/addons/fumadocs-setup.ts
@@ -2042,16 +2084,15 @@ async function setupFumadocs(config) {
2042
2084
  initialValue: "next-mdx"
2043
2085
  });
2044
2086
  if (isCancel(template)) return exitCancelled("Operation cancelled");
2045
- const fumadocsInitCommand = getPackageExecutionCommand(packageManager, `create-fumadocs-app@latest fumadocs --template ${TEMPLATES$2[template].value} --src --pm ${packageManager} --no-git`);
2087
+ const args = getPackageExecutionArgs(packageManager, `create-fumadocs-app@latest fumadocs --template ${TEMPLATES$2[template].value} --src --pm ${packageManager} --no-git`);
2046
2088
  const appsDir = path.join(projectDir, "apps");
2047
2089
  await fs.ensureDir(appsDir);
2048
2090
  const s = spinner();
2049
2091
  s.start("Running Fumadocs create command...");
2050
- await execa(fumadocsInitCommand, {
2092
+ await $({
2051
2093
  cwd: appsDir,
2052
- env: { CI: "true" },
2053
- shell: true
2054
- });
2094
+ env: { CI: "true" }
2095
+ })`${args}`;
2055
2096
  const fumadocsDir = path.join(projectDir, "apps", "fumadocs");
2056
2097
  const packageJsonPath = path.join(fumadocsDir, "package.json");
2057
2098
  if (await fs.pathExists(packageJsonPath)) {
@@ -2084,18 +2125,17 @@ async function setupOxlint(projectDir, packageManager) {
2084
2125
  await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
2085
2126
  }
2086
2127
  const s = spinner();
2087
- const oxlintInitCommand = getPackageExecutionCommand(packageManager, "oxlint@latest --init");
2128
+ const oxlintArgs = getPackageExecutionArgs(packageManager, "oxlint@latest --init");
2088
2129
  s.start("Initializing oxlint and oxfmt...");
2089
- await execa(oxlintInitCommand, {
2130
+ await $({
2090
2131
  cwd: projectDir,
2091
- env: { CI: "true" },
2092
- shell: true
2093
- });
2094
- await execa(getPackageExecutionCommand(packageManager, "oxfmt@latest --init"), {
2132
+ env: { CI: "true" }
2133
+ })`${oxlintArgs}`;
2134
+ const oxfmtArgs = getPackageExecutionArgs(packageManager, "oxfmt@latest --init");
2135
+ await $({
2095
2136
  cwd: projectDir,
2096
- env: { CI: "true" },
2097
- shell: true
2098
- });
2137
+ env: { CI: "true" }
2138
+ })`${oxfmtArgs}`;
2099
2139
  s.stop("oxlint and oxfmt initialized successfully!");
2100
2140
  }
2101
2141
 
@@ -2160,11 +2200,11 @@ async function setupRuler(config) {
2160
2200
  const s = spinner();
2161
2201
  s.start("Applying rules with Ruler...");
2162
2202
  try {
2163
- await execa(getPackageExecutionCommand(packageManager, `@intellectronica/ruler@latest apply --agents ${selectedEditors.join(",")} --local-only`), {
2203
+ const rulerApplyArgs = getPackageExecutionArgs(packageManager, `@intellectronica/ruler@latest apply --agents ${selectedEditors.join(",")} --local-only`);
2204
+ await $({
2164
2205
  cwd: projectDir,
2165
- env: { CI: "true" },
2166
- shell: true
2167
- });
2206
+ env: { CI: "true" }
2207
+ })`${rulerApplyArgs}`;
2168
2208
  s.stop("Applied rules with Ruler");
2169
2209
  } catch {
2170
2210
  s.stop(pc.red("Failed to apply rules"));
@@ -2194,7 +2234,7 @@ async function setupStarlight(config) {
2194
2234
  const s = spinner();
2195
2235
  try {
2196
2236
  s.start("Setting up Starlight docs...");
2197
- const starlightInitCommand = getPackageExecutionCommand(packageManager, `create-astro@latest ${[
2237
+ const args = getPackageExecutionArgs(packageManager, `create-astro@latest ${[
2198
2238
  "docs",
2199
2239
  "--template",
2200
2240
  "starlight",
@@ -2206,11 +2246,10 @@ async function setupStarlight(config) {
2206
2246
  ].join(" ")}`);
2207
2247
  const appsDir = path.join(projectDir, "apps");
2208
2248
  await fs.ensureDir(appsDir);
2209
- await execa(starlightInitCommand, {
2249
+ await $({
2210
2250
  cwd: appsDir,
2211
- env: { CI: "true" },
2212
- shell: true
2213
- });
2251
+ env: { CI: "true" }
2252
+ })`${args}`;
2214
2253
  s.stop("Starlight docs setup successfully!");
2215
2254
  } catch (error) {
2216
2255
  s.stop(pc.red("Failed to set up Starlight docs"));
@@ -2250,19 +2289,21 @@ async function setupTauri(config) {
2250
2289
  const hasNext = frontend.includes("next");
2251
2290
  const devUrl = hasReactRouter || hasSvelte ? "http://localhost:5173" : hasNext ? "http://localhost:3001" : "http://localhost:3001";
2252
2291
  const frontendDist = hasNuxt ? "../.output/public" : hasSvelte ? "../build" : hasNext ? "../.next" : hasReactRouter ? "../build/client" : "../dist";
2253
- await execa(getPackageExecutionCommand(packageManager, `@tauri-apps/cli@latest ${[
2292
+ const tauriArgs = [
2293
+ "@tauri-apps/cli@latest",
2254
2294
  "init",
2255
2295
  `--app-name=${path.basename(projectDir)}`,
2256
2296
  `--window-title=${path.basename(projectDir)}`,
2257
2297
  `--frontend-dist=${frontendDist}`,
2258
2298
  `--dev-url=${devUrl}`,
2259
- `--before-dev-command="${packageManager} run dev"`,
2260
- `--before-build-command="${packageManager} run build"`
2261
- ].join(" ")}`), {
2299
+ `--before-dev-command=${packageManager} run dev`,
2300
+ `--before-build-command=${packageManager} run build`
2301
+ ];
2302
+ const prefix = getPackageRunnerPrefix(packageManager);
2303
+ await $({
2262
2304
  cwd: clientPackageDir,
2263
- env: { CI: "true" },
2264
- shell: true
2265
- });
2305
+ env: { CI: "true" }
2306
+ })`${[...prefix, ...tauriArgs]}`;
2266
2307
  s.stop("Tauri desktop app support configured successfully!");
2267
2308
  } catch (error) {
2268
2309
  s.stop(pc.red("Failed to set up Tauri"));
@@ -2300,16 +2341,15 @@ async function setupTui(config) {
2300
2341
  initialValue: "core"
2301
2342
  });
2302
2343
  if (isCancel(template)) return exitCancelled("Operation cancelled");
2303
- const tuiInitCommand = getPackageExecutionCommand(packageManager, `create-tui@latest --template ${template} --no-git --no-install tui`);
2344
+ const args = getPackageExecutionArgs(packageManager, `create-tui@latest --template ${template} --no-git --no-install tui`);
2304
2345
  const appsDir = path.join(projectDir, "apps");
2305
2346
  await fs.ensureDir(appsDir);
2306
2347
  const s = spinner();
2307
2348
  s.start("Running OpenTUI create command...");
2308
- await execa(tuiInitCommand, {
2349
+ await $({
2309
2350
  cwd: appsDir,
2310
- env: { CI: "true" },
2311
- shell: true
2312
- });
2351
+ env: { CI: "true" }
2352
+ })`${args}`;
2313
2353
  s.stop("OpenTUI setup complete!");
2314
2354
  } catch (error) {
2315
2355
  log.error(pc.red("Failed to set up OpenTUI"));
@@ -2410,14 +2450,13 @@ async function setupUltracite(config, hasHusky) {
2410
2450
  if (agents.length > 0) ultraciteArgs.push("--agents", ...agents);
2411
2451
  if (hooks.length > 0) ultraciteArgs.push("--hooks", ...hooks);
2412
2452
  if (hasHusky) ultraciteArgs.push("--integrations", "husky", "lint-staged");
2413
- const ultraciteInitCommand = getPackageExecutionCommand(packageManager, `ultracite@latest ${ultraciteArgs.join(" ")} --skip-install`);
2453
+ const args = getPackageExecutionArgs(packageManager, `ultracite@latest ${ultraciteArgs.join(" ")} --skip-install`);
2414
2454
  const s = spinner();
2415
2455
  s.start("Running Ultracite init command...");
2416
- await execa(ultraciteInitCommand, {
2456
+ await $({
2417
2457
  cwd: projectDir,
2418
- env: { CI: "true" },
2419
- shell: true
2420
- });
2458
+ env: { CI: "true" }
2459
+ })`${args}`;
2421
2460
  if (hasHusky) await addPackageDependency({
2422
2461
  devDependencies: ["husky", "lint-staged"],
2423
2462
  projectDir
@@ -2429,6 +2468,54 @@ async function setupUltracite(config, hasHusky) {
2429
2468
  }
2430
2469
  }
2431
2470
 
2471
+ //#endregion
2472
+ //#region src/utils/ts-morph.ts
2473
+ const tsProject = new Project({
2474
+ useInMemoryFileSystem: false,
2475
+ skipAddingFilesFromTsConfig: true,
2476
+ manipulationSettings: {
2477
+ quoteKind: QuoteKind.Single,
2478
+ indentationText: IndentationText.TwoSpaces
2479
+ }
2480
+ });
2481
+ function ensureArrayProperty(obj, name) {
2482
+ return obj.getProperty(name)?.getFirstDescendantByKind(SyntaxKind.ArrayLiteralExpression) ?? obj.addPropertyAssignment({
2483
+ name,
2484
+ initializer: "[]"
2485
+ }).getFirstDescendantByKindOrThrow(SyntaxKind.ArrayLiteralExpression);
2486
+ }
2487
+
2488
+ //#endregion
2489
+ //#region src/helpers/addons/vite-pwa-setup.ts
2490
+ async function addPwaToViteConfig(viteConfigPath, projectName) {
2491
+ const sourceFile = tsProject.addSourceFileAtPathIfExists(viteConfigPath);
2492
+ if (!sourceFile) throw new Error("vite config not found");
2493
+ if (!sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === "vite-plugin-pwa")) sourceFile.insertImportDeclaration(0, {
2494
+ namedImports: ["VitePWA"],
2495
+ moduleSpecifier: "vite-plugin-pwa"
2496
+ });
2497
+ const defineCall = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression).find((expr) => {
2498
+ const expression = expr.getExpression();
2499
+ return Node.isIdentifier(expression) && expression.getText() === "defineConfig";
2500
+ });
2501
+ if (!defineCall) throw new Error("Could not find defineConfig call in vite config");
2502
+ const configObject = defineCall.getArguments()[0];
2503
+ if (!configObject) throw new Error("defineConfig argument is not an object literal");
2504
+ const pluginsArray = ensureArrayProperty(configObject, "plugins");
2505
+ if (!pluginsArray.getElements().some((el) => el.getText().startsWith("VitePWA("))) pluginsArray.addElement(`VitePWA({
2506
+ registerType: "autoUpdate",
2507
+ manifest: {
2508
+ name: "${projectName}",
2509
+ short_name: "${projectName}",
2510
+ description: "${projectName} - PWA Application",
2511
+ theme_color: "#0c0c0c",
2512
+ },
2513
+ pwaAssets: { disabled: false, config: true },
2514
+ devOptions: { enabled: true },
2515
+ })`);
2516
+ await tsProject.save();
2517
+ }
2518
+
2432
2519
  //#endregion
2433
2520
  //#region src/helpers/addons/wxt-setup.ts
2434
2521
  const TEMPLATES = {
@@ -2467,16 +2554,15 @@ async function setupWxt(config) {
2467
2554
  initialValue: "react"
2468
2555
  });
2469
2556
  if (isCancel(template)) return exitCancelled("Operation cancelled");
2470
- const wxtInitCommand = getPackageExecutionCommand(packageManager, `wxt@latest init extension --template ${template} --pm ${packageManager}`);
2557
+ const args = getPackageExecutionArgs(packageManager, `wxt@latest init extension --template ${template} --pm ${packageManager}`);
2471
2558
  const appsDir = path.join(projectDir, "apps");
2472
2559
  await fs.ensureDir(appsDir);
2473
2560
  const s = spinner();
2474
2561
  s.start("Running WXT init command...");
2475
- await execa(wxtInitCommand, {
2562
+ await $({
2476
2563
  cwd: appsDir,
2477
- env: { CI: "true" },
2478
- shell: true
2479
- });
2564
+ env: { CI: "true" }
2565
+ })`${args}`;
2480
2566
  const extensionDir = path.join(projectDir, "apps", "extension");
2481
2567
  const packageJsonPath = path.join(extensionDir, "package.json");
2482
2568
  if (await fs.pathExists(packageJsonPath)) {
@@ -2492,54 +2578,6 @@ async function setupWxt(config) {
2492
2578
  }
2493
2579
  }
2494
2580
 
2495
- //#endregion
2496
- //#region src/utils/ts-morph.ts
2497
- const tsProject$1 = new Project({
2498
- useInMemoryFileSystem: false,
2499
- skipAddingFilesFromTsConfig: true,
2500
- manipulationSettings: {
2501
- quoteKind: QuoteKind.Single,
2502
- indentationText: IndentationText.TwoSpaces
2503
- }
2504
- });
2505
- function ensureArrayProperty(obj, name) {
2506
- return obj.getProperty(name)?.getFirstDescendantByKind(SyntaxKind.ArrayLiteralExpression) ?? obj.addPropertyAssignment({
2507
- name,
2508
- initializer: "[]"
2509
- }).getFirstDescendantByKindOrThrow(SyntaxKind.ArrayLiteralExpression);
2510
- }
2511
-
2512
- //#endregion
2513
- //#region src/helpers/addons/vite-pwa-setup.ts
2514
- async function addPwaToViteConfig(viteConfigPath, projectName) {
2515
- const sourceFile = tsProject$1.addSourceFileAtPathIfExists(viteConfigPath);
2516
- if (!sourceFile) throw new Error("vite config not found");
2517
- if (!sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === "vite-plugin-pwa")) sourceFile.insertImportDeclaration(0, {
2518
- namedImports: ["VitePWA"],
2519
- moduleSpecifier: "vite-plugin-pwa"
2520
- });
2521
- const defineCall = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression).find((expr) => {
2522
- const expression = expr.getExpression();
2523
- return Node.isIdentifier(expression) && expression.getText() === "defineConfig";
2524
- });
2525
- if (!defineCall) throw new Error("Could not find defineConfig call in vite config");
2526
- const configObject = defineCall.getArguments()[0];
2527
- if (!configObject) throw new Error("defineConfig argument is not an object literal");
2528
- const pluginsArray = ensureArrayProperty(configObject, "plugins");
2529
- if (!pluginsArray.getElements().some((el) => el.getText().startsWith("VitePWA("))) pluginsArray.addElement(`VitePWA({
2530
- registerType: "autoUpdate",
2531
- manifest: {
2532
- name: "${projectName}",
2533
- short_name: "${projectName}",
2534
- description: "${projectName} - PWA Application",
2535
- theme_color: "#0c0c0c",
2536
- },
2537
- pwaAssets: { disabled: false, config: true },
2538
- devOptions: { enabled: true },
2539
- })`);
2540
- await tsProject$1.save();
2541
- }
2542
-
2543
2581
  //#endregion
2544
2582
  //#region src/helpers/addons/addons-setup.ts
2545
2583
  async function setupAddons(config, isAddCommand = false) {
@@ -2768,38 +2806,6 @@ handlebars.registerHelper("or", (...args) => {
2768
2806
  });
2769
2807
  handlebars.registerHelper("includes", (array, value) => Array.isArray(array) && array.includes(value));
2770
2808
 
2771
- //#endregion
2772
- //#region src/helpers/deployment/alchemy/env-dts-setup.ts
2773
- const tsProject = new Project({
2774
- useInMemoryFileSystem: false,
2775
- skipAddingFilesFromTsConfig: true
2776
- });
2777
- function determineImportPath(envDtsPath, projectDir, config) {
2778
- const { webDeploy, serverDeploy, backend } = config;
2779
- const isBackendSelf = backend === "self";
2780
- let alchemyRunPath;
2781
- if (webDeploy === "alchemy" && (serverDeploy === "alchemy" || isBackendSelf)) if (isBackendSelf) alchemyRunPath = path.join(projectDir, "apps/web/alchemy.run.ts");
2782
- else alchemyRunPath = path.join(projectDir, "alchemy.run.ts");
2783
- else if (webDeploy === "alchemy") alchemyRunPath = path.join(projectDir, "apps/web/alchemy.run.ts");
2784
- else if (serverDeploy === "alchemy") alchemyRunPath = path.join(projectDir, "apps/server/alchemy.run.ts");
2785
- else alchemyRunPath = path.join(projectDir, "alchemy.run.ts");
2786
- const relativePath = path.relative(path.dirname(envDtsPath), alchemyRunPath.replace(/\.ts$/, ""));
2787
- return (relativePath.startsWith(".") ? relativePath : `./${relativePath}`).replace(/\\/g, "/");
2788
- }
2789
- async function setupEnvDtsImport(envDtsPath, projectDir, config) {
2790
- if (!await fs.pathExists(envDtsPath)) return;
2791
- const importPath = determineImportPath(envDtsPath, projectDir, config);
2792
- const sourceFile = tsProject.addSourceFileAtPath(envDtsPath);
2793
- if (!sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === importPath && imp.getNamedImports().some((named) => named.getName() === "server"))) sourceFile.insertImportDeclaration(0, {
2794
- moduleSpecifier: importPath,
2795
- namedImports: [{
2796
- name: "server",
2797
- isTypeOnly: true
2798
- }]
2799
- });
2800
- await sourceFile.save();
2801
- }
2802
-
2803
2809
  //#endregion
2804
2810
  //#region src/helpers/core/template-manager.ts
2805
2811
  async function processAndCopyFiles(sourcePattern, baseSourceDir, destDir, context, overwrite = true, ignorePatterns) {
@@ -2904,7 +2910,7 @@ async function setupFrontendTemplates(projectDir, context) {
2904
2910
  }
2905
2911
  }
2906
2912
  }
2907
- async function setupApiPackage(projectDir, context) {
2913
+ async function setupApiPackage$1(projectDir, context) {
2908
2914
  if (context.api === "none") return;
2909
2915
  const apiPackageDir = path.join(projectDir, "packages/api");
2910
2916
  await fs.ensureDir(apiPackageDir);
@@ -2917,7 +2923,7 @@ async function setupConfigPackage(projectDir, context) {
2917
2923
  const configBaseDir = path.join(PKG_ROOT, "templates/packages/config");
2918
2924
  if (await fs.pathExists(configBaseDir)) await processAndCopyFiles("**/*", configBaseDir, configPackageDir, context);
2919
2925
  }
2920
- async function setupDbPackage(projectDir, context) {
2926
+ async function setupDbPackage$1(projectDir, context) {
2921
2927
  if (context.database === "none" || context.orm === "none") return;
2922
2928
  const dbPackageDir = path.join(projectDir, "packages/db");
2923
2929
  await fs.ensureDir(dbPackageDir);
@@ -2944,21 +2950,78 @@ async function setupServerApp(projectDir, context) {
2944
2950
  const frameworkSrcDir = path.join(PKG_ROOT, `templates/backend/server/${context.backend}`);
2945
2951
  if (await fs.pathExists(frameworkSrcDir)) await processAndCopyFiles("**/*", frameworkSrcDir, serverAppDir, context, true);
2946
2952
  }
2953
+ async function setupEnvPackage$1(projectDir, context) {
2954
+ const hasWebFrontend$1 = context.frontend.some((f) => [
2955
+ "tanstack-router",
2956
+ "react-router",
2957
+ "tanstack-start",
2958
+ "next",
2959
+ "nuxt",
2960
+ "svelte",
2961
+ "solid"
2962
+ ].includes(f));
2963
+ const hasNative = context.frontend.some((f) => [
2964
+ "native-bare",
2965
+ "native-uniwind",
2966
+ "native-unistyles"
2967
+ ].includes(f));
2968
+ if (!hasWebFrontend$1 && !hasNative && context.backend === "none") return;
2969
+ const envPackageDir = path.join(projectDir, "packages/env");
2970
+ await fs.ensureDir(envPackageDir);
2971
+ const envBaseDir = path.join(PKG_ROOT, "templates/packages/env");
2972
+ const packageJsonSrc = path.join(envBaseDir, "package.json.hbs");
2973
+ if (await fs.pathExists(packageJsonSrc)) await processAndCopyFiles("package.json.hbs", envBaseDir, envPackageDir, context);
2974
+ const tsconfigSrc = path.join(envBaseDir, "tsconfig.json.hbs");
2975
+ if (await fs.pathExists(tsconfigSrc)) await processAndCopyFiles("tsconfig.json.hbs", envBaseDir, envPackageDir, context);
2976
+ const needsServerEnv = context.backend !== "none" && context.backend !== "convex";
2977
+ if (needsServerEnv) {
2978
+ const serverSrc = path.join(envBaseDir, "src/server.ts.hbs");
2979
+ if (await fs.pathExists(serverSrc)) {
2980
+ await fs.ensureDir(path.join(envPackageDir, "src"));
2981
+ await processAndCopyFiles("src/server.ts.hbs", envBaseDir, envPackageDir, context);
2982
+ }
2983
+ }
2984
+ if (hasWebFrontend$1) {
2985
+ const webSrc = path.join(envBaseDir, "src/web.ts.hbs");
2986
+ if (await fs.pathExists(webSrc)) {
2987
+ await fs.ensureDir(path.join(envPackageDir, "src"));
2988
+ await processAndCopyFiles("src/web.ts.hbs", envBaseDir, envPackageDir, context);
2989
+ }
2990
+ }
2991
+ if (hasNative) {
2992
+ const nativeSrc = path.join(envBaseDir, "src/native.ts.hbs");
2993
+ if (await fs.pathExists(nativeSrc)) {
2994
+ await fs.ensureDir(path.join(envPackageDir, "src"));
2995
+ await processAndCopyFiles("src/native.ts.hbs", envBaseDir, envPackageDir, context);
2996
+ }
2997
+ }
2998
+ const packageJsonPath = path.join(envPackageDir, "package.json");
2999
+ if (await fs.pathExists(packageJsonPath)) {
3000
+ const packageJson = await fs.readJson(packageJsonPath);
3001
+ const exports = {};
3002
+ if (needsServerEnv) exports["./server"] = "./src/server.ts";
3003
+ if (hasWebFrontend$1) exports["./web"] = "./src/web.ts";
3004
+ if (hasNative) exports["./native"] = "./src/native.ts";
3005
+ packageJson.exports = exports;
3006
+ await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
3007
+ }
3008
+ }
2947
3009
  async function setupBackendFramework(projectDir, context) {
2948
3010
  await setupConfigPackage(projectDir, context);
3011
+ await setupEnvPackage$1(projectDir, context);
2949
3012
  if (context.backend === "none") return;
2950
3013
  if (context.backend === "convex") {
2951
3014
  await setupConvexBackend(projectDir, context);
2952
3015
  return;
2953
3016
  }
2954
3017
  if (context.backend === "self") {
2955
- await setupApiPackage(projectDir, context);
2956
- await setupDbPackage(projectDir, context);
3018
+ await setupApiPackage$1(projectDir, context);
3019
+ await setupDbPackage$1(projectDir, context);
2957
3020
  return;
2958
3021
  }
2959
3022
  await setupServerApp(projectDir, context);
2960
- await setupApiPackage(projectDir, context);
2961
- await setupDbPackage(projectDir, context);
3023
+ await setupApiPackage$1(projectDir, context);
3024
+ await setupDbPackage$1(projectDir, context);
2962
3025
  }
2963
3026
  async function setupAuthTemplate(projectDir, context) {
2964
3027
  if (!context.auth || context.auth === "none") return;
@@ -3287,35 +3350,22 @@ async function setupDockerComposeTemplates(projectDir, context) {
3287
3350
  }
3288
3351
  async function setupDeploymentTemplates(projectDir, context) {
3289
3352
  const isBackendSelf = context.backend === "self";
3290
- if (context.webDeploy === "alchemy" || context.serverDeploy === "alchemy") {
3291
- const alchemyTemplateSrc = path.join(PKG_ROOT, "templates/deploy/alchemy");
3292
- if (context.webDeploy === "alchemy" && (context.serverDeploy === "alchemy" || isBackendSelf)) {
3293
- if (await fs.pathExists(alchemyTemplateSrc)) {
3294
- const webAppDir = path.join(projectDir, "apps/web");
3295
- await processAndCopyFiles("alchemy.run.ts.hbs", alchemyTemplateSrc, isBackendSelf && await fs.pathExists(webAppDir) ? webAppDir : projectDir, context);
3296
- if (!isBackendSelf) await addEnvDtsToPackages(projectDir, context, alchemyTemplateSrc);
3297
- }
3298
- } else {
3299
- if (context.webDeploy === "alchemy") {
3300
- const webAppDir = path.join(projectDir, "apps/web");
3301
- if (await fs.pathExists(alchemyTemplateSrc) && await fs.pathExists(webAppDir)) {
3302
- await processAndCopyFiles("alchemy.run.ts.hbs", alchemyTemplateSrc, webAppDir, context);
3303
- if (!isBackendSelf) await addEnvDtsToPackages(projectDir, context, alchemyTemplateSrc);
3304
- }
3305
- }
3306
- if (context.serverDeploy === "alchemy" && !isBackendSelf) {
3307
- const serverAppDir = path.join(projectDir, "apps/server");
3308
- if (await fs.pathExists(alchemyTemplateSrc) && await fs.pathExists(serverAppDir)) {
3309
- await processAndCopyFiles("alchemy.run.ts.hbs", alchemyTemplateSrc, serverAppDir, context);
3310
- const envDtsPath = path.join(serverAppDir, "env.d.ts");
3311
- await processTemplate(path.join(alchemyTemplateSrc, "env.d.ts.hbs"), envDtsPath, context);
3312
- await setupEnvDtsImport(envDtsPath, projectDir, context);
3313
- await addEnvDtsToPackages(projectDir, context, alchemyTemplateSrc);
3314
- }
3315
- }
3353
+ if (context.webDeploy === "cloudflare" || context.serverDeploy === "cloudflare") {
3354
+ const infraTemplateSrc = path.join(PKG_ROOT, "templates/packages/infra");
3355
+ const infraDir = path.join(projectDir, "packages/infra");
3356
+ if (await fs.pathExists(infraTemplateSrc)) {
3357
+ await fs.ensureDir(infraDir);
3358
+ await processAndCopyFiles("package.json.hbs", infraTemplateSrc, infraDir, context);
3359
+ await processAndCopyFiles("alchemy.run.ts.hbs", infraTemplateSrc, infraDir, context);
3360
+ }
3361
+ if (!isBackendSelf) {
3362
+ const envTemplateSrc = path.join(PKG_ROOT, "templates/packages/env");
3363
+ const envDir = path.join(projectDir, "packages/env");
3364
+ const envDtsTemplatePath = path.join(envTemplateSrc, "env.d.ts.hbs");
3365
+ if (await fs.pathExists(envDtsTemplatePath)) await processTemplate(envDtsTemplatePath, path.join(envDir, "env.d.ts"), context);
3316
3366
  }
3317
3367
  }
3318
- if (context.webDeploy !== "none" && context.webDeploy !== "alchemy") {
3368
+ if (context.webDeploy !== "none" && context.webDeploy !== "cloudflare") {
3319
3369
  const webAppDir = path.join(projectDir, "apps/web");
3320
3370
  if (await fs.pathExists(webAppDir)) {
3321
3371
  const frontends = context.frontend;
@@ -3334,7 +3384,7 @@ async function setupDeploymentTemplates(projectDir, context) {
3334
3384
  }
3335
3385
  }
3336
3386
  }
3337
- if (context.serverDeploy !== "none" && context.serverDeploy !== "alchemy" && !isBackendSelf) {
3387
+ if (context.serverDeploy !== "none" && context.serverDeploy !== "cloudflare" && !isBackendSelf) {
3338
3388
  const serverAppDir = path.join(projectDir, "apps/server");
3339
3389
  if (await fs.pathExists(serverAppDir)) {
3340
3390
  const deployTemplateSrc = path.join(PKG_ROOT, `templates/deploy/${context.serverDeploy}/server`);
@@ -3342,26 +3392,6 @@ async function setupDeploymentTemplates(projectDir, context) {
3342
3392
  }
3343
3393
  }
3344
3394
  }
3345
- async function addEnvDtsToPackages(projectDir, context, alchemyTemplateSrc) {
3346
- for (const packageName of [
3347
- "packages/api",
3348
- "packages/auth",
3349
- "packages/db"
3350
- ]) {
3351
- const packageDir = path.join(projectDir, packageName);
3352
- if (await fs.pathExists(packageDir)) {
3353
- const envDtsPath = path.join(packageDir, "env.d.ts");
3354
- await processTemplate(path.join(alchemyTemplateSrc, "env.d.ts.hbs"), envDtsPath, context);
3355
- await setupEnvDtsImport(envDtsPath, projectDir, context);
3356
- }
3357
- }
3358
- const serverAppDir = path.join(projectDir, "apps/server");
3359
- if (await fs.pathExists(serverAppDir)) {
3360
- const envDtsPath = path.join(serverAppDir, "env.d.ts");
3361
- await processTemplate(path.join(alchemyTemplateSrc, "env.d.ts.hbs"), envDtsPath, context);
3362
- await setupEnvDtsImport(envDtsPath, projectDir, context);
3363
- }
3364
- }
3365
3395
 
3366
3396
  //#endregion
3367
3397
  //#region src/helpers/core/add-addons.ts
@@ -3410,50 +3440,9 @@ async function addAddonsToProject(input) {
3410
3440
  }
3411
3441
  }
3412
3442
 
3413
- //#endregion
3414
- //#region src/helpers/deployment/server-deploy-setup.ts
3415
- async function setupServerDeploy(config) {
3416
- const { serverDeploy, webDeploy, projectDir } = config;
3417
- if (serverDeploy === "none") return;
3418
- if (serverDeploy === "alchemy" && webDeploy === "alchemy") return;
3419
- const serverDir = path.join(projectDir, "apps/server");
3420
- if (!await fs.pathExists(serverDir)) return;
3421
- if (serverDeploy === "alchemy") await setupAlchemyServerDeploy(serverDir, projectDir);
3422
- }
3423
- async function setupAlchemyServerDeploy(serverDir, projectDir) {
3424
- if (!await fs.pathExists(serverDir)) return;
3425
- await addPackageDependency({
3426
- devDependencies: [
3427
- "alchemy",
3428
- "wrangler",
3429
- "@types/node",
3430
- "@cloudflare/workers-types"
3431
- ],
3432
- projectDir: serverDir
3433
- });
3434
- if (projectDir) await addAlchemyPackagesDependencies$1(projectDir);
3435
- const packageJsonPath = path.join(serverDir, "package.json");
3436
- if (await fs.pathExists(packageJsonPath)) {
3437
- const packageJson = await fs.readJson(packageJsonPath);
3438
- packageJson.scripts = {
3439
- ...packageJson.scripts,
3440
- dev: "alchemy dev",
3441
- deploy: "alchemy deploy",
3442
- destroy: "alchemy destroy"
3443
- };
3444
- await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
3445
- }
3446
- }
3447
- async function addAlchemyPackagesDependencies$1(projectDir) {
3448
- await addPackageDependency({
3449
- devDependencies: ["@cloudflare/workers-types"],
3450
- projectDir
3451
- });
3452
- }
3453
-
3454
3443
  //#endregion
3455
3444
  //#region src/helpers/deployment/alchemy/alchemy-next-setup.ts
3456
- async function setupNextAlchemyDeploy(projectDir, _packageManager, options) {
3445
+ async function setupNextAlchemyDeploy(projectDir, _packageManager, _options) {
3457
3446
  const webAppDir = path.join(projectDir, "apps/web");
3458
3447
  if (!await fs.pathExists(webAppDir)) return;
3459
3448
  await addPackageDependency({
@@ -3465,17 +3454,6 @@ async function setupNextAlchemyDeploy(projectDir, _packageManager, options) {
3465
3454
  ],
3466
3455
  projectDir: webAppDir
3467
3456
  });
3468
- const pkgPath = path.join(webAppDir, "package.json");
3469
- if (await fs.pathExists(pkgPath)) {
3470
- const pkg = await fs.readJson(pkgPath);
3471
- if (!options?.skipAppScripts) pkg.scripts = {
3472
- ...pkg.scripts,
3473
- dev: "alchemy dev",
3474
- deploy: "alchemy deploy",
3475
- destroy: "alchemy destroy"
3476
- };
3477
- await fs.writeJson(pkgPath, pkg, { spaces: 2 });
3478
- }
3479
3457
  const openNextConfigPath = path.join(webAppDir, "open-next.config.ts");
3480
3458
  await fs.writeFile(openNextConfigPath, `import { defineCloudflareConfig } from "@opennextjs/cloudflare";
3481
3459
 
@@ -3489,7 +3467,7 @@ export default defineCloudflareConfig({});
3489
3467
 
3490
3468
  //#endregion
3491
3469
  //#region src/helpers/deployment/alchemy/alchemy-nuxt-setup.ts
3492
- async function setupNuxtAlchemyDeploy(projectDir, _packageManager, options) {
3470
+ async function setupNuxtAlchemyDeploy(projectDir, _packageManager, _options) {
3493
3471
  const webAppDir = path.join(projectDir, "apps/web");
3494
3472
  if (!await fs.pathExists(webAppDir)) return;
3495
3473
  await addPackageDependency({
@@ -3500,17 +3478,6 @@ async function setupNuxtAlchemyDeploy(projectDir, _packageManager, options) {
3500
3478
  ],
3501
3479
  projectDir: webAppDir
3502
3480
  });
3503
- const pkgPath = path.join(webAppDir, "package.json");
3504
- if (await fs.pathExists(pkgPath)) {
3505
- const pkg = await fs.readJson(pkgPath);
3506
- if (!options?.skipAppScripts) pkg.scripts = {
3507
- ...pkg.scripts,
3508
- dev: "alchemy dev",
3509
- deploy: "alchemy deploy",
3510
- destroy: "alchemy destroy"
3511
- };
3512
- await fs.writeJson(pkgPath, pkg, { spaces: 2 });
3513
- }
3514
3481
  const nuxtConfigPath = path.join(webAppDir, "nuxt.config.ts");
3515
3482
  if (!await fs.pathExists(nuxtConfigPath)) return;
3516
3483
  try {
@@ -3555,68 +3522,35 @@ async function setupNuxtAlchemyDeploy(projectDir, _packageManager, options) {
3555
3522
 
3556
3523
  //#endregion
3557
3524
  //#region src/helpers/deployment/alchemy/alchemy-react-router-setup.ts
3558
- async function setupReactRouterAlchemyDeploy(projectDir, _packageManager, options) {
3525
+ async function setupReactRouterAlchemyDeploy(projectDir, _packageManager, _options) {
3559
3526
  const webAppDir = path.join(projectDir, "apps/web");
3560
3527
  if (!await fs.pathExists(webAppDir)) return;
3561
3528
  await addPackageDependency({
3562
3529
  devDependencies: ["alchemy"],
3563
3530
  projectDir: webAppDir
3564
3531
  });
3565
- const pkgPath = path.join(webAppDir, "package.json");
3566
- if (await fs.pathExists(pkgPath)) {
3567
- const pkg = await fs.readJson(pkgPath);
3568
- if (!options?.skipAppScripts) pkg.scripts = {
3569
- ...pkg.scripts,
3570
- dev: "alchemy dev",
3571
- deploy: "alchemy deploy",
3572
- destroy: "alchemy destroy"
3573
- };
3574
- await fs.writeJson(pkgPath, pkg, { spaces: 2 });
3575
- }
3576
3532
  }
3577
3533
 
3578
3534
  //#endregion
3579
3535
  //#region src/helpers/deployment/alchemy/alchemy-solid-setup.ts
3580
- async function setupSolidAlchemyDeploy(projectDir, _packageManager, options) {
3536
+ async function setupSolidAlchemyDeploy(projectDir, _packageManager, _options) {
3581
3537
  const webAppDir = path.join(projectDir, "apps/web");
3582
3538
  if (!await fs.pathExists(webAppDir)) return;
3583
3539
  await addPackageDependency({
3584
3540
  devDependencies: ["alchemy"],
3585
3541
  projectDir: webAppDir
3586
3542
  });
3587
- const pkgPath = path.join(webAppDir, "package.json");
3588
- if (await fs.pathExists(pkgPath)) {
3589
- const pkg = await fs.readJson(pkgPath);
3590
- if (!options?.skipAppScripts) pkg.scripts = {
3591
- ...pkg.scripts,
3592
- dev: "alchemy dev",
3593
- deploy: "alchemy deploy",
3594
- destroy: "alchemy destroy"
3595
- };
3596
- await fs.writeJson(pkgPath, pkg, { spaces: 2 });
3597
- }
3598
3543
  }
3599
3544
 
3600
3545
  //#endregion
3601
3546
  //#region src/helpers/deployment/alchemy/alchemy-svelte-setup.ts
3602
- async function setupSvelteAlchemyDeploy(projectDir, _packageManager, options) {
3547
+ async function setupSvelteAlchemyDeploy(projectDir, _packageManager, _options) {
3603
3548
  const webAppDir = path.join(projectDir, "apps/web");
3604
3549
  if (!await fs.pathExists(webAppDir)) return;
3605
3550
  await addPackageDependency({
3606
3551
  devDependencies: ["alchemy", "@sveltejs/adapter-cloudflare"],
3607
3552
  projectDir: webAppDir
3608
3553
  });
3609
- const pkgPath = path.join(webAppDir, "package.json");
3610
- if (await fs.pathExists(pkgPath)) {
3611
- const pkg = await fs.readJson(pkgPath);
3612
- if (!options?.skipAppScripts) pkg.scripts = {
3613
- ...pkg.scripts,
3614
- dev: "alchemy dev",
3615
- deploy: "alchemy deploy",
3616
- destroy: "alchemy destroy"
3617
- };
3618
- await fs.writeJson(pkgPath, pkg, { spaces: 2 });
3619
- }
3620
3554
  const svelteConfigPath = path.join(webAppDir, "svelte.config.js");
3621
3555
  if (!await fs.pathExists(svelteConfigPath)) return;
3622
3556
  try {
@@ -3665,46 +3599,24 @@ function updateAdapterInConfig(configObject) {
3665
3599
 
3666
3600
  //#endregion
3667
3601
  //#region src/helpers/deployment/alchemy/alchemy-tanstack-router-setup.ts
3668
- async function setupTanStackRouterAlchemyDeploy(projectDir, _packageManager, options) {
3602
+ async function setupTanStackRouterAlchemyDeploy(projectDir, _packageManager, _options) {
3669
3603
  const webAppDir = path.join(projectDir, "apps/web");
3670
3604
  if (!await fs.pathExists(webAppDir)) return;
3671
3605
  await addPackageDependency({
3672
3606
  devDependencies: ["alchemy"],
3673
3607
  projectDir: webAppDir
3674
3608
  });
3675
- const pkgPath = path.join(webAppDir, "package.json");
3676
- if (await fs.pathExists(pkgPath)) {
3677
- const pkg = await fs.readJson(pkgPath);
3678
- if (!options?.skipAppScripts) pkg.scripts = {
3679
- ...pkg.scripts,
3680
- dev: "alchemy dev",
3681
- deploy: "alchemy deploy",
3682
- destroy: "alchemy destroy"
3683
- };
3684
- await fs.writeJson(pkgPath, pkg, { spaces: 2 });
3685
- }
3686
3609
  }
3687
3610
 
3688
3611
  //#endregion
3689
3612
  //#region src/helpers/deployment/alchemy/alchemy-tanstack-start-setup.ts
3690
- async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, options) {
3613
+ async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, _options) {
3691
3614
  const webAppDir = path.join(projectDir, "apps/web");
3692
3615
  if (!await fs.pathExists(webAppDir)) return;
3693
3616
  await addPackageDependency({
3694
3617
  devDependencies: ["alchemy", "@cloudflare/vite-plugin"],
3695
3618
  projectDir: webAppDir
3696
3619
  });
3697
- const pkgPath = path.join(webAppDir, "package.json");
3698
- if (await fs.pathExists(pkgPath)) {
3699
- const pkg = await fs.readJson(pkgPath);
3700
- if (!options?.skipAppScripts) pkg.scripts = {
3701
- ...pkg.scripts,
3702
- dev: "alchemy dev",
3703
- deploy: "alchemy deploy",
3704
- destroy: "alchemy destroy"
3705
- };
3706
- await fs.writeJson(pkgPath, pkg, { spaces: 2 });
3707
- }
3708
3620
  const viteConfigPath = path.join(webAppDir, "vite.config.ts");
3709
3621
  if (await fs.pathExists(viteConfigPath)) try {
3710
3622
  const project = new Project({ manipulationSettings: {
@@ -3745,22 +3657,16 @@ async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, opti
3745
3657
 
3746
3658
  //#endregion
3747
3659
  //#region src/helpers/deployment/alchemy/alchemy-combined-setup.ts
3748
- async function setupCombinedAlchemyDeploy(projectDir, packageManager, config) {
3749
- await addPackageDependency({
3750
- devDependencies: ["alchemy"],
3751
- projectDir
3752
- });
3753
- const rootPkgPath = path.join(projectDir, "package.json");
3754
- if (await fs.pathExists(rootPkgPath)) {
3755
- const pkg = await fs.readJson(rootPkgPath);
3756
- pkg.scripts = {
3757
- ...pkg.scripts,
3758
- deploy: "alchemy deploy",
3759
- destroy: "alchemy destroy",
3760
- dev: "alchemy dev"
3761
- };
3762
- await fs.writeJson(rootPkgPath, pkg, { spaces: 2 });
3660
+ function getInfraFilter(packageManager, hasTurborepo, infraWorkspace) {
3661
+ if (hasTurborepo) return (script) => `turbo -F ${infraWorkspace} ${script}`;
3662
+ switch (packageManager) {
3663
+ case "pnpm": return (script) => `pnpm --filter ${infraWorkspace} ${script}`;
3664
+ case "npm": return (script) => `npm run ${script} --workspace ${infraWorkspace}`;
3665
+ case "bun": return (script) => `bun run --filter ${infraWorkspace} ${script}`;
3763
3666
  }
3667
+ }
3668
+ async function setupCombinedAlchemyDeploy(projectDir, packageManager, config) {
3669
+ await setupInfraScripts(projectDir, packageManager, config);
3764
3670
  const serverDir = path.join(projectDir, "apps/server");
3765
3671
  if (await fs.pathExists(serverDir)) await setupAlchemyServerDeploy(serverDir, projectDir);
3766
3672
  const frontend = config.frontend;
@@ -3779,6 +3685,77 @@ async function setupCombinedAlchemyDeploy(projectDir, packageManager, config) {
3779
3685
  else if (isReactRouter) await setupReactRouterAlchemyDeploy(projectDir, packageManager, { skipAppScripts: true });
3780
3686
  else if (isSolid) await setupSolidAlchemyDeploy(projectDir, packageManager, { skipAppScripts: true });
3781
3687
  }
3688
+ async function setupInfraScripts(projectDir, packageManager, config) {
3689
+ const projectName = config.projectName;
3690
+ const hasTurborepo = config.addons.includes("turborepo");
3691
+ const infraWorkspace = `@${projectName}/infra`;
3692
+ const rootPkgPath = path.join(projectDir, "package.json");
3693
+ if (await fs.pathExists(rootPkgPath)) {
3694
+ const pkg = await fs.readJson(rootPkgPath);
3695
+ const filter = getInfraFilter(packageManager, hasTurborepo, infraWorkspace);
3696
+ pkg.scripts = {
3697
+ ...pkg.scripts,
3698
+ deploy: filter("deploy"),
3699
+ destroy: filter("destroy")
3700
+ };
3701
+ await fs.writeJson(rootPkgPath, pkg, { spaces: 2 });
3702
+ }
3703
+ if (config.serverDeploy === "cloudflare") {
3704
+ const serverPkgPath = path.join(projectDir, "apps/server/package.json");
3705
+ if (await fs.pathExists(serverPkgPath)) {
3706
+ const serverPkg = await fs.readJson(serverPkgPath);
3707
+ if (serverPkg.scripts?.dev) {
3708
+ serverPkg.scripts["dev:bare"] = serverPkg.scripts.dev;
3709
+ delete serverPkg.scripts.dev;
3710
+ await fs.writeJson(serverPkgPath, serverPkg, { spaces: 2 });
3711
+ }
3712
+ }
3713
+ }
3714
+ if (config.webDeploy === "cloudflare") {
3715
+ const webPkgPath = path.join(projectDir, "apps/web/package.json");
3716
+ if (await fs.pathExists(webPkgPath)) {
3717
+ const webPkg = await fs.readJson(webPkgPath);
3718
+ if (webPkg.scripts?.dev) {
3719
+ webPkg.scripts["dev:bare"] = webPkg.scripts.dev;
3720
+ delete webPkg.scripts.dev;
3721
+ await fs.writeJson(webPkgPath, webPkg, { spaces: 2 });
3722
+ }
3723
+ }
3724
+ }
3725
+ }
3726
+
3727
+ //#endregion
3728
+ //#region src/helpers/deployment/server-deploy-setup.ts
3729
+ async function setupServerDeploy(config) {
3730
+ const { serverDeploy, webDeploy, projectDir, packageManager } = config;
3731
+ if (serverDeploy === "none") return;
3732
+ if (serverDeploy === "cloudflare" && webDeploy === "cloudflare") return;
3733
+ const serverDir = path.join(projectDir, "apps/server");
3734
+ if (!await fs.pathExists(serverDir)) return;
3735
+ if (serverDeploy === "cloudflare") {
3736
+ await setupInfraScripts(projectDir, packageManager, config);
3737
+ await setupAlchemyServerDeploy(serverDir, projectDir);
3738
+ }
3739
+ }
3740
+ async function setupAlchemyServerDeploy(serverDir, projectDir) {
3741
+ if (!await fs.pathExists(serverDir)) return;
3742
+ await addPackageDependency({
3743
+ devDependencies: [
3744
+ "alchemy",
3745
+ "wrangler",
3746
+ "@types/node",
3747
+ "@cloudflare/workers-types"
3748
+ ],
3749
+ projectDir: serverDir
3750
+ });
3751
+ if (projectDir) await addAlchemyPackagesDependencies$1(projectDir);
3752
+ }
3753
+ async function addAlchemyPackagesDependencies$1(projectDir) {
3754
+ await addPackageDependency({
3755
+ devDependencies: ["@cloudflare/workers-types"],
3756
+ projectDir
3757
+ });
3758
+ }
3782
3759
 
3783
3760
  //#endregion
3784
3761
  //#region src/helpers/deployment/web-deploy-setup.ts
@@ -3786,12 +3763,13 @@ async function setupWebDeploy(config) {
3786
3763
  const { webDeploy, serverDeploy, frontend, projectDir } = config;
3787
3764
  const { packageManager } = config;
3788
3765
  if (webDeploy === "none") return;
3789
- if (webDeploy !== "alchemy") return;
3790
- if (webDeploy === "alchemy" && serverDeploy === "alchemy") {
3766
+ if (webDeploy !== "cloudflare") return;
3767
+ if (webDeploy === "cloudflare" && serverDeploy === "cloudflare") {
3791
3768
  await setupCombinedAlchemyDeploy(projectDir, packageManager, config);
3792
3769
  await addAlchemyPackagesDependencies(projectDir);
3793
3770
  return;
3794
3771
  }
3772
+ await setupInfraScripts(projectDir, packageManager, config);
3795
3773
  const isNext = frontend.includes("next");
3796
3774
  const isNuxt = frontend.includes("nuxt");
3797
3775
  const isSvelte = frontend.includes("svelte");
@@ -3880,7 +3858,9 @@ async function setupCatalogs(projectDir, options) {
3880
3858
  "packages/db",
3881
3859
  "packages/auth",
3882
3860
  "packages/backend",
3883
- "packages/config"
3861
+ "packages/config",
3862
+ "packages/env",
3863
+ "packages/infra"
3884
3864
  ];
3885
3865
  const packagesInfo = [];
3886
3866
  for (const pkgPath of packagePaths) {
@@ -4298,7 +4278,7 @@ async function setupBackendDependencies(config) {
4298
4278
  //#region src/utils/better-auth-plugin-setup.ts
4299
4279
  async function setupBetterAuthPlugins(projectDir, config) {
4300
4280
  const authIndexPath = `${projectDir}/packages/auth/src/index.ts`;
4301
- const authIndexFile = tsProject$1.addSourceFileAtPath(authIndexPath);
4281
+ const authIndexFile = tsProject.addSourceFileAtPath(authIndexPath);
4302
4282
  if (!authIndexFile) return;
4303
4283
  const pluginsToAdd = [];
4304
4284
  const importsToAdd = [];
@@ -4570,11 +4550,6 @@ async function setupEnvironmentVariables(config) {
4570
4550
  value: backend === "convex" ? "https://<YOUR_CONVEX_URL>" : baseVar.value,
4571
4551
  condition: backend === "convex" ? true : baseVar.write
4572
4552
  }];
4573
- if (hasNextJs) clientVars.push({
4574
- key: "PORT",
4575
- value: "3001",
4576
- condition: true
4577
- });
4578
4553
  if (backend === "convex" && auth === "clerk") {
4579
4554
  if (hasNextJs) clientVars.push({
4580
4555
  key: "NEXT_PUBLIC_CLERK_FRONTEND_API_URL",
@@ -4710,7 +4685,7 @@ ${hasWebFrontend$1 ? "# npx convex env set SITE_URL http://localhost:3001\n" : "
4710
4685
  databaseUrl = "mongodb://localhost:27017/mydatabase";
4711
4686
  break;
4712
4687
  case "sqlite":
4713
- if (config.runtime === "workers" || webDeploy === "alchemy" || serverDeploy === "alchemy") databaseUrl = "http://127.0.0.1:8080";
4688
+ if (config.runtime === "workers" || webDeploy === "cloudflare" || serverDeploy === "cloudflare") databaseUrl = "http://127.0.0.1:8080";
4714
4689
  else {
4715
4690
  const dbAppDir = backend === "self" ? "apps/web" : "apps/server";
4716
4691
  databaseUrl = `file:${path.join(config.projectDir, dbAppDir, "local.db")}`;
@@ -4758,33 +4733,13 @@ ${hasWebFrontend$1 ? "# npx convex env set SITE_URL http://localhost:3001\n" : "
4758
4733
  const webDir = path.join(projectDir, "apps/web");
4759
4734
  if (await fs.pathExists(webDir)) await addEnvVariablesToFile(path.join(webDir, ".env"), serverVars);
4760
4735
  } else if (await fs.pathExists(serverDir)) await addEnvVariablesToFile(path.join(serverDir, ".env"), serverVars);
4761
- const isUnifiedAlchemy = webDeploy === "alchemy" && serverDeploy === "alchemy";
4762
- const isIndividualAlchemy = webDeploy === "alchemy" || serverDeploy === "alchemy";
4763
- if (isUnifiedAlchemy) await addEnvVariablesToFile(path.join(projectDir, ".env"), [{
4764
- key: "ALCHEMY_PASSWORD",
4765
- value: "please-change-this",
4766
- condition: true
4767
- }]);
4768
- else if (isIndividualAlchemy) {
4769
- if (webDeploy === "alchemy") {
4770
- const webDir = path.join(projectDir, "apps/web");
4771
- if (await fs.pathExists(webDir)) await addEnvVariablesToFile(path.join(webDir, ".env"), [{
4772
- key: "ALCHEMY_PASSWORD",
4773
- value: "please-change-this",
4774
- condition: true
4775
- }]);
4776
- }
4777
- if (serverDeploy === "alchemy") {
4778
- const serverAlchemyVars = [{
4779
- key: "ALCHEMY_PASSWORD",
4780
- value: "please-change-this",
4781
- condition: true
4782
- }];
4783
- if (backend === "self") {
4784
- const webDir = path.join(projectDir, "apps/web");
4785
- if (await fs.pathExists(webDir)) await addEnvVariablesToFile(path.join(webDir, ".env"), serverAlchemyVars);
4786
- } else await addEnvVariablesToFile(path.join(serverDir, ".env"), serverAlchemyVars);
4787
- }
4736
+ if (webDeploy === "cloudflare" && serverDeploy === "cloudflare" || webDeploy === "cloudflare" || serverDeploy === "cloudflare") {
4737
+ const infraDir = path.join(projectDir, "packages/infra");
4738
+ if (await fs.pathExists(infraDir)) await addEnvVariablesToFile(path.join(infraDir, ".env"), [{
4739
+ key: "ALCHEMY_PASSWORD",
4740
+ value: "please-change-this",
4741
+ condition: true
4742
+ }]);
4788
4743
  }
4789
4744
  }
4790
4745
 
@@ -4792,7 +4747,7 @@ ${hasWebFrontend$1 ? "# npx convex env set SITE_URL http://localhost:3001\n" : "
4792
4747
  //#region src/helpers/database-providers/d1-setup.ts
4793
4748
  async function setupCloudflareD1(config) {
4794
4749
  const { projectDir, serverDeploy, orm, backend } = config;
4795
- if (serverDeploy === "alchemy" && orm === "prisma") {
4750
+ if (serverDeploy === "cloudflare" && orm === "prisma") {
4796
4751
  const targetApp2 = backend === "self" ? "apps/web" : "apps/server";
4797
4752
  await addEnvVariablesToFile(path.join(projectDir, targetApp2, ".env"), [{
4798
4753
  key: "DATABASE_URL",
@@ -4838,8 +4793,8 @@ function getDatabaseUrl(database, projectName) {
4838
4793
  //#region src/utils/command-exists.ts
4839
4794
  async function commandExists(command) {
4840
4795
  try {
4841
- if (process.platform === "win32") return (await execa("where", [command])).exitCode === 0;
4842
- return (await execa("which", [command])).exitCode === 0;
4796
+ if (process.platform === "win32") return (await $({ reject: false })`where ${command}`).exitCode === 0;
4797
+ return (await $({ reject: false })`which ${command}`).exitCode === 0;
4843
4798
  } catch {
4844
4799
  return false;
4845
4800
  }
@@ -4866,11 +4821,10 @@ async function initMongoDBAtlas(serverDir) {
4866
4821
  return null;
4867
4822
  }
4868
4823
  log.info("Running MongoDB Atlas setup...");
4869
- await execa("atlas", ["deployments", "setup"], {
4824
+ await $({
4870
4825
  cwd: serverDir,
4871
- shell: true,
4872
4826
  stdio: "inherit"
4873
- });
4827
+ })`atlas deployments setup`;
4874
4828
  log.success("MongoDB Atlas deployment ready");
4875
4829
  const connectionString = await text({
4876
4830
  message: "Enter your MongoDB connection string:",
@@ -5008,9 +4962,9 @@ const NEON_REGIONS = [
5008
4962
  async function executeNeonCommand(packageManager, commandArgsString, spinnerText) {
5009
4963
  const s = spinner();
5010
4964
  try {
5011
- const fullCommand = getPackageExecutionCommand(packageManager, commandArgsString);
4965
+ const args = getPackageExecutionArgs(packageManager, commandArgsString);
5012
4966
  if (spinnerText) s.start(spinnerText);
5013
- const result = await execa(fullCommand, { shell: true });
4967
+ const result = await $`${args}`;
5014
4968
  if (spinnerText) s.stop(pc.green(spinnerText.replace("...", "").replace("ing ", "ed ").trim()));
5015
4969
  return result;
5016
4970
  } catch (error) {
@@ -5055,10 +5009,8 @@ async function setupWithNeonDb(projectDir, packageManager, backend) {
5055
5009
  const targetApp = backend === "self" ? "apps/web" : "apps/server";
5056
5010
  const targetDir = path.join(projectDir, targetApp);
5057
5011
  await fs.ensureDir(targetDir);
5058
- await execa(getPackageExecutionCommand(packageManager, "get-db@latest --yes"), {
5059
- shell: true,
5060
- cwd: targetDir
5061
- });
5012
+ const packageArgs = getPackageExecutionArgs(packageManager, "get-db@latest --yes");
5013
+ await $({ cwd: targetDir })`${packageArgs}`;
5062
5014
  s.stop(pc.green("Neon database created successfully!"));
5063
5015
  return true;
5064
5016
  } catch (error) {
@@ -5244,13 +5196,10 @@ async function setupWithCreateDb(serverDir, packageManager) {
5244
5196
  initialValue: "ap-southeast-1"
5245
5197
  });
5246
5198
  if (isCancel(selectedRegion)) return null;
5247
- const createDbCommand = getPackageExecutionCommand(packageManager, `create-db@latest --json --region ${selectedRegion}`);
5199
+ const createDbArgs = getPackageExecutionArgs(packageManager, `create-db@latest --json --region ${selectedRegion}`);
5248
5200
  const s = spinner();
5249
5201
  s.start("Creating Prisma Postgres database...");
5250
- const { stdout } = await execa(createDbCommand, {
5251
- cwd: serverDir,
5252
- shell: true
5253
- });
5202
+ const { stdout } = await $({ cwd: serverDir })`${createDbArgs}`;
5254
5203
  s.stop("Database created successfully!");
5255
5204
  let createDbResponse;
5256
5205
  try {
@@ -5377,11 +5326,11 @@ function extractDbUrl(output) {
5377
5326
  async function initializeSupabase(serverDir, packageManager) {
5378
5327
  log.info("Initializing Supabase project...");
5379
5328
  try {
5380
- await execa(getPackageExecutionCommand(packageManager, "supabase init"), {
5329
+ const supabaseInitArgs = getPackageExecutionArgs(packageManager, "supabase init");
5330
+ await $({
5381
5331
  cwd: serverDir,
5382
- stdio: "inherit",
5383
- shell: true
5384
- });
5332
+ stdio: "inherit"
5333
+ })`${supabaseInitArgs}`;
5385
5334
  log.success("Supabase project initialized");
5386
5335
  return true;
5387
5336
  } catch (error) {
@@ -5397,12 +5346,9 @@ async function initializeSupabase(serverDir, packageManager) {
5397
5346
  }
5398
5347
  async function startSupabase(serverDir, packageManager) {
5399
5348
  log.info("Starting Supabase services (this may take a moment)...");
5400
- const supabaseStartCommand = getPackageExecutionCommand(packageManager, "supabase start");
5349
+ const supabaseStartArgs = getPackageExecutionArgs(packageManager, "supabase start");
5401
5350
  try {
5402
- const subprocess = execa(supabaseStartCommand, {
5403
- cwd: serverDir,
5404
- shell: true
5405
- });
5351
+ const subprocess = execa(supabaseStartArgs[0], supabaseStartArgs.slice(1), { cwd: serverDir });
5406
5352
  let stdoutData = "";
5407
5353
  if (subprocess.stdout) subprocess.stdout.on("data", (data) => {
5408
5354
  const text$1 = data.toString();
@@ -6200,15 +6146,35 @@ BETTER_AUTH_URL={your-production-server-domain}
6200
6146
  }
6201
6147
  function generateDeploymentCommands(packageManagerRunCmd, webDeploy, serverDeploy) {
6202
6148
  const lines = [];
6203
- if (webDeploy === "alchemy" || serverDeploy === "alchemy") {
6204
- lines.push("## Deployment (Alchemy)");
6205
- if (webDeploy === "alchemy" && serverDeploy !== "alchemy") lines.push(`- Web dev: cd apps/web && ${packageManagerRunCmd} dev`, `- Web deploy: cd apps/web && ${packageManagerRunCmd} deploy`, `- Web destroy: cd apps/web && ${packageManagerRunCmd} destroy`);
6206
- if (serverDeploy === "alchemy" && webDeploy !== "alchemy") lines.push(`- Server dev: cd apps/server && ${packageManagerRunCmd} dev`, `- Server deploy: cd apps/server && ${packageManagerRunCmd} deploy`, `- Server destroy: cd apps/server && ${packageManagerRunCmd} destroy`);
6207
- if (webDeploy === "alchemy" && serverDeploy === "alchemy") lines.push(`- Dev: ${packageManagerRunCmd} dev`, `- Deploy: ${packageManagerRunCmd} deploy`, `- Destroy: ${packageManagerRunCmd} destroy`);
6149
+ if (webDeploy === "cloudflare" || serverDeploy === "cloudflare") {
6150
+ lines.push("## Deployment (Cloudflare via Alchemy)");
6151
+ if (webDeploy === "cloudflare" && serverDeploy !== "cloudflare") lines.push(`- Web dev: cd apps/web && ${packageManagerRunCmd} dev`, `- Web deploy: cd apps/web && ${packageManagerRunCmd} deploy`, `- Web destroy: cd apps/web && ${packageManagerRunCmd} destroy`);
6152
+ if (serverDeploy === "cloudflare" && webDeploy !== "cloudflare") lines.push(`- Server dev: cd apps/server && ${packageManagerRunCmd} dev`, `- Server deploy: cd apps/server && ${packageManagerRunCmd} deploy`, `- Server destroy: cd apps/server && ${packageManagerRunCmd} destroy`);
6153
+ if (webDeploy === "cloudflare" && serverDeploy === "cloudflare") lines.push(`- Dev: ${packageManagerRunCmd} dev`, `- Deploy: ${packageManagerRunCmd} deploy`, `- Destroy: ${packageManagerRunCmd} destroy`);
6208
6154
  }
6209
6155
  return lines.length ? `\n${lines.join("\n")}\n` : "";
6210
6156
  }
6211
6157
 
6158
+ //#endregion
6159
+ //#region src/helpers/core/env-package-setup.ts
6160
+ async function setupEnvPackageDependencies(projectDir, options) {
6161
+ const envDir = path.join(projectDir, "packages/env");
6162
+ if (!await fs.pathExists(envDir)) return;
6163
+ await addPackageDependency({
6164
+ dependencies: getT3EnvDeps(options),
6165
+ projectDir: envDir
6166
+ });
6167
+ }
6168
+ function getT3EnvDeps(options) {
6169
+ const deps = ["zod"];
6170
+ const { frontend, backend, runtime } = options;
6171
+ if (frontend.includes("next")) deps.push("@t3-oss/env-nextjs");
6172
+ else if (frontend.includes("nuxt")) deps.push("@t3-oss/env-nuxt");
6173
+ else deps.push("@t3-oss/env-core");
6174
+ if (backend !== "convex" && backend !== "none" && runtime !== "workers" && !deps.includes("@t3-oss/env-core")) deps.push("@t3-oss/env-core");
6175
+ return deps;
6176
+ }
6177
+
6212
6178
  //#endregion
6213
6179
  //#region src/helpers/core/git.ts
6214
6180
  async function initializeGit(projectDir, useGit) {
@@ -6231,6 +6197,17 @@ async function initializeGit(projectDir, useGit) {
6231
6197
  await $({ cwd: projectDir })`git commit -m ${"initial commit"}`;
6232
6198
  }
6233
6199
 
6200
+ //#endregion
6201
+ //#region src/helpers/core/infra-package-setup.ts
6202
+ async function setupInfraPackageDependencies(projectDir, _options) {
6203
+ const infraDir = path.join(projectDir, "packages/infra");
6204
+ if (!await fs.pathExists(infraDir)) return;
6205
+ await addPackageDependency({
6206
+ devDependencies: ["alchemy"],
6207
+ projectDir: infraDir
6208
+ });
6209
+ }
6210
+
6234
6211
  //#endregion
6235
6212
  //#region src/helpers/core/payments-setup.ts
6236
6213
  async function setupPayments(config) {
@@ -6417,7 +6394,7 @@ async function getDatabaseInstructions(database, orm, runCmd, _runtime, dbSetup,
6417
6394
  instructions.push("");
6418
6395
  }
6419
6396
  }
6420
- if (dbSetup === "d1" && serverDeploy === "alchemy") {
6397
+ if (dbSetup === "d1" && serverDeploy === "cloudflare") {
6421
6398
  if (orm === "drizzle") instructions.push(`${pc.cyan("•")} Generate migrations: ${`${runCmd} db:generate`}`);
6422
6399
  else if (orm === "prisma") {
6423
6400
  instructions.push(`${pc.cyan("•")} Generate Prisma client: ${`${runCmd} db:generate`}`);
@@ -6432,15 +6409,15 @@ async function getDatabaseInstructions(database, orm, runCmd, _runtime, dbSetup,
6432
6409
  if (orm === "prisma") {
6433
6410
  if (database === "mongodb" && dbSetup === "docker") instructions.push(`${pc.yellow("WARNING:")} Prisma + MongoDB + Docker combination\n may not work.`);
6434
6411
  if (dbSetup === "docker") instructions.push(`${pc.cyan("•")} Start docker container: ${`${runCmd} db:start`}`);
6435
- if (!(dbSetup === "d1" && serverDeploy === "alchemy")) {
6412
+ if (!(dbSetup === "d1" && serverDeploy === "cloudflare")) {
6436
6413
  instructions.push(`${pc.cyan("•")} Generate Prisma Client: ${`${runCmd} db:generate`}`);
6437
6414
  instructions.push(`${pc.cyan("•")} Apply schema: ${`${runCmd} db:push`}`);
6438
6415
  }
6439
- if (!(dbSetup === "d1" && serverDeploy === "alchemy")) instructions.push(`${pc.cyan("•")} Database UI: ${`${runCmd} db:studio`}`);
6416
+ if (!(dbSetup === "d1" && serverDeploy === "cloudflare")) instructions.push(`${pc.cyan("•")} Database UI: ${`${runCmd} db:studio`}`);
6440
6417
  } else if (orm === "drizzle") {
6441
6418
  if (dbSetup === "docker") instructions.push(`${pc.cyan("•")} Start docker container: ${`${runCmd} db:start`}`);
6442
6419
  if (dbSetup !== "d1") instructions.push(`${pc.cyan("•")} Apply schema: ${`${runCmd} db:push`}`);
6443
- if (!(dbSetup === "d1" && serverDeploy === "alchemy")) instructions.push(`${pc.cyan("•")} Database UI: ${`${runCmd} db:studio`}`);
6420
+ if (!(dbSetup === "d1" && serverDeploy === "cloudflare")) instructions.push(`${pc.cyan("•")} Database UI: ${`${runCmd} db:studio`}`);
6444
6421
  } else if (orm === "mongoose") {
6445
6422
  if (dbSetup === "docker") instructions.push(`${pc.cyan("•")} Start docker container: ${`${runCmd} db:start`}`);
6446
6423
  } else if (orm === "none") instructions.push(`${pc.yellow("NOTE:")} Manual database schema setup\n required.`);
@@ -6471,116 +6448,174 @@ function getPolarInstructions(backend) {
6471
6448
  function getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy, backend) {
6472
6449
  const instructions = [];
6473
6450
  const isBackendSelf = backend === "self";
6474
- if (webDeploy === "alchemy" && serverDeploy !== "alchemy") instructions.push(`${pc.bold("Deploy web with Alchemy:")}\n${pc.cyan("•")} Dev: ${`cd apps/web && ${runCmd} alchemy dev`}\n${pc.cyan("•")} Deploy: ${`cd apps/web && ${runCmd} deploy`}\n${pc.cyan("•")} Destroy: ${`cd apps/web && ${runCmd} destroy`}`);
6475
- else if (serverDeploy === "alchemy" && webDeploy !== "alchemy" && !isBackendSelf) instructions.push(`${pc.bold("Deploy server with Alchemy:")}\n${pc.cyan("•")} Dev: ${`cd apps/server && ${runCmd} dev`}\n${pc.cyan("•")} Deploy: ${`cd apps/server && ${runCmd} deploy`}\n${pc.cyan("•")} Destroy: ${`cd apps/server && ${runCmd} destroy`}`);
6476
- else if (webDeploy === "alchemy" && (serverDeploy === "alchemy" || isBackendSelf)) instructions.push(`${pc.bold("Deploy with Alchemy:")}\n${pc.cyan("•")} Dev: ${`${runCmd} dev`}\n${pc.cyan("•")} Deploy: ${`${runCmd} deploy`}\n${pc.cyan("•")} Destroy: ${`${runCmd} destroy`}`);
6451
+ if (webDeploy === "cloudflare" && serverDeploy !== "cloudflare") instructions.push(`${pc.bold("Deploy web with Alchemy:")}\n${pc.cyan("•")} Dev: ${`cd apps/web && ${runCmd} alchemy dev`}\n${pc.cyan("•")} Deploy: ${`cd apps/web && ${runCmd} deploy`}\n${pc.cyan("•")} Destroy: ${`cd apps/web && ${runCmd} destroy`}`);
6452
+ else if (serverDeploy === "cloudflare" && webDeploy !== "cloudflare" && !isBackendSelf) instructions.push(`${pc.bold("Deploy server with Alchemy:")}\n${pc.cyan("•")} Dev: ${`cd apps/server && ${runCmd} dev`}\n${pc.cyan("•")} Deploy: ${`cd apps/server && ${runCmd} deploy`}\n${pc.cyan("•")} Destroy: ${`cd apps/server && ${runCmd} destroy`}`);
6453
+ else if (webDeploy === "cloudflare" && (serverDeploy === "cloudflare" || isBackendSelf)) instructions.push(`${pc.bold("Deploy with Alchemy:")}\n${pc.cyan("•")} Dev: ${`${runCmd} dev`}\n${pc.cyan("•")} Deploy: ${`${runCmd} deploy`}\n${pc.cyan("•")} Destroy: ${`${runCmd} destroy`}`);
6477
6454
  return instructions.length ? `\n${instructions.join("\n")}` : "";
6478
6455
  }
6479
6456
 
6480
6457
  //#endregion
6481
6458
  //#region src/helpers/core/workspace-setup.ts
6482
6459
  async function setupWorkspaceDependencies(projectDir, options) {
6483
- const { projectName, packageManager, database, auth, api, runtime, backend } = options;
6460
+ const { projectName, packageManager, runtime, backend } = options;
6484
6461
  const workspaceVersion = packageManager === "npm" ? "*" : "workspace:*";
6485
- const commonDeps = ["dotenv", "zod"];
6486
- const commonDevDeps = ["typescript"];
6487
- const configDir = path.join(projectDir, "packages/config");
6488
- const dbDir = path.join(projectDir, "packages/db");
6489
- const authDir = path.join(projectDir, "packages/auth");
6490
- const apiDir = path.join(projectDir, "packages/api");
6491
- const backendDir = path.join(projectDir, "packages/backend");
6492
- const serverDir = path.join(projectDir, "apps/server");
6493
- const webDir = path.join(projectDir, "apps/web");
6494
- const nativeDir = path.join(projectDir, "apps/native");
6495
- const [configExists, dbExists, authExists, apiExists, backendExists, serverExists, webExists, nativeExists] = await Promise.all([
6496
- fs.pathExists(configDir),
6497
- fs.pathExists(dbDir),
6498
- fs.pathExists(authDir),
6499
- fs.pathExists(apiDir),
6500
- fs.pathExists(backendDir),
6501
- fs.pathExists(serverDir),
6502
- fs.pathExists(webDir),
6503
- fs.pathExists(nativeDir)
6462
+ const packages = await detectPackages(projectDir);
6463
+ const configDep = packages.config.exists ? { [`@${projectName}/config`]: workspaceVersion } : {};
6464
+ const envDep = packages.env.exists ? { [`@${projectName}/env`]: workspaceVersion } : {};
6465
+ const ctx = {
6466
+ projectName,
6467
+ workspaceVersion,
6468
+ options,
6469
+ commonDeps: ["dotenv", "zod"],
6470
+ commonDevDeps: ["typescript"],
6471
+ configDep,
6472
+ envDep
6473
+ };
6474
+ await Promise.all([
6475
+ setupEnvPackage(packages.env, packages.infra, ctx),
6476
+ setupInfraPackage(packages.infra, ctx),
6477
+ setupDbPackage(packages.db, ctx),
6478
+ setupAuthPackage(packages.auth, packages.db, ctx),
6479
+ setupApiPackage(packages.api, packages.auth, packages.db, ctx),
6480
+ setupBackendPackage(packages.backend, ctx),
6481
+ setupServerPackage(packages.server, packages.api, packages.auth, packages.db, ctx),
6482
+ setupWebPackage(packages.web, packages.api, packages.auth, packages.backend, ctx),
6483
+ setupNativePackage(packages.native, packages.api, packages.backend, ctx)
6504
6484
  ]);
6505
- const configDep = configExists ? { [`@${projectName}/config`]: workspaceVersion } : {};
6506
- if (dbExists) await addPackageDependency({
6507
- dependencies: commonDeps,
6508
- devDependencies: commonDevDeps,
6485
+ const runtimeDevDeps = getRuntimeDevDeps(runtime, backend);
6486
+ await addPackageDependency({
6487
+ dependencies: ctx.commonDeps,
6488
+ devDependencies: [...ctx.commonDevDeps, ...runtimeDevDeps],
6489
+ customDependencies: envDep,
6509
6490
  customDevDependencies: configDep,
6510
- projectDir: dbDir
6491
+ projectDir
6511
6492
  });
6512
- if (authExists) {
6513
- const authDeps = {};
6514
- if (database !== "none" && dbExists) authDeps[`@${projectName}/db`] = workspaceVersion;
6515
- await addPackageDependency({
6516
- dependencies: commonDeps,
6517
- devDependencies: commonDevDeps,
6518
- customDependencies: authDeps,
6519
- customDevDependencies: configDep,
6520
- projectDir: authDir
6521
- });
6522
- }
6523
- if (apiExists) {
6524
- const apiDeps = {};
6525
- if (auth !== "none" && authExists) apiDeps[`@${projectName}/auth`] = workspaceVersion;
6526
- if (database !== "none" && dbExists) apiDeps[`@${projectName}/db`] = workspaceVersion;
6527
- await addPackageDependency({
6528
- dependencies: commonDeps,
6529
- devDependencies: commonDevDeps,
6530
- customDependencies: apiDeps,
6531
- customDevDependencies: configDep,
6532
- projectDir: apiDir
6533
- });
6534
- }
6535
- if (backendExists) await addPackageDependency({
6536
- dependencies: commonDeps,
6537
- devDependencies: commonDevDeps,
6538
- customDevDependencies: configDep,
6539
- projectDir: backendDir
6493
+ }
6494
+ async function detectPackages(projectDir) {
6495
+ const entries = await Promise.all(Object.entries({
6496
+ config: "packages/config",
6497
+ env: "packages/env",
6498
+ infra: "packages/infra",
6499
+ db: "packages/db",
6500
+ auth: "packages/auth",
6501
+ api: "packages/api",
6502
+ backend: "packages/backend",
6503
+ server: "apps/server",
6504
+ web: "apps/web",
6505
+ native: "apps/native"
6506
+ }).map(async ([name, relativePath]) => {
6507
+ const dir = path.join(projectDir, relativePath);
6508
+ return [name, {
6509
+ dir,
6510
+ exists: await fs.pathExists(dir)
6511
+ }];
6512
+ }));
6513
+ return Object.fromEntries(entries);
6514
+ }
6515
+ async function setupEnvPackage(pkg, infraPkg, ctx) {
6516
+ if (!pkg.exists) return;
6517
+ const runtimeDevDeps = getRuntimeDevDeps(ctx.options.runtime, ctx.options.backend);
6518
+ const customDevDeps = { ...ctx.configDep };
6519
+ if ((ctx.options.serverDeploy === "cloudflare" || ctx.options.webDeploy === "cloudflare") && infraPkg.exists) customDevDeps[`@${ctx.projectName}/infra`] = ctx.workspaceVersion;
6520
+ await addPackageDependency({
6521
+ dependencies: ctx.commonDeps,
6522
+ devDependencies: [...ctx.commonDevDeps, ...runtimeDevDeps],
6523
+ customDevDependencies: customDevDeps,
6524
+ projectDir: pkg.dir
6540
6525
  });
6541
- if (serverExists) {
6542
- const serverDeps = {};
6543
- if (api !== "none" && apiExists) serverDeps[`@${projectName}/api`] = workspaceVersion;
6544
- if (auth !== "none" && authExists) serverDeps[`@${projectName}/auth`] = workspaceVersion;
6545
- if (database !== "none" && dbExists) serverDeps[`@${projectName}/db`] = workspaceVersion;
6546
- await addPackageDependency({
6547
- dependencies: commonDeps,
6548
- devDependencies: [...commonDevDeps, "tsdown"],
6549
- customDependencies: serverDeps,
6550
- customDevDependencies: configDep,
6551
- projectDir: serverDir
6552
- });
6553
- }
6554
- if (webExists) {
6555
- const webDeps = {};
6556
- if (api !== "none" && apiExists) webDeps[`@${projectName}/api`] = workspaceVersion;
6557
- if (auth !== "none" && authExists) webDeps[`@${projectName}/auth`] = workspaceVersion;
6558
- if (backend === "convex" && backendExists) webDeps[`@${projectName}/backend`] = workspaceVersion;
6559
- await addPackageDependency({
6560
- dependencies: commonDeps,
6561
- devDependencies: commonDevDeps,
6562
- customDependencies: webDeps,
6563
- customDevDependencies: configDep,
6564
- projectDir: webDir
6565
- });
6566
- }
6567
- if (nativeExists) {
6568
- const nativeDeps = {};
6569
- if (api !== "none" && apiExists) nativeDeps[`@${projectName}/api`] = workspaceVersion;
6570
- if (backend === "convex" && backendExists) nativeDeps[`@${projectName}/backend`] = workspaceVersion;
6571
- await addPackageDependency({
6572
- dependencies: commonDeps,
6573
- devDependencies: commonDevDeps,
6574
- customDependencies: nativeDeps,
6575
- customDevDependencies: configDep,
6576
- projectDir: nativeDir
6577
- });
6578
- }
6579
- const runtimeDevDeps = getRuntimeDevDeps(runtime, backend);
6526
+ }
6527
+ async function setupInfraPackage(pkg, ctx) {
6528
+ if (!pkg.exists) return;
6580
6529
  await addPackageDependency({
6581
- dependencies: commonDeps,
6582
- devDependencies: [...commonDevDeps, ...runtimeDevDeps],
6583
- projectDir
6530
+ dependencies: ctx.commonDeps,
6531
+ devDependencies: ctx.commonDevDeps,
6532
+ customDevDependencies: ctx.configDep,
6533
+ projectDir: pkg.dir
6534
+ });
6535
+ }
6536
+ async function setupDbPackage(pkg, ctx) {
6537
+ if (!pkg.exists) return;
6538
+ await addPackageDependency({
6539
+ dependencies: ctx.commonDeps,
6540
+ devDependencies: ctx.commonDevDeps,
6541
+ customDependencies: ctx.envDep,
6542
+ customDevDependencies: ctx.configDep,
6543
+ projectDir: pkg.dir
6544
+ });
6545
+ }
6546
+ async function setupAuthPackage(pkg, dbPkg, ctx) {
6547
+ if (!pkg.exists) return;
6548
+ const deps = { ...ctx.envDep };
6549
+ if (ctx.options.database !== "none" && dbPkg.exists) deps[`@${ctx.projectName}/db`] = ctx.workspaceVersion;
6550
+ await addPackageDependency({
6551
+ dependencies: ctx.commonDeps,
6552
+ devDependencies: ctx.commonDevDeps,
6553
+ customDependencies: deps,
6554
+ customDevDependencies: ctx.configDep,
6555
+ projectDir: pkg.dir
6556
+ });
6557
+ }
6558
+ async function setupApiPackage(pkg, authPkg, dbPkg, ctx) {
6559
+ if (!pkg.exists) return;
6560
+ const deps = { ...ctx.envDep };
6561
+ if (ctx.options.auth !== "none" && authPkg.exists) deps[`@${ctx.projectName}/auth`] = ctx.workspaceVersion;
6562
+ if (ctx.options.database !== "none" && dbPkg.exists) deps[`@${ctx.projectName}/db`] = ctx.workspaceVersion;
6563
+ await addPackageDependency({
6564
+ dependencies: ctx.commonDeps,
6565
+ devDependencies: ctx.commonDevDeps,
6566
+ customDependencies: deps,
6567
+ customDevDependencies: ctx.configDep,
6568
+ projectDir: pkg.dir
6569
+ });
6570
+ }
6571
+ async function setupBackendPackage(pkg, ctx) {
6572
+ if (!pkg.exists) return;
6573
+ await addPackageDependency({
6574
+ dependencies: ctx.commonDeps,
6575
+ devDependencies: ctx.commonDevDeps,
6576
+ customDevDependencies: ctx.configDep,
6577
+ projectDir: pkg.dir
6578
+ });
6579
+ }
6580
+ async function setupServerPackage(pkg, apiPkg, authPkg, dbPkg, ctx) {
6581
+ if (!pkg.exists) return;
6582
+ const deps = { ...ctx.envDep };
6583
+ if (ctx.options.api !== "none" && apiPkg.exists) deps[`@${ctx.projectName}/api`] = ctx.workspaceVersion;
6584
+ if (ctx.options.auth !== "none" && authPkg.exists) deps[`@${ctx.projectName}/auth`] = ctx.workspaceVersion;
6585
+ if (ctx.options.database !== "none" && dbPkg.exists) deps[`@${ctx.projectName}/db`] = ctx.workspaceVersion;
6586
+ await addPackageDependency({
6587
+ dependencies: ctx.commonDeps,
6588
+ devDependencies: [...ctx.commonDevDeps, "tsdown"],
6589
+ customDependencies: deps,
6590
+ customDevDependencies: ctx.configDep,
6591
+ projectDir: pkg.dir
6592
+ });
6593
+ }
6594
+ async function setupWebPackage(pkg, apiPkg, authPkg, backendPkg, ctx) {
6595
+ if (!pkg.exists) return;
6596
+ const deps = { ...ctx.envDep };
6597
+ if (ctx.options.api !== "none" && apiPkg.exists) deps[`@${ctx.projectName}/api`] = ctx.workspaceVersion;
6598
+ if (ctx.options.auth !== "none" && authPkg.exists) deps[`@${ctx.projectName}/auth`] = ctx.workspaceVersion;
6599
+ if (ctx.options.backend === "convex" && backendPkg.exists) deps[`@${ctx.projectName}/backend`] = ctx.workspaceVersion;
6600
+ await addPackageDependency({
6601
+ dependencies: ctx.commonDeps,
6602
+ devDependencies: ctx.commonDevDeps,
6603
+ customDependencies: deps,
6604
+ customDevDependencies: ctx.configDep,
6605
+ projectDir: pkg.dir
6606
+ });
6607
+ }
6608
+ async function setupNativePackage(pkg, apiPkg, backendPkg, ctx) {
6609
+ if (!pkg.exists) return;
6610
+ const deps = { ...ctx.envDep };
6611
+ if (ctx.options.api !== "none" && apiPkg.exists) deps[`@${ctx.projectName}/api`] = ctx.workspaceVersion;
6612
+ if (ctx.options.backend === "convex" && backendPkg.exists) deps[`@${ctx.projectName}/backend`] = ctx.workspaceVersion;
6613
+ await addPackageDependency({
6614
+ dependencies: ctx.commonDeps,
6615
+ devDependencies: ctx.commonDevDeps,
6616
+ customDependencies: deps,
6617
+ customDevDependencies: ctx.configDep,
6618
+ projectDir: pkg.dir
6584
6619
  });
6585
6620
  }
6586
6621
  function getRuntimeDevDeps(runtime, backend) {
@@ -6617,7 +6652,7 @@ async function updateRootPackageJson(projectDir, options) {
6617
6652
  const dbPackageName = `@${projectName}/db`;
6618
6653
  const hasTurborepo = addons.includes("turborepo");
6619
6654
  const needsDbScripts = backend !== "convex" && database !== "none" && orm !== "none" && orm !== "mongoose";
6620
- const isD1Alchemy = dbSetup === "d1" && serverDeploy === "alchemy";
6655
+ const isD1Alchemy = dbSetup === "d1" && serverDeploy === "cloudflare";
6621
6656
  const pmConfig = getPackageManagerConfig(packageManager, hasTurborepo);
6622
6657
  scripts.dev = pmConfig.dev;
6623
6658
  scripts.build = pmConfig.build;
@@ -6645,7 +6680,7 @@ async function updateRootPackageJson(projectDir, options) {
6645
6680
  scripts["db:down"] = pmConfig.filter(dbPackageName, "db:down");
6646
6681
  }
6647
6682
  try {
6648
- const { stdout } = await execa(packageManager, ["-v"], { cwd: projectDir });
6683
+ const { stdout } = await $`${packageManager} -v`;
6649
6684
  packageJson.packageManager = `${packageManager}@${stdout.trim()}`;
6650
6685
  } catch {
6651
6686
  log.warn(`Could not determine ${packageManager} version.`);
@@ -6702,7 +6737,7 @@ async function updateDbPackageJson(projectDir, options) {
6702
6737
  dbPackageJson.scripts = dbPackageJson.scripts || {};
6703
6738
  const scripts = dbPackageJson.scripts;
6704
6739
  const { database, orm, dbSetup, serverDeploy } = options;
6705
- const isD1Alchemy = dbSetup === "d1" && serverDeploy === "alchemy";
6740
+ const isD1Alchemy = dbSetup === "d1" && serverDeploy === "cloudflare";
6706
6741
  if (database !== "none") {
6707
6742
  if (database === "sqlite" && dbSetup !== "d1") scripts["db:local"] = "turso dev --db-file local.db";
6708
6743
  if (orm === "prisma") {
@@ -6752,7 +6787,8 @@ async function updateConvexPackageJson(projectDir, options) {
6752
6787
 
6753
6788
  //#endregion
6754
6789
  //#region src/helpers/core/create-project.ts
6755
- async function createProject(options, cliInput) {
6790
+ async function createProject(options, cliInput = {}) {
6791
+ const { silent = false } = cliInput;
6756
6792
  const projectDir = options.projectDir;
6757
6793
  const isConvex = options.backend === "convex";
6758
6794
  const isSelfBackend = options.backend === "self";
@@ -6768,6 +6804,8 @@ async function createProject(options, cliInput) {
6768
6804
  if (options.examples.length > 0 && options.examples[0] !== "none") await setupExamplesTemplate(projectDir, options);
6769
6805
  await setupAddonsTemplate(projectDir, options);
6770
6806
  await setupDeploymentTemplates(projectDir, options);
6807
+ await setupEnvPackageDependencies(projectDir, options);
6808
+ if (options.serverDeploy === "cloudflare" || options.webDeploy === "cloudflare") await setupInfraPackageDependencies(projectDir, options);
6771
6809
  await setupApi(options);
6772
6810
  if (isConvex || needsServerSetup) await setupBackendDependencies(options);
6773
6811
  if (!isConvex) {
@@ -6786,23 +6824,23 @@ async function createProject(options, cliInput) {
6786
6824
  await setupCatalogs(projectDir, options);
6787
6825
  await createReadme(projectDir, options);
6788
6826
  await writeBtsConfig(options);
6789
- log.success("Project template successfully scaffolded!");
6827
+ if (!silent) log.success("Project template successfully scaffolded!");
6790
6828
  if (options.install) await installDependencies({
6791
6829
  projectDir,
6792
6830
  packageManager: options.packageManager
6793
6831
  });
6794
6832
  await initializeGit(projectDir, options.git);
6795
- await displayPostInstallInstructions({
6833
+ if (!silent) await displayPostInstallInstructions({
6796
6834
  ...options,
6797
6835
  depsInstalled: options.install
6798
6836
  });
6799
6837
  return projectDir;
6800
6838
  } catch (error) {
6801
6839
  if (error instanceof Error) {
6802
- console.error(error.stack);
6840
+ if (!silent) console.error(error.stack);
6803
6841
  exitWithError(`Error during project creation: ${error.message}`);
6804
6842
  } else {
6805
- console.error(error);
6843
+ if (!silent) console.error(error);
6806
6844
  exitWithError(`An unexpected error occurred: ${String(error)}`);
6807
6845
  }
6808
6846
  }
@@ -6810,12 +6848,13 @@ async function createProject(options, cliInput) {
6810
6848
 
6811
6849
  //#endregion
6812
6850
  //#region src/helpers/core/command-handlers.ts
6813
- async function createProjectHandler(input) {
6851
+ async function createProjectHandler(input, options = {}) {
6852
+ const { silent = false } = options;
6814
6853
  const startTime = Date.now();
6815
6854
  const timeScaffolded = (/* @__PURE__ */ new Date()).toISOString();
6816
- if (input.renderTitle !== false) renderTitle();
6817
- intro(pc.magenta("Creating a new Better-T-Stack project"));
6818
- if (input.yolo) consola.fatal("YOLO mode enabled - skipping checks. Things may break!");
6855
+ if (!silent && input.renderTitle !== false) renderTitle();
6856
+ if (!silent) intro(pc.magenta("Creating a new Better-T-Stack project"));
6857
+ if (!silent && input.yolo) consola.fatal("YOLO mode enabled - skipping checks. Things may break!");
6819
6858
  let currentPathInput;
6820
6859
  if (input.yes && input.projectName) currentPathInput = input.projectName;
6821
6860
  else if (input.yes) {
@@ -6884,8 +6923,10 @@ async function createProjectHandler(input) {
6884
6923
  if (templateConfig) {
6885
6924
  const templateName = input.template.toUpperCase();
6886
6925
  const templateDescription = getTemplateDescription(input.template);
6887
- log.message(pc.bold(pc.cyan(`Using template: ${pc.white(templateName)}`)));
6888
- log.message(pc.dim(` ${templateDescription}`));
6926
+ if (!silent) {
6927
+ log.message(pc.bold(pc.cyan(`Using template: ${pc.white(templateName)}`)));
6928
+ log.message(pc.dim(` ${templateDescription}`));
6929
+ }
6889
6930
  const userOverrides = {};
6890
6931
  for (const [key, value] of Object.entries(originalInput)) if (value !== void 0) userOverrides[key] = value;
6891
6932
  cliInput = {
@@ -6907,25 +6948,32 @@ async function createProjectHandler(input) {
6907
6948
  relativePath: finalPathInput
6908
6949
  };
6909
6950
  validateConfigCompatibility(config, providedFlags, cliInput);
6910
- log.info(pc.yellow("Using default/flag options (config prompts skipped):"));
6911
- log.message(displayConfig(config));
6951
+ if (!silent) {
6952
+ log.info(pc.yellow("Using default/flag options (config prompts skipped):"));
6953
+ log.message(displayConfig(config));
6954
+ }
6912
6955
  } else {
6913
6956
  const flagConfig = processAndValidateFlags(cliInput, providedFlags, finalBaseName);
6914
6957
  const { projectName: _projectNameFromFlags, ...otherFlags } = flagConfig;
6915
- if (Object.keys(otherFlags).length > 0) {
6958
+ if (!silent && Object.keys(otherFlags).length > 0) {
6916
6959
  log.info(pc.yellow("Using these pre-selected options:"));
6917
6960
  log.message(displayConfig(otherFlags));
6918
6961
  log.message("");
6919
6962
  }
6920
6963
  config = await gatherConfig(flagConfig, finalBaseName, finalResolvedPath, finalPathInput);
6921
6964
  }
6922
- await createProject(config, { manualDb: cliInput.manualDb ?? input.manualDb });
6965
+ await createProject(config, {
6966
+ manualDb: cliInput.manualDb ?? input.manualDb,
6967
+ silent
6968
+ });
6923
6969
  const reproducibleCommand = generateReproducibleCommand(config);
6924
- log.success(pc.blue(`You can reproduce this setup with the following command:\n${reproducibleCommand}`));
6970
+ if (!silent) log.success(pc.blue(`You can reproduce this setup with the following command:\n${reproducibleCommand}`));
6925
6971
  await trackProjectCreation(config, input.disableAnalytics);
6926
6972
  const elapsedTimeMs = Date.now() - startTime;
6927
- const elapsedTimeInSeconds = (elapsedTimeMs / 1e3).toFixed(2);
6928
- outro(pc.magenta(`Project created successfully in ${pc.bold(elapsedTimeInSeconds)} seconds!`));
6973
+ if (!silent) {
6974
+ const elapsedTimeInSeconds = (elapsedTimeMs / 1e3).toFixed(2);
6975
+ outro(pc.magenta(`Project created successfully in ${pc.bold(elapsedTimeInSeconds)} seconds!`));
6976
+ }
6929
6977
  return {
6930
6978
  success: true,
6931
6979
  projectConfig: config,
@@ -7037,25 +7085,12 @@ async function addAddonsHandler(input) {
7037
7085
  //#region src/utils/open-url.ts
7038
7086
  async function openUrl(url) {
7039
7087
  const platform = process.platform;
7040
- let command;
7041
- let args = [];
7042
- if (platform === "darwin") {
7043
- command = "open";
7044
- args = [url];
7045
- } else if (platform === "win32") {
7046
- command = "cmd";
7047
- args = [
7048
- "/c",
7049
- "start",
7050
- "",
7051
- url.replace(/&/g, "^&")
7052
- ];
7053
- } else {
7054
- command = "xdg-open";
7055
- args = [url];
7056
- }
7057
7088
  try {
7058
- await execa(command, args, { stdio: "ignore" });
7089
+ if (platform === "darwin") await $({ stdio: "ignore" })`open ${url}`;
7090
+ else if (platform === "win32") {
7091
+ const escapedUrl = url.replace(/&/g, "^&");
7092
+ await $({ stdio: "ignore" })`cmd /c start "" ${escapedUrl}`;
7093
+ } else await $({ stdio: "ignore" })`xdg-open ${url}`;
7059
7094
  } catch {
7060
7095
  log.message(`Please open ${url} in your browser.`);
7061
7096
  }
@@ -7105,32 +7140,32 @@ function displaySponsorsBox(sponsors$1) {
7105
7140
  //#endregion
7106
7141
  //#region src/index.ts
7107
7142
  const router = os.router({
7108
- init: os.meta({
7143
+ create: os.meta({
7109
7144
  description: "Create a new Better-T-Stack project",
7110
7145
  default: true,
7111
7146
  negateBooleans: true
7112
- }).input(z.tuple([ProjectNameSchema.optional(), z.object({
7113
- template: TemplateSchema.optional().describe("Use a predefined template"),
7147
+ }).input(z.tuple([types_exports.ProjectNameSchema.optional(), z.object({
7148
+ template: types_exports.TemplateSchema.optional().describe("Use a predefined template"),
7114
7149
  yes: z.boolean().optional().default(false).describe("Use default configuration"),
7115
7150
  yolo: z.boolean().optional().default(false).describe("(WARNING - NOT RECOMMENDED) Bypass validations and compatibility checks"),
7116
7151
  verbose: z.boolean().optional().default(false).describe("Show detailed result information"),
7117
- database: DatabaseSchema.optional(),
7118
- orm: ORMSchema.optional(),
7119
- auth: AuthSchema.optional(),
7120
- payments: PaymentsSchema.optional(),
7121
- frontend: z.array(FrontendSchema).optional(),
7122
- addons: z.array(AddonsSchema).optional(),
7123
- examples: z.array(ExamplesSchema).optional(),
7152
+ database: types_exports.DatabaseSchema.optional(),
7153
+ orm: types_exports.ORMSchema.optional(),
7154
+ auth: types_exports.AuthSchema.optional(),
7155
+ payments: types_exports.PaymentsSchema.optional(),
7156
+ frontend: z.array(types_exports.FrontendSchema).optional(),
7157
+ addons: z.array(types_exports.AddonsSchema).optional(),
7158
+ examples: z.array(types_exports.ExamplesSchema).optional(),
7124
7159
  git: z.boolean().optional(),
7125
- packageManager: PackageManagerSchema.optional(),
7160
+ packageManager: types_exports.PackageManagerSchema.optional(),
7126
7161
  install: z.boolean().optional(),
7127
- dbSetup: DatabaseSetupSchema.optional(),
7128
- backend: BackendSchema.optional(),
7129
- runtime: RuntimeSchema.optional(),
7130
- api: APISchema.optional(),
7131
- webDeploy: WebDeploySchema.optional(),
7132
- serverDeploy: ServerDeploySchema.optional(),
7133
- directoryConflict: DirectoryConflictSchema.optional(),
7162
+ dbSetup: types_exports.DatabaseSetupSchema.optional(),
7163
+ backend: types_exports.BackendSchema.optional(),
7164
+ runtime: types_exports.RuntimeSchema.optional(),
7165
+ api: types_exports.APISchema.optional(),
7166
+ webDeploy: types_exports.WebDeploySchema.optional(),
7167
+ serverDeploy: types_exports.ServerDeploySchema.optional(),
7168
+ directoryConflict: types_exports.DirectoryConflictSchema.optional(),
7134
7169
  renderTitle: z.boolean().optional(),
7135
7170
  disableAnalytics: z.boolean().optional().default(false).describe("Disable analytics"),
7136
7171
  manualDb: z.boolean().optional().default(false).describe("Skip automatic/manual database setup prompt and use manual setup")
@@ -7143,12 +7178,12 @@ const router = os.router({
7143
7178
  if (options.verbose) return result;
7144
7179
  }),
7145
7180
  add: os.meta({ description: "Add addons or deployment configurations to an existing Better-T-Stack project" }).input(z.tuple([z.object({
7146
- addons: z.array(AddonsSchema).optional().default([]),
7147
- webDeploy: WebDeploySchema.optional(),
7148
- serverDeploy: ServerDeploySchema.optional(),
7181
+ addons: z.array(types_exports.AddonsSchema).optional().default([]),
7182
+ webDeploy: types_exports.WebDeploySchema.optional(),
7183
+ serverDeploy: types_exports.ServerDeploySchema.optional(),
7149
7184
  projectDir: z.string().optional(),
7150
7185
  install: z.boolean().optional().default(false).describe("Install dependencies after adding addons or deployment"),
7151
- packageManager: PackageManagerSchema.optional()
7186
+ packageManager: types_exports.PackageManagerSchema.optional()
7152
7187
  })])).handler(async ({ input }) => {
7153
7188
  const [options] = input;
7154
7189
  await addAddonsHandler(options);
@@ -7190,49 +7225,49 @@ function createBtsCli() {
7190
7225
  });
7191
7226
  }
7192
7227
  /**
7193
- * Initialize a new Better-T-Stack project
7228
+ * Programmatic API to create a new Better-T-Stack project.
7229
+ * Returns pure JSON - no console output, no interactive prompts.
7194
7230
  *
7195
- * @example CLI usage:
7196
- * ```bash
7197
- * npx create-better-t-stack my-app --yes
7198
- * ```
7199
- *
7200
- * @example Programmatic usage (always returns structured data):
7231
+ * @example
7201
7232
  * ```typescript
7202
- * import { init } from "create-better-t-stack";
7233
+ * import { create } from "create-better-t-stack";
7203
7234
  *
7204
- * const result = await init("my-app", {
7205
- * yes: true,
7235
+ * const result = await create("my-app", {
7206
7236
  * frontend: ["tanstack-router"],
7207
7237
  * backend: "hono",
7238
+ * runtime: "bun",
7208
7239
  * database: "sqlite",
7209
7240
  * orm: "drizzle",
7210
- * auth: "better-auth",
7211
- * addons: ["biome", "turborepo"],
7212
- * packageManager: "bun",
7213
- * install: false,
7214
- * directoryConflict: "increment", // auto-handle conflicts
7215
- * disableAnalytics: true, // disable analytics
7216
7241
  * });
7217
7242
  *
7218
7243
  * if (result.success) {
7219
7244
  * console.log(`Project created at: ${result.projectDirectory}`);
7220
- * console.log(`Reproducible command: ${result.reproducibleCommand}`);
7221
- * console.log(`Time taken: ${result.elapsedTimeMs}ms`);
7222
7245
  * }
7223
7246
  * ```
7224
7247
  */
7225
- async function init(projectName, options) {
7226
- const programmaticOpts = {
7227
- ...options ?? {},
7228
- verbose: true
7248
+ async function create(projectName, options) {
7249
+ const input = {
7250
+ ...options,
7251
+ projectName,
7252
+ renderTitle: false,
7253
+ verbose: true,
7254
+ disableAnalytics: options?.disableAnalytics ?? true,
7255
+ directoryConflict: options?.directoryConflict ?? "error"
7229
7256
  };
7230
- const prev = process.env.BTS_PROGRAMMATIC;
7231
- process.env.BTS_PROGRAMMATIC = "1";
7232
- const result = await caller.init([projectName, programmaticOpts]);
7233
- if (prev === void 0) delete process.env.BTS_PROGRAMMATIC;
7234
- else process.env.BTS_PROGRAMMATIC = prev;
7235
- return result;
7257
+ try {
7258
+ return await createProjectHandler(input, { silent: true });
7259
+ } catch (error) {
7260
+ return {
7261
+ success: false,
7262
+ error: error instanceof Error ? error.message : String(error),
7263
+ projectConfig: {},
7264
+ reproducibleCommand: "",
7265
+ timeScaffolded: (/* @__PURE__ */ new Date()).toISOString(),
7266
+ elapsedTimeMs: 0,
7267
+ projectDirectory: "",
7268
+ relativePath: ""
7269
+ };
7270
+ }
7236
7271
  }
7237
7272
  async function sponsors() {
7238
7273
  return caller.sponsors();
@@ -7245,4 +7280,4 @@ async function builder() {
7245
7280
  }
7246
7281
 
7247
7282
  //#endregion
7248
- export { router as a, init as i, createBtsCli as n, sponsors as o, docs as r, builder as t };
7283
+ export { router as a, docs as i, create as n, sponsors as o, createBtsCli as r, builder as t };