create-better-t-stack 3.47.0 → 3.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -94,9 +94,9 @@ Options:
94
94
 
95
95
  ```bash
96
96
  # Raw JSON payload input (agent-friendly)
97
- create-better-t-stack create-json --input '{"projectName":"my-app","yes":true,"dryRun":true}'
98
- create-better-t-stack add-json --input '{"projectDir":"./my-app","addons":["wxt"],"addonOptions":{"wxt":{"template":"react"}}}'
99
- create-better-t-stack create-json --input '{"projectName":"db-app","database":"postgres","orm":"drizzle","dbSetup":"neon","dbSetupOptions":{"mode":"manual"}}'
97
+ create-better-t-stack create-json --json '{"projectName":"my-app","yes":true,"dryRun":true}'
98
+ create-better-t-stack add-json --json '{"projectDir":"./my-app","addons":["wxt"],"addonOptions":{"wxt":{"template":"react"}}}'
99
+ create-better-t-stack create-json --json '{"projectName":"db-app","database":"postgres","orm":"drizzle","dbSetup":"neon","dbSetupOptions":{"mode":"manual"}}'
100
100
 
101
101
  # Runtime schema/introspection output
102
102
  create-better-t-stack schema --name all
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { D as setProcessMode, i as SchemaNameSchema, l as create, m as getSchemaResult, s as add, u as createBtsCli, v as types_exports, y as getLatestCLIVersion } from "./src-CC7QNkfh.mjs";
2
+ import { D as setProcessMode, i as SchemaNameSchema, l as create, m as getSchemaResult, s as add, u as createBtsCli, v as types_exports, y as getLatestCLIVersion } from "./src-DswDqMzo.mjs";
3
3
  import { lineByLineConsoleLogger } from "trpc-cli";
4
4
  import z from "zod";
5
5
  import { McpServer } from "@modelcontextprotocol/server";
package/dist/index.d.mts CHANGED
@@ -592,7 +592,7 @@ export declare const router: {
592
592
  "expo/skills": "expo/skills";
593
593
  "prisma/skills": "prisma/skills";
594
594
  "elysiajs/skills": "elysiajs/skills";
595
- "waynesutton/convexskills": "waynesutton/convexskills";
595
+ "get-convex/agent-skills": "get-convex/agent-skills";
596
596
  "msmps/opentui-skill": "msmps/opentui-skill";
597
597
  "haydenbleasel/ultracite": "haydenbleasel/ultracite";
598
598
  "https://www.evlog.dev": "https://www.evlog.dev";
@@ -606,7 +606,6 @@ export declare const router: {
606
606
  oxlint: "oxlint";
607
607
  }>>;
608
608
  editors: z.ZodOptional<z.ZodArray<z.ZodEnum<{
609
- void: "void";
610
609
  antigravity: "antigravity";
611
610
  cursor: "cursor";
612
611
  vscode: "vscode";
@@ -617,6 +616,7 @@ export declare const router: {
617
616
  codebuddy: "codebuddy";
618
617
  devin: "devin";
619
618
  trae: "trae";
619
+ void: "void";
620
620
  kiro: "kiro";
621
621
  }>>>;
622
622
  agents: z.ZodOptional<z.ZodArray<z.ZodEnum<{
@@ -1119,7 +1119,7 @@ export declare const router: {
1119
1119
  "expo/skills": "expo/skills";
1120
1120
  "prisma/skills": "prisma/skills";
1121
1121
  "elysiajs/skills": "elysiajs/skills";
1122
- "waynesutton/convexskills": "waynesutton/convexskills";
1122
+ "get-convex/agent-skills": "get-convex/agent-skills";
1123
1123
  "msmps/opentui-skill": "msmps/opentui-skill";
1124
1124
  "haydenbleasel/ultracite": "haydenbleasel/ultracite";
1125
1125
  "https://www.evlog.dev": "https://www.evlog.dev";
@@ -1133,7 +1133,6 @@ export declare const router: {
1133
1133
  oxlint: "oxlint";
1134
1134
  }>>;
1135
1135
  editors: z.ZodOptional<z.ZodArray<z.ZodEnum<{
1136
- void: "void";
1137
1136
  antigravity: "antigravity";
1138
1137
  cursor: "cursor";
1139
1138
  vscode: "vscode";
@@ -1144,6 +1143,7 @@ export declare const router: {
1144
1143
  codebuddy: "codebuddy";
1145
1144
  devin: "devin";
1146
1145
  trae: "trae";
1146
+ void: "void";
1147
1147
  kiro: "kiro";
1148
1148
  }>>>;
1149
1149
  agents: z.ZodOptional<z.ZodArray<z.ZodEnum<{
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import { C as DirectoryConflictError, E as ValidationError, S as DatabaseSetupError, T as UserCancelledError, _ as ProjectLauncherSchema, a as TEMPLATE_COUNT, b as CLIError, c as builder, d as createVirtual, f as docs, g as sponsors, h as router, i as SchemaNameSchema, l as create, m as getSchemaResult, n as GeneratorError, o as VirtualFileSystem, p as generate, r as Result, s as add, t as EMBEDDED_TEMPLATES, u as createBtsCli, w as ProjectCreationError, x as CompatibilityError } from "./src-CC7QNkfh.mjs";
2
+ import { C as DirectoryConflictError, E as ValidationError, S as DatabaseSetupError, T as UserCancelledError, _ as ProjectLauncherSchema, a as TEMPLATE_COUNT, b as CLIError, c as builder, d as createVirtual, f as docs, g as sponsors, h as router, i as SchemaNameSchema, l as create, m as getSchemaResult, n as GeneratorError, o as VirtualFileSystem, p as generate, r as Result, s as add, t as EMBEDDED_TEMPLATES, u as createBtsCli, w as ProjectCreationError, x as CompatibilityError } from "./src-DswDqMzo.mjs";
3
3
  export { CLIError, CompatibilityError, DatabaseSetupError, DirectoryConflictError, EMBEDDED_TEMPLATES, GeneratorError, ProjectCreationError, ProjectLauncherSchema, Result, SchemaNameSchema, TEMPLATE_COUNT, UserCancelledError, ValidationError, VirtualFileSystem, add, builder, create, createBtsCli, createVirtual, docs, generate, getSchemaResult, router, sponsors };
@@ -228,7 +228,8 @@ function defaultContext() {
228
228
  silent: false,
229
229
  verbose: false,
230
230
  promptShown: false,
231
- analyticsDisabled: false
231
+ analyticsDisabled: false,
232
+ stepFailures: []
232
233
  };
233
234
  }
234
235
  function getContext() {
@@ -290,7 +291,8 @@ function createContext(options) {
290
291
  analyticsDisabled: options.analyticsDisabled ?? false,
291
292
  projectDir: options.projectDir,
292
293
  projectName: options.projectName,
293
- packageManager: options.packageManager
294
+ packageManager: options.packageManager,
295
+ stepFailures: []
294
296
  };
295
297
  }
296
298
  async function runWithContextAsync(options, fn) {
@@ -337,6 +339,8 @@ function beginInterruptibleScope() {
337
339
  endInterruptibleScope();
338
340
  stepInterrupted = false;
339
341
  anyStepInterrupted = false;
342
+ const ctx = tryGetContext();
343
+ if (ctx) ctx.stepFailures = [];
340
344
  listener = () => {
341
345
  stepInterrupted = true;
342
346
  anyStepInterrupted = true;
@@ -360,6 +364,13 @@ function wasInterrupted() {
360
364
  function wasAnyStepInterrupted() {
361
365
  return anyStepInterrupted;
362
366
  }
367
+ /** Records an optional step that failed, so silent/JSON callers can report it. */
368
+ function recordStepFailure(message) {
369
+ tryGetContext()?.stepFailures.push(message);
370
+ }
371
+ function getStepFailures() {
372
+ return [...tryGetContext()?.stepFailures ?? []];
373
+ }
363
374
  function getInterruptSignal() {
364
375
  return controller?.signal;
365
376
  }
@@ -1697,10 +1708,6 @@ function parseBtsConfig(content) {
1697
1708
  const errors = [];
1698
1709
  const value = parse(content, errors, { allowTrailingComma: true });
1699
1710
  if (errors.length > 0) throw new Error("Invalid JSONC in bts.jsonc");
1700
- const selections = value?.addonOptions?.skills?.selections;
1701
- if (Array.isArray(selections)) {
1702
- for (const selection of selections) if (selection?.source === "yusukebe/hono-skill") selection.source = "honojs/skills";
1703
- }
1704
1711
  return BetterTStackConfigSchema.parse(value);
1705
1712
  }
1706
1713
  async function readBtsConfig(projectDir) {
@@ -1888,7 +1895,7 @@ function getNodeToolingRequirements(config) {
1888
1895
  addNodeRequirement(requirements, ">=22.12.0", "Astro 7");
1889
1896
  break;
1890
1897
  case "nuxt":
1891
- addNodeRequirement(requirements, "^22.19.0 || ^24.11.0 || >=26.0.0", "Nuxt 4");
1898
+ addNodeRequirement(requirements, "^22.22.3 || ^24.15.0 || >=26.0.0", "Nuxt 4.6");
1892
1899
  break;
1893
1900
  case "solid":
1894
1901
  addNodeRequirement(requirements, ">=24.0.0", "Solid");
@@ -1897,6 +1904,8 @@ function getNodeToolingRequirements(config) {
1897
1904
  addNodeRequirement(requirements, ">=22.22.0", "React Router 8");
1898
1905
  break;
1899
1906
  case "svelte":
1907
+ addNodeRequirement(requirements, ">=22.17.0", "SvelteKit 3");
1908
+ break;
1900
1909
  case "tanstack-router":
1901
1910
  addNodeRequirement(requirements, "^20.19.0 || >=22.12.0", "Vite 8");
1902
1911
  break;
@@ -1951,11 +1960,8 @@ function getBaselineRequirements(packageManager, hostRuntime) {
1951
1960
  }
1952
1961
  function getLocalVersionRequirements(config, hostRuntime) {
1953
1962
  const requirements = getBaselineRequirements(config.packageManager, hostRuntime);
1954
- if (config.packageManager !== "bun") requirements.push(...getNodeToolingRequirements(config));
1955
- else {
1956
- requirements.push(...getAddonNodeRequirements(config));
1957
- if (config.runtime === "node") addNodeRequirement(requirements, ">=22.0.0", "the selected Node.js server runtime");
1958
- }
1963
+ requirements.push(...getNodeToolingRequirements(config));
1964
+ if (config.runtime === "node") addNodeRequirement(requirements, ">=22.0.0", "the selected Node.js server runtime");
1959
1965
  return requirements;
1960
1966
  }
1961
1967
  function formatRequirementError(requirement, rawVersion) {
@@ -2082,6 +2088,7 @@ async function runOptionalStep(step, cancelledMessage) {
2082
2088
  cliLog.warn(pc.yellow(cancelledMessage));
2083
2089
  return;
2084
2090
  }
2091
+ recordStepFailure(result.error.message);
2085
2092
  cliConsola.error(pc.red(result.error.message));
2086
2093
  }
2087
2094
  //#endregion
@@ -2278,9 +2285,24 @@ function addNuxtEvlogSetup(content, serviceName) {
2278
2285
  return nextContent;
2279
2286
  }
2280
2287
  function addSvelteViteEvlogSetup(content, serviceName) {
2281
- let nextContent = prependMissingImports(content, ["import evlog from \"evlog/vite\";"]);
2288
+ const nextContent = prependMissingImports(content, ["import evlog from \"evlog/vite\";"]);
2282
2289
  if (nextContent.includes("evlog({")) return nextContent;
2283
- return nextContent.replace("sveltekit(),", `sveltekit(),\n evlog({ service: "${serviceName}" }),`);
2290
+ const source = ts.createSourceFile("vite.config.ts", nextContent, ts.ScriptTarget.Latest);
2291
+ let insertion;
2292
+ function visit(node) {
2293
+ if (insertion !== void 0) return;
2294
+ if (ts.isPropertyAssignment(node) && node.name.getText(source) === "plugins" && ts.isArrayLiteralExpression(node.initializer)) {
2295
+ const kitPlugin = node.initializer.elements.find((element) => ts.isCallExpression(element) && element.expression.getText(source) === "sveltekit");
2296
+ if (kitPlugin) {
2297
+ insertion = kitPlugin.getEnd();
2298
+ return;
2299
+ }
2300
+ }
2301
+ ts.forEachChild(node, visit);
2302
+ }
2303
+ visit(source);
2304
+ if (insertion === void 0) return nextContent;
2305
+ return `${nextContent.slice(0, insertion)},\n evlog({ service: ${JSON.stringify(serviceName)} })${nextContent.slice(insertion)}`;
2284
2306
  }
2285
2307
  function getSvelteEvlogHooksCall(fsDrain, axiom = false) {
2286
2308
  if (axiom) return "createEvlogHooks({ drain: createAxiomDrain() })";
@@ -2292,7 +2314,7 @@ function addSvelteHooksEvlogSetup(content, fsDrain, axiom) {
2292
2314
  ...fsDrain ? ["import { createFsDrain } from \"evlog/fs\";"] : [],
2293
2315
  ...axiom ? ["import { createAxiomDrain } from \"evlog/axiom\";"] : []
2294
2316
  ]);
2295
- if (fsDrain) nextContent = addNamedImport(nextContent, "$app/environment", ["dev"]);
2317
+ if (fsDrain) nextContent = addNamedImport(nextContent, "$app/env", ["dev"]);
2296
2318
  const hooksCall = getSvelteEvlogHooksCall(fsDrain, axiom);
2297
2319
  if (fsDrain || axiom) nextContent = nextContent.replaceAll("createEvlogHooks()", hooksCall);
2298
2320
  if (!nextContent.includes("export const handle") && !nextContent.includes("const authHandle")) {
@@ -2302,7 +2324,7 @@ function addSvelteHooksEvlogSetup(content, fsDrain, axiom) {
2302
2324
  nextContent = prependMissingImports(nextContent, ["import { sequence } from \"@sveltejs/kit/hooks\";"]);
2303
2325
  if (!nextContent.includes("const { handle: evlogHandle, handleError }")) nextContent = nextContent.replace(/((?:import .+\n)+)/, `$1\nconst { handle: evlogHandle, handleError } = ${hooksCall};\n\n`);
2304
2326
  nextContent = nextContent.replace(/export const handle(:\s*Handle)?\s*=\s*async/, (_match, typeAnnotation) => `const authHandle${typeAnnotation ?? ""} = async`);
2305
- if (!nextContent.includes("sequence(evlogHandle, authHandle)")) nextContent = `${nextContent.trimEnd()}\n\nexport const handle = sequence(evlogHandle as Handle, authHandle);\nexport { handleError };\n`;
2327
+ if (!nextContent.includes("sequence(evlogHandle, authHandle)")) nextContent = `${nextContent.trimEnd()}\n\nexport const handle = sequence(evlogHandle, authHandle);\nexport { handleError };\n`;
2306
2328
  return nextContent;
2307
2329
  }
2308
2330
  function addSvelteLocalsType(content) {
@@ -2398,7 +2420,7 @@ function addNextBetterAuthToRoute(content) {
2398
2420
  }
2399
2421
  function addSvelteBetterAuthEvlogSetup(content, config) {
2400
2422
  if (!content.includes("authHandle") || content.includes("evlogAuthHandle")) return content;
2401
- let nextContent = addNamedImport(content, "evlog/better-auth", ["createAuthMiddleware", "type BetterAuthInstance"]);
2423
+ let nextContent = addNamedImport(content, "evlog/better-auth", ["createAuthMiddleware"]);
2402
2424
  if (!nextContent.includes("from \"./services\"")) nextContent = prependMissingImports(nextContent, [getAuthImportLine(config, "./services")]);
2403
2425
  let environmentBinding = "ENV";
2404
2426
  if (usesCreateAuthFactory(config) && config.webDeploy === "cloudflare") {
@@ -2411,10 +2433,10 @@ function addSvelteBetterAuthEvlogSetup(content, config) {
2411
2433
  }
2412
2434
  const authExpression = getAuthExpression(config);
2413
2435
  const authOptions = "{ exclude: [\"/api/auth/**\"], maskEmail: true }";
2414
- 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 ?? ${environmentBinding};\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`;
2436
+ 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 = ${environmentBinding};\n\tconst identifyUser = createAuthMiddleware(await createAuth(authEnv), ${authOptions});\n\tawait identifyUser(event.locals.log, event.request.headers, event.url.pathname);\n\treturn resolve(event);\n};\n\n` : `const identifyUser = createAuthMiddleware(${authExpression}, ${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`;
2415
2437
  const evlogHandleDeclaration = nextContent.match(/const \{ handle: evlogHandle, handleError \} = createEvlogHooks\([\s\S]*?\);\n\n/)?.[0];
2416
2438
  if (evlogHandleDeclaration) nextContent = insertAfterOnce(nextContent, evlogHandleDeclaration, authHandleSnippet, "evlogAuthHandle");
2417
- return nextContent.replace("sequence(evlogHandle as Handle, authHandle)", "sequence(evlogHandle as Handle, evlogAuthHandle, authHandle)").replace("sequence(evlogHandle, authHandle)", "sequence(evlogHandle as Handle, evlogAuthHandle, authHandle)");
2439
+ return nextContent.replace("sequence(evlogHandle as Handle, authHandle)", "sequence(evlogHandle, evlogAuthHandle, authHandle)").replace("sequence(evlogHandle, authHandle)", "sequence(evlogHandle, evlogAuthHandle, authHandle)");
2418
2440
  }
2419
2441
  function addAstroBetterAuthEvlogSetup(content, config) {
2420
2442
  if (content.includes("createAuthMiddleware(")) return content;
@@ -2447,14 +2469,16 @@ export const { register, onRequestError } = createInstrumentation({
2447
2469
  });
2448
2470
  `;
2449
2471
  }
2472
+ const NITRO_V3_PLUGIN_IMPORT = "import { definePlugin as defineNitroPlugin } from \"nitro\";\n";
2450
2473
  function getNitroEvlogDrainFile(axiom = false, nitroV3 = false) {
2451
- if (axiom) return `${nitroV3 ? "import { definePlugin as defineNitroPlugin } from \"nitro\";\n" : ""}import { createAxiomDrain } from "evlog/axiom";
2474
+ const pluginImport = nitroV3 ? NITRO_V3_PLUGIN_IMPORT : "";
2475
+ if (axiom) return `${pluginImport}import { createAxiomDrain } from "evlog/axiom";
2452
2476
 
2453
2477
  export default defineNitroPlugin((nitroApp) => {
2454
2478
  nitroApp.hooks.hook("evlog:drain", createAxiomDrain());
2455
2479
  });
2456
2480
  `;
2457
- return `import { createFsDrain } from "evlog/fs";
2481
+ return `${pluginImport}import { createFsDrain } from "evlog/fs";
2458
2482
 
2459
2483
  export default defineNitroPlugin((nitroApp) => {
2460
2484
  if (!import.meta.dev) return;
@@ -2505,31 +2529,32 @@ export async function identifyEvlogUser(request: Request) {
2505
2529
  }
2506
2530
  `;
2507
2531
  }
2508
- function getNitroEvlogAuthPluginFile(config) {
2532
+ function getTanstackStartEvlogAuthMiddlewareFile(config) {
2509
2533
  if (usesCreateAuthFactory(config)) return `${getAuthImportLine(config, "../../src/services")}
2510
- import { createAuthIdentifier, type BetterAuthInstance } from "evlog/better-auth";
2534
+ import { createAuthMiddleware, type BetterAuthInstance } from "evlog/better-auth";
2535
+ import { useLogger } from "evlog/nitro/v3";
2536
+ import { defineHandler } from "nitro";
2511
2537
 
2512
- export default defineNitroPlugin((nitroApp) => {
2513
- nitroApp.hooks.hook("request", async (event) => {
2514
- const identify = createAuthIdentifier(${getAuthExpression(config)} as BetterAuthInstance, {
2515
- exclude: ["/api/auth/**"],
2516
- maskEmail: true,
2517
- });
2518
- await identify(event);
2538
+ export default defineHandler(async (event) => {
2539
+ const identifyUser = createAuthMiddleware(${getAuthExpression(config)} as BetterAuthInstance, {
2540
+ exclude: ["/api/auth/**"],
2541
+ maskEmail: true,
2519
2542
  });
2543
+ await identifyUser(useLogger(event), event.req.headers, event.url.pathname);
2520
2544
  });
2521
2545
  `;
2522
2546
  return `${getAuthImportLine(config, "../../src/services")}
2523
- import { createAuthIdentifier, type BetterAuthInstance } from "evlog/better-auth";
2547
+ import { createAuthMiddleware, type BetterAuthInstance } from "evlog/better-auth";
2548
+ import { useLogger } from "evlog/nitro/v3";
2549
+ import { defineHandler } from "nitro";
2524
2550
 
2525
- export default defineNitroPlugin((nitroApp) => {
2526
- nitroApp.hooks.hook(
2527
- "request",
2528
- createAuthIdentifier(${getAuthExpression(config)} as BetterAuthInstance, {
2529
- exclude: ["/api/auth/**"],
2530
- maskEmail: true,
2531
- }),
2532
- );
2551
+ const identifyUser = createAuthMiddleware(${getAuthExpression(config)} as BetterAuthInstance, {
2552
+ exclude: ["/api/auth/**"],
2553
+ maskEmail: true,
2554
+ });
2555
+
2556
+ export default defineHandler(async (event) => {
2557
+ await identifyUser(useLogger(event), event.req.headers, event.url.pathname);
2533
2558
  });
2534
2559
  `;
2535
2560
  }
@@ -2664,10 +2689,6 @@ async function setupNuxtEvlog(config, serviceName) {
2664
2689
  if (!await fs.pathExists(drainPath)) await writeFileIfChanged(drainPath, getNitroEvlogDrainFile(usesAxiom(config), config.frontend.includes("tanstack-start")));
2665
2690
  }
2666
2691
  if (shouldIdentifyWebAuth(config)) {
2667
- const oldAuthPluginPath = path.join(webDir, "server/plugins/evlog-auth.ts");
2668
- if (await fs.pathExists(oldAuthPluginPath)) {
2669
- if ((await fs.readFile(oldAuthPluginPath, "utf-8")).includes("evlog/better-auth")) await fs.remove(oldAuthPluginPath);
2670
- }
2671
2692
  const authMiddlewarePath = path.join(webDir, "server/middleware/evlog-auth.ts");
2672
2693
  if (!await fs.pathExists(authMiddlewarePath)) await writeFileIfChanged(authMiddlewarePath, getNuxtEvlogAuthMiddlewareFile(config));
2673
2694
  }
@@ -2683,7 +2704,7 @@ async function setupSvelteEvlog(config, serviceName) {
2683
2704
  else await writeFileIfChanged(hooksPath, `import { createEvlogHooks } from "evlog/sveltekit";
2684
2705
  ${fsDrain ? "import { createFsDrain } from \"evlog/fs\";\n" : ""}
2685
2706
  ${axiom ? "import { createAxiomDrain } from \"evlog/axiom\";\n" : ""}
2686
- ${fsDrain ? "import { dev } from \"$app/environment\";\n" : ""}
2707
+ ${fsDrain ? "import { dev } from \"$app/env\";\n" : ""}
2687
2708
 
2688
2709
  export const { handle, handleError } = ${getSvelteEvlogHooksCall(fsDrain, axiom)};
2689
2710
  `);
@@ -2725,8 +2746,8 @@ async function setupTanstackStartEvlog(config, serviceName) {
2725
2746
  if (!await fs.pathExists(drainPath)) await writeFileIfChanged(drainPath, getNitroEvlogDrainFile(usesAxiom(config), config.frontend.includes("tanstack-start")));
2726
2747
  }
2727
2748
  if (shouldIdentifyWebAuth(config)) {
2728
- const authPluginPath = path.join(webDir, "server/plugins/evlog-auth.ts");
2729
- if (!await fs.pathExists(authPluginPath)) await writeFileIfChanged(authPluginPath, getNitroEvlogAuthPluginFile(config));
2749
+ const authMiddlewarePath = path.join(webDir, "server/middleware/evlog-auth.ts");
2750
+ if (!await fs.pathExists(authMiddlewarePath)) await writeFileIfChanged(authMiddlewarePath, getTanstackStartEvlogAuthMiddlewareFile(config));
2730
2751
  }
2731
2752
  if (config.examples.includes("ai")) await updateFileIfExists(path.join(webDir, "src/routes/api/ai/$.ts"), addTanstackStartAiEvlogSetup);
2732
2753
  }
@@ -3607,7 +3628,7 @@ const SKILL_SOURCES = {
3607
3628
  "expo/skills": { label: "Expo" },
3608
3629
  "prisma/skills": { label: "Prisma" },
3609
3630
  "elysiajs/skills": { label: "ElysiaJS" },
3610
- "waynesutton/convexskills": { label: "Convex" },
3631
+ "get-convex/agent-skills": { label: "Convex" },
3611
3632
  "msmps/opentui-skill": { label: "OpenTUI Platform" },
3612
3633
  "haydenbleasel/ultracite": { label: "Ultracite" },
3613
3634
  "https://www.evlog.dev": { label: "evlog" }
@@ -3994,7 +4015,7 @@ function getRecommendedSourceKeys(config) {
3994
4015
  if (addons.includes("turborepo")) sources.push("vercel/turborepo");
3995
4016
  if (backend === "hono") sources.push("honojs/skills");
3996
4017
  if (backend === "elysia") sources.push("elysiajs/skills");
3997
- if (backend === "convex") sources.push("waynesutton/convexskills");
4018
+ if (backend === "convex") sources.push("get-convex/agent-skills");
3998
4019
  if (addons.includes("opentui")) sources.push("msmps/opentui-skill");
3999
4020
  if (addons.includes("ultracite")) sources.push("haydenbleasel/ultracite");
4000
4021
  if (addons.includes("evlog") || addons.includes("axiom")) sources.push("https://www.evlog.dev");
@@ -4051,36 +4072,32 @@ const CURATED_SKILLS_BY_SOURCE = {
4051
4072
  if (config.database === "mysql") return ["mysql", "vitess"];
4052
4073
  return [];
4053
4074
  },
4054
- "expo/skills": (config) => {
4055
- const skills = [
4056
- "expo-overview",
4057
- "expo-router",
4058
- "expo-dev-client",
4059
- "expo-native-ui",
4060
- "expo-data-fetching",
4061
- "eas-app-stores",
4062
- "eas-workflows"
4063
- ];
4064
- if (config.frontend.includes("native-uniwind")) skills.push("expo-tailwind-setup");
4065
- return skills;
4066
- },
4075
+ "expo/skills": () => [
4076
+ "expo-overview",
4077
+ "expo-router",
4078
+ "expo-dev-client",
4079
+ "expo-native-ui",
4080
+ "expo-data-fetching",
4081
+ "eas-app-stores",
4082
+ "eas-workflows"
4083
+ ],
4067
4084
  "prisma/skills": (config) => {
4068
4085
  const skills = [];
4069
- if (config.orm === "prisma") skills.push("prisma-cli", "prisma-client-api", "prisma-database-setup");
4070
- if (config.dbSetup === "prisma-postgres") skills.push("prisma-postgres");
4086
+ if (config.orm === "prisma") skills.push("prisma-cli", "prisma-client-api", "prisma-orm-setup");
4087
+ if (config.dbSetup === "prisma-postgres") skills.push("prisma-postgres-setup");
4071
4088
  return skills;
4072
4089
  },
4073
4090
  "elysiajs/skills": () => ["elysiajs"],
4074
- "waynesutton/convexskills": () => [
4075
- "convex-best-practices",
4076
- "convex-functions",
4077
- "convex-schema-validator",
4078
- "convex-realtime",
4079
- "convex-http-actions",
4080
- "convex-cron-jobs",
4081
- "convex-file-storage",
4082
- "convex-migrations",
4083
- "convex-security-check"
4091
+ "get-convex/agent-skills": () => [
4092
+ "convex",
4093
+ "convex-design",
4094
+ "convex-docs",
4095
+ "convex-reviewer",
4096
+ "convex-authz",
4097
+ "convex-crons",
4098
+ "convex-migrate",
4099
+ "convex-env",
4100
+ "convex-test"
4084
4101
  ],
4085
4102
  "msmps/opentui-skill": () => ["opentui"],
4086
4103
  "haydenbleasel/ultracite": () => ["ultracite"],
@@ -4189,31 +4206,43 @@ async function setupSkills(config) {
4189
4206
  const runner = getPackageRunnerPrefix(packageManager);
4190
4207
  const globalFlags = scope === "global" ? ["-g"] : [];
4191
4208
  const agentTargets = expandSkillsAgentTargets(selectedAgents);
4192
- for (const [source, skills] of Object.entries(skillsBySource)) if ((await Result.tryPromise({
4193
- try: async () => {
4194
- const args = [
4195
- ...runner,
4196
- "skills@latest",
4197
- "add",
4198
- source,
4199
- ...globalFlags,
4200
- "--skill",
4201
- ...skills,
4202
- "--agent",
4203
- ...agentTargets,
4204
- "-y"
4205
- ];
4206
- await $({
4207
- cwd: projectDir,
4208
- env: { CI: "true" }
4209
- })`${args}`;
4210
- },
4211
- catch: (e) => new AddonSetupError({
4209
+ const failures = [];
4210
+ for (const [source, skills] of Object.entries(skillsBySource)) {
4211
+ const installResult = await Result.tryPromise({
4212
+ try: async () => {
4213
+ const args = [
4214
+ ...runner,
4215
+ "skills@latest",
4216
+ "add",
4217
+ source,
4218
+ ...globalFlags,
4219
+ "--skill",
4220
+ ...skills,
4221
+ "--agent",
4222
+ ...agentTargets,
4223
+ "-y"
4224
+ ];
4225
+ await $({
4226
+ cwd: projectDir,
4227
+ env: { CI: "true" }
4228
+ })`${args}`;
4229
+ },
4230
+ catch: (e) => new AddonSetupError({
4231
+ addon: "skills",
4232
+ message: `Failed to install skills from ${source}: ${e instanceof Error ? e.message : String(e)}`,
4233
+ cause: e
4234
+ })
4235
+ });
4236
+ if (installResult.isErr()) failures.push(installResult.error);
4237
+ }
4238
+ if (failures.length > 0) {
4239
+ installSpinner.stop(pc.red("Failed to install skills"));
4240
+ return Result.err(new AddonSetupError({
4212
4241
  addon: "skills",
4213
- message: `Failed to install skills from ${source}: ${e instanceof Error ? e.message : String(e)}`,
4214
- cause: e
4215
- })
4216
- })).isErr()) cliLog.warn(pc.yellow(`Warning: Could not install skills from ${source}`));
4242
+ message: failures.map((failure) => failure.message).join("\n"),
4243
+ cause: failures.map((failure) => failure.cause)
4244
+ }));
4245
+ }
4217
4246
  installSpinner.stop("Skills installed");
4218
4247
  return Result.ok(void 0);
4219
4248
  }
@@ -5301,7 +5330,31 @@ async function addHandlerInternal(input) {
5301
5330
  });
5302
5331
  }
5303
5332
  });
5304
- if (setupResult.isErr()) return Result.err(setupResult.error);
5333
+ const cleanupReservedPackage = async () => {
5334
+ if (!reservedPackageDir) return void 0;
5335
+ const dir = reservedPackageDir;
5336
+ return (await Result.tryPromise({
5337
+ try: () => fs.remove(dir),
5338
+ catch: (cause) => new CLIError({
5339
+ message: `Failed to remove ${dir}`,
5340
+ cause
5341
+ })
5342
+ })).isErr() ? `Also failed to remove ${dir}; delete it before retrying.` : void 0;
5343
+ };
5344
+ if (setupResult.isErr()) {
5345
+ const cleanupNote = await cleanupReservedPackage();
5346
+ if (!cleanupNote) return Result.err(setupResult.error);
5347
+ return Result.err(new CLIError({
5348
+ message: `${setupResult.error.message}\n${cleanupNote}`,
5349
+ cause: setupResult.error
5350
+ }));
5351
+ }
5352
+ const stepFailures = getStepFailures();
5353
+ if (stepFailures.length > 0) {
5354
+ const cleanupNote = await cleanupReservedPackage();
5355
+ const message = isSilent() ? stepFailures.join("\n") : "Addon setup failed";
5356
+ return Result.err(new CLIError({ message: cleanupNote ? `${message}\n${cleanupNote}` : message }));
5357
+ }
5305
5358
  if (addonsToAdd.length > 0) {
5306
5359
  const configResult = await updateBtsConfig(projectDir, {
5307
5360
  addons: updatedAddons,
@@ -8785,7 +8838,7 @@ async function displayPostInstallInstructions(config) {
8785
8838
  let output = `${pc.cyan("1.")} ${cdCmd}\n`;
8786
8839
  let stepCounter = 2;
8787
8840
  if (!depsInstalled) output += `${pc.cyan(`${stepCounter++}.`)} ${packageManager} install\n`;
8788
- if (database === "sqlite" && dbSetup !== "d1") output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} db:local\n${pc.dim(" (optional - starts local SQLite database)")}\n`;
8841
+ if ((0, types_exports.usesTursoDevServer)(config)) output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} db:local\n${pc.dim(dbSetup === "turso" ? " (optional - starts a local Turso database)" : " (starts the local SQLite server)")}\n`;
8789
8842
  const hasAlchemyD1 = dbSetup === "d1" && (serverDeploy === "cloudflare" || isBackendSelf && webDeploy === "cloudflare");
8790
8843
  const hasWranglerLocalD1 = (0, types_exports.getLocalD1Owner)(config) === "wrangler";
8791
8844
  if (orm === "prisma" && !hasAlchemyD1) output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} db:generate\n`;
@@ -9188,7 +9241,8 @@ async function* runPostScaffoldSteps(options, cliInput, projectDir, isConvex) {
9188
9241
  projectDir,
9189
9242
  install,
9190
9243
  installError,
9191
- interrupted: wasAnyStepInterrupted()
9244
+ interrupted: wasAnyStepInterrupted(),
9245
+ stepFailures: getStepFailures()
9192
9246
  });
9193
9247
  }
9194
9248
  async function setPackageManagerVersion(projectDir, packageManager, version) {
@@ -9457,6 +9511,7 @@ async function createProjectHandlerInternal(input, startTime, timeScaffolded) {
9457
9511
  await trackProjectCreation(config, input.disableAnalytics, mode);
9458
9512
  const warnings = [];
9459
9513
  if (created.interrupted) warnings.push("One or more setup steps were cancelled.");
9514
+ warnings.push(...created.stepFailures);
9460
9515
  if (created.installError) {
9461
9516
  warnings.push(created.installError.message);
9462
9517
  await reportDiagnostic("cli_failed", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-better-t-stack",
3
- "version": "3.47.0",
3
+ "version": "3.49.0",
4
4
  "description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations",
5
5
  "keywords": [
6
6
  "better-auth",
@@ -64,17 +64,17 @@
64
64
  "dev": "tsdown --watch",
65
65
  "check-types": "tsc --noEmit",
66
66
  "check-test-types": "tsc --noEmit -p tsconfig.tests.json",
67
- "test": "bun test",
68
- "test:ci": "bun run build && BTS_SKIP_EXTERNAL_COMMANDS=1 BTS_TEST_MODE=1 bun test --bail=5",
69
- "test:complete": "cd ../.. && bun run build && cd apps/cli && BTS_MATRIX_MODE=smoke BTS_BUILD_SAMPLES=1 bun test --bail=5",
67
+ "test": "bun test ./test",
68
+ "test:ci": "bun run build && BTS_SKIP_EXTERNAL_COMMANDS=1 BTS_TEST_MODE=1 bun run test --bail=5",
69
+ "test:complete": "cd ../.. && bun run build && cd apps/cli && BTS_BUILD_SAMPLES=1 bun run test --bail=5",
70
70
  "prepublishOnly": "npm run build"
71
71
  },
72
72
  "dependencies": {
73
- "@better-t-stack/template-generator": "^3.47.0",
74
- "@better-t-stack/types": "^3.47.0",
73
+ "@better-t-stack/template-generator": "^3.49.0",
74
+ "@better-t-stack/types": "^3.49.0",
75
75
  "@clack/core": "^1.5.0",
76
76
  "@clack/prompts": "^1.8.0",
77
- "@modelcontextprotocol/server": "2.0.0",
77
+ "@modelcontextprotocol/server": "2.3.1",
78
78
  "@orpc/server": "^1.15.6",
79
79
  "better-result": "^3.0.1",
80
80
  "consola": "^3.4.2",
@@ -94,9 +94,9 @@
94
94
  "zod": "^4.5.4"
95
95
  },
96
96
  "devDependencies": {
97
- "@modelcontextprotocol/client": "2.0.0",
97
+ "@modelcontextprotocol/client": "2.3.1",
98
98
  "@playwright/test": "1.63.0",
99
- "@types/bun": "^1.4.2",
99
+ "@types/bun": "^1.4.3",
100
100
  "@types/fs-extra": "^11.0.4",
101
101
  "@types/node": "^26.5.0",
102
102
  "@types/semver": "^7.8.0",