create-better-t-stack 3.19.5 → 3.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +1 -1
- package/dist/{src-C8vduSK8.mjs → src-BqpeME-D.mjs} +372 -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-BqpeME-D.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,261 @@ 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 hasNativeFrontend$1(frontend) {
|
|
1452
|
+
return frontend.includes("native-bare") || frontend.includes("native-uniwind") || frontend.includes("native-unistyles");
|
|
1453
|
+
}
|
|
1454
|
+
function getRecommendedMcpServers(config) {
|
|
1455
|
+
const servers = [];
|
|
1456
|
+
servers.push({
|
|
1457
|
+
key: "context7",
|
|
1458
|
+
label: "Context7",
|
|
1459
|
+
name: "context7",
|
|
1460
|
+
target: "@upstash/context7-mcp"
|
|
1461
|
+
});
|
|
1462
|
+
if (config.runtime === "workers" || config.webDeploy === "cloudflare" || config.serverDeploy === "cloudflare") servers.push({
|
|
1463
|
+
key: "cloudflare-docs",
|
|
1464
|
+
label: "Cloudflare Docs",
|
|
1465
|
+
name: "cloudflare-docs",
|
|
1466
|
+
target: "https://docs.mcp.cloudflare.com/sse",
|
|
1467
|
+
transport: "sse"
|
|
1468
|
+
});
|
|
1469
|
+
if (config.backend === "convex") servers.push({
|
|
1470
|
+
key: "convex",
|
|
1471
|
+
label: "Convex",
|
|
1472
|
+
name: "convex",
|
|
1473
|
+
target: "npx -y convex@latest mcp start"
|
|
1474
|
+
});
|
|
1475
|
+
if (hasReactBasedFrontend$1(config.frontend)) servers.push({
|
|
1476
|
+
key: "shadcn",
|
|
1477
|
+
label: "shadcn/ui",
|
|
1478
|
+
name: "shadcn",
|
|
1479
|
+
target: "npx -y shadcn@latest mcp"
|
|
1480
|
+
});
|
|
1481
|
+
if (config.frontend.includes("next")) servers.push({
|
|
1482
|
+
key: "next-devtools",
|
|
1483
|
+
label: "Next Devtools",
|
|
1484
|
+
name: "next-devtools",
|
|
1485
|
+
target: "npx -y next-devtools-mcp@latest"
|
|
1486
|
+
});
|
|
1487
|
+
if (config.frontend.includes("nuxt")) servers.push({
|
|
1488
|
+
key: "nuxt-docs",
|
|
1489
|
+
label: "Nuxt Docs",
|
|
1490
|
+
name: "nuxt",
|
|
1491
|
+
target: "https://nuxt.com/mcp"
|
|
1492
|
+
}, {
|
|
1493
|
+
key: "nuxt-ui-docs",
|
|
1494
|
+
label: "Nuxt UI Docs",
|
|
1495
|
+
name: "nuxt-ui",
|
|
1496
|
+
target: "https://ui.nuxt.com/mcp"
|
|
1497
|
+
});
|
|
1498
|
+
if (config.frontend.includes("svelte")) servers.push({
|
|
1499
|
+
key: "svelte-docs",
|
|
1500
|
+
label: "Svelte Docs",
|
|
1501
|
+
name: "svelte",
|
|
1502
|
+
target: "https://mcp.svelte.dev/mcp"
|
|
1503
|
+
});
|
|
1504
|
+
if (config.frontend.includes("astro")) servers.push({
|
|
1505
|
+
key: "astro-docs",
|
|
1506
|
+
label: "Astro Docs",
|
|
1507
|
+
name: "astro-docs",
|
|
1508
|
+
target: "https://mcp.docs.astro.build/mcp"
|
|
1509
|
+
});
|
|
1510
|
+
if (config.dbSetup === "planetscale") servers.push({
|
|
1511
|
+
key: "planetscale",
|
|
1512
|
+
label: "PlanetScale",
|
|
1513
|
+
name: "planetscale",
|
|
1514
|
+
target: "https://mcp.pscale.dev/mcp/planetscale"
|
|
1515
|
+
});
|
|
1516
|
+
if (config.dbSetup === "neon") servers.push({
|
|
1517
|
+
key: "neon",
|
|
1518
|
+
label: "Neon",
|
|
1519
|
+
name: "neon",
|
|
1520
|
+
target: "https://mcp.neon.tech/mcp"
|
|
1521
|
+
});
|
|
1522
|
+
if (config.dbSetup === "supabase") servers.push({
|
|
1523
|
+
key: "supabase",
|
|
1524
|
+
label: "Supabase",
|
|
1525
|
+
name: "supabase",
|
|
1526
|
+
target: "https://mcp.supabase.com/mcp"
|
|
1527
|
+
});
|
|
1528
|
+
if (config.auth === "better-auth") servers.push({
|
|
1529
|
+
key: "better-auth",
|
|
1530
|
+
label: "Better Auth",
|
|
1531
|
+
name: "better-auth",
|
|
1532
|
+
target: "https://mcp.inkeep.com/better-auth/mcp"
|
|
1533
|
+
});
|
|
1534
|
+
if (config.auth === "clerk") servers.push({
|
|
1535
|
+
key: "clerk",
|
|
1536
|
+
label: "Clerk",
|
|
1537
|
+
name: "clerk",
|
|
1538
|
+
target: "https://mcp.clerk.com/mcp"
|
|
1539
|
+
});
|
|
1540
|
+
if (hasNativeFrontend$1(config.frontend)) servers.push({
|
|
1541
|
+
key: "expo",
|
|
1542
|
+
label: "Expo",
|
|
1543
|
+
name: "expo-mcp",
|
|
1544
|
+
target: "https://mcp.expo.dev/mcp"
|
|
1545
|
+
});
|
|
1546
|
+
if (config.payments === "polar") servers.push({
|
|
1547
|
+
key: "polar",
|
|
1548
|
+
label: "Polar",
|
|
1549
|
+
name: "polar",
|
|
1550
|
+
target: "https://mcp.polar.sh/mcp/polar-mcp"
|
|
1551
|
+
});
|
|
1552
|
+
return servers;
|
|
1553
|
+
}
|
|
1554
|
+
function filterAgentsForScope(scope) {
|
|
1555
|
+
return MCP_AGENTS.filter((a) => a.scope === "both" || a.scope === scope);
|
|
1556
|
+
}
|
|
1557
|
+
async function setupMcp(config) {
|
|
1558
|
+
if (shouldSkipExternalCommands()) return Result.ok(void 0);
|
|
1559
|
+
const { packageManager, projectDir } = config;
|
|
1560
|
+
log.info("Setting up MCP servers...");
|
|
1561
|
+
const scope = await select({
|
|
1562
|
+
message: "Where should MCP servers be installed?",
|
|
1563
|
+
options: [{
|
|
1564
|
+
value: "project",
|
|
1565
|
+
label: "Project",
|
|
1566
|
+
hint: "Writes to project config files (recommended for teams)"
|
|
1567
|
+
}, {
|
|
1568
|
+
value: "global",
|
|
1569
|
+
label: "Global",
|
|
1570
|
+
hint: "Writes to user-level config files (personal machine)"
|
|
1571
|
+
}],
|
|
1572
|
+
initialValue: "project"
|
|
1573
|
+
});
|
|
1574
|
+
if (isCancel(scope)) return Result.err(new UserCancelledError({ message: "Operation cancelled" }));
|
|
1575
|
+
const recommendedServers = getRecommendedMcpServers(config);
|
|
1576
|
+
if (recommendedServers.length === 0) return Result.ok(void 0);
|
|
1577
|
+
const serverOptions = recommendedServers.map((s) => ({
|
|
1578
|
+
value: s.key,
|
|
1579
|
+
label: s.label,
|
|
1580
|
+
hint: s.target
|
|
1581
|
+
}));
|
|
1582
|
+
const selectedServerKeys = await multiselect({
|
|
1583
|
+
message: "Select MCP servers to install",
|
|
1584
|
+
options: serverOptions,
|
|
1585
|
+
required: false,
|
|
1586
|
+
initialValues: serverOptions.map((o) => o.value)
|
|
1587
|
+
});
|
|
1588
|
+
if (isCancel(selectedServerKeys)) return Result.err(new UserCancelledError({ message: "Operation cancelled" }));
|
|
1589
|
+
if (selectedServerKeys.length === 0) return Result.ok(void 0);
|
|
1590
|
+
const agentOptions = filterAgentsForScope(scope).map((a) => ({
|
|
1591
|
+
value: a.value,
|
|
1592
|
+
label: a.label
|
|
1593
|
+
}));
|
|
1594
|
+
const selectedAgents = await multiselect({
|
|
1595
|
+
message: "Select agents to install MCP servers to",
|
|
1596
|
+
options: agentOptions,
|
|
1597
|
+
required: false,
|
|
1598
|
+
initialValues: uniqueValues$1([
|
|
1599
|
+
"cursor",
|
|
1600
|
+
"claude-code",
|
|
1601
|
+
"vscode"
|
|
1602
|
+
].filter((a) => agentOptions.some((o) => o.value === a)))
|
|
1603
|
+
});
|
|
1604
|
+
if (isCancel(selectedAgents)) return Result.err(new UserCancelledError({ message: "Operation cancelled" }));
|
|
1605
|
+
if (selectedAgents.length === 0) return Result.ok(void 0);
|
|
1606
|
+
const serversByKey = new Map(recommendedServers.map((s) => [s.key, s]));
|
|
1607
|
+
const selectedServers = [];
|
|
1608
|
+
for (const key of selectedServerKeys) {
|
|
1609
|
+
const server = serversByKey.get(key);
|
|
1610
|
+
if (server) selectedServers.push(server);
|
|
1611
|
+
}
|
|
1612
|
+
if (selectedServers.length === 0) return Result.ok(void 0);
|
|
1613
|
+
const installSpinner = spinner();
|
|
1614
|
+
installSpinner.start("Installing MCP servers...");
|
|
1615
|
+
const runner = getPackageRunnerPrefix(packageManager);
|
|
1616
|
+
const globalFlags = scope === "global" ? ["-g"] : [];
|
|
1617
|
+
for (const server of selectedServers) {
|
|
1618
|
+
const transportFlags = server.transport ? ["-t", server.transport] : [];
|
|
1619
|
+
const headerFlags = (server.headers ?? []).flatMap((h) => ["--header", h]);
|
|
1620
|
+
const agentFlags = selectedAgents.flatMap((a) => ["-a", a]);
|
|
1621
|
+
const args = [
|
|
1622
|
+
...runner,
|
|
1623
|
+
"add-mcp@latest",
|
|
1624
|
+
server.target,
|
|
1625
|
+
"--name",
|
|
1626
|
+
server.name,
|
|
1627
|
+
...transportFlags,
|
|
1628
|
+
...headerFlags,
|
|
1629
|
+
...agentFlags,
|
|
1630
|
+
...globalFlags,
|
|
1631
|
+
"-y"
|
|
1632
|
+
];
|
|
1633
|
+
if ((await Result.tryPromise({
|
|
1634
|
+
try: async () => {
|
|
1635
|
+
await $({
|
|
1636
|
+
cwd: projectDir,
|
|
1637
|
+
env: { CI: "true" }
|
|
1638
|
+
})`${args}`;
|
|
1639
|
+
},
|
|
1640
|
+
catch: (e) => new AddonSetupError({
|
|
1641
|
+
addon: "mcp",
|
|
1642
|
+
message: `Failed to install MCP server '${server.name}': ${e instanceof Error ? e.message : String(e)}`,
|
|
1643
|
+
cause: e
|
|
1644
|
+
})
|
|
1645
|
+
})).isErr()) log.warn(pc.yellow(`Warning: Could not install MCP server '${server.name}'`));
|
|
1646
|
+
}
|
|
1647
|
+
installSpinner.stop("MCP servers installed");
|
|
1648
|
+
return Result.ok(void 0);
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1387
1651
|
//#endregion
|
|
1388
1652
|
//#region src/helpers/addons/oxlint-setup.ts
|
|
1389
1653
|
async function setupOxlint(projectDir, packageManager) {
|
|
@@ -1532,58 +1796,20 @@ async function addRulerScriptToPackageJson(projectDir, packageManager) {
|
|
|
1532
1796
|
//#endregion
|
|
1533
1797
|
//#region src/helpers/addons/skills-setup.ts
|
|
1534
1798
|
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
|
-
}
|
|
1799
|
+
"vercel-labs/agent-skills": { label: "Vercel Agent Skills" },
|
|
1800
|
+
"vercel/ai": { label: "Vercel AI SDK" },
|
|
1801
|
+
"vercel/turborepo": { label: "Turborepo" },
|
|
1802
|
+
"yusukebe/hono-skill": { label: "Hono Backend" },
|
|
1803
|
+
"vercel-labs/next-skills": { label: "Next.js Best Practices" },
|
|
1804
|
+
"nuxt/ui": { label: "Nuxt UI" },
|
|
1805
|
+
"heroui-inc/heroui": { label: "HeroUI Native" },
|
|
1806
|
+
"better-auth/skills": { label: "Better Auth" },
|
|
1807
|
+
"neondatabase/agent-skills": { label: "Neon Database" },
|
|
1808
|
+
"supabase/agent-skills": { label: "Supabase" },
|
|
1809
|
+
"expo/skills": { label: "Expo" },
|
|
1810
|
+
"prisma/skills": { label: "Prisma" },
|
|
1811
|
+
"elysiajs/skills": { label: "ElysiaJS" },
|
|
1812
|
+
"waynesutton/convexskills": { label: "Convex" }
|
|
1587
1813
|
};
|
|
1588
1814
|
const AVAILABLE_AGENTS = [
|
|
1589
1815
|
{
|
|
@@ -1687,19 +1913,24 @@ const AVAILABLE_AGENTS = [
|
|
|
1687
1913
|
label: "MCPJam"
|
|
1688
1914
|
}
|
|
1689
1915
|
];
|
|
1916
|
+
function hasReactBasedFrontend(frontend) {
|
|
1917
|
+
return frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("tanstack-start") || frontend.includes("next");
|
|
1918
|
+
}
|
|
1919
|
+
function hasNativeFrontend(frontend) {
|
|
1920
|
+
return frontend.includes("native-bare") || frontend.includes("native-uniwind") || frontend.includes("native-unistyles");
|
|
1921
|
+
}
|
|
1690
1922
|
function getRecommendedSourceKeys(config) {
|
|
1691
1923
|
const sources = [];
|
|
1692
1924
|
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");
|
|
1925
|
+
if (hasReactBasedFrontend(frontend)) sources.push("vercel-labs/agent-skills");
|
|
1696
1926
|
if (frontend.includes("next")) sources.push("vercel-labs/next-skills");
|
|
1927
|
+
if (frontend.includes("nuxt")) sources.push("nuxt/ui");
|
|
1697
1928
|
if (frontend.includes("native-uniwind")) sources.push("heroui-inc/heroui");
|
|
1698
|
-
if (hasNativeFrontend) sources.push("expo/skills");
|
|
1929
|
+
if (hasNativeFrontend(frontend)) sources.push("expo/skills");
|
|
1699
1930
|
if (auth === "better-auth") sources.push("better-auth/skills");
|
|
1700
1931
|
if (dbSetup === "neon") sources.push("neondatabase/agent-skills");
|
|
1701
1932
|
if (dbSetup === "supabase") sources.push("supabase/agent-skills");
|
|
1702
|
-
if (orm === "prisma") sources.push("prisma/skills");
|
|
1933
|
+
if (orm === "prisma" || dbSetup === "prisma-postgres") sources.push("prisma/skills");
|
|
1703
1934
|
if (examples.includes("ai")) sources.push("vercel/ai");
|
|
1704
1935
|
if (addons.includes("turborepo")) sources.push("vercel/turborepo");
|
|
1705
1936
|
if (backend === "hono") sources.push("yusukebe/hono-skill");
|
|
@@ -1707,60 +1938,95 @@ function getRecommendedSourceKeys(config) {
|
|
|
1707
1938
|
if (backend === "convex") sources.push("waynesutton/convexskills");
|
|
1708
1939
|
return sources;
|
|
1709
1940
|
}
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1941
|
+
const CURATED_SKILLS_BY_SOURCE = {
|
|
1942
|
+
"vercel-labs/agent-skills": (config) => {
|
|
1943
|
+
const skills = [
|
|
1944
|
+
"web-design-guidelines",
|
|
1945
|
+
"vercel-composition-patterns",
|
|
1946
|
+
"vercel-react-best-practices"
|
|
1947
|
+
];
|
|
1948
|
+
if (hasNativeFrontend(config.frontend)) skills.push("vercel-react-native-skills");
|
|
1949
|
+
return skills;
|
|
1950
|
+
},
|
|
1951
|
+
"vercel/ai": () => ["ai-sdk"],
|
|
1952
|
+
"vercel/turborepo": () => ["turborepo"],
|
|
1953
|
+
"yusukebe/hono-skill": () => ["hono"],
|
|
1954
|
+
"vercel-labs/next-skills": () => ["next-best-practices", "next-cache-components"],
|
|
1955
|
+
"nuxt/ui": () => ["nuxt-ui"],
|
|
1956
|
+
"heroui-inc/heroui": () => ["heroui-native"],
|
|
1957
|
+
"better-auth/skills": () => ["better-auth-best-practices"],
|
|
1958
|
+
"neondatabase/agent-skills": () => ["neon-postgres"],
|
|
1959
|
+
"supabase/agent-skills": () => ["supabase-postgres-best-practices"],
|
|
1960
|
+
"expo/skills": (config) => {
|
|
1961
|
+
const skills = [
|
|
1962
|
+
"expo-dev-client",
|
|
1963
|
+
"building-native-ui",
|
|
1964
|
+
"native-data-fetching",
|
|
1965
|
+
"expo-deployment",
|
|
1966
|
+
"upgrading-expo",
|
|
1967
|
+
"expo-cicd-workflows"
|
|
1968
|
+
];
|
|
1969
|
+
if (config.frontend.includes("native-uniwind")) skills.push("expo-tailwind-setup");
|
|
1970
|
+
return skills;
|
|
1971
|
+
},
|
|
1972
|
+
"prisma/skills": (config) => {
|
|
1973
|
+
const skills = [];
|
|
1974
|
+
if (config.orm === "prisma") skills.push("prisma-cli", "prisma-client-api", "prisma-database-setup");
|
|
1975
|
+
if (config.dbSetup === "prisma-postgres") skills.push("prisma-postgres");
|
|
1976
|
+
return skills;
|
|
1977
|
+
},
|
|
1978
|
+
"elysiajs/skills": () => ["elysiajs"],
|
|
1979
|
+
"waynesutton/convexskills": () => [
|
|
1980
|
+
"convex-best-practices",
|
|
1981
|
+
"convex-functions",
|
|
1982
|
+
"convex-schema-validator",
|
|
1983
|
+
"convex-realtime",
|
|
1984
|
+
"convex-http-actions",
|
|
1985
|
+
"convex-cron-jobs",
|
|
1986
|
+
"convex-file-storage",
|
|
1987
|
+
"convex-migrations",
|
|
1988
|
+
"convex-security-check"
|
|
1989
|
+
]
|
|
1990
|
+
};
|
|
1991
|
+
function getCuratedSkillNamesForSourceKey(sourceKey, config) {
|
|
1992
|
+
return CURATED_SKILLS_BY_SOURCE[sourceKey](config);
|
|
1719
1993
|
}
|
|
1720
1994
|
function uniqueValues(values) {
|
|
1721
1995
|
return Array.from(new Set(values));
|
|
1722
1996
|
}
|
|
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
1997
|
async function setupSkills(config) {
|
|
1735
1998
|
if (shouldSkipExternalCommands()) return Result.ok(void 0);
|
|
1736
1999
|
const { packageManager, projectDir } = config;
|
|
1737
2000
|
const btsConfig = await readBtsConfig(projectDir);
|
|
1738
|
-
const
|
|
2001
|
+
const fullConfig = btsConfig ? {
|
|
1739
2002
|
...config,
|
|
1740
2003
|
addons: btsConfig.addons ?? config.addons
|
|
1741
|
-
} : config
|
|
2004
|
+
} : config;
|
|
2005
|
+
const recommendedSourceKeys = getRecommendedSourceKeys(fullConfig);
|
|
1742
2006
|
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
|
-
|
|
2007
|
+
const skillOptions = uniqueValues(recommendedSourceKeys).flatMap((sourceKey) => {
|
|
2008
|
+
const source = SKILL_SOURCES[sourceKey];
|
|
2009
|
+
return getCuratedSkillNamesForSourceKey(sourceKey, fullConfig).map((skillName) => ({
|
|
2010
|
+
value: `${sourceKey}::${skillName}`,
|
|
2011
|
+
label: skillName,
|
|
2012
|
+
hint: source.label
|
|
2013
|
+
}));
|
|
2014
|
+
});
|
|
2015
|
+
if (skillOptions.length === 0) return Result.ok(void 0);
|
|
2016
|
+
const scope = await select({
|
|
2017
|
+
message: "Where should skills be installed?",
|
|
2018
|
+
options: [{
|
|
2019
|
+
value: "project",
|
|
2020
|
+
label: "Project",
|
|
2021
|
+
hint: "Writes to project config files (recommended for teams)"
|
|
2022
|
+
}, {
|
|
2023
|
+
value: "global",
|
|
2024
|
+
label: "Global",
|
|
2025
|
+
hint: "Writes to user-level config files (personal machine)"
|
|
2026
|
+
}],
|
|
2027
|
+
initialValue: "project"
|
|
2028
|
+
});
|
|
2029
|
+
if (isCancel(scope)) return Result.err(new UserCancelledError({ message: "Operation cancelled" }));
|
|
1764
2030
|
const selectedSkills = await multiselect({
|
|
1765
2031
|
message: "Select skills to install",
|
|
1766
2032
|
options: skillOptions,
|
|
@@ -1790,11 +2056,12 @@ async function setupSkills(config) {
|
|
|
1790
2056
|
const installSpinner = spinner();
|
|
1791
2057
|
installSpinner.start("Installing skills...");
|
|
1792
2058
|
const agentFlags = selectedAgents.map((a) => `-a ${a}`).join(" ");
|
|
2059
|
+
const globalFlag = scope === "global" ? "-g" : "";
|
|
1793
2060
|
for (const [source, skills] of Object.entries(skillsBySource)) {
|
|
1794
2061
|
const skillFlags = skills.map((s) => `-s ${s}`).join(" ");
|
|
1795
2062
|
if ((await Result.tryPromise({
|
|
1796
2063
|
try: async () => {
|
|
1797
|
-
const args = getPackageExecutionArgs(packageManager, `skills@latest add ${source} ${skillFlags} ${agentFlags} -y`);
|
|
2064
|
+
const args = getPackageExecutionArgs(packageManager, `skills@latest add ${source} ${globalFlag} ${skillFlags} ${agentFlags} -y`);
|
|
1798
2065
|
await $({
|
|
1799
2066
|
cwd: projectDir,
|
|
1800
2067
|
env: { CI: "true" }
|
|
@@ -2294,6 +2561,7 @@ async function setupAddons(config) {
|
|
|
2294
2561
|
if (addons.includes("wxt")) await runSetup(() => setupWxt(config));
|
|
2295
2562
|
if (addons.includes("ruler")) await runSetup(() => setupRuler(config));
|
|
2296
2563
|
if (addons.includes("skills")) await runSetup(() => setupSkills(config));
|
|
2564
|
+
if (addons.includes("mcp")) await runSetup(() => setupMcp(config));
|
|
2297
2565
|
}
|
|
2298
2566
|
async function setupBiome(projectDir) {
|
|
2299
2567
|
await addPackageDependency({
|
|
@@ -5437,7 +5705,7 @@ async function setPackageManagerVersion(projectDir, packageManager) {
|
|
|
5437
5705
|
if (!await fs.pathExists(pkgJsonPath)) return Result.ok(void 0);
|
|
5438
5706
|
const versionResult = await Result.tryPromise({
|
|
5439
5707
|
try: async () => {
|
|
5440
|
-
const { stdout } = await
|
|
5708
|
+
const { stdout } = await $({ cwd: os$1.tmpdir() })`${packageManager} -v`;
|
|
5441
5709
|
return stdout.trim();
|
|
5442
5710
|
},
|
|
5443
5711
|
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.1",
|
|
4
4
|
"description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"better-auth",
|
|
@@ -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.1",
|
|
74
|
+
"@better-t-stack/types": "^3.20.1",
|
|
75
75
|
"@clack/core": "^1.0.0",
|
|
76
76
|
"@clack/prompts": "^1.0.0",
|
|
77
77
|
"@orpc/server": "^1.13.4",
|