create-better-t-stack 3.10.0 → 3.11.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/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { n as createBtsCli } from "./src-QkFdHtZE.mjs";
2
+ import { n as createBtsCli } from "./src-XVvJUQ_h.mjs";
3
3
 
4
4
  //#region src/cli.ts
5
5
  createBtsCli().run();
package/dist/index.d.mts CHANGED
@@ -64,6 +64,8 @@ declare const router: {
64
64
  fumadocs: "fumadocs";
65
65
  ultracite: "ultracite";
66
66
  oxlint: "oxlint";
67
+ opentui: "opentui";
68
+ wxt: "wxt";
67
69
  }>>>;
68
70
  examples: z.ZodOptional<z.ZodArray<z.ZodEnum<{
69
71
  none: "none";
@@ -165,7 +167,7 @@ declare const router: {
165
167
  backend: "none" | "hono" | "express" | "fastify" | "elysia" | "convex" | "self";
166
168
  runtime: "none" | "bun" | "node" | "workers";
167
169
  frontend: ("none" | "tanstack-router" | "react-router" | "tanstack-start" | "next" | "nuxt" | "native-bare" | "native-uniwind" | "native-unistyles" | "svelte" | "solid")[];
168
- addons: ("none" | "pwa" | "tauri" | "starlight" | "biome" | "husky" | "ruler" | "turborepo" | "fumadocs" | "ultracite" | "oxlint")[];
170
+ addons: ("none" | "pwa" | "tauri" | "starlight" | "biome" | "husky" | "ruler" | "turborepo" | "fumadocs" | "ultracite" | "oxlint" | "opentui" | "wxt")[];
169
171
  examples: ("none" | "todo" | "ai")[];
170
172
  auth: "none" | "better-auth" | "clerk";
171
173
  payments: "none" | "polar";
@@ -223,7 +225,7 @@ declare const router: {
223
225
  backend: "none" | "hono" | "express" | "fastify" | "elysia" | "convex" | "self";
224
226
  runtime: "none" | "bun" | "node" | "workers";
225
227
  frontend: ("none" | "tanstack-router" | "react-router" | "tanstack-start" | "next" | "nuxt" | "native-bare" | "native-uniwind" | "native-unistyles" | "svelte" | "solid")[];
226
- addons: ("none" | "pwa" | "tauri" | "starlight" | "biome" | "husky" | "ruler" | "turborepo" | "fumadocs" | "ultracite" | "oxlint")[];
228
+ addons: ("none" | "pwa" | "tauri" | "starlight" | "biome" | "husky" | "ruler" | "turborepo" | "fumadocs" | "ultracite" | "oxlint" | "opentui" | "wxt")[];
227
229
  examples: ("none" | "todo" | "ai")[];
228
230
  auth: "none" | "better-auth" | "clerk";
229
231
  payments: "none" | "polar";
@@ -255,6 +257,8 @@ declare const router: {
255
257
  fumadocs: "fumadocs";
256
258
  ultracite: "ultracite";
257
259
  oxlint: "oxlint";
260
+ opentui: "opentui";
261
+ wxt: "wxt";
258
262
  }>>>>;
259
263
  webDeploy: z.ZodOptional<z.ZodEnum<{
260
264
  none: "none";
@@ -321,7 +325,7 @@ declare function init(projectName?: string, options?: CreateInput): Promise<{
321
325
  backend: "none" | "hono" | "express" | "fastify" | "elysia" | "convex" | "self";
322
326
  runtime: "none" | "bun" | "node" | "workers";
323
327
  frontend: ("none" | "tanstack-router" | "react-router" | "tanstack-start" | "next" | "nuxt" | "native-bare" | "native-uniwind" | "native-unistyles" | "svelte" | "solid")[];
324
- addons: ("none" | "pwa" | "tauri" | "starlight" | "biome" | "husky" | "ruler" | "turborepo" | "fumadocs" | "ultracite" | "oxlint")[];
328
+ addons: ("none" | "pwa" | "tauri" | "starlight" | "biome" | "husky" | "ruler" | "turborepo" | "fumadocs" | "ultracite" | "oxlint" | "opentui" | "wxt")[];
325
329
  examples: ("none" | "todo" | "ai")[];
326
330
  auth: "none" | "better-auth" | "clerk";
327
331
  payments: "none" | "polar";
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { a as router, i as init, n as createBtsCli, o as sponsors, r as docs, t as builder } from "./src-QkFdHtZE.mjs";
2
+ import { a as router, i as init, n as createBtsCli, o as sponsors, r as docs, t as builder } from "./src-XVvJUQ_h.mjs";
3
3
 
4
4
  export { builder, createBtsCli, docs, init, router, sponsors };
@@ -117,7 +117,7 @@ const dependencyVersionMap = {
117
117
  "@fastify/cors": "^11.0.1",
118
118
  turbo: "^2.6.3",
119
119
  ai: "^5.0.49",
120
- "@ai-sdk/google": "^2.0.13",
120
+ "@ai-sdk/google": "^2.0.51",
121
121
  "@ai-sdk/vue": "^2.0.49",
122
122
  "@ai-sdk/svelte": "^3.0.39",
123
123
  "@ai-sdk/react": "^2.0.39",
@@ -132,12 +132,13 @@ const dependencyVersionMap = {
132
132
  "@trpc/server": "^11.7.2",
133
133
  "@trpc/client": "^11.7.2",
134
134
  next: "^16.0.10",
135
- convex: "^1.29.3",
135
+ convex: "^1.31.2",
136
136
  "@convex-dev/react-query": "^0.1.0",
137
+ "@convex-dev/agent": "^0.3.2",
137
138
  "convex-svelte": "^0.0.12",
138
139
  "convex-nuxt": "0.1.5",
139
140
  "convex-vue": "^0.1.5",
140
- "@convex-dev/better-auth": "^0.10.4",
141
+ "@convex-dev/better-auth": "^0.10.6",
141
142
  "@tanstack/svelte-query": "^5.85.3",
142
143
  "@tanstack/svelte-query-devtools": "^5.85.3",
143
144
  "@tanstack/vue-query-devtools": "^5.90.2",
@@ -184,6 +185,8 @@ const ADDON_COMPATIBILITY = {
184
185
  ruler: [],
185
186
  oxlint: [],
186
187
  fumadocs: [],
188
+ opentui: [],
189
+ wxt: [],
187
190
  none: []
188
191
  };
189
192
 
@@ -289,8 +292,12 @@ function isExampleTodoAllowed(backend, database) {
289
292
  return !(backend !== "convex" && backend !== "none" && database === "none");
290
293
  }
291
294
  function isExampleAIAllowed(backend, frontends = []) {
292
- if (backend === "convex") return false;
293
295
  if (frontends.includes("solid")) return false;
296
+ if (backend === "convex") {
297
+ const includesNuxt = frontends.includes("nuxt");
298
+ const includesSvelte = frontends.includes("svelte");
299
+ if (includesNuxt || includesSvelte) return false;
300
+ }
294
301
  return true;
295
302
  }
296
303
  function validateWebDeployRequiresWebFrontend(webDeploy, hasWebFrontendFlag) {
@@ -336,8 +343,13 @@ function validateExamplesCompatibility(examples, backend, database, frontend) {
336
343
  const examplesArr = examples ?? [];
337
344
  if (examplesArr.length === 0 || examplesArr.includes("none")) return;
338
345
  if (examplesArr.includes("todo") && backend !== "convex" && backend !== "none" && database === "none") exitWithError("The 'todo' example requires a database if a backend (other than Convex) is present. Cannot use --examples todo when database is 'none' and a backend is selected.");
339
- if (examplesArr.includes("ai") && backend === "convex") exitWithError("The 'ai' example is not yet available with Convex backend.");
340
346
  if (examplesArr.includes("ai") && (frontend ?? []).includes("solid")) exitWithError("The 'ai' example is not compatible with the Solid frontend.");
347
+ if (examplesArr.includes("ai") && backend === "convex") {
348
+ const frontendArr = frontend ?? [];
349
+ const includesNuxt = frontendArr.includes("nuxt");
350
+ const includesSvelte = frontendArr.includes("svelte");
351
+ if (includesNuxt || includesSvelte) exitWithError("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.");
352
+ }
341
353
  }
342
354
 
343
355
  //#endregion
@@ -386,6 +398,14 @@ function getAddonDisplay(addon) {
386
398
  label = "Fumadocs";
387
399
  hint = "Build excellent documentation site";
388
400
  break;
401
+ case "opentui":
402
+ label = "OpenTUI";
403
+ hint = "Build terminal user interfaces";
404
+ break;
405
+ case "wxt":
406
+ label = "WXT";
407
+ hint = "Build browser extensions";
408
+ break;
389
409
  default:
390
410
  label = addon;
391
411
  hint = `Add ${addon}`;
@@ -404,10 +424,12 @@ const ADDON_GROUPS = {
404
424
  ],
405
425
  Other: [
406
426
  "ruler",
407
- "turborepo",
408
427
  "pwa",
409
428
  "tauri",
410
- "husky"
429
+ "husky",
430
+ "opentui",
431
+ "wxt",
432
+ "turborepo"
411
433
  ]
412
434
  };
413
435
  async function getAddonsChoice(addons, frontends, auth) {
@@ -434,6 +456,12 @@ async function getAddonsChoice(addons, frontends, auth) {
434
456
  }
435
457
  Object.keys(groupedOptions).forEach((group$1) => {
436
458
  if (groupedOptions[group$1].length === 0) delete groupedOptions[group$1];
459
+ else {
460
+ const groupOrder = ADDON_GROUPS[group$1] || [];
461
+ groupedOptions[group$1].sort((a, b) => {
462
+ return groupOrder.indexOf(a.value) - groupOrder.indexOf(b.value);
463
+ });
464
+ }
437
465
  });
438
466
  const response = await groupMultiselect({
439
467
  message: "Select addons",
@@ -466,6 +494,12 @@ async function getAddonsToAdd(frontend, existingAddons = [], auth) {
466
494
  }
467
495
  Object.keys(groupedOptions).forEach((group$1) => {
468
496
  if (groupedOptions[group$1].length === 0) delete groupedOptions[group$1];
497
+ else {
498
+ const groupOrder = ADDON_GROUPS[group$1] || [];
499
+ groupedOptions[group$1].sort((a, b) => {
500
+ return groupOrder.indexOf(a.value) - groupOrder.indexOf(b.value);
501
+ });
502
+ }
469
503
  });
470
504
  if (Object.keys(groupedOptions).length === 0) return [];
471
505
  const response = await groupMultiselect({
@@ -1966,7 +2000,7 @@ function getPackageExecutionCommand(packageManager, commandWithArgs) {
1966
2000
 
1967
2001
  //#endregion
1968
2002
  //#region src/helpers/addons/fumadocs-setup.ts
1969
- const TEMPLATES = {
2003
+ const TEMPLATES$2 = {
1970
2004
  "next-mdx": {
1971
2005
  label: "Next.js: Fumadocs MDX",
1972
2006
  hint: "Recommended template with MDX support",
@@ -1999,7 +2033,7 @@ async function setupFumadocs(config) {
1999
2033
  log.info("Setting up Fumadocs...");
2000
2034
  const template = await select({
2001
2035
  message: "Choose a template",
2002
- options: Object.entries(TEMPLATES).map(([key, template$1]) => ({
2036
+ options: Object.entries(TEMPLATES$2).map(([key, template$1]) => ({
2003
2037
  value: key,
2004
2038
  label: template$1.label,
2005
2039
  hint: template$1.hint
@@ -2007,7 +2041,7 @@ async function setupFumadocs(config) {
2007
2041
  initialValue: "next-mdx"
2008
2042
  });
2009
2043
  if (isCancel(template)) return exitCancelled("Operation cancelled");
2010
- const fumadocsInitCommand = getPackageExecutionCommand(packageManager, `create-fumadocs-app@latest fumadocs --template ${TEMPLATES[template].value} --src --pm ${packageManager} --no-git`);
2044
+ const fumadocsInitCommand = getPackageExecutionCommand(packageManager, `create-fumadocs-app@latest fumadocs --template ${TEMPLATES$2[template].value} --src --pm ${packageManager} --no-git`);
2011
2045
  const appsDir = path.join(projectDir, "apps");
2012
2046
  await fs.ensureDir(appsDir);
2013
2047
  const s = spinner();
@@ -2235,6 +2269,53 @@ async function setupTauri(config) {
2235
2269
  }
2236
2270
  }
2237
2271
 
2272
+ //#endregion
2273
+ //#region src/helpers/addons/tui-setup.ts
2274
+ const TEMPLATES$1 = {
2275
+ core: {
2276
+ label: "Core",
2277
+ hint: "Basic OpenTUI template"
2278
+ },
2279
+ react: {
2280
+ label: "React",
2281
+ hint: "React-based OpenTUI template"
2282
+ },
2283
+ solid: {
2284
+ label: "Solid",
2285
+ hint: "SolidJS-based OpenTUI template"
2286
+ }
2287
+ };
2288
+ async function setupTui(config) {
2289
+ const { packageManager, projectDir } = config;
2290
+ try {
2291
+ log.info("Setting up OpenTUI...");
2292
+ const template = await select({
2293
+ message: "Choose a template",
2294
+ options: Object.entries(TEMPLATES$1).map(([key, template$1]) => ({
2295
+ value: key,
2296
+ label: template$1.label,
2297
+ hint: template$1.hint
2298
+ })),
2299
+ initialValue: "core"
2300
+ });
2301
+ if (isCancel(template)) return exitCancelled("Operation cancelled");
2302
+ const tuiInitCommand = getPackageExecutionCommand(packageManager, `create-tui@latest --template ${template} --no-git --no-install tui`);
2303
+ const appsDir = path.join(projectDir, "apps");
2304
+ await fs.ensureDir(appsDir);
2305
+ const s = spinner();
2306
+ s.start("Running OpenTUI create command...");
2307
+ await execa(tuiInitCommand, {
2308
+ cwd: appsDir,
2309
+ env: { CI: "true" },
2310
+ shell: true
2311
+ });
2312
+ s.stop("OpenTUI setup complete!");
2313
+ } catch (error) {
2314
+ log.error(pc.red("Failed to set up OpenTUI"));
2315
+ if (error instanceof Error) console.error(pc.red(error.message));
2316
+ }
2317
+ }
2318
+
2238
2319
  //#endregion
2239
2320
  //#region src/helpers/addons/ultracite-setup.ts
2240
2321
  const EDITORS = {
@@ -2347,9 +2428,72 @@ async function setupUltracite(config, hasHusky) {
2347
2428
  }
2348
2429
  }
2349
2430
 
2431
+ //#endregion
2432
+ //#region src/helpers/addons/wxt-setup.ts
2433
+ const TEMPLATES = {
2434
+ vanilla: {
2435
+ label: "Vanilla",
2436
+ hint: "Vanilla JavaScript template"
2437
+ },
2438
+ vue: {
2439
+ label: "Vue",
2440
+ hint: "Vue.js template"
2441
+ },
2442
+ react: {
2443
+ label: "React",
2444
+ hint: "React template"
2445
+ },
2446
+ solid: {
2447
+ label: "Solid",
2448
+ hint: "SolidJS template"
2449
+ },
2450
+ svelte: {
2451
+ label: "Svelte",
2452
+ hint: "Svelte template"
2453
+ }
2454
+ };
2455
+ async function setupWxt(config) {
2456
+ const { packageManager, projectDir } = config;
2457
+ try {
2458
+ log.info("Setting up WXT...");
2459
+ const template = await select({
2460
+ message: "Choose a template",
2461
+ options: Object.entries(TEMPLATES).map(([key, template$1]) => ({
2462
+ value: key,
2463
+ label: template$1.label,
2464
+ hint: template$1.hint
2465
+ })),
2466
+ initialValue: "react"
2467
+ });
2468
+ if (isCancel(template)) return exitCancelled("Operation cancelled");
2469
+ const wxtInitCommand = getPackageExecutionCommand(packageManager, `wxt@latest init extension --template ${template} --pm ${packageManager}`);
2470
+ const appsDir = path.join(projectDir, "apps");
2471
+ await fs.ensureDir(appsDir);
2472
+ const s = spinner();
2473
+ s.start("Running WXT init command...");
2474
+ await execa(wxtInitCommand, {
2475
+ cwd: appsDir,
2476
+ env: { CI: "true" },
2477
+ shell: true
2478
+ });
2479
+ const extensionDir = path.join(projectDir, "apps", "extension");
2480
+ const packageJsonPath = path.join(extensionDir, "package.json");
2481
+ if (await fs.pathExists(packageJsonPath)) {
2482
+ const packageJson = await fs.readJson(packageJsonPath);
2483
+ packageJson.name = "extension";
2484
+ if (packageJson.scripts?.dev) packageJson.scripts.dev = `${packageJson.scripts.dev} --port 5555`;
2485
+ await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
2486
+ }
2487
+ s.stop("WXT setup complete!");
2488
+ } catch (error) {
2489
+ log.error(pc.red("Failed to set up WXT"));
2490
+ if (error instanceof Error) console.error(pc.red(error.message));
2491
+ }
2492
+ }
2493
+
2350
2494
  //#endregion
2351
2495
  //#region src/utils/ts-morph.ts
2352
- const tsProject = new Project({
2496
+ const tsProject$1 = new Project({
2353
2497
  useInMemoryFileSystem: false,
2354
2498
  skipAddingFilesFromTsConfig: true,
2355
2499
  manipulationSettings: {
@@ -2367,7 +2511,7 @@ function ensureArrayProperty(obj, name) {
2367
2511
  //#endregion
2368
2512
  //#region src/helpers/addons/vite-pwa-setup.ts
2369
2513
  async function addPwaToViteConfig(viteConfigPath, projectName) {
2370
- const sourceFile = tsProject.addSourceFileAtPathIfExists(viteConfigPath);
2514
+ const sourceFile = tsProject$1.addSourceFileAtPathIfExists(viteConfigPath);
2371
2515
  if (!sourceFile) throw new Error("vite config not found");
2372
2516
  if (!sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === "vite-plugin-pwa")) sourceFile.insertImportDeclaration(0, {
2373
2517
  namedImports: ["VitePWA"],
@@ -2392,7 +2536,7 @@ async function addPwaToViteConfig(viteConfigPath, projectName) {
2392
2536
  pwaAssets: { disabled: false, config: true },
2393
2537
  devOptions: { enabled: true },
2394
2538
  })`);
2395
- await tsProject.save();
2539
+ await tsProject$1.save();
2396
2540
  }
2397
2541
 
2398
2542
  //#endregion
@@ -2437,6 +2581,8 @@ ${pc.cyan("Docs:")} ${pc.underline("https://turborepo.com/docs")}
2437
2581
  if (addons.includes("starlight")) await setupStarlight(config);
2438
2582
  if (addons.includes("ruler")) await setupRuler(config);
2439
2583
  if (addons.includes("fumadocs")) await setupFumadocs(config);
2584
+ if (addons.includes("opentui")) await setupTui(config);
2585
+ if (addons.includes("wxt")) await setupWxt(config);
2440
2586
  }
2441
2587
  function getWebAppDir(projectDir, frontends) {
2442
2588
  if (frontends.some((f) => [
@@ -2623,7 +2769,7 @@ handlebars.registerHelper("includes", (array, value) => Array.isArray(array) &&
2623
2769
 
2624
2770
  //#endregion
2625
2771
  //#region src/helpers/deployment/alchemy/env-dts-setup.ts
2626
- const tsProject$1 = new Project({
2772
+ const tsProject = new Project({
2627
2773
  useInMemoryFileSystem: false,
2628
2774
  skipAddingFilesFromTsConfig: true
2629
2775
  });
@@ -2642,7 +2788,7 @@ function determineImportPath(envDtsPath, projectDir, config) {
2642
2788
  async function setupEnvDtsImport(envDtsPath, projectDir, config) {
2643
2789
  if (!await fs.pathExists(envDtsPath)) return;
2644
2790
  const importPath = determineImportPath(envDtsPath, projectDir, config);
2645
- const sourceFile = tsProject$1.addSourceFileAtPath(envDtsPath);
2791
+ const sourceFile = tsProject.addSourceFileAtPath(envDtsPath);
2646
2792
  if (!sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === importPath && imp.getNamedImports().some((named) => named.getName() === "server"))) sourceFile.insertImportDeclaration(0, {
2647
2793
  moduleSpecifier: importPath,
2648
2794
  namedImports: [{
@@ -3071,10 +3217,6 @@ async function setupExamplesTemplate(projectDir, context) {
3071
3217
  if (hasReactWeb) {
3072
3218
  const exampleWebSrc = path.join(exampleBaseDir, "web/react");
3073
3219
  if (await fs.pathExists(exampleWebSrc)) {
3074
- if (example === "ai") {
3075
- const exampleWebBaseSrc = path.join(exampleWebSrc, "base");
3076
- if (await fs.pathExists(exampleWebBaseSrc)) await processAndCopyFiles("**/*", exampleWebBaseSrc, webAppDir, context, false);
3077
- }
3078
3220
  const reactFramework = context.frontend.find((f) => [
3079
3221
  "next",
3080
3222
  "react-router",
@@ -3836,62 +3978,84 @@ async function updatePackageJsonsWithCatalogs(packagesInfo, catalog) {
3836
3978
  //#endregion
3837
3979
  //#region src/helpers/addons/examples-setup.ts
3838
3980
  async function setupExamples(config) {
3839
- const { examples, frontend, backend, projectDir, orm, database } = config;
3840
- if (backend === "convex" || !examples || examples.length === 0 || examples[0] === "none") return;
3981
+ const { examples, backend } = config;
3982
+ if (!examples || examples.length === 0 || examples[0] === "none") return;
3983
+ if (examples.includes("todo") && backend !== "convex" && backend !== "none") await setupTodoDependencies(config);
3984
+ if (examples.includes("ai")) await setupAIDependencies(config);
3985
+ }
3986
+ async function setupTodoDependencies(config) {
3987
+ const { projectDir, orm, database, backend } = config;
3841
3988
  const apiDir = path.join(projectDir, "packages/api");
3842
- if (await fs.pathExists(apiDir) && backend !== "none") {
3843
- if (orm === "drizzle") {
3844
- const dependencies = ["drizzle-orm"];
3845
- if (database === "postgres") dependencies.push("@types/pg");
3846
- await addPackageDependency({
3847
- dependencies,
3848
- projectDir: apiDir
3849
- });
3850
- } else if (orm === "prisma") await addPackageDependency({
3851
- dependencies: ["@prisma/client"],
3852
- projectDir: apiDir
3853
- });
3854
- else if (orm === "mongoose") await addPackageDependency({
3855
- dependencies: ["mongoose"],
3989
+ if (!await fs.pathExists(apiDir) || backend === "none") return;
3990
+ if (orm === "drizzle") {
3991
+ const dependencies = ["drizzle-orm"];
3992
+ if (database === "postgres") dependencies.push("@types/pg");
3993
+ await addPackageDependency({
3994
+ dependencies,
3856
3995
  projectDir: apiDir
3857
3996
  });
3858
- }
3859
- if (examples.includes("ai")) {
3860
- const webClientDir = path.join(projectDir, "apps/web");
3861
- const nativeClientDir = path.join(projectDir, "apps/native");
3862
- const apiDir$1 = path.join(projectDir, "packages/api");
3863
- const webClientDirExists = await fs.pathExists(webClientDir);
3864
- const nativeClientDirExists = await fs.pathExists(nativeClientDir);
3865
- const apiDirExists = await fs.pathExists(apiDir$1);
3866
- const hasNuxt = frontend.includes("nuxt");
3867
- const hasSvelte = frontend.includes("svelte");
3868
- const hasReactWeb = frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("next") || frontend.includes("tanstack-start");
3869
- const hasNext = frontend.includes("next");
3870
- const hasReactNative = frontend.includes("native-bare") || frontend.includes("native-uniwind") || frontend.includes("native-unistyles");
3871
- if (webClientDirExists) {
3872
- const dependencies = ["ai"];
3997
+ } else if (orm === "prisma") await addPackageDependency({
3998
+ dependencies: ["@prisma/client"],
3999
+ projectDir: apiDir
4000
+ });
4001
+ else if (orm === "mongoose") await addPackageDependency({
4002
+ dependencies: ["mongoose"],
4003
+ projectDir: apiDir
4004
+ });
4005
+ }
4006
+ async function setupAIDependencies(config) {
4007
+ const { frontend, backend, projectDir } = config;
4008
+ const webClientDir = path.join(projectDir, "apps/web");
4009
+ const nativeClientDir = path.join(projectDir, "apps/native");
4010
+ const serverDir = path.join(projectDir, "apps/server");
4011
+ const convexBackendDir = path.join(projectDir, "packages/backend");
4012
+ const webClientDirExists = await fs.pathExists(webClientDir);
4013
+ const nativeClientDirExists = await fs.pathExists(nativeClientDir);
4014
+ const serverDirExists = await fs.pathExists(serverDir);
4015
+ const convexBackendDirExists = await fs.pathExists(convexBackendDir);
4016
+ const hasReactWeb = frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("next") || frontend.includes("tanstack-start");
4017
+ const hasNuxt = frontend.includes("nuxt");
4018
+ const hasSvelte = frontend.includes("svelte");
4019
+ const hasReactNative = frontend.includes("native-bare") || frontend.includes("native-uniwind") || frontend.includes("native-unistyles");
4020
+ if (backend === "convex" && convexBackendDirExists) await addPackageDependency({
4021
+ dependencies: [
4022
+ "@convex-dev/agent",
4023
+ "ai",
4024
+ "@ai-sdk/google"
4025
+ ],
4026
+ projectDir: convexBackendDir
4027
+ });
4028
+ else if (backend === "self" && webClientDirExists) await addPackageDependency({
4029
+ dependencies: ["ai", "@ai-sdk/google"],
4030
+ projectDir: webClientDir
4031
+ });
4032
+ else if (serverDirExists && backend !== "none") await addPackageDependency({
4033
+ dependencies: ["ai", "@ai-sdk/google"],
4034
+ projectDir: serverDir
4035
+ });
4036
+ if (webClientDirExists) {
4037
+ const dependencies = [];
4038
+ if (backend === "convex") {
4039
+ if (hasReactWeb) dependencies.push("@convex-dev/agent", "streamdown");
4040
+ } else {
4041
+ dependencies.push("ai");
3873
4042
  if (hasNuxt) dependencies.push("@ai-sdk/vue");
3874
4043
  else if (hasSvelte) dependencies.push("@ai-sdk/svelte");
3875
4044
  else if (hasReactWeb) dependencies.push("@ai-sdk/react", "streamdown");
3876
- if (hasNext) dependencies.push("shiki");
3877
- await addPackageDependency({
3878
- dependencies,
3879
- projectDir: webClientDir
3880
- });
3881
4045
  }
3882
- if (nativeClientDirExists && hasReactNative) await addPackageDependency({
3883
- dependencies: ["ai", "@ai-sdk/react"],
3884
- projectDir: nativeClientDir
3885
- });
3886
- if (apiDirExists && backend !== "none") await addPackageDependency({
3887
- dependencies: ["ai", "@ai-sdk/google"],
3888
- projectDir: apiDir$1
3889
- });
3890
- if (backend === "self" && webClientDirExists) await addPackageDependency({
3891
- dependencies: ["ai", "@ai-sdk/google"],
4046
+ if (dependencies.length > 0) await addPackageDependency({
4047
+ dependencies,
3892
4048
  projectDir: webClientDir
3893
4049
  });
3894
4050
  }
4051
+ if (nativeClientDirExists && hasReactNative) if (backend === "convex") await addPackageDependency({
4052
+ dependencies: ["@convex-dev/agent"],
4053
+ projectDir: nativeClientDir
4054
+ });
4055
+ else await addPackageDependency({
4056
+ dependencies: ["ai", "@ai-sdk/react"],
4057
+ projectDir: nativeClientDir
4058
+ });
3895
4059
  }
3896
4060
 
3897
4061
  //#endregion
@@ -4089,7 +4253,7 @@ async function setupApi(config) {
4089
4253
  //#endregion
4090
4254
  //#region src/helpers/core/backend-setup.ts
4091
4255
  async function setupBackendDependencies(config) {
4092
- const { backend, runtime, api, auth, examples, projectDir } = config;
4256
+ const { backend, runtime, api, auth, projectDir } = config;
4093
4257
  if (backend === "convex") {
4094
4258
  await addPackageDependency({
4095
4259
  dependencies: ["convex"],
@@ -4117,7 +4281,6 @@ async function setupBackendDependencies(config) {
4117
4281
  else if (framework === "elysia") dependencies.push("@elysiajs/trpc");
4118
4282
  } else if (api === "orpc") dependencies.push("@orpc/server", "@orpc/openapi", "@orpc/zod");
4119
4283
  if (auth === "better-auth") dependencies.push("better-auth");
4120
- if (examples.includes("ai")) dependencies.push("ai", "@ai-sdk/google");
4121
4284
  if (runtime === "node") devDependencies.push("tsx", "@types/node");
4122
4285
  else if (runtime === "bun") devDependencies.push("@types/bun");
4123
4286
  if (dependencies.length > 0 || devDependencies.length > 0) await addPackageDependency({
@@ -4131,7 +4294,7 @@ async function setupBackendDependencies(config) {
4131
4294
  //#region src/utils/better-auth-plugin-setup.ts
4132
4295
  async function setupBetterAuthPlugins(projectDir, config) {
4133
4296
  const authIndexPath = `${projectDir}/packages/auth/src/index.ts`;
4134
- const authIndexFile = tsProject.addSourceFileAtPath(authIndexPath);
4297
+ const authIndexFile = tsProject$1.addSourceFileAtPath(authIndexPath);
4135
4298
  if (!authIndexFile) return;
4136
4299
  const pluginsToAdd = [];
4137
4300
  const importsToAdd = [];
@@ -4479,21 +4642,32 @@ async function setupEnvironmentVariables(config) {
4479
4642
  }
4480
4643
  }
4481
4644
  if (backend === "convex") {
4482
- if (auth === "better-auth") {
4483
- const convexBackendDir = path.join(projectDir, "packages/backend");
4484
- if (await fs.pathExists(convexBackendDir)) {
4485
- const envLocalPath = path.join(convexBackendDir, ".env.local");
4645
+ const convexBackendDir = path.join(projectDir, "packages/backend");
4646
+ if (await fs.pathExists(convexBackendDir)) {
4647
+ const envLocalPath = path.join(convexBackendDir, ".env.local");
4648
+ let commentBlocks = "";
4649
+ if (examples?.includes("ai")) commentBlocks += `# Set Google AI API key for AI agent
4650
+ # npx convex env set GOOGLE_GENERATIVE_AI_API_KEY=your_google_api_key
4651
+
4652
+ `;
4653
+ if (auth === "better-auth") commentBlocks += `# Set Convex environment variables
4654
+ # npx convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)
4655
+ ${hasWebFrontend$1 ? "# npx convex env set SITE_URL http://localhost:3001\n" : ""}`;
4656
+ if (commentBlocks) {
4657
+ let existingContent = "";
4658
+ if (await fs.pathExists(envLocalPath)) existingContent = await fs.readFile(envLocalPath, "utf8");
4659
+ await fs.writeFile(envLocalPath, commentBlocks + existingContent);
4660
+ }
4661
+ const convexBackendVars = [];
4662
+ if (examples?.includes("ai")) convexBackendVars.push({
4663
+ key: "GOOGLE_GENERATIVE_AI_API_KEY",
4664
+ value: "",
4665
+ condition: true,
4666
+ comment: "Google AI API key for AI agent"
4667
+ });
4668
+ if (auth === "better-auth") {
4486
4669
  const hasNative = frontend.includes("native-bare") || frontend.includes("native-uniwind") || frontend.includes("native-unistyles");
4487
4670
  const hasWeb = hasWebFrontend$1;
4488
- if (!await fs.pathExists(envLocalPath) || !(await fs.readFile(envLocalPath, "utf8")).includes("npx convex env set")) {
4489
- let siteUrlComments = "";
4490
- if (hasWeb) siteUrlComments += "# npx convex env set SITE_URL http://localhost:3001\n";
4491
- const convexCommands = `# Set Convex environment variables
4492
- # npx convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)
4493
- ${siteUrlComments}`;
4494
- await fs.appendFile(envLocalPath, convexCommands);
4495
- }
4496
- const convexBackendVars = [];
4497
4671
  if (hasNative) convexBackendVars.push({
4498
4672
  key: "EXPO_PUBLIC_CONVEX_SITE_URL",
4499
4673
  value: "",
@@ -4511,8 +4685,8 @@ ${siteUrlComments}`;
4511
4685
  condition: true,
4512
4686
  comment: "Web app URL for authentication"
4513
4687
  });
4514
- await addEnvVariablesToFile(envLocalPath, convexBackendVars);
4515
4688
  }
4689
+ if (convexBackendVars.length > 0) await addEnvVariablesToFile(envLocalPath, convexBackendVars);
4516
4690
  }
4517
4691
  return;
4518
4692
  }
@@ -6597,8 +6771,8 @@ async function createProject(options, cliInput) {
6597
6771
  if (!isConvex) {
6598
6772
  if (needsServerSetup) await setupRuntime(options);
6599
6773
  await setupDatabase(options, cliInput);
6600
- if (options.examples.length > 0 && options.examples[0] !== "none") await setupExamples(options);
6601
6774
  }
6775
+ if (options.examples.length > 0 && options.examples[0] !== "none") await setupExamples(options);
6602
6776
  if (options.addons.length > 0 && options.addons[0] !== "none") await setupAddons(options);
6603
6777
  if (options.auth && options.auth !== "none") await setupAuth(options);
6604
6778
  if (options.payments && options.payments !== "none") await setupPayments(options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-better-t-stack",
3
- "version": "3.10.0",
3
+ "version": "3.11.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",
@@ -67,12 +67,12 @@
67
67
  "prepublishOnly": "npm run build"
68
68
  },
69
69
  "dependencies": {
70
- "@better-t-stack/types": "^3.10.0",
70
+ "@better-t-stack/types": "^3.11.0",
71
71
  "@clack/prompts": "^1.0.0-alpha.8",
72
- "@orpc/server": "^1.12.2",
72
+ "@orpc/server": "^1.13.0",
73
73
  "consola": "^3.4.2",
74
74
  "execa": "^9.6.1",
75
- "fs-extra": "^11.3.2",
75
+ "fs-extra": "^11.3.3",
76
76
  "gradient-string": "^3.0.0",
77
77
  "handlebars": "^4.7.8",
78
78
  "jsonc-parser": "^3.3.1",
@@ -82,14 +82,14 @@
82
82
  "trpc-cli": "^0.12.1",
83
83
  "ts-morph": "^27.0.2",
84
84
  "yaml": "^2.8.2",
85
- "zod": "^4.1.13"
85
+ "zod": "^4.2.1"
86
86
  },
87
87
  "devDependencies": {
88
- "@types/bun": "^1.2.17",
88
+ "@types/bun": "^1.3.5",
89
89
  "@types/fs-extra": "^11.0.4",
90
- "@types/node": "^24.10.2",
90
+ "@types/node": "^25.0.3",
91
91
  "publint": "^0.3.16",
92
- "tsdown": "^0.17.2",
92
+ "tsdown": "^0.18.2",
93
93
  "typescript": "^5.9.3"
94
94
  }
95
95
  }
@@ -64,7 +64,7 @@ export { createAuth };
64
64
  export const getCurrentUser = query({
65
65
  args: {},
66
66
  returns: v.any(),
67
- handler: async function (ctx, args) {
67
+ handler: async function (ctx) {
68
68
  return authComponent.getAuthUser(ctx);
69
69
  },
70
70
  });