create-better-t-stack 3.42.2 → 3.43.1

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,26 +1,27 @@
1
1
  #!/usr/bin/env node
2
2
  import { n as __reExport, t as __exportAll } from "./rolldown-runtime-yhw22V8Z.mjs";
3
3
  import { getAllJsonSchemas } from "@better-t-stack/types/json-schema";
4
- import { initTRPC } from "@trpc/server";
4
+ import { os } from "@orpc/server";
5
5
  import { Result, Result as Result$1, TaggedError } from "better-result";
6
6
  import { createCli } from "trpc-cli";
7
- import z from "zod";
7
+ import z, { z as z$1 } from "zod";
8
8
  import { box, cancel, confirm, intro, isCancel, limitOptions, log, outro, select, spinner, text } from "@clack/prompts";
9
9
  import pc from "picocolors";
10
10
  import path from "node:path";
11
11
  import envPaths from "env-paths";
12
12
  import fs from "fs-extra";
13
13
  import { fileURLToPath } from "node:url";
14
- import { desktopWebFrontends as desktopWebFrontends$2, usesAlchemyManagedDatabase } from "@better-t-stack/types";
15
- import { EMBEDDED_TEMPLATES, EMBEDDED_TEMPLATES as EMBEDDED_TEMPLATES$1, GeneratorError, GeneratorError as GeneratorError$1, TEMPLATE_COUNT, VirtualFileSystem, VirtualFileSystem as VirtualFileSystem$1, dependencyVersionMap, generate, generate as generate$1, generateReproducibleCommand, processAddonTemplates, processAddonsDeps, processNxConfig, processPackageConfigs, processTemplateString, processTurboConfig, processVitePlusConfig } from "@better-t-stack/template-generator";
14
+ import { EMBEDDED_TEMPLATES, EMBEDDED_TEMPLATES as EMBEDDED_TEMPLATES$1, GeneratorError, GeneratorError as GeneratorError$1, TEMPLATE_COUNT, VirtualFileSystem, VirtualFileSystem as VirtualFileSystem$1, dependencyVersionMap, generate, generate as generate$1, generateReproducibleCommand, processAddonTemplates, processAddonsDeps, processNxConfig, processPackageConfigs, processPwaPlugins, processTemplateString, processTurboConfig, processVitePlusConfig } from "@better-t-stack/template-generator";
15
+ import { BetterTStackConfigSchema, CONVEX_AI_INCOMPATIBLE_FRONTENDS, CONVEX_BETTER_AUTH_INCOMPATIBLE_FRONTENDS, CONVEX_BETTER_AUTH_SUPPORTED_FRONTENDS, FULLSTACK_FRONTENDS, OBSERVABILITY_ADDONS, OBSERVABILITY_ADDONS as OBSERVABILITY_ADDONS$1, SERVER_BACKENDS, TASK_RUNNER_ADDONS, TASK_RUNNER_ADDONS as TASK_RUNNER_ADDONS$1, TRPC_INCOMPATIBLE_FRONTENDS, allowedApisForFrontends, allowedApisForFrontends as allowedApisForFrontends$1, getBackendDisabledOptions, getDesktopDeployConflict, hasCloudflareNextPostgresConflict, isExampleAIAllowed, isExampleAIAllowed as isExampleAIAllowed$1, isExampleTodoAllowed, isExampleTodoAllowed as isExampleTodoAllowed$1, isFrontendAllowedWithBackend, isFrontendAllowedWithBackend as isFrontendAllowedWithBackend$1, supportsClerkBackend, supportsClerkFrontend, supportsConvexBetterAuth, supportsDatabaseSetup, supportsDatabaseSetupRuntime, supportsOrmDatabase, supportsPaymentsAuth, supportsPrismaWebDeploy, supportsPrismaWebDeploy as supportsPrismaWebDeploy$1, supportsRuntimeBackend, supportsRuntimeDatabase, supportsServerDeployRuntime, usesAlchemyManagedDatabase, validateAddonCompatibility, validateAddonCompatibility as validateAddonCompatibility$1, webFrontends } from "@better-t-stack/types";
16
+ import { ConfirmPrompt, GroupMultiSelectPrompt, MultiSelectPrompt, SelectPrompt, isCancel as isCancel$1, wrapTextWithPrefix } from "@clack/core";
16
17
  import { consola, createConsola } from "consola";
17
18
  import { AsyncLocalStorage } from "node:async_hooks";
18
19
  import gradient from "gradient-string";
19
20
  import { $, execa } from "execa";
20
21
  import { writeTree } from "@better-t-stack/template-generator/fs-writer";
21
- import { ConfirmPrompt, GroupMultiSelectPrompt, MultiSelectPrompt, SelectPrompt, isCancel as isCancel$1 } from "@clack/core";
22
+ import * as fs$1 from "node:fs/promises";
22
23
  import { applyEdits, modify, parse } from "jsonc-parser";
23
- import os from "node:os";
24
+ import os$1 from "node:os";
24
25
  import { clean, coerce, satisfies } from "semver";
25
26
  import { format } from "oxfmt";
26
27
  //#region src/utils/display-config.ts
@@ -76,6 +77,7 @@ const VALUE_LABELS = {
76
77
  skills: "Agent skills",
77
78
  mcp: "MCP servers",
78
79
  evlog: "evlog",
80
+ axiom: "Axiom",
79
81
  todo: "Todo app",
80
82
  ai: "AI chat",
81
83
  turso: "Turso",
@@ -205,32 +207,6 @@ function getDefaultConfig() {
205
207
  };
206
208
  }
207
209
  const DEFAULT_CONFIG = getDefaultConfig();
208
- const ADDON_COMPATIBILITY = {
209
- pwa: [
210
- "tanstack-router",
211
- "react-router",
212
- "solid",
213
- "next"
214
- ],
215
- tauri: desktopWebFrontends$2,
216
- electrobun: desktopWebFrontends$2,
217
- biome: [],
218
- husky: [],
219
- lefthook: [],
220
- turborepo: [],
221
- nx: [],
222
- "vite-plus": [],
223
- starlight: [],
224
- ultracite: [],
225
- mcp: [],
226
- oxlint: [],
227
- fumadocs: [],
228
- opentui: [],
229
- wxt: [],
230
- skills: [],
231
- evlog: [],
232
- none: []
233
- };
234
210
  //#endregion
235
211
  //#region src/utils/context.ts
236
212
  const cliStorage = new AsyncLocalStorage();
@@ -396,42 +372,52 @@ const noopSpinner = {
396
372
  const FRAME_MS = 80;
397
373
  const HIDE_CURSOR = "\x1B[?25l";
398
374
  const SHOW_CURSOR = "\x1B[?25h";
399
- const CLEAR_LINE = "\r\x1B[2K";
400
- let cursorHidden = false;
375
+ const ERASE_DOWN = "\r\x1B[J";
376
+ const cursorUp = (rows) => `\x1b[${rows}A`;
377
+ let cursorOutput;
401
378
  let restoreCursorOnExit = false;
402
- function hideCursor() {
403
- if (cursorHidden) return;
404
- cursorHidden = true;
405
- process.stdout.write(HIDE_CURSOR);
379
+ function hideCursor(out) {
380
+ if (cursorOutput) return;
381
+ cursorOutput = out;
382
+ out.write(HIDE_CURSOR);
406
383
  if (!restoreCursorOnExit) {
407
384
  restoreCursorOnExit = true;
408
385
  process.once("exit", () => {
409
- if (cursorHidden) process.stdout.write(SHOW_CURSOR);
386
+ cursorOutput?.write(SHOW_CURSOR);
410
387
  });
411
388
  }
412
389
  }
413
- function showCursor() {
414
- if (!cursorHidden) return;
415
- cursorHidden = false;
416
- process.stdout.write(SHOW_CURSOR);
390
+ function showCursor(out) {
391
+ if (cursorOutput !== out) return;
392
+ cursorOutput = void 0;
393
+ out.write(SHOW_CURSOR);
417
394
  }
418
395
  /**
419
396
  * The clack spinner puts stdin in raw mode and exits the process on Ctrl-C from its own
420
397
  * keypress handler. This one never touches stdin, so Ctrl-C stays a SIGINT and the
421
398
  * interrupt scope decides what happens.
422
399
  */
423
- function createTerminalSpinner() {
424
- const out = process.stdout;
425
- const animate = out.isTTY && !process.env.CI;
400
+ function createTerminalSpinner(out) {
401
+ const animate = Boolean(out.isTTY) && !process.env.CI;
426
402
  let text = "";
427
403
  let active = false;
428
404
  let interruptedBefore = false;
429
405
  let frame = 0;
430
406
  let dots = 0;
407
+ let renderedRows = 0;
431
408
  let timer;
409
+ const clearFrame = () => {
410
+ if (renderedRows > 1) out.write(cursorUp(renderedRows - 1));
411
+ out.write(ERASE_DOWN);
412
+ renderedRows = 0;
413
+ };
432
414
  const render = () => {
433
415
  const suffix = ".".repeat(Math.floor(dots)).slice(0, 3);
434
- out.write(`${CLEAR_LINE}${pc.magenta(SPINNER_FRAMES[frame])} ${text}${suffix}`);
416
+ const line = `${pc.magenta(SPINNER_FRAMES[frame])} ${text}${suffix}`;
417
+ clearFrame();
418
+ const wrapped = wrapTextWithPrefix(out, line, "");
419
+ out.write(wrapped.replaceAll("\n", "\r\n"));
420
+ renderedRows = wrapped.split("\n").length;
435
421
  frame = (frame + 1) % SPINNER_FRAMES.length;
436
422
  dots = dots < 4 ? dots + .125 : 0;
437
423
  };
@@ -446,7 +432,7 @@ function createTerminalSpinner() {
446
432
  setText(message);
447
433
  out.write(`${pc.gray(S_BAR)}\n`);
448
434
  if (animate) {
449
- hideCursor();
435
+ hideCursor(out);
450
436
  render();
451
437
  timer = setInterval(render, FRAME_MS);
452
438
  } else out.write(`${pc.magenta(SPINNER_FRAMES[0])} ${text}\n`);
@@ -457,16 +443,16 @@ function createTerminalSpinner() {
457
443
  active = false;
458
444
  if (timer) clearInterval(timer);
459
445
  if (animate) {
460
- out.write(CLEAR_LINE);
461
- showCursor();
446
+ clearFrame();
447
+ showCursor(out);
462
448
  }
463
449
  const cancelled = wasInterrupted() && !interruptedBefore;
464
450
  out.write(cancelled ? `${pc.yellow(S_STEP_CANCEL)} ${text} (cancelled)\n` : `${pc.green(S_STEP_SUBMIT)} ${message || text}\n`);
465
451
  }
466
452
  };
467
453
  }
468
- function createSpinner() {
469
- return isSilent() ? noopSpinner : createTerminalSpinner();
454
+ function createSpinner(output) {
455
+ return isSilent() ? noopSpinner : createTerminalSpinner(output ?? process.stdout);
470
456
  }
471
457
  const baseConsola = createConsola({
472
458
  ...consola.options,
@@ -1059,40 +1045,9 @@ import * as import__better_t_stack_types from "@better-t-stack/types";
1059
1045
  __reExport(types_exports, import__better_t_stack_types);
1060
1046
  //#endregion
1061
1047
  //#region src/utils/compatibility.ts
1062
- const WEB_FRAMEWORKS = [
1063
- "tanstack-router",
1064
- "react-router",
1065
- "tanstack-start",
1066
- "next",
1067
- "nuxt",
1068
- "svelte",
1069
- "solid",
1070
- "astro"
1071
- ];
1048
+ const WEB_FRAMEWORKS = webFrontends;
1072
1049
  //#endregion
1073
1050
  //#region src/utils/compatibility-rules.ts
1074
- const TASK_RUNNER_ADDONS = [
1075
- "turborepo",
1076
- "nx",
1077
- "vite-plus"
1078
- ];
1079
- const STATIC_DESKTOP_ADDONS = ["tauri", "electrobun"];
1080
- const TAURI_STATIC_EXPORT_FRONTENDS = ["next", "tanstack-start"];
1081
- const CONVEX_BETTER_AUTH_INCOMPATIBLE_FRONTENDS = [
1082
- "nuxt",
1083
- "svelte",
1084
- "solid",
1085
- "astro"
1086
- ];
1087
- const CONVEX_BETTER_AUTH_SUPPORTED_FRONTENDS = [
1088
- "tanstack-router",
1089
- "react-router",
1090
- "tanstack-start",
1091
- "next",
1092
- "native-bare",
1093
- "native-uniwind",
1094
- "native-unistyles"
1095
- ];
1096
1051
  function validationErr$1(message) {
1097
1052
  return Result.err(new ValidationError({ message }));
1098
1053
  }
@@ -1111,111 +1066,29 @@ function ensureSingleWebAndNative(frontends) {
1111
1066
  if (native.length > 1) return validationErr$1("Cannot select multiple native frameworks. Choose only one of: native-bare, native-uniwind, native-unistyles");
1112
1067
  return Result.ok(void 0);
1113
1068
  }
1114
- const FULLSTACK_FRONTENDS$1 = [
1115
- "next",
1116
- "tanstack-start",
1117
- "nuxt",
1118
- "svelte",
1119
- "solid",
1120
- "astro"
1121
- ];
1122
- const EVLOG_SERVER_BACKENDS = [
1123
- "hono",
1124
- "express",
1125
- "fastify",
1126
- "elysia"
1127
- ];
1128
- const EVLOG_FULLSTACK_FRONTENDS = [
1129
- "next",
1130
- "tanstack-start",
1131
- "nuxt",
1132
- "svelte",
1133
- "astro"
1134
- ];
1135
- const evlogCompatibilityMessage = "evlog addon supports Hono, Express, Fastify, Elysia, or backend self with Next.js, TanStack Start, Nuxt, SvelteKit, or Astro. Convex and backend none are not supported yet.";
1136
- function supportsEvlogAddon(frontend = [], backend, _runtime) {
1137
- if (!backend) return true;
1138
- if (EVLOG_SERVER_BACKENDS.includes(backend)) return true;
1139
- if (backend === "self") {
1140
- if (frontend.length === 0) return true;
1141
- return frontend.some((f) => EVLOG_FULLSTACK_FRONTENDS.includes(f));
1142
- }
1143
- return false;
1144
- }
1145
- function validateSelfBackendCompatibility(providedFlags, options, config) {
1069
+ function validateSelfBackendCompatibility(_providedFlags, options, config) {
1146
1070
  const backend = config.backend || options.backend;
1147
1071
  const frontends = config.frontend || options.frontend || [];
1148
1072
  if (backend === "self") {
1149
1073
  const { web, native } = splitFrontends(frontends);
1150
- if (!(web.length === 1 && FULLSTACK_FRONTENDS$1.includes(web[0]))) return validationErr$1("Backend 'self' (fullstack) currently only supports Next.js, TanStack Start, Nuxt, SvelteKit, Solid, and Astro frontends. Please use --frontend next, --frontend tanstack-start, --frontend nuxt, --frontend svelte, --frontend solid, or --frontend astro.");
1074
+ if (!(web.length === 1 && FULLSTACK_FRONTENDS.some((frontend) => frontend === web[0]))) return validationErr$1("Backend 'self' (fullstack) currently only supports Next.js, TanStack Start, Nuxt, SvelteKit, Solid, and Astro frontends. Please use --frontend next, --frontend tanstack-start, --frontend nuxt, --frontend svelte, --frontend solid, or --frontend astro.");
1151
1075
  if (native.length > 1) return validationErr$1("Cannot select multiple native frameworks. Choose only one of: native-bare, native-uniwind, native-unistyles");
1152
1076
  }
1153
- const hasFullstackFrontend = frontends.some((f) => FULLSTACK_FRONTENDS$1.includes(f));
1154
- if (providedFlags.has("backend") && !hasFullstackFrontend && backend === "self") return validationErr$1("Backend 'self' (fullstack) currently only supports Next.js, TanStack Start, Nuxt, SvelteKit, Solid, and Astro frontends. Please use --frontend next, --frontend tanstack-start, --frontend nuxt, --frontend svelte, --frontend solid, --frontend astro, or choose a different backend.");
1155
1077
  return Result.ok(void 0);
1156
1078
  }
1157
1079
  function validateWorkersCompatibility(providedFlags, options, config) {
1158
- if (providedFlags.has("runtime") && options.runtime === "workers" && config.backend && config.backend !== "hono") return validationErr$1(`Cloudflare Workers runtime (--runtime workers) is only supported with Hono backend (--backend hono). Current backend: ${config.backend}. Please use '--backend hono' or choose a different runtime.`);
1159
- if (providedFlags.has("backend") && config.backend && config.backend !== "hono" && config.runtime === "workers") return validationErr$1(`Backend '${config.backend}' is not compatible with Cloudflare Workers runtime. Cloudflare Workers runtime is only supported with Hono backend. Please use '--backend hono' or choose a different runtime.`);
1160
- if (providedFlags.has("runtime") && options.runtime === "workers" && config.database === "mongodb") return validationErr$1("Cloudflare Workers runtime (--runtime workers) is not compatible with MongoDB database. MongoDB requires Prisma or Mongoose ORM, but Workers runtime only supports Drizzle or Prisma ORM. Please use a different database or runtime.");
1161
- if (providedFlags.has("database") && config.database === "mongodb" && config.runtime === "workers") return validationErr$1("MongoDB database is not compatible with Cloudflare Workers runtime. MongoDB requires Prisma or Mongoose ORM, but Workers runtime only supports Drizzle or Prisma ORM. Please use a different database or runtime.");
1080
+ if (providedFlags.has("runtime") && options.runtime === "workers" && config.backend && !supportsRuntimeBackend("workers", config.backend)) return validationErr$1(`Cloudflare Workers runtime (--runtime workers) is only supported with Hono backend (--backend hono). Current backend: ${config.backend}. Please use '--backend hono' or choose a different runtime.`);
1081
+ if (providedFlags.has("backend") && config.backend && !supportsRuntimeBackend("workers", config.backend) && config.runtime === "workers") return validationErr$1(`Backend '${config.backend}' is not compatible with Cloudflare Workers runtime. Cloudflare Workers runtime is only supported with Hono backend. Please use '--backend hono' or choose a different runtime.`);
1082
+ if (providedFlags.has("runtime") && !supportsRuntimeDatabase(options.runtime, config.database)) return validationErr$1("Cloudflare Workers runtime (--runtime workers) is not compatible with MongoDB database. MongoDB requires Prisma or Mongoose ORM, but Workers runtime only supports Drizzle or Prisma ORM. Please use a different database or runtime.");
1083
+ if (providedFlags.has("database") && !supportsRuntimeDatabase(config.runtime, config.database)) return validationErr$1("MongoDB database is not compatible with Cloudflare Workers runtime. MongoDB requires Prisma or Mongoose ORM, but Workers runtime only supports Drizzle or Prisma ORM. Please use a different database or runtime.");
1162
1084
  return Result.ok(void 0);
1163
1085
  }
1164
1086
  function validateApiFrontendCompatibility(api, frontends = []) {
1165
- const includesNuxt = frontends.includes("nuxt");
1166
- const includesSvelte = frontends.includes("svelte");
1167
- const includesSolid = frontends.includes("solid");
1168
- const includesAstro = frontends.includes("astro");
1169
- if ((includesNuxt || includesSvelte || includesSolid || includesAstro) && api === "trpc") return validationErr$1(`tRPC API is not supported with '${includesNuxt ? "nuxt" : includesSvelte ? "svelte" : includesSolid ? "solid" : "astro"}' frontend. Please use --api orpc or --api none or remove '${includesNuxt ? "nuxt" : includesSvelte ? "svelte" : includesSolid ? "solid" : "astro"}' from --frontend.`);
1170
- return Result.ok(void 0);
1171
- }
1172
- function isFrontendAllowedWithBackend(frontend, backend, auth) {
1173
- if (backend === "convex") {
1174
- if (auth === "better-auth" && CONVEX_BETTER_AUTH_INCOMPATIBLE_FRONTENDS.includes(frontend)) return false;
1175
- if (frontend === "solid" || frontend === "astro") return false;
1087
+ if (api === "trpc" && !allowedApisForFrontends(frontends).includes(api)) {
1088
+ const frontend = frontends.find((f) => TRPC_INCOMPATIBLE_FRONTENDS.some((value) => value === f));
1089
+ return validationErr$1(`tRPC API is not supported with '${frontend}' frontend. Please use --api orpc or --api none or remove '${frontend}' from --frontend.`);
1176
1090
  }
1177
- if (auth === "clerk") {
1178
- if ([
1179
- "nuxt",
1180
- "svelte",
1181
- "solid",
1182
- "astro"
1183
- ].includes(frontend)) return false;
1184
- }
1185
- return true;
1186
- }
1187
- function supportsConvexBetterAuth(frontends = []) {
1188
- return frontends.some((frontend) => CONVEX_BETTER_AUTH_SUPPORTED_FRONTENDS.includes(frontend));
1189
- }
1190
- function allowedApisForFrontends(frontends = []) {
1191
- const includesNuxt = frontends.includes("nuxt");
1192
- const includesSvelte = frontends.includes("svelte");
1193
- const includesSolid = frontends.includes("solid");
1194
- const includesAstro = frontends.includes("astro");
1195
- const base = [
1196
- "trpc",
1197
- "orpc",
1198
- "none"
1199
- ];
1200
- if (includesNuxt || includesSvelte || includesSolid || includesAstro) return ["orpc", "none"];
1201
- return base;
1202
- }
1203
- function isExampleTodoAllowed(backend, database, api) {
1204
- if (backend === "convex") return true;
1205
- if (database === "none" || api === "none") return false;
1206
- return true;
1207
- }
1208
- function isExampleAIAllowed(backend, frontends = []) {
1209
- if (backend === "none") return false;
1210
- const includesSolid = frontends.includes("solid");
1211
- const includesAstro = frontends.includes("astro");
1212
- if (includesSolid || includesAstro) return false;
1213
- if (backend === "convex") {
1214
- const includesNuxt = frontends.includes("nuxt");
1215
- const includesSvelte = frontends.includes("svelte");
1216
- if (includesNuxt || includesSvelte) return false;
1217
- }
1218
- return true;
1091
+ return Result.ok(void 0);
1219
1092
  }
1220
1093
  function validateWebDeployRequiresWebFrontend(webDeploy, hasWebFrontendFlag) {
1221
1094
  if (webDeploy && webDeploy !== "none" && !hasWebFrontendFlag) return validationErr$1("'--web-deploy' requires a web frontend. Please select a web frontend or set '--web-deploy none'.");
@@ -1227,97 +1100,48 @@ function validateServerDeployRequiresBackend(serverDeploy, backend) {
1227
1100
  }
1228
1101
  function validateDockerServerDeploy(serverDeploy, backend, runtime) {
1229
1102
  if (serverDeploy !== "docker") return Result.ok(void 0);
1230
- if (backend === "convex" || backend === "self") return validationErr$1("'--server-deploy docker' requires a separate server backend (hono, express, fastify, elysia). For a fullstack 'self' backend, use '--web-deploy docker' instead.");
1231
- if (runtime === "workers") return validationErr$1("'--server-deploy docker' is not compatible with '--runtime workers'. Use '--runtime bun' or '--runtime node', or choose '--server-deploy cloudflare'.");
1103
+ if (backend && backend !== "none" && !SERVER_BACKENDS.includes(backend)) return validationErr$1("'--server-deploy docker' requires a separate server backend (hono, express, fastify, elysia). For a fullstack 'self' backend, use '--web-deploy docker' instead.");
1104
+ if (runtime && !supportsServerDeployRuntime(serverDeploy, runtime)) return validationErr$1("'--server-deploy docker' is not compatible with '--runtime workers'. Use '--runtime bun' or '--runtime node', or choose '--server-deploy cloudflare'.");
1232
1105
  return Result.ok(void 0);
1233
1106
  }
1234
1107
  function validateVercelServerDeploy(serverDeploy, backend, runtime) {
1235
1108
  if (serverDeploy !== "vercel") return Result.ok(void 0);
1236
- if (backend === "convex" || backend === "self") return validationErr$1("'--server-deploy vercel' requires a separate server backend (hono, express, fastify, elysia). For a fullstack 'self' backend, use '--web-deploy vercel' instead.");
1237
- if (runtime === "workers") return validationErr$1("'--server-deploy vercel' is not compatible with '--runtime workers'. Use '--runtime bun' or '--runtime node', or choose '--server-deploy cloudflare'.");
1109
+ if (backend && backend !== "none" && !SERVER_BACKENDS.includes(backend)) return validationErr$1("'--server-deploy vercel' requires a separate server backend (hono, express, fastify, elysia). For a fullstack 'self' backend, use '--web-deploy vercel' instead.");
1110
+ if (runtime && !supportsServerDeployRuntime(serverDeploy, runtime)) return validationErr$1("'--server-deploy vercel' is not compatible with '--runtime workers'. Use '--runtime bun' or '--runtime node', or choose '--server-deploy cloudflare'.");
1238
1111
  return Result.ok(void 0);
1239
1112
  }
1240
1113
  function validatePrismaServerDeploy(serverDeploy, backend, runtime) {
1241
1114
  if (serverDeploy !== "prisma") return Result.ok(void 0);
1242
- if (backend === "convex" || backend === "self") return validationErr$1("'--server-deploy prisma' requires a separate server backend (hono, express, fastify, elysia). For a fullstack 'self' backend, use '--web-deploy prisma' instead.");
1243
- if (runtime !== "bun" && runtime !== "node") return validationErr$1("'--server-deploy prisma' requires '--runtime bun' or '--runtime node'. Use '--server-deploy cloudflare' for Workers.");
1115
+ if (backend && backend !== "none" && !SERVER_BACKENDS.includes(backend)) return validationErr$1("'--server-deploy prisma' requires a separate server backend (hono, express, fastify, elysia). For a fullstack 'self' backend, use '--web-deploy prisma' instead.");
1116
+ if (!supportsServerDeployRuntime(serverDeploy, runtime)) return validationErr$1("'--server-deploy prisma' requires '--runtime bun' or '--runtime node'. Use '--server-deploy cloudflare' for Workers.");
1244
1117
  return Result.ok(void 0);
1245
1118
  }
1246
- const PRISMA_COMPUTE_WEB_FRONTENDS = [
1247
- "next",
1248
- "nuxt",
1249
- "astro",
1250
- "react-router",
1251
- "tanstack-start",
1252
- "svelte",
1253
- "solid"
1254
- ];
1255
- function supportsPrismaWebDeploy(frontend) {
1256
- return frontend.some((value) => PRISMA_COMPUTE_WEB_FRONTENDS.includes(value));
1257
- }
1258
1119
  function validatePrismaWebDeploy(webDeploy, frontend) {
1259
1120
  if (webDeploy !== "prisma" || !frontend) return Result.ok(void 0);
1260
1121
  if (!supportsPrismaWebDeploy(frontend)) return validationErr$1("'--web-deploy prisma' requires a supported server frontend. Choose Next.js, Nuxt, Astro, React Router, TanStack Start, SvelteKit, or Solid. TanStack Router is a static SPA, while Prisma Compute requires an executable server artifact.");
1261
1122
  return Result.ok(void 0);
1262
1123
  }
1263
1124
  function validateCloudflareWebDeployKnownIssues(config) {
1264
- if (config.webDeploy !== "cloudflare" || !config.frontend?.includes("next")) return Result.ok(void 0);
1265
- if (config.database === "postgres" && config.orm === "prisma" && config.dbSetup !== "neon" && config.dbSetup !== "prisma-postgres") return validationErr$1("This Prisma PostgreSQL setup with Next.js on Cloudflare is temporarily unavailable because OpenNext does not preserve pg-cloudflare's workerd files. Use Neon or Prisma Postgres, choose another Cloudflare frontend, or choose Prisma, Docker, or Vercel deployment.");
1125
+ if (hasCloudflareNextPostgresConflict(config)) return validationErr$1("This Prisma PostgreSQL setup with Next.js on Cloudflare is temporarily unavailable because OpenNext does not preserve pg-cloudflare's workerd files. Use Neon or Prisma Postgres, choose another Cloudflare frontend, or choose Prisma, Docker, or Vercel deployment.");
1266
1126
  return Result.ok(void 0);
1267
1127
  }
1268
- const DESKTOP_STATIC_EXPORT_FRONTENDS = [
1269
- "next",
1270
- "svelte",
1271
- "astro",
1272
- "react-router"
1273
- ];
1274
1128
  function validateDockerWebDeployDesktopAddons(webDeploy, addons, frontend, backend, auth) {
1275
- if (webDeploy !== "docker" || !addons || !frontend) return Result.ok(void 0);
1276
- const desktopAddons = addons.filter((addon) => STATIC_DESKTOP_ADDONS.includes(addon));
1277
- if (desktopAddons.length === 0) return Result.ok(void 0);
1278
- const affected = frontend.find((f) => DESKTOP_STATIC_EXPORT_FRONTENDS.includes(f));
1279
- if (!affected) return Result.ok(void 0);
1280
- if (affected === "next" && !desktopAddons.includes("tauri") && backend === "convex" && auth === "better-auth") return Result.ok(void 0);
1129
+ const conflict = getDesktopDeployConflict(webDeploy, addons, frontend, backend, auth);
1130
+ if (webDeploy !== "docker" || !conflict) return Result.ok(void 0);
1131
+ const { selectedDesktopAddons: desktopAddons, affectedFrontend: affected } = conflict;
1281
1132
  return validationErr$1(`'--web-deploy docker' is not compatible with the ${desktopAddons.join(", ")} addon on '${affected}' because desktop addons switch the web build to a static export, which the docker image cannot serve. Remove the addon or use the static-serving tanstack-router frontend.`);
1282
1133
  }
1283
1134
  function validatePrismaWebDeployDesktopAddons(webDeploy, addons, frontend) {
1284
- if (webDeploy !== "prisma" || !addons || !frontend) return Result.ok(void 0);
1285
- const desktopAddons = addons.filter((addon) => STATIC_DESKTOP_ADDONS.includes(addon));
1286
- if (desktopAddons.length === 0) return Result.ok(void 0);
1287
- const affected = frontend.find((value) => DESKTOP_STATIC_EXPORT_FRONTENDS.includes(value));
1288
- if (!affected) return Result.ok(void 0);
1135
+ const conflict = getDesktopDeployConflict(webDeploy, addons, frontend);
1136
+ if (webDeploy !== "prisma" || !conflict) return Result.ok(void 0);
1137
+ const { selectedDesktopAddons: desktopAddons, affectedFrontend: affected } = conflict;
1289
1138
  return validationErr$1(`'--web-deploy prisma' is not compatible with the ${desktopAddons.join(", ")} addon on '${affected}' because desktop addons replace its executable server output with a static export, while Prisma Compute requires an executable server artifact. Remove the addon or choose a server deployment that supports this desktop build.`);
1290
1139
  }
1291
- function validateAddonCompatibility(addon, frontend, auth, backend, runtime) {
1292
- if (addon === "evlog" && !supportsEvlogAddon(frontend, backend, runtime)) return {
1293
- isCompatible: false,
1294
- reason: evlogCompatibilityMessage
1295
- };
1296
- if (STATIC_DESKTOP_ADDONS.includes(addon) && auth === "clerk" && frontend.includes("react-router")) return {
1297
- isCompatible: false,
1298
- reason: `${addon} addon forces React Router into a static export, but Clerk on React Router requires SSR middleware. Remove the addon or use a different auth/frontend.`
1299
- };
1300
- if (backend === "self" && STATIC_DESKTOP_ADDONS.includes(addon)) return {
1301
- isCompatible: false,
1302
- reason: `${addon} addon requires a separate backend or no backend because backend 'self' emits server routes that cannot be bundled as static desktop assets.`
1303
- };
1304
- if (addon === "tauri" && backend === "convex" && auth === "better-auth" && frontend.some((f) => TAURI_STATIC_EXPORT_FRONTENDS.includes(f))) return {
1305
- isCompatible: false,
1306
- reason: "tauri addon is not compatible with Convex Better Auth on Next.js or TanStack Start because those templates use server auth bootstrap and cannot be exported as static desktop assets."
1307
- };
1308
- const compatibleFrontends = ADDON_COMPATIBILITY[addon];
1309
- if (compatibleFrontends.length > 0) {
1310
- if (!frontend.some((f) => compatibleFrontends.includes(f))) return {
1311
- isCompatible: false,
1312
- reason: `${addon} addon requires one of these frontends: ${compatibleFrontends.join(", ")}`
1313
- };
1314
- }
1315
- return { isCompatible: true };
1316
- }
1317
1140
  function getCompatibleAddons(allAddons, frontend, existingAddons = [], auth, backend, runtime) {
1318
1141
  return allAddons.filter((addon) => {
1319
1142
  if (existingAddons.includes(addon)) return false;
1320
1143
  if (addon === "none") return false;
1144
+ if (OBSERVABILITY_ADDONS.includes(addon) && existingAddons.some((existing) => OBSERVABILITY_ADDONS.includes(existing))) return false;
1321
1145
  if (TASK_RUNNER_ADDONS.includes(addon) && existingAddons.some((existingAddon) => TASK_RUNNER_ADDONS.includes(existingAddon))) return false;
1322
1146
  const { isCompatible } = validateAddonCompatibility(addon, frontend, auth, backend, runtime);
1323
1147
  return isCompatible;
@@ -1327,8 +1151,8 @@ function validateAddonsAgainstFrontends(addons = [], frontends = [], auth, backe
1327
1151
  if (addons.filter((addon) => TASK_RUNNER_ADDONS.includes(addon)).length > 1) return validationErr$1("Cannot combine 'turborepo', 'nx', and 'vite-plus' addons. Choose one task runner.");
1328
1152
  for (const addon of addons) {
1329
1153
  if (addon === "none") continue;
1330
- const { isCompatible, reason } = validateAddonCompatibility(addon, frontends, auth, backend, runtime);
1331
- if (!isCompatible) return validationErr$1(`Incompatible addon/frontend combination: ${reason}`);
1154
+ const compatibility = validateAddonCompatibility(addon, frontends, auth, backend, runtime);
1155
+ if (!compatibility.isCompatible) return validationErr$1(`Incompatible addon/frontend combination: ${compatibility.reason}`);
1332
1156
  }
1333
1157
  return Result.ok(void 0);
1334
1158
  }
@@ -1344,25 +1168,22 @@ function validateAddonsAgainstConfig(addons = [], config) {
1344
1168
  function validatePaymentsCompatibility(payments, auth, _backend, _frontends = []) {
1345
1169
  if (!payments || payments === "none") return Result.ok(void 0);
1346
1170
  if (payments === "polar") {
1347
- if (!auth || auth === "none" || auth !== "better-auth") return validationErr$1("Polar payments requires Better Auth. Please use '--auth better-auth' or choose a different payments provider.");
1171
+ if (!supportsPaymentsAuth(payments, auth)) return validationErr$1("Polar payments requires Better Auth. Please use '--auth better-auth' or choose a different payments provider.");
1348
1172
  }
1349
1173
  return Result.ok(void 0);
1350
1174
  }
1351
1175
  function validateExamplesCompatibility(examples, backend, database, frontend, api) {
1352
1176
  const examplesArr = examples ?? [];
1353
1177
  if (examplesArr.length === 0 || examplesArr.includes("none")) return Result.ok(void 0);
1354
- if (examplesArr.includes("todo") && backend !== "convex") {
1178
+ if (examplesArr.includes("todo") && !isExampleTodoAllowed(backend, database, api)) {
1355
1179
  if (database === "none") return validationErr$1("The 'todo' example requires a database. Cannot use --examples todo when database is 'none'.");
1356
1180
  if (api === "none") return validationErr$1("The 'todo' example requires an API layer (tRPC or oRPC). Cannot use --examples todo when api is 'none'.");
1357
1181
  }
1358
- if (examplesArr.includes("ai") && (frontend ?? []).includes("solid")) return validationErr$1("The 'ai' example is not compatible with the Solid frontend.");
1359
- if (examplesArr.includes("ai") && (frontend ?? []).includes("astro")) return validationErr$1("The 'ai' example is not compatible with the Astro frontend.");
1182
+ if (examplesArr.includes("ai") && !isExampleAIAllowed(backend, frontend) && frontend?.includes("solid")) return validationErr$1("The 'ai' example is not compatible with the Solid frontend.");
1183
+ if (examplesArr.includes("ai") && !isExampleAIAllowed(backend, frontend) && frontend?.includes("astro")) return validationErr$1("The 'ai' example is not compatible with the Astro frontend.");
1360
1184
  if (examplesArr.includes("ai") && backend === "none") return validationErr$1("The 'ai' example requires a backend.");
1361
1185
  if (examplesArr.includes("ai") && backend === "convex") {
1362
- const frontendArr = frontend ?? [];
1363
- const includesNuxt = frontendArr.includes("nuxt");
1364
- const includesSvelte = frontendArr.includes("svelte");
1365
- if (includesNuxt || includesSvelte) return validationErr$1("The 'ai' example with Convex backend only supports React-based frontends (Next.js, TanStack Router, TanStack Start, React Router). Svelte and Nuxt are not supported with Convex AI.");
1186
+ if ((frontend ?? []).some((f) => CONVEX_AI_INCOMPATIBLE_FRONTENDS.some((value) => value === f))) return validationErr$1("The 'ai' example with Convex backend only supports React-based frontends (Next.js, TanStack Router, TanStack Start, React Router). Svelte and Nuxt are not supported with Convex AI.");
1366
1187
  }
1367
1188
  return Result.ok(void 0);
1368
1189
  }
@@ -1755,6 +1576,10 @@ function getAddonDisplay(addon) {
1755
1576
  label = "evlog";
1756
1577
  hint = "Request logging with Better Auth context and AI SDK telemetry";
1757
1578
  break;
1579
+ case "axiom":
1580
+ label = "Axiom";
1581
+ hint = "Managed production observability through evlog and Alchemy";
1582
+ break;
1758
1583
  default:
1759
1584
  label = addon;
1760
1585
  hint = `Add ${addon}`;
@@ -1785,7 +1610,7 @@ const ADDON_GROUPS = {
1785
1610
  "opentui",
1786
1611
  "wxt"
1787
1612
  ],
1788
- Observability: ["evlog"],
1613
+ Observability: ["evlog", "axiom"],
1789
1614
  "AI & Agent Tools": ["skills", "mcp"]
1790
1615
  };
1791
1616
  function createGroupedOptions() {
@@ -1810,7 +1635,8 @@ function sortAndPruneGroupedOptions(groupedOptions) {
1810
1635
  });
1811
1636
  }
1812
1637
  function validateAddonSelection(selected) {
1813
- if ((selected?.filter((addon) => TASK_RUNNER_ADDONS.includes(addon)) ?? []).length > 1) return "Choose Turborepo, Nx, or Vite+ as your task runner, not more than one.";
1638
+ if ((selected?.filter((addon) => TASK_RUNNER_ADDONS$1.includes(addon)) ?? []).length > 1) return "Choose Turborepo, Nx, or Vite+ as your task runner, not more than one.";
1639
+ if ((selected?.filter((addon) => OBSERVABILITY_ADDONS$1.includes(addon)) ?? []).length > 1) return "Choose evlog or Axiom, not both. Axiom already includes evlog.";
1814
1640
  }
1815
1641
  async function getAddonsChoice(addons, frontends, auth, backend, runtime, previousValue) {
1816
1642
  if (addons !== void 0) return addons;
@@ -1818,7 +1644,7 @@ async function getAddonsChoice(addons, frontends, auth, backend, runtime, previo
1818
1644
  const groupedOptions = createGroupedOptions();
1819
1645
  const frontendsArray = frontends || [];
1820
1646
  for (const addon of allAddons) {
1821
- const { isCompatible } = validateAddonCompatibility(addon, frontendsArray, auth, backend, runtime);
1647
+ const { isCompatible } = validateAddonCompatibility$1(addon, frontendsArray, auth, backend, runtime);
1822
1648
  if (!isCompatible) continue;
1823
1649
  const { label, hint } = getAddonDisplay(addon);
1824
1650
  addOptionToGroup(groupedOptions, {
@@ -1833,7 +1659,7 @@ async function getAddonsChoice(addons, frontends, auth, backend, runtime, previo
1833
1659
  options: groupedOptions,
1834
1660
  initialValues: (previousValue ?? DEFAULT_CONFIG.addons).filter((addonValue) => Object.values(groupedOptions).some((options) => options.some((opt) => opt.value === addonValue))),
1835
1661
  required: false,
1836
- exclusive: [TASK_RUNNER_ADDONS],
1662
+ exclusive: [TASK_RUNNER_ADDONS$1, OBSERVABILITY_ADDONS$1],
1837
1663
  validate: validateAddonSelection
1838
1664
  });
1839
1665
  if (isCancel$1(response)) throw new UserCancelledError({ message: "Operation cancelled" });
@@ -1842,7 +1668,7 @@ async function getAddonsChoice(addons, frontends, auth, backend, runtime, previo
1842
1668
  async function getAddonsToAdd(config) {
1843
1669
  const groupedOptions = createGroupedOptions();
1844
1670
  const frontendArray = config.frontend || [];
1845
- const compatibleAddons = getCompatibleAddons(types_exports.AddonsSchema.options.filter((addon) => addon !== "none"), frontendArray, config.addons, config.auth, config.backend, config.runtime);
1671
+ const compatibleAddons = getCompatibleAddons(types_exports.AddonsSchema.options.filter((addon) => addon !== "none" && addon !== "axiom"), frontendArray, config.addons, config.auth, config.backend, config.runtime);
1846
1672
  for (const addon of compatibleAddons) {
1847
1673
  const { label, hint } = getAddonDisplay(addon);
1848
1674
  addOptionToGroup(groupedOptions, {
@@ -1857,41 +1683,45 @@ async function getAddonsToAdd(config) {
1857
1683
  message: "Select addons to add",
1858
1684
  options: groupedOptions,
1859
1685
  required: false,
1860
- exclusive: [TASK_RUNNER_ADDONS],
1686
+ exclusive: [TASK_RUNNER_ADDONS$1, OBSERVABILITY_ADDONS$1],
1861
1687
  validate: validateAddonSelection
1862
1688
  });
1863
- if (isCancel$1(response)) throw new UserCancelledError({ message: "Operation cancelled" });
1689
+ if (!Array.isArray(response)) throw new UserCancelledError({ message: "Operation cancelled" });
1864
1690
  return response;
1865
1691
  }
1866
1692
  //#endregion
1867
1693
  //#region src/utils/bts-config.ts
1868
1694
  const BTS_CONFIG_FILE = "bts.jsonc";
1869
- /**
1870
- * Reads the BTS configuration file from the project directory.
1871
- */
1695
+ function parseBtsConfig(content) {
1696
+ const errors = [];
1697
+ const value = parse(content, errors, { allowTrailingComma: true });
1698
+ if (errors.length > 0) throw new Error("Invalid JSONC in bts.jsonc");
1699
+ return BetterTStackConfigSchema.parse(value);
1700
+ }
1872
1701
  async function readBtsConfig(projectDir) {
1873
- try {
1874
- const configPath = path.join(projectDir, BTS_CONFIG_FILE);
1875
- if (!await fs.pathExists(configPath)) return null;
1876
- return parse(await fs.readFile(configPath, "utf-8"));
1877
- } catch {
1878
- return null;
1879
- }
1702
+ const result = await Result.tryPromise({
1703
+ try: async () => parseBtsConfig(await fs$1.readFile(path.join(projectDir, BTS_CONFIG_FILE), "utf8")),
1704
+ catch: () => null
1705
+ });
1706
+ return result.isOk() ? result.value : null;
1880
1707
  }
1881
- /**
1882
- * Updates specific fields in the BTS configuration file.
1883
- */
1884
1708
  async function updateBtsConfig(projectDir, updates) {
1885
- try {
1886
- const configPath = path.join(projectDir, BTS_CONFIG_FILE);
1887
- if (!await fs.pathExists(configPath)) return;
1888
- let content = await fs.readFile(configPath, "utf-8");
1889
- for (const [key, value] of Object.entries(updates)) {
1890
- const edits = modify(content, [key], value, { formattingOptions: { tabSize: 2 } });
1891
- content = applyEdits(content, edits);
1892
- }
1893
- await fs.writeFile(configPath, content, "utf-8");
1894
- } catch {}
1709
+ return Result.tryPromise({
1710
+ try: async () => {
1711
+ const configPath = path.join(projectDir, BTS_CONFIG_FILE);
1712
+ let content = await fs$1.readFile(configPath, "utf8");
1713
+ BetterTStackConfigSchema.parse({
1714
+ ...parseBtsConfig(content),
1715
+ ...updates
1716
+ });
1717
+ for (const [key, value] of Object.entries(updates)) content = applyEdits(content, modify(content, [key], value, { formattingOptions: { tabSize: 2 } }));
1718
+ await fs$1.writeFile(configPath, content, "utf8");
1719
+ },
1720
+ catch: (cause) => new CLIError({
1721
+ message: `Failed to update bts.jsonc: ${cause instanceof Error ? cause.message : String(cause)}`,
1722
+ cause
1723
+ })
1724
+ });
1895
1725
  }
1896
1726
  //#endregion
1897
1727
  //#region src/utils/telemetry.ts
@@ -2019,12 +1849,12 @@ function shouldSkipExternalCommands() {
2019
1849
  //#endregion
2020
1850
  //#region src/utils/requirements.ts
2021
1851
  const PACKAGE_MANAGER_VERSION_RANGES = {
2022
- bun: ">=1.2.14",
1852
+ bun: ">=1.3.3",
2023
1853
  npm: ">=11.16.0",
2024
1854
  pnpm: ">=10.26.0"
2025
1855
  };
2026
1856
  const PACKAGE_MANAGER_REASONS = {
2027
- bun: "generated Bun workspaces use dependency catalogs",
1857
+ bun: "Varlock requires Bun 1.3.3 or newer",
2028
1858
  npm: "generated npm workspaces use the allowScripts install-script policy",
2029
1859
  pnpm: "generated pnpm workspaces use catalogs and the allowBuilds policy"
2030
1860
  };
@@ -2047,6 +1877,7 @@ function addNodeRequirement(requirements, range, reason) {
2047
1877
  }
2048
1878
  function getNodeToolingRequirements(config) {
2049
1879
  const requirements = [];
1880
+ addNodeRequirement(requirements, ">=22.3.0", "Varlock configuration loading");
2050
1881
  for (const frontend of config.frontend) switch (frontend) {
2051
1882
  case "astro":
2052
1883
  addNodeRequirement(requirements, ">=22.12.0", "Astro 7");
@@ -2070,9 +1901,7 @@ function getNodeToolingRequirements(config) {
2070
1901
  break;
2071
1902
  case "native-bare":
2072
1903
  case "native-uniwind":
2073
- case "native-unistyles":
2074
- addNodeRequirement(requirements, "^22.13.0 || ^24.3.0 || >=26.0.0", "React Native 0.86");
2075
- break;
1904
+ case "native-unistyles": addNodeRequirement(requirements, "^22.13.0 || ^24.3.0 || >=26.0.0", "React Native 0.86");
2076
1905
  }
2077
1906
  if (![
2078
1907
  "none",
@@ -2101,7 +1930,7 @@ function getBaselineRequirements(packageManager, hostRuntime) {
2101
1930
  range: PACKAGE_MANAGER_VERSION_RANGES[packageManager],
2102
1931
  reason: PACKAGE_MANAGER_REASONS[packageManager]
2103
1932
  });
2104
- if (hostRuntime === "node") addNodeRequirement(requirements, ">=22.0.0", "create-better-t-stack");
1933
+ if (hostRuntime === "node") addNodeRequirement(requirements, ">=22.12.0", "create-better-t-stack");
2105
1934
  return requirements;
2106
1935
  }
2107
1936
  function getLocalVersionRequirements(config, hostRuntime) {
@@ -2151,7 +1980,7 @@ async function readToolVersion(tool) {
2151
1980
  const result = await Result.tryPromise({
2152
1981
  try: async () => {
2153
1982
  const { stdout } = await execa(tool, ["--version"], {
2154
- cwd: os.tmpdir(),
1983
+ cwd: os$1.tmpdir(),
2155
1984
  stderr: "pipe"
2156
1985
  });
2157
1986
  return normalizeVersion(stdout);
@@ -2260,11 +2089,14 @@ function isEvlogBackend(backend) {
2260
2089
  function getEvlogWebFrontend(frontends) {
2261
2090
  return frontends.find((frontend) => evlogWebFrontends.includes(frontend));
2262
2091
  }
2092
+ function usesAxiom(config) {
2093
+ return config.addons.includes("axiom");
2094
+ }
2263
2095
  function shouldWireEvlogServerFsDrain(config) {
2264
- return isEvlogBackend(config.backend) && config.runtime !== "workers" && config.serverDeploy !== "cloudflare";
2096
+ return !usesAxiom(config) && isEvlogBackend(config.backend) && config.runtime !== "workers" && config.serverDeploy !== "cloudflare";
2265
2097
  }
2266
2098
  function shouldWireEvlogWebFsDrain(config) {
2267
- return getEvlogWebFrontend(config.frontend) !== void 0 && config.webDeploy !== "cloudflare";
2099
+ return !usesAxiom(config) && getEvlogWebFrontend(config.frontend) !== void 0 && config.webDeploy !== "cloudflare";
2268
2100
  }
2269
2101
  function supportsEvlogLocalLogs(config) {
2270
2102
  return shouldWireEvlogServerFsDrain(config) || shouldWireEvlogWebFsDrain(config);
@@ -2272,13 +2104,15 @@ function supportsEvlogLocalLogs(config) {
2272
2104
  function shouldIdentifyWebAuth(config) {
2273
2105
  return config.auth === "better-auth" && config.backend === "self";
2274
2106
  }
2275
- function getEvlogServerMiddlewareMarker(backend, fsDrain) {
2276
- const options = fsDrain ? `{ drain: ${NODE_DEV_FS_DRAIN_EXPRESSION} }` : "";
2107
+ function getEvlogServerMiddlewareMarker(backend, fsDrain, axiom = false) {
2108
+ const options = axiom ? "{ drain: createAxiomDrain() }" : fsDrain ? `{ drain: ${NODE_DEV_FS_DRAIN_EXPRESSION} }` : "";
2277
2109
  if (backend === "hono" || backend === "express") return `app.use(evlog(${options}));`;
2278
2110
  if (backend === "fastify") return `fastify.register(evlog${options ? `, ${options}` : ""});`;
2279
2111
  return `.use(evlog(${options}))`;
2280
2112
  }
2281
2113
  function findEvlogServerMiddlewareMarker(content, backend) {
2114
+ const axiomMarker = getEvlogServerMiddlewareMarker(backend, false, true);
2115
+ if (content.includes(axiomMarker)) return axiomMarker;
2282
2116
  const fsDrainMarker = getEvlogServerMiddlewareMarker(backend, true);
2283
2117
  return content.includes(fsDrainMarker) ? fsDrainMarker : getEvlogServerMiddlewareMarker(backend, false);
2284
2118
  }
@@ -2322,11 +2156,11 @@ async function updateFileIfExists(filePath, update) {
2322
2156
  function usesCreateAuthFactory(config) {
2323
2157
  return config.runtime === "workers" || config.serverDeploy === "cloudflare" || config.backend === "self" && config.webDeploy === "cloudflare";
2324
2158
  }
2325
- function getAuthImportLine(config) {
2326
- return usesCreateAuthFactory(config) ? `import { createAuth } from "@${config.projectName}/auth";` : `import { auth } from "@${config.projectName}/auth";`;
2159
+ function getAuthImportLine(config, source) {
2160
+ return usesCreateAuthFactory(config) ? `import { createAuth } from "${source}";` : `import { auth } from "${source}";`;
2327
2161
  }
2328
2162
  function getAuthExpression(config) {
2329
- return usesCreateAuthFactory(config) ? "createAuth()" : "auth";
2163
+ return usesCreateAuthFactory(config) ? "(await createAuth())" : "auth";
2330
2164
  }
2331
2165
  function addAiSdkEvlogTelemetry(content, loggerExpression) {
2332
2166
  let nextContent = addNamedImport(content, "evlog/ai", ["createAILogger", "createEvlogIntegration"]);
@@ -2364,15 +2198,16 @@ function addEvlogBetterAuthServerSetup(content, backend, authExpression) {
2364
2198
  const evlogMarker = findEvlogServerMiddlewareMarker(nextContent, backend);
2365
2199
  return insertAfterOnce(nextContent, evlogMarker, `\n\t.derive(async ({ request, log }) => {${identifyUserSetup.replace(/\n\t/g, "\n ")}\n\t\tawait identifyUser(log, request.headers, new URL(request.url).pathname);\n\t\treturn {};\n\t})`, "identifyUser(log");
2366
2200
  }
2367
- function addEvlogServerSetup(content, backend, serviceName, fsDrain) {
2201
+ function addEvlogServerSetup(content, backend, serviceName, fsDrain, axiom = false) {
2368
2202
  const initSnippet = `initLogger({\n\tenv: { service: "${serviceName}" },\n});\n\n`;
2369
- const evlogMarker = getEvlogServerMiddlewareMarker(backend, fsDrain);
2203
+ const evlogMarker = getEvlogServerMiddlewareMarker(backend, fsDrain, axiom);
2370
2204
  const legacyEvlogMarker = getEvlogServerMiddlewareMarker(backend, false);
2371
2205
  if (backend === "hono") {
2372
2206
  let nextContent = prependMissingImports(content, [
2373
2207
  "import { initLogger } from \"evlog\";",
2374
2208
  "import { evlog, type EvlogVariables } from \"evlog/hono\";",
2375
- ...fsDrain ? ["import { createFsDrain } from \"evlog/fs\";"] : []
2209
+ ...fsDrain ? ["import { createFsDrain } from \"evlog/fs\";"] : [],
2210
+ ...axiom ? ["import { createAxiomDrain } from \"evlog/axiom\";"] : []
2376
2211
  ]);
2377
2212
  nextContent = insertBeforeOnce(nextContent, "const app = new Hono", initSnippet, "initLogger({");
2378
2213
  nextContent = nextContent.replace("const app = new Hono();", "const app = new Hono<EvlogVariables>();");
@@ -2384,7 +2219,8 @@ function addEvlogServerSetup(content, backend, serviceName, fsDrain) {
2384
2219
  let nextContent = prependMissingImports(content, [
2385
2220
  "import { initLogger } from \"evlog\";",
2386
2221
  "import { evlog } from \"evlog/express\";",
2387
- ...fsDrain ? ["import { createFsDrain } from \"evlog/fs\";"] : []
2222
+ ...fsDrain ? ["import { createFsDrain } from \"evlog/fs\";"] : [],
2223
+ ...axiom ? ["import { createAxiomDrain } from \"evlog/axiom\";"] : []
2388
2224
  ]);
2389
2225
  nextContent = insertBeforeOnce(nextContent, "const app = express();", initSnippet, "initLogger({");
2390
2226
  if (fsDrain) nextContent = nextContent.replace(legacyEvlogMarker, evlogMarker);
@@ -2394,7 +2230,8 @@ function addEvlogServerSetup(content, backend, serviceName, fsDrain) {
2394
2230
  let nextContent = prependMissingImports(content, [
2395
2231
  "import { initLogger } from \"evlog\";",
2396
2232
  "import { evlog } from \"evlog/fastify\";",
2397
- ...fsDrain ? ["import { createFsDrain } from \"evlog/fs\";"] : []
2233
+ ...fsDrain ? ["import { createFsDrain } from \"evlog/fs\";"] : [],
2234
+ ...axiom ? ["import { createAxiomDrain } from \"evlog/axiom\";"] : []
2398
2235
  ]);
2399
2236
  nextContent = insertBeforeOnce(nextContent, "const fastify = Fastify", initSnippet, "initLogger({");
2400
2237
  if (fsDrain) nextContent = nextContent.replace(legacyEvlogMarker, evlogMarker);
@@ -2403,7 +2240,8 @@ function addEvlogServerSetup(content, backend, serviceName, fsDrain) {
2403
2240
  let nextContent = prependMissingImports(content, [
2404
2241
  "import { initLogger } from \"evlog\";",
2405
2242
  "import { evlog } from \"evlog/elysia\";",
2406
- ...fsDrain ? ["import { createFsDrain } from \"evlog/fs\";"] : []
2243
+ ...fsDrain ? ["import { createFsDrain } from \"evlog/fs\";"] : [],
2244
+ ...axiom ? ["import { createAxiomDrain } from \"evlog/axiom\";"] : []
2407
2245
  ]);
2408
2246
  const elysiaMarker = nextContent.includes("const app = new Elysia") ? "const app = new Elysia" : "new Elysia";
2409
2247
  nextContent = insertBeforeOnce(nextContent, elysiaMarker, initSnippet, "initLogger({");
@@ -2425,14 +2263,19 @@ function addSvelteViteEvlogSetup(content, serviceName) {
2425
2263
  if (nextContent.includes("evlog({")) return nextContent;
2426
2264
  return nextContent.replace("sveltekit(),", `sveltekit(),\n evlog({ service: "${serviceName}" }),`);
2427
2265
  }
2428
- function getSvelteEvlogHooksCall(fsDrain) {
2266
+ function getSvelteEvlogHooksCall(fsDrain, axiom = false) {
2267
+ if (axiom) return "createEvlogHooks({ drain: createAxiomDrain() })";
2429
2268
  return fsDrain ? `createEvlogHooks({ drain: ${SVELTE_DEV_FS_DRAIN_EXPRESSION} })` : "createEvlogHooks()";
2430
2269
  }
2431
- function addSvelteHooksEvlogSetup(content, fsDrain) {
2432
- let nextContent = prependMissingImports(content, ["import { createEvlogHooks } from \"evlog/sveltekit\";", ...fsDrain ? ["import { createFsDrain } from \"evlog/fs\";"] : []]);
2270
+ function addSvelteHooksEvlogSetup(content, fsDrain, axiom) {
2271
+ let nextContent = prependMissingImports(content, [
2272
+ "import { createEvlogHooks } from \"evlog/sveltekit\";",
2273
+ ...fsDrain ? ["import { createFsDrain } from \"evlog/fs\";"] : [],
2274
+ ...axiom ? ["import { createAxiomDrain } from \"evlog/axiom\";"] : []
2275
+ ]);
2433
2276
  if (fsDrain) nextContent = addNamedImport(nextContent, "$app/environment", ["dev"]);
2434
- const hooksCall = getSvelteEvlogHooksCall(fsDrain);
2435
- if (fsDrain) nextContent = nextContent.replaceAll("createEvlogHooks()", hooksCall);
2277
+ const hooksCall = getSvelteEvlogHooksCall(fsDrain, axiom);
2278
+ if (fsDrain || axiom) nextContent = nextContent.replaceAll("createEvlogHooks()", hooksCall);
2436
2279
  if (!nextContent.includes("export const handle") && !nextContent.includes("const authHandle")) {
2437
2280
  if (!nextContent.includes("createEvlogHooks(")) nextContent = `${nextContent.trimEnd()}\n\nexport const { handle, handleError } = ${hooksCall};\n`;
2438
2281
  return nextContent;
@@ -2457,33 +2300,36 @@ function addTanstackStartRootEvlogSetup(content) {
2457
2300
  if (/server:\s*{/.test(nextContent)) return nextContent.replace(/server:\s*{\n/, `server: {\n middleware: [${middlewareEntry}],\n`);
2458
2301
  return nextContent.replace("head: () => ({", `server: {\n middleware: [${middlewareEntry}],\n },\n\n head: () => ({`);
2459
2302
  }
2460
- function getInitLoggerSnippet(serviceName, fsDrain, indent) {
2461
- return `initLogger({\n${indent}env: { service: "${serviceName}" },${fsDrain ? `\n${indent}drain: ${ASTRO_DEV_FS_DRAIN_EXPRESSION},` : ""}\n});\n\n`;
2303
+ function getInitLoggerSnippet(serviceName, fsDrain, indent, axiom = false) {
2304
+ return `initLogger({\n${indent}env: { service: "${serviceName}" },${axiom ? `\n${indent}drain: createAxiomDrain(),` : fsDrain ? `\n${indent}drain: ${ASTRO_DEV_FS_DRAIN_EXPRESSION},` : ""}\n});\n\n`;
2462
2305
  }
2463
- function addAstroMiddlewareEvlogSetup(content, serviceName, fsDrain) {
2464
- let nextContent = prependMissingImports(content, ["import { createRequestLogger, initLogger } from \"evlog\";", ...fsDrain ? ["import { createFsDrain } from \"evlog/fs\";"] : []]);
2465
- const initSnippet = getInitLoggerSnippet(serviceName, fsDrain, " ");
2306
+ function addAstroMiddlewareEvlogSetup(content, serviceName, fsDrain, axiom, cloudflare) {
2307
+ let nextContent = prependMissingImports(content, [
2308
+ "import { createRequestLogger, initLogger } from \"evlog\";",
2309
+ ...fsDrain ? ["import { createFsDrain } from \"evlog/fs\";"] : [],
2310
+ ...axiom ? ["import { createAxiomDrain } from \"evlog/axiom\";"] : []
2311
+ ]);
2312
+ const initSnippet = getInitLoggerSnippet(serviceName, fsDrain, " ", axiom);
2466
2313
  nextContent = insertBeforeOnce(nextContent, "export const onRequest", initSnippet, "initLogger({");
2467
2314
  if (fsDrain && !nextContent.includes("drain:")) nextContent = nextContent.replace(/initLogger\(\{\n(\s+env: \{ service: "[^"]+" \},)/, `initLogger({\n$1\n drain: ${ASTRO_DEV_FS_DRAIN_EXPRESSION},`);
2468
2315
  if (nextContent.includes("createRequestLogger({")) return nextContent;
2469
2316
  const contextMarker = "export const onRequest = defineMiddleware(async (context, next) => {";
2470
2317
  if (nextContent.includes(contextMarker)) {
2471
- nextContent = insertAfterOnce(nextContent, contextMarker, `\n const url = new URL(context.request.url);\n const log = createRequestLogger({\n method: context.request.method,\n path: url.pathname,\n });\n\n context.locals.log = log;\n`, "const log = createRequestLogger({");
2472
- return nextContent.replace("return next();", "const response = await next();\n log.emit();\n return response;");
2318
+ nextContent = insertAfterOnce(nextContent, contextMarker, `\n const url = new URL(context.request.url);\n const log = createRequestLogger({\n method: context.request.method,\n path: url.pathname,\n${cloudflare ? " waitUntil: context.locals.cfContext.waitUntil.bind(context.locals.cfContext),\n" : ""} });\n\n context.locals.log = log;\n`, "const log = createRequestLogger({");
2319
+ return nextContent.replace("return next();", "const response = await next();\n log.set({ status: response.status });\n log.emit();\n return response;");
2473
2320
  }
2474
2321
  const localsMarker = "export const onRequest = defineMiddleware(async ({ request, locals }, next) => {";
2475
2322
  if (nextContent.includes(localsMarker)) {
2476
- nextContent = insertAfterOnce(nextContent, localsMarker, `\n const url = new URL(request.url);\n const log = createRequestLogger({\n method: request.method,\n path: url.pathname,\n });\n\n locals.log = log;\n`, "const log = createRequestLogger({");
2477
- return nextContent.replace("return next();", "const response = await next();\n log.emit();\n return response;");
2323
+ nextContent = insertAfterOnce(nextContent, localsMarker, `\n const url = new URL(request.url);\n const log = createRequestLogger({\n method: request.method,\n path: url.pathname,\n${cloudflare ? " waitUntil: locals.cfContext.waitUntil.bind(locals.cfContext),\n" : ""} });\n\n locals.log = log;\n`, "const log = createRequestLogger({");
2324
+ return nextContent.replace("return next();", "const response = await next();\n log.set({ status: response.status });\n log.emit();\n return response;");
2478
2325
  }
2479
2326
  return nextContent;
2480
2327
  }
2481
2328
  function addAstroLocalsType(content) {
2482
2329
  let nextContent = prependMissingImports(content, ["import type { RequestLogger } from \"evlog\";"]);
2483
2330
  if (nextContent.includes("log: RequestLogger")) return nextContent;
2484
- if (nextContent.includes("interface Locals {")) return nextContent.replace("interface Locals {", "interface Locals {\n log: RequestLogger;");
2485
- if (nextContent.includes("declare namespace App {")) return nextContent.replace("declare namespace App {", "declare namespace App {\n interface Locals {\n log: RequestLogger;\n }\n");
2486
- return `${nextContent.trimEnd()}\n\ndeclare namespace App {\n interface Locals {\n log: RequestLogger;\n }\n}\n`;
2331
+ if (nextContent.includes("declare global {") && nextContent.includes("interface Locals {")) return nextContent.replace("interface Locals {", "interface Locals {\n log: RequestLogger;");
2332
+ return `${nextContent.trimEnd()}\n\ndeclare global {\n namespace App {\n interface Locals {\n log: RequestLogger;\n }\n }\n}\n`;
2487
2333
  }
2488
2334
  function addNextRouteWrappers(content) {
2489
2335
  let nextContent = prependMissingImports(content, ["import { withEvlog } from \"@/lib/evlog\";"]);
@@ -2534,11 +2380,11 @@ function addNextBetterAuthToRoute(content) {
2534
2380
  function addSvelteBetterAuthEvlogSetup(content, config) {
2535
2381
  if (!content.includes("authHandle") || content.includes("evlogAuthHandle")) return content;
2536
2382
  let nextContent = addNamedImport(content, "evlog/better-auth", ["createAuthMiddleware", "type BetterAuthInstance"]);
2537
- if (!nextContent.includes(`@${config.projectName}/auth`)) nextContent = prependMissingImports(nextContent, [getAuthImportLine(config)]);
2538
- if (usesCreateAuthFactory(config) && config.webDeploy === "cloudflare" && !nextContent.includes(`@${config.projectName}/env/server`)) nextContent = prependMissingImports(nextContent, [`import { env as localEnv } from "@${config.projectName}/env/server";`]);
2383
+ if (!nextContent.includes("from \"./services\"")) nextContent = prependMissingImports(nextContent, [getAuthImportLine(config, "./services")]);
2384
+ if (usesCreateAuthFactory(config) && config.webDeploy === "cloudflare" && !nextContent.includes("from \"./env.server\"")) nextContent = prependMissingImports(nextContent, ["import { env as localEnv } from \"./env.server\";"]);
2539
2385
  const authExpression = getAuthExpression(config);
2540
2386
  const authOptions = "{ exclude: [\"/api/auth/**\"], maskEmail: true }";
2541
- const authHandleSnippet = usesCreateAuthFactory(config) && config.webDeploy === "cloudflare" ? `const evlogAuthHandle: Handle = async ({ event, resolve }) => {\n\tif (building) {\n\t\treturn resolve(event);\n\t}\n\n\tconst authEnv = event.platform?.env ?? localEnv;\n\tconst identifyUser = createAuthMiddleware(createAuth(authEnv) as BetterAuthInstance, ${authOptions});\n\tawait identifyUser(event.locals.log, event.request.headers, event.url.pathname);\n\treturn resolve(event);\n};\n\n` : `const identifyUser = createAuthMiddleware(${authExpression} as BetterAuthInstance, ${authOptions});\n\nconst evlogAuthHandle: Handle = async ({ event, resolve }) => {\n\tawait identifyUser(event.locals.log, event.request.headers, event.url.pathname);\n\treturn resolve(event);\n};\n\n`;
2387
+ const authHandleSnippet = usesCreateAuthFactory(config) && config.webDeploy === "cloudflare" ? `const evlogAuthHandle: Handle = async ({ event, resolve }) => {\n\tif (building) {\n\t\treturn resolve(event);\n\t}\n\n\tconst authEnv = event.platform?.env ?? localEnv;\n\tconst identifyUser = createAuthMiddleware((await createAuth(authEnv)) as BetterAuthInstance, ${authOptions});\n\tawait identifyUser(event.locals.log, event.request.headers, event.url.pathname);\n\treturn resolve(event);\n};\n\n` : `const identifyUser = createAuthMiddleware(${authExpression} as BetterAuthInstance, ${authOptions});\n\nconst evlogAuthHandle: Handle = async ({ event, resolve }) => {\n\tawait identifyUser(event.locals.log, event.request.headers, event.url.pathname);\n\treturn resolve(event);\n};\n\n`;
2542
2388
  const evlogHandleDeclaration = nextContent.match(/const \{ handle: evlogHandle, handleError \} = createEvlogHooks\([\s\S]*?\);\n\n/)?.[0];
2543
2389
  if (evlogHandleDeclaration) nextContent = insertAfterOnce(nextContent, evlogHandleDeclaration, authHandleSnippet, "evlogAuthHandle");
2544
2390
  return nextContent.replace("sequence(evlogHandle as Handle, authHandle)", "sequence(evlogHandle as Handle, evlogAuthHandle, authHandle)").replace("sequence(evlogHandle, authHandle)", "sequence(evlogHandle as Handle, evlogAuthHandle, authHandle)");
@@ -2546,7 +2392,7 @@ function addSvelteBetterAuthEvlogSetup(content, config) {
2546
2392
  function addAstroBetterAuthEvlogSetup(content, config) {
2547
2393
  if (content.includes("createAuthMiddleware(")) return content;
2548
2394
  let nextContent = addNamedImport(content, "evlog/better-auth", ["createAuthMiddleware", "type BetterAuthInstance"]);
2549
- if (!nextContent.includes(`@${config.projectName}/auth`)) nextContent = prependMissingImports(nextContent, [getAuthImportLine(config)]);
2395
+ if (!nextContent.includes("from \"./services\"")) nextContent = prependMissingImports(nextContent, [getAuthImportLine(config, "./services")]);
2550
2396
  const authExpression = getAuthExpression(config);
2551
2397
  const authOptions = "{ exclude: [\"/api/auth/**\"], maskEmail: true }";
2552
2398
  const usesFactory = usesCreateAuthFactory(config);
@@ -2559,21 +2405,28 @@ function addAstroBetterAuthEvlogSetup(content, config) {
2559
2405
  }
2560
2406
  return nextContent;
2561
2407
  }
2562
- function getNextEvlogFile(serviceName, fsDrain) {
2408
+ function getNextEvlogFile(serviceName, fsDrain, axiom) {
2563
2409
  return `import { createEvlog } from "evlog/next";
2564
2410
  import { createInstrumentation } from "evlog/next/instrumentation/create";
2565
2411
  ${fsDrain ? "import { createFsDrain } from \"evlog/fs\";\n" : ""}
2412
+ ${axiom ? "import { createAxiomDrain } from \"evlog/axiom\";\n" : ""}
2566
2413
 
2567
2414
  export const { withEvlog, useLogger, log, createError } = createEvlog({
2568
2415
  service: "${serviceName}",
2569
- ${fsDrain ? ` drain: ${NODE_DEV_FS_DRAIN_EXPRESSION},\n` : ""}});
2416
+ ${axiom ? " drain: createAxiomDrain(),\n" : fsDrain ? ` drain: ${NODE_DEV_FS_DRAIN_EXPRESSION},\n` : ""}});
2570
2417
 
2571
2418
  export const { register, onRequestError } = createInstrumentation({
2572
2419
  service: "${serviceName}",
2573
2420
  });
2574
2421
  `;
2575
2422
  }
2576
- function getNitroEvlogDrainFile() {
2423
+ function getNitroEvlogDrainFile(axiom = false, nitroV3 = false) {
2424
+ if (axiom) return `${nitroV3 ? "import { definePlugin as defineNitroPlugin } from \"nitro\";\n" : ""}import { createAxiomDrain } from "evlog/axiom";
2425
+
2426
+ export default defineNitroPlugin((nitroApp) => {
2427
+ nitroApp.hooks.hook("evlog:drain", createAxiomDrain());
2428
+ });
2429
+ `;
2577
2430
  return `import { createFsDrain } from "evlog/fs";
2578
2431
 
2579
2432
  export default defineNitroPlugin((nitroApp) => {
@@ -2599,7 +2452,7 @@ export const config = {
2599
2452
  `;
2600
2453
  }
2601
2454
  function getNextEvlogAuthFile(config) {
2602
- if (usesCreateAuthFactory(config)) return `${getAuthImportLine(config)}
2455
+ if (usesCreateAuthFactory(config)) return `${getAuthImportLine(config, "../services")}
2603
2456
  import { createAuthMiddleware, type BetterAuthInstance } from "evlog/better-auth";
2604
2457
  import { useLogger } from "@/lib/evlog";
2605
2458
 
@@ -2611,7 +2464,7 @@ export async function identifyEvlogUser(request: Request) {
2611
2464
  await identifyUser(useLogger(), request.headers, new URL(request.url).pathname);
2612
2465
  }
2613
2466
  `;
2614
- return `${getAuthImportLine(config)}
2467
+ return `${getAuthImportLine(config, "../services")}
2615
2468
  import { createAuthMiddleware, type BetterAuthInstance } from "evlog/better-auth";
2616
2469
  import { useLogger } from "@/lib/evlog";
2617
2470
 
@@ -2626,7 +2479,7 @@ export async function identifyEvlogUser(request: Request) {
2626
2479
  `;
2627
2480
  }
2628
2481
  function getNitroEvlogAuthPluginFile(config) {
2629
- if (usesCreateAuthFactory(config)) return `${getAuthImportLine(config)}
2482
+ if (usesCreateAuthFactory(config)) return `${getAuthImportLine(config, "../../src/services")}
2630
2483
  import { createAuthIdentifier, type BetterAuthInstance } from "evlog/better-auth";
2631
2484
 
2632
2485
  export default defineNitroPlugin((nitroApp) => {
@@ -2639,7 +2492,7 @@ export default defineNitroPlugin((nitroApp) => {
2639
2492
  });
2640
2493
  });
2641
2494
  `;
2642
- return `${getAuthImportLine(config)}
2495
+ return `${getAuthImportLine(config, "../../src/services")}
2643
2496
  import { createAuthIdentifier, type BetterAuthInstance } from "evlog/better-auth";
2644
2497
 
2645
2498
  export default defineNitroPlugin((nitroApp) => {
@@ -2656,9 +2509,9 @@ export default defineNitroPlugin((nitroApp) => {
2656
2509
  function getNuxtEvlogAuthMiddlewareFile(config) {
2657
2510
  if (usesCreateAuthFactory(config)) {
2658
2511
  const usesCloudflareRequestEnv = config.backend === "self" && config.webDeploy === "cloudflare";
2659
- const authExpression = usesCloudflareRequestEnv ? "createAuth((event.context.cloudflare as { env: CloudflareEnv }).env)" : getAuthExpression(config);
2660
- return `${getAuthImportLine(config)}
2661
- ${usesCloudflareRequestEnv ? `import type { CloudflareEnv } from "@${config.projectName}/env/server";\n` : ""}
2512
+ const authExpression = usesCloudflareRequestEnv ? "(await createAuth((event.context.cloudflare as { env: CloudflareEnv }).env))" : getAuthExpression(config);
2513
+ return `${getAuthImportLine(config, "../../src/services")}
2514
+ ${usesCloudflareRequestEnv ? `import type { CloudflareEnv } from "../../src/env.server";\n` : ""}
2662
2515
  import { createAuthMiddleware, type BetterAuthInstance } from "evlog/better-auth";
2663
2516
 
2664
2517
  export default defineEventHandler(async (event) => {
@@ -2671,7 +2524,7 @@ export default defineEventHandler(async (event) => {
2671
2524
  });
2672
2525
  `;
2673
2526
  }
2674
- return `${getAuthImportLine(config)}
2527
+ return `${getAuthImportLine(config, "../../src/services")}
2675
2528
  import { createAuthMiddleware, type BetterAuthInstance } from "evlog/better-auth";
2676
2529
 
2677
2530
  const identify = createAuthMiddleware(${getAuthExpression(config)} as BetterAuthInstance, {
@@ -2690,6 +2543,7 @@ function getTanstackNitroConfigFile(serviceName) {
2690
2543
  import evlog from "evlog/nitro/v3";
2691
2544
 
2692
2545
  export default defineConfig({
2546
+ serverDir: "./server",
2693
2547
  experimental: {
2694
2548
  asyncContext: true,
2695
2549
  },
@@ -2701,24 +2555,26 @@ export default defineConfig({
2701
2555
  });
2702
2556
  `;
2703
2557
  }
2704
- function getAstroMiddlewareFile(serviceName, fsDrain) {
2558
+ function getAstroMiddlewareFile(serviceName, fsDrain, axiom, cloudflare) {
2705
2559
  return `import { defineMiddleware } from "astro:middleware";
2706
2560
  import { createRequestLogger, initLogger } from "evlog";
2707
2561
  ${fsDrain ? "import { createFsDrain } from \"evlog/fs\";\n" : ""}
2562
+ ${axiom ? "import { createAxiomDrain } from \"evlog/axiom\";\n" : ""}
2708
2563
 
2709
- ${getInitLoggerSnippet(serviceName, fsDrain, " ").trimEnd()}
2564
+ ${getInitLoggerSnippet(serviceName, fsDrain, " ", axiom).trimEnd()}
2710
2565
 
2711
2566
  export const onRequest = defineMiddleware(async ({ request, locals }, next) => {
2712
2567
  const url = new URL(request.url);
2713
2568
  const log = createRequestLogger({
2714
2569
  method: request.method,
2715
2570
  path: url.pathname,
2716
- });
2571
+ ${cloudflare ? " waitUntil: locals.cfContext.waitUntil.bind(locals.cfContext),\n" : ""} });
2717
2572
 
2718
2573
  locals.log = log;
2719
2574
 
2720
2575
  try {
2721
2576
  const response = await next();
2577
+ log.set({ status: response.status });
2722
2578
  log.emit();
2723
2579
  return response;
2724
2580
  } catch (error) {
@@ -2734,9 +2590,11 @@ function getAstroEnvFile() {
2734
2590
 
2735
2591
  import type { RequestLogger } from "evlog";
2736
2592
 
2737
- declare namespace App {
2738
- interface Locals {
2739
- log: RequestLogger;
2593
+ declare global {
2594
+ namespace App {
2595
+ interface Locals {
2596
+ log: RequestLogger;
2597
+ }
2740
2598
  }
2741
2599
  }
2742
2600
  `;
@@ -2744,8 +2602,9 @@ declare namespace App {
2744
2602
  async function setupNextEvlog(config, serviceName) {
2745
2603
  const webDir = path.join(config.projectDir, "apps/web");
2746
2604
  const fsDrain = shouldWireEvlogWebFsDrain(config);
2605
+ const axiom = usesAxiom(config);
2747
2606
  const evlogPath = path.join(webDir, "src/lib/evlog.ts");
2748
- if (!await fs.pathExists(evlogPath)) await writeFileIfChanged(evlogPath, getNextEvlogFile(serviceName, fsDrain));
2607
+ if (!await fs.pathExists(evlogPath)) await writeFileIfChanged(evlogPath, getNextEvlogFile(serviceName, fsDrain, axiom));
2749
2608
  const identifyWebAuth = shouldIdentifyWebAuth(config);
2750
2609
  if (identifyWebAuth) {
2751
2610
  const evlogAuthPath = path.join(webDir, "src/lib/evlog-auth.ts");
@@ -2773,9 +2632,9 @@ async function setupNuxtEvlog(config, serviceName) {
2773
2632
  const webDir = path.join(config.projectDir, "apps/web");
2774
2633
  const fsDrain = shouldWireEvlogWebFsDrain(config);
2775
2634
  await updateFileIfExists(path.join(webDir, "nuxt.config.ts"), (content) => addNuxtEvlogSetup(content, serviceName));
2776
- if (fsDrain) {
2635
+ if (fsDrain || usesAxiom(config)) {
2777
2636
  const drainPath = path.join(webDir, "server/plugins/evlog-drain.ts");
2778
- if (!await fs.pathExists(drainPath)) await writeFileIfChanged(drainPath, getNitroEvlogDrainFile());
2637
+ if (!await fs.pathExists(drainPath)) await writeFileIfChanged(drainPath, getNitroEvlogDrainFile(usesAxiom(config), config.frontend.includes("tanstack-start")));
2779
2638
  }
2780
2639
  if (shouldIdentifyWebAuth(config)) {
2781
2640
  const oldAuthPluginPath = path.join(webDir, "server/plugins/evlog-auth.ts");
@@ -2790,28 +2649,53 @@ async function setupNuxtEvlog(config, serviceName) {
2790
2649
  async function setupSvelteEvlog(config, serviceName) {
2791
2650
  const webDir = path.join(config.projectDir, "apps/web");
2792
2651
  const fsDrain = shouldWireEvlogWebFsDrain(config);
2652
+ const axiom = usesAxiom(config);
2793
2653
  await updateFileIfExists(path.join(webDir, "vite.config.ts"), (content) => addSvelteViteEvlogSetup(content, serviceName));
2794
2654
  const hooksPath = path.join(webDir, "src/hooks.server.ts");
2795
- if (await fs.pathExists(hooksPath)) await updateFileIfExists(hooksPath, (content) => addSvelteHooksEvlogSetup(content, fsDrain));
2655
+ if (await fs.pathExists(hooksPath)) await updateFileIfExists(hooksPath, (content) => addSvelteHooksEvlogSetup(content, fsDrain, axiom));
2796
2656
  else await writeFileIfChanged(hooksPath, `import { createEvlogHooks } from "evlog/sveltekit";
2797
2657
  ${fsDrain ? "import { createFsDrain } from \"evlog/fs\";\n" : ""}
2658
+ ${axiom ? "import { createAxiomDrain } from \"evlog/axiom\";\n" : ""}
2798
2659
  ${fsDrain ? "import { dev } from \"$app/environment\";\n" : ""}
2799
2660
 
2800
- export const { handle, handleError } = ${getSvelteEvlogHooksCall(fsDrain)};
2661
+ export const { handle, handleError } = ${getSvelteEvlogHooksCall(fsDrain, axiom)};
2801
2662
  `);
2802
2663
  await updateFileIfExists(path.join(webDir, "src/app.d.ts"), addSvelteLocalsType);
2803
2664
  if (shouldIdentifyWebAuth(config)) await updateFileIfExists(path.join(webDir, "src/hooks.server.ts"), (content) => addSvelteBetterAuthEvlogSetup(content, config));
2804
2665
  if (config.examples.includes("ai")) await updateFileIfExists(path.join(webDir, "src/routes/api/ai/+server.ts"), addSvelteAiEvlogSetup);
2805
2666
  }
2667
+ function getTanstackWorkersEvlogFile(config, serviceName) {
2668
+ const identify = shouldIdentifyWebAuth(config);
2669
+ return `import handler from "@tanstack/react-start/server-entry";
2670
+ import { initWorkersLogger, withEvlog } from "evlog/workers";
2671
+ import { createAxiomDrain } from "evlog/axiom";
2672
+ ${identify ? `${getAuthImportLine(config, "./services")}\nimport { createAuthMiddleware, type BetterAuthInstance } from "evlog/better-auth";\n` : ""}
2673
+ initWorkersLogger({ env: { service: "${serviceName}" } });
2674
+
2675
+ export default withEvlog(async (request${identify ? ", _env, _ctx, log" : ""}) => {
2676
+ ${identify ? ` const identifyUser = createAuthMiddleware(${getAuthExpression(config)} as BetterAuthInstance, {
2677
+ exclude: ["/api/auth/**"],
2678
+ maskEmail: true,
2679
+ });
2680
+ await identifyUser(log, request.headers, new URL(request.url).pathname);
2681
+ ` : ""} return handler.fetch(request);
2682
+ }, { drain: createAxiomDrain() });
2683
+ `;
2684
+ }
2806
2685
  async function setupTanstackStartEvlog(config, serviceName) {
2807
2686
  const webDir = path.join(config.projectDir, "apps/web");
2808
2687
  const fsDrain = shouldWireEvlogWebFsDrain(config);
2688
+ if (usesAxiom(config) && config.webDeploy === "cloudflare") {
2689
+ await writeFileIfChanged(path.join(webDir, "src/server.ts"), getTanstackWorkersEvlogFile(config, serviceName));
2690
+ if (config.examples.includes("ai")) await updateFileIfExists(path.join(webDir, "src/routes/api/ai/$.ts"), (content) => addAiSdkEvlogTelemetry(addNamedImport(content, "evlog", ["useLogger"]), "useLogger()"));
2691
+ return;
2692
+ }
2809
2693
  const nitroConfigPath = path.join(webDir, "nitro.config.ts");
2810
2694
  if (!await fs.pathExists(nitroConfigPath)) await writeFileIfChanged(nitroConfigPath, getTanstackNitroConfigFile(serviceName));
2811
2695
  await updateFileIfExists(path.join(webDir, "src/routes/__root.tsx"), addTanstackStartRootEvlogSetup);
2812
- if (fsDrain) {
2696
+ if (fsDrain || usesAxiom(config)) {
2813
2697
  const drainPath = path.join(webDir, "server/plugins/evlog-drain.ts");
2814
- if (!await fs.pathExists(drainPath)) await writeFileIfChanged(drainPath, getNitroEvlogDrainFile());
2698
+ if (!await fs.pathExists(drainPath)) await writeFileIfChanged(drainPath, getNitroEvlogDrainFile(usesAxiom(config), config.frontend.includes("tanstack-start")));
2815
2699
  }
2816
2700
  if (shouldIdentifyWebAuth(config)) {
2817
2701
  const authPluginPath = path.join(webDir, "server/plugins/evlog-auth.ts");
@@ -2822,12 +2706,17 @@ async function setupTanstackStartEvlog(config, serviceName) {
2822
2706
  async function setupAstroEvlog(config, serviceName) {
2823
2707
  const webDir = path.join(config.projectDir, "apps/web");
2824
2708
  const fsDrain = shouldWireEvlogWebFsDrain(config);
2709
+ const axiom = usesAxiom(config);
2825
2710
  const middlewarePath = path.join(webDir, "src/middleware.ts");
2826
- if (!await fs.pathExists(middlewarePath)) await writeFileIfChanged(middlewarePath, getAstroMiddlewareFile(serviceName, fsDrain));
2827
- else await updateFileIfExists(middlewarePath, (content) => addAstroMiddlewareEvlogSetup(content, serviceName, fsDrain));
2828
- const envPath = path.join(webDir, "src/env.d.ts");
2711
+ if (!await fs.pathExists(middlewarePath)) await writeFileIfChanged(middlewarePath, getAstroMiddlewareFile(serviceName, fsDrain, axiom, config.webDeploy === "cloudflare"));
2712
+ else await updateFileIfExists(middlewarePath, (content) => addAstroMiddlewareEvlogSetup(content, serviceName, fsDrain, axiom, config.webDeploy === "cloudflare"));
2713
+ const envPath = path.join(webDir, "src/locals.d.ts");
2829
2714
  if (!await fs.pathExists(envPath)) await writeFileIfChanged(envPath, getAstroEnvFile());
2830
2715
  else await updateFileIfExists(envPath, addAstroLocalsType);
2716
+ if (config.webDeploy === "cloudflare") await updateFileIfExists(envPath, (content) => {
2717
+ if (content.includes("cfContext:")) return content;
2718
+ return `/// <reference types="@cloudflare/workers-types" />\n${content.replace("interface Locals {", "interface Locals {\n cfContext: ExecutionContext;")}`;
2719
+ });
2831
2720
  if (shouldIdentifyWebAuth(config)) await updateFileIfExists(middlewarePath, (content) => addAstroBetterAuthEvlogSetup(content, config));
2832
2721
  }
2833
2722
  async function setupEvlogWeb(config) {
@@ -2847,7 +2736,7 @@ async function setupEvlog(config) {
2847
2736
  const serverIndexPath = path.join(config.projectDir, "apps/server/src/index.ts");
2848
2737
  if (await fs.pathExists(serverIndexPath)) {
2849
2738
  const content = await fs.readFile(serverIndexPath, "utf-8");
2850
- let nextContent = addEvlogServerSetup(content, config.backend, `${config.projectName}-server`, shouldWireEvlogServerFsDrain(config));
2739
+ let nextContent = addEvlogServerSetup(content, config.backend, `${config.projectName}-server`, shouldWireEvlogServerFsDrain(config), usesAxiom(config));
2851
2740
  if (config.auth === "better-auth") nextContent = addEvlogBetterAuthServerSetup(nextContent, config.backend, getAuthExpression(config));
2852
2741
  if (config.examples.includes("ai")) nextContent = addBackendAiEvlogSetup(nextContent, config.backend);
2853
2742
  if (nextContent !== content) await fs.writeFile(serverIndexPath, nextContent);
@@ -4081,7 +3970,7 @@ function getRecommendedSourceKeys(config) {
4081
3970
  if (backend === "convex") sources.push("waynesutton/convexskills");
4082
3971
  if (addons.includes("opentui")) sources.push("msmps/opentui-skill");
4083
3972
  if (addons.includes("ultracite")) sources.push("haydenbleasel/ultracite");
4084
- if (addons.includes("evlog")) sources.push("https://www.evlog.dev");
3973
+ if (addons.includes("evlog") || addons.includes("axiom")) sources.push("https://www.evlog.dev");
4085
3974
  return sources;
4086
3975
  }
4087
3976
  const CURATED_SKILLS_BY_SOURCE = {
@@ -4597,7 +4486,7 @@ const HOOKS = {
4597
4486
  claude: { label: "Claude Code" },
4598
4487
  copilot: { label: "GitHub Copilot" }
4599
4488
  };
4600
- const ULTRACITE_VERSION = "7.10.7";
4489
+ const ULTRACITE_VERSION = "7.11.0";
4601
4490
  const DEFAULT_LINTER = "biome";
4602
4491
  const DEFAULT_EDITORS = ["vscode"];
4603
4492
  const DEFAULT_AGENTS = ["universal"];
@@ -4657,73 +4546,75 @@ async function setupUltracite(config, gitHooks) {
4657
4546
  let editors = configuredOptions?.editors;
4658
4547
  let agents = configuredOptions?.agents;
4659
4548
  let hooks = configuredOptions?.hooks;
4660
- if (!linter || !editors || !agents || !hooks) if (isSilent()) {
4661
- linter = linter ?? DEFAULT_LINTER;
4662
- editors = editors ?? [...DEFAULT_EDITORS];
4663
- agents = agents ?? [...DEFAULT_AGENTS];
4664
- hooks = hooks ?? [...DEFAULT_HOOKS];
4665
- } else {
4666
- const results = await navigableGroup({
4667
- linter: async () => {
4668
- if (linter !== void 0) return linter;
4669
- return navigableSelect({
4670
- message: "Which linter do you want to use?",
4671
- options: Object.entries(LINTERS).map(([key, linterOption]) => ({
4672
- value: key,
4673
- label: linterOption.label
4674
- })),
4675
- initialValue: linter ?? DEFAULT_LINTER
4676
- });
4677
- },
4678
- editors: async () => {
4679
- if (editors !== void 0) return editors;
4680
- return navigableMultiselect({
4681
- message: "Which editors do you want to configure (recommended)?",
4682
- required: false,
4683
- options: [
4684
- {
4685
- value: "vscode",
4686
- label: "VSCode / Cursor / Windsurf"
4687
- },
4688
- {
4689
- value: "zed",
4690
- label: "Zed"
4691
- },
4692
- {
4693
- value: "universal",
4694
- label: "Universal (.vscode/settings.json)"
4695
- }
4696
- ]
4697
- });
4698
- },
4699
- agents: async () => {
4700
- if (agents !== void 0) return agents;
4701
- return navigableMultiselect({
4702
- message: "Which agent files do you want to add (optional)?",
4703
- required: false,
4704
- options: Object.entries(AGENTS).map(([key, agent]) => ({
4705
- value: key,
4706
- label: agent.label
4707
- }))
4708
- });
4709
- },
4710
- hooks: async () => {
4711
- if (hooks !== void 0) return hooks;
4712
- return navigableMultiselect({
4713
- message: "Which agent hooks do you want to enable (optional)?",
4714
- required: false,
4715
- options: Object.entries(HOOKS).map(([key, hook]) => ({
4716
- value: key,
4717
- label: hook.label
4718
- }))
4719
- });
4720
- }
4721
- });
4722
- if (results.linter === void 0 || results.editors === void 0 || results.agents === void 0 || results.hooks === void 0) return userCancelled("Operation cancelled");
4723
- linter = results.linter;
4724
- editors = results.editors;
4725
- agents = results.agents;
4726
- hooks = results.hooks;
4549
+ if (!linter || !editors || !agents || !hooks) {
4550
+ if (isSilent()) {
4551
+ linter = linter ?? DEFAULT_LINTER;
4552
+ editors = editors ?? [...DEFAULT_EDITORS];
4553
+ agents = agents ?? [...DEFAULT_AGENTS];
4554
+ hooks = hooks ?? [...DEFAULT_HOOKS];
4555
+ } else {
4556
+ const results = await navigableGroup({
4557
+ linter: async () => {
4558
+ if (linter !== void 0) return linter;
4559
+ return navigableSelect({
4560
+ message: "Which linter do you want to use?",
4561
+ options: Object.entries(LINTERS).map(([key, linterOption]) => ({
4562
+ value: key,
4563
+ label: linterOption.label
4564
+ })),
4565
+ initialValue: linter ?? DEFAULT_LINTER
4566
+ });
4567
+ },
4568
+ editors: async () => {
4569
+ if (editors !== void 0) return editors;
4570
+ return navigableMultiselect({
4571
+ message: "Which editors do you want to configure (recommended)?",
4572
+ required: false,
4573
+ options: [
4574
+ {
4575
+ value: "vscode",
4576
+ label: "VSCode / Cursor / Windsurf"
4577
+ },
4578
+ {
4579
+ value: "zed",
4580
+ label: "Zed"
4581
+ },
4582
+ {
4583
+ value: "universal",
4584
+ label: "Universal (.vscode/settings.json)"
4585
+ }
4586
+ ]
4587
+ });
4588
+ },
4589
+ agents: async () => {
4590
+ if (agents !== void 0) return agents;
4591
+ return navigableMultiselect({
4592
+ message: "Which agent files do you want to add (optional)?",
4593
+ required: false,
4594
+ options: Object.entries(AGENTS).map(([key, agent]) => ({
4595
+ value: key,
4596
+ label: agent.label
4597
+ }))
4598
+ });
4599
+ },
4600
+ hooks: async () => {
4601
+ if (hooks !== void 0) return hooks;
4602
+ return navigableMultiselect({
4603
+ message: "Which agent hooks do you want to enable (optional)?",
4604
+ required: false,
4605
+ options: Object.entries(HOOKS).map(([key, hook]) => ({
4606
+ value: key,
4607
+ label: hook.label
4608
+ }))
4609
+ });
4610
+ }
4611
+ });
4612
+ if (results.linter === void 0 || results.editors === void 0 || results.agents === void 0 || results.hooks === void 0) return userCancelled("Operation cancelled");
4613
+ linter = results.linter;
4614
+ editors = results.editors;
4615
+ agents = results.agents;
4616
+ hooks = results.hooks;
4617
+ }
4727
4618
  }
4728
4619
  const frameworks = getFrameworksFromFrontend(frontend);
4729
4620
  const args = buildUltraciteInitArgs({
@@ -4796,20 +4687,22 @@ async function setupWxt(config) {
4796
4687
  cliLog.info("Setting up WXT...");
4797
4688
  const configuredOptions = config.addonOptions?.wxt;
4798
4689
  let template = configuredOptions?.template;
4799
- if (!template) if (isSilent()) template = DEFAULT_TEMPLATE;
4800
- else {
4801
- setIsFirstPrompt(true);
4802
- const selectedTemplate = await navigableSelect({
4803
- message: "Choose a template",
4804
- options: Object.entries(TEMPLATES).map(([key, templateOption]) => ({
4805
- value: key,
4806
- label: templateOption.label,
4807
- hint: templateOption.hint
4808
- })),
4809
- initialValue: DEFAULT_TEMPLATE
4810
- });
4811
- if (isCancel$1(selectedTemplate)) return userCancelled("Operation cancelled");
4812
- template = selectedTemplate;
4690
+ if (!template) {
4691
+ if (isSilent()) template = DEFAULT_TEMPLATE;
4692
+ else {
4693
+ setIsFirstPrompt(true);
4694
+ const selectedTemplate = await navigableSelect({
4695
+ message: "Choose a template",
4696
+ options: Object.entries(TEMPLATES).map(([key, templateOption]) => ({
4697
+ value: key,
4698
+ label: templateOption.label,
4699
+ hint: templateOption.hint
4700
+ })),
4701
+ initialValue: DEFAULT_TEMPLATE
4702
+ });
4703
+ if (isCancel$1(selectedTemplate)) return userCancelled("Operation cancelled");
4704
+ template = selectedTemplate;
4705
+ }
4813
4706
  }
4814
4707
  const devPort = configuredOptions?.devPort ?? DEFAULT_DEV_PORT;
4815
4708
  const args = getPackageExecutionArgs(packageManager, `wxt@latest init extension --template ${template} --pm ${packageManager}`);
@@ -4909,7 +4802,7 @@ async function setupAddons(config) {
4909
4802
  if (addons.includes("wxt")) await runSetup(() => setupWxt(config));
4910
4803
  if (addons.includes("skills")) await runSetup(() => setupSkills(config));
4911
4804
  if (addons.includes("mcp")) await runSetup(() => setupMcp(config));
4912
- if (addons.includes("evlog")) await runSetup(() => setupEvlog(config));
4805
+ if (addons.includes("evlog") || addons.includes("axiom")) await runSetup(() => setupEvlog(config));
4913
4806
  }
4914
4807
  async function setupBiome(projectDir) {
4915
4808
  await addPackageDependency({
@@ -4953,30 +4846,39 @@ async function setupLefthook(projectDir) {
4953
4846
  }
4954
4847
  //#endregion
4955
4848
  //#region src/helpers/core/detect-project-config.ts
4849
+ const packageIdentitySchema = z.object({ name: z.string().optional() });
4956
4850
  async function detectProjectConfig(projectDir) {
4851
+ projectDir = path.resolve(projectDir);
4957
4852
  const result = await Result.tryPromise({
4958
4853
  try: async () => {
4959
4854
  const btsConfig = await readBtsConfig(projectDir);
4960
- if (btsConfig) return {
4961
- projectDir,
4962
- projectName: path.basename(projectDir),
4963
- addonOptions: btsConfig.addonOptions,
4964
- dbSetupOptions: btsConfig.dbSetupOptions,
4965
- database: btsConfig.database,
4966
- orm: btsConfig.orm,
4967
- backend: btsConfig.backend,
4968
- runtime: btsConfig.runtime,
4969
- frontend: btsConfig.frontend,
4970
- addons: btsConfig.addons,
4971
- examples: btsConfig.examples,
4972
- auth: btsConfig.auth,
4973
- payments: btsConfig.payments,
4974
- packageManager: btsConfig.packageManager,
4975
- dbSetup: btsConfig.dbSetup,
4976
- api: btsConfig.api,
4977
- webDeploy: btsConfig.webDeploy,
4978
- serverDeploy: btsConfig.serverDeploy
4979
- };
4855
+ if (btsConfig) {
4856
+ const configPackagePath = path.join(projectDir, "packages/config/package.json");
4857
+ const configPackage = await fs.pathExists(configPackagePath) ? packageIdentitySchema.parse(await fs.readJson(configPackagePath)) : void 0;
4858
+ const rootPackagePath = path.join(projectDir, "package.json");
4859
+ const rootPackage = await fs.pathExists(rootPackagePath) ? packageIdentitySchema.parse(await fs.readJson(rootPackagePath)) : void 0;
4860
+ const scope = configPackage?.name ? /^@([^/]+)\/config$/.exec(configPackage.name)?.[1] : void 0;
4861
+ return {
4862
+ projectDir,
4863
+ projectName: scope || rootPackage?.name || path.basename(projectDir),
4864
+ addonOptions: btsConfig.addonOptions,
4865
+ dbSetupOptions: btsConfig.dbSetupOptions,
4866
+ database: btsConfig.database,
4867
+ orm: btsConfig.orm,
4868
+ backend: btsConfig.backend,
4869
+ runtime: btsConfig.runtime,
4870
+ frontend: btsConfig.frontend,
4871
+ addons: btsConfig.addons,
4872
+ examples: btsConfig.examples,
4873
+ auth: btsConfig.auth,
4874
+ payments: btsConfig.payments,
4875
+ packageManager: btsConfig.packageManager,
4876
+ dbSetup: btsConfig.dbSetup,
4877
+ api: btsConfig.api,
4878
+ webDeploy: btsConfig.webDeploy,
4879
+ serverDeploy: btsConfig.serverDeploy
4880
+ };
4881
+ }
4980
4882
  return null;
4981
4883
  },
4982
4884
  catch: () => null
@@ -4987,7 +4889,7 @@ async function detectProjectConfig(projectDir) {
4987
4889
  //#region src/helpers/core/install-dependencies.ts
4988
4890
  const FORCE_KILL_AFTER_MS = 2e3;
4989
4891
  async function installDependencies({ projectDir, packageManager }) {
4990
- if (shouldSkipExternalCommands()) return Result.ok("installed");
4892
+ if (shouldSkipExternalCommands()) return Result.ok("skipped");
4991
4893
  startInterruptibleStep();
4992
4894
  const s = createSpinner();
4993
4895
  s.start(`Running ${packageManager} install...`);
@@ -5036,7 +4938,14 @@ const ADD_PACKAGE_JSON_PATHS = [
5036
4938
  "packages/infra/package.json",
5037
4939
  "packages/ui/package.json"
5038
4940
  ];
5039
- const ADD_TEXT_FILE_PATHS = ["apps/web/vite.config.ts", "lefthook.yml"];
4941
+ const ADD_TEXT_FILE_PATHS = [
4942
+ "apps/web/vite.config.ts",
4943
+ "apps/web/next.config.ts",
4944
+ "apps/web/src/Document.tsx",
4945
+ "apps/web/src/root.tsx",
4946
+ "apps/web/src/app/layout.tsx",
4947
+ "lefthook.yml"
4948
+ ];
5040
4949
  const HOOK_ADDONS = ["husky", "lefthook"];
5041
4950
  const HOOK_LINTER_ADDONS = [
5042
4951
  "biome",
@@ -5197,12 +5106,13 @@ async function reportAddOutcome(input, result) {
5197
5106
  });
5198
5107
  }
5199
5108
  async function addHandlerInternal(input) {
5200
- const projectDir = input.projectDir || process.cwd();
5201
- const hardeningResult = validateAgentSafePathInput(projectDir, "projectDir");
5109
+ const projectDirInput = input.projectDir || process.cwd();
5110
+ const hardeningResult = validateAgentSafePathInput(projectDirInput, "projectDir");
5202
5111
  if (hardeningResult.isErr()) return Result.err(new CLIError({
5203
5112
  message: hardeningResult.error.message,
5204
5113
  cause: hardeningResult.error
5205
5114
  }));
5115
+ const projectDir = path.resolve(projectDirInput);
5206
5116
  if (!isSilent()) {
5207
5117
  renderTitle();
5208
5118
  intro(pc.magenta("Add to your project"));
@@ -5249,6 +5159,7 @@ async function addHandlerInternal(input) {
5249
5159
  }
5250
5160
  addonsToAdd = selectedAddons;
5251
5161
  }
5162
+ if (addonsToAdd.includes("axiom")) return Result.err(new CLIError({ message: "Axiom must be selected during project creation because it configures Alchemy infrastructure and deployment bindings." }));
5252
5163
  const updatedAddons = [...existingConfig.addons, ...addonsToAdd];
5253
5164
  const addonsValidationResult = validateAddonsAgainstConfig(updatedAddons, existingConfig);
5254
5165
  if (addonsValidationResult.isErr()) return Result.err(new CLIError({ message: addonsValidationResult.error.message }));
@@ -5311,10 +5222,21 @@ async function addHandlerInternal(input) {
5311
5222
  }
5312
5223
  await processAddonTemplates(vfs, EMBEDDED_TEMPLATES, config);
5313
5224
  processAddonsDeps(vfs, config);
5225
+ processPwaPlugins(vfs, config);
5314
5226
  if (addonsToAdd.includes("turborepo")) processTurboConfig(vfs, updatedConfig);
5315
5227
  if (addonsToAdd.includes("nx")) processNxConfig(vfs, updatedConfig);
5316
5228
  if (addonsToAdd.includes("vite-plus")) processVitePlusConfig(vfs, updatedConfig);
5317
- if (updatedAddons.some((addon) => TASK_RUNNER_ADDONS.includes(addon))) processPackageConfigs(vfs, updatedConfig);
5229
+ if (updatedAddons.some((addon) => TASK_RUNNER_ADDONS$1.includes(addon)) || addonsToAdd.includes("electrobun")) {
5230
+ const existingNames = new Map(ADD_PACKAGE_JSON_PATHS.map((filePath) => [filePath, vfs.readJson(filePath)?.name]));
5231
+ processPackageConfigs(vfs, updatedConfig);
5232
+ for (const [filePath, name] of existingNames) if (name) {
5233
+ const pkg = vfs.readJson(filePath);
5234
+ if (pkg) vfs.writeJson(filePath, {
5235
+ ...pkg,
5236
+ name
5237
+ });
5238
+ }
5239
+ }
5318
5240
  if (updatedAddons.includes("vite-plus")) updateViteConfigImportsForVitePlus(vfs);
5319
5241
  if (shouldRefreshLefthook(addonsToAdd, updatedAddons)) refreshLefthookTemplate(vfs, updatedConfig);
5320
5242
  }
@@ -5380,10 +5302,13 @@ async function addHandlerInternal(input) {
5380
5302
  }
5381
5303
  });
5382
5304
  if (setupResult.isErr()) return Result.err(setupResult.error);
5383
- if (addonsToAdd.length > 0) await updateBtsConfig(projectDir, {
5384
- addons: updatedAddons,
5385
- addonOptions: updatedConfig.addonOptions
5386
- });
5305
+ if (addonsToAdd.length > 0) {
5306
+ const configResult = await updateBtsConfig(projectDir, {
5307
+ addons: updatedAddons,
5308
+ addonOptions: updatedConfig.addonOptions
5309
+ });
5310
+ if (configResult.isErr()) return Result.err(configResult.error);
5311
+ }
5387
5312
  if (input.install) await installDependencies({
5388
5313
  projectDir,
5389
5314
  packageManager: config.packageManager
@@ -5457,7 +5382,7 @@ function resolveProjectDbSetupOptions(config, cliOptions = {}) {
5457
5382
  //#region src/prompts/api.ts
5458
5383
  async function getApiChoice(Api, frontend, backend, previousValue) {
5459
5384
  if (backend === "convex" || backend === "none") return "none";
5460
- const allowed = allowedApisForFrontends(frontend ?? []);
5385
+ const allowed = allowedApisForFrontends$1(frontend ?? []);
5461
5386
  if (Api) {
5462
5387
  const compat = validateApiFrontendCompatibility(Api, frontend ?? []);
5463
5388
  if (compat.isErr()) throw compat.error;
@@ -5488,20 +5413,11 @@ async function getApiChoice(Api, frontend, backend, previousValue) {
5488
5413
  //#region src/prompts/auth.ts
5489
5414
  function getAvailableAuthProviders(backend, frontend = []) {
5490
5415
  if (backend === "none") return ["none"];
5491
- const hasClerkCompatibleFrontends = frontend.some((f) => [
5492
- "react-router",
5493
- "tanstack-router",
5494
- "tanstack-start",
5495
- "next",
5496
- "native-bare",
5497
- "native-uniwind",
5498
- "native-unistyles"
5499
- ].includes(f));
5500
5416
  const options = [];
5501
5417
  if (backend === "convex") {
5502
- if (supportsConvexBetterAuth(frontend)) options.push("better-auth");
5418
+ if (supportsConvexBetterAuth(frontend) && frontend.every((f) => isFrontendAllowedWithBackend(f, backend, "better-auth"))) options.push("better-auth");
5503
5419
  } else options.push("better-auth");
5504
- if (hasClerkCompatibleFrontends) options.push("clerk");
5420
+ if (supportsClerkFrontend(frontend) && supportsClerkBackend(backend, frontend)) options.push("clerk");
5505
5421
  if (options.length === 0) return ["none"];
5506
5422
  return [...options, "none"];
5507
5423
  }
@@ -5538,7 +5454,7 @@ async function getAuthChoice(auth, backend, frontend = [], previousValue) {
5538
5454
  }
5539
5455
  //#endregion
5540
5456
  //#region src/prompts/backend.ts
5541
- const FULLSTACK_FRONTENDS = [
5457
+ const FULLSTACK_FRONTENDS$1 = [
5542
5458
  "next",
5543
5459
  "tanstack-start",
5544
5460
  "nuxt",
@@ -5549,7 +5465,7 @@ const FULLSTACK_FRONTENDS = [
5549
5465
  async function getBackendFrameworkChoice(backendFramework, frontends, previousValue) {
5550
5466
  if (backendFramework !== void 0) return backendFramework;
5551
5467
  const hasIncompatibleFrontend = frontends?.some((f) => f === "solid" || f === "astro");
5552
- const hasFullstackFrontend = frontends?.some((f) => FULLSTACK_FRONTENDS.includes(f));
5468
+ const hasFullstackFrontend = frontends?.some((f) => FULLSTACK_FRONTENDS$1.includes(f));
5553
5469
  const backendOptions = [];
5554
5470
  if (hasFullstackFrontend) backendOptions.push({
5555
5471
  value: "self",
@@ -5618,7 +5534,7 @@ async function getDatabaseChoice(database, backend, runtime, previousValue) {
5618
5534
  hint: "popular open-source relational database system"
5619
5535
  }
5620
5536
  ];
5621
- if (runtime !== "workers") databaseOptions.push({
5537
+ if (supportsRuntimeDatabase(runtime, "mongodb")) databaseOptions.push({
5622
5538
  value: "mongodb",
5623
5539
  label: "MongoDB",
5624
5540
  hint: "open-source NoSQL database that stores data in JSON-like documents called BSON"
@@ -5637,25 +5553,17 @@ async function getDBSetupChoice(databaseType, dbSetup, _orm, backend, runtime, p
5637
5553
  if (backend === "convex") return "none";
5638
5554
  if (dbSetup !== void 0) return dbSetup;
5639
5555
  if (databaseType === "none") return "none";
5640
- let options = [];
5641
- if (databaseType === "sqlite") options = [
5556
+ const options = [
5642
5557
  {
5643
5558
  value: "turso",
5644
5559
  label: "Turso",
5645
5560
  hint: "SQLite for Production. Powered by libSQL"
5646
5561
  },
5647
- ...runtime === "workers" || backend === "self" ? [{
5562
+ {
5648
5563
  value: "d1",
5649
5564
  label: "Cloudflare D1",
5650
5565
  hint: "Cloudflare's managed, serverless database with SQLite's SQL semantics"
5651
- }] : [],
5652
- {
5653
- value: "none",
5654
- label: "None",
5655
- hint: "Manual setup"
5656
- }
5657
- ];
5658
- else if (databaseType === "postgres") options = [
5566
+ },
5659
5567
  {
5660
5568
  value: "neon",
5661
5569
  label: "Neon Postgres",
@@ -5676,35 +5584,6 @@ async function getDBSetupChoice(databaseType, dbSetup, _orm, backend, runtime, p
5676
5584
  label: "Prisma Postgres",
5677
5585
  hint: "Instant Postgres for Global Applications"
5678
5586
  },
5679
- {
5680
- value: "docker",
5681
- label: "Docker",
5682
- hint: "Run locally with docker compose"
5683
- },
5684
- {
5685
- value: "none",
5686
- label: "None",
5687
- hint: "Manual setup"
5688
- }
5689
- ];
5690
- else if (databaseType === "mysql") options = [
5691
- {
5692
- value: "planetscale",
5693
- label: "PlanetScale",
5694
- hint: "MySQL on Vitess (NVMe, HA)"
5695
- },
5696
- {
5697
- value: "docker",
5698
- label: "Docker",
5699
- hint: "Run locally with docker compose"
5700
- },
5701
- {
5702
- value: "none",
5703
- label: "None",
5704
- hint: "Manual setup"
5705
- }
5706
- ];
5707
- else if (databaseType === "mongodb") options = [
5708
5587
  {
5709
5588
  value: "mongodb-atlas",
5710
5589
  label: "MongoDB Atlas",
@@ -5720,8 +5599,7 @@ async function getDBSetupChoice(databaseType, dbSetup, _orm, backend, runtime, p
5720
5599
  label: "None",
5721
5600
  hint: "Manual setup"
5722
5601
  }
5723
- ];
5724
- else return "none";
5602
+ ].filter(({ value }) => supportsDatabaseSetup(value, databaseType) && supportsDatabaseSetupRuntime(value, runtime, backend));
5725
5603
  const response = await navigableSelect({
5726
5604
  message: `Choose a ${databaseType} setup`,
5727
5605
  options,
@@ -5744,8 +5622,8 @@ async function getDbProvisioningChoice(mode, dbSetup, backend, webDeploy, server
5744
5622
  serverDeploy
5745
5623
  })) return mode === "alchemy" ? void 0 : mode;
5746
5624
  if (mode !== void 0) return mode;
5625
+ if (dbSetup !== "neon" && dbSetup !== "planetscale" && dbSetup !== "prisma-postgres") return;
5747
5626
  const provider = providerLabels[dbSetup];
5748
- if (!provider) return void 0;
5749
5627
  const options = [
5750
5628
  {
5751
5629
  value: "alchemy",
@@ -5778,12 +5656,12 @@ async function getExamplesChoice(examples, database, frontends, backend, api, pr
5778
5656
  if (backend === "none") return [];
5779
5657
  let response = [];
5780
5658
  const options = [];
5781
- if (isExampleTodoAllowed(backend, database, api)) options.push({
5659
+ if (isExampleTodoAllowed$1(backend, database, api)) options.push({
5782
5660
  value: "todo",
5783
5661
  label: "Todo App",
5784
5662
  hint: "A simple CRUD example app"
5785
5663
  });
5786
- if (isExampleAIAllowed(backend, frontends ?? [])) options.push({
5664
+ if (isExampleAIAllowed$1(backend, frontends ?? [])) options.push({
5787
5665
  value: "ai",
5788
5666
  label: "AI Chat",
5789
5667
  hint: "A simple AI chat interface using AI SDK"
@@ -5877,7 +5755,7 @@ async function getFrontendChoice(frontendOptions, backend, auth, previousValue)
5877
5755
  label: "TanStack Start",
5878
5756
  hint: "SSR, Server Functions, API Routes and more with TanStack Router"
5879
5757
  }
5880
- ].filter((option) => isFrontendAllowedWithBackend(option.value, backend, auth));
5758
+ ].filter((option) => isFrontendAllowedWithBackend$1(option.value, backend, auth));
5881
5759
  const webFramework = await navigableSelect({
5882
5760
  message: "Choose a web framework",
5883
5761
  options: webOptions,
@@ -5913,12 +5791,13 @@ async function getFrontendChoice(frontendOptions, backend, auth, previousValue)
5913
5791
  ],
5914
5792
  initialValue: previousNative ?? "native-bare"
5915
5793
  });
5916
- if (isGoBack(nativeFramework)) if (frontendTypes.includes("web")) shouldRestart = true;
5917
- else {
5918
- setIsFirstPrompt(wasFirstPrompt);
5919
- continue;
5920
- }
5921
- else if (isCancel$1(nativeFramework)) throw new UserCancelledError({ message: "Operation cancelled" });
5794
+ if (isGoBack(nativeFramework)) {
5795
+ if (frontendTypes.includes("web")) shouldRestart = true;
5796
+ else {
5797
+ setIsFirstPrompt(wasFirstPrompt);
5798
+ continue;
5799
+ }
5800
+ } else if (isCancel$1(nativeFramework)) throw new UserCancelledError({ message: "Operation cancelled" });
5922
5801
  else result.push(nativeFramework);
5923
5802
  }
5924
5803
  if (shouldRestart) {
@@ -5974,12 +5853,11 @@ function canResolveSelfCloudflareD1Target(config) {
5974
5853
  * prompt).
5975
5854
  */
5976
5855
  function validateOrmDatabaseCompat(orm, database) {
5977
- if (orm === "mongoose" && database && database !== "mongodb") return validationErr("Mongoose ORM requires MongoDB database. Please use '--database mongodb' or choose a different ORM.");
5856
+ if (!orm || !database || supportsOrmDatabase(orm, database)) return Result.ok(void 0);
5857
+ if (orm === "mongoose") return validationErr("Mongoose ORM requires MongoDB database. Please use '--database mongodb' or choose a different ORM.");
5978
5858
  if (orm === "drizzle" && database === "mongodb") return validationErr("Drizzle ORM does not support MongoDB. Please use '--orm mongoose' or '--orm prisma' or choose a different database.");
5979
- if (database === "mongodb" && orm && orm !== "mongoose" && orm !== "prisma" && orm !== "none") return validationErr("MongoDB database requires Mongoose or Prisma ORM. Please use '--orm mongoose' or '--orm prisma' or choose a different database.");
5980
- if (database && database !== "none" && orm === "none") return validationErr("Database selection requires an ORM. Please choose '--orm drizzle', '--orm prisma', or '--orm mongoose'.");
5981
- if (orm && orm !== "none" && database === "none") return validationErr("ORM selection requires a database. Please choose a database or set '--orm none'.");
5982
- return Result.ok(void 0);
5859
+ if (orm === "none") return validationErr("Database selection requires an ORM. Please choose '--orm drizzle', '--orm prisma', or '--orm mongoose'.");
5860
+ return validationErr("ORM selection requires a database. Please choose a database or set '--orm none'.");
5983
5861
  }
5984
5862
  function validateDatabaseOrmAuth(cfg, flags) {
5985
5863
  const has = (k) => flags ? flags.has(k) : true;
@@ -5990,40 +5868,19 @@ function validateDatabaseSetup(config, providedFlags) {
5990
5868
  const { dbSetup, database, runtime } = config;
5991
5869
  if (providedFlags.has("dbSetup") && providedFlags.has("database") && dbSetup && dbSetup !== "none" && database === "none") return validationErr("Database setup requires a database. Please choose a database or set '--db-setup none'.");
5992
5870
  const setupValidations = {
5993
- turso: {
5994
- database: "sqlite",
5995
- errorMessage: "Turso setup requires SQLite database. Please use '--database sqlite' or choose a different setup."
5996
- },
5997
- neon: {
5998
- database: "postgres",
5999
- errorMessage: "Neon setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup."
6000
- },
6001
- "prisma-postgres": {
6002
- database: "postgres",
6003
- errorMessage: "Prisma PostgreSQL setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup."
6004
- },
5871
+ turso: { errorMessage: "Turso setup requires SQLite database. Please use '--database sqlite' or choose a different setup." },
5872
+ neon: { errorMessage: "Neon setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup." },
5873
+ "prisma-postgres": { errorMessage: "Prisma PostgreSQL setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup." },
6005
5874
  planetscale: { errorMessage: "PlanetScale setup requires PostgreSQL or MySQL database. Please use '--database postgres' or '--database mysql' or choose a different setup." },
6006
- "mongodb-atlas": {
6007
- database: "mongodb",
6008
- errorMessage: "MongoDB Atlas setup requires MongoDB database. Please use '--database mongodb' or choose a different setup."
6009
- },
6010
- supabase: {
6011
- database: "postgres",
6012
- errorMessage: "Supabase setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup."
6013
- },
6014
- d1: {
6015
- database: "sqlite",
6016
- errorMessage: "Cloudflare D1 setup requires SQLite database."
6017
- },
5875
+ "mongodb-atlas": { errorMessage: "MongoDB Atlas setup requires MongoDB database. Please use '--database mongodb' or choose a different setup." },
5876
+ supabase: { errorMessage: "Supabase setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup." },
5877
+ d1: { errorMessage: "Cloudflare D1 setup requires SQLite database." },
6018
5878
  docker: { errorMessage: "Docker setup is not compatible with SQLite database or Cloudflare Workers runtime." },
6019
5879
  none: { errorMessage: "" }
6020
5880
  };
6021
5881
  if (dbSetup && dbSetup !== "none") {
6022
5882
  const validation = setupValidations[dbSetup];
6023
- if (dbSetup === "planetscale") {
6024
- if (database !== "postgres" && database !== "mysql") return validationErr(validation.errorMessage);
6025
- } else if ("database" in validation && validation.database && database !== validation.database) return validationErr(validation.errorMessage);
6026
- if ("runtime" in validation && validation.runtime && runtime !== validation.runtime) return validationErr(validation.errorMessage);
5883
+ if (dbSetup !== "docker" && !supportsDatabaseSetup(dbSetup, database)) return validationErr(validation.errorMessage);
6027
5884
  if (dbSetup === "d1") {
6028
5885
  const isWorkersTarget = hasResolvedWorkersD1Target(config);
6029
5886
  const isSelfCloudflareTarget = hasResolvedSelfCloudflareD1Target(config);
@@ -6032,8 +5889,8 @@ function validateDatabaseSetup(config, providedFlags) {
6032
5889
  if (!isWorkersTarget && !isSelfCloudflareTarget && !canResolveWorkersTarget && !canResolveSelfCloudflareTarget) return validationErr("Cloudflare D1 setup requires SQLite database and either Cloudflare Workers runtime with server deployment or backend 'self' with Cloudflare web deployment.");
6033
5890
  }
6034
5891
  if (dbSetup === "docker") {
6035
- if (database === "sqlite") return validationErr("Docker setup is not compatible with SQLite database. SQLite is file-based and doesn't require Docker. Please use '--database postgres', '--database mysql', '--database mongodb', or choose a different setup.");
6036
- if (runtime === "workers") return validationErr("Docker setup is not compatible with Cloudflare Workers runtime. Workers runtime uses serverless databases (D1) and doesn't support local Docker containers. Please use '--db-setup d1' for SQLite or choose a different runtime.");
5892
+ if (database && !supportsDatabaseSetup(dbSetup, database)) return validationErr("Docker setup is not compatible with SQLite database. SQLite is file-based and doesn't require Docker. Please use '--database postgres', '--database mysql', '--database mongodb', or choose a different setup.");
5893
+ if (!supportsDatabaseSetupRuntime(dbSetup, runtime, config.backend)) return validationErr("Docker setup is not compatible with Cloudflare Workers runtime. Workers runtime uses serverless databases (D1) and doesn't support local Docker containers. Please use '--db-setup d1' for SQLite or choose a different runtime.");
6037
5894
  }
6038
5895
  }
6039
5896
  return Result.ok(void 0);
@@ -6056,12 +5913,8 @@ function validateConvexConstraints(config, providedFlags) {
6056
5913
  const { backend } = config;
6057
5914
  if (backend !== "convex") return Result.ok(void 0);
6058
5915
  const has = (k) => providedFlags.has(k);
6059
- if (has("runtime") && config.runtime !== "none") return validationErr("Convex backend requires '--runtime none'. Please remove the --runtime flag or set it to 'none'.");
6060
- if (has("database") && config.database !== "none") return validationErr("Convex backend requires '--database none'. Please remove the --database flag or set it to 'none'.");
6061
- if (has("orm") && config.orm !== "none") return validationErr("Convex backend requires '--orm none'. Please remove the --orm flag or set it to 'none'.");
6062
- if (has("api") && config.api !== "none") return validationErr("Convex backend requires '--api none'. Please remove the --api flag or set it to 'none'.");
6063
- if (has("dbSetup") && config.dbSetup !== "none") return validationErr("Convex backend requires '--db-setup none'. Please remove the --db-setup flag or set it to 'none'.");
6064
- if (has("serverDeploy") && config.serverDeploy !== "none") return validationErr("Convex backend requires '--server-deploy none'. Please remove the --server-deploy flag or set it to 'none'.");
5916
+ const disabled = validateBackendDisabledOptions(config, providedFlags);
5917
+ if (disabled.isErr()) return disabled;
6065
5918
  if (has("auth") && config.auth === "better-auth") {
6066
5919
  const incompatibleFrontends = config.frontend?.filter((f) => CONVEX_BETTER_AUTH_INCOMPATIBLE_FRONTENDS.includes(f)) ?? [];
6067
5920
  const hasSupportedFrontend = supportsConvexBetterAuth(config.frontend);
@@ -6070,43 +5923,36 @@ function validateConvexConstraints(config, providedFlags) {
6070
5923
  }
6071
5924
  return Result.ok(void 0);
6072
5925
  }
6073
- function validateBackendNoneConstraints(config, providedFlags) {
6074
- const { backend } = config;
6075
- if (backend !== "none") return Result.ok(void 0);
6076
- const has = (k) => providedFlags.has(k);
6077
- if (has("runtime") && config.runtime !== "none") return validationErr("Backend 'none' requires '--runtime none'. Please remove the --runtime flag or set it to 'none'.");
6078
- if (has("database") && config.database !== "none") return validationErr("Backend 'none' requires '--database none'. Please remove the --database flag or set it to 'none'.");
6079
- if (has("orm") && config.orm !== "none") return validationErr("Backend 'none' requires '--orm none'. Please remove the --orm flag or set it to 'none'.");
6080
- if (has("api") && config.api !== "none") return validationErr("Backend 'none' requires '--api none'. Please remove the --api flag or set it to 'none'.");
6081
- if (has("auth") && config.auth !== "none") return validationErr("Backend 'none' requires '--auth none'. Please remove the --auth flag or set it to 'none'.");
6082
- if (has("payments") && config.payments !== "none") return validationErr("Backend 'none' requires '--payments none'. Please remove the --payments flag or set it to 'none'.");
6083
- if (has("dbSetup") && config.dbSetup !== "none") return validationErr("Backend 'none' requires '--db-setup none'. Please remove the --db-setup flag or set it to 'none'.");
6084
- if (has("serverDeploy") && config.serverDeploy !== "none") return validationErr("Backend 'none' requires '--server-deploy none'. Please remove the --server-deploy flag or set it to 'none'.");
5926
+ function validateBackendDisabledOptions(config, providedFlags) {
5927
+ if (!config.backend) return Result.ok(void 0);
5928
+ for (const key of getBackendDisabledOptions(config.backend)) {
5929
+ if (!providedFlags.has(key) || config[key] === "none") continue;
5930
+ const flag = key.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
5931
+ return validationErr(`${config.backend === "convex" ? "Convex backend" : `Backend '${config.backend}'${config.backend === "self" ? " (fullstack)" : ""}`} requires '--${flag} none'. Please remove the --${flag} flag or set it to 'none'.`);
5932
+ }
6085
5933
  return Result.ok(void 0);
6086
5934
  }
5935
+ function validateBackendNoneConstraints(config, providedFlags) {
5936
+ return config.backend === "none" ? validateBackendDisabledOptions(config, providedFlags) : Result.ok(void 0);
5937
+ }
6087
5938
  function validateSelfBackendConstraints(config, providedFlags) {
6088
- const { backend } = config;
6089
- if (backend !== "self") return Result.ok(void 0);
6090
- const has = (k) => providedFlags.has(k);
6091
- if (has("runtime") && config.runtime !== "none") return validationErr("Backend 'self' (fullstack) requires '--runtime none'. Please remove the --runtime flag or set it to 'none'.");
6092
- return Result.ok(void 0);
5939
+ return config.backend === "self" ? validateBackendDisabledOptions(config, providedFlags) : Result.ok(void 0);
6093
5940
  }
6094
5941
  function validateBackendConstraints(config, providedFlags, options) {
6095
5942
  const { backend } = config;
6096
5943
  if (config.auth === "clerk" && config.frontend) {
6097
- const incompatibleFrontends = config.frontend.filter((f) => [
6098
- "nuxt",
6099
- "svelte",
6100
- "solid",
6101
- "astro"
6102
- ].includes(f));
5944
+ const incompatibleFrontends = config.frontend.filter((f) => !isFrontendAllowedWithBackend(f, void 0, "clerk"));
6103
5945
  if (incompatibleFrontends.length > 0) return validationErr(`Clerk authentication is not compatible with the following frontends: ${incompatibleFrontends.join(", ")}. Please choose a different frontend or auth provider.`);
6104
5946
  }
6105
- if (providedFlags.has("backend") && backend && backend !== "convex" && backend !== "none" && backend !== "self") {
5947
+ if (config.auth === "clerk") {
5948
+ if (!supportsClerkBackend(backend, config.frontend)) return validationErr("Clerk requires Convex, a separate server, or backend self with Next.js or TanStack Start.");
5949
+ if (config.frontend && !supportsClerkFrontend(config.frontend)) return validationErr("Clerk requires a React web or native frontend.");
5950
+ }
5951
+ if (providedFlags.has("backend") && backend && !supportsRuntimeBackend("none", backend)) {
6106
5952
  if (providedFlags.has("runtime") && options.runtime === "none") return validationErr("'--runtime none' is only supported with '--backend convex', '--backend none', or '--backend self'. Please choose 'bun', 'node', or remove the --runtime flag.");
6107
5953
  }
6108
5954
  if (backend === "convex" && providedFlags.has("frontend") && options.frontend) {
6109
- const incompatibleFrontends = options.frontend.filter((f) => f === "solid" || f === "astro");
5955
+ const incompatibleFrontends = options.frontend.filter((frontend) => !isFrontendAllowedWithBackend(frontend, "convex"));
6110
5956
  if (incompatibleFrontends.length > 0) return validationErr(`The following frontends are not compatible with '--backend convex': ${incompatibleFrontends.join(", ")}. Please choose a different frontend or backend.`);
6111
5957
  }
6112
5958
  return Result.ok(void 0);
@@ -6154,7 +6000,7 @@ function validateFullConfig(config, providedFlags, options) {
6154
6000
  yield* validateSelfBackendCompatibility(providedFlags, options, config);
6155
6001
  yield* validateWorkersCompatibility(providedFlags, options, config);
6156
6002
  if (config.runtime === "workers" && config.serverDeploy === "none") yield* validationErr("Cloudflare Workers runtime requires a server deployment. Please choose 'cloudflare' for --server-deploy.");
6157
- if (providedFlags.has("serverDeploy") && config.serverDeploy === "cloudflare" && config.runtime !== "workers") yield* validationErr(`Server deployment '${config.serverDeploy}' requires '--runtime workers'. Please use '--runtime workers' or choose a different server deployment.`);
6003
+ if (providedFlags.has("serverDeploy") && config.serverDeploy === "cloudflare" && !supportsServerDeployRuntime(config.serverDeploy, config.runtime)) yield* validationErr(`Server deployment '${config.serverDeploy}' requires '--runtime workers'. Please use '--runtime workers' or choose a different server deployment.`);
6158
6004
  if (config.addons && config.addons.length > 0) {
6159
6005
  yield* validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth, config.backend, config.runtime);
6160
6006
  config.addons = [...new Set(config.addons)];
@@ -6202,7 +6048,11 @@ async function getORMChoice(orm, hasDatabase, database, backend, runtime, previo
6202
6048
  if (compat.isErr()) throw compat.error;
6203
6049
  return orm;
6204
6050
  }
6205
- const options = database === "mongodb" ? [ormOptions.prisma, ormOptions.mongoose] : [ormOptions.drizzle, ormOptions.prisma];
6051
+ const options = [
6052
+ ormOptions.drizzle,
6053
+ ormOptions.prisma,
6054
+ ormOptions.mongoose
6055
+ ].filter((option) => supportsOrmDatabase(option.value, database ?? "sqlite"));
6206
6056
  const response = await navigableSelect({
6207
6057
  message: "Choose an ORM",
6208
6058
  options,
@@ -6266,7 +6116,7 @@ async function getPaymentsChoice(payments, auth, backend, _frontends, previousVa
6266
6116
  //#endregion
6267
6117
  //#region src/prompts/runtime.ts
6268
6118
  async function getRuntimeChoice(runtime, backend, previousValue) {
6269
- if (backend === "convex" || backend === "none" || backend === "self") return "none";
6119
+ if (backend && supportsRuntimeBackend("none", backend)) return "none";
6270
6120
  if (runtime !== void 0) return runtime;
6271
6121
  const runtimeOptions = [{
6272
6122
  value: "bun",
@@ -6277,7 +6127,7 @@ async function getRuntimeChoice(runtime, backend, previousValue) {
6277
6127
  label: "Node.js",
6278
6128
  hint: "Traditional Node.js runtime"
6279
6129
  }];
6280
- if (backend === "hono") runtimeOptions.push({
6130
+ if (backend && supportsRuntimeBackend("workers", backend)) runtimeOptions.push({
6281
6131
  value: "workers",
6282
6132
  label: "Cloudflare Workers",
6283
6133
  hint: "Edge runtime on Cloudflare's global network"
@@ -6380,7 +6230,7 @@ async function getDeploymentChoice(deployment, _runtime, backend, frontend = [],
6380
6230
  if (deployment !== void 0) return deployment;
6381
6231
  if (!hasWebFrontend(frontend)) return "none";
6382
6232
  if (backend === "self" && dbSetup === "d1") return "cloudflare";
6383
- const supportsPrismaCompute = supportsPrismaWebDeploy(frontend);
6233
+ const supportsPrismaCompute = supportsPrismaWebDeploy$1(frontend);
6384
6234
  const options = [
6385
6235
  ...validateCloudflareWebDeployKnownIssues({
6386
6236
  webDeploy: "cloudflare",
@@ -6581,7 +6431,7 @@ async function getProjectName(initialName) {
6581
6431
  }
6582
6432
  });
6583
6433
  if (isCancel(response)) throw new UserCancelledError({ message: "Operation cancelled." });
6584
- projectPath = response || defaultName;
6434
+ projectPath = types_exports.ProjectNameSchema.parse(response || defaultName);
6585
6435
  isValid = true;
6586
6436
  }
6587
6437
  return projectPath;
@@ -7400,6 +7250,21 @@ const formatOptions = {
7400
7250
  experimentalSortPackageJson: true,
7401
7251
  experimentalSortImports: { order: "asc" }
7402
7252
  };
7253
+ const ignoredDirectories = /* @__PURE__ */ new Set([
7254
+ "node_modules",
7255
+ ".git",
7256
+ ".next",
7257
+ ".nuxt",
7258
+ ".output",
7259
+ ".svelte-kit",
7260
+ ".astro",
7261
+ ".turbo",
7262
+ ".vercel",
7263
+ ".wrangler",
7264
+ "dist",
7265
+ "build",
7266
+ "coverage"
7267
+ ]);
7403
7268
  async function formatCode(filePath, content) {
7404
7269
  const result = await Result.tryPromise({
7405
7270
  try: async () => {
@@ -7420,8 +7285,10 @@ async function formatProject(projectDir, signal) {
7420
7285
  await Promise.all(entries.map(async (entry) => {
7421
7286
  if (signal?.aborted) return;
7422
7287
  const fullPath = path.join(dir, entry.name);
7423
- if (entry.isDirectory()) await formatDirectory(fullPath);
7424
- else if (entry.isFile()) await Result.tryPromise({
7288
+ if (entry.isDirectory()) {
7289
+ if (ignoredDirectories.has(entry.name)) return;
7290
+ await formatDirectory(fullPath);
7291
+ } else if (entry.isFile()) await Result.tryPromise({
7425
7292
  try: async () => {
7426
7293
  const content = await fs.readFile(fullPath, "utf-8");
7427
7294
  const formatted = await formatCode(fullPath, content);
@@ -7483,9 +7350,10 @@ async function setupCloudflareD1(config) {
7483
7350
  condition: true
7484
7351
  }]);
7485
7352
  await fs.ensureFile(path.join(projectDir, "packages/db/local.db"));
7353
+ const serverDir = path.join(projectDir, backend === "self" ? "apps/web" : "apps/server");
7486
7354
  await addPackageDependency({
7487
7355
  dependencies: ["@prisma/adapter-d1"],
7488
- projectDir: path.join(projectDir, backend === "self" ? "apps/web" : "apps/server")
7356
+ projectDir: serverDir
7489
7357
  });
7490
7358
  },
7491
7359
  catch: (e) => new DatabaseSetupError({
@@ -7651,7 +7519,7 @@ async function setupMongoDBAtlas(config, cliInput) {
7651
7519
  initialValue: "auto"
7652
7520
  });
7653
7521
  if (isCancel(promptedMode)) return userCancelled("Operation cancelled");
7654
- mode = promptedMode;
7522
+ mode = types_exports.DbSetupModeSchema.parse(promptedMode);
7655
7523
  }
7656
7524
  if (mode === "manual") {
7657
7525
  cliLog.info("MongoDB Atlas manual setup selected");
@@ -7845,23 +7713,25 @@ async function setupNeonPostgres(config, cliInput) {
7845
7713
  return Result.ok(void 0);
7846
7714
  }
7847
7715
  let selectedMode = setupMode;
7848
- if (!selectedMode) if (isSilent()) selectedMode = "manual";
7849
- else {
7850
- const promptedMode = await select({
7851
- message: "Neon setup: choose mode",
7852
- options: [{
7853
- label: "Automatic",
7854
- value: "auto",
7855
- hint: "Automated setup with provider CLI, sets .env"
7856
- }, {
7857
- label: "Manual",
7858
- value: "manual",
7859
- hint: "Manual setup, add env vars yourself"
7860
- }],
7861
- initialValue: "auto"
7862
- });
7863
- if (isCancel(promptedMode)) return userCancelled("Operation cancelled");
7864
- selectedMode = promptedMode;
7716
+ if (!selectedMode) {
7717
+ if (isSilent()) selectedMode = "manual";
7718
+ else {
7719
+ const promptedMode = await select({
7720
+ message: "Neon setup: choose mode",
7721
+ options: [{
7722
+ label: "Automatic",
7723
+ value: "auto",
7724
+ hint: "Automated setup with provider CLI, sets .env"
7725
+ }, {
7726
+ label: "Manual",
7727
+ value: "manual",
7728
+ hint: "Manual setup, add env vars yourself"
7729
+ }],
7730
+ initialValue: "auto"
7731
+ });
7732
+ if (isCancel(promptedMode)) return userCancelled("Operation cancelled");
7733
+ selectedMode = types_exports.DbSetupModeSchema.parse(promptedMode);
7734
+ }
7865
7735
  }
7866
7736
  if (selectedMode === "manual") {
7867
7737
  const envResult = await writeEnvFile$2(projectDir, backend);
@@ -7870,23 +7740,25 @@ async function setupNeonPostgres(config, cliInput) {
7870
7740
  return Result.ok(void 0);
7871
7741
  }
7872
7742
  let setupMethod = cliInput?.dbSetupOptions?.neon?.method ?? config.dbSetupOptions?.neon?.method;
7873
- if (!setupMethod) if (isSilent()) setupMethod = "neon-new";
7874
- else {
7875
- const promptedSetupMethod = await select({
7876
- message: "Choose your Neon setup method:",
7877
- options: [{
7878
- label: "Quick setup with neon-new",
7879
- value: "neon-new",
7880
- hint: "fastest, no auth required"
7881
- }, {
7882
- label: "Custom setup with Neon CLI",
7883
- value: "neon",
7884
- hint: "More control - choose project name and region"
7885
- }],
7886
- initialValue: "neon-new"
7887
- });
7888
- if (isCancel(promptedSetupMethod)) return userCancelled("Operation cancelled");
7889
- setupMethod = promptedSetupMethod;
7743
+ if (!setupMethod) {
7744
+ if (isSilent()) setupMethod = "neon-new";
7745
+ else {
7746
+ const promptedSetupMethod = await select({
7747
+ message: "Choose your Neon setup method:",
7748
+ options: [{
7749
+ label: "Quick setup with neon-new",
7750
+ value: "neon-new",
7751
+ hint: "fastest, no auth required"
7752
+ }, {
7753
+ label: "Custom setup with Neon CLI",
7754
+ value: "neon",
7755
+ hint: "More control - choose project name and region"
7756
+ }],
7757
+ initialValue: "neon-new"
7758
+ });
7759
+ if (isCancel(promptedSetupMethod)) return userCancelled("Operation cancelled");
7760
+ setupMethod = types_exports.NeonSetupMethodSchema.parse(promptedSetupMethod);
7761
+ }
7890
7762
  }
7891
7763
  if (setupMethod === "neon-new" || setupMethod === "neondb") {
7892
7764
  const neonDbResult = await setupWithNeonDb(projectDir, packageManager, backend);
@@ -7902,26 +7774,30 @@ async function setupNeonPostgres(config, cliInput) {
7902
7774
  }
7903
7775
  const suggestedProjectName = path.basename(projectDir);
7904
7776
  let projectName = cliInput?.dbSetupOptions?.neon?.projectName ?? config.dbSetupOptions?.neon?.projectName;
7905
- if (!projectName) if (isSilent()) projectName = suggestedProjectName;
7906
- else {
7907
- const promptedProjectName = await text({
7908
- message: "Enter a name for your Neon project:",
7909
- defaultValue: suggestedProjectName,
7910
- initialValue: suggestedProjectName
7911
- });
7912
- if (isCancel(promptedProjectName)) return userCancelled("Operation cancelled");
7913
- projectName = promptedProjectName;
7777
+ if (!projectName) {
7778
+ if (isSilent()) projectName = suggestedProjectName;
7779
+ else {
7780
+ const promptedProjectName = await text({
7781
+ message: "Enter a name for your Neon project:",
7782
+ defaultValue: suggestedProjectName,
7783
+ initialValue: suggestedProjectName
7784
+ });
7785
+ if (isCancel(promptedProjectName)) return userCancelled("Operation cancelled");
7786
+ projectName = promptedProjectName;
7787
+ }
7914
7788
  }
7915
7789
  let regionId = cliInput?.dbSetupOptions?.neon?.regionId ?? config.dbSetupOptions?.neon?.regionId;
7916
- if (!regionId) if (isSilent()) regionId = NEON_REGIONS[0].value;
7917
- else {
7918
- const promptedRegionId = await select({
7919
- message: "Select a region for your Neon project:",
7920
- options: NEON_REGIONS,
7921
- initialValue: NEON_REGIONS[0].value
7922
- });
7923
- if (isCancel(promptedRegionId)) return userCancelled("Operation cancelled");
7924
- regionId = promptedRegionId;
7790
+ if (!regionId) {
7791
+ if (isSilent()) regionId = NEON_REGIONS[0].value;
7792
+ else {
7793
+ const promptedRegionId = await select({
7794
+ message: "Select a region for your Neon project:",
7795
+ options: NEON_REGIONS,
7796
+ initialValue: NEON_REGIONS[0].value
7797
+ });
7798
+ if (isCancel(promptedRegionId)) return userCancelled("Operation cancelled");
7799
+ regionId = z$1.string().parse(promptedRegionId);
7800
+ }
7925
7801
  }
7926
7802
  const neonConfigResult = await createNeonProject(projectName, regionId, packageManager);
7927
7803
  if (neonConfigResult.isErr()) {
@@ -8044,15 +7920,17 @@ const CREATE_DB_USER_AGENT = "aman/better-t-stack";
8044
7920
  async function setupWithCreateDb(serverDir, packageManager, regionId) {
8045
7921
  cliLog.info("Starting Prisma Postgres setup with create-db.");
8046
7922
  let selectedRegion = regionId;
8047
- if (!selectedRegion) if (isSilent()) selectedRegion = "ap-southeast-1";
8048
- else {
8049
- const promptedRegion = await select({
8050
- message: "Select your preferred region:",
8051
- options: AVAILABLE_REGIONS,
8052
- initialValue: "ap-southeast-1"
8053
- });
8054
- if (isCancel(promptedRegion)) return userCancelled("Operation cancelled");
8055
- selectedRegion = promptedRegion;
7923
+ if (!selectedRegion) {
7924
+ if (isSilent()) selectedRegion = "ap-southeast-1";
7925
+ else {
7926
+ const promptedRegion = await select({
7927
+ message: "Select your preferred region:",
7928
+ options: AVAILABLE_REGIONS,
7929
+ initialValue: "ap-southeast-1"
7930
+ });
7931
+ if (isCancel(promptedRegion)) return userCancelled("Operation cancelled");
7932
+ selectedRegion = z$1.string().parse(promptedRegion);
7933
+ }
8056
7934
  }
8057
7935
  const createDbArgs = [
8058
7936
  ...getPackageRunnerPrefix(packageManager),
@@ -8155,23 +8033,25 @@ async function setupPrismaPostgres(config, cliInput) {
8155
8033
  return Result.ok(void 0);
8156
8034
  }
8157
8035
  let selectedSetupMode = setupMode;
8158
- if (!selectedSetupMode) if (isSilent()) selectedSetupMode = "manual";
8159
- else {
8160
- const promptedSetupMode = await select({
8161
- message: "Prisma Postgres setup: choose mode",
8162
- options: [{
8163
- label: "Automatic (create-db)",
8164
- value: "auto",
8165
- hint: "Provision a database via Prisma's create-db CLI"
8166
- }, {
8167
- label: "Manual",
8168
- value: "manual",
8169
- hint: "Add your own DATABASE_URL later"
8170
- }],
8171
- initialValue: "auto"
8172
- });
8173
- if (isCancel(promptedSetupMode)) return userCancelled("Operation cancelled");
8174
- selectedSetupMode = promptedSetupMode;
8036
+ if (!selectedSetupMode) {
8037
+ if (isSilent()) selectedSetupMode = "manual";
8038
+ else {
8039
+ const promptedSetupMode = await select({
8040
+ message: "Prisma Postgres setup: choose mode",
8041
+ options: [{
8042
+ label: "Automatic (create-db)",
8043
+ value: "auto",
8044
+ hint: "Provision a database via Prisma's create-db CLI"
8045
+ }, {
8046
+ label: "Manual",
8047
+ value: "manual",
8048
+ hint: "Add your own DATABASE_URL later"
8049
+ }],
8050
+ initialValue: "auto"
8051
+ });
8052
+ if (isCancel(promptedSetupMode)) return userCancelled("Operation cancelled");
8053
+ selectedSetupMode = types_exports.DbSetupModeSchema.parse(promptedSetupMode);
8054
+ }
8175
8055
  }
8176
8056
  if (selectedSetupMode === "manual") {
8177
8057
  const envResult = await writeEnvFile$1(projectDir, backend);
@@ -8316,23 +8196,25 @@ async function setupSupabase(config, cliInput) {
8316
8196
  return writeSupabaseEnvFile(projectDir, backend, "");
8317
8197
  }
8318
8198
  let mode = setupMode;
8319
- if (!mode) if (isSilent()) mode = "manual";
8320
- else {
8321
- const promptedMode = await select({
8322
- message: "Supabase setup: choose mode",
8323
- options: [{
8324
- label: "Automatic",
8325
- value: "auto",
8326
- hint: "Automated setup with provider CLI, sets .env"
8327
- }, {
8328
- label: "Manual",
8329
- value: "manual",
8330
- hint: "Manual setup, add env vars yourself"
8331
- }],
8332
- initialValue: "auto"
8333
- });
8334
- if (isCancel(promptedMode)) return userCancelled("Operation cancelled");
8335
- mode = promptedMode;
8199
+ if (!mode) {
8200
+ if (isSilent()) mode = "manual";
8201
+ else {
8202
+ const promptedMode = await select({
8203
+ message: "Supabase setup: choose mode",
8204
+ options: [{
8205
+ label: "Automatic",
8206
+ value: "auto",
8207
+ hint: "Automated setup with provider CLI, sets .env"
8208
+ }, {
8209
+ label: "Manual",
8210
+ value: "manual",
8211
+ hint: "Manual setup, add env vars yourself"
8212
+ }],
8213
+ initialValue: "auto"
8214
+ });
8215
+ if (isCancel(promptedMode)) return userCancelled("Operation cancelled");
8216
+ mode = types_exports.DbSetupModeSchema.parse(promptedMode);
8217
+ }
8336
8218
  }
8337
8219
  if (mode === "manual") {
8338
8220
  displayManualSupabaseInstructions(targetApp);
@@ -8458,12 +8340,13 @@ async function selectTursoGroup() {
8458
8340
  cliLog.info(`Using the only available group: ${pc.blue(groups[0].name)}`);
8459
8341
  return Result.ok(groups[0].name);
8460
8342
  }
8343
+ const groupOptions = groups.map((group) => ({
8344
+ value: group.name,
8345
+ label: `${group.name} (${group.locations})`
8346
+ }));
8461
8347
  const selectedGroup = await select({
8462
8348
  message: "Select a Turso database group:",
8463
- options: groups.map((group) => ({
8464
- value: group.name,
8465
- label: `${group.name} (${group.locations})`
8466
- }))
8349
+ options: groupOptions
8467
8350
  });
8468
8351
  if (isCancel(selectedGroup)) return userCancelled("Operation cancelled");
8469
8352
  return Result.ok(selectedGroup);
@@ -8561,23 +8444,25 @@ async function setupTurso(config, cliInput) {
8561
8444
  return Result.ok(void 0);
8562
8445
  }
8563
8446
  let mode = setupMode;
8564
- if (!mode) if (isSilent()) mode = "manual";
8565
- else {
8566
- const promptedMode = await select({
8567
- message: "Turso setup: choose mode",
8568
- options: [{
8569
- label: "Automatic",
8570
- value: "auto",
8571
- hint: "Automated setup with provider CLI, sets .env"
8572
- }, {
8573
- label: "Manual",
8574
- value: "manual",
8575
- hint: "Manual setup, add env vars yourself"
8576
- }],
8577
- initialValue: "auto"
8578
- });
8579
- if (isCancel(promptedMode)) return userCancelled("Operation cancelled");
8580
- mode = promptedMode;
8447
+ if (!mode) {
8448
+ if (isSilent()) mode = "manual";
8449
+ else {
8450
+ const promptedMode = await select({
8451
+ message: "Turso setup: choose mode",
8452
+ options: [{
8453
+ label: "Automatic",
8454
+ value: "auto",
8455
+ hint: "Automated setup with provider CLI, sets .env"
8456
+ }, {
8457
+ label: "Manual",
8458
+ value: "manual",
8459
+ hint: "Manual setup, add env vars yourself"
8460
+ }],
8461
+ initialValue: "auto"
8462
+ });
8463
+ if (isCancel(promptedMode)) return userCancelled("Operation cancelled");
8464
+ mode = types_exports.DbSetupModeSchema.parse(promptedMode);
8465
+ }
8581
8466
  }
8582
8467
  if (mode === "manual") {
8583
8468
  const envResult = await writeEnvFile(projectDir, backend);
@@ -8586,7 +8471,7 @@ async function setupTurso(config, cliInput) {
8586
8471
  return Result.ok(void 0);
8587
8472
  }
8588
8473
  setupSpinner.start("Checking Turso CLI availability...");
8589
- const platform = os.platform();
8474
+ const platform = os$1.platform();
8590
8475
  const isMac = platform === "darwin";
8591
8476
  if (platform === "win32") {
8592
8477
  setupSpinner.stop(pc.yellow("Turso setup not supported on Windows"));
@@ -8599,14 +8484,16 @@ async function setupTurso(config, cliInput) {
8599
8484
  setupSpinner.stop("Turso CLI availability checked");
8600
8485
  if (!await isTursoInstalled()) {
8601
8486
  let shouldInstall = cliInput?.dbSetupOptions?.turso?.installCli;
8602
- if (shouldInstall === void 0) if (isSilent()) shouldInstall = false;
8603
- else {
8604
- const promptedInstall = await confirm({
8605
- message: "Would you like to install Turso CLI?",
8606
- initialValue: true
8607
- });
8608
- if (isCancel(promptedInstall)) return userCancelled("Operation cancelled");
8609
- shouldInstall = promptedInstall;
8487
+ if (shouldInstall === void 0) {
8488
+ if (isSilent()) shouldInstall = false;
8489
+ else {
8490
+ const promptedInstall = await confirm({
8491
+ message: "Would you like to install Turso CLI?",
8492
+ initialValue: true
8493
+ });
8494
+ if (isCancel(promptedInstall)) return userCancelled("Operation cancelled");
8495
+ shouldInstall = z$1.boolean().parse(promptedInstall);
8496
+ }
8610
8497
  }
8611
8498
  if (!shouldInstall) {
8612
8499
  const envResult = await writeEnvFile(projectDir, backend);
@@ -8641,11 +8528,13 @@ async function setupTurso(config, cliInput) {
8641
8528
  }
8642
8529
  }
8643
8530
  let selectedGroup = cliInput?.dbSetupOptions?.turso?.groupName ?? config.dbSetupOptions?.turso?.groupName ?? null;
8644
- if (!selectedGroup) if (isSilent()) selectedGroup = (await getTursoGroups())[0]?.name ?? null;
8645
- else {
8646
- const groupResult = await selectTursoGroup();
8647
- if (groupResult.isErr()) return Result.err(groupResult.error);
8648
- selectedGroup = groupResult.value;
8531
+ if (!selectedGroup) {
8532
+ if (isSilent()) selectedGroup = (await getTursoGroups())[0]?.name ?? null;
8533
+ else {
8534
+ const groupResult = await selectTursoGroup();
8535
+ if (groupResult.isErr()) return Result.err(groupResult.error);
8536
+ selectedGroup = groupResult.value;
8537
+ }
8649
8538
  }
8650
8539
  let suggestedName = cliInput?.dbSetupOptions?.turso?.databaseName ?? config.dbSetupOptions?.turso?.databaseName ?? path.basename(projectDir);
8651
8540
  if (isSilent()) {
@@ -8810,7 +8699,7 @@ function getDockerInstallInstructions(platform, database) {
8810
8699
  return `${pc.yellow("IMPORTANT:")} Docker required for ${databaseName}. Install for ${platformName}:\n${pc.blue(installUrl)}`;
8811
8700
  }
8812
8701
  async function getDockerStatus(database) {
8813
- const platform = os.platform();
8702
+ const platform = os$1.platform();
8814
8703
  if (!await isDockerInstalled()) return {
8815
8704
  installed: false,
8816
8705
  running: false,
@@ -8862,7 +8751,7 @@ async function displayPostInstallInstructions(config) {
8862
8751
  const pwaInstructions = addons?.includes("pwa") && frontend?.includes("react-router") ? getPwaInstructions() : "";
8863
8752
  const starlightInstructions = addons?.includes("starlight") ? getStarlightInstructions(runCmd) : "";
8864
8753
  const clerkInstructions = config.auth === "clerk" ? getClerkInstructions(frontend || [], backend, api) : "";
8865
- const alchemyDeployInstructions = getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy, backend, config.auth, frontend || []);
8754
+ const alchemyDeployInstructions = getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy, backend, config.auth, frontend || [], addons || []);
8866
8755
  const hasWeb = frontend?.some((f) => types_exports.webFrontends.includes(f));
8867
8756
  const hasNative = frontend?.includes("native-bare") || frontend?.includes("native-uniwind") || frontend?.includes("native-unistyles");
8868
8757
  const hasReactRouter = frontend?.includes("react-router");
@@ -8879,6 +8768,7 @@ async function displayPostInstallInstructions(config) {
8879
8768
  if (database === "sqlite" && dbSetup !== "d1") output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} db:local\n${pc.dim(" (optional - starts local SQLite database)")}\n`;
8880
8769
  const hasAlchemyD1 = dbSetup === "d1" && (serverDeploy === "cloudflare" || isBackendSelf && webDeploy === "cloudflare");
8881
8770
  const hasWranglerLocalD1 = (0, types_exports.getLocalD1Owner)(config) === "wrangler";
8771
+ if (orm === "prisma" && !hasAlchemyD1) output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} db:generate\n`;
8882
8772
  if (hasAlchemyD1 && orm !== "none") {
8883
8773
  output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} db:generate\n`;
8884
8774
  if (orm === "prisma") output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} db:migrate\n`;
@@ -9077,7 +8967,7 @@ function getElectrobunInstructions(runCmd, frontend) {
9077
8967
  return `\n${pc.bold("Desktop app with Electrobun:")}\n${pc.cyan("•")} Start desktop app with HMR (runs the whole stack): ${`${runCmd} dev:desktop`}\n${pc.cyan("•")} Build stable desktop app (DMG/App): ${`${runCmd} build:desktop`}\n${pc.cyan("•")} Build canary desktop app: ${`${runCmd} build:desktop:canary`}\n${pc.yellow("NOTE:")} Electrobun wraps your web frontend in a desktop shell.\n The first desktop command downloads Hutch and the Electrobun toolchain.\n Packaged builds bake the API URL from apps/web/.env, so point it at a reachable server first.\n See: https://framework.blackboard.sh/electrobun/${staticBuildNote ? `\n${staticBuildNote}` : ""}`;
9078
8968
  }
9079
8969
  function getPwaInstructions() {
9080
- return `\n${pc.bold("PWA with React Router v7:")}\n${pc.yellow("NOTE:")} There is a known compatibility issue between VitePWA\n and React Router v7. See:\n https://github.com/vite-pwa/vite-plugin-pwa/issues/809`;
8970
+ return `\n${pc.bold("PWA with React Router:")}\n${pc.yellow("NOTE:")} Verify PWA behavior with a production build on HTTPS or localhost.\n Offline navigation shows a precached fallback page.\n Server-rendered pages require a connection.`;
9081
8971
  }
9082
8972
  function getStarlightInstructions(runCmd) {
9083
8973
  return `\n${pc.bold("Documentation with Starlight:")}\n${pc.cyan("•")} Start docs site: ${`cd apps/docs && ${runCmd} dev`}\n${pc.cyan("•")} Build docs site: ${`cd apps/docs && ${runCmd} build`}`;
@@ -9159,21 +9049,26 @@ function getPolarInstructions(backend, packageManager) {
9159
9049
  const envPath = backend === "self" ? "apps/web/.env" : "apps/server/.env";
9160
9050
  return `${pc.bold("Polar Payments Setup:")}\n${pc.cyan("•")} Get access token & product ID from ${pc.underline("https://sandbox.polar.sh/")}\n${pc.cyan("•")} Set POLAR_ACCESS_TOKEN in ${envPath}`;
9161
9051
  }
9162
- function getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy, backend, auth, frontend) {
9052
+ function getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy, backend, auth, frontend, addons) {
9163
9053
  const instructions = [];
9164
9054
  const isBackendSelf = backend === "self";
9165
9055
  const hasAlchemyWeb = (0, types_exports.isAlchemyDeployTarget)(webDeploy);
9166
9056
  const hasAlchemyServer = (0, types_exports.isAlchemyDeployTarget)(serverDeploy);
9057
+ const hasAxiom = addons.includes("axiom");
9167
9058
  const alchemyExec = runCmd === "npm run" ? "npx" : runCmd === "pnpm run" ? "pnpm exec" : "bunx";
9168
- if (hasAlchemyWeb || hasAlchemyServer) {
9169
- const targetParts = [...hasAlchemyWeb ? [`web on ${webDeploy === "cloudflare" ? "Cloudflare" : "Prisma"}`] : [], ...hasAlchemyServer && !isBackendSelf ? [`server on ${serverDeploy === "cloudflare" ? "Cloudflare" : "Prisma"}`] : []];
9170
- const deployScript = webDeploy === "vercel" ? "deploy:server" : serverDeploy === "vercel" ? "deploy:web" : "deploy";
9059
+ if (hasAlchemyWeb || hasAlchemyServer || hasAxiom) {
9060
+ const targetParts = [
9061
+ ...hasAlchemyWeb ? [`web on ${webDeploy === "cloudflare" ? "Cloudflare" : "Prisma"}`] : [],
9062
+ ...hasAlchemyServer && !isBackendSelf ? [`server on ${serverDeploy === "cloudflare" ? "Cloudflare" : "Prisma"}`] : [],
9063
+ ...hasAxiom ? ["Axiom observability"] : []
9064
+ ];
9065
+ const deployScript = !(hasAlchemyWeb || hasAlchemyServer) && (webDeploy === "vercel" || serverDeploy === "vercel" || webDeploy === "docker" || serverDeploy === "docker") ? "deploy:infra" : webDeploy === "vercel" ? "deploy:server" : serverDeploy === "vercel" ? "deploy:web" : "deploy";
9171
9066
  const originSteps = [];
9172
9067
  const hasWeb = frontend.some((value) => types_exports.webFrontends.includes(value));
9173
9068
  if (!isBackendSelf && hasAlchemyServer && hasWeb) originSteps.push(`${pc.cyan("•")} Required after the first deploy: set CORS_ORIGIN in apps/server/.env to the deployed web origin, then deploy again`);
9174
9069
  if (auth === "better-auth" && isBackendSelf && webDeploy === "prisma") originSteps.push(`${pc.cyan("•")} After the first deploy, set BETTER_AUTH_URL in apps/web/.env to the returned web URL, then deploy again`);
9175
9070
  else if (auth === "better-auth" && !isBackendSelf && serverDeploy === "prisma") originSteps.push(`${pc.cyan("•")} After the first deploy, set BETTER_AUTH_URL in apps/server/.env to the returned server URL, then deploy again`);
9176
- instructions.push(`${pc.bold(`Deploy with Alchemy (${targetParts.join(" + ")}):`)}\n${pc.cyan("•")} Configure provider login: ${`cd packages/infra && ${alchemyExec} alchemy login --configure`}\n${pc.cyan("•")} Dev: ${`${runCmd} dev`}\n${pc.cyan("•")} Deploy: ${`${runCmd} ${deployScript}`}\n${originSteps.join("\n")}${originSteps.length > 0 ? "\n" : ""}${pc.cyan("•")} Destroy: ${`${runCmd} destroy`}`);
9071
+ instructions.push(`${pc.bold(`Deploy with Alchemy (${targetParts.join(" + ")}):`)}\n${pc.cyan("•")} Configure provider accounts: ${`cd packages/infra && ${alchemyExec} alchemy profile edit`}\n${hasAxiom && (webDeploy === "vercel" || serverDeploy === "vercel") ? `${pc.cyan("•")} For Axiom, deploy from packages/infra with alchemy deploy --stage preview or --stage production. Link Vercel first: ${`${runCmd} deploy:setup`}\n` : ""}${pc.cyan("•")} Dev: ${`${runCmd} dev`}\n${pc.cyan("•")} Deploy: ${`${runCmd} ${deployScript}`}\n${originSteps.join("\n")}${originSteps.length > 0 ? "\n" : ""}${pc.cyan("•")} Destroy: ${`${runCmd} destroy`}`);
9177
9072
  }
9178
9073
  if (webDeploy === "docker" || serverDeploy === "docker") {
9179
9074
  const dockerTargets = webDeploy === "docker" && serverDeploy === "docker" ? "web + server" : webDeploy === "docker" ? "web" : "server";
@@ -9693,7 +9588,7 @@ const CreateVirtualInputSchema = types_exports.ProjectConfigSchema.omit({
9693
9588
  projectDir: true,
9694
9589
  relativePath: true
9695
9590
  }).partial().strict();
9696
- const t = initTRPC.meta().create();
9591
+ const command = os.$meta({});
9697
9592
  function getCliSchemaJson() {
9698
9593
  return createCli({
9699
9594
  router,
@@ -9710,8 +9605,8 @@ function getSchemaResult(name) {
9710
9605
  if (name === "cli") return getCliSchemaJson();
9711
9606
  return schemas[name];
9712
9607
  }
9713
- const router = t.router({
9714
- create: t.procedure.meta({
9608
+ const router = os.router({
9609
+ create: command.meta({
9715
9610
  description: "Create a new Better-T-Stack project",
9716
9611
  default: true,
9717
9612
  negateBooleans: true
@@ -9743,7 +9638,7 @@ const router = t.router({
9743
9638
  disableAnalytics: z.boolean().optional().default(false).describe("Disable analytics"),
9744
9639
  manualDb: z.boolean().optional().default(false).describe("Skip automatic/manual database setup prompt and use manual setup"),
9745
9640
  dbSetupOptions: types_exports.DbSetupOptionsSchema.optional().describe("Structured database setup options")
9746
- })])).mutation(async ({ input }) => {
9641
+ })])).handler(async ({ input }) => {
9747
9642
  const [projectName, options] = input;
9748
9643
  const result = await createProjectHandler({
9749
9644
  projectName,
@@ -9751,10 +9646,10 @@ const router = t.router({
9751
9646
  });
9752
9647
  if (options.verbose) return result;
9753
9648
  }),
9754
- createJson: t.procedure.meta({
9649
+ createJson: command.meta({
9755
9650
  description: "Create a project from a raw JSON payload (agent-friendly)",
9756
9651
  jsonInput: "always"
9757
- }).input(types_exports.CreateInputSchema).mutation(async ({ input }) => {
9652
+ }).input(types_exports.CreateInputSchema).handler(async ({ input }) => {
9758
9653
  const result = await createProjectHandler(input, {
9759
9654
  silent: true,
9760
9655
  mode: "json"
@@ -9763,11 +9658,11 @@ const router = t.router({
9763
9658
  if (!result.success) throw new CLIError({ message: result.error || "Unknown error occurred" });
9764
9659
  return result;
9765
9660
  }),
9766
- schema: t.procedure.meta({ description: "Show runtime CLI and input schemas as JSON" }).input(z.object({ name: SchemaNameSchema.describe("Schema name to inspect") })).query(({ input }) => getSchemaResult(input.name)),
9767
- sponsors: t.procedure.meta({ description: "Show Better-T-Stack sponsors" }).mutation(() => showSponsorsCommand()),
9768
- docs: t.procedure.meta({ description: "Open Better-T-Stack documentation" }).mutation(() => openDocsCommand()),
9769
- builder: t.procedure.meta({ description: "Open the web-based stack builder" }).mutation(() => openBuilderCommand()),
9770
- add: t.procedure.meta({ description: "Add addons or a workspace package to an existing Better-T-Stack project" }).input(z.object({
9661
+ schema: command.meta({ description: "Show runtime CLI and input schemas as JSON" }).input(z.object({ name: SchemaNameSchema.describe("Schema name to inspect") })).handler(({ input }) => getSchemaResult(input.name)),
9662
+ sponsors: command.meta({ description: "Show Better-T-Stack sponsors" }).handler(() => showSponsorsCommand()),
9663
+ docs: command.meta({ description: "Open Better-T-Stack documentation" }).handler(() => openDocsCommand()),
9664
+ builder: command.meta({ description: "Open the web-based stack builder" }).handler(() => openBuilderCommand()),
9665
+ add: command.meta({ description: "Add addons or a workspace package to an existing Better-T-Stack project" }).input(z.object({
9771
9666
  addons: z.array(types_exports.AddonsSchema).optional().describe("Addons to add"),
9772
9667
  package: types_exports.WorkspacePackageNameSchema.optional(),
9773
9668
  install: z.boolean().optional().default(false).describe("Install dependencies after adding"),
@@ -9775,13 +9670,13 @@ const router = t.router({
9775
9670
  projectDir: z.string().optional().describe("Project directory (defaults to current)"),
9776
9671
  dryRun: z.boolean().optional().default(false).describe("Preview addon changes without writing files"),
9777
9672
  disableAnalytics: z.boolean().optional().default(false).describe("Disable analytics")
9778
- })).mutation(async ({ input }) => {
9673
+ })).handler(async ({ input }) => {
9779
9674
  await addHandler(input);
9780
9675
  }),
9781
- addJson: t.procedure.meta({
9676
+ addJson: command.meta({
9782
9677
  description: "Add addons or a workspace package from a raw JSON payload (agent-friendly)",
9783
9678
  jsonInput: "always"
9784
- }).input(types_exports.AddInputSchema).mutation(async ({ input }) => {
9679
+ }).input(types_exports.AddInputSchema).handler(async ({ input }) => {
9785
9680
  const result = await addHandler(input, {
9786
9681
  silent: true,
9787
9682
  mode: "json"
@@ -9790,11 +9685,11 @@ const router = t.router({
9790
9685
  if (!result.success) throw new CLIError({ message: result.error || "Unknown error occurred" });
9791
9686
  return result;
9792
9687
  }),
9793
- history: t.procedure.meta({ description: "Show project creation history" }).input(z.object({
9688
+ history: command.meta({ description: "Show project creation history" }).input(z.object({
9794
9689
  limit: z.number().optional().default(10).describe("Number of entries to show"),
9795
9690
  clear: z.boolean().optional().default(false).describe("Clear all history"),
9796
9691
  json: z.boolean().optional().default(false).describe("Output as JSON")
9797
- })).mutation(async ({ input }) => {
9692
+ })).handler(async ({ input }) => {
9798
9693
  await historyHandler(input);
9799
9694
  })
9800
9695
  });