create-better-t-stack 3.12.5 → 3.12.7-pr780.f6206a0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/cli.mjs +1 -1
  2. package/dist/index.d.mts +13 -53
  3. package/dist/index.mjs +1 -1
  4. package/dist/{src-D3EHRs6z.mjs → src-CvDgQFF4.mjs} +711 -304
  5. package/package.json +3 -2
  6. package/templates/api/orpc/web/react/base/src/utils/orpc.ts.hbs +3 -3
  7. package/templates/auth/better-auth/convex/native/uniwind/components/sign-in.tsx.hbs +32 -50
  8. package/templates/auth/better-auth/convex/native/uniwind/components/sign-up.tsx.hbs +40 -57
  9. package/templates/auth/better-auth/native/uniwind/components/sign-in.tsx.hbs +32 -35
  10. package/templates/auth/better-auth/native/uniwind/components/sign-up.tsx.hbs +49 -37
  11. package/templates/auth/better-auth/web/nuxt/app/components/SignInForm.vue.hbs +40 -35
  12. package/templates/auth/better-auth/web/nuxt/app/components/SignUpForm.vue.hbs +47 -40
  13. package/templates/auth/better-auth/web/nuxt/app/middleware/auth.ts.hbs +9 -7
  14. package/templates/auth/better-auth/web/nuxt/app/pages/dashboard.vue.hbs +61 -29
  15. package/templates/auth/better-auth/web/nuxt/app/pages/login.vue.hbs +6 -3
  16. package/templates/backend/server/elysia/src/index.ts.hbs +8 -3
  17. package/templates/backend/server/express/src/index.ts.hbs +8 -3
  18. package/templates/backend/server/fastify/src/index.ts.hbs +8 -3
  19. package/templates/backend/server/hono/src/index.ts.hbs +16 -6
  20. package/templates/examples/ai/fullstack/next/src/app/api/ai/route.ts.hbs +8 -3
  21. package/templates/examples/ai/fullstack/tanstack-start/src/routes/api/ai/$.ts.hbs +8 -3
  22. package/templates/examples/ai/native/bare/polyfills.js +14 -11
  23. package/templates/examples/ai/native/uniwind/app/(drawer)/ai.tsx.hbs +104 -124
  24. package/templates/examples/ai/web/nuxt/app/pages/ai.vue.hbs +22 -22
  25. package/templates/examples/todo/native/uniwind/app/(drawer)/todos.tsx.hbs +67 -80
  26. package/templates/examples/todo/web/nuxt/app/pages/todos.vue.hbs +115 -90
  27. package/templates/frontend/native/uniwind/app/(drawer)/index.tsx.hbs +60 -54
  28. package/templates/frontend/native/uniwind/app/+not-found.tsx.hbs +16 -21
  29. package/templates/frontend/native/uniwind/app/modal.tsx.hbs +18 -34
  30. package/templates/frontend/native/uniwind/package.json.hbs +10 -10
  31. package/templates/frontend/nuxt/app/components/Header.vue.hbs +25 -29
  32. package/templates/frontend/nuxt/app/layouts/default.vue.hbs +7 -8
  33. package/templates/frontend/nuxt/app/pages/index.vue.hbs +58 -39
  34. package/templates/frontend/nuxt/package.json.hbs +1 -1
  35. package/templates/frontend/react/tanstack-router/package.json.hbs +1 -1
  36. package/templates/frontend/react/web-base/src/components/ui/skeleton.tsx.hbs +5 -5
  37. package/templates/frontend/nuxt/app/components/Loader.vue.hbs +0 -5
  38. package/templates/frontend/nuxt/app/components/ModeToggle.vue.hbs +0 -25
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { t as __reExport } from "./chunk-Dt3mZKp0.mjs";
3
- import { autocompleteMultiselect, cancel, confirm, group, groupMultiselect, intro, isCancel, log, multiselect, outro, select, spinner, text } from "@clack/prompts";
3
+ import { autocompleteMultiselect, cancel, confirm, group, intro, isCancel, log, multiselect, outro, select, spinner, text } from "@clack/prompts";
4
4
  import { createRouterClient, os } from "@orpc/server";
5
5
  import pc from "picocolors";
6
6
  import { createCli } from "trpc-cli";
@@ -9,6 +9,8 @@ import consola, { consola as consola$1 } from "consola";
9
9
  import fs from "fs-extra";
10
10
  import path from "node:path";
11
11
  import { fileURLToPath } from "node:url";
12
+ import { ConfirmPrompt, GroupMultiSelectPrompt, MultiSelectPrompt, SelectPrompt, isCancel as isCancel$1 } from "@clack/core";
13
+ import { AsyncLocalStorage } from "node:async_hooks";
12
14
  import gradient from "gradient-string";
13
15
  import * as JSONC from "jsonc-parser";
14
16
  import { $, execa } from "execa";
@@ -100,7 +102,7 @@ const dependencyVersionMap = {
100
102
  husky: "^9.1.7",
101
103
  "lint-staged": "^16.1.2",
102
104
  tsx: "^4.19.2",
103
- "@types/node": "^22.13.11",
105
+ "@types/node": "^22.13.14",
104
106
  "@types/bun": "^1.3.4",
105
107
  "@elysiajs/node": "^1.3.1",
106
108
  "@elysiajs/cors": "^1.3.3",
@@ -116,13 +118,14 @@ const dependencyVersionMap = {
116
118
  fastify: "^5.3.3",
117
119
  "@fastify/cors": "^11.0.1",
118
120
  turbo: "^2.6.3",
119
- ai: "^5.0.49",
120
- "@ai-sdk/google": "^2.0.51",
121
- "@ai-sdk/vue": "^2.0.49",
122
- "@ai-sdk/svelte": "^3.0.39",
123
- "@ai-sdk/react": "^2.0.39",
121
+ ai: "^6.0.3",
122
+ "@ai-sdk/google": "^3.0.1",
123
+ "@ai-sdk/vue": "^3.0.3",
124
+ "@ai-sdk/svelte": "^4.0.3",
125
+ "@ai-sdk/react": "^3.0.3",
126
+ "@ai-sdk/devtools": "^0.0.2",
124
127
  streamdown: "^1.6.10",
125
- shiki: "^3.12.2",
128
+ shiki: "^3.20.0",
126
129
  "@orpc/server": "^1.12.2",
127
130
  "@orpc/client": "^1.12.2",
128
131
  "@orpc/openapi": "^1.12.2",
@@ -214,18 +217,30 @@ const WEB_FRAMEWORKS = [
214
217
 
215
218
  //#endregion
216
219
  //#region src/utils/errors.ts
220
+ var UserCancelledError = class extends Error {
221
+ constructor(message = "Operation cancelled") {
222
+ super(message);
223
+ this.name = "UserCancelledError";
224
+ }
225
+ };
226
+ var CLIError = class extends Error {
227
+ constructor(message) {
228
+ super(message);
229
+ this.name = "CLIError";
230
+ }
231
+ };
217
232
  function exitWithError(message) {
218
233
  consola.error(pc.red(message));
219
- throw new Error(message);
234
+ throw new CLIError(message);
220
235
  }
221
236
  function exitCancelled(message = "Operation cancelled") {
222
237
  cancel(pc.red(message));
223
- throw new Error(message);
238
+ throw new UserCancelledError(message);
224
239
  }
225
240
  function handleError(error, fallbackMessage) {
226
241
  const message = error instanceof Error ? error.message : fallbackMessage || String(error);
227
242
  consola.error(pc.red(message));
228
- throw new Error(message);
243
+ throw error instanceof Error ? error : new Error(message);
229
244
  }
230
245
 
231
246
  //#endregion
@@ -292,8 +307,10 @@ function allowedApisForFrontends(frontends = []) {
292
307
  if (includesNuxt || includesSvelte || includesSolid) return ["orpc", "none"];
293
308
  return base;
294
309
  }
295
- function isExampleTodoAllowed(backend, database) {
296
- return !(backend !== "convex" && backend !== "none" && database === "none");
310
+ function isExampleTodoAllowed(backend, database, api) {
311
+ if (backend === "convex") return true;
312
+ if (database === "none" || api === "none") return false;
313
+ return true;
297
314
  }
298
315
  function isExampleAIAllowed(backend, frontends = []) {
299
316
  if (frontends.includes("solid")) return false;
@@ -343,10 +360,13 @@ function validatePaymentsCompatibility(payments, auth, _backend, frontends = [])
343
360
  if (web.length === 0 && frontends.length > 0) exitWithError("Polar payments requires a web frontend or no frontend. Please select a web frontend or choose a different payments provider.");
344
361
  }
345
362
  }
346
- function validateExamplesCompatibility(examples, backend, database, frontend) {
363
+ function validateExamplesCompatibility(examples, backend, database, frontend, api) {
347
364
  const examplesArr = examples ?? [];
348
365
  if (examplesArr.length === 0 || examplesArr.includes("none")) return;
349
- 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.");
366
+ if (examplesArr.includes("todo") && backend !== "convex") {
367
+ if (database === "none") exitWithError("The 'todo' example requires a database. Cannot use --examples todo when database is 'none'.");
368
+ if (api === "none") exitWithError("The 'todo' example requires an API layer (tRPC or oRPC). Cannot use --examples todo when api is 'none'.");
369
+ }
350
370
  if (examplesArr.includes("ai") && (frontend ?? []).includes("solid")) exitWithError("The 'ai' example is not compatible with the Solid frontend.");
351
371
  if (examplesArr.includes("ai") && backend === "convex") {
352
372
  const frontendArr = frontend ?? [];
@@ -356,6 +376,295 @@ function validateExamplesCompatibility(examples, backend, database, frontend) {
356
376
  }
357
377
  }
358
378
 
379
+ //#endregion
380
+ //#region src/utils/context.ts
381
+ const cliStorage = new AsyncLocalStorage();
382
+ function defaultContext() {
383
+ return {
384
+ navigation: {
385
+ isFirstPrompt: false,
386
+ lastPromptShownUI: false
387
+ },
388
+ silent: false,
389
+ verbose: false
390
+ };
391
+ }
392
+ function getContext() {
393
+ const ctx = cliStorage.getStore();
394
+ if (!ctx) return defaultContext();
395
+ return ctx;
396
+ }
397
+ function tryGetContext() {
398
+ return cliStorage.getStore();
399
+ }
400
+ function isSilent() {
401
+ return getContext().silent;
402
+ }
403
+ function isFirstPrompt() {
404
+ return getContext().navigation.isFirstPrompt;
405
+ }
406
+ function didLastPromptShowUI() {
407
+ return getContext().navigation.lastPromptShownUI;
408
+ }
409
+ function setIsFirstPrompt$1(value) {
410
+ const ctx = tryGetContext();
411
+ if (ctx) ctx.navigation.isFirstPrompt = value;
412
+ }
413
+ function setLastPromptShownUI(value) {
414
+ const ctx = tryGetContext();
415
+ if (ctx) ctx.navigation.lastPromptShownUI = value;
416
+ }
417
+ async function runWithContextAsync(options, fn) {
418
+ const ctx = {
419
+ navigation: {
420
+ isFirstPrompt: false,
421
+ lastPromptShownUI: false
422
+ },
423
+ silent: options.silent ?? false,
424
+ verbose: options.verbose ?? false,
425
+ projectDir: options.projectDir,
426
+ projectName: options.projectName,
427
+ packageManager: options.packageManager
428
+ };
429
+ return cliStorage.run(ctx, fn);
430
+ }
431
+
432
+ //#endregion
433
+ //#region src/utils/navigation.ts
434
+ /**
435
+ * Navigation symbols and utilities for prompt navigation
436
+ */
437
+ const GO_BACK_SYMBOL = Symbol("clack:goBack");
438
+ function isGoBack(value) {
439
+ return value === GO_BACK_SYMBOL;
440
+ }
441
+
442
+ //#endregion
443
+ //#region src/prompts/navigable.ts
444
+ /**
445
+ * Navigable prompt wrappers using @clack/core
446
+ * These prompts return GO_BACK_SYMBOL when 'b' is pressed (instead of canceling)
447
+ */
448
+ const unicode = process.platform !== "win32";
449
+ const S_STEP_ACTIVE = unicode ? "◆" : "*";
450
+ const S_STEP_CANCEL = unicode ? "■" : "x";
451
+ const S_STEP_ERROR = unicode ? "▲" : "x";
452
+ const S_STEP_SUBMIT = unicode ? "◇" : "o";
453
+ const S_BAR = unicode ? "│" : "|";
454
+ const S_BAR_END = unicode ? "└" : "—";
455
+ const S_RADIO_ACTIVE = unicode ? "●" : ">";
456
+ const S_RADIO_INACTIVE = unicode ? "○" : " ";
457
+ const S_CHECKBOX_ACTIVE = unicode ? "◻" : "[•]";
458
+ const S_CHECKBOX_SELECTED = unicode ? "◼" : "[+]";
459
+ const S_CHECKBOX_INACTIVE = unicode ? "◻" : "[ ]";
460
+ function symbol(state) {
461
+ switch (state) {
462
+ case "initial":
463
+ case "active": return pc.cyan(S_STEP_ACTIVE);
464
+ case "cancel": return pc.red(S_STEP_CANCEL);
465
+ case "error": return pc.yellow(S_STEP_ERROR);
466
+ case "submit": return pc.green(S_STEP_SUBMIT);
467
+ }
468
+ }
469
+ const KEYBOARD_HINT = pc.dim(`${pc.gray("↑/↓")} navigate • ${pc.gray("enter")} confirm • ${pc.gray("b")} back • ${pc.gray("ctrl+c")} cancel`);
470
+ const KEYBOARD_HINT_FIRST = pc.dim(`${pc.gray("↑/↓")} navigate • ${pc.gray("enter")} confirm • ${pc.gray("ctrl+c")} cancel`);
471
+ const KEYBOARD_HINT_MULTI = pc.dim(`${pc.gray("↑/↓")} navigate • ${pc.gray("space")} select • ${pc.gray("enter")} confirm • ${pc.gray("b")} back • ${pc.gray("ctrl+c")} cancel`);
472
+ const KEYBOARD_HINT_MULTI_FIRST = pc.dim(`${pc.gray("↑/↓")} navigate • ${pc.gray("space")} select • ${pc.gray("enter")} confirm • ${pc.gray("ctrl+c")} cancel`);
473
+ const setIsFirstPrompt = setIsFirstPrompt$1;
474
+ function getHint() {
475
+ return isFirstPrompt() ? KEYBOARD_HINT_FIRST : KEYBOARD_HINT;
476
+ }
477
+ function getMultiHint() {
478
+ return isFirstPrompt() ? KEYBOARD_HINT_MULTI_FIRST : KEYBOARD_HINT_MULTI;
479
+ }
480
+ async function runWithNavigation(prompt) {
481
+ let goBack = false;
482
+ prompt.on("key", (char) => {
483
+ if (char === "b" && !isFirstPrompt()) {
484
+ goBack = true;
485
+ prompt.state = "cancel";
486
+ }
487
+ });
488
+ setLastPromptShownUI(true);
489
+ const result = await prompt.prompt();
490
+ return goBack ? GO_BACK_SYMBOL : result;
491
+ }
492
+ async function navigableSelect(opts) {
493
+ const opt = (option, state) => {
494
+ const label = option.label ?? String(option.value);
495
+ switch (state) {
496
+ case "disabled": return `${pc.gray(S_RADIO_INACTIVE)} ${pc.gray(label)}${option.hint ? ` ${pc.dim(`(${option.hint ?? "disabled"})`)}` : ""}`;
497
+ case "selected": return `${pc.dim(label)}`;
498
+ case "active": return `${pc.green(S_RADIO_ACTIVE)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
499
+ case "cancelled": return `${pc.strikethrough(pc.dim(label))}`;
500
+ default: return `${pc.dim(S_RADIO_INACTIVE)} ${pc.dim(label)}`;
501
+ }
502
+ };
503
+ return runWithNavigation(new SelectPrompt({
504
+ options: opts.options,
505
+ initialValue: opts.initialValue,
506
+ render() {
507
+ const title = `${pc.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`;
508
+ switch (this.state) {
509
+ case "submit": return `${title}${pc.gray(S_BAR)} ${opt(this.options[this.cursor], "selected")}`;
510
+ case "cancel": return `${title}${pc.gray(S_BAR)} ${opt(this.options[this.cursor], "cancelled")}\n${pc.gray(S_BAR)}`;
511
+ default: {
512
+ const optionsText = this.options.map((option, i) => opt(option, option.disabled ? "disabled" : i === this.cursor ? "active" : "inactive")).join(`\n${pc.cyan(S_BAR)} `);
513
+ const hint = `\n${pc.gray(S_BAR)} ${getHint()}`;
514
+ return `${title}${pc.cyan(S_BAR)} ${optionsText}\n${pc.cyan(S_BAR_END)}${hint}\n`;
515
+ }
516
+ }
517
+ }
518
+ }));
519
+ }
520
+ async function navigableMultiselect(opts) {
521
+ const required = opts.required ?? true;
522
+ const opt = (option, state) => {
523
+ const label = option.label ?? String(option.value);
524
+ if (state === "disabled") return `${pc.gray(S_CHECKBOX_INACTIVE)} ${pc.strikethrough(pc.gray(label))}${option.hint ? ` ${pc.dim(`(${option.hint ?? "disabled"})`)}` : ""}`;
525
+ if (state === "active") return `${pc.cyan(S_CHECKBOX_ACTIVE)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
526
+ if (state === "selected") return `${pc.green(S_CHECKBOX_SELECTED)} ${pc.dim(label)}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
527
+ if (state === "cancelled") return `${pc.strikethrough(pc.dim(label))}`;
528
+ if (state === "active-selected") return `${pc.green(S_CHECKBOX_SELECTED)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
529
+ if (state === "submitted") return `${pc.dim(label)}`;
530
+ return `${pc.dim(S_CHECKBOX_INACTIVE)} ${pc.dim(label)}`;
531
+ };
532
+ return runWithNavigation(new MultiSelectPrompt({
533
+ options: opts.options,
534
+ initialValues: opts.initialValues,
535
+ required,
536
+ validate(selected) {
537
+ if (required && (selected === void 0 || selected.length === 0)) return `Please select at least one option.\n${pc.reset(pc.dim(`Press ${pc.gray(pc.bgWhite(pc.inverse(" space ")))} to select, ${pc.gray(pc.bgWhite(pc.inverse(" enter ")))} to submit`))}`;
538
+ },
539
+ render() {
540
+ const title = `${pc.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`;
541
+ const value = this.value ?? [];
542
+ const styleOption = (option, active) => {
543
+ if (option.disabled) return opt(option, "disabled");
544
+ const selected = value.includes(option.value);
545
+ if (active && selected) return opt(option, "active-selected");
546
+ if (selected) return opt(option, "selected");
547
+ return opt(option, active ? "active" : "inactive");
548
+ };
549
+ switch (this.state) {
550
+ case "submit": {
551
+ const submitText = this.options.filter(({ value: optionValue }) => value.includes(optionValue)).map((option) => opt(option, "submitted")).join(pc.dim(", ")) || pc.dim("none");
552
+ return `${title}${pc.gray(S_BAR)} ${submitText}`;
553
+ }
554
+ case "cancel": {
555
+ const label = this.options.filter(({ value: optionValue }) => value.includes(optionValue)).map((option) => opt(option, "cancelled")).join(pc.dim(", "));
556
+ return `${title}${pc.gray(S_BAR)} ${label}\n${pc.gray(S_BAR)}`;
557
+ }
558
+ case "error": {
559
+ const footer = this.error.split("\n").map((ln, i) => i === 0 ? `${pc.yellow(S_BAR_END)} ${pc.yellow(ln)}` : ` ${ln}`).join("\n");
560
+ const optionsText = this.options.map((option, i) => styleOption(option, i === this.cursor)).join(`\n${pc.yellow(S_BAR)} `);
561
+ return `${title}${pc.yellow(S_BAR)} ${optionsText}\n${footer}\n`;
562
+ }
563
+ default: {
564
+ const optionsText = this.options.map((option, i) => styleOption(option, i === this.cursor)).join(`\n${pc.cyan(S_BAR)} `);
565
+ const hint = `\n${pc.gray(S_BAR)} ${getMultiHint()}`;
566
+ return `${title}${pc.cyan(S_BAR)} ${optionsText}\n${pc.cyan(S_BAR_END)}${hint}\n`;
567
+ }
568
+ }
569
+ }
570
+ }));
571
+ }
572
+ async function navigableConfirm(opts) {
573
+ const active = opts.active ?? "Yes";
574
+ const inactive = opts.inactive ?? "No";
575
+ return runWithNavigation(new ConfirmPrompt({
576
+ active,
577
+ inactive,
578
+ initialValue: opts.initialValue ?? true,
579
+ render() {
580
+ const title = `${pc.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`;
581
+ const value = this.value ? active : inactive;
582
+ switch (this.state) {
583
+ case "submit": return `${title}${pc.gray(S_BAR)} ${pc.dim(value)}`;
584
+ case "cancel": return `${title}${pc.gray(S_BAR)} ${pc.strikethrough(pc.dim(value))}\n${pc.gray(S_BAR)}`;
585
+ default: {
586
+ const hint = `\n${pc.gray(S_BAR)} ${getHint()}`;
587
+ return `${title}${pc.cyan(S_BAR)} ${this.value ? `${pc.green(S_RADIO_ACTIVE)} ${active}` : `${pc.dim(S_RADIO_INACTIVE)} ${pc.dim(active)}`} ${pc.dim("/")} ${!this.value ? `${pc.green(S_RADIO_ACTIVE)} ${inactive}` : `${pc.dim(S_RADIO_INACTIVE)} ${pc.dim(inactive)}`}\n${pc.cyan(S_BAR_END)}${hint}\n`;
588
+ }
589
+ }
590
+ }
591
+ }));
592
+ }
593
+ async function navigableGroupMultiselect(opts) {
594
+ const required = opts.required ?? true;
595
+ const opt = (option, state, options = []) => {
596
+ const label = option.label ?? String(option.value);
597
+ const isItem = typeof option.group === "string";
598
+ const next = isItem && (options[options.indexOf(option) + 1] ?? { group: true });
599
+ const isLast = isItem && next && next.group === true;
600
+ const prefix = isItem ? `${isLast ? S_BAR_END : S_BAR} ` : "";
601
+ if (state === "active") return `${pc.dim(prefix)}${pc.cyan(S_CHECKBOX_ACTIVE)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
602
+ if (state === "group-active") return `${prefix}${pc.cyan(S_CHECKBOX_ACTIVE)} ${pc.dim(label)}`;
603
+ if (state === "group-active-selected") return `${prefix}${pc.green(S_CHECKBOX_SELECTED)} ${pc.dim(label)}`;
604
+ if (state === "selected") {
605
+ const selectedCheckbox = isItem ? pc.green(S_CHECKBOX_SELECTED) : "";
606
+ return `${pc.dim(prefix)}${selectedCheckbox} ${pc.dim(label)}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
607
+ }
608
+ if (state === "cancelled") return `${pc.strikethrough(pc.dim(label))}`;
609
+ if (state === "active-selected") return `${pc.dim(prefix)}${pc.green(S_CHECKBOX_SELECTED)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
610
+ if (state === "submitted") return `${pc.dim(label)}`;
611
+ const unselectedCheckbox = isItem ? pc.dim(S_CHECKBOX_INACTIVE) : "";
612
+ return `${pc.dim(prefix)}${unselectedCheckbox} ${pc.dim(label)}`;
613
+ };
614
+ return runWithNavigation(new GroupMultiSelectPrompt({
615
+ options: opts.options,
616
+ initialValues: opts.initialValues,
617
+ required,
618
+ selectableGroups: true,
619
+ validate(selected) {
620
+ if (required && (selected === void 0 || selected.length === 0)) return `Please select at least one option.\n${pc.reset(pc.dim(`Press ${pc.gray(pc.bgWhite(pc.inverse(" space ")))} to select, ${pc.gray(pc.bgWhite(pc.inverse(" enter ")))} to submit`))}`;
621
+ },
622
+ render() {
623
+ const title = `${pc.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`;
624
+ const value = this.value ?? [];
625
+ switch (this.state) {
626
+ case "submit": {
627
+ const selectedOptions = this.options.filter(({ value: optionValue }) => value.includes(optionValue)).map((option) => opt(option, "submitted"));
628
+ const optionsText = selectedOptions.length === 0 ? "" : ` ${selectedOptions.join(pc.dim(", "))}`;
629
+ return `${title}${pc.gray(S_BAR)}${optionsText}`;
630
+ }
631
+ case "cancel": {
632
+ const label = this.options.filter(({ value: optionValue }) => value.includes(optionValue)).map((option) => opt(option, "cancelled")).join(pc.dim(", "));
633
+ return `${title}${pc.gray(S_BAR)} ${label.trim() ? `${label}\n${pc.gray(S_BAR)}` : ""}`;
634
+ }
635
+ case "error": {
636
+ const footer = this.error.split("\n").map((ln, i) => i === 0 ? `${pc.yellow(S_BAR_END)} ${pc.yellow(ln)}` : ` ${ln}`).join("\n");
637
+ const optionsText = this.options.map((option, i, options) => {
638
+ const selected = value.includes(option.value) || option.group === true && this.isGroupSelected(`${option.value}`);
639
+ const active = i === this.cursor;
640
+ if (!active && typeof option.group === "string" && this.options[this.cursor].value === option.group) return opt(option, selected ? "group-active-selected" : "group-active", options);
641
+ if (active && selected) return opt(option, "active-selected", options);
642
+ if (selected) return opt(option, "selected", options);
643
+ return opt(option, active ? "active" : "inactive", options);
644
+ }).join(`\n${pc.yellow(S_BAR)} `);
645
+ return `${title}${pc.yellow(S_BAR)} ${optionsText}\n${footer}\n`;
646
+ }
647
+ default: {
648
+ const optionsText = this.options.map((option, i, options) => {
649
+ const selected = value.includes(option.value) || option.group === true && this.isGroupSelected(`${option.value}`);
650
+ const active = i === this.cursor;
651
+ const groupActive = !active && typeof option.group === "string" && this.options[this.cursor].value === option.group;
652
+ let optionText = "";
653
+ if (groupActive) optionText = opt(option, selected ? "group-active-selected" : "group-active", options);
654
+ else if (active && selected) optionText = opt(option, "active-selected", options);
655
+ else if (selected) optionText = opt(option, "selected", options);
656
+ else optionText = opt(option, active ? "active" : "inactive", options);
657
+ return `${i !== 0 && !optionText.startsWith("\n") ? " " : ""}${optionText}`;
658
+ }).join(`\n${pc.cyan(S_BAR)}`);
659
+ const optionsPrefix = optionsText.startsWith("\n") ? "" : " ";
660
+ const hint = `\n${pc.gray(S_BAR)} ${getMultiHint()}`;
661
+ return `${title}${pc.cyan(S_BAR)}${optionsPrefix}${optionsText}\n${pc.cyan(S_BAR_END)}${hint}\n`;
662
+ }
663
+ }
664
+ }
665
+ }));
666
+ }
667
+
359
668
  //#endregion
360
669
  //#region src/prompts/addons.ts
361
670
  function getAddonDisplay(addon) {
@@ -467,14 +776,13 @@ async function getAddonsChoice(addons, frontends, auth) {
467
776
  });
468
777
  }
469
778
  });
470
- const response = await groupMultiselect({
779
+ const response = await navigableGroupMultiselect({
471
780
  message: "Select addons",
472
781
  options: groupedOptions,
473
782
  initialValues: DEFAULT_CONFIG.addons.filter((addonValue) => Object.values(groupedOptions).some((options) => options.some((opt) => opt.value === addonValue))),
474
- required: false,
475
- selectableGroups: false
783
+ required: false
476
784
  });
477
- if (isCancel(response)) return exitCancelled("Operation cancelled");
785
+ if (isCancel$1(response)) return exitCancelled("Operation cancelled");
478
786
  return response;
479
787
  }
480
788
  async function getAddonsToAdd(frontend, existingAddons = [], auth) {
@@ -506,13 +814,12 @@ async function getAddonsToAdd(frontend, existingAddons = [], auth) {
506
814
  }
507
815
  });
508
816
  if (Object.keys(groupedOptions).length === 0) return [];
509
- const response = await groupMultiselect({
817
+ const response = await navigableGroupMultiselect({
510
818
  message: "Select addons to add",
511
819
  options: groupedOptions,
512
- required: false,
513
- selectableGroups: false
820
+ required: false
514
821
  });
515
- if (isCancel(response)) return exitCancelled("Operation cancelled");
822
+ if (isCancel$1(response)) return exitCancelled("Operation cancelled");
516
823
  return response;
517
824
  }
518
825
 
@@ -535,12 +842,12 @@ async function getApiChoice(Api, frontend, backend) {
535
842
  label: "None",
536
843
  hint: "No API layer (e.g. for full-stack frameworks like Next.js with Route Handlers)"
537
844
  });
538
- const apiType = await select({
845
+ const apiType = await navigableSelect({
539
846
  message: "Select API type",
540
847
  options: apiOptions,
541
848
  initialValue: apiOptions[0].value
542
849
  });
543
- if (isCancel(apiType)) return exitCancelled("Operation cancelled");
850
+ if (isCancel$1(apiType)) return exitCancelled("Operation cancelled");
544
851
  return apiType;
545
852
  }
546
853
 
@@ -578,20 +885,21 @@ async function getAuthChoice(auth, backend, frontend) {
578
885
  label: "Clerk",
579
886
  hint: "More than auth, Complete User Management"
580
887
  });
888
+ if (options.length === 0) return "none";
581
889
  options.push({
582
890
  value: "none",
583
891
  label: "None",
584
892
  hint: "No auth"
585
893
  });
586
- const response$1 = await select({
894
+ const response$1 = await navigableSelect({
587
895
  message: "Select authentication provider",
588
896
  options,
589
897
  initialValue: "none"
590
898
  });
591
- if (isCancel(response$1)) return exitCancelled("Operation cancelled");
899
+ if (isCancel$1(response$1)) return exitCancelled("Operation cancelled");
592
900
  return response$1;
593
901
  }
594
- const response = await select({
902
+ const response = await navigableSelect({
595
903
  message: "Select authentication provider",
596
904
  options: [{
597
905
  value: "better-auth",
@@ -603,7 +911,7 @@ async function getAuthChoice(auth, backend, frontend) {
603
911
  }],
604
912
  initialValue: DEFAULT_CONFIG.auth
605
913
  });
606
- if (isCancel(response)) return exitCancelled("Operation cancelled");
914
+ if (isCancel$1(response)) return exitCancelled("Operation cancelled");
607
915
  return response;
608
916
  }
609
917
 
@@ -647,12 +955,12 @@ async function getBackendFrameworkChoice(backendFramework, frontends) {
647
955
  label: "None",
648
956
  hint: "No backend server"
649
957
  });
650
- const response = await select({
958
+ const response = await navigableSelect({
651
959
  message: "Select backend",
652
960
  options: backendOptions,
653
961
  initialValue: hasFullstackFrontend ? "self" : DEFAULT_CONFIG.backend
654
962
  });
655
- if (isCancel(response)) return exitCancelled("Operation cancelled");
963
+ if (isCancel$1(response)) return exitCancelled("Operation cancelled");
656
964
  return response;
657
965
  }
658
966
 
@@ -688,12 +996,12 @@ async function getDatabaseChoice(database, backend, runtime) {
688
996
  label: "MongoDB",
689
997
  hint: "open-source NoSQL database that stores data in JSON-like documents called BSON"
690
998
  });
691
- const response = await select({
999
+ const response = await navigableSelect({
692
1000
  message: "Select database",
693
1001
  options: databaseOptions,
694
1002
  initialValue: DEFAULT_CONFIG.database
695
1003
  });
696
- if (isCancel(response)) return exitCancelled("Operation cancelled");
1004
+ if (isCancel$1(response)) return exitCancelled("Operation cancelled");
697
1005
  return response;
698
1006
  }
699
1007
 
@@ -788,12 +1096,12 @@ async function getDBSetupChoice(databaseType, dbSetup, _orm, backend, runtime) {
788
1096
  }
789
1097
  ];
790
1098
  else return "none";
791
- const response = await select({
1099
+ const response = await navigableSelect({
792
1100
  message: `Select ${databaseType} setup option`,
793
1101
  options,
794
1102
  initialValue: "none"
795
1103
  });
796
- if (isCancel(response)) return exitCancelled("Operation cancelled");
1104
+ if (isCancel$1(response)) return exitCancelled("Operation cancelled");
797
1105
  return response;
798
1106
  }
799
1107
 
@@ -802,13 +1110,9 @@ async function getDBSetupChoice(databaseType, dbSetup, _orm, backend, runtime) {
802
1110
  async function getExamplesChoice(examples, database, frontends, backend, api) {
803
1111
  if (examples !== void 0) return examples;
804
1112
  if (backend === "none") return [];
805
- if (backend !== "convex") {
806
- if (api === "none") return [];
807
- if (database === "none") return [];
808
- }
809
1113
  let response = [];
810
1114
  const options = [];
811
- if (isExampleTodoAllowed(backend, database)) options.push({
1115
+ if (isExampleTodoAllowed(backend, database, api)) options.push({
812
1116
  value: "todo",
813
1117
  label: "Todo App",
814
1118
  hint: "A simple CRUD example app"
@@ -819,13 +1123,13 @@ async function getExamplesChoice(examples, database, frontends, backend, api) {
819
1123
  hint: "A simple AI chat interface using AI SDK"
820
1124
  });
821
1125
  if (options.length === 0) return [];
822
- response = await multiselect({
1126
+ response = await navigableMultiselect({
823
1127
  message: "Include examples",
824
1128
  options,
825
1129
  required: false,
826
1130
  initialValues: DEFAULT_CONFIG.examples?.filter((ex) => options.some((o) => o.value === ex))
827
1131
  });
828
- if (isCancel(response)) return exitCancelled("Operation cancelled");
1132
+ if (isCancel$1(response)) return exitCancelled("Operation cancelled");
829
1133
  return response;
830
1134
  }
831
1135
 
@@ -833,104 +1137,124 @@ async function getExamplesChoice(examples, database, frontends, backend, api) {
833
1137
  //#region src/prompts/frontend.ts
834
1138
  async function getFrontendChoice(frontendOptions, backend, auth) {
835
1139
  if (frontendOptions !== void 0) return frontendOptions;
836
- const frontendTypes = await multiselect({
837
- message: "Select project type",
838
- options: [{
839
- value: "web",
840
- label: "Web",
841
- hint: "React, Vue or Svelte Web Application"
842
- }, {
843
- value: "native",
844
- label: "Native",
845
- hint: "Create a React Native/Expo app"
846
- }],
847
- required: false,
848
- initialValues: ["web"]
849
- });
850
- if (isCancel(frontendTypes)) return exitCancelled("Operation cancelled");
851
- const result = [];
852
- if (frontendTypes.includes("web")) {
853
- const webFramework = await select({
854
- message: "Choose web",
855
- options: [
856
- {
857
- value: "tanstack-router",
858
- label: "TanStack Router",
859
- hint: "Modern and scalable routing for React Applications"
860
- },
861
- {
862
- value: "react-router",
863
- label: "React Router",
864
- hint: "A user‑obsessed, standards‑focused, multi‑strategy router"
865
- },
866
- {
867
- value: "next",
868
- label: "Next.js",
869
- hint: "The React Framework for the Web"
870
- },
871
- {
872
- value: "nuxt",
873
- label: "Nuxt",
874
- hint: "The Progressive Web Framework for Vue.js"
875
- },
876
- {
877
- value: "svelte",
878
- label: "Svelte",
879
- hint: "web development for the rest of us"
880
- },
881
- {
882
- value: "solid",
883
- label: "Solid",
884
- hint: "Simple and performant reactivity for building user interfaces"
885
- },
886
- {
887
- value: "tanstack-start",
888
- label: "TanStack Start",
889
- hint: "SSR, Server Functions, API Routes and more with TanStack Router"
890
- }
891
- ].filter((option) => isFrontendAllowedWithBackend(option.value, backend, auth)),
892
- initialValue: DEFAULT_CONFIG.frontend[0]
893
- });
894
- if (isCancel(webFramework)) return exitCancelled("Operation cancelled");
895
- result.push(webFramework);
896
- }
897
- if (frontendTypes.includes("native")) {
898
- const nativeFramework = await select({
899
- message: "Choose native",
900
- options: [
901
- {
902
- value: "native-bare",
903
- label: "Bare",
904
- hint: "Bare Expo without styling library"
905
- },
906
- {
907
- value: "native-uniwind",
908
- label: "Uniwind",
909
- hint: "Fastest Tailwind bindings for React Native with HeroUI Native"
910
- },
911
- {
912
- value: "native-unistyles",
913
- label: "Unistyles",
914
- hint: "Consistent styling for React Native"
915
- }
916
- ],
917
- initialValue: "native-bare"
1140
+ while (true) {
1141
+ const wasFirstPrompt = isFirstPrompt();
1142
+ const frontendTypes = await navigableMultiselect({
1143
+ message: "Select project type",
1144
+ options: [{
1145
+ value: "web",
1146
+ label: "Web",
1147
+ hint: "React, Vue or Svelte Web Application"
1148
+ }, {
1149
+ value: "native",
1150
+ label: "Native",
1151
+ hint: "Create a React Native/Expo app"
1152
+ }],
1153
+ required: false,
1154
+ initialValues: ["web"]
918
1155
  });
919
- if (isCancel(nativeFramework)) return exitCancelled("Operation cancelled");
920
- result.push(nativeFramework);
1156
+ if (isGoBack(frontendTypes)) return GO_BACK_SYMBOL;
1157
+ if (isCancel$1(frontendTypes)) return exitCancelled("Operation cancelled");
1158
+ setIsFirstPrompt(false);
1159
+ const result = [];
1160
+ let shouldRestart = false;
1161
+ if (frontendTypes.includes("web")) {
1162
+ const webFramework = await navigableSelect({
1163
+ message: "Choose web",
1164
+ options: [
1165
+ {
1166
+ value: "tanstack-router",
1167
+ label: "TanStack Router",
1168
+ hint: "Modern and scalable routing for React Applications"
1169
+ },
1170
+ {
1171
+ value: "react-router",
1172
+ label: "React Router",
1173
+ hint: "A user‑obsessed, standards‑focused, multi‑strategy router"
1174
+ },
1175
+ {
1176
+ value: "next",
1177
+ label: "Next.js",
1178
+ hint: "The React Framework for the Web"
1179
+ },
1180
+ {
1181
+ value: "nuxt",
1182
+ label: "Nuxt",
1183
+ hint: "The Progressive Web Framework for Vue.js"
1184
+ },
1185
+ {
1186
+ value: "svelte",
1187
+ label: "Svelte",
1188
+ hint: "web development for the rest of us"
1189
+ },
1190
+ {
1191
+ value: "solid",
1192
+ label: "Solid",
1193
+ hint: "Simple and performant reactivity for building user interfaces"
1194
+ },
1195
+ {
1196
+ value: "tanstack-start",
1197
+ label: "TanStack Start",
1198
+ hint: "SSR, Server Functions, API Routes and more with TanStack Router"
1199
+ }
1200
+ ].filter((option) => isFrontendAllowedWithBackend(option.value, backend, auth)),
1201
+ initialValue: DEFAULT_CONFIG.frontend[0]
1202
+ });
1203
+ if (isGoBack(webFramework)) shouldRestart = true;
1204
+ else if (isCancel$1(webFramework)) return exitCancelled("Operation cancelled");
1205
+ else result.push(webFramework);
1206
+ }
1207
+ if (shouldRestart) {
1208
+ setIsFirstPrompt(wasFirstPrompt);
1209
+ continue;
1210
+ }
1211
+ if (frontendTypes.includes("native")) {
1212
+ const nativeFramework = await navigableSelect({
1213
+ message: "Choose native",
1214
+ options: [
1215
+ {
1216
+ value: "native-bare",
1217
+ label: "Bare",
1218
+ hint: "Bare Expo without styling library"
1219
+ },
1220
+ {
1221
+ value: "native-uniwind",
1222
+ label: "Uniwind",
1223
+ hint: "Fastest Tailwind bindings for React Native with HeroUI Native"
1224
+ },
1225
+ {
1226
+ value: "native-unistyles",
1227
+ label: "Unistyles",
1228
+ hint: "Consistent styling for React Native"
1229
+ }
1230
+ ],
1231
+ initialValue: "native-bare"
1232
+ });
1233
+ if (isGoBack(nativeFramework)) if (frontendTypes.includes("web")) shouldRestart = true;
1234
+ else {
1235
+ setIsFirstPrompt(wasFirstPrompt);
1236
+ continue;
1237
+ }
1238
+ else if (isCancel$1(nativeFramework)) return exitCancelled("Operation cancelled");
1239
+ else result.push(nativeFramework);
1240
+ }
1241
+ if (shouldRestart) {
1242
+ setIsFirstPrompt(wasFirstPrompt);
1243
+ continue;
1244
+ }
1245
+ return result;
921
1246
  }
922
- return result;
923
1247
  }
924
1248
 
925
1249
  //#endregion
926
1250
  //#region src/prompts/git.ts
927
1251
  async function getGitChoice(git) {
928
1252
  if (git !== void 0) return git;
929
- const response = await confirm({
1253
+ const response = await navigableConfirm({
930
1254
  message: "Initialize git repository?",
931
1255
  initialValue: DEFAULT_CONFIG.git
932
1256
  });
933
- if (isCancel(response)) return exitCancelled("Operation cancelled");
1257
+ if (isCancel$1(response)) return exitCancelled("Operation cancelled");
934
1258
  return response;
935
1259
  }
936
1260
 
@@ -938,14 +1262,71 @@ async function getGitChoice(git) {
938
1262
  //#region src/prompts/install.ts
939
1263
  async function getinstallChoice(install) {
940
1264
  if (install !== void 0) return install;
941
- const response = await confirm({
1265
+ const response = await navigableConfirm({
942
1266
  message: "Install dependencies?",
943
1267
  initialValue: DEFAULT_CONFIG.install
944
1268
  });
945
- if (isCancel(response)) return exitCancelled("Operation cancelled");
1269
+ if (isCancel$1(response)) return exitCancelled("Operation cancelled");
946
1270
  return response;
947
1271
  }
948
1272
 
1273
+ //#endregion
1274
+ //#region src/prompts/navigable-group.ts
1275
+ /**
1276
+ * Navigable group - a group of prompts that allows going back
1277
+ */
1278
+ /**
1279
+ * Define a group of prompts that supports going back to previous prompts.
1280
+ * Returns a result object with all the values, or handles cancel/go-back navigation.
1281
+ */
1282
+ async function navigableGroup(prompts, opts) {
1283
+ const results = {};
1284
+ const promptNames = Object.keys(prompts);
1285
+ let currentIndex = 0;
1286
+ let goingBack = false;
1287
+ while (currentIndex < promptNames.length) {
1288
+ const name = promptNames[currentIndex];
1289
+ const prompt = prompts[name];
1290
+ setIsFirstPrompt$1(currentIndex === 0);
1291
+ setLastPromptShownUI(false);
1292
+ const result = await prompt({ results })?.catch((e) => {
1293
+ throw e;
1294
+ });
1295
+ if (isGoBack(result)) {
1296
+ goingBack = true;
1297
+ if (currentIndex > 0) {
1298
+ const prevName = promptNames[currentIndex - 1];
1299
+ delete results[prevName];
1300
+ currentIndex--;
1301
+ continue;
1302
+ }
1303
+ goingBack = false;
1304
+ continue;
1305
+ }
1306
+ if (isCancel$1(result)) {
1307
+ if (typeof opts?.onCancel === "function") {
1308
+ results[name] = "canceled";
1309
+ opts.onCancel({ results });
1310
+ }
1311
+ setIsFirstPrompt$1(false);
1312
+ return results;
1313
+ }
1314
+ if (goingBack && !didLastPromptShowUI()) {
1315
+ if (currentIndex > 0) {
1316
+ const prevName = promptNames[currentIndex - 1];
1317
+ delete results[prevName];
1318
+ currentIndex--;
1319
+ continue;
1320
+ }
1321
+ }
1322
+ goingBack = false;
1323
+ results[name] = result;
1324
+ currentIndex++;
1325
+ }
1326
+ setIsFirstPrompt$1(false);
1327
+ return results;
1328
+ }
1329
+
949
1330
  //#endregion
950
1331
  //#region src/prompts/orm.ts
951
1332
  const ormOptions = {
@@ -969,12 +1350,12 @@ async function getORMChoice(orm, hasDatabase, database, backend, runtime) {
969
1350
  if (backend === "convex") return "none";
970
1351
  if (!hasDatabase) return "none";
971
1352
  if (orm !== void 0) return orm;
972
- const response = await select({
1353
+ const response = await navigableSelect({
973
1354
  message: "Select ORM",
974
1355
  options: database === "mongodb" ? [ormOptions.prisma, ormOptions.mongoose] : [ormOptions.drizzle, ormOptions.prisma],
975
1356
  initialValue: database === "mongodb" ? "prisma" : runtime === "workers" ? "drizzle" : DEFAULT_CONFIG.orm
976
1357
  });
977
- if (isCancel(response)) return exitCancelled("Operation cancelled");
1358
+ if (isCancel$1(response)) return exitCancelled("Operation cancelled");
978
1359
  return response;
979
1360
  }
980
1361
 
@@ -982,13 +1363,13 @@ async function getORMChoice(orm, hasDatabase, database, backend, runtime) {
982
1363
  //#region src/prompts/package-manager.ts
983
1364
  async function getPackageManagerChoice(packageManager) {
984
1365
  if (packageManager !== void 0) return packageManager;
985
- const response = await select({
1366
+ const response = await navigableSelect({
986
1367
  message: "Choose package manager",
987
1368
  options: [
988
1369
  {
989
1370
  value: "npm",
990
1371
  label: "npm",
991
- hint: "Node Package Manager"
1372
+ hint: "not recommended"
992
1373
  },
993
1374
  {
994
1375
  value: "pnpm",
@@ -1003,7 +1384,7 @@ async function getPackageManagerChoice(packageManager) {
1003
1384
  ],
1004
1385
  initialValue: getUserPkgManager()
1005
1386
  });
1006
- if (isCancel(response)) return exitCancelled("Operation cancelled");
1387
+ if (isCancel$1(response)) return exitCancelled("Operation cancelled");
1007
1388
  return response;
1008
1389
  }
1009
1390
 
@@ -1013,7 +1394,7 @@ async function getPaymentsChoice(payments, auth, backend, frontends) {
1013
1394
  if (payments !== void 0) return payments;
1014
1395
  if (backend === "none") return "none";
1015
1396
  if (!(auth === "better-auth" && backend !== "convex" && (frontends?.length === 0 || splitFrontends(frontends).web.length > 0))) return "none";
1016
- const response = await select({
1397
+ const response = await navigableSelect({
1017
1398
  message: "Select payments provider",
1018
1399
  options: [{
1019
1400
  value: "polar",
@@ -1026,7 +1407,7 @@ async function getPaymentsChoice(payments, auth, backend, frontends) {
1026
1407
  }],
1027
1408
  initialValue: DEFAULT_CONFIG.payments
1028
1409
  });
1029
- if (isCancel(response)) return exitCancelled("Operation cancelled");
1410
+ if (isCancel$1(response)) return exitCancelled("Operation cancelled");
1030
1411
  return response;
1031
1412
  }
1032
1413
 
@@ -1049,12 +1430,12 @@ async function getRuntimeChoice(runtime, backend) {
1049
1430
  label: "Cloudflare Workers",
1050
1431
  hint: "Edge runtime on Cloudflare's global network"
1051
1432
  });
1052
- const response = await select({
1433
+ const response = await navigableSelect({
1053
1434
  message: "Select runtime",
1054
1435
  options: runtimeOptions,
1055
1436
  initialValue: DEFAULT_CONFIG.runtime
1056
1437
  });
1057
- if (isCancel(response)) return exitCancelled("Operation cancelled");
1438
+ if (isCancel$1(response)) return exitCancelled("Operation cancelled");
1058
1439
  return response;
1059
1440
  }
1060
1441
 
@@ -1092,12 +1473,12 @@ async function getServerDeploymentToAdd(runtime, existingDeployment, backend) {
1092
1473
  }
1093
1474
  if (existingDeployment && existingDeployment !== "none") return "none";
1094
1475
  if (options.length === 0) return "none";
1095
- const response = await select({
1476
+ const response = await navigableSelect({
1096
1477
  message: "Select server deployment",
1097
1478
  options,
1098
1479
  initialValue: DEFAULT_CONFIG.serverDeploy
1099
1480
  });
1100
- if (isCancel(response)) return exitCancelled("Operation cancelled");
1481
+ if (isCancel$1(response)) return exitCancelled("Operation cancelled");
1101
1482
  return response;
1102
1483
  }
1103
1484
 
@@ -1119,7 +1500,7 @@ function getDeploymentDisplay(deployment) {
1119
1500
  async function getDeploymentChoice(deployment, _runtime, _backend, frontend = []) {
1120
1501
  if (deployment !== void 0) return deployment;
1121
1502
  if (!hasWebFrontend(frontend)) return "none";
1122
- const response = await select({
1503
+ const response = await navigableSelect({
1123
1504
  message: "Select web deployment",
1124
1505
  options: ["cloudflare", "none"].map((deploy) => {
1125
1506
  const { label, hint } = getDeploymentDisplay(deploy);
@@ -1131,7 +1512,7 @@ async function getDeploymentChoice(deployment, _runtime, _backend, frontend = []
1131
1512
  }),
1132
1513
  initialValue: DEFAULT_CONFIG.webDeploy
1133
1514
  });
1134
- if (isCancel(response)) return exitCancelled("Operation cancelled");
1515
+ if (isCancel$1(response)) return exitCancelled("Operation cancelled");
1135
1516
  return response;
1136
1517
  }
1137
1518
  async function getDeploymentToAdd(frontend, existingDeployment) {
@@ -1152,19 +1533,19 @@ async function getDeploymentToAdd(frontend, existingDeployment) {
1152
1533
  hint: "Skip deployment setup"
1153
1534
  });
1154
1535
  if (options.length === 0) return "none";
1155
- const response = await select({
1536
+ const response = await navigableSelect({
1156
1537
  message: "Select web deployment",
1157
1538
  options,
1158
1539
  initialValue: DEFAULT_CONFIG.webDeploy
1159
1540
  });
1160
- if (isCancel(response)) return exitCancelled("Operation cancelled");
1541
+ if (isCancel$1(response)) return exitCancelled("Operation cancelled");
1161
1542
  return response;
1162
1543
  }
1163
1544
 
1164
1545
  //#endregion
1165
1546
  //#region src/prompts/config-prompts.ts
1166
1547
  async function gatherConfig(flags, projectName, projectDir, relativePath) {
1167
- const result = await group({
1548
+ const result = await navigableGroup({
1168
1549
  frontend: () => getFrontendChoice(flags.frontend, flags.backend, flags.auth),
1169
1550
  backend: ({ results }) => getBackendFrameworkChoice(flags.backend, results.frontend),
1170
1551
  runtime: ({ results }) => getRuntimeChoice(flags.runtime, results.backend),
@@ -1272,7 +1653,7 @@ const getLatestCLIVersion = () => {
1272
1653
  */
1273
1654
  function isTelemetryEnabled() {
1274
1655
  const BTS_TELEMETRY_DISABLED = process.env.BTS_TELEMETRY_DISABLED;
1275
- const BTS_TELEMETRY = "1";
1656
+ const BTS_TELEMETRY = "0";
1276
1657
  if (BTS_TELEMETRY_DISABLED !== void 0) return BTS_TELEMETRY_DISABLED !== "1";
1277
1658
  if (BTS_TELEMETRY !== void 0) return BTS_TELEMETRY === "1";
1278
1659
  return true;
@@ -1280,16 +1661,7 @@ function isTelemetryEnabled() {
1280
1661
 
1281
1662
  //#endregion
1282
1663
  //#region src/utils/analytics.ts
1283
- const CONVEX_INGEST_URL = "https://striped-seahorse-863.convex.site/api/analytics/ingest";
1284
- async function sendConvexEvent(payload) {
1285
- try {
1286
- await fetch(CONVEX_INGEST_URL, {
1287
- method: "POST",
1288
- headers: { "Content-Type": "application/json" },
1289
- body: JSON.stringify(payload)
1290
- });
1291
- } catch {}
1292
- }
1664
+ async function sendConvexEvent(payload) {}
1293
1665
  async function trackProjectCreation(config, disableAnalytics = false) {
1294
1666
  if (!isTelemetryEnabled() || disableAnalytics) return;
1295
1667
  const { projectName: _projectName, projectDir: _projectDir, relativePath: _relativePath, ...safeConfig } = config;
@@ -1380,14 +1752,14 @@ function generateReproducibleCommand(config) {
1380
1752
 
1381
1753
  //#endregion
1382
1754
  //#region src/utils/project-directory.ts
1383
- async function handleDirectoryConflict(currentPathInput, silent = false) {
1755
+ async function handleDirectoryConflict(currentPathInput) {
1384
1756
  while (true) {
1385
1757
  const resolvedPath = path.resolve(process.cwd(), currentPathInput);
1386
1758
  if (!(await fs.pathExists(resolvedPath) && (await fs.readdir(resolvedPath)).length > 0)) return {
1387
1759
  finalPathInput: currentPathInput,
1388
1760
  shouldClearDirectory: false
1389
1761
  };
1390
- if (silent) throw new Error(`Directory "${currentPathInput}" already exists and is not empty. In silent mode, please provide a different project name or clear the directory manually.`);
1762
+ if (isSilent()) throw new Error(`Directory "${currentPathInput}" already exists and is not empty. In silent mode, please provide a different project name or clear the directory manually.`);
1391
1763
  log.warn(`Directory "${pc.yellow(currentPathInput)}" already exists and is not empty.`);
1392
1764
  const action = await select({
1393
1765
  message: "What would you like to do?",
@@ -1494,11 +1866,7 @@ const catppuccinTheme = {
1494
1866
  const renderTitle = () => {
1495
1867
  const terminalWidth = process.stdout.columns || 80;
1496
1868
  const titleLines = TITLE_TEXT.split("\n");
1497
- if (terminalWidth < Math.max(...titleLines.map((line) => line.length))) console.log(gradient(Object.values(catppuccinTheme)).multiline(`
1498
- ╔══════════════════╗
1499
- ║ Better T Stack ║
1500
- ╚══════════════════╝
1501
- `));
1869
+ if (terminalWidth < Math.max(...titleLines.map((line) => line.length))) console.log(gradient(Object.values(catppuccinTheme)).multiline(`Better T Stack`));
1502
1870
  else console.log(gradient(Object.values(catppuccinTheme)).multiline(TITLE_TEXT));
1503
1871
  };
1504
1872
 
@@ -1758,7 +2126,7 @@ function validateFrontendConstraints(config, providedFlags) {
1758
2126
  }
1759
2127
  function validateApiConstraints(config, options) {
1760
2128
  if (config.api === "none") {
1761
- if (options.examples && !(options.examples.length === 1 && options.examples[0] === "none") && options.backend !== "convex" && options.backend !== "none") exitWithError("Cannot use '--examples' when '--api' is set to 'none'. Please remove the --examples flag or choose an API type.");
2129
+ if (options.examples?.includes("todo") && options.backend !== "convex" && options.backend !== "none") exitWithError("Cannot use '--examples todo' when '--api' is set to 'none'. The todo example requires an API layer. Please remove 'todo' from --examples or choose an API type.");
1762
2130
  }
1763
2131
  }
1764
2132
  function validateFullConfig(config, providedFlags, options) {
@@ -1779,7 +2147,7 @@ function validateFullConfig(config, providedFlags, options) {
1779
2147
  validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth);
1780
2148
  config.addons = [...new Set(config.addons)];
1781
2149
  }
1782
- validateExamplesCompatibility(config.examples ?? [], config.backend, config.database, config.frontend ?? []);
2150
+ validateExamplesCompatibility(config.examples ?? [], config.backend, config.database, config.frontend ?? [], config.api);
1783
2151
  validatePaymentsCompatibility(config.payments, config.auth, config.backend, config.frontend ?? []);
1784
2152
  }
1785
2153
  function validateConfigForProgrammaticUse(config) {
@@ -1789,7 +2157,7 @@ function validateConfigForProgrammaticUse(config) {
1789
2157
  validateApiFrontendCompatibility(config.api, config.frontend);
1790
2158
  validatePaymentsCompatibility(config.payments, config.auth, config.backend, config.frontend);
1791
2159
  if (config.addons && config.addons.length > 0) validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth);
1792
- validateExamplesCompatibility(config.examples ?? [], config.backend, config.database, config.frontend ?? []);
2160
+ validateExamplesCompatibility(config.examples ?? [], config.backend, config.database, config.frontend ?? [], config.api);
1793
2161
  } catch (error) {
1794
2162
  if (error instanceof Error) throw error;
1795
2163
  throw new Error(String(error));
@@ -4007,11 +4375,19 @@ async function setupAIDependencies(config) {
4007
4375
  projectDir: convexBackendDir
4008
4376
  });
4009
4377
  else if (backend === "self" && webClientDirExists) await addPackageDependency({
4010
- dependencies: ["ai", "@ai-sdk/google"],
4378
+ dependencies: [
4379
+ "ai",
4380
+ "@ai-sdk/google",
4381
+ "@ai-sdk/devtools"
4382
+ ],
4011
4383
  projectDir: webClientDir
4012
4384
  });
4013
4385
  else if (serverDirExists && backend !== "none") await addPackageDependency({
4014
- dependencies: ["ai", "@ai-sdk/google"],
4386
+ dependencies: [
4387
+ "ai",
4388
+ "@ai-sdk/google",
4389
+ "@ai-sdk/devtools"
4390
+ ],
4015
4391
  projectDir: serverDir
4016
4392
  });
4017
4393
  if (webClientDirExists) {
@@ -6788,7 +7164,6 @@ async function updateConvexPackageJson(projectDir, options) {
6788
7164
  //#endregion
6789
7165
  //#region src/helpers/core/create-project.ts
6790
7166
  async function createProject(options, cliInput = {}) {
6791
- const { silent = false } = cliInput;
6792
7167
  const projectDir = options.projectDir;
6793
7168
  const isConvex = options.backend === "convex";
6794
7169
  const isSelfBackend = options.backend === "self";
@@ -6824,23 +7199,23 @@ async function createProject(options, cliInput = {}) {
6824
7199
  await setupCatalogs(projectDir, options);
6825
7200
  await createReadme(projectDir, options);
6826
7201
  await writeBtsConfig(options);
6827
- if (!silent) log.success("Project template successfully scaffolded!");
7202
+ if (!isSilent()) log.success("Project template successfully scaffolded!");
6828
7203
  if (options.install) await installDependencies({
6829
7204
  projectDir,
6830
7205
  packageManager: options.packageManager
6831
7206
  });
6832
7207
  await initializeGit(projectDir, options.git);
6833
- if (!silent) await displayPostInstallInstructions({
7208
+ if (!isSilent()) await displayPostInstallInstructions({
6834
7209
  ...options,
6835
7210
  depsInstalled: options.install
6836
7211
  });
6837
7212
  return projectDir;
6838
7213
  } catch (error) {
6839
7214
  if (error instanceof Error) {
6840
- if (!silent) console.error(error.stack);
7215
+ if (!isSilent()) console.error(error.stack);
6841
7216
  exitWithError(`Error during project creation: ${error.message}`);
6842
7217
  } else {
6843
- if (!silent) console.error(error);
7218
+ if (!isSilent()) console.error(error);
6844
7219
  exitWithError(`An unexpected error occurred: ${String(error)}`);
6845
7220
  }
6846
7221
  }
@@ -6850,139 +7225,169 @@ async function createProject(options, cliInput = {}) {
6850
7225
  //#region src/helpers/core/command-handlers.ts
6851
7226
  async function createProjectHandler(input, options = {}) {
6852
7227
  const { silent = false } = options;
6853
- const startTime = Date.now();
6854
- const timeScaffolded = (/* @__PURE__ */ new Date()).toISOString();
6855
- if (!silent && input.renderTitle !== false) renderTitle();
6856
- if (!silent) intro(pc.magenta("Creating a new Better-T-Stack project"));
6857
- if (!silent && input.yolo) consola.fatal("YOLO mode enabled - skipping checks. Things may break!");
6858
- let currentPathInput;
6859
- if (input.yes && input.projectName) currentPathInput = input.projectName;
6860
- else if (input.yes) {
6861
- const defaultConfig = getDefaultConfig();
6862
- let defaultName = defaultConfig.relativePath;
6863
- let counter = 1;
6864
- while (await fs.pathExists(path.resolve(process.cwd(), defaultName)) && (await fs.readdir(path.resolve(process.cwd(), defaultName))).length > 0) {
6865
- defaultName = `${defaultConfig.projectName}-${counter}`;
6866
- counter++;
6867
- }
6868
- currentPathInput = defaultName;
6869
- } else currentPathInput = await getProjectName(input.projectName);
6870
- let finalPathInput;
6871
- let shouldClearDirectory;
6872
- try {
6873
- if (input.directoryConflict) {
6874
- const result = await handleDirectoryConflictProgrammatically(currentPathInput, input.directoryConflict);
6875
- finalPathInput = result.finalPathInput;
6876
- shouldClearDirectory = result.shouldClearDirectory;
6877
- } else {
6878
- const result = await handleDirectoryConflict(currentPathInput);
6879
- finalPathInput = result.finalPathInput;
6880
- shouldClearDirectory = result.shouldClearDirectory;
6881
- }
6882
- } catch (error) {
6883
- return {
6884
- success: false,
6885
- projectConfig: {
6886
- projectName: "",
6887
- projectDir: "",
6888
- relativePath: "",
6889
- database: "none",
6890
- orm: "none",
6891
- backend: "none",
6892
- runtime: "none",
6893
- frontend: [],
6894
- addons: [],
6895
- examples: [],
6896
- auth: "none",
6897
- payments: "none",
6898
- git: false,
6899
- packageManager: "npm",
6900
- install: false,
6901
- dbSetup: "none",
6902
- api: "none",
6903
- webDeploy: "none",
6904
- serverDeploy: "none"
6905
- },
6906
- reproducibleCommand: "",
6907
- timeScaffolded,
6908
- elapsedTimeMs: Date.now() - startTime,
6909
- projectDirectory: "",
6910
- relativePath: "",
6911
- error: error instanceof Error ? error.message : String(error)
6912
- };
6913
- }
6914
- const { finalResolvedPath, finalBaseName } = await setupProjectDirectory(finalPathInput, shouldClearDirectory);
6915
- const originalInput = {
6916
- ...input,
6917
- projectDirectory: input.projectName
6918
- };
6919
- const providedFlags = getProvidedFlags(originalInput);
6920
- let cliInput = originalInput;
6921
- if (input.template && input.template !== "none") {
6922
- const templateConfig = getTemplateConfig(input.template);
6923
- if (templateConfig) {
6924
- const templateName = input.template.toUpperCase();
6925
- const templateDescription = getTemplateDescription(input.template);
6926
- if (!silent) {
6927
- log.message(pc.bold(pc.cyan(`Using template: ${pc.white(templateName)}`)));
6928
- log.message(pc.dim(` ${templateDescription}`));
7228
+ return runWithContextAsync({ silent }, async () => {
7229
+ const startTime = Date.now();
7230
+ const timeScaffolded = (/* @__PURE__ */ new Date()).toISOString();
7231
+ try {
7232
+ if (!isSilent() && input.renderTitle !== false) renderTitle();
7233
+ if (!isSilent()) intro(pc.magenta("Creating a new Better-T-Stack project"));
7234
+ if (!isSilent() && input.yolo) consola.fatal("YOLO mode enabled - skipping checks. Things may break!");
7235
+ let currentPathInput;
7236
+ if (input.yes && input.projectName) currentPathInput = input.projectName;
7237
+ else if (input.yes) {
7238
+ const defaultConfig = getDefaultConfig();
7239
+ let defaultName = defaultConfig.relativePath;
7240
+ let counter = 1;
7241
+ while (await fs.pathExists(path.resolve(process.cwd(), defaultName)) && (await fs.readdir(path.resolve(process.cwd(), defaultName))).length > 0) {
7242
+ defaultName = `${defaultConfig.projectName}-${counter}`;
7243
+ counter++;
7244
+ }
7245
+ currentPathInput = defaultName;
7246
+ } else currentPathInput = await getProjectName(input.projectName);
7247
+ let finalPathInput;
7248
+ let shouldClearDirectory;
7249
+ try {
7250
+ if (input.directoryConflict) {
7251
+ const result = await handleDirectoryConflictProgrammatically(currentPathInput, input.directoryConflict);
7252
+ finalPathInput = result.finalPathInput;
7253
+ shouldClearDirectory = result.shouldClearDirectory;
7254
+ } else {
7255
+ const result = await handleDirectoryConflict(currentPathInput);
7256
+ finalPathInput = result.finalPathInput;
7257
+ shouldClearDirectory = result.shouldClearDirectory;
7258
+ }
7259
+ } catch (error) {
7260
+ if (error instanceof UserCancelledError || error instanceof CLIError) throw error;
7261
+ return {
7262
+ success: false,
7263
+ projectConfig: {
7264
+ projectName: "",
7265
+ projectDir: "",
7266
+ relativePath: "",
7267
+ database: "none",
7268
+ orm: "none",
7269
+ backend: "none",
7270
+ runtime: "none",
7271
+ frontend: [],
7272
+ addons: [],
7273
+ examples: [],
7274
+ auth: "none",
7275
+ payments: "none",
7276
+ git: false,
7277
+ packageManager: "npm",
7278
+ install: false,
7279
+ dbSetup: "none",
7280
+ api: "none",
7281
+ webDeploy: "none",
7282
+ serverDeploy: "none"
7283
+ },
7284
+ reproducibleCommand: "",
7285
+ timeScaffolded,
7286
+ elapsedTimeMs: Date.now() - startTime,
7287
+ projectDirectory: "",
7288
+ relativePath: "",
7289
+ error: error instanceof Error ? error.message : String(error)
7290
+ };
6929
7291
  }
6930
- const userOverrides = {};
6931
- for (const [key, value] of Object.entries(originalInput)) if (value !== void 0) userOverrides[key] = value;
6932
- cliInput = {
6933
- ...templateConfig,
6934
- ...userOverrides,
6935
- template: input.template,
6936
- projectDirectory: originalInput.projectDirectory
7292
+ const { finalResolvedPath, finalBaseName } = await setupProjectDirectory(finalPathInput, shouldClearDirectory);
7293
+ const originalInput = {
7294
+ ...input,
7295
+ projectDirectory: input.projectName
6937
7296
  };
7297
+ const providedFlags = getProvidedFlags(originalInput);
7298
+ let cliInput = originalInput;
7299
+ if (input.template && input.template !== "none") {
7300
+ const templateConfig = getTemplateConfig(input.template);
7301
+ if (templateConfig) {
7302
+ const templateName = input.template.toUpperCase();
7303
+ const templateDescription = getTemplateDescription(input.template);
7304
+ if (!isSilent()) {
7305
+ log.message(pc.bold(pc.cyan(`Using template: ${pc.white(templateName)}`)));
7306
+ log.message(pc.dim(` ${templateDescription}`));
7307
+ }
7308
+ const userOverrides = {};
7309
+ for (const [key, value] of Object.entries(originalInput)) if (value !== void 0) userOverrides[key] = value;
7310
+ cliInput = {
7311
+ ...templateConfig,
7312
+ ...userOverrides,
7313
+ template: input.template,
7314
+ projectDirectory: originalInput.projectDirectory
7315
+ };
7316
+ }
7317
+ }
7318
+ let config;
7319
+ if (cliInput.yes) {
7320
+ const flagConfig = processProvidedFlagsWithoutValidation(cliInput, finalBaseName);
7321
+ config = {
7322
+ ...getDefaultConfig(),
7323
+ ...flagConfig,
7324
+ projectName: finalBaseName,
7325
+ projectDir: finalResolvedPath,
7326
+ relativePath: finalPathInput
7327
+ };
7328
+ validateConfigCompatibility(config, providedFlags, cliInput);
7329
+ if (!isSilent()) {
7330
+ log.info(pc.yellow("Using default/flag options (config prompts skipped):"));
7331
+ log.message(displayConfig(config));
7332
+ }
7333
+ } else {
7334
+ const flagConfig = processAndValidateFlags(cliInput, providedFlags, finalBaseName);
7335
+ const { projectName: _projectNameFromFlags, ...otherFlags } = flagConfig;
7336
+ if (!isSilent() && Object.keys(otherFlags).length > 0) {
7337
+ log.info(pc.yellow("Using these pre-selected options:"));
7338
+ log.message(displayConfig(otherFlags));
7339
+ log.message("");
7340
+ }
7341
+ config = await gatherConfig(flagConfig, finalBaseName, finalResolvedPath, finalPathInput);
7342
+ }
7343
+ await createProject(config, { manualDb: cliInput.manualDb ?? input.manualDb });
7344
+ const reproducibleCommand = generateReproducibleCommand(config);
7345
+ if (!isSilent()) log.success(pc.blue(`You can reproduce this setup with the following command:\n${reproducibleCommand}`));
7346
+ await trackProjectCreation(config, input.disableAnalytics);
7347
+ const elapsedTimeMs = Date.now() - startTime;
7348
+ if (!isSilent()) {
7349
+ const elapsedTimeInSeconds = (elapsedTimeMs / 1e3).toFixed(2);
7350
+ outro(pc.magenta(`Project created successfully in ${pc.bold(elapsedTimeInSeconds)} seconds!`));
7351
+ }
7352
+ return {
7353
+ success: true,
7354
+ projectConfig: config,
7355
+ reproducibleCommand,
7356
+ timeScaffolded,
7357
+ elapsedTimeMs,
7358
+ projectDirectory: config.projectDir,
7359
+ relativePath: config.relativePath
7360
+ };
7361
+ } catch (error) {
7362
+ if (error instanceof UserCancelledError) {
7363
+ if (isSilent()) return {
7364
+ success: false,
7365
+ error: error.message,
7366
+ projectConfig: {},
7367
+ reproducibleCommand: "",
7368
+ timeScaffolded,
7369
+ elapsedTimeMs: Date.now() - startTime,
7370
+ projectDirectory: "",
7371
+ relativePath: ""
7372
+ };
7373
+ return;
7374
+ }
7375
+ if (error instanceof CLIError) {
7376
+ if (isSilent()) return {
7377
+ success: false,
7378
+ error: error.message,
7379
+ projectConfig: {},
7380
+ reproducibleCommand: "",
7381
+ timeScaffolded,
7382
+ elapsedTimeMs: Date.now() - startTime,
7383
+ projectDirectory: "",
7384
+ relativePath: ""
7385
+ };
7386
+ throw error;
7387
+ }
7388
+ throw error;
6938
7389
  }
6939
- }
6940
- let config;
6941
- if (cliInput.yes) {
6942
- const flagConfig = processProvidedFlagsWithoutValidation(cliInput, finalBaseName);
6943
- config = {
6944
- ...getDefaultConfig(),
6945
- ...flagConfig,
6946
- projectName: finalBaseName,
6947
- projectDir: finalResolvedPath,
6948
- relativePath: finalPathInput
6949
- };
6950
- validateConfigCompatibility(config, providedFlags, cliInput);
6951
- if (!silent) {
6952
- log.info(pc.yellow("Using default/flag options (config prompts skipped):"));
6953
- log.message(displayConfig(config));
6954
- }
6955
- } else {
6956
- const flagConfig = processAndValidateFlags(cliInput, providedFlags, finalBaseName);
6957
- const { projectName: _projectNameFromFlags, ...otherFlags } = flagConfig;
6958
- if (!silent && Object.keys(otherFlags).length > 0) {
6959
- log.info(pc.yellow("Using these pre-selected options:"));
6960
- log.message(displayConfig(otherFlags));
6961
- log.message("");
6962
- }
6963
- config = await gatherConfig(flagConfig, finalBaseName, finalResolvedPath, finalPathInput);
6964
- }
6965
- await createProject(config, {
6966
- manualDb: cliInput.manualDb ?? input.manualDb,
6967
- silent
6968
7390
  });
6969
- const reproducibleCommand = generateReproducibleCommand(config);
6970
- if (!silent) log.success(pc.blue(`You can reproduce this setup with the following command:\n${reproducibleCommand}`));
6971
- await trackProjectCreation(config, input.disableAnalytics);
6972
- const elapsedTimeMs = Date.now() - startTime;
6973
- if (!silent) {
6974
- const elapsedTimeInSeconds = (elapsedTimeMs / 1e3).toFixed(2);
6975
- outro(pc.magenta(`Project created successfully in ${pc.bold(elapsedTimeInSeconds)} seconds!`));
6976
- }
6977
- return {
6978
- success: true,
6979
- projectConfig: config,
6980
- reproducibleCommand,
6981
- timeScaffolded,
6982
- elapsedTimeMs,
6983
- projectDirectory: config.projectDir,
6984
- relativePath: config.relativePath
6985
- };
6986
7391
  }
6987
7392
  async function handleDirectoryConflictProgrammatically(currentPathInput, strategy) {
6988
7393
  const currentPath = path.resolve(process.cwd(), currentPathInput);
@@ -7077,6 +7482,8 @@ async function addAddonsHandler(input) {
7077
7482
  else log.info(`Run ${pc.bold(`${packageManager} install`)} to install dependencies`);
7078
7483
  outro("Add command completed successfully!");
7079
7484
  } catch (error) {
7485
+ if (error instanceof UserCancelledError) return;
7486
+ if (error instanceof CLIError) throw error;
7080
7487
  handleError(error, "Failed to add addons or deployment");
7081
7488
  }
7082
7489
  }