create-better-t-stack 3.19.5 → 3.20.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 +2 -0
- package/dist/index.mjs +1 -1
- package/dist/{src-C8vduSK8.mjs → src-xjt9vVj6.mjs} +357 -104
- package/package.json +3 -3
package/dist/cli.mjs
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -182,6 +182,7 @@ declare const router: {
|
|
|
182
182
|
lefthook: "lefthook";
|
|
183
183
|
husky: "husky";
|
|
184
184
|
ruler: "ruler";
|
|
185
|
+
mcp: "mcp";
|
|
185
186
|
turborepo: "turborepo";
|
|
186
187
|
fumadocs: "fumadocs";
|
|
187
188
|
ultracite: "ultracite";
|
|
@@ -264,6 +265,7 @@ declare const router: {
|
|
|
264
265
|
lefthook: "lefthook";
|
|
265
266
|
husky: "husky";
|
|
266
267
|
ruler: "ruler";
|
|
268
|
+
mcp: "mcp";
|
|
267
269
|
turborepo: "turborepo";
|
|
268
270
|
fumadocs: "fumadocs";
|
|
269
271
|
ultracite: "ultracite";
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { _ as DatabaseSetupError, a as VirtualFileSystem, b as UserCancelledError, c as create, d as docs, f as generate, g as CompatibilityError, h as CLIError, i as TEMPLATE_COUNT, l as createBtsCli, m as sponsors, n as GeneratorError, o as add, p as router, r as Result, s as builder, t as EMBEDDED_TEMPLATES, u as createVirtual, v as DirectoryConflictError, x as ValidationError, y as ProjectCreationError } from "./src-
|
|
2
|
+
import { _ as DatabaseSetupError, a as VirtualFileSystem, b as UserCancelledError, c as create, d as docs, f as generate, g as CompatibilityError, h as CLIError, i as TEMPLATE_COUNT, l as createBtsCli, m as sponsors, n as GeneratorError, o as add, p as router, r as Result, s as builder, t as EMBEDDED_TEMPLATES, u as createVirtual, v as DirectoryConflictError, x as ValidationError, y as ProjectCreationError } from "./src-xjt9vVj6.mjs";
|
|
3
3
|
|
|
4
4
|
export { CLIError, CompatibilityError, DatabaseSetupError, DirectoryConflictError, EMBEDDED_TEMPLATES, GeneratorError, ProjectCreationError, Result, TEMPLATE_COUNT, UserCancelledError, ValidationError, VirtualFileSystem, add, builder, create, createBtsCli, createVirtual, docs, generate, router, sponsors };
|
|
@@ -18,8 +18,8 @@ import { writeTree } from "@better-t-stack/template-generator/fs-writer";
|
|
|
18
18
|
import { ConfirmPrompt, GroupMultiSelectPrompt, MultiSelectPrompt, SelectPrompt, isCancel as isCancel$1 } from "@clack/core";
|
|
19
19
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
20
20
|
import { applyEdits, modify, parse } from "jsonc-parser";
|
|
21
|
-
import { format } from "oxfmt";
|
|
22
21
|
import os$1 from "node:os";
|
|
22
|
+
import { format } from "oxfmt";
|
|
23
23
|
|
|
24
24
|
//#region src/utils/get-package-manager.ts
|
|
25
25
|
const getUserPkgManager = () => {
|
|
@@ -86,6 +86,7 @@ const ADDON_COMPATIBILITY = {
|
|
|
86
86
|
starlight: [],
|
|
87
87
|
ultracite: [],
|
|
88
88
|
ruler: [],
|
|
89
|
+
mcp: [],
|
|
89
90
|
oxlint: [],
|
|
90
91
|
fumadocs: [],
|
|
91
92
|
opentui: [],
|
|
@@ -1024,6 +1025,10 @@ function getAddonDisplay(addon) {
|
|
|
1024
1025
|
label = "Skills";
|
|
1025
1026
|
hint = "AI coding agent skills for your stack";
|
|
1026
1027
|
break;
|
|
1028
|
+
case "mcp":
|
|
1029
|
+
label = "MCP";
|
|
1030
|
+
hint = "Install MCP servers (docs, databases, SaaS) via add-mcp";
|
|
1031
|
+
break;
|
|
1027
1032
|
default:
|
|
1028
1033
|
label = addon;
|
|
1029
1034
|
hint = `Add ${addon}`;
|
|
@@ -1049,7 +1054,11 @@ const ADDON_GROUPS = {
|
|
|
1049
1054
|
"opentui",
|
|
1050
1055
|
"wxt"
|
|
1051
1056
|
],
|
|
1052
|
-
AI: [
|
|
1057
|
+
AI: [
|
|
1058
|
+
"ruler",
|
|
1059
|
+
"skills",
|
|
1060
|
+
"mcp"
|
|
1061
|
+
]
|
|
1053
1062
|
};
|
|
1054
1063
|
async function getAddonsChoice(addons, frontends, auth) {
|
|
1055
1064
|
if (addons !== void 0) return addons;
|
|
@@ -1384,6 +1393,246 @@ async function setupFumadocs(config) {
|
|
|
1384
1393
|
return Result.ok(void 0);
|
|
1385
1394
|
}
|
|
1386
1395
|
|
|
1396
|
+
//#endregion
|
|
1397
|
+
//#region src/helpers/addons/mcp-setup.ts
|
|
1398
|
+
const MCP_AGENTS = [
|
|
1399
|
+
{
|
|
1400
|
+
value: "cursor",
|
|
1401
|
+
label: "Cursor",
|
|
1402
|
+
scope: "both"
|
|
1403
|
+
},
|
|
1404
|
+
{
|
|
1405
|
+
value: "claude-code",
|
|
1406
|
+
label: "Claude Code",
|
|
1407
|
+
scope: "both"
|
|
1408
|
+
},
|
|
1409
|
+
{
|
|
1410
|
+
value: "codex",
|
|
1411
|
+
label: "Codex",
|
|
1412
|
+
scope: "both"
|
|
1413
|
+
},
|
|
1414
|
+
{
|
|
1415
|
+
value: "opencode",
|
|
1416
|
+
label: "OpenCode",
|
|
1417
|
+
scope: "both"
|
|
1418
|
+
},
|
|
1419
|
+
{
|
|
1420
|
+
value: "gemini-cli",
|
|
1421
|
+
label: "Gemini CLI",
|
|
1422
|
+
scope: "both"
|
|
1423
|
+
},
|
|
1424
|
+
{
|
|
1425
|
+
value: "vscode",
|
|
1426
|
+
label: "VS Code (GitHub Copilot)",
|
|
1427
|
+
scope: "both"
|
|
1428
|
+
},
|
|
1429
|
+
{
|
|
1430
|
+
value: "zed",
|
|
1431
|
+
label: "Zed",
|
|
1432
|
+
scope: "both"
|
|
1433
|
+
},
|
|
1434
|
+
{
|
|
1435
|
+
value: "claude-desktop",
|
|
1436
|
+
label: "Claude Desktop",
|
|
1437
|
+
scope: "global"
|
|
1438
|
+
},
|
|
1439
|
+
{
|
|
1440
|
+
value: "goose",
|
|
1441
|
+
label: "Goose",
|
|
1442
|
+
scope: "global"
|
|
1443
|
+
}
|
|
1444
|
+
];
|
|
1445
|
+
function uniqueValues$1(values) {
|
|
1446
|
+
return Array.from(new Set(values));
|
|
1447
|
+
}
|
|
1448
|
+
function hasReactBasedFrontend$1(frontend) {
|
|
1449
|
+
return frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("tanstack-start") || frontend.includes("next");
|
|
1450
|
+
}
|
|
1451
|
+
function getRecommendedMcpServers(config) {
|
|
1452
|
+
const servers = [];
|
|
1453
|
+
servers.push({
|
|
1454
|
+
key: "context7",
|
|
1455
|
+
label: "Context7",
|
|
1456
|
+
name: "context7",
|
|
1457
|
+
target: "@upstash/context7-mcp"
|
|
1458
|
+
});
|
|
1459
|
+
if (config.runtime === "workers" || config.webDeploy === "cloudflare" || config.serverDeploy === "cloudflare") servers.push({
|
|
1460
|
+
key: "cloudflare-docs",
|
|
1461
|
+
label: "Cloudflare Docs",
|
|
1462
|
+
name: "cloudflare-docs",
|
|
1463
|
+
target: "https://docs.mcp.cloudflare.com/sse",
|
|
1464
|
+
transport: "sse"
|
|
1465
|
+
});
|
|
1466
|
+
if (config.backend === "convex") servers.push({
|
|
1467
|
+
key: "convex",
|
|
1468
|
+
label: "Convex",
|
|
1469
|
+
name: "convex",
|
|
1470
|
+
target: "npx -y convex@latest mcp start"
|
|
1471
|
+
});
|
|
1472
|
+
if (hasReactBasedFrontend$1(config.frontend)) servers.push({
|
|
1473
|
+
key: "shadcn",
|
|
1474
|
+
label: "shadcn/ui",
|
|
1475
|
+
name: "shadcn",
|
|
1476
|
+
target: "npx -y shadcn@latest mcp"
|
|
1477
|
+
});
|
|
1478
|
+
if (config.frontend.includes("next")) servers.push({
|
|
1479
|
+
key: "next-devtools",
|
|
1480
|
+
label: "Next Devtools",
|
|
1481
|
+
name: "next-devtools",
|
|
1482
|
+
target: "npx -y next-devtools-mcp@latest"
|
|
1483
|
+
});
|
|
1484
|
+
if (config.frontend.includes("nuxt")) servers.push({
|
|
1485
|
+
key: "nuxt-docs",
|
|
1486
|
+
label: "Nuxt Docs",
|
|
1487
|
+
name: "nuxt",
|
|
1488
|
+
target: "https://nuxt.com/mcp"
|
|
1489
|
+
}, {
|
|
1490
|
+
key: "nuxt-ui-docs",
|
|
1491
|
+
label: "Nuxt UI Docs",
|
|
1492
|
+
name: "nuxt-ui",
|
|
1493
|
+
target: "https://ui.nuxt.com/mcp"
|
|
1494
|
+
});
|
|
1495
|
+
if (config.frontend.includes("svelte")) servers.push({
|
|
1496
|
+
key: "svelte-docs",
|
|
1497
|
+
label: "Svelte Docs",
|
|
1498
|
+
name: "svelte",
|
|
1499
|
+
target: "https://mcp.svelte.dev/mcp"
|
|
1500
|
+
});
|
|
1501
|
+
if (config.frontend.includes("astro")) servers.push({
|
|
1502
|
+
key: "astro-docs",
|
|
1503
|
+
label: "Astro Docs",
|
|
1504
|
+
name: "astro-docs",
|
|
1505
|
+
target: "https://mcp.docs.astro.build/mcp"
|
|
1506
|
+
});
|
|
1507
|
+
if (config.dbSetup === "planetscale") servers.push({
|
|
1508
|
+
key: "planetscale",
|
|
1509
|
+
label: "PlanetScale",
|
|
1510
|
+
name: "planetscale",
|
|
1511
|
+
target: "https://mcp.pscale.dev/mcp/planetscale"
|
|
1512
|
+
});
|
|
1513
|
+
if (config.dbSetup === "neon") servers.push({
|
|
1514
|
+
key: "neon",
|
|
1515
|
+
label: "Neon",
|
|
1516
|
+
name: "neon",
|
|
1517
|
+
target: "https://mcp.neon.tech/mcp"
|
|
1518
|
+
});
|
|
1519
|
+
if (config.dbSetup === "supabase") servers.push({
|
|
1520
|
+
key: "supabase",
|
|
1521
|
+
label: "Supabase",
|
|
1522
|
+
name: "supabase",
|
|
1523
|
+
target: "https://mcp.supabase.com/mcp"
|
|
1524
|
+
});
|
|
1525
|
+
if (config.auth === "better-auth") servers.push({
|
|
1526
|
+
key: "better-auth",
|
|
1527
|
+
label: "Better Auth",
|
|
1528
|
+
name: "better-auth",
|
|
1529
|
+
target: "https://mcp.inkeep.com/better-auth/mcp"
|
|
1530
|
+
});
|
|
1531
|
+
if (config.payments === "polar") servers.push({
|
|
1532
|
+
key: "polar",
|
|
1533
|
+
label: "Polar",
|
|
1534
|
+
name: "polar",
|
|
1535
|
+
target: "https://mcp.polar.sh/mcp/polar-mcp"
|
|
1536
|
+
});
|
|
1537
|
+
return servers;
|
|
1538
|
+
}
|
|
1539
|
+
function filterAgentsForScope(scope) {
|
|
1540
|
+
return MCP_AGENTS.filter((a) => a.scope === "both" || a.scope === scope);
|
|
1541
|
+
}
|
|
1542
|
+
async function setupMcp(config) {
|
|
1543
|
+
if (shouldSkipExternalCommands()) return Result.ok(void 0);
|
|
1544
|
+
const { packageManager, projectDir } = config;
|
|
1545
|
+
log.info("Setting up MCP servers...");
|
|
1546
|
+
const scope = await select({
|
|
1547
|
+
message: "Where should MCP servers be installed?",
|
|
1548
|
+
options: [{
|
|
1549
|
+
value: "project",
|
|
1550
|
+
label: "Project",
|
|
1551
|
+
hint: "Writes to project config files (recommended for teams)"
|
|
1552
|
+
}, {
|
|
1553
|
+
value: "global",
|
|
1554
|
+
label: "Global",
|
|
1555
|
+
hint: "Writes to user-level config files (personal machine)"
|
|
1556
|
+
}],
|
|
1557
|
+
initialValue: "project"
|
|
1558
|
+
});
|
|
1559
|
+
if (isCancel(scope)) return Result.err(new UserCancelledError({ message: "Operation cancelled" }));
|
|
1560
|
+
const recommendedServers = getRecommendedMcpServers(config);
|
|
1561
|
+
if (recommendedServers.length === 0) return Result.ok(void 0);
|
|
1562
|
+
const serverOptions = recommendedServers.map((s) => ({
|
|
1563
|
+
value: s.key,
|
|
1564
|
+
label: s.label,
|
|
1565
|
+
hint: s.target
|
|
1566
|
+
}));
|
|
1567
|
+
const selectedServerKeys = await multiselect({
|
|
1568
|
+
message: "Select MCP servers to install",
|
|
1569
|
+
options: serverOptions,
|
|
1570
|
+
required: false,
|
|
1571
|
+
initialValues: serverOptions.map((o) => o.value)
|
|
1572
|
+
});
|
|
1573
|
+
if (isCancel(selectedServerKeys)) return Result.err(new UserCancelledError({ message: "Operation cancelled" }));
|
|
1574
|
+
if (selectedServerKeys.length === 0) return Result.ok(void 0);
|
|
1575
|
+
const agentOptions = filterAgentsForScope(scope).map((a) => ({
|
|
1576
|
+
value: a.value,
|
|
1577
|
+
label: a.label
|
|
1578
|
+
}));
|
|
1579
|
+
const selectedAgents = await multiselect({
|
|
1580
|
+
message: "Select agents to install MCP servers to",
|
|
1581
|
+
options: agentOptions,
|
|
1582
|
+
required: false,
|
|
1583
|
+
initialValues: uniqueValues$1([
|
|
1584
|
+
"cursor",
|
|
1585
|
+
"claude-code",
|
|
1586
|
+
"vscode"
|
|
1587
|
+
].filter((a) => agentOptions.some((o) => o.value === a)))
|
|
1588
|
+
});
|
|
1589
|
+
if (isCancel(selectedAgents)) return Result.err(new UserCancelledError({ message: "Operation cancelled" }));
|
|
1590
|
+
if (selectedAgents.length === 0) return Result.ok(void 0);
|
|
1591
|
+
const serversByKey = new Map(recommendedServers.map((s) => [s.key, s]));
|
|
1592
|
+
const selectedServers = [];
|
|
1593
|
+
for (const key of selectedServerKeys) {
|
|
1594
|
+
const server = serversByKey.get(key);
|
|
1595
|
+
if (server) selectedServers.push(server);
|
|
1596
|
+
}
|
|
1597
|
+
if (selectedServers.length === 0) return Result.ok(void 0);
|
|
1598
|
+
const installSpinner = spinner();
|
|
1599
|
+
installSpinner.start("Installing MCP servers...");
|
|
1600
|
+
const runner = getPackageRunnerPrefix(packageManager);
|
|
1601
|
+
const globalFlags = scope === "global" ? ["-g"] : [];
|
|
1602
|
+
for (const server of selectedServers) {
|
|
1603
|
+
const transportFlags = server.transport ? ["-t", server.transport] : [];
|
|
1604
|
+
const headerFlags = (server.headers ?? []).flatMap((h) => ["--header", h]);
|
|
1605
|
+
const agentFlags = selectedAgents.flatMap((a) => ["-a", a]);
|
|
1606
|
+
const args = [
|
|
1607
|
+
...runner,
|
|
1608
|
+
"add-mcp@latest",
|
|
1609
|
+
server.target,
|
|
1610
|
+
"--name",
|
|
1611
|
+
server.name,
|
|
1612
|
+
...transportFlags,
|
|
1613
|
+
...headerFlags,
|
|
1614
|
+
...agentFlags,
|
|
1615
|
+
...globalFlags,
|
|
1616
|
+
"-y"
|
|
1617
|
+
];
|
|
1618
|
+
if ((await Result.tryPromise({
|
|
1619
|
+
try: async () => {
|
|
1620
|
+
await $({
|
|
1621
|
+
cwd: projectDir,
|
|
1622
|
+
env: { CI: "true" }
|
|
1623
|
+
})`${args}`;
|
|
1624
|
+
},
|
|
1625
|
+
catch: (e) => new AddonSetupError({
|
|
1626
|
+
addon: "mcp",
|
|
1627
|
+
message: `Failed to install MCP server '${server.name}': ${e instanceof Error ? e.message : String(e)}`,
|
|
1628
|
+
cause: e
|
|
1629
|
+
})
|
|
1630
|
+
})).isErr()) log.warn(pc.yellow(`Warning: Could not install MCP server '${server.name}'`));
|
|
1631
|
+
}
|
|
1632
|
+
installSpinner.stop("MCP servers installed");
|
|
1633
|
+
return Result.ok(void 0);
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1387
1636
|
//#endregion
|
|
1388
1637
|
//#region src/helpers/addons/oxlint-setup.ts
|
|
1389
1638
|
async function setupOxlint(projectDir, packageManager) {
|
|
@@ -1532,58 +1781,20 @@ async function addRulerScriptToPackageJson(projectDir, packageManager) {
|
|
|
1532
1781
|
//#endregion
|
|
1533
1782
|
//#region src/helpers/addons/skills-setup.ts
|
|
1534
1783
|
const SKILL_SOURCES = {
|
|
1535
|
-
"vercel-labs/agent-skills": {
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
},
|
|
1539
|
-
"vercel/
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
},
|
|
1543
|
-
"
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
},
|
|
1547
|
-
"
|
|
1548
|
-
|
|
1549
|
-
label: "Hono Backend"
|
|
1550
|
-
},
|
|
1551
|
-
"vercel-labs/next-skills": {
|
|
1552
|
-
source: "vercel-labs/next-skills",
|
|
1553
|
-
label: "Next.js Best Practices"
|
|
1554
|
-
},
|
|
1555
|
-
"heroui-inc/heroui": {
|
|
1556
|
-
source: "heroui-inc/heroui",
|
|
1557
|
-
label: "HeroUI Native"
|
|
1558
|
-
},
|
|
1559
|
-
"better-auth/skills": {
|
|
1560
|
-
source: "better-auth/skills",
|
|
1561
|
-
label: "Better Auth"
|
|
1562
|
-
},
|
|
1563
|
-
"neondatabase/agent-skills": {
|
|
1564
|
-
source: "neondatabase/agent-skills",
|
|
1565
|
-
label: "Neon Database"
|
|
1566
|
-
},
|
|
1567
|
-
"supabase/agent-skills": {
|
|
1568
|
-
source: "supabase/agent-skills",
|
|
1569
|
-
label: "Supabase"
|
|
1570
|
-
},
|
|
1571
|
-
"expo/skills": {
|
|
1572
|
-
source: "expo/skills",
|
|
1573
|
-
label: "Expo"
|
|
1574
|
-
},
|
|
1575
|
-
"prisma/skills": {
|
|
1576
|
-
source: "prisma/skills",
|
|
1577
|
-
label: "Prisma"
|
|
1578
|
-
},
|
|
1579
|
-
"elysiajs/skills": {
|
|
1580
|
-
source: "elysiajs/skills",
|
|
1581
|
-
label: "ElysiaJS"
|
|
1582
|
-
},
|
|
1583
|
-
"waynesutton/convexskills": {
|
|
1584
|
-
source: "waynesutton/convexskills",
|
|
1585
|
-
label: "Convex"
|
|
1586
|
-
}
|
|
1784
|
+
"vercel-labs/agent-skills": { label: "Vercel Agent Skills" },
|
|
1785
|
+
"vercel/ai": { label: "Vercel AI SDK" },
|
|
1786
|
+
"vercel/turborepo": { label: "Turborepo" },
|
|
1787
|
+
"yusukebe/hono-skill": { label: "Hono Backend" },
|
|
1788
|
+
"vercel-labs/next-skills": { label: "Next.js Best Practices" },
|
|
1789
|
+
"nuxt/ui": { label: "Nuxt UI" },
|
|
1790
|
+
"heroui-inc/heroui": { label: "HeroUI Native" },
|
|
1791
|
+
"better-auth/skills": { label: "Better Auth" },
|
|
1792
|
+
"neondatabase/agent-skills": { label: "Neon Database" },
|
|
1793
|
+
"supabase/agent-skills": { label: "Supabase" },
|
|
1794
|
+
"expo/skills": { label: "Expo" },
|
|
1795
|
+
"prisma/skills": { label: "Prisma" },
|
|
1796
|
+
"elysiajs/skills": { label: "ElysiaJS" },
|
|
1797
|
+
"waynesutton/convexskills": { label: "Convex" }
|
|
1587
1798
|
};
|
|
1588
1799
|
const AVAILABLE_AGENTS = [
|
|
1589
1800
|
{
|
|
@@ -1687,19 +1898,24 @@ const AVAILABLE_AGENTS = [
|
|
|
1687
1898
|
label: "MCPJam"
|
|
1688
1899
|
}
|
|
1689
1900
|
];
|
|
1901
|
+
function hasReactBasedFrontend(frontend) {
|
|
1902
|
+
return frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("tanstack-start") || frontend.includes("next");
|
|
1903
|
+
}
|
|
1904
|
+
function hasNativeFrontend(frontend) {
|
|
1905
|
+
return frontend.includes("native-bare") || frontend.includes("native-uniwind") || frontend.includes("native-unistyles");
|
|
1906
|
+
}
|
|
1690
1907
|
function getRecommendedSourceKeys(config) {
|
|
1691
1908
|
const sources = [];
|
|
1692
1909
|
const { frontend, backend, dbSetup, auth, examples, addons, orm } = config;
|
|
1693
|
-
|
|
1694
|
-
const hasNativeFrontend = frontend.includes("native-bare") || frontend.includes("native-uniwind") || frontend.includes("native-unistyles");
|
|
1695
|
-
if (hasReactBasedFrontend) sources.push("vercel-labs/agent-skills");
|
|
1910
|
+
if (hasReactBasedFrontend(frontend)) sources.push("vercel-labs/agent-skills");
|
|
1696
1911
|
if (frontend.includes("next")) sources.push("vercel-labs/next-skills");
|
|
1912
|
+
if (frontend.includes("nuxt")) sources.push("nuxt/ui");
|
|
1697
1913
|
if (frontend.includes("native-uniwind")) sources.push("heroui-inc/heroui");
|
|
1698
|
-
if (hasNativeFrontend) sources.push("expo/skills");
|
|
1914
|
+
if (hasNativeFrontend(frontend)) sources.push("expo/skills");
|
|
1699
1915
|
if (auth === "better-auth") sources.push("better-auth/skills");
|
|
1700
1916
|
if (dbSetup === "neon") sources.push("neondatabase/agent-skills");
|
|
1701
1917
|
if (dbSetup === "supabase") sources.push("supabase/agent-skills");
|
|
1702
|
-
if (orm === "prisma") sources.push("prisma/skills");
|
|
1918
|
+
if (orm === "prisma" || dbSetup === "prisma-postgres") sources.push("prisma/skills");
|
|
1703
1919
|
if (examples.includes("ai")) sources.push("vercel/ai");
|
|
1704
1920
|
if (addons.includes("turborepo")) sources.push("vercel/turborepo");
|
|
1705
1921
|
if (backend === "hono") sources.push("yusukebe/hono-skill");
|
|
@@ -1707,60 +1923,95 @@ function getRecommendedSourceKeys(config) {
|
|
|
1707
1923
|
if (backend === "convex") sources.push("waynesutton/convexskills");
|
|
1708
1924
|
return sources;
|
|
1709
1925
|
}
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1926
|
+
const CURATED_SKILLS_BY_SOURCE = {
|
|
1927
|
+
"vercel-labs/agent-skills": (config) => {
|
|
1928
|
+
const skills = [
|
|
1929
|
+
"web-design-guidelines",
|
|
1930
|
+
"vercel-composition-patterns",
|
|
1931
|
+
"vercel-react-best-practices"
|
|
1932
|
+
];
|
|
1933
|
+
if (hasNativeFrontend(config.frontend)) skills.push("vercel-react-native-skills");
|
|
1934
|
+
return skills;
|
|
1935
|
+
},
|
|
1936
|
+
"vercel/ai": () => ["ai-sdk"],
|
|
1937
|
+
"vercel/turborepo": () => ["turborepo"],
|
|
1938
|
+
"yusukebe/hono-skill": () => ["hono"],
|
|
1939
|
+
"vercel-labs/next-skills": () => ["next-best-practices", "next-cache-components"],
|
|
1940
|
+
"nuxt/ui": () => ["nuxt-ui"],
|
|
1941
|
+
"heroui-inc/heroui": () => ["heroui-native"],
|
|
1942
|
+
"better-auth/skills": () => ["better-auth-best-practices"],
|
|
1943
|
+
"neondatabase/agent-skills": () => ["neon-postgres"],
|
|
1944
|
+
"supabase/agent-skills": () => ["supabase-postgres-best-practices"],
|
|
1945
|
+
"expo/skills": (config) => {
|
|
1946
|
+
const skills = [
|
|
1947
|
+
"expo-dev-client",
|
|
1948
|
+
"building-native-ui",
|
|
1949
|
+
"native-data-fetching",
|
|
1950
|
+
"expo-deployment",
|
|
1951
|
+
"upgrading-expo",
|
|
1952
|
+
"expo-cicd-workflows"
|
|
1953
|
+
];
|
|
1954
|
+
if (config.frontend.includes("native-uniwind")) skills.push("expo-tailwind-setup");
|
|
1955
|
+
return skills;
|
|
1956
|
+
},
|
|
1957
|
+
"prisma/skills": (config) => {
|
|
1958
|
+
const skills = [];
|
|
1959
|
+
if (config.orm === "prisma") skills.push("prisma-cli", "prisma-client-api", "prisma-database-setup");
|
|
1960
|
+
if (config.dbSetup === "prisma-postgres") skills.push("prisma-postgres");
|
|
1961
|
+
return skills;
|
|
1962
|
+
},
|
|
1963
|
+
"elysiajs/skills": () => ["elysiajs"],
|
|
1964
|
+
"waynesutton/convexskills": () => [
|
|
1965
|
+
"convex-best-practices",
|
|
1966
|
+
"convex-functions",
|
|
1967
|
+
"convex-schema-validator",
|
|
1968
|
+
"convex-realtime",
|
|
1969
|
+
"convex-http-actions",
|
|
1970
|
+
"convex-cron-jobs",
|
|
1971
|
+
"convex-file-storage",
|
|
1972
|
+
"convex-migrations",
|
|
1973
|
+
"convex-security-check"
|
|
1974
|
+
]
|
|
1975
|
+
};
|
|
1976
|
+
function getCuratedSkillNamesForSourceKey(sourceKey, config) {
|
|
1977
|
+
return CURATED_SKILLS_BY_SOURCE[sourceKey](config);
|
|
1719
1978
|
}
|
|
1720
1979
|
function uniqueValues(values) {
|
|
1721
1980
|
return Array.from(new Set(values));
|
|
1722
1981
|
}
|
|
1723
|
-
async function fetchSkillsFromSource(source, packageManager, projectDir) {
|
|
1724
|
-
try {
|
|
1725
|
-
const args = getPackageExecutionArgs(packageManager, `skills@latest add ${source.source} --list`);
|
|
1726
|
-
return parseSkillsFromOutput((await $({
|
|
1727
|
-
cwd: projectDir,
|
|
1728
|
-
env: { CI: "true" }
|
|
1729
|
-
})`${args}`).stdout);
|
|
1730
|
-
} catch {
|
|
1731
|
-
return [];
|
|
1732
|
-
}
|
|
1733
|
-
}
|
|
1734
1982
|
async function setupSkills(config) {
|
|
1735
1983
|
if (shouldSkipExternalCommands()) return Result.ok(void 0);
|
|
1736
1984
|
const { packageManager, projectDir } = config;
|
|
1737
1985
|
const btsConfig = await readBtsConfig(projectDir);
|
|
1738
|
-
const
|
|
1986
|
+
const fullConfig = btsConfig ? {
|
|
1739
1987
|
...config,
|
|
1740
1988
|
addons: btsConfig.addons ?? config.addons
|
|
1741
|
-
} : config
|
|
1989
|
+
} : config;
|
|
1990
|
+
const recommendedSourceKeys = getRecommendedSourceKeys(fullConfig);
|
|
1742
1991
|
if (recommendedSourceKeys.length === 0) return Result.ok(void 0);
|
|
1743
|
-
const
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1992
|
+
const skillOptions = uniqueValues(recommendedSourceKeys).flatMap((sourceKey) => {
|
|
1993
|
+
const source = SKILL_SOURCES[sourceKey];
|
|
1994
|
+
return getCuratedSkillNamesForSourceKey(sourceKey, fullConfig).map((skillName) => ({
|
|
1995
|
+
value: `${sourceKey}::${skillName}`,
|
|
1996
|
+
label: skillName,
|
|
1997
|
+
hint: source.label
|
|
1998
|
+
}));
|
|
1999
|
+
});
|
|
2000
|
+
if (skillOptions.length === 0) return Result.ok(void 0);
|
|
2001
|
+
const scope = await select({
|
|
2002
|
+
message: "Where should skills be installed?",
|
|
2003
|
+
options: [{
|
|
2004
|
+
value: "project",
|
|
2005
|
+
label: "Project",
|
|
2006
|
+
hint: "Writes to project config files (recommended for teams)"
|
|
2007
|
+
}, {
|
|
2008
|
+
value: "global",
|
|
2009
|
+
label: "Global",
|
|
2010
|
+
hint: "Writes to user-level config files (personal machine)"
|
|
2011
|
+
}],
|
|
2012
|
+
initialValue: "project"
|
|
2013
|
+
});
|
|
2014
|
+
if (isCancel(scope)) return Result.err(new UserCancelledError({ message: "Operation cancelled" }));
|
|
1764
2015
|
const selectedSkills = await multiselect({
|
|
1765
2016
|
message: "Select skills to install",
|
|
1766
2017
|
options: skillOptions,
|
|
@@ -1790,11 +2041,12 @@ async function setupSkills(config) {
|
|
|
1790
2041
|
const installSpinner = spinner();
|
|
1791
2042
|
installSpinner.start("Installing skills...");
|
|
1792
2043
|
const agentFlags = selectedAgents.map((a) => `-a ${a}`).join(" ");
|
|
2044
|
+
const globalFlag = scope === "global" ? "-g" : "";
|
|
1793
2045
|
for (const [source, skills] of Object.entries(skillsBySource)) {
|
|
1794
2046
|
const skillFlags = skills.map((s) => `-s ${s}`).join(" ");
|
|
1795
2047
|
if ((await Result.tryPromise({
|
|
1796
2048
|
try: async () => {
|
|
1797
|
-
const args = getPackageExecutionArgs(packageManager, `skills@latest add ${source} ${skillFlags} ${agentFlags} -y`);
|
|
2049
|
+
const args = getPackageExecutionArgs(packageManager, `skills@latest add ${source} ${globalFlag} ${skillFlags} ${agentFlags} -y`);
|
|
1798
2050
|
await $({
|
|
1799
2051
|
cwd: projectDir,
|
|
1800
2052
|
env: { CI: "true" }
|
|
@@ -2294,6 +2546,7 @@ async function setupAddons(config) {
|
|
|
2294
2546
|
if (addons.includes("wxt")) await runSetup(() => setupWxt(config));
|
|
2295
2547
|
if (addons.includes("ruler")) await runSetup(() => setupRuler(config));
|
|
2296
2548
|
if (addons.includes("skills")) await runSetup(() => setupSkills(config));
|
|
2549
|
+
if (addons.includes("mcp")) await runSetup(() => setupMcp(config));
|
|
2297
2550
|
}
|
|
2298
2551
|
async function setupBiome(projectDir) {
|
|
2299
2552
|
await addPackageDependency({
|
|
@@ -5437,7 +5690,7 @@ async function setPackageManagerVersion(projectDir, packageManager) {
|
|
|
5437
5690
|
if (!await fs.pathExists(pkgJsonPath)) return Result.ok(void 0);
|
|
5438
5691
|
const versionResult = await Result.tryPromise({
|
|
5439
5692
|
try: async () => {
|
|
5440
|
-
const { stdout } = await
|
|
5693
|
+
const { stdout } = await $({ cwd: os$1.tmpdir() })`${packageManager} -v`;
|
|
5441
5694
|
return stdout.trim();
|
|
5442
5695
|
},
|
|
5443
5696
|
catch: () => null
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-better-t-stack",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.20.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",
|
|
@@ -70,8 +70,8 @@
|
|
|
70
70
|
"prepublishOnly": "npm run build"
|
|
71
71
|
},
|
|
72
72
|
"dependencies": {
|
|
73
|
-
"@better-t-stack/template-generator": "^3.
|
|
74
|
-
"@better-t-stack/types": "^3.
|
|
73
|
+
"@better-t-stack/template-generator": "^3.20.0",
|
|
74
|
+
"@better-t-stack/types": "^3.20.0",
|
|
75
75
|
"@clack/core": "^1.0.0",
|
|
76
76
|
"@clack/prompts": "^1.0.0",
|
|
77
77
|
"@orpc/server": "^1.13.4",
|