create-better-t-stack 2.48.2 → 2.49.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.
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { autocompleteMultiselect, cancel, confirm, group, groupMultiselect, intro, isCancel, log, multiselect, outro, select, spinner, text } from "@clack/prompts";
3
+ import { createRouterClient, os } from "@orpc/server";
3
4
  import pc from "picocolors";
4
- import { createCli, trpcServer } from "trpc-cli";
5
- import z from "zod";
5
+ import { createCli } from "trpc-cli";
6
+ import z$1, { z } from "zod";
6
7
  import path from "node:path";
7
8
  import consola, { consola as consola$1 } from "consola";
8
9
  import fs from "fs-extra";
@@ -14,7 +15,7 @@ import { IndentationText, Node, Project, QuoteKind, SyntaxKind } from "ts-morph"
14
15
  import { glob } from "tinyglobby";
15
16
  import handlebars from "handlebars";
16
17
  import { Biome } from "@biomejs/js-api/nodejs";
17
- import os from "node:os";
18
+ import os$1 from "node:os";
18
19
 
19
20
  //#region src/utils/get-package-manager.ts
20
21
  const getUserPkgManager = () => {
@@ -263,7 +264,7 @@ const AuthSchema = z.enum([
263
264
  ]).describe("Authentication provider");
264
265
  const PaymentsSchema = z.enum(["polar", "none"]).describe("Payments provider");
265
266
  const ProjectNameSchema = z.string().min(1, "Project name cannot be empty").max(255, "Project name must be less than 255 characters").refine((name) => name === "." || !name.startsWith("."), "Project name cannot start with a dot (except for '.')").refine((name) => name === "." || !name.startsWith("-"), "Project name cannot start with a dash").refine((name) => {
266
- const invalidChars = [
267
+ return ![
267
268
  "<",
268
269
  ">",
269
270
  ":",
@@ -271,8 +272,7 @@ const ProjectNameSchema = z.string().min(1, "Project name cannot be empty").max(
271
272
  "|",
272
273
  "?",
273
274
  "*"
274
- ];
275
- return !invalidChars.some((char) => name.includes(char));
275
+ ].some((char) => name.includes(char));
276
276
  }, "Project name contains invalid characters").refine((name) => name.toLowerCase() !== "node_modules", "Project name is reserved").describe("Project name or path");
277
277
  const WebDeploySchema = z.enum([
278
278
  "wrangler",
@@ -360,12 +360,11 @@ function validateApiFrontendCompatibility(api, frontends = []) {
360
360
  function isFrontendAllowedWithBackend(frontend, backend, auth) {
361
361
  if (backend === "convex" && frontend === "solid") return false;
362
362
  if (auth === "clerk" && backend === "convex") {
363
- const incompatibleFrontends = [
363
+ if ([
364
364
  "nuxt",
365
365
  "svelte",
366
366
  "solid"
367
- ];
368
- if (incompatibleFrontends.includes(frontend)) return false;
367
+ ].includes(frontend)) return false;
369
368
  }
370
369
  return true;
371
370
  }
@@ -385,8 +384,7 @@ function isExampleTodoAllowed(backend, database) {
385
384
  return !(backend !== "convex" && backend !== "none" && database === "none");
386
385
  }
387
386
  function isExampleAIAllowed(_backend, frontends = []) {
388
- const includesSolid = frontends.includes("solid");
389
- if (includesSolid) return false;
387
+ if (frontends.includes("solid")) return false;
390
388
  return true;
391
389
  }
392
390
  function validateWebDeployRequiresWebFrontend(webDeploy, hasWebFrontendFlag) {
@@ -398,8 +396,7 @@ function validateServerDeployRequiresBackend(serverDeploy, backend) {
398
396
  function validateAddonCompatibility(addon, frontend, _auth) {
399
397
  const compatibleFrontends = ADDON_COMPATIBILITY[addon];
400
398
  if (compatibleFrontends.length > 0) {
401
- const hasCompatibleFrontend = frontend.some((f) => compatibleFrontends.includes(f));
402
- if (!hasCompatibleFrontend) {
399
+ if (!frontend.some((f) => compatibleFrontends.includes(f))) {
403
400
  const frontendList = compatibleFrontends.join(", ");
404
401
  return {
405
402
  isCompatible: false,
@@ -912,7 +909,7 @@ async function getFrontendChoice(frontendOptions, backend, auth) {
912
909
  if (isCancel(frontendTypes)) return exitCancelled("Operation cancelled");
913
910
  const result = [];
914
911
  if (frontendTypes.includes("web")) {
915
- const allWebOptions = [
912
+ const webOptions = [
916
913
  {
917
914
  value: "tanstack-router",
918
915
  label: "TanStack Router",
@@ -948,8 +945,7 @@ async function getFrontendChoice(frontendOptions, backend, auth) {
948
945
  label: "TanStack Start",
949
946
  hint: "SSR, Server Functions, API Routes and more with TanStack Router"
950
947
  }
951
- ];
952
- const webOptions = allWebOptions.filter((option) => isFrontendAllowedWithBackend(option.value, backend, auth));
948
+ ].filter((option) => isFrontendAllowedWithBackend(option.value, backend, auth));
953
949
  const webFramework = await select({
954
950
  message: "Choose web",
955
951
  options: webOptions,
@@ -1069,20 +1065,18 @@ async function getPackageManagerChoice(packageManager) {
1069
1065
  //#region src/prompts/payments.ts
1070
1066
  async function getPaymentsChoice(payments, auth, backend, frontends) {
1071
1067
  if (payments !== void 0) return payments;
1072
- const isPolarCompatible = auth === "better-auth" && backend !== "convex" && (frontends?.length === 0 || splitFrontends(frontends).web.length > 0);
1073
- if (!isPolarCompatible) return "none";
1074
- const options = [{
1075
- value: "polar",
1076
- label: "Polar",
1077
- hint: "Turn your software into a business. 6 lines of code."
1078
- }, {
1079
- value: "none",
1080
- label: "None",
1081
- hint: "No payments integration"
1082
- }];
1068
+ if (!(auth === "better-auth" && backend !== "convex" && (frontends?.length === 0 || splitFrontends(frontends).web.length > 0))) return "none";
1083
1069
  const response = await select({
1084
1070
  message: "Select payments provider",
1085
- options,
1071
+ options: [{
1072
+ value: "polar",
1073
+ label: "Polar",
1074
+ hint: "Turn your software into a business. 6 lines of code."
1075
+ }, {
1076
+ value: "none",
1077
+ label: "None",
1078
+ hint: "No payments integration"
1079
+ }],
1086
1080
  initialValue: DEFAULT_CONFIG.payments
1087
1081
  });
1088
1082
  if (isCancel(response)) return exitCancelled("Operation cancelled");
@@ -1211,12 +1205,11 @@ function getDeploymentDisplay(deployment) {
1211
1205
  async function getDeploymentChoice(deployment, _runtime, _backend, frontend = []) {
1212
1206
  if (deployment !== void 0) return deployment;
1213
1207
  if (!hasWebFrontend(frontend)) return "none";
1214
- const availableDeployments = [
1208
+ const options = [
1215
1209
  "wrangler",
1216
1210
  "alchemy",
1217
1211
  "none"
1218
- ];
1219
- const options = availableDeployments.map((deploy) => {
1212
+ ].map((deploy) => {
1220
1213
  const { label, hint } = getDeploymentDisplay(deploy);
1221
1214
  return {
1222
1215
  value: deploy,
@@ -1322,14 +1315,12 @@ function validateDirectoryName(name) {
1322
1315
  if (name === ".") return void 0;
1323
1316
  const result = ProjectNameSchema.safeParse(name);
1324
1317
  if (!result.success) return result.error.issues[0]?.message || "Invalid project name";
1325
- return void 0;
1326
1318
  }
1327
1319
  async function getProjectName(initialName) {
1328
1320
  if (initialName) {
1329
1321
  if (initialName === ".") return initialName;
1330
1322
  const finalDirName = path.basename(initialName);
1331
- const validationError = validateDirectoryName(finalDirName);
1332
- if (!validationError) {
1323
+ if (!validateDirectoryName(finalDirName)) {
1333
1324
  const projectDir = path.resolve(process.cwd(), initialName);
1334
1325
  if (isPathWithinCwd(projectDir)) return initialName;
1335
1326
  consola.error(pc.red("Project path must be within current directory"));
@@ -1358,7 +1349,6 @@ async function getProjectName(initialName) {
1358
1349
  const projectDir = path.resolve(process.cwd(), nameToUse);
1359
1350
  if (!isPathWithinCwd(projectDir)) return "Project path must be within current directory";
1360
1351
  }
1361
- return void 0;
1362
1352
  }
1363
1353
  });
1364
1354
  if (isCancel(response)) return exitCancelled("Operation cancelled.");
@@ -1372,8 +1362,7 @@ async function getProjectName(initialName) {
1372
1362
  //#region src/utils/get-latest-cli-version.ts
1373
1363
  const getLatestCLIVersion = () => {
1374
1364
  const packageJsonPath = path.join(PKG_ROOT, "package.json");
1375
- const packageJsonContent = fs.readJSONSync(packageJsonPath);
1376
- return packageJsonContent.version ?? "1.0.0";
1365
+ return fs.readJSONSync(packageJsonPath).version ?? "1.0.0";
1377
1366
  };
1378
1367
 
1379
1368
  //#endregion
@@ -1398,8 +1387,7 @@ const POSTHOG_API_KEY = "phc_8ZUxEwwfKMajJLvxz1daGd931dYbQrwKNficBmsdIrs";
1398
1387
  const POSTHOG_HOST = "https://us.i.posthog.com";
1399
1388
  function generateSessionId() {
1400
1389
  const rand = Math.random().toString(36).slice(2);
1401
- const now = Date.now().toString(36);
1402
- return `cli_${now}${rand}`;
1390
+ return `cli_${Date.now().toString(36)}${rand}`;
1403
1391
  }
1404
1392
  async function trackProjectCreation(config, disableAnalytics = false) {
1405
1393
  if (!isTelemetryEnabled() || disableAnalytics) return;
@@ -1506,9 +1494,7 @@ function generateReproducibleCommand(config) {
1506
1494
  async function handleDirectoryConflict(currentPathInput, silent = false) {
1507
1495
  while (true) {
1508
1496
  const resolvedPath = path.resolve(process.cwd(), currentPathInput);
1509
- const dirExists = await fs.pathExists(resolvedPath);
1510
- const dirIsNotEmpty = dirExists && (await fs.readdir(resolvedPath)).length > 0;
1511
- if (!dirIsNotEmpty) return {
1497
+ if (!(await fs.pathExists(resolvedPath) && (await fs.readdir(resolvedPath)).length > 0)) return {
1512
1498
  finalPathInput: currentPathInput,
1513
1499
  shouldClearDirectory: false
1514
1500
  };
@@ -1622,14 +1608,12 @@ const renderTitle = () => {
1622
1608
  const terminalWidth = process.stdout.columns || 80;
1623
1609
  const titleLines = TITLE_TEXT.split("\n");
1624
1610
  const titleWidth = Math.max(...titleLines.map((line) => line.length));
1625
- if (terminalWidth < titleWidth) {
1626
- const simplifiedTitle = `
1611
+ if (terminalWidth < titleWidth) console.log(gradient(Object.values(catppuccinTheme)).multiline(`
1627
1612
  ╔══════════════════╗
1628
1613
  ║ Better T Stack ║
1629
1614
  ╚══════════════════╝
1630
- `;
1631
- console.log(gradient(Object.values(catppuccinTheme)).multiline(simplifiedTitle));
1632
- } else console.log(gradient(Object.values(catppuccinTheme)).multiline(TITLE_TEXT));
1615
+ `));
1616
+ else console.log(gradient(Object.values(catppuccinTheme)).multiline(TITLE_TEXT));
1633
1617
  };
1634
1618
 
1635
1619
  //#endregion
@@ -1755,8 +1739,7 @@ function validateConvexConstraints(config, providedFlags) {
1755
1739
  "tanstack-start",
1756
1740
  "next"
1757
1741
  ];
1758
- const hasSupportedFrontend = config.frontend?.some((f) => supportedFrontends.includes(f));
1759
- if (!hasSupportedFrontend) exitWithError("Better-Auth with Convex backend is only supported with TanStack Router, TanStack Start, or Next.js frontends. Please use '--auth clerk' or '--auth none'.");
1742
+ if (!config.frontend?.some((f) => supportedFrontends.includes(f))) exitWithError("Better-Auth with Convex backend is only supported with TanStack Router, TanStack Start, or Next.js frontends. Please use '--auth clerk' or '--auth none'.");
1760
1743
  }
1761
1744
  }
1762
1745
  function validateBackendNoneConstraints(config, providedFlags) {
@@ -1987,8 +1970,7 @@ async function updateBtsConfig(projectDir, updates) {
1987
1970
  try {
1988
1971
  const configPath = path.join(projectDir, BTS_CONFIG_FILE);
1989
1972
  if (!await fs.pathExists(configPath)) return;
1990
- const configContent = await fs.readFile(configPath, "utf-8");
1991
- let modifiedContent = configContent;
1973
+ let modifiedContent = await fs.readFile(configPath, "utf-8");
1992
1974
  for (const [key, value] of Object.entries(updates)) {
1993
1975
  const editResult = JSONC.modify(modifiedContent, [key], value, { formattingOptions: {
1994
1976
  tabSize: 2,
@@ -2078,8 +2060,7 @@ async function setupFumadocs(config) {
2078
2060
  initialValue: "next-mdx"
2079
2061
  });
2080
2062
  if (isCancel(template)) return exitCancelled("Operation cancelled");
2081
- const templateArg = TEMPLATES[template].value;
2082
- const commandWithArgs = `create-fumadocs-app@latest fumadocs --template ${templateArg} --src --no-install --pm ${packageManager} --no-eslint --no-git`;
2063
+ const commandWithArgs = `create-fumadocs-app@latest fumadocs --template ${TEMPLATES[template].value} --src --no-install --pm ${packageManager} --no-eslint --no-git`;
2083
2064
  const fumadocsInitCommand = getPackageExecutionCommand(packageManager, commandWithArgs);
2084
2065
  await execa(fumadocsInitCommand, {
2085
2066
  cwd: path.join(projectDir, "apps"),
@@ -2112,27 +2093,26 @@ async function setupRuler(config) {
2112
2093
  log.error(pc.red("Ruler template directory not found. Please ensure ruler addon is properly installed."));
2113
2094
  return;
2114
2095
  }
2115
- const EDITORS$1 = {
2116
- amp: { label: "AMP" },
2117
- copilot: { label: "GitHub Copilot" },
2118
- claude: { label: "Claude Code" },
2119
- codex: { label: "OpenAI Codex CLI" },
2120
- cursor: { label: "Cursor" },
2121
- windsurf: { label: "Windsurf" },
2122
- cline: { label: "Cline" },
2123
- aider: { label: "Aider" },
2124
- firebase: { label: "Firebase Studio" },
2125
- "gemini-cli": { label: "Gemini CLI" },
2126
- junie: { label: "Junie" },
2127
- kilocode: { label: "Kilo Code" },
2128
- opencode: { label: "OpenCode" },
2129
- crush: { label: "Crush" },
2130
- zed: { label: "Zed" },
2131
- qwen: { label: "Qwen" }
2132
- };
2133
2096
  const selectedEditors = await autocompleteMultiselect({
2134
2097
  message: "Select AI assistants for Ruler",
2135
- options: Object.entries(EDITORS$1).map(([key, v]) => ({
2098
+ options: Object.entries({
2099
+ amp: { label: "AMP" },
2100
+ copilot: { label: "GitHub Copilot" },
2101
+ claude: { label: "Claude Code" },
2102
+ codex: { label: "OpenAI Codex CLI" },
2103
+ cursor: { label: "Cursor" },
2104
+ windsurf: { label: "Windsurf" },
2105
+ cline: { label: "Cline" },
2106
+ aider: { label: "Aider" },
2107
+ firebase: { label: "Firebase Studio" },
2108
+ "gemini-cli": { label: "Gemini CLI" },
2109
+ junie: { label: "Junie" },
2110
+ kilocode: { label: "Kilo Code" },
2111
+ opencode: { label: "OpenCode" },
2112
+ crush: { label: "Crush" },
2113
+ zed: { label: "Zed" },
2114
+ qwen: { label: "Qwen" }
2115
+ }).map(([key, v]) => ({
2136
2116
  value: key,
2137
2117
  label: v.label
2138
2118
  })),
@@ -2145,8 +2125,7 @@ async function setupRuler(config) {
2145
2125
  return;
2146
2126
  }
2147
2127
  const configFile = path.join(rulerDir, "ruler.toml");
2148
- const currentConfig = await fs.readFile(configFile, "utf-8");
2149
- let updatedConfig = currentConfig;
2128
+ let updatedConfig = await fs.readFile(configFile, "utf-8");
2150
2129
  const defaultAgentsLine = `default_agents = [${selectedEditors.map((editor) => `"${editor}"`).join(", ")}]`;
2151
2130
  updatedConfig = updatedConfig.replace(/default_agents = \[\]/, defaultAgentsLine);
2152
2131
  await fs.writeFile(configFile, updatedConfig);
@@ -2189,7 +2168,7 @@ async function setupStarlight(config) {
2189
2168
  const s = spinner();
2190
2169
  try {
2191
2170
  s.start("Setting up Starlight docs...");
2192
- const starlightArgs = [
2171
+ const commandWithArgs = `create-astro@latest ${[
2193
2172
  "docs",
2194
2173
  "--template",
2195
2174
  "starlight",
@@ -2198,9 +2177,7 @@ async function setupStarlight(config) {
2198
2177
  "tailwind",
2199
2178
  "--no-git",
2200
2179
  "--skip-houston"
2201
- ];
2202
- const starlightArgsString = starlightArgs.join(" ");
2203
- const commandWithArgs = `create-astro@latest ${starlightArgsString}`;
2180
+ ].join(" ")}`;
2204
2181
  const starlightInitCommand = getPackageExecutionCommand(packageManager, commandWithArgs);
2205
2182
  await execa(starlightInitCommand, {
2206
2183
  cwd: path.join(projectDir, "apps"),
@@ -2246,7 +2223,7 @@ async function setupTauri(config) {
2246
2223
  const hasNext = frontend.includes("next");
2247
2224
  const devUrl = hasReactRouter || hasSvelte ? "http://localhost:5173" : hasNext ? "http://localhost:3001" : "http://localhost:3001";
2248
2225
  const frontendDist = hasNuxt ? "../.output/public" : hasSvelte ? "../build" : hasNext ? "../.next" : hasReactRouter ? "../build/client" : "../dist";
2249
- const tauriArgs = [
2226
+ const commandWithArgs = `@tauri-apps/cli@latest ${[
2250
2227
  "init",
2251
2228
  `--app-name=${path.basename(projectDir)}`,
2252
2229
  `--window-title=${path.basename(projectDir)}`,
@@ -2254,9 +2231,7 @@ async function setupTauri(config) {
2254
2231
  `--dev-url=${devUrl}`,
2255
2232
  `--before-dev-command="${packageManager} run dev"`,
2256
2233
  `--before-build-command="${packageManager} run build"`
2257
- ];
2258
- const tauriArgsString = tauriArgs.join(" ");
2259
- const commandWithArgs = `@tauri-apps/cli@latest ${tauriArgsString}`;
2234
+ ].join(" ")}`;
2260
2235
  const tauriInitCommand = getPackageExecutionCommand(packageManager, commandWithArgs);
2261
2236
  await execa(tauriInitCommand, {
2262
2237
  cwd: clientPackageDir,
@@ -2330,8 +2305,7 @@ async function setupUltracite(config, hasHusky) {
2330
2305
  if (editors.length > 0) ultraciteArgs.push("--editors", ...editors);
2331
2306
  if (rules.length > 0) ultraciteArgs.push("--rules", ...rules);
2332
2307
  if (hasHusky) ultraciteArgs.push("--integrations", "husky", "lint-staged");
2333
- const ultraciteArgsString = ultraciteArgs.join(" ");
2334
- const commandWithArgs = `ultracite@latest ${ultraciteArgsString} --skip-install`;
2308
+ const commandWithArgs = `ultracite@latest ${ultraciteArgs.join(" ")} --skip-install`;
2335
2309
  const ultraciteInitCommand = getPackageExecutionCommand(packageManager, commandWithArgs);
2336
2310
  await execa(ultraciteInitCommand, {
2337
2311
  cwd: projectDir,
@@ -2371,8 +2345,7 @@ function ensureArrayProperty(obj, name) {
2371
2345
  async function addPwaToViteConfig(viteConfigPath, projectName) {
2372
2346
  const sourceFile = tsProject.addSourceFileAtPathIfExists(viteConfigPath);
2373
2347
  if (!sourceFile) throw new Error("vite config not found");
2374
- const hasImport = sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === "vite-plugin-pwa");
2375
- if (!hasImport) sourceFile.insertImportDeclaration(0, {
2348
+ if (!sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === "vite-plugin-pwa")) sourceFile.insertImportDeclaration(0, {
2376
2349
  namedImports: ["VitePWA"],
2377
2350
  moduleSpecifier: "vite-plugin-pwa"
2378
2351
  });
@@ -2381,12 +2354,10 @@ async function addPwaToViteConfig(viteConfigPath, projectName) {
2381
2354
  return Node.isIdentifier(expression) && expression.getText() === "defineConfig";
2382
2355
  });
2383
2356
  if (!defineCall) throw new Error("Could not find defineConfig call in vite config");
2384
- const callExpr = defineCall;
2385
- const configObject = callExpr.getArguments()[0];
2357
+ const configObject = defineCall.getArguments()[0];
2386
2358
  if (!configObject) throw new Error("defineConfig argument is not an object literal");
2387
2359
  const pluginsArray = ensureArrayProperty(configObject, "plugins");
2388
- const alreadyPresent = pluginsArray.getElements().some((el) => el.getText().startsWith("VitePWA("));
2389
- if (!alreadyPresent) pluginsArray.addElement(`VitePWA({
2360
+ if (!pluginsArray.getElements().some((el) => el.getText().startsWith("VitePWA("))) pluginsArray.addElement(`VitePWA({
2390
2361
  registerType: "autoUpdate",
2391
2362
  manifest: {
2392
2363
  name: "${projectName}",
@@ -2487,12 +2458,11 @@ async function setupHusky(projectDir, linter) {
2487
2458
  }
2488
2459
  }
2489
2460
  async function setupPwa(projectDir, frontends) {
2490
- const isCompatibleFrontend = frontends.some((f) => [
2461
+ if (!frontends.some((f) => [
2491
2462
  "react-router",
2492
2463
  "tanstack-router",
2493
2464
  "solid"
2494
- ].includes(f));
2495
- if (!isCompatibleFrontend) return;
2465
+ ].includes(f))) return;
2496
2466
  const clientPackageDir = getWebAppDir(projectDir, frontends);
2497
2467
  if (!await fs.pathExists(clientPackageDir)) return;
2498
2468
  await addPackageDependency({
@@ -2592,8 +2562,7 @@ async function installDependencies({ projectDir, packageManager }) {
2592
2562
  function initializeBiome() {
2593
2563
  try {
2594
2564
  const biome = new Biome();
2595
- const result = biome.openProject("./");
2596
- const projectKey = result.projectKey;
2565
+ const projectKey = biome.openProject("./").projectKey;
2597
2566
  biome.applyConfiguration(projectKey, {
2598
2567
  formatter: {
2599
2568
  enabled: true,
@@ -2615,27 +2584,25 @@ function initializeBiome() {
2615
2584
  }
2616
2585
  function isSupportedFile(filePath) {
2617
2586
  const ext = path.extname(filePath).toLowerCase();
2618
- const supportedExtensions = [
2587
+ return [
2619
2588
  ".js",
2620
2589
  ".jsx",
2621
2590
  ".ts",
2622
2591
  ".tsx",
2623
2592
  ".json",
2624
2593
  ".jsonc"
2625
- ];
2626
- return supportedExtensions.includes(ext);
2594
+ ].includes(ext);
2627
2595
  }
2628
2596
  function shouldSkipFile(filePath) {
2629
2597
  const basename = path.basename(filePath);
2630
- const skipPatterns = [
2598
+ return [
2631
2599
  ".hbs",
2632
2600
  "package-lock.json",
2633
2601
  "yarn.lock",
2634
2602
  "pnpm-lock.yaml",
2635
2603
  "bun.lock",
2636
2604
  ".d.ts"
2637
- ];
2638
- return skipPatterns.some((pattern) => basename.includes(pattern));
2605
+ ].some((pattern) => basename.includes(pattern));
2639
2606
  }
2640
2607
  function formatFileWithBiome(filePath, content) {
2641
2608
  if (!isSupportedFile(filePath) || shouldSkipFile(filePath)) return null;
@@ -2672,8 +2639,7 @@ async function processTemplate(srcPath, destPath, context) {
2672
2639
  let content;
2673
2640
  if (srcPath.endsWith(".hbs")) {
2674
2641
  const templateContent = await fs.readFile(srcPath, "utf-8");
2675
- const template = handlebars.compile(templateContent);
2676
- content = template(context);
2642
+ content = handlebars.compile(templateContent)(context);
2677
2643
  } else content = await fs.readFile(srcPath, "utf-8");
2678
2644
  try {
2679
2645
  const formattedContent = await formatFileWithBiome(destPath, content);
@@ -3175,8 +3141,7 @@ async function setupDeploymentTemplates(projectDir, context) {
3175
3141
  async function addAddonsToProject(input) {
3176
3142
  try {
3177
3143
  const projectDir = input.projectDir || process.cwd();
3178
- const isBetterTStack = await isBetterTStackProject(projectDir);
3179
- if (!isBetterTStack) exitWithError("This doesn't appear to be a Better-T-Stack project. Please run this command from the root of a Better-T-Stack project.");
3144
+ if (!await isBetterTStackProject(projectDir)) exitWithError("This doesn't appear to be a Better-T-Stack project. Please run this command from the root of a Better-T-Stack project.");
3180
3145
  const detectedConfig = await detectProjectConfig(projectDir);
3181
3146
  if (!detectedConfig) exitWithError("Could not detect the project configuration. Please ensure this is a valid Better-T-Stack project.");
3182
3147
  const config = {
@@ -3323,15 +3288,13 @@ async function setupNextAlchemyDeploy(projectDir, _packageManager, options) {
3323
3288
  await fs.writeJson(pkgPath, pkg, { spaces: 2 });
3324
3289
  }
3325
3290
  const openNextConfigPath = path.join(webAppDir, "open-next.config.ts");
3326
- const openNextConfigContent = `import { defineCloudflareConfig } from "@opennextjs/cloudflare";
3291
+ await fs.writeFile(openNextConfigPath, `import { defineCloudflareConfig } from "@opennextjs/cloudflare";
3327
3292
 
3328
3293
  export default defineCloudflareConfig({});
3329
- `;
3330
- await fs.writeFile(openNextConfigPath, openNextConfigContent);
3294
+ `);
3331
3295
  const gitignorePath = path.join(webAppDir, ".gitignore");
3332
3296
  if (await fs.pathExists(gitignorePath)) {
3333
- const gitignoreContent = await fs.readFile(gitignorePath, "utf-8");
3334
- if (!gitignoreContent.includes("wrangler.jsonc")) await fs.appendFile(gitignorePath, "\nwrangler.jsonc\n");
3297
+ if (!(await fs.readFile(gitignorePath, "utf-8")).includes("wrangler.jsonc")) await fs.appendFile(gitignorePath, "\nwrangler.jsonc\n");
3335
3298
  } else await fs.writeFile(gitignorePath, "wrangler.jsonc\n");
3336
3299
  }
3337
3300
 
@@ -3367,8 +3330,7 @@ async function setupNuxtAlchemyDeploy(projectDir, _packageManager, options) {
3367
3330
  quoteKind: QuoteKind.Double
3368
3331
  } });
3369
3332
  project.addSourceFileAtPath(nuxtConfigPath);
3370
- const sourceFile = project.getSourceFileOrThrow(nuxtConfigPath);
3371
- const exportAssignment = sourceFile.getExportAssignment((d) => !d.isExportEquals());
3333
+ const exportAssignment = project.getSourceFileOrThrow(nuxtConfigPath).getExportAssignment((d) => !d.isExportEquals());
3372
3334
  if (!exportAssignment) return;
3373
3335
  const defineConfigCall = exportAssignment.getExpression();
3374
3336
  if (!Node.isCallExpression(defineConfigCall) || defineConfigCall.getExpression().getText() !== "defineNuxtConfig") return;
@@ -3389,8 +3351,7 @@ async function setupNuxtAlchemyDeploy(projectDir, _packageManager, options) {
3389
3351
  if (modulesProperty && Node.isPropertyAssignment(modulesProperty)) {
3390
3352
  const initializer = modulesProperty.getInitializer();
3391
3353
  if (Node.isArrayLiteralExpression(initializer)) {
3392
- const hasModule = initializer.getElements().some((el) => el.getText() === "\"nitro-cloudflare-dev\"" || el.getText() === "'nitro-cloudflare-dev'");
3393
- if (!hasModule) initializer.addElement("\"nitro-cloudflare-dev\"");
3354
+ if (!initializer.getElements().some((el) => el.getText() === "\"nitro-cloudflare-dev\"" || el.getText() === "'nitro-cloudflare-dev'")) initializer.addElement("\"nitro-cloudflare-dev\"");
3394
3355
  }
3395
3356
  } else if (!modulesProperty) configObject.addPropertyAssignment({
3396
3357
  name: "modules",
@@ -3477,8 +3438,7 @@ async function setupSvelteAlchemyDeploy(projectDir, _packageManager, options) {
3477
3438
  } });
3478
3439
  project.addSourceFileAtPath(svelteConfigPath);
3479
3440
  const sourceFile = project.getSourceFileOrThrow(svelteConfigPath);
3480
- const importDeclarations = sourceFile.getImportDeclarations();
3481
- const adapterImport = importDeclarations.find((imp) => imp.getModuleSpecifierValue().includes("@sveltejs/adapter"));
3441
+ const adapterImport = sourceFile.getImportDeclarations().find((imp) => imp.getModuleSpecifierValue().includes("@sveltejs/adapter"));
3482
3442
  if (adapterImport) {
3483
3443
  adapterImport.setModuleSpecifier("alchemy/cloudflare/sveltekit");
3484
3444
  adapterImport.removeDefaultImport();
@@ -3604,8 +3564,7 @@ async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, opti
3604
3564
  if (pluginsProperty && Node.isPropertyAssignment(pluginsProperty)) {
3605
3565
  const initializer = pluginsProperty.getInitializer();
3606
3566
  if (Node.isArrayLiteralExpression(initializer)) {
3607
- const hasShim = initializer.getElements().some((el) => el.getText().includes("alchemy"));
3608
- if (!hasShim) initializer.addElement("alchemy()");
3567
+ if (!initializer.getElements().some((el) => el.getText().includes("alchemy"))) initializer.addElement("alchemy()");
3609
3568
  const tanstackElements = initializer.getElements().filter((el) => el.getText().includes("tanstackStart"));
3610
3569
  let needsReactPlugin = false;
3611
3570
  tanstackElements.forEach((element) => {
@@ -3623,8 +3582,7 @@ async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, opti
3623
3582
  name: "target",
3624
3583
  initializer: "\"cloudflare-module\""
3625
3584
  });
3626
- const hasCustomViteReactPlugin = !!configObj.getProperty("customViteReactPlugin");
3627
- if (!hasCustomViteReactPlugin) configObj.addPropertyAssignment({
3585
+ if (!!!configObj.getProperty("customViteReactPlugin")) configObj.addPropertyAssignment({
3628
3586
  name: "customViteReactPlugin",
3629
3587
  initializer: "true"
3630
3588
  });
@@ -3645,7 +3603,7 @@ async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, opti
3645
3603
  console.warn("Failed to update vite.config.ts:", error);
3646
3604
  }
3647
3605
  const nitroConfigPath = path.join(webAppDir, "nitro.config.ts");
3648
- const nitroConfigContent = `import { defineNitroConfig } from "nitropack/config";
3606
+ await fs.writeFile(nitroConfigPath, `import { defineNitroConfig } from "nitropack/config";
3649
3607
 
3650
3608
  export default defineNitroConfig({
3651
3609
  preset: "cloudflare-module",
@@ -3653,8 +3611,7 @@ export default defineNitroConfig({
3653
3611
  nodeCompat: true,
3654
3612
  },
3655
3613
  });
3656
- `;
3657
- await fs.writeFile(nitroConfigPath, nitroConfigContent, "utf-8");
3614
+ `, "utf-8");
3658
3615
  }
3659
3616
 
3660
3617
  //#endregion
@@ -3772,8 +3729,7 @@ async function setupNuxtWorkersDeploy(projectDir, packageManager) {
3772
3729
  if (modulesProp && modulesProp.getKind() === SyntaxKind.PropertyAssignment) {
3773
3730
  const arrayExpr = modulesProp.getFirstDescendantByKind(SyntaxKind.ArrayLiteralExpression);
3774
3731
  if (arrayExpr) {
3775
- const alreadyHas = arrayExpr.getElements().some((el) => el.getText().replace(/['"`]/g, "") === "nitro-cloudflare-dev");
3776
- if (!alreadyHas) arrayExpr.addElement("'nitro-cloudflare-dev'");
3732
+ if (!arrayExpr.getElements().some((el) => el.getText().replace(/['"`]/g, "") === "nitro-cloudflare-dev")) arrayExpr.addElement("'nitro-cloudflare-dev'");
3777
3733
  }
3778
3734
  } else configObj.addPropertyAssignment({
3779
3735
  name: "modules",
@@ -3808,13 +3764,10 @@ async function setupSvelteWorkersDeploy(projectDir, packageManager) {
3808
3764
  if (!sourceFile) return;
3809
3765
  const adapterImport = sourceFile.getImportDeclarations().find((imp) => ["@sveltejs/adapter-auto", "@sveltejs/adapter-node"].includes(imp.getModuleSpecifierValue()));
3810
3766
  if (adapterImport) adapterImport.setModuleSpecifier("@sveltejs/adapter-cloudflare");
3811
- else {
3812
- const alreadyHasCloudflare = sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === "@sveltejs/adapter-cloudflare");
3813
- if (!alreadyHasCloudflare) sourceFile.insertImportDeclaration(0, {
3814
- defaultImport: "adapter",
3815
- moduleSpecifier: "@sveltejs/adapter-cloudflare"
3816
- });
3817
- }
3767
+ else if (!sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === "@sveltejs/adapter-cloudflare")) sourceFile.insertImportDeclaration(0, {
3768
+ defaultImport: "adapter",
3769
+ moduleSpecifier: "@sveltejs/adapter-cloudflare"
3770
+ });
3818
3771
  await tsProject.save();
3819
3772
  }
3820
3773
  }
@@ -3865,8 +3818,7 @@ async function setupTanstackStartWorkersDeploy(projectDir, packageManager) {
3865
3818
  const tanstackPluginText = "tanstackStart({ target: \"cloudflare-module\", customViteReactPlugin: true })";
3866
3819
  if (tanstackPluginIndex === -1) pluginsArray.addElement(tanstackPluginText);
3867
3820
  else pluginsArray.getElements()[tanstackPluginIndex].replaceWithText(tanstackPluginText);
3868
- const hasReactPlugin = pluginsArray.getElements().some((el) => Node.isCallExpression(el) && el.getExpression().getText() === reactPluginIdentifier);
3869
- if (!hasReactPlugin) {
3821
+ if (!pluginsArray.getElements().some((el) => Node.isCallExpression(el) && el.getExpression().getText() === reactPluginIdentifier)) {
3870
3822
  const nextIndex = pluginsArray.getElements().findIndex((el) => el.getText().includes("tanstackStart(")) + 1;
3871
3823
  if (nextIndex > 0) pluginsArray.insertElement(nextIndex, `${reactPluginIdentifier}()`);
3872
3824
  else pluginsArray.addElement(`${reactPluginIdentifier}()`);
@@ -3886,8 +3838,7 @@ async function setupWorkersVitePlugin(projectDir) {
3886
3838
  });
3887
3839
  const sourceFile = tsProject.addSourceFileAtPathIfExists(viteConfigPath);
3888
3840
  if (!sourceFile) throw new Error("vite.config.ts not found in web app directory");
3889
- const hasCloudflareImport = sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === "@cloudflare/vite-plugin");
3890
- if (!hasCloudflareImport) sourceFile.insertImportDeclaration(0, {
3841
+ if (!sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === "@cloudflare/vite-plugin")) sourceFile.insertImportDeclaration(0, {
3891
3842
  namedImports: ["cloudflare"],
3892
3843
  moduleSpecifier: "@cloudflare/vite-plugin"
3893
3844
  });
@@ -3896,12 +3847,10 @@ async function setupWorkersVitePlugin(projectDir) {
3896
3847
  return Node.isIdentifier(expression) && expression.getText() === "defineConfig";
3897
3848
  });
3898
3849
  if (!defineCall) throw new Error("Could not find defineConfig call in vite config");
3899
- const callExpr = defineCall;
3900
- const configObject = callExpr.getArguments()[0];
3850
+ const configObject = defineCall.getArguments()[0];
3901
3851
  if (!configObject) throw new Error("defineConfig argument is not an object literal");
3902
3852
  const pluginsArray = ensureArrayProperty(configObject, "plugins");
3903
- const hasCloudflarePlugin = pluginsArray.getElements().some((el) => el.getText().includes("cloudflare("));
3904
- if (!hasCloudflarePlugin) pluginsArray.addElement("cloudflare()");
3853
+ if (!pluginsArray.getElements().some((el) => el.getText().includes("cloudflare("))) pluginsArray.addElement("cloudflare()");
3905
3854
  await tsProject.save();
3906
3855
  }
3907
3856
 
@@ -3960,8 +3909,7 @@ async function setupWorkersWebDeploy(projectDir, pkgManager) {
3960
3909
  async function addDeploymentToProject(input) {
3961
3910
  try {
3962
3911
  const projectDir = input.projectDir || process.cwd();
3963
- const isBetterTStack = await isBetterTStackProject(projectDir);
3964
- if (!isBetterTStack) exitWithError("This doesn't appear to be a Better-T-Stack project. Please run this command from the root of a Better-T-Stack project.");
3912
+ if (!await isBetterTStackProject(projectDir)) exitWithError("This doesn't appear to be a Better-T-Stack project. Please run this command from the root of a Better-T-Stack project.");
3965
3913
  const detectedConfig = await detectProjectConfig(projectDir);
3966
3914
  if (!detectedConfig) exitWithError("Could not detect the project configuration. Please ensure this is a valid Better-T-Stack project.");
3967
3915
  if (input.webDeploy && detectedConfig.webDeploy === input.webDeploy) exitWithError(`${input.webDeploy} web deployment is already configured for this project.`);
@@ -4131,8 +4079,7 @@ function getQueryDependencies(frontend) {
4131
4079
  "native-unistyles"
4132
4080
  ];
4133
4081
  const deps = {};
4134
- const needsReactQuery = frontend.some((f) => reactBasedFrontends.includes(f));
4135
- if (needsReactQuery) {
4082
+ if (frontend.some((f) => reactBasedFrontends.includes(f))) {
4136
4083
  const hasReactWeb = frontend.some((f) => f !== "native-nativewind" && f !== "native-unistyles" && reactBasedFrontends.includes(f));
4137
4084
  const hasNative = frontend.includes("native-nativewind") || frontend.includes("native-unistyles");
4138
4085
  if (hasReactWeb) deps.web = {
@@ -4295,8 +4242,7 @@ async function setupAuth(config) {
4295
4242
  }
4296
4243
  if (auth === "better-auth") {
4297
4244
  const convexBackendDir = path.join(projectDir, "packages/backend");
4298
- const convexBackendDirExists = await fs.pathExists(convexBackendDir);
4299
- if (convexBackendDirExists) await addPackageDependency({
4245
+ if (await fs.pathExists(convexBackendDir)) await addPackageDependency({
4300
4246
  dependencies: ["better-auth", "@convex-dev/better-auth"],
4301
4247
  customDependencies: { "better-auth": "1.3.8" },
4302
4248
  projectDir: convexBackendDir
@@ -4334,7 +4280,7 @@ async function setupAuth(config) {
4334
4280
  dependencies: ["better-auth"],
4335
4281
  projectDir: serverDir
4336
4282
  });
4337
- const hasWebFrontend$1 = frontend.some((f) => [
4283
+ if (frontend.some((f) => [
4338
4284
  "react-router",
4339
4285
  "tanstack-router",
4340
4286
  "tanstack-start",
@@ -4342,8 +4288,7 @@ async function setupAuth(config) {
4342
4288
  "nuxt",
4343
4289
  "svelte",
4344
4290
  "solid"
4345
- ].includes(f));
4346
- if (hasWebFrontend$1 && clientDirExists) {
4291
+ ].includes(f)) && clientDirExists) {
4347
4292
  if (auth === "better-auth") await addPackageDependency({
4348
4293
  dependencies: ["better-auth"],
4349
4294
  projectDir: clientDir
@@ -4411,8 +4356,7 @@ async function addEnvVariablesToFile(filePath, variables) {
4411
4356
  let exampleContentToAdd = "";
4412
4357
  for (const exampleVar of exampleVariables) {
4413
4358
  const key = exampleVar.split("=")[0];
4414
- const regex = new RegExp(`^${key}=.*$`, "m");
4415
- if (!regex.test(exampleEnvContent)) {
4359
+ if (!new RegExp(`^${key}=.*$`, "m").test(exampleEnvContent)) {
4416
4360
  exampleContentToAdd += `${exampleVar}\n`;
4417
4361
  exampleModified = true;
4418
4362
  }
@@ -4432,8 +4376,7 @@ async function setupEnvironmentVariables(config) {
4432
4376
  const hasNuxt = frontend.includes("nuxt");
4433
4377
  const hasSvelte = frontend.includes("svelte");
4434
4378
  const hasSolid = frontend.includes("solid");
4435
- const hasWebFrontend$1 = hasReactRouter || hasTanStackRouter || hasTanStackStart || hasNextJs || hasNuxt || hasSolid || hasSvelte;
4436
- if (hasWebFrontend$1) {
4379
+ if (hasReactRouter || hasTanStackRouter || hasTanStackStart || hasNextJs || hasNuxt || hasSolid || hasSvelte) {
4437
4380
  const clientDir = path.join(projectDir, "apps/web");
4438
4381
  if (await fs.pathExists(clientDir)) {
4439
4382
  let envVarName = "VITE_SERVER_URL";
@@ -4522,15 +4465,12 @@ async function setupEnvironmentVariables(config) {
4522
4465
  const convexBackendDir = path.join(projectDir, "packages/backend");
4523
4466
  if (await fs.pathExists(convexBackendDir)) {
4524
4467
  const envLocalPath = path.join(convexBackendDir, ".env.local");
4525
- if (!await fs.pathExists(envLocalPath) || !(await fs.readFile(envLocalPath, "utf8")).includes("npx convex env set")) {
4526
- const convexCommands = `# Set Convex environment variables
4468
+ if (!await fs.pathExists(envLocalPath) || !(await fs.readFile(envLocalPath, "utf8")).includes("npx convex env set")) await fs.appendFile(envLocalPath, `# Set Convex environment variables
4527
4469
  # npx convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)
4528
4470
  # npx convex env set SITE_URL http://localhost:3001
4529
4471
 
4530
- `;
4531
- await fs.appendFile(envLocalPath, convexCommands);
4532
- }
4533
- const convexBackendVars = [{
4472
+ `);
4473
+ await addEnvVariablesToFile(envLocalPath, [{
4534
4474
  key: hasNextJs ? "NEXT_PUBLIC_CONVEX_SITE_URL" : "VITE_CONVEX_SITE_URL",
4535
4475
  value: "",
4536
4476
  condition: true,
@@ -4539,8 +4479,7 @@ async function setupEnvironmentVariables(config) {
4539
4479
  key: "SITE_URL",
4540
4480
  value: "http://localhost:3001",
4541
4481
  condition: true
4542
- }];
4543
- await addEnvVariablesToFile(envLocalPath, convexBackendVars);
4482
+ }]);
4544
4483
  }
4545
4484
  }
4546
4485
  return;
@@ -4608,34 +4547,27 @@ async function setupEnvironmentVariables(config) {
4608
4547
  const isIndividualAlchemy = webDeploy === "alchemy" || serverDeploy === "alchemy";
4609
4548
  if (isUnifiedAlchemy) {
4610
4549
  const rootEnvPath = path.join(projectDir, ".env");
4611
- const rootAlchemyVars = [{
4550
+ await addEnvVariablesToFile(rootEnvPath, [{
4612
4551
  key: "ALCHEMY_PASSWORD",
4613
4552
  value: "please-change-this",
4614
4553
  condition: true
4615
- }];
4616
- await addEnvVariablesToFile(rootEnvPath, rootAlchemyVars);
4554
+ }]);
4617
4555
  } else if (isIndividualAlchemy) {
4618
4556
  if (webDeploy === "alchemy") {
4619
4557
  const webDir = path.join(projectDir, "apps/web");
4620
- if (await fs.pathExists(webDir)) {
4621
- const webAlchemyVars = [{
4622
- key: "ALCHEMY_PASSWORD",
4623
- value: "please-change-this",
4624
- condition: true
4625
- }];
4626
- await addEnvVariablesToFile(path.join(webDir, ".env"), webAlchemyVars);
4627
- }
4558
+ if (await fs.pathExists(webDir)) await addEnvVariablesToFile(path.join(webDir, ".env"), [{
4559
+ key: "ALCHEMY_PASSWORD",
4560
+ value: "please-change-this",
4561
+ condition: true
4562
+ }]);
4628
4563
  }
4629
4564
  if (serverDeploy === "alchemy") {
4630
4565
  const serverDir$1 = path.join(projectDir, "apps/server");
4631
- if (await fs.pathExists(serverDir$1)) {
4632
- const serverAlchemyVars = [{
4633
- key: "ALCHEMY_PASSWORD",
4634
- value: "please-change-this",
4635
- condition: true
4636
- }];
4637
- await addEnvVariablesToFile(path.join(serverDir$1, ".env"), serverAlchemyVars);
4638
- }
4566
+ if (await fs.pathExists(serverDir$1)) await addEnvVariablesToFile(path.join(serverDir$1, ".env"), [{
4567
+ key: "ALCHEMY_PASSWORD",
4568
+ value: "please-change-this",
4569
+ condition: true
4570
+ }]);
4639
4571
  }
4640
4572
  }
4641
4573
  }
@@ -4718,13 +4650,8 @@ function getDatabaseUrl(database, projectName) {
4718
4650
  //#region src/utils/command-exists.ts
4719
4651
  async function commandExists(command) {
4720
4652
  try {
4721
- const isWindows = process.platform === "win32";
4722
- if (isWindows) {
4723
- const result$1 = await execa("where", [command]);
4724
- return result$1.exitCode === 0;
4725
- }
4726
- const result = await execa("which", [command]);
4727
- return result.exitCode === 0;
4653
+ if (process.platform === "win32") return (await execa("where", [command])).exitCode === 0;
4654
+ return (await execa("which", [command])).exitCode === 0;
4728
4655
  } catch {
4729
4656
  return false;
4730
4657
  }
@@ -4746,8 +4673,7 @@ async function checkAtlasCLI() {
4746
4673
  }
4747
4674
  async function initMongoDBAtlas(serverDir) {
4748
4675
  try {
4749
- const hasAtlas = await checkAtlasCLI();
4750
- if (!hasAtlas) {
4676
+ if (!await checkAtlasCLI()) {
4751
4677
  consola.error(pc.red("MongoDB Atlas CLI not found."));
4752
4678
  log.info(pc.yellow("Please install it from: https://www.mongodb.com/docs/atlas/cli/current/install-atlas-cli/"));
4753
4679
  return null;
@@ -5152,9 +5078,8 @@ async function setupWithCreateDb(serverDir, packageManager, orm) {
5152
5078
  consola$1.error("Failed to parse create-db response");
5153
5079
  return null;
5154
5080
  }
5155
- const databaseUrl = orm === "drizzle" ? createDbResponse.directConnectionString : createDbResponse.connectionString;
5156
5081
  return {
5157
- databaseUrl,
5082
+ databaseUrl: orm === "drizzle" ? createDbResponse.directConnectionString : createDbResponse.connectionString,
5158
5083
  claimUrl: createDbResponse.claimUrl
5159
5084
  };
5160
5085
  } catch (error) {
@@ -5316,7 +5241,7 @@ async function writeSupabaseEnvFile(projectDir, databaseUrl) {
5316
5241
  try {
5317
5242
  const envPath = path.join(projectDir, "apps/server", ".env");
5318
5243
  const dbUrlToUse = databaseUrl || "postgresql://postgres:postgres@127.0.0.1:54322/postgres";
5319
- const variables = [{
5244
+ await addEnvVariablesToFile(envPath, [{
5320
5245
  key: "DATABASE_URL",
5321
5246
  value: dbUrlToUse,
5322
5247
  condition: true
@@ -5324,8 +5249,7 @@ async function writeSupabaseEnvFile(projectDir, databaseUrl) {
5324
5249
  key: "DIRECT_URL",
5325
5250
  value: dbUrlToUse,
5326
5251
  condition: true
5327
- }];
5328
- await addEnvVariablesToFile(envPath, variables);
5252
+ }]);
5329
5253
  return true;
5330
5254
  } catch (error) {
5331
5255
  consola$1.error(pc.red("Failed to update .env file for Supabase."));
@@ -5334,8 +5258,7 @@ async function writeSupabaseEnvFile(projectDir, databaseUrl) {
5334
5258
  }
5335
5259
  }
5336
5260
  function extractDbUrl(output) {
5337
- const dbUrlMatch = output.match(/DB URL:\s*(postgresql:\/\/[^\s]+)/);
5338
- const url = dbUrlMatch?.[1];
5261
+ const url = output.match(/DB URL:\s*(postgresql:\/\/[^\s]+)/)?.[1];
5339
5262
  if (url) return url;
5340
5263
  return null;
5341
5264
  }
@@ -5431,8 +5354,7 @@ async function setupSupabase(config, cliInput) {
5431
5354
  await writeSupabaseEnvFile(projectDir, "");
5432
5355
  return;
5433
5356
  }
5434
- const initialized = await initializeSupabase(serverDir, packageManager);
5435
- if (!initialized) {
5357
+ if (!await initializeSupabase(serverDir, packageManager)) {
5436
5358
  displayManualSupabaseInstructions();
5437
5359
  return;
5438
5360
  }
@@ -5442,14 +5364,12 @@ async function setupSupabase(config, cliInput) {
5442
5364
  return;
5443
5365
  }
5444
5366
  const dbUrl = extractDbUrl(supabaseOutput);
5445
- if (dbUrl) {
5446
- const envUpdated = await writeSupabaseEnvFile(projectDir, dbUrl);
5447
- if (envUpdated) log.success(pc.green("Supabase local development setup ready!"));
5448
- else {
5449
- log.error(pc.red("Supabase setup completed, but failed to update .env automatically."));
5450
- displayManualSupabaseInstructions(supabaseOutput);
5451
- }
5452
- } else {
5367
+ if (dbUrl) if (await writeSupabaseEnvFile(projectDir, dbUrl)) log.success(pc.green("Supabase local development setup ready!"));
5368
+ else {
5369
+ log.error(pc.red("Supabase setup completed, but failed to update .env automatically."));
5370
+ displayManualSupabaseInstructions(supabaseOutput);
5371
+ }
5372
+ else {
5453
5373
  log.error(pc.yellow("Supabase started, but could not extract DB URL automatically."));
5454
5374
  displayManualSupabaseInstructions(supabaseOutput);
5455
5375
  }
@@ -5467,8 +5387,7 @@ async function isTursoInstalled() {
5467
5387
  }
5468
5388
  async function isTursoLoggedIn() {
5469
5389
  try {
5470
- const output = await $`turso auth whoami`;
5471
- return !output.stdout.includes("You are not logged in");
5390
+ return !(await $`turso auth whoami`).stdout.includes("You are not logged in");
5472
5391
  } catch {
5473
5392
  return false;
5474
5393
  }
@@ -5627,10 +5546,9 @@ async function setupTurso(config, cliInput) {
5627
5546
  return;
5628
5547
  }
5629
5548
  setupSpinner.start("Checking Turso CLI availability...");
5630
- const platform = os.platform();
5549
+ const platform = os$1.platform();
5631
5550
  const isMac = platform === "darwin";
5632
- const isWindows = platform === "win32";
5633
- if (isWindows) {
5551
+ if (platform === "win32") {
5634
5552
  if (setupSpinner) setupSpinner.stop(pc.yellow("Turso setup not supported on Windows"));
5635
5553
  log.warn(pc.yellow("Automatic Turso setup is not supported on Windows."));
5636
5554
  await writeEnvFile(projectDir);
@@ -5638,8 +5556,7 @@ async function setupTurso(config, cliInput) {
5638
5556
  return;
5639
5557
  }
5640
5558
  if (setupSpinner) setupSpinner.stop("Turso CLI availability checked");
5641
- const isCliInstalled = await isTursoInstalled();
5642
- if (!isCliInstalled) {
5559
+ if (!await isTursoInstalled()) {
5643
5560
  const shouldInstall = await confirm({
5644
5561
  message: "Would you like to install Turso CLI?",
5645
5562
  initialValue: true
@@ -5652,8 +5569,7 @@ async function setupTurso(config, cliInput) {
5652
5569
  }
5653
5570
  await installTursoCLI(isMac);
5654
5571
  }
5655
- const isLoggedIn = await isTursoLoggedIn();
5656
- if (!isLoggedIn) await loginToTurso();
5572
+ if (!await isTursoLoggedIn()) await loginToTurso();
5657
5573
  const selectedGroup = await selectTursoGroup();
5658
5574
  let success = false;
5659
5575
  let dbName = "";
@@ -5910,8 +5826,7 @@ function generateStackDescription(frontend, backend, api, isConvex) {
5910
5826
  const hasSvelte = frontend.includes("svelte");
5911
5827
  const hasNuxt = frontend.includes("nuxt");
5912
5828
  const hasSolid = frontend.includes("solid");
5913
- const hasFrontendNone = frontend.length === 0 || frontend.includes("none");
5914
- if (!hasFrontendNone) {
5829
+ if (!(frontend.length === 0 || frontend.includes("none"))) {
5915
5830
  if (hasTanstackRouter) parts.push("React, TanStack Router");
5916
5831
  else if (hasReactRouter) parts.push("React, React Router");
5917
5832
  else if (hasNext) parts.push("Next.js");
@@ -5967,8 +5882,7 @@ function generateProjectStructure(projectName, frontend, backend, addons, isConv
5967
5882
  structure.push(`│ ├── web/ # Frontend application (${frontendType})`);
5968
5883
  }
5969
5884
  }
5970
- const hasNative = frontend.includes("native-nativewind") || frontend.includes("native-unistyles");
5971
- if (hasNative) structure.push("│ ├── native/ # Mobile application (React Native, Expo)");
5885
+ if (frontend.includes("native-nativewind") || frontend.includes("native-unistyles")) structure.push("│ ├── native/ # Mobile application (React Native, Expo)");
5972
5886
  if (addons.includes("starlight")) structure.push("│ ├── docs/ # Documentation site (Astro Starlight)");
5973
5887
  if (isConvex) {
5974
5888
  structure.push("├── packages/");
@@ -6152,12 +6066,11 @@ function generateDeploymentCommands(packageManagerRunCmd, webDeploy, serverDeplo
6152
6066
  //#region src/helpers/core/git.ts
6153
6067
  async function initializeGit(projectDir, useGit) {
6154
6068
  if (!useGit) return;
6155
- const gitVersionResult = await $({
6069
+ if ((await $({
6156
6070
  cwd: projectDir,
6157
6071
  reject: false,
6158
6072
  stderr: "pipe"
6159
- })`git --version`;
6160
- if (gitVersionResult.exitCode !== 0) {
6073
+ })`git --version`).exitCode !== 0) {
6161
6074
  log.warn(pc.yellow("Git is not installed"));
6162
6075
  return;
6163
6076
  }
@@ -6187,7 +6100,7 @@ async function setupPayments(config) {
6187
6100
  projectDir: serverDir
6188
6101
  });
6189
6102
  if (clientDirExists) {
6190
- const hasWebFrontend$1 = frontend.some((f) => [
6103
+ if (frontend.some((f) => [
6191
6104
  "react-router",
6192
6105
  "tanstack-router",
6193
6106
  "tanstack-start",
@@ -6195,8 +6108,7 @@ async function setupPayments(config) {
6195
6108
  "nuxt",
6196
6109
  "svelte",
6197
6110
  "solid"
6198
- ].includes(f));
6199
- if (hasWebFrontend$1) await addPackageDependency({
6111
+ ].includes(f))) await addPackageDependency({
6200
6112
  dependencies: ["@polar-sh/better-auth"],
6201
6113
  projectDir: clientDir
6202
6114
  });
@@ -6237,15 +6149,13 @@ function getDockerInstallInstructions(platform, database) {
6237
6149
  return `${pc.yellow("IMPORTANT:")} Docker required for ${databaseName}. Install for ${platformName}:\n${pc.blue(installUrl)}`;
6238
6150
  }
6239
6151
  async function getDockerStatus(database) {
6240
- const platform = os.platform();
6241
- const installed = await isDockerInstalled();
6242
- if (!installed) return {
6152
+ const platform = os$1.platform();
6153
+ if (!await isDockerInstalled()) return {
6243
6154
  installed: false,
6244
6155
  running: false,
6245
6156
  message: getDockerInstallInstructions(platform, database)
6246
6157
  };
6247
- const running = await isDockerRunning();
6248
- if (!running) return {
6158
+ if (!await isDockerRunning()) return {
6249
6159
  installed: true,
6250
6160
  running: false,
6251
6161
  message: `${pc.yellow("IMPORTANT:")} Docker is installed but not running.`
@@ -6560,8 +6470,7 @@ async function updateRootPackageJson(projectDir, options) {
6560
6470
  const workspaces = packageJson.workspaces;
6561
6471
  if (options.backend === "convex") {
6562
6472
  if (!workspaces.includes("packages/*")) workspaces.push("packages/*");
6563
- const needsAppsDir = options.frontend.length > 0 || options.addons.includes("starlight");
6564
- if (needsAppsDir && !workspaces.includes("apps/*")) workspaces.push("apps/*");
6473
+ if ((options.frontend.length > 0 || options.addons.includes("starlight")) && !workspaces.includes("apps/*")) workspaces.push("apps/*");
6565
6474
  } else {
6566
6475
  if (!workspaces.includes("apps/*")) workspaces.push("apps/*");
6567
6476
  if (!workspaces.includes("packages/*")) workspaces.push("packages/*");
@@ -6781,9 +6690,7 @@ async function handleDirectoryConflictProgrammatically(currentPathInput, strateg
6781
6690
  finalPathInput: currentPathInput,
6782
6691
  shouldClearDirectory: false
6783
6692
  };
6784
- const dirContents = await fs.readdir(currentPath);
6785
- const isNotEmpty = dirContents.length > 0;
6786
- if (!isNotEmpty) return {
6693
+ if (!((await fs.readdir(currentPath)).length > 0)) return {
6787
6694
  finalPathInput: currentPathInput,
6788
6695
  shouldClearDirectory: false
6789
6696
  };
@@ -6945,26 +6852,25 @@ function displaySponsorsBox(sponsors$1) {
6945
6852
 
6946
6853
  //#endregion
6947
6854
  //#region src/index.ts
6948
- const t = trpcServer.initTRPC.create();
6949
- const router = t.router({
6950
- init: t.procedure.meta({
6855
+ const router = os.router({
6856
+ init: os.meta({
6951
6857
  description: "Create a new Better-T-Stack project",
6952
6858
  default: true,
6953
6859
  negateBooleans: true
6954
- }).input(z.tuple([ProjectNameSchema.optional(), z.object({
6955
- yes: z.boolean().optional().default(false).describe("Use default configuration"),
6956
- yolo: z.boolean().optional().default(false).describe("(WARNING - NOT RECOMMENDED) Bypass validations and compatibility checks"),
6957
- verbose: z.boolean().optional().default(false).describe("Show detailed result information"),
6860
+ }).input(z$1.tuple([ProjectNameSchema.optional(), z$1.object({
6861
+ yes: z$1.boolean().optional().default(false).describe("Use default configuration"),
6862
+ yolo: z$1.boolean().optional().default(false).describe("(WARNING - NOT RECOMMENDED) Bypass validations and compatibility checks"),
6863
+ verbose: z$1.boolean().optional().default(false).describe("Show detailed result information"),
6958
6864
  database: DatabaseSchema.optional(),
6959
6865
  orm: ORMSchema.optional(),
6960
6866
  auth: AuthSchema.optional(),
6961
6867
  payments: PaymentsSchema.optional(),
6962
- frontend: z.array(FrontendSchema).optional(),
6963
- addons: z.array(AddonsSchema).optional(),
6964
- examples: z.array(ExamplesSchema).optional(),
6965
- git: z.boolean().optional(),
6868
+ frontend: z$1.array(FrontendSchema).optional(),
6869
+ addons: z$1.array(AddonsSchema).optional(),
6870
+ examples: z$1.array(ExamplesSchema).optional(),
6871
+ git: z$1.boolean().optional(),
6966
6872
  packageManager: PackageManagerSchema.optional(),
6967
- install: z.boolean().optional(),
6873
+ install: z$1.boolean().optional(),
6968
6874
  dbSetup: DatabaseSetupSchema.optional(),
6969
6875
  backend: BackendSchema.optional(),
6970
6876
  runtime: RuntimeSchema.optional(),
@@ -6972,10 +6878,10 @@ const router = t.router({
6972
6878
  webDeploy: WebDeploySchema.optional(),
6973
6879
  serverDeploy: ServerDeploySchema.optional(),
6974
6880
  directoryConflict: DirectoryConflictSchema.optional(),
6975
- renderTitle: z.boolean().optional(),
6976
- disableAnalytics: z.boolean().optional().default(false).describe("Disable analytics"),
6977
- manualDb: z.boolean().optional().default(false).describe("Skip automatic/manual database setup prompt and use manual setup")
6978
- })])).mutation(async ({ input }) => {
6881
+ renderTitle: z$1.boolean().optional(),
6882
+ disableAnalytics: z$1.boolean().optional().default(false).describe("Disable analytics"),
6883
+ manualDb: z$1.boolean().optional().default(false).describe("Skip automatic/manual database setup prompt and use manual setup")
6884
+ })])).handler(async ({ input }) => {
6979
6885
  const [projectName, options] = input;
6980
6886
  const combinedInput = {
6981
6887
  projectName,
@@ -6984,18 +6890,18 @@ const router = t.router({
6984
6890
  const result = await createProjectHandler(combinedInput);
6985
6891
  if (options.verbose) return result;
6986
6892
  }),
6987
- add: t.procedure.meta({ description: "Add addons or deployment configurations to an existing Better-T-Stack project" }).input(z.tuple([z.object({
6988
- addons: z.array(AddonsSchema).optional().default([]),
6893
+ add: os.meta({ description: "Add addons or deployment configurations to an existing Better-T-Stack project" }).input(z$1.tuple([z$1.object({
6894
+ addons: z$1.array(AddonsSchema).optional().default([]),
6989
6895
  webDeploy: WebDeploySchema.optional(),
6990
6896
  serverDeploy: ServerDeploySchema.optional(),
6991
- projectDir: z.string().optional(),
6992
- install: z.boolean().optional().default(false).describe("Install dependencies after adding addons or deployment"),
6897
+ projectDir: z$1.string().optional(),
6898
+ install: z$1.boolean().optional().default(false).describe("Install dependencies after adding addons or deployment"),
6993
6899
  packageManager: PackageManagerSchema.optional()
6994
- })])).mutation(async ({ input }) => {
6900
+ })])).handler(async ({ input }) => {
6995
6901
  const [options] = input;
6996
6902
  await addAddonsHandler(options);
6997
6903
  }),
6998
- sponsors: t.procedure.meta({ description: "Show Better-T-Stack sponsors" }).mutation(async () => {
6904
+ sponsors: os.meta({ description: "Show Better-T-Stack sponsors" }).handler(async () => {
6999
6905
  try {
7000
6906
  renderTitle();
7001
6907
  intro(pc.magenta("Better-T-Stack Sponsors"));
@@ -7005,7 +6911,7 @@ const router = t.router({
7005
6911
  handleError(error, "Failed to display sponsors");
7006
6912
  }
7007
6913
  }),
7008
- docs: t.procedure.meta({ description: "Open Better-T-Stack documentation" }).mutation(async () => {
6914
+ docs: os.meta({ description: "Open Better-T-Stack documentation" }).handler(async () => {
7009
6915
  const DOCS_URL = "https://better-t-stack.dev/docs";
7010
6916
  try {
7011
6917
  await openUrl(DOCS_URL);
@@ -7014,7 +6920,7 @@ const router = t.router({
7014
6920
  log.message(`Please visit ${DOCS_URL}`);
7015
6921
  }
7016
6922
  }),
7017
- builder: t.procedure.meta({ description: "Open the web-based stack builder" }).mutation(async () => {
6923
+ builder: os.meta({ description: "Open the web-based stack builder" }).handler(async () => {
7018
6924
  const BUILDER_URL = "https://better-t-stack.dev/new";
7019
6925
  try {
7020
6926
  await openUrl(BUILDER_URL);
@@ -7024,7 +6930,7 @@ const router = t.router({
7024
6930
  }
7025
6931
  })
7026
6932
  });
7027
- const caller = t.createCallerFactory(router)({});
6933
+ const caller = createRouterClient(router, { context: {} });
7028
6934
  function createBtsCli() {
7029
6935
  return createCli({
7030
6936
  router,
@@ -7066,9 +6972,8 @@ function createBtsCli() {
7066
6972
  * ```
7067
6973
  */
7068
6974
  async function init(projectName, options) {
7069
- const opts = options ?? {};
7070
6975
  const programmaticOpts = {
7071
- ...opts,
6976
+ ...options ?? {},
7072
6977
  verbose: true
7073
6978
  };
7074
6979
  const prev = process.env.BTS_PROGRAMMATIC;