create-better-t-stack 3.41.3 → 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-BhgA6H5Y.mjs → src-C0_lYEa0.mjs} +492 -151
- 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);
|
|
@@ -381,6 +521,14 @@ var UserCancelledError = class extends TaggedError("UserCancelledError") {
|
|
|
381
521
|
}
|
|
382
522
|
};
|
|
383
523
|
/**
|
|
524
|
+
* True for a direct cancellation or one that a setup step wrapped in a ProjectCreationError
|
|
525
|
+
* (for example cancelling a prompt inside addon or database setup).
|
|
526
|
+
*/
|
|
527
|
+
function isUserCancellation(cause) {
|
|
528
|
+
if (UserCancelledError.is(cause)) return true;
|
|
529
|
+
return ProjectCreationError.is(cause) && UserCancelledError.is(cause.cause);
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
384
532
|
* General CLI error for validation failures, invalid flags, etc.
|
|
385
533
|
*/
|
|
386
534
|
var CLIError = class extends TaggedError("CLIError") {};
|
|
@@ -923,7 +1071,7 @@ const WEB_FRAMEWORKS = [
|
|
|
923
1071
|
];
|
|
924
1072
|
//#endregion
|
|
925
1073
|
//#region src/utils/compatibility-rules.ts
|
|
926
|
-
const TASK_RUNNER_ADDONS
|
|
1074
|
+
const TASK_RUNNER_ADDONS = [
|
|
927
1075
|
"turborepo",
|
|
928
1076
|
"nx",
|
|
929
1077
|
"vite-plus"
|
|
@@ -1170,13 +1318,13 @@ function getCompatibleAddons(allAddons, frontend, existingAddons = [], auth, bac
|
|
|
1170
1318
|
return allAddons.filter((addon) => {
|
|
1171
1319
|
if (existingAddons.includes(addon)) return false;
|
|
1172
1320
|
if (addon === "none") return false;
|
|
1173
|
-
if (TASK_RUNNER_ADDONS
|
|
1321
|
+
if (TASK_RUNNER_ADDONS.includes(addon) && existingAddons.some((existingAddon) => TASK_RUNNER_ADDONS.includes(existingAddon))) return false;
|
|
1174
1322
|
const { isCompatible } = validateAddonCompatibility(addon, frontend, auth, backend, runtime);
|
|
1175
1323
|
return isCompatible;
|
|
1176
1324
|
});
|
|
1177
1325
|
}
|
|
1178
1326
|
function validateAddonsAgainstFrontends(addons = [], frontends = [], auth, backend, runtime) {
|
|
1179
|
-
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.");
|
|
1180
1328
|
for (const addon of addons) {
|
|
1181
1329
|
if (addon === "none") continue;
|
|
1182
1330
|
const { isCompatible, reason } = validateAddonCompatibility(addon, frontends, auth, backend, runtime);
|
|
@@ -1226,19 +1374,6 @@ function isGoBack(value) {
|
|
|
1226
1374
|
}
|
|
1227
1375
|
//#endregion
|
|
1228
1376
|
//#region src/prompts/navigable.ts
|
|
1229
|
-
const unicode = process.platform !== "win32";
|
|
1230
|
-
const S_STEP_ACTIVE = unicode ? "◆" : "*";
|
|
1231
|
-
const S_STEP_CANCEL = unicode ? "■" : "x";
|
|
1232
|
-
const S_STEP_ERROR = unicode ? "▲" : "x";
|
|
1233
|
-
const S_STEP_SUBMIT = unicode ? "◇" : "o";
|
|
1234
|
-
const S_STEP_BACK = unicode ? "↶" : "<";
|
|
1235
|
-
const S_BAR = unicode ? "│" : "|";
|
|
1236
|
-
const S_BAR_END = unicode ? "└" : "—";
|
|
1237
|
-
const S_RADIO_ACTIVE = unicode ? "●" : ">";
|
|
1238
|
-
const S_RADIO_INACTIVE = unicode ? "○" : " ";
|
|
1239
|
-
const S_CHECKBOX_ACTIVE = unicode ? "◻" : "[•]";
|
|
1240
|
-
const S_CHECKBOX_SELECTED = unicode ? "◼" : "[+]";
|
|
1241
|
-
const S_CHECKBOX_INACTIVE = unicode ? "◻" : "[ ]";
|
|
1242
1377
|
const promptsNavigatingBack = /* @__PURE__ */ new WeakSet();
|
|
1243
1378
|
function keycap(label) {
|
|
1244
1379
|
return pc.inverse(` ${label} `);
|
|
@@ -1430,28 +1565,46 @@ async function navigableConfirm(opts) {
|
|
|
1430
1565
|
}
|
|
1431
1566
|
}));
|
|
1432
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
|
+
}
|
|
1433
1578
|
async function navigableGroupMultiselect(opts) {
|
|
1434
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));
|
|
1435
1583
|
const opt = (option, state, options = []) => {
|
|
1436
|
-
const label = option.label ?? String(option.value);
|
|
1437
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)") : ""}`;
|
|
1438
1587
|
const next = isItem && (options[options.indexOf(option) + 1] ?? { group: true });
|
|
1439
1588
|
const isLast = isItem && next && next.group === true;
|
|
1440
1589
|
const prefix = isItem ? `${isLast ? S_BAR_END : S_BAR} ` : "";
|
|
1441
|
-
|
|
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})`)}` : ""}`;
|
|
1442
1595
|
if (state === "group-active") return `${prefix}${pc.cyan(S_CHECKBOX_ACTIVE)} ${pc.dim(label)}`;
|
|
1443
1596
|
if (state === "group-active-selected") return `${prefix}${pc.green(S_CHECKBOX_SELECTED)} ${pc.dim(label)}`;
|
|
1444
1597
|
if (state === "selected") {
|
|
1445
|
-
const selectedCheckbox = isItem ? pc.green(
|
|
1598
|
+
const selectedCheckbox = isItem ? pc.green(selectedBox) : "";
|
|
1446
1599
|
return `${pc.dim(prefix)}${selectedCheckbox} ${pc.dim(label)}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
|
|
1447
1600
|
}
|
|
1448
1601
|
if (state === "cancelled") return `${pc.strikethrough(pc.dim(label))}`;
|
|
1449
|
-
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})`)}` : ""}`;
|
|
1450
1603
|
if (state === "submitted") return `${pc.dim(label)}`;
|
|
1451
|
-
const unselectedCheckbox = isItem ? pc.dim(
|
|
1604
|
+
const unselectedCheckbox = isItem ? pc.dim(inactiveBox) : "";
|
|
1452
1605
|
return `${pc.dim(prefix)}${unselectedCheckbox} ${pc.dim(label)}`;
|
|
1453
1606
|
};
|
|
1454
|
-
|
|
1607
|
+
const prompt = new GroupMultiSelectPrompt({
|
|
1455
1608
|
options: opts.options,
|
|
1456
1609
|
initialValues: opts.initialValues,
|
|
1457
1610
|
cursorAt: opts.cursorAt,
|
|
@@ -1510,7 +1663,15 @@ async function navigableGroupMultiselect(opts) {
|
|
|
1510
1663
|
}
|
|
1511
1664
|
}
|
|
1512
1665
|
}
|
|
1513
|
-
})
|
|
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);
|
|
1514
1675
|
}
|
|
1515
1676
|
/** Use the remembered answer as the initial value only while it is still selectable. */
|
|
1516
1677
|
function preferValidInitial(options, previous, fallback) {
|
|
@@ -1649,11 +1810,7 @@ function sortAndPruneGroupedOptions(groupedOptions) {
|
|
|
1649
1810
|
});
|
|
1650
1811
|
}
|
|
1651
1812
|
function validateAddonSelection(selected) {
|
|
1652
|
-
if ((selected?.filter((addon) => [
|
|
1653
|
-
"turborepo",
|
|
1654
|
-
"nx",
|
|
1655
|
-
"vite-plus"
|
|
1656
|
-
].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.";
|
|
1657
1814
|
}
|
|
1658
1815
|
async function getAddonsChoice(addons, frontends, auth, backend, runtime, previousValue) {
|
|
1659
1816
|
if (addons !== void 0) return addons;
|
|
@@ -1676,6 +1833,7 @@ async function getAddonsChoice(addons, frontends, auth, backend, runtime, previo
|
|
|
1676
1833
|
options: groupedOptions,
|
|
1677
1834
|
initialValues: (previousValue ?? DEFAULT_CONFIG.addons).filter((addonValue) => Object.values(groupedOptions).some((options) => options.some((opt) => opt.value === addonValue))),
|
|
1678
1835
|
required: false,
|
|
1836
|
+
exclusive: [TASK_RUNNER_ADDONS],
|
|
1679
1837
|
validate: validateAddonSelection
|
|
1680
1838
|
});
|
|
1681
1839
|
if (isCancel$1(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
@@ -1699,6 +1857,7 @@ async function getAddonsToAdd(config) {
|
|
|
1699
1857
|
message: "Select addons to add",
|
|
1700
1858
|
options: groupedOptions,
|
|
1701
1859
|
required: false,
|
|
1860
|
+
exclusive: [TASK_RUNNER_ADDONS],
|
|
1702
1861
|
validate: validateAddonSelection
|
|
1703
1862
|
});
|
|
1704
1863
|
if (isCancel$1(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
@@ -1782,7 +1941,13 @@ function failureStage(cause) {
|
|
|
1782
1941
|
* project replaced by placeholders: quoted names, URLs, emails, and paths.
|
|
1783
1942
|
*/
|
|
1784
1943
|
function scrubReason(cause) {
|
|
1785
|
-
const
|
|
1944
|
+
const lines = (cause instanceof Error ? cause.message : String(cause)).split(/\r?\n/);
|
|
1945
|
+
const bullets = [];
|
|
1946
|
+
for (const line of lines.slice(1)) {
|
|
1947
|
+
if (!line.startsWith("- ")) break;
|
|
1948
|
+
bullets.push(line.slice(2));
|
|
1949
|
+
}
|
|
1950
|
+
const scrubbed = [lines[0] ?? "", ...bullets].join(" ").replaceAll(/(?<![\p{L}\p{N}\p{M}_])(["'`])(?:(?!\1).)*\1(?![\p{L}\p{N}\p{M}_])/gu, "<name>").replaceAll(/https?:\/\/[^\s)\]"'>]+/gi, "<url>").replaceAll(/[\w.+-]+@[\w-]+\.[\w.-]+/g, "<email>").replaceAll(/(?:[a-zA-Z]:)?(?:[\\/][\w.@+ -]+)+[\\/][\w.@+-]+(?:[ \w.@+-]*\.\w+)?/g, "<path>").replaceAll(/\S*[\\/]\S*/g, "<path>").replaceAll(/\s+/g, " ").trim();
|
|
1786
1951
|
return scrubbed.length > MAX_REASON_LENGTH ? scrubbed.slice(0, MAX_REASON_LENGTH) : scrubbed;
|
|
1787
1952
|
}
|
|
1788
1953
|
function clampValue(value) {
|
|
@@ -1925,13 +2090,22 @@ function getNodeToolingRequirements(config) {
|
|
|
1925
2090
|
if (config.webDeploy === "cloudflare" || config.serverDeploy === "cloudflare") addNodeRequirement(requirements, ">=22.0.0", "Wrangler 4");
|
|
1926
2091
|
return requirements;
|
|
1927
2092
|
}
|
|
1928
|
-
function
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
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
|
+
});
|
|
1934
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);
|
|
1935
2109
|
if (config.packageManager !== "bun") requirements.push(...getNodeToolingRequirements(config));
|
|
1936
2110
|
else if (config.runtime === "node") addNodeRequirement(requirements, ">=22.0.0", "the selected Node.js server runtime");
|
|
1937
2111
|
return requirements;
|
|
@@ -1945,7 +2119,11 @@ function formatRequirementError(requirement, rawVersion) {
|
|
|
1945
2119
|
return null;
|
|
1946
2120
|
}
|
|
1947
2121
|
function validateLocalToolVersions(config, versions, hostRuntime) {
|
|
1948
|
-
|
|
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) {
|
|
1949
2127
|
const failures = requirements.map((requirement) => formatRequirementError(requirement, versions[requirement.tool])).filter((failure) => failure !== null);
|
|
1950
2128
|
if (failures.length === 0) return Result.ok(void 0);
|
|
1951
2129
|
const upgradeInstructions = /* @__PURE__ */ new Set();
|
|
@@ -1955,7 +2133,7 @@ function validateLocalToolVersions(config, versions, hostRuntime) {
|
|
|
1955
2133
|
else upgradeInstructions.add(PACKAGE_MANAGER_UPGRADE_INSTRUCTIONS[requirement.tool]);
|
|
1956
2134
|
}
|
|
1957
2135
|
return Result.err(new CLIError({ message: [
|
|
1958
|
-
|
|
2136
|
+
headline,
|
|
1959
2137
|
...failures.map((failure) => `- ${failure}`),
|
|
1960
2138
|
"",
|
|
1961
2139
|
...upgradeInstructions
|
|
@@ -1982,12 +2160,35 @@ async function readToolVersion(tool) {
|
|
|
1982
2160
|
});
|
|
1983
2161
|
return result.isOk() ? result.value : null;
|
|
1984
2162
|
}
|
|
1985
|
-
async function
|
|
1986
|
-
const hostRuntime = process.versions.bun ? "bun" : "node";
|
|
2163
|
+
async function readLocalToolVersions(packageManager, hostRuntime, needsNode) {
|
|
1987
2164
|
const versions = {};
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
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];
|
|
1991
2192
|
if (!shouldSkipExternalCommands()) {
|
|
1992
2193
|
const validationResult = validateLocalToolVersions(config, versions, hostRuntime);
|
|
1993
2194
|
if (validationResult.isErr()) return Result.err(validationResult.error);
|
|
@@ -2020,6 +2221,22 @@ const addPackageDependency = async (opts) => {
|
|
|
2020
2221
|
await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
|
|
2021
2222
|
};
|
|
2022
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
|
|
2023
2240
|
//#region src/helpers/addons/evlog-setup.ts
|
|
2024
2241
|
const evlogBackends = [
|
|
2025
2242
|
"hono",
|
|
@@ -2857,7 +3074,7 @@ const TEMPLATES$2 = {
|
|
|
2857
3074
|
const DEFAULT_TEMPLATE$2 = "next-mdx";
|
|
2858
3075
|
const DEFAULT_DEV_PORT$1 = 4e3;
|
|
2859
3076
|
function aiChatDisabledForTemplate(template) {
|
|
2860
|
-
return template === "next-mdx-static" || template.endsWith("-spa");
|
|
3077
|
+
return template === "next-mdx-static" || template === "astro" || template.endsWith("-spa");
|
|
2861
3078
|
}
|
|
2862
3079
|
function getFumadocsLinter(addons) {
|
|
2863
3080
|
if (addons.includes("oxlint")) return "oxlint";
|
|
@@ -3101,6 +3318,26 @@ const MCP_AGENTS = [
|
|
|
3101
3318
|
value: "goose",
|
|
3102
3319
|
label: "Goose",
|
|
3103
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"
|
|
3104
3341
|
}
|
|
3105
3342
|
];
|
|
3106
3343
|
const DEFAULT_SCOPE$1 = "project";
|
|
@@ -3142,8 +3379,7 @@ function getAllMcpServers(config) {
|
|
|
3142
3379
|
key: "cloudflare-docs",
|
|
3143
3380
|
label: "Cloudflare Docs",
|
|
3144
3381
|
name: "cloudflare-docs",
|
|
3145
|
-
target: "https://docs.mcp.cloudflare.com/
|
|
3146
|
-
transport: "sse"
|
|
3382
|
+
target: "https://docs.mcp.cloudflare.com/mcp"
|
|
3147
3383
|
},
|
|
3148
3384
|
{
|
|
3149
3385
|
key: "convex",
|
|
@@ -3209,7 +3445,7 @@ function getAllMcpServers(config) {
|
|
|
3209
3445
|
key: "better-auth",
|
|
3210
3446
|
label: "Better Auth",
|
|
3211
3447
|
name: "better-auth",
|
|
3212
|
-
target: "https://mcp.
|
|
3448
|
+
target: "https://mcp.better-auth.com/mcp"
|
|
3213
3449
|
},
|
|
3214
3450
|
{
|
|
3215
3451
|
key: "clerk",
|
|
@@ -3443,7 +3679,7 @@ const SKILL_SOURCES = {
|
|
|
3443
3679
|
"vercel/ai": { label: "Vercel AI SDK" },
|
|
3444
3680
|
"vercel/turborepo": { label: "Turborepo" },
|
|
3445
3681
|
"yusukebe/hono-skill": { label: "Hono Backend" },
|
|
3446
|
-
"vercel
|
|
3682
|
+
"vercel/next.js": { label: "Next.js" },
|
|
3447
3683
|
"nuxt/ui": { label: "Nuxt UI" },
|
|
3448
3684
|
"heroui-inc/heroui": { label: "HeroUI Native" },
|
|
3449
3685
|
"shadcn/ui": { label: "shadcn/ui" },
|
|
@@ -3641,6 +3877,10 @@ const SKILLS_CLI_AGENT_OPTIONS = [
|
|
|
3641
3877
|
value: "mcpjam",
|
|
3642
3878
|
label: "MCPJam"
|
|
3643
3879
|
},
|
|
3880
|
+
{
|
|
3881
|
+
value: "minimax-code",
|
|
3882
|
+
label: "MiniMax Code"
|
|
3883
|
+
},
|
|
3644
3884
|
{
|
|
3645
3885
|
value: "mistral-vibe",
|
|
3646
3886
|
label: "Mistral Vibe"
|
|
@@ -3681,6 +3921,10 @@ const SKILLS_CLI_AGENT_OPTIONS = [
|
|
|
3681
3921
|
value: "pochi",
|
|
3682
3922
|
label: "Pochi"
|
|
3683
3923
|
},
|
|
3924
|
+
{
|
|
3925
|
+
value: "posit-assistant",
|
|
3926
|
+
label: "Posit Assistant"
|
|
3927
|
+
},
|
|
3684
3928
|
{
|
|
3685
3929
|
value: "promptscript",
|
|
3686
3930
|
label: "PromptScript"
|
|
@@ -3820,7 +4064,7 @@ function getRecommendedSourceKeys(config) {
|
|
|
3820
4064
|
sources.push("shadcn/ui");
|
|
3821
4065
|
}
|
|
3822
4066
|
if ((webDeploy === "vercel" || serverDeploy === "vercel") && !sources.includes("vercel-labs/agent-skills")) sources.push("vercel-labs/agent-skills");
|
|
3823
|
-
if (frontend.includes("next")) sources.push("vercel
|
|
4067
|
+
if (frontend.includes("next")) sources.push("vercel/next.js");
|
|
3824
4068
|
if (frontend.includes("nuxt")) sources.push("nuxt/ui");
|
|
3825
4069
|
if (frontend.includes("native-uniwind")) sources.push("heroui-inc/heroui");
|
|
3826
4070
|
if (hasNativeFrontend(frontend)) sources.push("expo/skills");
|
|
@@ -3851,11 +4095,20 @@ const CURATED_SKILLS_BY_SOURCE = {
|
|
|
3851
4095
|
"vercel/ai": () => ["ai-sdk"],
|
|
3852
4096
|
"vercel/turborepo": () => ["turborepo"],
|
|
3853
4097
|
"yusukebe/hono-skill": () => ["hono"],
|
|
3854
|
-
"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
|
+
],
|
|
3855
4104
|
"nuxt/ui": () => ["nuxt-ui"],
|
|
3856
4105
|
"heroui-inc/heroui": () => ["heroui-native"],
|
|
3857
4106
|
"shadcn/ui": () => ["shadcn"],
|
|
3858
|
-
"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
|
+
],
|
|
3859
4112
|
"clerk/skills": (config) => {
|
|
3860
4113
|
const skills = [
|
|
3861
4114
|
"clerk",
|
|
@@ -3866,6 +4119,12 @@ const CURATED_SKILLS_BY_SOURCE = {
|
|
|
3866
4119
|
"clerk-orgs"
|
|
3867
4120
|
];
|
|
3868
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");
|
|
3869
4128
|
return skills;
|
|
3870
4129
|
},
|
|
3871
4130
|
"neondatabase/agent-skills": () => ["neon-postgres"],
|
|
@@ -3878,11 +4137,13 @@ const CURATED_SKILLS_BY_SOURCE = {
|
|
|
3878
4137
|
},
|
|
3879
4138
|
"expo/skills": (config) => {
|
|
3880
4139
|
const skills = [
|
|
4140
|
+
"expo-overview",
|
|
4141
|
+
"expo-router",
|
|
3881
4142
|
"expo-dev-client",
|
|
3882
|
-
"
|
|
3883
|
-
"
|
|
3884
|
-
"
|
|
3885
|
-
"
|
|
4143
|
+
"expo-native-ui",
|
|
4144
|
+
"expo-data-fetching",
|
|
4145
|
+
"eas-app-stores",
|
|
4146
|
+
"eas-workflows"
|
|
3886
4147
|
];
|
|
3887
4148
|
if (config.frontend.includes("native-uniwind")) skills.push("expo-tailwind-setup");
|
|
3888
4149
|
return skills;
|
|
@@ -4054,7 +4315,8 @@ async function setupStarlight(config) {
|
|
|
4054
4315
|
"--yes",
|
|
4055
4316
|
"--no-install",
|
|
4056
4317
|
"--no-git",
|
|
4057
|
-
"--skip-houston"
|
|
4318
|
+
"--skip-houston",
|
|
4319
|
+
"--no-ai"
|
|
4058
4320
|
].join(" ")}`);
|
|
4059
4321
|
const appsDir = path.join(projectDir, "apps");
|
|
4060
4322
|
await fs.ensureDir(appsDir);
|
|
@@ -4335,7 +4597,7 @@ const HOOKS = {
|
|
|
4335
4597
|
claude: { label: "Claude Code" },
|
|
4336
4598
|
copilot: { label: "GitHub Copilot" }
|
|
4337
4599
|
};
|
|
4338
|
-
const ULTRACITE_VERSION = "7.
|
|
4600
|
+
const ULTRACITE_VERSION = "7.10.7";
|
|
4339
4601
|
const DEFAULT_LINTER = "biome";
|
|
4340
4602
|
const DEFAULT_EDITORS = ["vscode"];
|
|
4341
4603
|
const DEFAULT_AGENTS = ["universal"];
|
|
@@ -4418,13 +4680,20 @@ async function setupUltracite(config, gitHooks) {
|
|
|
4418
4680
|
return navigableMultiselect({
|
|
4419
4681
|
message: "Which editors do you want to configure (recommended)?",
|
|
4420
4682
|
required: false,
|
|
4421
|
-
options: [
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
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
|
+
]
|
|
4428
4697
|
});
|
|
4429
4698
|
},
|
|
4430
4699
|
agents: async () => {
|
|
@@ -4598,24 +4867,15 @@ async function setupWxt(config) {
|
|
|
4598
4867
|
}
|
|
4599
4868
|
//#endregion
|
|
4600
4869
|
//#region src/helpers/addons/addons-setup.ts
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
try: async () => step(),
|
|
4611
|
-
catch: (e) => new AddonSetupError({
|
|
4612
|
-
addon,
|
|
4613
|
-
message: `Failed to set up ${addon}: ${e instanceof Error ? e.message : String(e)}`,
|
|
4614
|
-
cause: e
|
|
4615
|
-
})
|
|
4616
|
-
});
|
|
4617
|
-
if (result.isErr()) cliConsola.error(pc.red(result.error.message));
|
|
4618
|
-
}
|
|
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.`);
|
|
4619
4879
|
async function setupAddons(config) {
|
|
4620
4880
|
const { addons, frontend, projectDir } = config;
|
|
4621
4881
|
const hasWebFrontend = frontend.some((value) => types_exports.desktopWebFrontends.includes(value));
|
|
@@ -4725,15 +4985,19 @@ async function detectProjectConfig(projectDir) {
|
|
|
4725
4985
|
}
|
|
4726
4986
|
//#endregion
|
|
4727
4987
|
//#region src/helpers/core/install-dependencies.ts
|
|
4988
|
+
const FORCE_KILL_AFTER_MS = 2e3;
|
|
4728
4989
|
async function installDependencies({ projectDir, packageManager }) {
|
|
4729
|
-
if (shouldSkipExternalCommands()) return Result.ok(
|
|
4990
|
+
if (shouldSkipExternalCommands()) return Result.ok("installed");
|
|
4991
|
+
startInterruptibleStep();
|
|
4730
4992
|
const s = createSpinner();
|
|
4731
4993
|
s.start(`Running ${packageManager} install...`);
|
|
4732
4994
|
const result = await Result.tryPromise({
|
|
4733
4995
|
try: async () => {
|
|
4734
4996
|
await $({
|
|
4735
4997
|
cwd: projectDir,
|
|
4736
|
-
stderr: "inherit"
|
|
4998
|
+
stderr: "inherit",
|
|
4999
|
+
cancelSignal: getInterruptSignal(),
|
|
5000
|
+
forceKillAfterDelay: FORCE_KILL_AFTER_MS
|
|
4737
5001
|
})`${packageManager} install`;
|
|
4738
5002
|
},
|
|
4739
5003
|
catch: (e) => new ProjectCreationError({
|
|
@@ -4742,9 +5006,16 @@ async function installDependencies({ projectDir, packageManager }) {
|
|
|
4742
5006
|
cause: e
|
|
4743
5007
|
})
|
|
4744
5008
|
});
|
|
4745
|
-
if (
|
|
4746
|
-
|
|
4747
|
-
|
|
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);
|
|
4748
5019
|
}
|
|
4749
5020
|
//#endregion
|
|
4750
5021
|
//#region src/helpers/core/add-handler.ts
|
|
@@ -4772,11 +5043,6 @@ const HOOK_LINTER_ADDONS = [
|
|
|
4772
5043
|
"oxlint",
|
|
4773
5044
|
"vite-plus"
|
|
4774
5045
|
];
|
|
4775
|
-
const TASK_RUNNER_ADDONS = [
|
|
4776
|
-
"turborepo",
|
|
4777
|
-
"nx",
|
|
4778
|
-
"vite-plus"
|
|
4779
|
-
];
|
|
4780
5046
|
const fileExistsErrorSchema = z.object({ code: z.literal("EEXIST") });
|
|
4781
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));
|
|
4782
5048
|
const rootTypescriptVersionSchema = z.object({ devDependencies: z.object({ typescript: z.string().min(1) }) }).transform(({ devDependencies }) => devDependencies.typescript);
|
|
@@ -4889,7 +5155,12 @@ async function addHandler(input, options = {}) {
|
|
|
4889
5155
|
mode,
|
|
4890
5156
|
analyticsDisabled: input.disableAnalytics
|
|
4891
5157
|
}, async () => {
|
|
4892
|
-
|
|
5158
|
+
let result;
|
|
5159
|
+
try {
|
|
5160
|
+
result = await addHandlerInternal(input);
|
|
5161
|
+
} finally {
|
|
5162
|
+
endInterruptibleScope();
|
|
5163
|
+
}
|
|
4893
5164
|
await reportAddOutcome(input, result);
|
|
4894
5165
|
if (result.isOk()) return result.value;
|
|
4895
5166
|
const error = result.error;
|
|
@@ -5092,6 +5363,7 @@ async function addHandlerInternal(input) {
|
|
|
5092
5363
|
return Result.err(new CLIError({ message: `Failed to write files: ${writeResult.error.message}` }));
|
|
5093
5364
|
}
|
|
5094
5365
|
if (vfs.getFileCount() > 0 && !isSilent()) log.info(pc.dim(`Wrote ${vfs.getFileCount()} files`));
|
|
5366
|
+
beginInterruptibleScope();
|
|
5095
5367
|
const setupResult = await Result.tryPromise({
|
|
5096
5368
|
try: async () => {
|
|
5097
5369
|
if (addonsToAdd.length > 0) await setupAddons({
|
|
@@ -6825,7 +7097,9 @@ const PROJECT_LAUNCHERS = [
|
|
|
6825
7097
|
}]
|
|
6826
7098
|
}
|
|
6827
7099
|
];
|
|
6828
|
-
async function detectProjectLaunchers(projectDir,
|
|
7100
|
+
async function detectProjectLaunchers(projectDir, options = {}) {
|
|
7101
|
+
const detectCommand = options.detectCommand ?? commandExists;
|
|
7102
|
+
const platform = options.platform ?? process.platform;
|
|
6829
7103
|
const supportedDefinitions = PROJECT_LAUNCHERS.filter(({ platforms }) => platforms === void 0 || platforms.includes(platform));
|
|
6830
7104
|
const commandsFor = (launcher) => launcher.commandsByPlatform?.[platform] ?? launcher.commands;
|
|
6831
7105
|
const commands = [...new Set(supportedDefinitions.flatMap(commandsFor))];
|
|
@@ -6848,7 +7122,8 @@ async function detectProjectLaunchers(projectDir, detectCommand = commandExists,
|
|
|
6848
7122
|
async function getProjectLauncherChoice(requestedLauncher, projectDir, options = {}) {
|
|
6849
7123
|
if (requestedLauncher === "none") return Result.ok(void 0);
|
|
6850
7124
|
if (!requestedLauncher && options.prompt === false) return Result.ok(void 0);
|
|
6851
|
-
const
|
|
7125
|
+
const { prompt: _prompt, ...detectOptions } = options;
|
|
7126
|
+
const availableLaunchers = await detectProjectLaunchers(projectDir, detectOptions);
|
|
6852
7127
|
if (requestedLauncher) {
|
|
6853
7128
|
const launcher = availableLaunchers.find(({ id }) => id === requestedLauncher);
|
|
6854
7129
|
if (launcher) return Result.ok(launcher);
|
|
@@ -7136,12 +7411,14 @@ async function formatCode(filePath, content) {
|
|
|
7136
7411
|
});
|
|
7137
7412
|
return result.isOk() ? result.value : null;
|
|
7138
7413
|
}
|
|
7139
|
-
async function formatProject(projectDir) {
|
|
7414
|
+
async function formatProject(projectDir, signal) {
|
|
7140
7415
|
return Result.tryPromise({
|
|
7141
7416
|
try: async () => {
|
|
7142
7417
|
async function formatDirectory(dir) {
|
|
7418
|
+
if (signal?.aborted) return;
|
|
7143
7419
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
7144
7420
|
await Promise.all(entries.map(async (entry) => {
|
|
7421
|
+
if (signal?.aborted) return;
|
|
7145
7422
|
const fullPath = path.join(dir, entry.name);
|
|
7146
7423
|
if (entry.isDirectory()) await formatDirectory(fullPath);
|
|
7147
7424
|
else if (entry.isFile()) await Result.tryPromise({
|
|
@@ -7943,8 +8220,19 @@ async function writeSupabaseEnvFile(projectDir, backend, databaseUrl) {
|
|
|
7943
8220
|
})
|
|
7944
8221
|
});
|
|
7945
8222
|
}
|
|
7946
|
-
function extractDbUrl(
|
|
7947
|
-
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 : "";
|
|
7948
8236
|
}
|
|
7949
8237
|
async function initializeSupabase(serverDir, packageManager) {
|
|
7950
8238
|
cliLog.info("Initializing Supabase project...");
|
|
@@ -7969,7 +8257,7 @@ async function initializeSupabase(serverDir, packageManager) {
|
|
|
7969
8257
|
}
|
|
7970
8258
|
async function startSupabase(serverDir, packageManager) {
|
|
7971
8259
|
cliLog.info("Starting Supabase services (this may take a moment)...");
|
|
7972
|
-
const supabaseStartArgs = getPackageExecutionArgs(packageManager, "supabase start");
|
|
8260
|
+
const supabaseStartArgs = getPackageExecutionArgs(packageManager, "supabase start --agent no");
|
|
7973
8261
|
return Result.tryPromise({
|
|
7974
8262
|
try: async () => {
|
|
7975
8263
|
const subprocess = execa(supabaseStartArgs[0], supabaseStartArgs.slice(1), { cwd: serverDir });
|
|
@@ -8000,10 +8288,10 @@ function displayManualSupabaseInstructions(targetApp, output) {
|
|
|
8000
8288
|
2. Install the Supabase CLI (e.g., \`npm install -g supabase\`).
|
|
8001
8289
|
3. Run \`supabase init\` in your project's \`packages/db\` directory.
|
|
8002
8290
|
4. Run \`supabase start\` in your project's \`packages/db\` directory.
|
|
8003
|
-
5. Copy the
|
|
8291
|
+
5. Copy the database URL from the output.${output ? `
|
|
8004
8292
|
${pc.bold("Relevant output from `supabase start`:")}
|
|
8005
8293
|
${pc.dim(output)}` : ""}
|
|
8006
|
-
6. Add the
|
|
8294
|
+
6. Add the database URL to the .env file in \`${targetApp}/.env\` as \`DATABASE_URL\`:
|
|
8007
8295
|
${pc.gray("DATABASE_URL=\"your_supabase_db_url\"")}`);
|
|
8008
8296
|
}
|
|
8009
8297
|
async function setupSupabase(config, cliInput) {
|
|
@@ -8063,7 +8351,7 @@ async function setupSupabase(config, cliInput) {
|
|
|
8063
8351
|
return writeSupabaseEnvFile(projectDir, backend, "");
|
|
8064
8352
|
}
|
|
8065
8353
|
const supabaseOutput = startResult.value;
|
|
8066
|
-
const dbUrl = extractDbUrl(
|
|
8354
|
+
const dbUrl = extractDbUrl(await readSupabaseStatus(serverDir, packageManager));
|
|
8067
8355
|
if (dbUrl) {
|
|
8068
8356
|
const envResult = await writeSupabaseEnvFile(projectDir, backend, dbUrl);
|
|
8069
8357
|
if (envResult.isOk()) cliLog.success(pc.green("Supabase local development setup ready!"));
|
|
@@ -8192,7 +8480,7 @@ async function createTursoDatabase(dbName, groupName) {
|
|
|
8192
8480
|
catch: (e) => {
|
|
8193
8481
|
const error = e;
|
|
8194
8482
|
s.stop(pc.red(`Failed to create database "${dbName}"`));
|
|
8195
|
-
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({
|
|
8196
8484
|
provider: "turso",
|
|
8197
8485
|
message: "DATABASE_EXISTS",
|
|
8198
8486
|
cause: e
|
|
@@ -8423,13 +8711,7 @@ async function setupDatabase(config, cliInput) {
|
|
|
8423
8711
|
const dbPackageDir = path.join(projectDir, "packages/db");
|
|
8424
8712
|
if (!await fs.pathExists(dbPackageDir)) return;
|
|
8425
8713
|
if (usesAlchemyManagedDatabase(config)) return;
|
|
8426
|
-
|
|
8427
|
-
const result = await setupFn();
|
|
8428
|
-
if (result.isErr()) {
|
|
8429
|
-
if (UserCancelledError.is(result.error)) throw result.error;
|
|
8430
|
-
cliConsola.error(pc.red(result.error.message));
|
|
8431
|
-
}
|
|
8432
|
-
}
|
|
8714
|
+
const runSetup = (setupFn) => runOptionalStep(setupFn, "Database setup cancelled. Configure the connection manually.");
|
|
8433
8715
|
const resolvedCliInput = {
|
|
8434
8716
|
...cliInput,
|
|
8435
8717
|
dbSetupOptions: mergeResolvedDbSetupOptions(dbSetup, config.dbSetupOptions, cliInput)
|
|
@@ -8447,29 +8729,44 @@ async function setupDatabase(config, cliInput) {
|
|
|
8447
8729
|
}
|
|
8448
8730
|
//#endregion
|
|
8449
8731
|
//#region src/helpers/core/git.ts
|
|
8450
|
-
async function initializeGit(projectDir, useGit) {
|
|
8732
|
+
async function initializeGit(projectDir, useGit, signal) {
|
|
8451
8733
|
if (!useGit) return Result.ok(void 0);
|
|
8452
|
-
|
|
8734
|
+
const cancelled = () => Result.err(new ProjectCreationError({
|
|
8735
|
+
phase: "git-initialization",
|
|
8736
|
+
message: "Git initialization cancelled"
|
|
8737
|
+
}));
|
|
8738
|
+
const gitVersionResult = await $({
|
|
8453
8739
|
cwd: projectDir,
|
|
8454
8740
|
reject: false,
|
|
8455
|
-
stderr: "pipe"
|
|
8456
|
-
|
|
8741
|
+
stderr: "pipe",
|
|
8742
|
+
cancelSignal: signal
|
|
8743
|
+
})`git --version`;
|
|
8744
|
+
if (signal?.aborted) return cancelled();
|
|
8745
|
+
if (gitVersionResult.exitCode !== 0) {
|
|
8457
8746
|
cliLog.warn(pc.yellow("Git is not installed"));
|
|
8458
8747
|
return Result.ok(void 0);
|
|
8459
8748
|
}
|
|
8460
8749
|
const result = await $({
|
|
8461
8750
|
cwd: projectDir,
|
|
8462
8751
|
reject: false,
|
|
8463
|
-
stderr: "pipe"
|
|
8752
|
+
stderr: "pipe",
|
|
8753
|
+
cancelSignal: signal
|
|
8464
8754
|
})`git init`;
|
|
8755
|
+
if (signal?.aborted) return cancelled();
|
|
8465
8756
|
if (result.exitCode !== 0) return Result.err(new ProjectCreationError({
|
|
8466
8757
|
phase: "git-initialization",
|
|
8467
8758
|
message: `Git initialization failed: ${result.stderr}`
|
|
8468
8759
|
}));
|
|
8469
8760
|
return Result.tryPromise({
|
|
8470
8761
|
try: async () => {
|
|
8471
|
-
await $({
|
|
8472
|
-
|
|
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"}`;
|
|
8473
8770
|
},
|
|
8474
8771
|
catch: (e) => new ProjectCreationError({
|
|
8475
8772
|
phase: "git-initialization",
|
|
@@ -8777,7 +9074,7 @@ function getTauriInstructions(runCmd, frontend) {
|
|
|
8777
9074
|
}
|
|
8778
9075
|
function getElectrobunInstructions(runCmd, frontend) {
|
|
8779
9076
|
const staticBuildNote = getDesktopStaticBuildNote(frontend);
|
|
8780
|
-
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}` : ""}`;
|
|
8781
9078
|
}
|
|
8782
9079
|
function getPwaInstructions() {
|
|
8783
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`;
|
|
@@ -8896,7 +9193,8 @@ function getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy, backend,
|
|
|
8896
9193
|
//#region src/helpers/core/create-project.ts
|
|
8897
9194
|
/**
|
|
8898
9195
|
* Creates a new project with the given configuration.
|
|
8899
|
-
*
|
|
9196
|
+
* A failed dependency install is returned in the outcome instead of failing, since the project
|
|
9197
|
+
* is already on disk by then.
|
|
8900
9198
|
*/
|
|
8901
9199
|
async function createProject(options, cliInput) {
|
|
8902
9200
|
return Result.gen(async function* () {
|
|
@@ -8925,34 +9223,57 @@ async function createProject(options, cliInput) {
|
|
|
8925
9223
|
cause: e
|
|
8926
9224
|
}))));
|
|
8927
9225
|
yield* Result.await(setPackageManagerVersion(projectDir, options.packageManager, cliInput.packageManagerVersion));
|
|
8928
|
-
|
|
8929
|
-
|
|
8930
|
-
|
|
8931
|
-
|
|
8932
|
-
|
|
8933
|
-
|
|
8934
|
-
|
|
8935
|
-
|
|
8936
|
-
|
|
8937
|
-
|
|
8938
|
-
|
|
8939
|
-
|
|
8940
|
-
|
|
8941
|
-
|
|
8942
|
-
|
|
8943
|
-
})
|
|
8944
|
-
|
|
8945
|
-
|
|
8946
|
-
|
|
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({
|
|
8947
9259
|
projectDir,
|
|
8948
9260
|
packageManager: options.packageManager
|
|
8949
|
-
}));
|
|
8950
|
-
yield* Result.await(initializeGit(projectDir, options.git));
|
|
8951
|
-
if (!isSilent()) await displayPostInstallInstructions({
|
|
8952
|
-
...options,
|
|
8953
|
-
depsInstalled: options.install
|
|
8954
9261
|
});
|
|
8955
|
-
|
|
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()
|
|
8956
9277
|
});
|
|
8957
9278
|
}
|
|
8958
9279
|
async function setPackageManagerVersion(projectDir, packageManager, version) {
|
|
@@ -9030,7 +9351,7 @@ async function executeCreateProjectHandler(input, options) {
|
|
|
9030
9351
|
async function reportCreateOutcome(input, result) {
|
|
9031
9352
|
if (result.isOk()) return;
|
|
9032
9353
|
const error = result.error;
|
|
9033
|
-
if (
|
|
9354
|
+
if (isUserCancellation(error)) return;
|
|
9034
9355
|
await reportDiagnostic("cli_failed", {
|
|
9035
9356
|
command: "create",
|
|
9036
9357
|
mode: resolveInvocationMode(input.yes),
|
|
@@ -9050,7 +9371,7 @@ async function createProjectHandler(input, options = {}) {
|
|
|
9050
9371
|
if (result.isOk()) return result.value;
|
|
9051
9372
|
const error = result.error;
|
|
9052
9373
|
const elapsedTimeMs = Date.now() - startTime;
|
|
9053
|
-
if (
|
|
9374
|
+
if (isUserCancellation(error)) {
|
|
9054
9375
|
if (silent) return createEmptyResult(timeScaffolded, elapsedTimeMs, error.message);
|
|
9055
9376
|
return;
|
|
9056
9377
|
}
|
|
@@ -9063,6 +9384,10 @@ async function createProjectHandlerInternal(input, startTime, timeScaffolded) {
|
|
|
9063
9384
|
if (!isSilent() && input.renderTitle !== false) renderTitle();
|
|
9064
9385
|
if (!isSilent()) intro(pc.magenta("Configure your new project"));
|
|
9065
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
|
+
}
|
|
9066
9391
|
let currentPathInput;
|
|
9067
9392
|
if (isSilent()) currentPathInput = yield* Result.await(resolveProjectNameForSilent(input));
|
|
9068
9393
|
else if (input.yes && input.projectName) currentPathInput = input.projectName;
|
|
@@ -9208,12 +9533,27 @@ async function createProjectHandlerInternal(input, startTime, timeScaffolded) {
|
|
|
9208
9533
|
relativePath: config.relativePath
|
|
9209
9534
|
});
|
|
9210
9535
|
}
|
|
9211
|
-
yield* Result.await(createProject(config, {
|
|
9536
|
+
const created = yield* Result.await(createProject(config, {
|
|
9212
9537
|
manualDb: cliInput.manualDb ?? input.manualDb,
|
|
9213
9538
|
dbSetupOptions: effectiveDbSetupOptions,
|
|
9214
9539
|
packageManagerVersion: localRequirements.packageManagerVersion
|
|
9215
9540
|
}));
|
|
9216
|
-
|
|
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
|
+
}
|
|
9217
9557
|
const historyResult = await addToHistory(config, reproducibleCommand);
|
|
9218
9558
|
if (historyResult.isErr() && !isSilent()) {
|
|
9219
9559
|
log.warn(pc.yellow(historyResult.error.message));
|
|
@@ -9244,7 +9584,8 @@ async function createProjectHandlerInternal(input, startTime, timeScaffolded) {
|
|
|
9244
9584
|
timeScaffolded,
|
|
9245
9585
|
elapsedTimeMs,
|
|
9246
9586
|
projectDirectory: config.projectDir,
|
|
9247
|
-
relativePath: config.relativePath
|
|
9587
|
+
relativePath: config.relativePath,
|
|
9588
|
+
warnings: warnings.length > 0 ? warnings : void 0
|
|
9248
9589
|
});
|
|
9249
9590
|
});
|
|
9250
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",
|