create-better-t-stack 3.41.4 → 3.42.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { D as setProcessMode, i as SchemaNameSchema, l as create, m as getSchemaResult, s as add, u as createBtsCli, v as types_exports, y as getLatestCLIVersion } from "./src-C8JXMrWB.mjs";
2
+ import { D as setProcessMode, i as SchemaNameSchema, l as create, m as getSchemaResult, s as add, u as createBtsCli, v as types_exports, y as getLatestCLIVersion } from "./src-C0_lYEa0.mjs";
3
3
  import z from "zod";
4
4
  import { McpServer } from "@modelcontextprotocol/server";
5
5
  import { serveStdio } from "@modelcontextprotocol/server/stdio";
package/dist/index.d.mts CHANGED
@@ -144,6 +144,7 @@ interface CreateProjectResult {
144
144
  projectDirectory: string;
145
145
  relativePath: string;
146
146
  error?: string;
147
+ warnings?: string[];
147
148
  }
148
149
  //#endregion
149
150
  //#region src/helpers/core/add-handler.d.ts
@@ -268,20 +269,20 @@ declare const router: import("@trpc/server").TRPCBuiltRouter<{
268
269
  mcp?: {
269
270
  scope?: "project" | "global" | undefined;
270
271
  servers?: ("nx" | "better-auth" | "clerk" | "polar" | "neon" | "planetscale" | "supabase" | "convex" | "better-t-stack" | "context7" | "cloudflare-docs" | "shadcn" | "next-devtools" | "nuxt-docs" | "nuxt-ui-docs" | "svelte-docs" | "astro-docs" | "expo")[] | undefined;
271
- agents?: ("cursor" | "vscode" | "zed" | "windsurf" | "codex" | "claude-code" | "opencode" | "gemini-cli" | "goose" | "cline" | "antigravity" | "cline-cli" | "github-copilot-cli" | "grok-build" | "mcporter" | "claude-desktop")[] | undefined;
272
+ agents?: ("cursor" | "vscode" | "zed" | "windsurf" | "codex" | "claude-code" | "opencode" | "gemini-cli" | "kiro-cli" | "goose" | "cline" | "antigravity" | "cline-cli" | "github-copilot-cli" | "grok-build" | "mcporter" | "claude-desktop" | "fx" | "kilo-code" | "kimi-code")[] | undefined;
272
273
  } | undefined;
273
274
  skills?: {
274
275
  scope?: "project" | "global" | undefined;
275
- agents?: ("cursor" | "zed" | "windsurf" | "codex" | "claude-code" | "opencode" | "pi" | "gemini-cli" | "github-copilot" | "kiro-cli" | "droid" | "goose" | "cline" | "continue" | "amp" | "qwen-code" | "crush" | "antigravity" | "universal" | "adal" | "aider-desk" | "antigravity-cli" | "astrbot" | "augment" | "autohand-code" | "bob" | "codearts-agent" | "codebuddy" | "codemaker" | "codestudio" | "command-code" | "cortex" | "deepagents" | "devin" | "dexto" | "eve" | "firebender" | "forgecode" | "grok" | "hermes-agent" | "iflow-cli" | "inference-sh" | "jazz" | "junie" | "kilo" | "kimchi" | "kimi-code-cli" | "kode" | "lingma" | "loaf" | "mcpjam" | "mistral-vibe" | "moxby" | "mux" | "neovate" | "ona" | "clawdbot" | "openclaw" | "openhands" | "pochi" | "promptscript" | "qoder" | "qoder-cn" | "reasonix" | "replit" | "roo" | "rovodev" | "tabnine-cli" | "terramind" | "tinycloud" | "trae" | "trae-cn" | "warp" | "zcode" | "zencoder" | "zenflow")[] | undefined;
276
+ agents?: ("cursor" | "zed" | "windsurf" | "codex" | "claude-code" | "opencode" | "pi" | "gemini-cli" | "github-copilot" | "kiro-cli" | "droid" | "goose" | "cline" | "continue" | "amp" | "qwen-code" | "crush" | "antigravity" | "universal" | "adal" | "aider-desk" | "antigravity-cli" | "astrbot" | "augment" | "autohand-code" | "bob" | "codearts-agent" | "codebuddy" | "codemaker" | "codestudio" | "command-code" | "cortex" | "deepagents" | "devin" | "dexto" | "eve" | "firebender" | "forgecode" | "grok" | "hermes-agent" | "iflow-cli" | "inference-sh" | "jazz" | "junie" | "kilo" | "kimchi" | "kimi-code-cli" | "kode" | "lingma" | "loaf" | "mcpjam" | "minimax-code" | "mistral-vibe" | "moxby" | "mux" | "neovate" | "ona" | "clawdbot" | "openclaw" | "openhands" | "pochi" | "posit-assistant" | "promptscript" | "qoder" | "qoder-cn" | "reasonix" | "replit" | "roo" | "rovodev" | "tabnine-cli" | "terramind" | "tinycloud" | "trae" | "trae-cn" | "warp" | "zcode" | "zencoder" | "zenflow")[] | undefined;
276
277
  selections?: {
277
- source: "vercel-labs/agent-skills" | "vercel/ai" | "vercel/turborepo" | "yusukebe/hono-skill" | "vercel-labs/next-skills" | "nuxt/ui" | "heroui-inc/heroui" | "shadcn/ui" | "better-auth/skills" | "clerk/skills" | "neondatabase/agent-skills" | "supabase/agent-skills" | "planetscale/database-skills" | "expo/skills" | "prisma/skills" | "elysiajs/skills" | "waynesutton/convexskills" | "msmps/opentui-skill" | "haydenbleasel/ultracite" | "https://www.evlog.dev";
278
+ source: "vercel-labs/agent-skills" | "vercel/ai" | "vercel/turborepo" | "yusukebe/hono-skill" | "vercel/next.js" | "nuxt/ui" | "heroui-inc/heroui" | "shadcn/ui" | "better-auth/skills" | "clerk/skills" | "neondatabase/agent-skills" | "supabase/agent-skills" | "planetscale/database-skills" | "expo/skills" | "prisma/skills" | "elysiajs/skills" | "waynesutton/convexskills" | "msmps/opentui-skill" | "haydenbleasel/ultracite" | "https://www.evlog.dev";
278
279
  skills: string[];
279
280
  }[] | undefined;
280
281
  } | undefined;
281
282
  ultracite?: {
282
283
  linter?: "biome" | "oxlint" | undefined;
283
- editors?: ("void" | "cursor" | "vscode" | "zed" | "windsurf" | "antigravity" | "bob" | "codebuddy" | "trae" | "kiro")[] | undefined;
284
- agents?: ("codex" | "opencode" | "pi" | "droid" | "goose" | "cline" | "continue" | "amp" | "aider" | "crush" | "vercel" | "universal" | "adal" | "bob" | "deepagents" | "devin" | "firebender" | "junie" | "mcpjam" | "mistral-vibe" | "mux" | "ona" | "openclaw" | "qoder" | "replit" | "warp" | "zencoder" | "claude" | "jules" | "lovable" | "snowflake-cortex" | "kimi-cli" | "copilot" | "firebase-studio" | "open-hands" | "gemini" | "augmentcode" | "kilo-code" | "roo-code" | "qwen" | "amazon-q-cli" | "cursor-cli")[] | undefined;
284
+ editors?: ("void" | "cursor" | "vscode" | "zed" | "windsurf" | "antigravity" | "universal" | "bob" | "codebuddy" | "trae" | "kiro")[] | undefined;
285
+ agents?: ("codex" | "opencode" | "pi" | "droid" | "goose" | "cline" | "continue" | "amp" | "aider" | "crush" | "vercel" | "kilo-code" | "universal" | "adal" | "bob" | "deepagents" | "devin" | "firebender" | "junie" | "mcpjam" | "mistral-vibe" | "mux" | "ona" | "openclaw" | "qoder" | "replit" | "warp" | "zencoder" | "claude" | "jules" | "lovable" | "snowflake-cortex" | "kimi-cli" | "copilot" | "firebase-studio" | "open-hands" | "gemini" | "augmentcode" | "roo-code" | "qwen" | "amazon-q-cli" | "cursor-cli")[] | undefined;
285
286
  hooks?: ("cursor" | "windsurf" | "codebuddy" | "claude" | "copilot")[] | undefined;
286
287
  } | undefined;
287
288
  } | undefined;
@@ -382,20 +383,20 @@ declare const router: import("@trpc/server").TRPCBuiltRouter<{
382
383
  mcp?: {
383
384
  scope?: "project" | "global" | undefined;
384
385
  servers?: ("nx" | "better-auth" | "clerk" | "polar" | "neon" | "planetscale" | "supabase" | "convex" | "better-t-stack" | "context7" | "cloudflare-docs" | "shadcn" | "next-devtools" | "nuxt-docs" | "nuxt-ui-docs" | "svelte-docs" | "astro-docs" | "expo")[] | undefined;
385
- agents?: ("cursor" | "vscode" | "zed" | "windsurf" | "codex" | "claude-code" | "opencode" | "gemini-cli" | "goose" | "cline" | "antigravity" | "cline-cli" | "github-copilot-cli" | "grok-build" | "mcporter" | "claude-desktop")[] | undefined;
386
+ agents?: ("cursor" | "vscode" | "zed" | "windsurf" | "codex" | "claude-code" | "opencode" | "gemini-cli" | "kiro-cli" | "goose" | "cline" | "antigravity" | "cline-cli" | "github-copilot-cli" | "grok-build" | "mcporter" | "claude-desktop" | "fx" | "kilo-code" | "kimi-code")[] | undefined;
386
387
  } | undefined;
387
388
  skills?: {
388
389
  scope?: "project" | "global" | undefined;
389
- agents?: ("cursor" | "zed" | "windsurf" | "codex" | "claude-code" | "opencode" | "pi" | "gemini-cli" | "github-copilot" | "kiro-cli" | "droid" | "goose" | "cline" | "continue" | "amp" | "qwen-code" | "crush" | "antigravity" | "universal" | "adal" | "aider-desk" | "antigravity-cli" | "astrbot" | "augment" | "autohand-code" | "bob" | "codearts-agent" | "codebuddy" | "codemaker" | "codestudio" | "command-code" | "cortex" | "deepagents" | "devin" | "dexto" | "eve" | "firebender" | "forgecode" | "grok" | "hermes-agent" | "iflow-cli" | "inference-sh" | "jazz" | "junie" | "kilo" | "kimchi" | "kimi-code-cli" | "kode" | "lingma" | "loaf" | "mcpjam" | "mistral-vibe" | "moxby" | "mux" | "neovate" | "ona" | "clawdbot" | "openclaw" | "openhands" | "pochi" | "promptscript" | "qoder" | "qoder-cn" | "reasonix" | "replit" | "roo" | "rovodev" | "tabnine-cli" | "terramind" | "tinycloud" | "trae" | "trae-cn" | "warp" | "zcode" | "zencoder" | "zenflow")[] | undefined;
390
+ agents?: ("cursor" | "zed" | "windsurf" | "codex" | "claude-code" | "opencode" | "pi" | "gemini-cli" | "github-copilot" | "kiro-cli" | "droid" | "goose" | "cline" | "continue" | "amp" | "qwen-code" | "crush" | "antigravity" | "universal" | "adal" | "aider-desk" | "antigravity-cli" | "astrbot" | "augment" | "autohand-code" | "bob" | "codearts-agent" | "codebuddy" | "codemaker" | "codestudio" | "command-code" | "cortex" | "deepagents" | "devin" | "dexto" | "eve" | "firebender" | "forgecode" | "grok" | "hermes-agent" | "iflow-cli" | "inference-sh" | "jazz" | "junie" | "kilo" | "kimchi" | "kimi-code-cli" | "kode" | "lingma" | "loaf" | "mcpjam" | "minimax-code" | "mistral-vibe" | "moxby" | "mux" | "neovate" | "ona" | "clawdbot" | "openclaw" | "openhands" | "pochi" | "posit-assistant" | "promptscript" | "qoder" | "qoder-cn" | "reasonix" | "replit" | "roo" | "rovodev" | "tabnine-cli" | "terramind" | "tinycloud" | "trae" | "trae-cn" | "warp" | "zcode" | "zencoder" | "zenflow")[] | undefined;
390
391
  selections?: {
391
- source: "vercel-labs/agent-skills" | "vercel/ai" | "vercel/turborepo" | "yusukebe/hono-skill" | "vercel-labs/next-skills" | "nuxt/ui" | "heroui-inc/heroui" | "shadcn/ui" | "better-auth/skills" | "clerk/skills" | "neondatabase/agent-skills" | "supabase/agent-skills" | "planetscale/database-skills" | "expo/skills" | "prisma/skills" | "elysiajs/skills" | "waynesutton/convexskills" | "msmps/opentui-skill" | "haydenbleasel/ultracite" | "https://www.evlog.dev";
392
+ source: "vercel-labs/agent-skills" | "vercel/ai" | "vercel/turborepo" | "yusukebe/hono-skill" | "vercel/next.js" | "nuxt/ui" | "heroui-inc/heroui" | "shadcn/ui" | "better-auth/skills" | "clerk/skills" | "neondatabase/agent-skills" | "supabase/agent-skills" | "planetscale/database-skills" | "expo/skills" | "prisma/skills" | "elysiajs/skills" | "waynesutton/convexskills" | "msmps/opentui-skill" | "haydenbleasel/ultracite" | "https://www.evlog.dev";
392
393
  skills: string[];
393
394
  }[] | undefined;
394
395
  } | undefined;
395
396
  ultracite?: {
396
397
  linter?: "biome" | "oxlint" | undefined;
397
- editors?: ("void" | "cursor" | "vscode" | "zed" | "windsurf" | "antigravity" | "bob" | "codebuddy" | "trae" | "kiro")[] | undefined;
398
- agents?: ("codex" | "opencode" | "pi" | "droid" | "goose" | "cline" | "continue" | "amp" | "aider" | "crush" | "vercel" | "universal" | "adal" | "bob" | "deepagents" | "devin" | "firebender" | "junie" | "mcpjam" | "mistral-vibe" | "mux" | "ona" | "openclaw" | "qoder" | "replit" | "warp" | "zencoder" | "claude" | "jules" | "lovable" | "snowflake-cortex" | "kimi-cli" | "copilot" | "firebase-studio" | "open-hands" | "gemini" | "augmentcode" | "kilo-code" | "roo-code" | "qwen" | "amazon-q-cli" | "cursor-cli")[] | undefined;
398
+ editors?: ("void" | "cursor" | "vscode" | "zed" | "windsurf" | "antigravity" | "universal" | "bob" | "codebuddy" | "trae" | "kiro")[] | undefined;
399
+ agents?: ("codex" | "opencode" | "pi" | "droid" | "goose" | "cline" | "continue" | "amp" | "aider" | "crush" | "vercel" | "kilo-code" | "universal" | "adal" | "bob" | "deepagents" | "devin" | "firebender" | "junie" | "mcpjam" | "mistral-vibe" | "mux" | "ona" | "openclaw" | "qoder" | "replit" | "warp" | "zencoder" | "claude" | "jules" | "lovable" | "snowflake-cortex" | "kimi-cli" | "copilot" | "firebase-studio" | "open-hands" | "gemini" | "augmentcode" | "roo-code" | "qwen" | "amazon-q-cli" | "cursor-cli")[] | undefined;
399
400
  hooks?: ("cursor" | "windsurf" | "codebuddy" | "claude" | "copilot")[] | undefined;
400
401
  } | undefined;
401
402
  } | undefined;
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import { C as DirectoryConflictError, E as ValidationError, S as DatabaseSetupError, T as UserCancelledError, _ as ProjectLauncherSchema, a as TEMPLATE_COUNT, b as CLIError, c as builder, d as createVirtual, f as docs, g as sponsors, h as router, i as SchemaNameSchema, l as create, m as getSchemaResult, n as GeneratorError, o as VirtualFileSystem, p as generate, r as Result, s as add, t as EMBEDDED_TEMPLATES, u as createBtsCli, w as ProjectCreationError, x as CompatibilityError } from "./src-C8JXMrWB.mjs";
2
+ import { C as DirectoryConflictError, E as ValidationError, S as DatabaseSetupError, T as UserCancelledError, _ as ProjectLauncherSchema, a as TEMPLATE_COUNT, b as CLIError, c as builder, d as createVirtual, f as docs, g as sponsors, h as router, i as SchemaNameSchema, l as create, m as getSchemaResult, n as GeneratorError, o as VirtualFileSystem, p as generate, r as Result, s as add, t as EMBEDDED_TEMPLATES, u as createBtsCli, w as ProjectCreationError, x as CompatibilityError } from "./src-C0_lYEa0.mjs";
3
3
  export { CLIError, CompatibilityError, DatabaseSetupError, DirectoryConflictError, EMBEDDED_TEMPLATES, GeneratorError, ProjectCreationError, ProjectLauncherSchema, Result, SchemaNameSchema, TEMPLATE_COUNT, UserCancelledError, ValidationError, VirtualFileSystem, add, builder, create, createBtsCli, createVirtual, docs, generate, getSchemaResult, router, sponsors };
@@ -320,14 +320,153 @@ async function runWithContextAsync(options, fn) {
320
320
  return cliStorage.run(createContext(options), fn);
321
321
  }
322
322
  //#endregion
323
+ //#region src/utils/glyphs.ts
324
+ const unicode = process.platform !== "win32";
325
+ const S_STEP_ACTIVE = unicode ? "◆" : "*";
326
+ const S_STEP_CANCEL = unicode ? "■" : "x";
327
+ const S_STEP_ERROR = unicode ? "▲" : "x";
328
+ const S_STEP_SUBMIT = unicode ? "◇" : "o";
329
+ const S_STEP_BACK = unicode ? "↶" : "<";
330
+ const S_BAR = unicode ? "│" : "|";
331
+ const S_BAR_END = unicode ? "└" : "—";
332
+ const S_RADIO_ACTIVE = unicode ? "●" : ">";
333
+ const S_RADIO_INACTIVE = unicode ? "○" : " ";
334
+ const S_CHECKBOX_ACTIVE = unicode ? "◻" : "[•]";
335
+ const S_CHECKBOX_SELECTED = unicode ? "◼" : "[+]";
336
+ const S_CHECKBOX_INACTIVE = unicode ? "◻" : "[ ]";
337
+ const SPINNER_FRAMES = unicode ? [
338
+ "◒",
339
+ "◐",
340
+ "◓",
341
+ "◑"
342
+ ] : [
343
+ "•",
344
+ "o",
345
+ "O",
346
+ "0"
347
+ ];
348
+ //#endregion
349
+ //#region src/utils/interrupt.ts
350
+ let stepInterrupted = false;
351
+ let anyStepInterrupted = false;
352
+ let controller = null;
353
+ let listener = null;
354
+ /**
355
+ * Once files are on disk, Ctrl-C should only stop the step that is running. Holding a SIGINT
356
+ * listener keeps the CLI alive; the terminal's signal already ends the subprocess. Each
357
+ * optional step starts with startInterruptibleStep() so a cancel applies to that step only.
358
+ */
359
+ function beginInterruptibleScope() {
360
+ endInterruptibleScope();
361
+ stepInterrupted = false;
362
+ anyStepInterrupted = false;
363
+ listener = () => {
364
+ stepInterrupted = true;
365
+ anyStepInterrupted = true;
366
+ controller?.abort();
367
+ };
368
+ process.on("SIGINT", listener);
369
+ }
370
+ function endInterruptibleScope() {
371
+ if (listener) process.off("SIGINT", listener);
372
+ listener = null;
373
+ controller = null;
374
+ }
375
+ function startInterruptibleStep() {
376
+ stepInterrupted = false;
377
+ controller = listener ? new AbortController() : null;
378
+ }
379
+ /** True if the user pressed Ctrl-C during the current step. */
380
+ function wasInterrupted() {
381
+ return stepInterrupted;
382
+ }
383
+ function wasAnyStepInterrupted() {
384
+ return anyStepInterrupted;
385
+ }
386
+ function getInterruptSignal() {
387
+ return controller?.signal;
388
+ }
389
+ //#endregion
323
390
  //#region src/utils/terminal-output.ts
324
391
  const noopSpinner = {
325
392
  start() {},
326
393
  stop() {},
327
394
  message() {}
328
395
  };
396
+ const FRAME_MS = 80;
397
+ const HIDE_CURSOR = "\x1B[?25l";
398
+ const SHOW_CURSOR = "\x1B[?25h";
399
+ const CLEAR_LINE = "\r\x1B[2K";
400
+ let cursorHidden = false;
401
+ let restoreCursorOnExit = false;
402
+ function hideCursor() {
403
+ if (cursorHidden) return;
404
+ cursorHidden = true;
405
+ process.stdout.write(HIDE_CURSOR);
406
+ if (!restoreCursorOnExit) {
407
+ restoreCursorOnExit = true;
408
+ process.once("exit", () => {
409
+ if (cursorHidden) process.stdout.write(SHOW_CURSOR);
410
+ });
411
+ }
412
+ }
413
+ function showCursor() {
414
+ if (!cursorHidden) return;
415
+ cursorHidden = false;
416
+ process.stdout.write(SHOW_CURSOR);
417
+ }
418
+ /**
419
+ * The clack spinner puts stdin in raw mode and exits the process on Ctrl-C from its own
420
+ * keypress handler. This one never touches stdin, so Ctrl-C stays a SIGINT and the
421
+ * interrupt scope decides what happens.
422
+ */
423
+ function createTerminalSpinner() {
424
+ const out = process.stdout;
425
+ const animate = out.isTTY && !process.env.CI;
426
+ let text = "";
427
+ let active = false;
428
+ let interruptedBefore = false;
429
+ let frame = 0;
430
+ let dots = 0;
431
+ let timer;
432
+ const render = () => {
433
+ const suffix = ".".repeat(Math.floor(dots)).slice(0, 3);
434
+ out.write(`${CLEAR_LINE}${pc.magenta(SPINNER_FRAMES[frame])} ${text}${suffix}`);
435
+ frame = (frame + 1) % SPINNER_FRAMES.length;
436
+ dots = dots < 4 ? dots + .125 : 0;
437
+ };
438
+ const setText = (message) => {
439
+ text = message.replace(/\.+$/, "");
440
+ };
441
+ return {
442
+ start(message) {
443
+ if (active) return;
444
+ active = true;
445
+ interruptedBefore = wasInterrupted();
446
+ setText(message);
447
+ out.write(`${pc.gray(S_BAR)}\n`);
448
+ if (animate) {
449
+ hideCursor();
450
+ render();
451
+ timer = setInterval(render, FRAME_MS);
452
+ } else out.write(`${pc.magenta(SPINNER_FRAMES[0])} ${text}\n`);
453
+ },
454
+ message: setText,
455
+ stop(message) {
456
+ if (!active) return;
457
+ active = false;
458
+ if (timer) clearInterval(timer);
459
+ if (animate) {
460
+ out.write(CLEAR_LINE);
461
+ showCursor();
462
+ }
463
+ const cancelled = wasInterrupted() && !interruptedBefore;
464
+ out.write(cancelled ? `${pc.yellow(S_STEP_CANCEL)} ${text} (cancelled)\n` : `${pc.green(S_STEP_SUBMIT)} ${message || text}\n`);
465
+ }
466
+ };
467
+ }
329
468
  function createSpinner() {
330
- return isSilent() ? noopSpinner : spinner();
469
+ return isSilent() ? noopSpinner : createTerminalSpinner();
331
470
  }
332
471
  const baseConsola = createConsola({
333
472
  ...consola.options,
@@ -346,8 +485,9 @@ const cliLog = {
346
485
  success(message) {
347
486
  if (!isSilent()) log.success(message);
348
487
  },
488
+ /** Silent after a Ctrl-C in the current step: the cancelled line already said it. */
349
489
  error(message) {
350
- if (!isSilent()) log.error(message);
490
+ if (!isSilent() && !wasInterrupted()) log.error(message);
351
491
  },
352
492
  message(message) {
353
493
  if (!isSilent()) log.message(message);
@@ -355,7 +495,7 @@ const cliLog = {
355
495
  };
356
496
  const cliConsola = {
357
497
  error(message) {
358
- if (!isSilent()) baseConsola.error(message);
498
+ if (!isSilent() && !wasInterrupted()) baseConsola.error(message);
359
499
  },
360
500
  warn(message) {
361
501
  if (!isSilent()) baseConsola.warn(message);
@@ -931,7 +1071,7 @@ const WEB_FRAMEWORKS = [
931
1071
  ];
932
1072
  //#endregion
933
1073
  //#region src/utils/compatibility-rules.ts
934
- const TASK_RUNNER_ADDONS$1 = [
1074
+ const TASK_RUNNER_ADDONS = [
935
1075
  "turborepo",
936
1076
  "nx",
937
1077
  "vite-plus"
@@ -1178,13 +1318,13 @@ function getCompatibleAddons(allAddons, frontend, existingAddons = [], auth, bac
1178
1318
  return allAddons.filter((addon) => {
1179
1319
  if (existingAddons.includes(addon)) return false;
1180
1320
  if (addon === "none") return false;
1181
- if (TASK_RUNNER_ADDONS$1.includes(addon) && existingAddons.some((existingAddon) => TASK_RUNNER_ADDONS$1.includes(existingAddon))) return false;
1321
+ if (TASK_RUNNER_ADDONS.includes(addon) && existingAddons.some((existingAddon) => TASK_RUNNER_ADDONS.includes(existingAddon))) return false;
1182
1322
  const { isCompatible } = validateAddonCompatibility(addon, frontend, auth, backend, runtime);
1183
1323
  return isCompatible;
1184
1324
  });
1185
1325
  }
1186
1326
  function validateAddonsAgainstFrontends(addons = [], frontends = [], auth, backend, runtime) {
1187
- if (addons.filter((addon) => TASK_RUNNER_ADDONS$1.includes(addon)).length > 1) return validationErr$1("Cannot combine 'turborepo', 'nx', and 'vite-plus' addons. Choose one task runner.");
1327
+ if (addons.filter((addon) => TASK_RUNNER_ADDONS.includes(addon)).length > 1) return validationErr$1("Cannot combine 'turborepo', 'nx', and 'vite-plus' addons. Choose one task runner.");
1188
1328
  for (const addon of addons) {
1189
1329
  if (addon === "none") continue;
1190
1330
  const { isCompatible, reason } = validateAddonCompatibility(addon, frontends, auth, backend, runtime);
@@ -1234,19 +1374,6 @@ function isGoBack(value) {
1234
1374
  }
1235
1375
  //#endregion
1236
1376
  //#region src/prompts/navigable.ts
1237
- const unicode = process.platform !== "win32";
1238
- const S_STEP_ACTIVE = unicode ? "◆" : "*";
1239
- const S_STEP_CANCEL = unicode ? "■" : "x";
1240
- const S_STEP_ERROR = unicode ? "▲" : "x";
1241
- const S_STEP_SUBMIT = unicode ? "◇" : "o";
1242
- const S_STEP_BACK = unicode ? "↶" : "<";
1243
- const S_BAR = unicode ? "│" : "|";
1244
- const S_BAR_END = unicode ? "└" : "—";
1245
- const S_RADIO_ACTIVE = unicode ? "●" : ">";
1246
- const S_RADIO_INACTIVE = unicode ? "○" : " ";
1247
- const S_CHECKBOX_ACTIVE = unicode ? "◻" : "[•]";
1248
- const S_CHECKBOX_SELECTED = unicode ? "◼" : "[+]";
1249
- const S_CHECKBOX_INACTIVE = unicode ? "◻" : "[ ]";
1250
1377
  const promptsNavigatingBack = /* @__PURE__ */ new WeakSet();
1251
1378
  function keycap(label) {
1252
1379
  return pc.inverse(` ${label} `);
@@ -1438,28 +1565,46 @@ async function navigableConfirm(opts) {
1438
1565
  }
1439
1566
  }));
1440
1567
  }
1568
+ /** Keeps at most one value per exclusive set, preferring what was just selected. */
1569
+ function resolveExclusiveSelection(previous, next, exclusive) {
1570
+ let resolved = [...next];
1571
+ for (const set of exclusive) {
1572
+ const added = resolved.find((value) => set.includes(value) && !previous.includes(value));
1573
+ if (added === void 0) continue;
1574
+ resolved = resolved.filter((value) => !set.includes(value) || value === added);
1575
+ }
1576
+ return resolved;
1577
+ }
1441
1578
  async function navigableGroupMultiselect(opts) {
1442
1579
  const required = opts.required ?? true;
1580
+ const exclusive = opts.exclusive ?? [];
1581
+ const exclusiveValues = new Set(exclusive.flat());
1582
+ const exclusiveGroups = new Set(Object.entries(opts.options).filter(([, items]) => exclusive.some((set) => items.length > 1 && items.every((item) => set.includes(item.value)))).map(([group]) => group));
1443
1583
  const opt = (option, state, options = []) => {
1444
- const label = option.label ?? String(option.value);
1445
1584
  const isItem = option.group !== true && option.group !== false;
1585
+ const chooseOne = option.group === true && exclusiveGroups.has(String(option.value));
1586
+ const label = `${option.label ?? String(option.value)}${chooseOne ? pc.dim(" (choose one)") : ""}`;
1446
1587
  const next = isItem && (options[options.indexOf(option) + 1] ?? { group: true });
1447
1588
  const isLast = isItem && next && next.group === true;
1448
1589
  const prefix = isItem ? `${isLast ? S_BAR_END : S_BAR} ` : "";
1449
- if (state === "active") return `${pc.dim(prefix)}${pc.cyan(S_CHECKBOX_ACTIVE)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
1590
+ const radio = isItem && exclusiveValues.has(option.value);
1591
+ const activeBox = radio ? S_RADIO_INACTIVE : S_CHECKBOX_ACTIVE;
1592
+ const selectedBox = radio ? S_RADIO_ACTIVE : S_CHECKBOX_SELECTED;
1593
+ const inactiveBox = radio ? S_RADIO_INACTIVE : S_CHECKBOX_INACTIVE;
1594
+ if (state === "active") return `${pc.dim(prefix)}${pc.cyan(activeBox)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
1450
1595
  if (state === "group-active") return `${prefix}${pc.cyan(S_CHECKBOX_ACTIVE)} ${pc.dim(label)}`;
1451
1596
  if (state === "group-active-selected") return `${prefix}${pc.green(S_CHECKBOX_SELECTED)} ${pc.dim(label)}`;
1452
1597
  if (state === "selected") {
1453
- const selectedCheckbox = isItem ? pc.green(S_CHECKBOX_SELECTED) : "";
1598
+ const selectedCheckbox = isItem ? pc.green(selectedBox) : "";
1454
1599
  return `${pc.dim(prefix)}${selectedCheckbox} ${pc.dim(label)}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
1455
1600
  }
1456
1601
  if (state === "cancelled") return `${pc.strikethrough(pc.dim(label))}`;
1457
- if (state === "active-selected") return `${pc.dim(prefix)}${pc.green(S_CHECKBOX_SELECTED)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
1602
+ if (state === "active-selected") return `${pc.dim(prefix)}${pc.green(selectedBox)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
1458
1603
  if (state === "submitted") return `${pc.dim(label)}`;
1459
- const unselectedCheckbox = isItem ? pc.dim(S_CHECKBOX_INACTIVE) : "";
1604
+ const unselectedCheckbox = isItem ? pc.dim(inactiveBox) : "";
1460
1605
  return `${pc.dim(prefix)}${unselectedCheckbox} ${pc.dim(label)}`;
1461
1606
  };
1462
- return runWithNavigation(new GroupMultiSelectPrompt({
1607
+ const prompt = new GroupMultiSelectPrompt({
1463
1608
  options: opts.options,
1464
1609
  initialValues: opts.initialValues,
1465
1610
  cursorAt: opts.cursorAt,
@@ -1518,7 +1663,15 @@ async function navigableGroupMultiselect(opts) {
1518
1663
  }
1519
1664
  }
1520
1665
  }
1521
- }));
1666
+ });
1667
+ if (exclusive.length > 0) {
1668
+ let previous = [...opts.initialValues ?? []];
1669
+ prompt.on("cursor", (key) => {
1670
+ if (key === "space") prompt.value = resolveExclusiveSelection(previous, prompt.value ?? [], exclusive);
1671
+ previous = [...prompt.value ?? []];
1672
+ });
1673
+ }
1674
+ return runWithNavigation(prompt);
1522
1675
  }
1523
1676
  /** Use the remembered answer as the initial value only while it is still selectable. */
1524
1677
  function preferValidInitial(options, previous, fallback) {
@@ -1657,11 +1810,7 @@ function sortAndPruneGroupedOptions(groupedOptions) {
1657
1810
  });
1658
1811
  }
1659
1812
  function validateAddonSelection(selected) {
1660
- if ((selected?.filter((addon) => [
1661
- "turborepo",
1662
- "nx",
1663
- "vite-plus"
1664
- ].includes(addon)) ?? []).length > 1) return "Choose Turborepo, Nx, or Vite+ as your task runner, not more than one.";
1813
+ if ((selected?.filter((addon) => TASK_RUNNER_ADDONS.includes(addon)) ?? []).length > 1) return "Choose Turborepo, Nx, or Vite+ as your task runner, not more than one.";
1665
1814
  }
1666
1815
  async function getAddonsChoice(addons, frontends, auth, backend, runtime, previousValue) {
1667
1816
  if (addons !== void 0) return addons;
@@ -1684,6 +1833,7 @@ async function getAddonsChoice(addons, frontends, auth, backend, runtime, previo
1684
1833
  options: groupedOptions,
1685
1834
  initialValues: (previousValue ?? DEFAULT_CONFIG.addons).filter((addonValue) => Object.values(groupedOptions).some((options) => options.some((opt) => opt.value === addonValue))),
1686
1835
  required: false,
1836
+ exclusive: [TASK_RUNNER_ADDONS],
1687
1837
  validate: validateAddonSelection
1688
1838
  });
1689
1839
  if (isCancel$1(response)) throw new UserCancelledError({ message: "Operation cancelled" });
@@ -1707,6 +1857,7 @@ async function getAddonsToAdd(config) {
1707
1857
  message: "Select addons to add",
1708
1858
  options: groupedOptions,
1709
1859
  required: false,
1860
+ exclusive: [TASK_RUNNER_ADDONS],
1710
1861
  validate: validateAddonSelection
1711
1862
  });
1712
1863
  if (isCancel$1(response)) throw new UserCancelledError({ message: "Operation cancelled" });
@@ -1939,13 +2090,22 @@ function getNodeToolingRequirements(config) {
1939
2090
  if (config.webDeploy === "cloudflare" || config.serverDeploy === "cloudflare") addNodeRequirement(requirements, ">=22.0.0", "Wrangler 4");
1940
2091
  return requirements;
1941
2092
  }
1942
- function getLocalVersionRequirements(config, hostRuntime) {
1943
- const requirements = [{
1944
- tool: config.packageManager,
1945
- range: PACKAGE_MANAGER_VERSION_RANGES[config.packageManager],
1946
- reason: PACKAGE_MANAGER_REASONS[config.packageManager]
1947
- }];
2093
+ function getHostRuntime() {
2094
+ return process.versions.bun ? "bun" : "node";
2095
+ }
2096
+ /** Requirements that do not depend on the chosen stack, so they can run before any prompt. */
2097
+ function getBaselineRequirements(packageManager, hostRuntime) {
2098
+ const requirements = [];
2099
+ if (packageManager) requirements.push({
2100
+ tool: packageManager,
2101
+ range: PACKAGE_MANAGER_VERSION_RANGES[packageManager],
2102
+ reason: PACKAGE_MANAGER_REASONS[packageManager]
2103
+ });
1948
2104
  if (hostRuntime === "node") addNodeRequirement(requirements, ">=22.0.0", "create-better-t-stack");
2105
+ return requirements;
2106
+ }
2107
+ function getLocalVersionRequirements(config, hostRuntime) {
2108
+ const requirements = getBaselineRequirements(config.packageManager, hostRuntime);
1949
2109
  if (config.packageManager !== "bun") requirements.push(...getNodeToolingRequirements(config));
1950
2110
  else if (config.runtime === "node") addNodeRequirement(requirements, ">=22.0.0", "the selected Node.js server runtime");
1951
2111
  return requirements;
@@ -1959,7 +2119,11 @@ function formatRequirementError(requirement, rawVersion) {
1959
2119
  return null;
1960
2120
  }
1961
2121
  function validateLocalToolVersions(config, versions, hostRuntime) {
1962
- const requirements = getLocalVersionRequirements(config, hostRuntime);
2122
+ return validateRequirements(getLocalVersionRequirements(config, hostRuntime), versions);
2123
+ }
2124
+ const STACK_REQUIREMENTS_HEADLINE = "Your local toolchain does not meet this stack's requirements:";
2125
+ const BASELINE_REQUIREMENTS_HEADLINE = "Your local toolchain does not meet create-better-t-stack's requirements:";
2126
+ function validateRequirements(requirements, versions, headline = STACK_REQUIREMENTS_HEADLINE) {
1963
2127
  const failures = requirements.map((requirement) => formatRequirementError(requirement, versions[requirement.tool])).filter((failure) => failure !== null);
1964
2128
  if (failures.length === 0) return Result.ok(void 0);
1965
2129
  const upgradeInstructions = /* @__PURE__ */ new Set();
@@ -1969,7 +2133,7 @@ function validateLocalToolVersions(config, versions, hostRuntime) {
1969
2133
  else upgradeInstructions.add(PACKAGE_MANAGER_UPGRADE_INSTRUCTIONS[requirement.tool]);
1970
2134
  }
1971
2135
  return Result.err(new CLIError({ message: [
1972
- "Your local toolchain does not meet this stack's requirements:",
2136
+ headline,
1973
2137
  ...failures.map((failure) => `- ${failure}`),
1974
2138
  "",
1975
2139
  ...upgradeInstructions
@@ -1996,12 +2160,35 @@ async function readToolVersion(tool) {
1996
2160
  });
1997
2161
  return result.isOk() ? result.value : null;
1998
2162
  }
1999
- async function checkLocalRequirements(config) {
2000
- const hostRuntime = process.versions.bun ? "bun" : "node";
2163
+ async function readLocalToolVersions(packageManager, hostRuntime, needsNode) {
2001
2164
  const versions = {};
2002
- const packageManagerVersion = await readToolVersion(config.packageManager);
2003
- if (packageManagerVersion) versions[config.packageManager] = packageManagerVersion;
2004
- if (getLocalVersionRequirements(config, hostRuntime).some((requirement) => requirement.tool === "node")) versions.node = hostRuntime === "node" ? process.versions.node : await readToolVersion("node") ?? void 0;
2165
+ if (packageManager) {
2166
+ const packageManagerVersion = await readToolVersion(packageManager);
2167
+ if (packageManagerVersion) versions[packageManager] = packageManagerVersion;
2168
+ }
2169
+ if (needsNode) versions.node = hostRuntime === "node" ? process.versions.node : await readToolVersion("node") ?? void 0;
2170
+ return versions;
2171
+ }
2172
+ /**
2173
+ * Pre-prompt check. An inferred package manager can still be changed at its prompt, so it only
2174
+ * warns; an explicit one and the host Node.js version are fatal.
2175
+ */
2176
+ async function checkBaselineRequirements(packageManager, packageManagerIsExplicit) {
2177
+ if (shouldSkipExternalCommands()) return Result.ok({ warnings: [] });
2178
+ const hostRuntime = getHostRuntime();
2179
+ const requirements = getBaselineRequirements(packageManager, hostRuntime);
2180
+ const versions = await readLocalToolVersions(packageManager, hostRuntime, requirements.some((requirement) => requirement.tool === "node"));
2181
+ const fatal = requirements.filter((requirement) => requirement.tool === "node" || packageManagerIsExplicit);
2182
+ const fatalResult = validateRequirements(fatal, versions, BASELINE_REQUIREMENTS_HEADLINE);
2183
+ if (fatalResult.isErr()) return Result.err(fatalResult.error);
2184
+ const advisoryResult = validateRequirements(requirements.filter((requirement) => !fatal.includes(requirement)), versions, BASELINE_REQUIREMENTS_HEADLINE);
2185
+ if (advisoryResult.isErr()) return Result.ok({ warnings: [`${advisoryResult.error.message}\nYou can also choose a different package manager at the package manager step.`] });
2186
+ return Result.ok({ warnings: [] });
2187
+ }
2188
+ async function checkLocalRequirements(config) {
2189
+ const hostRuntime = getHostRuntime();
2190
+ const versions = await readLocalToolVersions(config.packageManager, hostRuntime, !shouldSkipExternalCommands() && getLocalVersionRequirements(config, hostRuntime).some((requirement) => requirement.tool === "node"));
2191
+ const packageManagerVersion = versions[config.packageManager];
2005
2192
  if (!shouldSkipExternalCommands()) {
2006
2193
  const validationResult = validateLocalToolVersions(config, versions, hostRuntime);
2007
2194
  if (validationResult.isErr()) return Result.err(validationResult.error);
@@ -2034,6 +2221,22 @@ const addPackageDependency = async (opts) => {
2034
2221
  await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
2035
2222
  };
2036
2223
  //#endregion
2224
+ //#region src/utils/optional-step.ts
2225
+ /**
2226
+ * Runs a setup step after the files are on disk. A failure is reported and the run continues;
2227
+ * a cancellation (prompt or Ctrl-C) is only a warning.
2228
+ */
2229
+ async function runOptionalStep(step, cancelledMessage) {
2230
+ startInterruptibleStep();
2231
+ const result = await step();
2232
+ if (result.isOk()) return;
2233
+ if (UserCancelledError.is(result.error) || wasInterrupted()) {
2234
+ cliLog.warn(pc.yellow(cancelledMessage));
2235
+ return;
2236
+ }
2237
+ cliConsola.error(pc.red(result.error.message));
2238
+ }
2239
+ //#endregion
2037
2240
  //#region src/helpers/addons/evlog-setup.ts
2038
2241
  const evlogBackends = [
2039
2242
  "hono",
@@ -2871,7 +3074,7 @@ const TEMPLATES$2 = {
2871
3074
  const DEFAULT_TEMPLATE$2 = "next-mdx";
2872
3075
  const DEFAULT_DEV_PORT$1 = 4e3;
2873
3076
  function aiChatDisabledForTemplate(template) {
2874
- return template === "next-mdx-static" || template.endsWith("-spa");
3077
+ return template === "next-mdx-static" || template === "astro" || template.endsWith("-spa");
2875
3078
  }
2876
3079
  function getFumadocsLinter(addons) {
2877
3080
  if (addons.includes("oxlint")) return "oxlint";
@@ -3115,6 +3318,26 @@ const MCP_AGENTS = [
3115
3318
  value: "goose",
3116
3319
  label: "Goose",
3117
3320
  scope: "global"
3321
+ },
3322
+ {
3323
+ value: "fx",
3324
+ label: "fx",
3325
+ scope: "global"
3326
+ },
3327
+ {
3328
+ value: "kilo-code",
3329
+ label: "Kilo Code",
3330
+ scope: "both"
3331
+ },
3332
+ {
3333
+ value: "kimi-code",
3334
+ label: "Kimi Code",
3335
+ scope: "both"
3336
+ },
3337
+ {
3338
+ value: "kiro-cli",
3339
+ label: "Kiro CLI",
3340
+ scope: "both"
3118
3341
  }
3119
3342
  ];
3120
3343
  const DEFAULT_SCOPE$1 = "project";
@@ -3156,8 +3379,7 @@ function getAllMcpServers(config) {
3156
3379
  key: "cloudflare-docs",
3157
3380
  label: "Cloudflare Docs",
3158
3381
  name: "cloudflare-docs",
3159
- target: "https://docs.mcp.cloudflare.com/sse",
3160
- transport: "sse"
3382
+ target: "https://docs.mcp.cloudflare.com/mcp"
3161
3383
  },
3162
3384
  {
3163
3385
  key: "convex",
@@ -3223,7 +3445,7 @@ function getAllMcpServers(config) {
3223
3445
  key: "better-auth",
3224
3446
  label: "Better Auth",
3225
3447
  name: "better-auth",
3226
- target: "https://mcp.inkeep.com/better-auth/mcp"
3448
+ target: "https://mcp.better-auth.com/mcp"
3227
3449
  },
3228
3450
  {
3229
3451
  key: "clerk",
@@ -3457,7 +3679,7 @@ const SKILL_SOURCES = {
3457
3679
  "vercel/ai": { label: "Vercel AI SDK" },
3458
3680
  "vercel/turborepo": { label: "Turborepo" },
3459
3681
  "yusukebe/hono-skill": { label: "Hono Backend" },
3460
- "vercel-labs/next-skills": { label: "Next.js Best Practices" },
3682
+ "vercel/next.js": { label: "Next.js" },
3461
3683
  "nuxt/ui": { label: "Nuxt UI" },
3462
3684
  "heroui-inc/heroui": { label: "HeroUI Native" },
3463
3685
  "shadcn/ui": { label: "shadcn/ui" },
@@ -3655,6 +3877,10 @@ const SKILLS_CLI_AGENT_OPTIONS = [
3655
3877
  value: "mcpjam",
3656
3878
  label: "MCPJam"
3657
3879
  },
3880
+ {
3881
+ value: "minimax-code",
3882
+ label: "MiniMax Code"
3883
+ },
3658
3884
  {
3659
3885
  value: "mistral-vibe",
3660
3886
  label: "Mistral Vibe"
@@ -3695,6 +3921,10 @@ const SKILLS_CLI_AGENT_OPTIONS = [
3695
3921
  value: "pochi",
3696
3922
  label: "Pochi"
3697
3923
  },
3924
+ {
3925
+ value: "posit-assistant",
3926
+ label: "Posit Assistant"
3927
+ },
3698
3928
  {
3699
3929
  value: "promptscript",
3700
3930
  label: "PromptScript"
@@ -3834,7 +4064,7 @@ function getRecommendedSourceKeys(config) {
3834
4064
  sources.push("shadcn/ui");
3835
4065
  }
3836
4066
  if ((webDeploy === "vercel" || serverDeploy === "vercel") && !sources.includes("vercel-labs/agent-skills")) sources.push("vercel-labs/agent-skills");
3837
- if (frontend.includes("next")) sources.push("vercel-labs/next-skills");
4067
+ if (frontend.includes("next")) sources.push("vercel/next.js");
3838
4068
  if (frontend.includes("nuxt")) sources.push("nuxt/ui");
3839
4069
  if (frontend.includes("native-uniwind")) sources.push("heroui-inc/heroui");
3840
4070
  if (hasNativeFrontend(frontend)) sources.push("expo/skills");
@@ -3865,11 +4095,20 @@ const CURATED_SKILLS_BY_SOURCE = {
3865
4095
  "vercel/ai": () => ["ai-sdk"],
3866
4096
  "vercel/turborepo": () => ["turborepo"],
3867
4097
  "yusukebe/hono-skill": () => ["hono"],
3868
- "vercel-labs/next-skills": () => ["next-best-practices", "next-cache-components"],
4098
+ "vercel/next.js": () => [
4099
+ "next-dev-loop",
4100
+ "next-cache-components-adoption",
4101
+ "next-cache-components-optimizer",
4102
+ "next-partial-prefetching-adoption"
4103
+ ],
3869
4104
  "nuxt/ui": () => ["nuxt-ui"],
3870
4105
  "heroui-inc/heroui": () => ["heroui-native"],
3871
4106
  "shadcn/ui": () => ["shadcn"],
3872
- "better-auth/skills": () => ["better-auth-best-practices"],
4107
+ "better-auth/skills": () => [
4108
+ "better-auth-best-practices",
4109
+ "better-auth-security-best-practices",
4110
+ "email-and-password-best-practices"
4111
+ ],
3873
4112
  "clerk/skills": (config) => {
3874
4113
  const skills = [
3875
4114
  "clerk",
@@ -3880,6 +4119,12 @@ const CURATED_SKILLS_BY_SOURCE = {
3880
4119
  "clerk-orgs"
3881
4120
  ];
3882
4121
  if (config.frontend.includes("next")) skills.push("clerk-nextjs-patterns");
4122
+ if (config.frontend.includes("react-router")) skills.push("clerk-react-router-patterns");
4123
+ if (config.frontend.includes("tanstack-start")) skills.push("clerk-tanstack-patterns");
4124
+ if (config.frontend.includes("tanstack-router")) skills.push("clerk-react-patterns");
4125
+ if (config.frontend.includes("nuxt")) skills.push("clerk-nuxt-patterns");
4126
+ if (config.frontend.includes("astro")) skills.push("clerk-astro-patterns");
4127
+ if (hasNativeFrontend(config.frontend)) skills.push("clerk-expo");
3883
4128
  return skills;
3884
4129
  },
3885
4130
  "neondatabase/agent-skills": () => ["neon-postgres"],
@@ -3892,11 +4137,13 @@ const CURATED_SKILLS_BY_SOURCE = {
3892
4137
  },
3893
4138
  "expo/skills": (config) => {
3894
4139
  const skills = [
4140
+ "expo-overview",
4141
+ "expo-router",
3895
4142
  "expo-dev-client",
3896
- "building-native-ui",
3897
- "native-data-fetching",
3898
- "expo-deployment",
3899
- "expo-cicd-workflows"
4143
+ "expo-native-ui",
4144
+ "expo-data-fetching",
4145
+ "eas-app-stores",
4146
+ "eas-workflows"
3900
4147
  ];
3901
4148
  if (config.frontend.includes("native-uniwind")) skills.push("expo-tailwind-setup");
3902
4149
  return skills;
@@ -4068,7 +4315,8 @@ async function setupStarlight(config) {
4068
4315
  "--yes",
4069
4316
  "--no-install",
4070
4317
  "--no-git",
4071
- "--skip-houston"
4318
+ "--skip-houston",
4319
+ "--no-ai"
4072
4320
  ].join(" ")}`);
4073
4321
  const appsDir = path.join(projectDir, "apps");
4074
4322
  await fs.ensureDir(appsDir);
@@ -4349,7 +4597,7 @@ const HOOKS = {
4349
4597
  claude: { label: "Claude Code" },
4350
4598
  copilot: { label: "GitHub Copilot" }
4351
4599
  };
4352
- const ULTRACITE_VERSION = "7.9.4";
4600
+ const ULTRACITE_VERSION = "7.10.7";
4353
4601
  const DEFAULT_LINTER = "biome";
4354
4602
  const DEFAULT_EDITORS = ["vscode"];
4355
4603
  const DEFAULT_AGENTS = ["universal"];
@@ -4432,13 +4680,20 @@ async function setupUltracite(config, gitHooks) {
4432
4680
  return navigableMultiselect({
4433
4681
  message: "Which editors do you want to configure (recommended)?",
4434
4682
  required: false,
4435
- options: [{
4436
- value: "vscode",
4437
- label: "VSCode / Cursor / Windsurf"
4438
- }, {
4439
- value: "zed",
4440
- label: "Zed"
4441
- }]
4683
+ options: [
4684
+ {
4685
+ value: "vscode",
4686
+ label: "VSCode / Cursor / Windsurf"
4687
+ },
4688
+ {
4689
+ value: "zed",
4690
+ label: "Zed"
4691
+ },
4692
+ {
4693
+ value: "universal",
4694
+ label: "Universal (.vscode/settings.json)"
4695
+ }
4696
+ ]
4442
4697
  });
4443
4698
  },
4444
4699
  agents: async () => {
@@ -4612,24 +4867,15 @@ async function setupWxt(config) {
4612
4867
  }
4613
4868
  //#endregion
4614
4869
  //#region src/helpers/addons/addons-setup.ts
4615
- async function runSetup(setupFn) {
4616
- const result = await setupFn();
4617
- if (result.isErr()) {
4618
- if (UserCancelledError.is(result.error)) throw result.error;
4619
- cliConsola.error(pc.red(result.error.message));
4620
- }
4621
- }
4622
- async function runAddonStep(addon, step) {
4623
- const result = await Result.tryPromise({
4624
- try: async () => step(),
4625
- catch: (e) => new AddonSetupError({
4626
- addon,
4627
- message: `Failed to set up ${addon}: ${e instanceof Error ? e.message : String(e)}`,
4628
- cause: e
4629
- })
4630
- });
4631
- if (result.isErr()) cliConsola.error(pc.red(result.error.message));
4632
- }
4870
+ const runSetup = (setupFn) => runOptionalStep(setupFn, "Addon setup cancelled.");
4871
+ const runAddonStep = (addon, step) => runOptionalStep(() => Result.tryPromise({
4872
+ try: async () => step(),
4873
+ catch: (e) => new AddonSetupError({
4874
+ addon,
4875
+ message: `Failed to set up ${addon}: ${e instanceof Error ? e.message : String(e)}`,
4876
+ cause: e
4877
+ })
4878
+ }), `${addon} setup cancelled.`);
4633
4879
  async function setupAddons(config) {
4634
4880
  const { addons, frontend, projectDir } = config;
4635
4881
  const hasWebFrontend = frontend.some((value) => types_exports.desktopWebFrontends.includes(value));
@@ -4739,15 +4985,19 @@ async function detectProjectConfig(projectDir) {
4739
4985
  }
4740
4986
  //#endregion
4741
4987
  //#region src/helpers/core/install-dependencies.ts
4988
+ const FORCE_KILL_AFTER_MS = 2e3;
4742
4989
  async function installDependencies({ projectDir, packageManager }) {
4743
- if (shouldSkipExternalCommands()) return Result.ok(void 0);
4990
+ if (shouldSkipExternalCommands()) return Result.ok("installed");
4991
+ startInterruptibleStep();
4744
4992
  const s = createSpinner();
4745
4993
  s.start(`Running ${packageManager} install...`);
4746
4994
  const result = await Result.tryPromise({
4747
4995
  try: async () => {
4748
4996
  await $({
4749
4997
  cwd: projectDir,
4750
- stderr: "inherit"
4998
+ stderr: "inherit",
4999
+ cancelSignal: getInterruptSignal(),
5000
+ forceKillAfterDelay: FORCE_KILL_AFTER_MS
4751
5001
  })`${packageManager} install`;
4752
5002
  },
4753
5003
  catch: (e) => new ProjectCreationError({
@@ -4756,9 +5006,16 @@ async function installDependencies({ projectDir, packageManager }) {
4756
5006
  cause: e
4757
5007
  })
4758
5008
  });
4759
- if (result.isOk()) s.stop("Dependencies installed");
4760
- else s.stop(pc.red("Failed to install dependencies"));
4761
- return result;
5009
+ if (wasInterrupted()) {
5010
+ s.stop();
5011
+ return Result.ok("cancelled");
5012
+ }
5013
+ if (result.isOk()) {
5014
+ s.stop("Dependencies installed");
5015
+ return Result.ok("installed");
5016
+ }
5017
+ s.stop(pc.red("Failed to install dependencies"));
5018
+ return Result.err(result.error);
4762
5019
  }
4763
5020
  //#endregion
4764
5021
  //#region src/helpers/core/add-handler.ts
@@ -4786,11 +5043,6 @@ const HOOK_LINTER_ADDONS = [
4786
5043
  "oxlint",
4787
5044
  "vite-plus"
4788
5045
  ];
4789
- const TASK_RUNNER_ADDONS = [
4790
- "turborepo",
4791
- "nx",
4792
- "vite-plus"
4793
- ];
4794
5046
  const fileExistsErrorSchema = z.object({ code: z.literal("EEXIST") });
4795
5047
  const configPackageScopeSchema = z.object({ name: z.string().regex(/^@[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?\/config$/) }).transform(({ name }) => name.slice(0, -7));
4796
5048
  const rootTypescriptVersionSchema = z.object({ devDependencies: z.object({ typescript: z.string().min(1) }) }).transform(({ devDependencies }) => devDependencies.typescript);
@@ -4903,7 +5155,12 @@ async function addHandler(input, options = {}) {
4903
5155
  mode,
4904
5156
  analyticsDisabled: input.disableAnalytics
4905
5157
  }, async () => {
4906
- const result = await addHandlerInternal(input);
5158
+ let result;
5159
+ try {
5160
+ result = await addHandlerInternal(input);
5161
+ } finally {
5162
+ endInterruptibleScope();
5163
+ }
4907
5164
  await reportAddOutcome(input, result);
4908
5165
  if (result.isOk()) return result.value;
4909
5166
  const error = result.error;
@@ -5106,6 +5363,7 @@ async function addHandlerInternal(input) {
5106
5363
  return Result.err(new CLIError({ message: `Failed to write files: ${writeResult.error.message}` }));
5107
5364
  }
5108
5365
  if (vfs.getFileCount() > 0 && !isSilent()) log.info(pc.dim(`Wrote ${vfs.getFileCount()} files`));
5366
+ beginInterruptibleScope();
5109
5367
  const setupResult = await Result.tryPromise({
5110
5368
  try: async () => {
5111
5369
  if (addonsToAdd.length > 0) await setupAddons({
@@ -6839,7 +7097,9 @@ const PROJECT_LAUNCHERS = [
6839
7097
  }]
6840
7098
  }
6841
7099
  ];
6842
- async function detectProjectLaunchers(projectDir, detectCommand = commandExists, platform = process.platform) {
7100
+ async function detectProjectLaunchers(projectDir, options = {}) {
7101
+ const detectCommand = options.detectCommand ?? commandExists;
7102
+ const platform = options.platform ?? process.platform;
6843
7103
  const supportedDefinitions = PROJECT_LAUNCHERS.filter(({ platforms }) => platforms === void 0 || platforms.includes(platform));
6844
7104
  const commandsFor = (launcher) => launcher.commandsByPlatform?.[platform] ?? launcher.commands;
6845
7105
  const commands = [...new Set(supportedDefinitions.flatMap(commandsFor))];
@@ -6862,7 +7122,8 @@ async function detectProjectLaunchers(projectDir, detectCommand = commandExists,
6862
7122
  async function getProjectLauncherChoice(requestedLauncher, projectDir, options = {}) {
6863
7123
  if (requestedLauncher === "none") return Result.ok(void 0);
6864
7124
  if (!requestedLauncher && options.prompt === false) return Result.ok(void 0);
6865
- const availableLaunchers = await detectProjectLaunchers(projectDir, options.detectCommand, options.platform);
7125
+ const { prompt: _prompt, ...detectOptions } = options;
7126
+ const availableLaunchers = await detectProjectLaunchers(projectDir, detectOptions);
6866
7127
  if (requestedLauncher) {
6867
7128
  const launcher = availableLaunchers.find(({ id }) => id === requestedLauncher);
6868
7129
  if (launcher) return Result.ok(launcher);
@@ -7150,12 +7411,14 @@ async function formatCode(filePath, content) {
7150
7411
  });
7151
7412
  return result.isOk() ? result.value : null;
7152
7413
  }
7153
- async function formatProject(projectDir) {
7414
+ async function formatProject(projectDir, signal) {
7154
7415
  return Result.tryPromise({
7155
7416
  try: async () => {
7156
7417
  async function formatDirectory(dir) {
7418
+ if (signal?.aborted) return;
7157
7419
  const entries = await fs.readdir(dir, { withFileTypes: true });
7158
7420
  await Promise.all(entries.map(async (entry) => {
7421
+ if (signal?.aborted) return;
7159
7422
  const fullPath = path.join(dir, entry.name);
7160
7423
  if (entry.isDirectory()) await formatDirectory(fullPath);
7161
7424
  else if (entry.isFile()) await Result.tryPromise({
@@ -7957,8 +8220,19 @@ async function writeSupabaseEnvFile(projectDir, backend, databaseUrl) {
7957
8220
  })
7958
8221
  });
7959
8222
  }
7960
- function extractDbUrl(output) {
7961
- return output.match(/DB URL:\s*(postgresql:\/\/[^\s]+)/)?.[1] ?? null;
8223
+ function extractDbUrl(statusEnv) {
8224
+ return statusEnv.match(/^DB_URL="(postgresql:\/\/[^"]+)"$/m)?.[1] ?? null;
8225
+ }
8226
+ async function readSupabaseStatus(serverDir, packageManager) {
8227
+ const statusArgs = getPackageExecutionArgs(packageManager, "supabase status --agent no -o env");
8228
+ const result = await Result.tryPromise({
8229
+ try: async () => {
8230
+ const { stdout } = await execa(statusArgs[0], statusArgs.slice(1), { cwd: serverDir });
8231
+ return stdout;
8232
+ },
8233
+ catch: () => ""
8234
+ });
8235
+ return result.isOk() ? result.value : "";
7962
8236
  }
7963
8237
  async function initializeSupabase(serverDir, packageManager) {
7964
8238
  cliLog.info("Initializing Supabase project...");
@@ -7983,7 +8257,7 @@ async function initializeSupabase(serverDir, packageManager) {
7983
8257
  }
7984
8258
  async function startSupabase(serverDir, packageManager) {
7985
8259
  cliLog.info("Starting Supabase services (this may take a moment)...");
7986
- const supabaseStartArgs = getPackageExecutionArgs(packageManager, "supabase start");
8260
+ const supabaseStartArgs = getPackageExecutionArgs(packageManager, "supabase start --agent no");
7987
8261
  return Result.tryPromise({
7988
8262
  try: async () => {
7989
8263
  const subprocess = execa(supabaseStartArgs[0], supabaseStartArgs.slice(1), { cwd: serverDir });
@@ -8014,10 +8288,10 @@ function displayManualSupabaseInstructions(targetApp, output) {
8014
8288
  2. Install the Supabase CLI (e.g., \`npm install -g supabase\`).
8015
8289
  3. Run \`supabase init\` in your project's \`packages/db\` directory.
8016
8290
  4. Run \`supabase start\` in your project's \`packages/db\` directory.
8017
- 5. Copy the 'DB URL' from the output.${output ? `
8291
+ 5. Copy the database URL from the output.${output ? `
8018
8292
  ${pc.bold("Relevant output from `supabase start`:")}
8019
8293
  ${pc.dim(output)}` : ""}
8020
- 6. Add the DB URL to the .env file in \`${targetApp}/.env\` as \`DATABASE_URL\`:
8294
+ 6. Add the database URL to the .env file in \`${targetApp}/.env\` as \`DATABASE_URL\`:
8021
8295
  ${pc.gray("DATABASE_URL=\"your_supabase_db_url\"")}`);
8022
8296
  }
8023
8297
  async function setupSupabase(config, cliInput) {
@@ -8077,7 +8351,7 @@ async function setupSupabase(config, cliInput) {
8077
8351
  return writeSupabaseEnvFile(projectDir, backend, "");
8078
8352
  }
8079
8353
  const supabaseOutput = startResult.value;
8080
- const dbUrl = extractDbUrl(supabaseOutput);
8354
+ const dbUrl = extractDbUrl(await readSupabaseStatus(serverDir, packageManager));
8081
8355
  if (dbUrl) {
8082
8356
  const envResult = await writeSupabaseEnvFile(projectDir, backend, dbUrl);
8083
8357
  if (envResult.isOk()) cliLog.success(pc.green("Supabase local development setup ready!"));
@@ -8206,7 +8480,7 @@ async function createTursoDatabase(dbName, groupName) {
8206
8480
  catch: (e) => {
8207
8481
  const error = e;
8208
8482
  s.stop(pc.red(`Failed to create database "${dbName}"`));
8209
- if (error.message?.includes("already exists")) return new DatabaseSetupError({
8483
+ if (error.message?.includes("already exists") || error.message?.includes("is not available")) return new DatabaseSetupError({
8210
8484
  provider: "turso",
8211
8485
  message: "DATABASE_EXISTS",
8212
8486
  cause: e
@@ -8437,13 +8711,7 @@ async function setupDatabase(config, cliInput) {
8437
8711
  const dbPackageDir = path.join(projectDir, "packages/db");
8438
8712
  if (!await fs.pathExists(dbPackageDir)) return;
8439
8713
  if (usesAlchemyManagedDatabase(config)) return;
8440
- async function runSetup(setupFn) {
8441
- const result = await setupFn();
8442
- if (result.isErr()) {
8443
- if (UserCancelledError.is(result.error)) throw result.error;
8444
- cliConsola.error(pc.red(result.error.message));
8445
- }
8446
- }
8714
+ const runSetup = (setupFn) => runOptionalStep(setupFn, "Database setup cancelled. Configure the connection manually.");
8447
8715
  const resolvedCliInput = {
8448
8716
  ...cliInput,
8449
8717
  dbSetupOptions: mergeResolvedDbSetupOptions(dbSetup, config.dbSetupOptions, cliInput)
@@ -8461,29 +8729,44 @@ async function setupDatabase(config, cliInput) {
8461
8729
  }
8462
8730
  //#endregion
8463
8731
  //#region src/helpers/core/git.ts
8464
- async function initializeGit(projectDir, useGit) {
8732
+ async function initializeGit(projectDir, useGit, signal) {
8465
8733
  if (!useGit) return Result.ok(void 0);
8466
- if ((await $({
8734
+ const cancelled = () => Result.err(new ProjectCreationError({
8735
+ phase: "git-initialization",
8736
+ message: "Git initialization cancelled"
8737
+ }));
8738
+ const gitVersionResult = await $({
8467
8739
  cwd: projectDir,
8468
8740
  reject: false,
8469
- stderr: "pipe"
8470
- })`git --version`).exitCode !== 0) {
8741
+ stderr: "pipe",
8742
+ cancelSignal: signal
8743
+ })`git --version`;
8744
+ if (signal?.aborted) return cancelled();
8745
+ if (gitVersionResult.exitCode !== 0) {
8471
8746
  cliLog.warn(pc.yellow("Git is not installed"));
8472
8747
  return Result.ok(void 0);
8473
8748
  }
8474
8749
  const result = await $({
8475
8750
  cwd: projectDir,
8476
8751
  reject: false,
8477
- stderr: "pipe"
8752
+ stderr: "pipe",
8753
+ cancelSignal: signal
8478
8754
  })`git init`;
8755
+ if (signal?.aborted) return cancelled();
8479
8756
  if (result.exitCode !== 0) return Result.err(new ProjectCreationError({
8480
8757
  phase: "git-initialization",
8481
8758
  message: `Git initialization failed: ${result.stderr}`
8482
8759
  }));
8483
8760
  return Result.tryPromise({
8484
8761
  try: async () => {
8485
- await $({ cwd: projectDir })`git add -A`;
8486
- await $({ cwd: projectDir })`git commit -m ${"initial commit"}`;
8762
+ await $({
8763
+ cwd: projectDir,
8764
+ cancelSignal: signal
8765
+ })`git add -A`;
8766
+ await $({
8767
+ cwd: projectDir,
8768
+ cancelSignal: signal
8769
+ })`git commit -m ${"initial commit"}`;
8487
8770
  },
8488
8771
  catch: (e) => new ProjectCreationError({
8489
8772
  phase: "git-initialization",
@@ -8791,7 +9074,7 @@ function getTauriInstructions(runCmd, frontend) {
8791
9074
  }
8792
9075
  function getElectrobunInstructions(runCmd, frontend) {
8793
9076
  const staticBuildNote = getDesktopStaticBuildNote(frontend);
8794
- return `\n${pc.bold("Desktop app with Electrobun:")}\n${pc.cyan("•")} Start desktop app with HMR: ${`${runCmd} dev:desktop`}\n${pc.cyan("•")} Build stable desktop app (DMG/App): ${`${runCmd} build:desktop`}\n${pc.cyan("•")} Build canary desktop app: ${`${runCmd} build:desktop:canary`}\n${pc.yellow("NOTE:")} Electrobun wraps your web frontend in a desktop shell.\n See: https://framework.blackboard.sh/electrobun/${staticBuildNote ? `\n${staticBuildNote}` : ""}`;
9077
+ return `\n${pc.bold("Desktop app with Electrobun:")}\n${pc.cyan("•")} Start desktop app with HMR (runs the whole stack): ${`${runCmd} dev:desktop`}\n${pc.cyan("•")} Build stable desktop app (DMG/App): ${`${runCmd} build:desktop`}\n${pc.cyan("•")} Build canary desktop app: ${`${runCmd} build:desktop:canary`}\n${pc.yellow("NOTE:")} Electrobun wraps your web frontend in a desktop shell.\n The first desktop command downloads Hutch and the Electrobun toolchain.\n Packaged builds bake the API URL from apps/web/.env, so point it at a reachable server first.\n See: https://framework.blackboard.sh/electrobun/${staticBuildNote ? `\n${staticBuildNote}` : ""}`;
8795
9078
  }
8796
9079
  function getPwaInstructions() {
8797
9080
  return `\n${pc.bold("PWA with React Router v7:")}\n${pc.yellow("NOTE:")} There is a known compatibility issue between VitePWA\n and React Router v7. See:\n https://github.com/vite-pwa/vite-plugin-pwa/issues/809`;
@@ -8910,7 +9193,8 @@ function getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy, backend,
8910
9193
  //#region src/helpers/core/create-project.ts
8911
9194
  /**
8912
9195
  * Creates a new project with the given configuration.
8913
- * Returns a Result with the project directory path on success, or an error on failure.
9196
+ * A failed dependency install is returned in the outcome instead of failing, since the project
9197
+ * is already on disk by then.
8914
9198
  */
8915
9199
  async function createProject(options, cliInput) {
8916
9200
  return Result.gen(async function* () {
@@ -8939,34 +9223,57 @@ async function createProject(options, cliInput) {
8939
9223
  cause: e
8940
9224
  }))));
8941
9225
  yield* Result.await(setPackageManagerVersion(projectDir, options.packageManager, cliInput.packageManagerVersion));
8942
- if (!isConvex && options.database !== "none") yield* Result.await(Result.tryPromise({
8943
- try: () => setupDatabase(options, cliInput),
8944
- catch: (e) => new ProjectCreationError({
8945
- phase: "database-setup",
8946
- message: `Failed to setup database: ${e instanceof Error ? e.message : String(e)}`,
8947
- cause: e
8948
- })
8949
- }));
8950
- if (options.addons.length > 0 && options.addons[0] !== "none") yield* Result.await(Result.tryPromise({
8951
- try: () => setupAddons(options),
8952
- catch: (e) => new ProjectCreationError({
8953
- phase: "addons-setup",
8954
- message: `Failed to setup addons: ${e instanceof Error ? e.message : String(e)}`,
8955
- cause: e
8956
- })
8957
- }));
8958
- yield* Result.await(formatProject(projectDir));
8959
- if (!isSilent()) log.success("Project scaffolded");
8960
- if (options.install) yield* Result.await(installDependencies({
9226
+ beginInterruptibleScope();
9227
+ try {
9228
+ return yield* runPostScaffoldSteps(options, cliInput, projectDir, isConvex);
9229
+ } finally {
9230
+ endInterruptibleScope();
9231
+ }
9232
+ });
9233
+ }
9234
+ async function* runPostScaffoldSteps(options, cliInput, projectDir, isConvex) {
9235
+ if (!isConvex && options.database !== "none") yield* Result.await(Result.tryPromise({
9236
+ try: () => setupDatabase(options, cliInput),
9237
+ catch: (e) => new ProjectCreationError({
9238
+ phase: "database-setup",
9239
+ message: `Failed to setup database: ${e instanceof Error ? e.message : String(e)}`,
9240
+ cause: e
9241
+ })
9242
+ }));
9243
+ if (options.addons.length > 0 && options.addons[0] !== "none") yield* Result.await(Result.tryPromise({
9244
+ try: () => setupAddons(options),
9245
+ catch: (e) => new ProjectCreationError({
9246
+ phase: "addons-setup",
9247
+ message: `Failed to setup addons: ${e instanceof Error ? e.message : String(e)}`,
9248
+ cause: e
9249
+ })
9250
+ }));
9251
+ startInterruptibleStep();
9252
+ yield* Result.await(formatProject(projectDir, getInterruptSignal()));
9253
+ if (wasInterrupted()) cliLog.warn(pc.yellow("Formatting cancelled."));
9254
+ if (!isSilent()) log.success("Project scaffolded");
9255
+ let install = "skipped";
9256
+ let installError = null;
9257
+ if (options.install) {
9258
+ const installResult = await installDependencies({
8961
9259
  projectDir,
8962
9260
  packageManager: options.packageManager
8963
- }));
8964
- yield* Result.await(initializeGit(projectDir, options.git));
8965
- if (!isSilent()) await displayPostInstallInstructions({
8966
- ...options,
8967
- depsInstalled: options.install
8968
9261
  });
8969
- return Result.ok(projectDir);
9262
+ if (installResult.isErr()) {
9263
+ install = "failed";
9264
+ installError = installResult.error;
9265
+ } else install = installResult.value;
9266
+ }
9267
+ await runOptionalStep(() => initializeGit(projectDir, options.git, getInterruptSignal()), "Git initialization cancelled.");
9268
+ if (!isSilent()) await displayPostInstallInstructions({
9269
+ ...options,
9270
+ depsInstalled: install === "installed"
9271
+ });
9272
+ return Result.ok({
9273
+ projectDir,
9274
+ install,
9275
+ installError,
9276
+ interrupted: wasAnyStepInterrupted()
8970
9277
  });
8971
9278
  }
8972
9279
  async function setPackageManagerVersion(projectDir, packageManager, version) {
@@ -9077,6 +9384,10 @@ async function createProjectHandlerInternal(input, startTime, timeScaffolded) {
9077
9384
  if (!isSilent() && input.renderTitle !== false) renderTitle();
9078
9385
  if (!isSilent()) intro(pc.magenta("Configure your new project"));
9079
9386
  if (!isSilent() && input.yolo) log.warn(pc.yellow("YOLO mode enabled — compatibility checks are disabled."));
9387
+ if (!isSilent()) {
9388
+ const baseline = yield* Result.await(checkBaselineRequirements(input.packageManager ?? getUserPkgManager(), input.packageManager !== void 0));
9389
+ for (const warning of baseline.warnings) log.warn(pc.yellow(warning));
9390
+ }
9080
9391
  let currentPathInput;
9081
9392
  if (isSilent()) currentPathInput = yield* Result.await(resolveProjectNameForSilent(input));
9082
9393
  else if (input.yes && input.projectName) currentPathInput = input.projectName;
@@ -9222,12 +9533,27 @@ async function createProjectHandlerInternal(input, startTime, timeScaffolded) {
9222
9533
  relativePath: config.relativePath
9223
9534
  });
9224
9535
  }
9225
- yield* Result.await(createProject(config, {
9536
+ const created = yield* Result.await(createProject(config, {
9226
9537
  manualDb: cliInput.manualDb ?? input.manualDb,
9227
9538
  dbSetupOptions: effectiveDbSetupOptions,
9228
9539
  packageManagerVersion: localRequirements.packageManagerVersion
9229
9540
  }));
9230
- await trackProjectCreation(config, input.disableAnalytics, resolveInvocationMode(input.yes));
9541
+ const mode = resolveInvocationMode(input.yes);
9542
+ await trackProjectCreation(config, input.disableAnalytics, mode);
9543
+ const warnings = [];
9544
+ if (created.interrupted) warnings.push("One or more setup steps were cancelled.");
9545
+ if (created.installError) {
9546
+ warnings.push(created.installError.message);
9547
+ await reportDiagnostic("cli_failed", {
9548
+ command: "create",
9549
+ mode,
9550
+ stage: "dependency-installation",
9551
+ error: errorClass(created.installError),
9552
+ reason: scrubReason(created.installError),
9553
+ packageManager: config.packageManager,
9554
+ backend: config.backend
9555
+ });
9556
+ }
9231
9557
  const historyResult = await addToHistory(config, reproducibleCommand);
9232
9558
  if (historyResult.isErr() && !isSilent()) {
9233
9559
  log.warn(pc.yellow(historyResult.error.message));
@@ -9258,7 +9584,8 @@ async function createProjectHandlerInternal(input, startTime, timeScaffolded) {
9258
9584
  timeScaffolded,
9259
9585
  elapsedTimeMs,
9260
9586
  projectDirectory: config.projectDir,
9261
- relativePath: config.relativePath
9587
+ relativePath: config.relativePath,
9588
+ warnings: warnings.length > 0 ? warnings : void 0
9262
9589
  });
9263
9590
  });
9264
9591
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-better-t-stack",
3
- "version": "3.41.4",
3
+ "version": "3.42.1",
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",
@@ -69,8 +69,8 @@
69
69
  "prepublishOnly": "npm run build"
70
70
  },
71
71
  "dependencies": {
72
- "@better-t-stack/template-generator": "^3.41.4",
73
- "@better-t-stack/types": "^3.41.4",
72
+ "@better-t-stack/template-generator": "^3.42.1",
73
+ "@better-t-stack/types": "^3.42.1",
74
74
  "@clack/core": "^1.4.3",
75
75
  "@clack/prompts": "^1.7.0",
76
76
  "@modelcontextprotocol/server": "2.0.0",