create-better-t-stack 3.41.4 → 3.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +11 -10
- package/dist/index.mjs +1 -1
- package/dist/{src-C8JXMrWB.mjs → src-C0_lYEa0.mjs} +475 -148
- package/package.json +3 -3
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-
|
|
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
|
|
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" | "
|
|
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
|
|
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" | "
|
|
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-
|
|
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 :
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
1604
|
+
const unselectedCheckbox = isItem ? pc.dim(inactiveBox) : "";
|
|
1460
1605
|
return `${pc.dim(prefix)}${unselectedCheckbox} ${pc.dim(label)}`;
|
|
1461
1606
|
};
|
|
1462
|
-
|
|
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
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2000
|
-
const hostRuntime = process.versions.bun ? "bun" : "node";
|
|
2163
|
+
async function readLocalToolVersions(packageManager, hostRuntime, needsNode) {
|
|
2001
2164
|
const versions = {};
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
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/
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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": () => [
|
|
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
|
-
"
|
|
3897
|
-
"
|
|
3898
|
-
"
|
|
3899
|
-
"
|
|
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.
|
|
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
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
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
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
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(
|
|
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 (
|
|
4760
|
-
|
|
4761
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
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(
|
|
7961
|
-
return
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 $({
|
|
8486
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
8943
|
-
|
|
8944
|
-
|
|
8945
|
-
|
|
8946
|
-
|
|
8947
|
-
|
|
8948
|
-
|
|
8949
|
-
|
|
8950
|
-
|
|
8951
|
-
|
|
8952
|
-
|
|
8953
|
-
|
|
8954
|
-
|
|
8955
|
-
|
|
8956
|
-
|
|
8957
|
-
})
|
|
8958
|
-
|
|
8959
|
-
|
|
8960
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "3.42.0",
|
|
4
4
|
"description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"better-auth",
|
|
@@ -69,8 +69,8 @@
|
|
|
69
69
|
"prepublishOnly": "npm run build"
|
|
70
70
|
},
|
|
71
71
|
"dependencies": {
|
|
72
|
-
"@better-t-stack/template-generator": "^3.
|
|
73
|
-
"@better-t-stack/types": "^3.
|
|
72
|
+
"@better-t-stack/template-generator": "^3.42.0",
|
|
73
|
+
"@better-t-stack/types": "^3.42.0",
|
|
74
74
|
"@clack/core": "^1.4.3",
|
|
75
75
|
"@clack/prompts": "^1.7.0",
|
|
76
76
|
"@modelcontextprotocol/server": "2.0.0",
|