create-better-t-stack 3.20.2 → 3.21.0-pr892.a7e1b0f
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/README.md +9 -1
- package/dist/cli.mjs +6 -2
- package/dist/index.d.mts +1 -0
- package/dist/index.mjs +1 -1
- package/dist/{src-BqpeME-D.mjs → src-Dtzva6-_.mjs} +832 -150
- package/dist/virtual.d.mts +2 -1
- package/dist/virtual.mjs +2 -1
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -58,7 +58,7 @@ Options:
|
|
|
58
58
|
--auth Include authentication
|
|
59
59
|
--no-auth Exclude authentication
|
|
60
60
|
--frontend <types...> Frontend types (tanstack-router, react-router, tanstack-start, next, nuxt, svelte, solid, native-bare, native-uniwind, native-unistyles, none)
|
|
61
|
-
--addons <types...> Additional addons (pwa, tauri, starlight, biome, lefthook, husky, turborepo,
|
|
61
|
+
--addons <types...> Additional addons (pwa, tauri, starlight, fumadocs, biome, lefthook, husky, turborepo, ultracite, oxlint, ruler, opentui, wxt, skills, mcp, none)
|
|
62
62
|
--examples <types...> Examples to include (todo, ai, none)
|
|
63
63
|
--git Initialize git repository
|
|
64
64
|
--no-git Skip git initialization
|
|
@@ -74,6 +74,14 @@ Options:
|
|
|
74
74
|
-h, --help Display help
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
+
Additional commands:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
create-better-t-stack add [options] # Add addons to an existing project
|
|
81
|
+
create-better-t-stack history [options] # Show or clear local scaffold history
|
|
82
|
+
create-better-t-stack mcp # Start MCP server over stdio
|
|
83
|
+
```
|
|
84
|
+
|
|
77
85
|
## Telemetry
|
|
78
86
|
|
|
79
87
|
This CLI collects anonymous usage data to help improve the tool. The data collected includes:
|
package/dist/cli.mjs
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { l as createBtsCli } from "./src-
|
|
2
|
+
import { h as startMcpServer, l as createBtsCli } from "./src-Dtzva6-_.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/cli.ts
|
|
5
|
-
|
|
5
|
+
if (process.argv[2] === "mcp") startMcpServer().catch((error) => {
|
|
6
|
+
console.error("Failed to start MCP server:", error);
|
|
7
|
+
process.exit(1);
|
|
8
|
+
});
|
|
9
|
+
else createBtsCli().run();
|
|
6
10
|
|
|
7
11
|
//#endregion
|
|
8
12
|
export { };
|
package/dist/index.d.mts
CHANGED
|
@@ -287,6 +287,7 @@ declare const router: {
|
|
|
287
287
|
clear: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
288
288
|
json: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
289
289
|
}, z.core.$strip>, _orpc_server0.Schema<void, void>, _orpc_server0.MergedErrorMap<Record<never, never>, Record<never, never>>, Record<never, never>>;
|
|
290
|
+
mcp: _orpc_server0.Procedure<_orpc_server0.MergedInitialContext<Record<never, never>, Record<never, never>, Record<never, never>>, Record<never, never>, _orpc_server0.Schema<unknown, unknown>, _orpc_server0.Schema<void, void>, _orpc_server0.MergedErrorMap<Record<never, never>, Record<never, never>>, Record<never, never>>;
|
|
290
291
|
};
|
|
291
292
|
declare function createBtsCli(): trpc_cli0.TrpcCli;
|
|
292
293
|
/**
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { _ as
|
|
2
|
+
import { S as ValidationError, _ as CompatibilityError, a as VirtualFileSystem, b as ProjectCreationError, c as create, d as docs, f as generate, g 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 DatabaseSetupError, x as UserCancelledError, y as DirectoryConflictError } from "./src-Dtzva6-_.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 };
|
|
@@ -20,6 +20,9 @@ import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
20
20
|
import { applyEdits, modify, parse } from "jsonc-parser";
|
|
21
21
|
import os$1 from "node:os";
|
|
22
22
|
import { format } from "oxfmt";
|
|
23
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
24
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
25
|
+
import * as z$1 from "zod/v4";
|
|
23
26
|
|
|
24
27
|
//#region src/utils/get-package-manager.ts
|
|
25
28
|
const getUserPkgManager = () => {
|
|
@@ -199,7 +202,7 @@ function getLatestCLIVersionResult() {
|
|
|
199
202
|
});
|
|
200
203
|
}
|
|
201
204
|
function getLatestCLIVersion() {
|
|
202
|
-
return getLatestCLIVersionResult().unwrapOr("1.0.0");
|
|
205
|
+
return getLatestCLIVersionResult().unwrapOr("1.0.0") ?? "1.0.0";
|
|
203
206
|
}
|
|
204
207
|
|
|
205
208
|
//#endregion
|
|
@@ -1336,20 +1339,33 @@ const TEMPLATES$2 = {
|
|
|
1336
1339
|
value: "tanstack-start-spa"
|
|
1337
1340
|
}
|
|
1338
1341
|
};
|
|
1339
|
-
async function setupFumadocs(config) {
|
|
1340
|
-
|
|
1342
|
+
async function setupFumadocs(config, context = {}) {
|
|
1343
|
+
const emit = context.collectExternalReport;
|
|
1344
|
+
if (shouldSkipExternalCommands()) {
|
|
1345
|
+
emit?.({
|
|
1346
|
+
addon: "fumadocs",
|
|
1347
|
+
status: "skipped",
|
|
1348
|
+
warning: "Skipped because BTS_SKIP_EXTERNAL_COMMANDS or BTS_TEST_MODE is enabled."
|
|
1349
|
+
});
|
|
1350
|
+
return Result.ok(void 0);
|
|
1351
|
+
}
|
|
1341
1352
|
const { packageManager, projectDir } = config;
|
|
1353
|
+
const isInteractive = context.interactive ?? true;
|
|
1342
1354
|
log.info("Setting up Fumadocs...");
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1355
|
+
let template = context.addonOptions?.fumadocs?.template ?? "next-mdx";
|
|
1356
|
+
if (isInteractive) {
|
|
1357
|
+
const selectedTemplate = await select({
|
|
1358
|
+
message: "Choose a template",
|
|
1359
|
+
options: Object.entries(TEMPLATES$2).map(([key, template]) => ({
|
|
1360
|
+
value: key,
|
|
1361
|
+
label: template.label,
|
|
1362
|
+
hint: template.hint
|
|
1363
|
+
})),
|
|
1364
|
+
initialValue: template
|
|
1365
|
+
});
|
|
1366
|
+
if (isCancel(selectedTemplate)) return userCancelled("Operation cancelled");
|
|
1367
|
+
template = selectedTemplate;
|
|
1368
|
+
}
|
|
1353
1369
|
const templateArg = TEMPLATES$2[template].value;
|
|
1354
1370
|
const isNextTemplate = template.startsWith("next-");
|
|
1355
1371
|
const options = [
|
|
@@ -1372,12 +1388,14 @@ async function setupFumadocs(config) {
|
|
|
1372
1388
|
})`${args}`;
|
|
1373
1389
|
const fumadocsDir = path.join(projectDir, "apps", "fumadocs");
|
|
1374
1390
|
const packageJsonPath = path.join(fumadocsDir, "package.json");
|
|
1375
|
-
if (await fs.pathExists(packageJsonPath)) {
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1391
|
+
if (!await fs.pathExists(packageJsonPath)) throw new AddonSetupError({
|
|
1392
|
+
addon: "fumadocs",
|
|
1393
|
+
message: "Fumadocs generator did not create apps/fumadocs/package.json. Upstream template shape may have changed."
|
|
1394
|
+
});
|
|
1395
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
1396
|
+
packageJson.name = "fumadocs";
|
|
1397
|
+
if (packageJson.scripts?.dev) packageJson.scripts.dev = `${packageJson.scripts.dev} --port=4000`;
|
|
1398
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
1381
1399
|
},
|
|
1382
1400
|
catch: (e) => new AddonSetupError({
|
|
1383
1401
|
addon: "fumadocs",
|
|
@@ -1387,9 +1405,24 @@ async function setupFumadocs(config) {
|
|
|
1387
1405
|
});
|
|
1388
1406
|
if (result.isErr()) {
|
|
1389
1407
|
s.stop("Failed to set up Fumadocs");
|
|
1408
|
+
emit?.({
|
|
1409
|
+
addon: "fumadocs",
|
|
1410
|
+
status: "failed",
|
|
1411
|
+
selectedOptions: { template },
|
|
1412
|
+
commands: [args.join(" ")],
|
|
1413
|
+
postChecks: ["apps/fumadocs/package.json exists", "apps/fumadocs/package.json scripts.dev uses --port=4000"],
|
|
1414
|
+
error: result.error.message
|
|
1415
|
+
});
|
|
1390
1416
|
return result;
|
|
1391
1417
|
}
|
|
1392
1418
|
s.stop("Fumadocs setup complete!");
|
|
1419
|
+
emit?.({
|
|
1420
|
+
addon: "fumadocs",
|
|
1421
|
+
status: "success",
|
|
1422
|
+
selectedOptions: { template },
|
|
1423
|
+
commands: [args.join(" ")],
|
|
1424
|
+
postChecks: ["apps/fumadocs/package.json exists", "apps/fumadocs/package.json scripts.dev uses --port=4000"]
|
|
1425
|
+
});
|
|
1393
1426
|
return Result.ok(void 0);
|
|
1394
1427
|
}
|
|
1395
1428
|
|
|
@@ -1554,24 +1587,37 @@ function getRecommendedMcpServers(config) {
|
|
|
1554
1587
|
function filterAgentsForScope(scope) {
|
|
1555
1588
|
return MCP_AGENTS.filter((a) => a.scope === "both" || a.scope === scope);
|
|
1556
1589
|
}
|
|
1557
|
-
async function setupMcp(config) {
|
|
1558
|
-
|
|
1590
|
+
async function setupMcp(config, context = {}) {
|
|
1591
|
+
const emit = context.collectExternalReport;
|
|
1592
|
+
if (shouldSkipExternalCommands()) {
|
|
1593
|
+
emit?.({
|
|
1594
|
+
addon: "mcp",
|
|
1595
|
+
status: "skipped",
|
|
1596
|
+
warning: "Skipped because BTS_SKIP_EXTERNAL_COMMANDS or BTS_TEST_MODE is enabled."
|
|
1597
|
+
});
|
|
1598
|
+
return Result.ok(void 0);
|
|
1599
|
+
}
|
|
1559
1600
|
const { packageManager, projectDir } = config;
|
|
1601
|
+
const isInteractive = context.interactive ?? true;
|
|
1560
1602
|
log.info("Setting up MCP servers...");
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1603
|
+
let scope = context.addonOptions?.mcp?.scope ?? "project";
|
|
1604
|
+
if (isInteractive) {
|
|
1605
|
+
const selectedScope = await select({
|
|
1606
|
+
message: "Where should MCP servers be installed?",
|
|
1607
|
+
options: [{
|
|
1608
|
+
value: "project",
|
|
1609
|
+
label: "Project",
|
|
1610
|
+
hint: "Writes to project config files (recommended for teams)"
|
|
1611
|
+
}, {
|
|
1612
|
+
value: "global",
|
|
1613
|
+
label: "Global",
|
|
1614
|
+
hint: "Writes to user-level config files (personal machine)"
|
|
1615
|
+
}],
|
|
1616
|
+
initialValue: scope
|
|
1617
|
+
});
|
|
1618
|
+
if (isCancel(selectedScope)) return Result.err(new UserCancelledError({ message: "Operation cancelled" }));
|
|
1619
|
+
scope = selectedScope;
|
|
1620
|
+
}
|
|
1575
1621
|
const recommendedServers = getRecommendedMcpServers(config);
|
|
1576
1622
|
if (recommendedServers.length === 0) return Result.ok(void 0);
|
|
1577
1623
|
const serverOptions = recommendedServers.map((s) => ({
|
|
@@ -1579,37 +1625,77 @@ async function setupMcp(config) {
|
|
|
1579
1625
|
label: s.label,
|
|
1580
1626
|
hint: s.target
|
|
1581
1627
|
}));
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1628
|
+
let selectedServerKeys = context.addonOptions?.mcp?.serverKeys?.length === 0 ? [] : context.addonOptions?.mcp?.serverKeys ?? serverOptions.map((o) => o.value);
|
|
1629
|
+
if (isInteractive) {
|
|
1630
|
+
const selectedServersResult = await multiselect({
|
|
1631
|
+
message: "Select MCP servers to install",
|
|
1632
|
+
options: serverOptions,
|
|
1633
|
+
required: false,
|
|
1634
|
+
initialValues: selectedServerKeys
|
|
1635
|
+
});
|
|
1636
|
+
if (isCancel(selectedServersResult)) return Result.err(new UserCancelledError({ message: "Operation cancelled" }));
|
|
1637
|
+
selectedServerKeys = selectedServersResult;
|
|
1638
|
+
}
|
|
1639
|
+
if (selectedServerKeys.length === 0) {
|
|
1640
|
+
emit?.({
|
|
1641
|
+
addon: "mcp",
|
|
1642
|
+
status: "warning",
|
|
1643
|
+
selectedOptions: { scope },
|
|
1644
|
+
warning: "No MCP servers were selected for installation."
|
|
1645
|
+
});
|
|
1646
|
+
return Result.ok(void 0);
|
|
1647
|
+
}
|
|
1590
1648
|
const agentOptions = filterAgentsForScope(scope).map((a) => ({
|
|
1591
1649
|
value: a.value,
|
|
1592
1650
|
label: a.label
|
|
1593
1651
|
}));
|
|
1594
|
-
const
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1652
|
+
const defaultAgents = uniqueValues$1([
|
|
1653
|
+
"cursor",
|
|
1654
|
+
"claude-code",
|
|
1655
|
+
"vscode"
|
|
1656
|
+
].filter((a) => agentOptions.some((o) => o.value === a)));
|
|
1657
|
+
let selectedAgents = uniqueValues$1((context.addonOptions?.mcp?.agents ?? defaultAgents).filter((agent) => agentOptions.some((option) => option.value === agent)));
|
|
1658
|
+
if (isInteractive) {
|
|
1659
|
+
const selectedAgentsResult = await multiselect({
|
|
1660
|
+
message: "Select agents to install MCP servers to",
|
|
1661
|
+
options: agentOptions,
|
|
1662
|
+
required: false,
|
|
1663
|
+
initialValues: selectedAgents
|
|
1664
|
+
});
|
|
1665
|
+
if (isCancel(selectedAgentsResult)) return Result.err(new UserCancelledError({ message: "Operation cancelled" }));
|
|
1666
|
+
selectedAgents = selectedAgentsResult;
|
|
1667
|
+
}
|
|
1668
|
+
if (selectedAgents.length === 0) {
|
|
1669
|
+
emit?.({
|
|
1670
|
+
addon: "mcp",
|
|
1671
|
+
status: "warning",
|
|
1672
|
+
selectedOptions: {
|
|
1673
|
+
scope,
|
|
1674
|
+
selectedServerKeys
|
|
1675
|
+
},
|
|
1676
|
+
warning: "No agents were selected for MCP server installation."
|
|
1677
|
+
});
|
|
1678
|
+
return Result.ok(void 0);
|
|
1679
|
+
}
|
|
1606
1680
|
const serversByKey = new Map(recommendedServers.map((s) => [s.key, s]));
|
|
1607
1681
|
const selectedServers = [];
|
|
1608
1682
|
for (const key of selectedServerKeys) {
|
|
1609
1683
|
const server = serversByKey.get(key);
|
|
1610
1684
|
if (server) selectedServers.push(server);
|
|
1611
1685
|
}
|
|
1612
|
-
if (selectedServers.length === 0)
|
|
1686
|
+
if (selectedServers.length === 0) {
|
|
1687
|
+
emit?.({
|
|
1688
|
+
addon: "mcp",
|
|
1689
|
+
status: "warning",
|
|
1690
|
+
selectedOptions: {
|
|
1691
|
+
scope,
|
|
1692
|
+
selectedServerKeys,
|
|
1693
|
+
selectedAgents
|
|
1694
|
+
},
|
|
1695
|
+
warning: "No matching recommended MCP servers were found for the selected keys."
|
|
1696
|
+
});
|
|
1697
|
+
return Result.ok(void 0);
|
|
1698
|
+
}
|
|
1613
1699
|
const installSpinner = spinner();
|
|
1614
1700
|
installSpinner.start("Installing MCP servers...");
|
|
1615
1701
|
const runner = getPackageRunnerPrefix(packageManager);
|
|
@@ -1630,7 +1716,8 @@ async function setupMcp(config) {
|
|
|
1630
1716
|
...globalFlags,
|
|
1631
1717
|
"-y"
|
|
1632
1718
|
];
|
|
1633
|
-
|
|
1719
|
+
const postChecks = [`MCP agent configs updated with server '${server.name}'`];
|
|
1720
|
+
const installResult = await Result.tryPromise({
|
|
1634
1721
|
try: async () => {
|
|
1635
1722
|
await $({
|
|
1636
1723
|
cwd: projectDir,
|
|
@@ -1642,7 +1729,36 @@ async function setupMcp(config) {
|
|
|
1642
1729
|
message: `Failed to install MCP server '${server.name}': ${e instanceof Error ? e.message : String(e)}`,
|
|
1643
1730
|
cause: e
|
|
1644
1731
|
})
|
|
1645
|
-
})
|
|
1732
|
+
});
|
|
1733
|
+
if (installResult.isErr()) {
|
|
1734
|
+
log.warn(pc.yellow(`Warning: Could not install MCP server '${server.name}'`));
|
|
1735
|
+
emit?.({
|
|
1736
|
+
addon: "mcp",
|
|
1737
|
+
status: "warning",
|
|
1738
|
+
selectedOptions: {
|
|
1739
|
+
scope,
|
|
1740
|
+
server: server.key,
|
|
1741
|
+
serverName: server.name,
|
|
1742
|
+
agents: selectedAgents
|
|
1743
|
+
},
|
|
1744
|
+
commands: [args.join(" ")],
|
|
1745
|
+
postChecks,
|
|
1746
|
+
warning: installResult.error.message
|
|
1747
|
+
});
|
|
1748
|
+
continue;
|
|
1749
|
+
}
|
|
1750
|
+
emit?.({
|
|
1751
|
+
addon: "mcp",
|
|
1752
|
+
status: "success",
|
|
1753
|
+
selectedOptions: {
|
|
1754
|
+
scope,
|
|
1755
|
+
server: server.key,
|
|
1756
|
+
serverName: server.name,
|
|
1757
|
+
agents: selectedAgents
|
|
1758
|
+
},
|
|
1759
|
+
commands: [args.join(" ")],
|
|
1760
|
+
postChecks
|
|
1761
|
+
});
|
|
1646
1762
|
}
|
|
1647
1763
|
installSpinner.stop("MCP servers installed");
|
|
1648
1764
|
return Result.ok(void 0);
|
|
@@ -1994,9 +2110,18 @@ function getCuratedSkillNamesForSourceKey(sourceKey, config) {
|
|
|
1994
2110
|
function uniqueValues(values) {
|
|
1995
2111
|
return Array.from(new Set(values));
|
|
1996
2112
|
}
|
|
1997
|
-
async function setupSkills(config) {
|
|
1998
|
-
|
|
2113
|
+
async function setupSkills(config, context = {}) {
|
|
2114
|
+
const emit = context.collectExternalReport;
|
|
2115
|
+
if (shouldSkipExternalCommands()) {
|
|
2116
|
+
emit?.({
|
|
2117
|
+
addon: "skills",
|
|
2118
|
+
status: "skipped",
|
|
2119
|
+
warning: "Skipped because BTS_SKIP_EXTERNAL_COMMANDS or BTS_TEST_MODE is enabled."
|
|
2120
|
+
});
|
|
2121
|
+
return Result.ok(void 0);
|
|
2122
|
+
}
|
|
1999
2123
|
const { packageManager, projectDir } = config;
|
|
2124
|
+
const isInteractive = context.interactive ?? true;
|
|
2000
2125
|
const btsConfig = await readBtsConfig(projectDir);
|
|
2001
2126
|
const fullConfig = btsConfig ? {
|
|
2002
2127
|
...config,
|
|
@@ -2013,40 +2138,73 @@ async function setupSkills(config) {
|
|
|
2013
2138
|
}));
|
|
2014
2139
|
});
|
|
2015
2140
|
if (skillOptions.length === 0) return Result.ok(void 0);
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
message: "
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
if (
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2141
|
+
let scope = context.addonOptions?.skills?.scope ?? "project";
|
|
2142
|
+
if (isInteractive) {
|
|
2143
|
+
const selectedScope = await select({
|
|
2144
|
+
message: "Where should skills be installed?",
|
|
2145
|
+
options: [{
|
|
2146
|
+
value: "project",
|
|
2147
|
+
label: "Project",
|
|
2148
|
+
hint: "Writes to project config files (recommended for teams)"
|
|
2149
|
+
}, {
|
|
2150
|
+
value: "global",
|
|
2151
|
+
label: "Global",
|
|
2152
|
+
hint: "Writes to user-level config files (personal machine)"
|
|
2153
|
+
}],
|
|
2154
|
+
initialValue: scope
|
|
2155
|
+
});
|
|
2156
|
+
if (isCancel(selectedScope)) return Result.err(new UserCancelledError({ message: "Operation cancelled" }));
|
|
2157
|
+
scope = selectedScope;
|
|
2158
|
+
}
|
|
2159
|
+
const allSkillValues = skillOptions.map((opt) => opt.value);
|
|
2160
|
+
let selectedSkills = context.addonOptions?.skills?.skillKeys?.length === 0 ? [] : context.addonOptions?.skills?.skillKeys ?? allSkillValues;
|
|
2161
|
+
if (isInteractive) {
|
|
2162
|
+
const selectedSkillsResult = await multiselect({
|
|
2163
|
+
message: "Select skills to install",
|
|
2164
|
+
options: skillOptions,
|
|
2165
|
+
required: false,
|
|
2166
|
+
initialValues: allSkillValues
|
|
2167
|
+
});
|
|
2168
|
+
if (isCancel(selectedSkillsResult)) return Result.err(new UserCancelledError({ message: "Operation cancelled" }));
|
|
2169
|
+
selectedSkills = selectedSkillsResult;
|
|
2170
|
+
}
|
|
2171
|
+
if (selectedSkills.length === 0) {
|
|
2172
|
+
emit?.({
|
|
2173
|
+
addon: "skills",
|
|
2174
|
+
status: "warning",
|
|
2175
|
+
selectedOptions: { scope },
|
|
2176
|
+
warning: "No skills were selected for installation."
|
|
2177
|
+
});
|
|
2178
|
+
return Result.ok(void 0);
|
|
2179
|
+
}
|
|
2180
|
+
let selectedAgents = context.addonOptions?.skills?.agents ?? [
|
|
2181
|
+
"cursor",
|
|
2182
|
+
"claude-code",
|
|
2183
|
+
"github-copilot"
|
|
2184
|
+
];
|
|
2185
|
+
selectedAgents = selectedAgents.filter((agent) => AVAILABLE_AGENTS.some((option) => option.value === agent));
|
|
2186
|
+
if (isInteractive) {
|
|
2187
|
+
const selectedAgentsResult = await multiselect({
|
|
2188
|
+
message: "Select agents to install skills to",
|
|
2189
|
+
options: AVAILABLE_AGENTS,
|
|
2190
|
+
required: false,
|
|
2191
|
+
initialValues: selectedAgents
|
|
2192
|
+
});
|
|
2193
|
+
if (isCancel(selectedAgentsResult)) return Result.err(new UserCancelledError({ message: "Operation cancelled" }));
|
|
2194
|
+
selectedAgents = selectedAgentsResult;
|
|
2195
|
+
}
|
|
2196
|
+
if (selectedAgents.length === 0) {
|
|
2197
|
+
emit?.({
|
|
2198
|
+
addon: "skills",
|
|
2199
|
+
status: "warning",
|
|
2200
|
+
selectedOptions: {
|
|
2201
|
+
scope,
|
|
2202
|
+
selectedSkills
|
|
2203
|
+
},
|
|
2204
|
+
warning: "No agents were selected for skills installation."
|
|
2205
|
+
});
|
|
2206
|
+
return Result.ok(void 0);
|
|
2207
|
+
}
|
|
2050
2208
|
const skillsBySource = {};
|
|
2051
2209
|
for (const skillKey of selectedSkills) {
|
|
2052
2210
|
const [source, skillName] = skillKey.split("::");
|
|
@@ -2059,7 +2217,7 @@ async function setupSkills(config) {
|
|
|
2059
2217
|
const globalFlag = scope === "global" ? "-g" : "";
|
|
2060
2218
|
for (const [source, skills] of Object.entries(skillsBySource)) {
|
|
2061
2219
|
const skillFlags = skills.map((s) => `-s ${s}`).join(" ");
|
|
2062
|
-
|
|
2220
|
+
const installResult = await Result.tryPromise({
|
|
2063
2221
|
try: async () => {
|
|
2064
2222
|
const args = getPackageExecutionArgs(packageManager, `skills@latest add ${source} ${globalFlag} ${skillFlags} ${agentFlags} -y`);
|
|
2065
2223
|
await $({
|
|
@@ -2072,7 +2230,32 @@ async function setupSkills(config) {
|
|
|
2072
2230
|
message: `Failed to install skills from ${source}: ${e instanceof Error ? e.message : String(e)}`,
|
|
2073
2231
|
cause: e
|
|
2074
2232
|
})
|
|
2075
|
-
})
|
|
2233
|
+
});
|
|
2234
|
+
if (installResult.isErr()) {
|
|
2235
|
+
log.warn(pc.yellow(`Warning: Could not install skills from ${source}`));
|
|
2236
|
+
emit?.({
|
|
2237
|
+
addon: "skills",
|
|
2238
|
+
status: "warning",
|
|
2239
|
+
selectedOptions: {
|
|
2240
|
+
scope,
|
|
2241
|
+
source,
|
|
2242
|
+
skills,
|
|
2243
|
+
agents: selectedAgents
|
|
2244
|
+
},
|
|
2245
|
+
warning: installResult.error.message
|
|
2246
|
+
});
|
|
2247
|
+
continue;
|
|
2248
|
+
}
|
|
2249
|
+
emit?.({
|
|
2250
|
+
addon: "skills",
|
|
2251
|
+
status: "success",
|
|
2252
|
+
selectedOptions: {
|
|
2253
|
+
scope,
|
|
2254
|
+
source,
|
|
2255
|
+
skills,
|
|
2256
|
+
agents: selectedAgents
|
|
2257
|
+
}
|
|
2258
|
+
});
|
|
2076
2259
|
}
|
|
2077
2260
|
installSpinner.stop("Skills installed");
|
|
2078
2261
|
return Result.ok(void 0);
|
|
@@ -2080,8 +2263,16 @@ async function setupSkills(config) {
|
|
|
2080
2263
|
|
|
2081
2264
|
//#endregion
|
|
2082
2265
|
//#region src/helpers/addons/starlight-setup.ts
|
|
2083
|
-
async function setupStarlight(config) {
|
|
2084
|
-
|
|
2266
|
+
async function setupStarlight(config, context = {}) {
|
|
2267
|
+
const emit = context.collectExternalReport;
|
|
2268
|
+
if (shouldSkipExternalCommands()) {
|
|
2269
|
+
emit?.({
|
|
2270
|
+
addon: "starlight",
|
|
2271
|
+
status: "skipped",
|
|
2272
|
+
warning: "Skipped because BTS_SKIP_EXTERNAL_COMMANDS or BTS_TEST_MODE is enabled."
|
|
2273
|
+
});
|
|
2274
|
+
return Result.ok(void 0);
|
|
2275
|
+
}
|
|
2085
2276
|
const { packageManager, projectDir } = config;
|
|
2086
2277
|
const s = spinner();
|
|
2087
2278
|
s.start("Setting up Starlight docs...");
|
|
@@ -2112,20 +2303,48 @@ async function setupStarlight(config) {
|
|
|
2112
2303
|
});
|
|
2113
2304
|
if (result.isErr()) {
|
|
2114
2305
|
s.stop("Failed to set up Starlight docs");
|
|
2306
|
+
emit?.({
|
|
2307
|
+
addon: "starlight",
|
|
2308
|
+
status: "failed",
|
|
2309
|
+
commands: [args.join(" ")],
|
|
2310
|
+
postChecks: ["apps/docs exists"],
|
|
2311
|
+
error: result.error.message
|
|
2312
|
+
});
|
|
2115
2313
|
return result;
|
|
2116
2314
|
}
|
|
2117
2315
|
s.stop("Starlight docs setup successfully!");
|
|
2316
|
+
emit?.({
|
|
2317
|
+
addon: "starlight",
|
|
2318
|
+
status: "success",
|
|
2319
|
+
commands: [args.join(" ")],
|
|
2320
|
+
postChecks: ["apps/docs exists"]
|
|
2321
|
+
});
|
|
2118
2322
|
return Result.ok(void 0);
|
|
2119
2323
|
}
|
|
2120
2324
|
|
|
2121
2325
|
//#endregion
|
|
2122
2326
|
//#region src/helpers/addons/tauri-setup.ts
|
|
2123
|
-
async function setupTauri(config) {
|
|
2124
|
-
|
|
2327
|
+
async function setupTauri(config, context = {}) {
|
|
2328
|
+
const emit = context.collectExternalReport;
|
|
2329
|
+
if (shouldSkipExternalCommands()) {
|
|
2330
|
+
emit?.({
|
|
2331
|
+
addon: "tauri",
|
|
2332
|
+
status: "skipped",
|
|
2333
|
+
warning: "Skipped because BTS_SKIP_EXTERNAL_COMMANDS or BTS_TEST_MODE is enabled."
|
|
2334
|
+
});
|
|
2335
|
+
return Result.ok(void 0);
|
|
2336
|
+
}
|
|
2125
2337
|
const { packageManager, frontend, projectDir } = config;
|
|
2126
2338
|
const s = spinner();
|
|
2127
2339
|
const clientPackageDir = path.join(projectDir, "apps/web");
|
|
2128
|
-
if (!await fs.pathExists(clientPackageDir))
|
|
2340
|
+
if (!await fs.pathExists(clientPackageDir)) {
|
|
2341
|
+
emit?.({
|
|
2342
|
+
addon: "tauri",
|
|
2343
|
+
status: "skipped",
|
|
2344
|
+
warning: "Skipped because apps/web does not exist in this project."
|
|
2345
|
+
});
|
|
2346
|
+
return Result.ok(void 0);
|
|
2347
|
+
}
|
|
2129
2348
|
s.start("Setting up Tauri desktop app support...");
|
|
2130
2349
|
const hasReactRouter = frontend.includes("react-router");
|
|
2131
2350
|
const hasNuxt = frontend.includes("nuxt");
|
|
@@ -2159,9 +2378,22 @@ async function setupTauri(config) {
|
|
|
2159
2378
|
});
|
|
2160
2379
|
if (result.isErr()) {
|
|
2161
2380
|
s.stop("Failed to set up Tauri");
|
|
2381
|
+
emit?.({
|
|
2382
|
+
addon: "tauri",
|
|
2383
|
+
status: "failed",
|
|
2384
|
+
commands: [[...prefix, ...tauriArgs].join(" ")],
|
|
2385
|
+
postChecks: ["apps/web/src-tauri exists"],
|
|
2386
|
+
error: result.error.message
|
|
2387
|
+
});
|
|
2162
2388
|
return result;
|
|
2163
2389
|
}
|
|
2164
2390
|
s.stop("Tauri desktop app support configured successfully!");
|
|
2391
|
+
emit?.({
|
|
2392
|
+
addon: "tauri",
|
|
2393
|
+
status: "success",
|
|
2394
|
+
commands: [[...prefix, ...tauriArgs].join(" ")],
|
|
2395
|
+
postChecks: ["apps/web/src-tauri exists"]
|
|
2396
|
+
});
|
|
2165
2397
|
return Result.ok(void 0);
|
|
2166
2398
|
}
|
|
2167
2399
|
|
|
@@ -2438,20 +2670,33 @@ const TEMPLATES = {
|
|
|
2438
2670
|
hint: "Svelte template"
|
|
2439
2671
|
}
|
|
2440
2672
|
};
|
|
2441
|
-
async function setupWxt(config) {
|
|
2442
|
-
|
|
2673
|
+
async function setupWxt(config, context = {}) {
|
|
2674
|
+
const emit = context.collectExternalReport;
|
|
2675
|
+
if (shouldSkipExternalCommands()) {
|
|
2676
|
+
emit?.({
|
|
2677
|
+
addon: "wxt",
|
|
2678
|
+
status: "skipped",
|
|
2679
|
+
warning: "Skipped because BTS_SKIP_EXTERNAL_COMMANDS or BTS_TEST_MODE is enabled."
|
|
2680
|
+
});
|
|
2681
|
+
return Result.ok(void 0);
|
|
2682
|
+
}
|
|
2443
2683
|
const { packageManager, projectDir } = config;
|
|
2684
|
+
const isInteractive = context.interactive ?? true;
|
|
2444
2685
|
log.info("Setting up WXT...");
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2686
|
+
let template = context.addonOptions?.wxt?.template ?? "react";
|
|
2687
|
+
if (isInteractive) {
|
|
2688
|
+
const selectedTemplate = await select({
|
|
2689
|
+
message: "Choose a template",
|
|
2690
|
+
options: Object.entries(TEMPLATES).map(([key, template]) => ({
|
|
2691
|
+
value: key,
|
|
2692
|
+
label: template.label,
|
|
2693
|
+
hint: template.hint
|
|
2694
|
+
})),
|
|
2695
|
+
initialValue: template
|
|
2696
|
+
});
|
|
2697
|
+
if (isCancel(selectedTemplate)) return userCancelled("Operation cancelled");
|
|
2698
|
+
template = selectedTemplate;
|
|
2699
|
+
}
|
|
2455
2700
|
const args = getPackageExecutionArgs(packageManager, `wxt@latest init extension --template ${template} --pm ${packageManager}`);
|
|
2456
2701
|
const appsDir = path.join(projectDir, "apps");
|
|
2457
2702
|
const ensureDirResult = await Result.tryPromise({
|
|
@@ -2483,26 +2728,56 @@ async function setupWxt(config) {
|
|
|
2483
2728
|
});
|
|
2484
2729
|
if (initResult.isErr()) {
|
|
2485
2730
|
log.error(pc.red("Failed to set up WXT"));
|
|
2731
|
+
emit?.({
|
|
2732
|
+
addon: "wxt",
|
|
2733
|
+
status: "failed",
|
|
2734
|
+
selectedOptions: { template },
|
|
2735
|
+
commands: [args.join(" ")],
|
|
2736
|
+
postChecks: ["apps/extension/package.json exists", "apps/extension/package.json scripts.dev uses --port 5555"],
|
|
2737
|
+
error: initResult.error.message
|
|
2738
|
+
});
|
|
2486
2739
|
return initResult;
|
|
2487
2740
|
}
|
|
2488
2741
|
const extensionDir = path.join(projectDir, "apps", "extension");
|
|
2489
2742
|
const packageJsonPath = path.join(extensionDir, "package.json");
|
|
2490
|
-
|
|
2743
|
+
const updatePackageResult = await Result.tryPromise({
|
|
2491
2744
|
try: async () => {
|
|
2492
|
-
if (await fs.pathExists(packageJsonPath)) {
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2745
|
+
if (!await fs.pathExists(packageJsonPath)) throw new AddonSetupError({
|
|
2746
|
+
addon: "wxt",
|
|
2747
|
+
message: "WXT generator did not create apps/extension/package.json. Upstream template shape may have changed."
|
|
2748
|
+
});
|
|
2749
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
2750
|
+
packageJson.name = "extension";
|
|
2751
|
+
if (packageJson.scripts?.dev) packageJson.scripts.dev = `${packageJson.scripts.dev} --port 5555`;
|
|
2752
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
2498
2753
|
},
|
|
2499
2754
|
catch: (e) => new AddonSetupError({
|
|
2500
2755
|
addon: "wxt",
|
|
2501
2756
|
message: `Failed to update package.json: ${e instanceof Error ? e.message : String(e)}`,
|
|
2502
2757
|
cause: e
|
|
2503
2758
|
})
|
|
2504
|
-
})
|
|
2759
|
+
});
|
|
2760
|
+
if (updatePackageResult.isErr()) {
|
|
2761
|
+
log.warn(pc.yellow("WXT setup completed but failed to update package.json"));
|
|
2762
|
+
s.stop("WXT setup completed with warnings");
|
|
2763
|
+
emit?.({
|
|
2764
|
+
addon: "wxt",
|
|
2765
|
+
status: "warning",
|
|
2766
|
+
selectedOptions: { template },
|
|
2767
|
+
commands: [args.join(" ")],
|
|
2768
|
+
postChecks: ["apps/extension/package.json exists", "apps/extension/package.json scripts.dev uses --port 5555"],
|
|
2769
|
+
warning: updatePackageResult.error.message
|
|
2770
|
+
});
|
|
2771
|
+
return Result.ok(void 0);
|
|
2772
|
+
}
|
|
2505
2773
|
s.stop("WXT setup complete!");
|
|
2774
|
+
emit?.({
|
|
2775
|
+
addon: "wxt",
|
|
2776
|
+
status: "success",
|
|
2777
|
+
selectedOptions: { template },
|
|
2778
|
+
commands: [args.join(" ")],
|
|
2779
|
+
postChecks: ["apps/extension/package.json exists", "apps/extension/package.json scripts.dev uses --port 5555"]
|
|
2780
|
+
});
|
|
2506
2781
|
return Result.ok(void 0);
|
|
2507
2782
|
}
|
|
2508
2783
|
|
|
@@ -2526,14 +2801,14 @@ async function runAddonStep(addon, step) {
|
|
|
2526
2801
|
});
|
|
2527
2802
|
if (result.isErr()) consola.error(pc.red(result.error.message));
|
|
2528
2803
|
}
|
|
2529
|
-
async function setupAddons(config) {
|
|
2804
|
+
async function setupAddons(config, context = {}) {
|
|
2530
2805
|
const { addons, frontend, projectDir } = config;
|
|
2531
2806
|
const hasReactWebFrontend = frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("next");
|
|
2532
2807
|
const hasNuxtFrontend = frontend.includes("nuxt");
|
|
2533
2808
|
const hasSvelteFrontend = frontend.includes("svelte");
|
|
2534
2809
|
const hasSolidFrontend = frontend.includes("solid");
|
|
2535
2810
|
const hasNextFrontend = frontend.includes("next");
|
|
2536
|
-
if (addons.includes("tauri") && (hasReactWebFrontend || hasNuxtFrontend || hasSvelteFrontend || hasSolidFrontend || hasNextFrontend)) await runSetup(() => setupTauri(config));
|
|
2811
|
+
if (addons.includes("tauri") && (hasReactWebFrontend || hasNuxtFrontend || hasSvelteFrontend || hasSolidFrontend || hasNextFrontend)) await runSetup(() => setupTauri(config, context));
|
|
2537
2812
|
const hasUltracite = addons.includes("ultracite");
|
|
2538
2813
|
const hasBiome = addons.includes("biome");
|
|
2539
2814
|
const hasHusky = addons.includes("husky");
|
|
@@ -2555,13 +2830,13 @@ async function setupAddons(config) {
|
|
|
2555
2830
|
if (hasLefthook) await runAddonStep("lefthook", () => setupLefthook(projectDir));
|
|
2556
2831
|
}
|
|
2557
2832
|
}
|
|
2558
|
-
if (addons.includes("starlight")) await runSetup(() => setupStarlight(config));
|
|
2559
|
-
if (addons.includes("fumadocs")) await runSetup(() => setupFumadocs(config));
|
|
2833
|
+
if (addons.includes("starlight")) await runSetup(() => setupStarlight(config, context));
|
|
2834
|
+
if (addons.includes("fumadocs")) await runSetup(() => setupFumadocs(config, context));
|
|
2560
2835
|
if (addons.includes("opentui")) await runSetup(() => setupTui(config));
|
|
2561
|
-
if (addons.includes("wxt")) await runSetup(() => setupWxt(config));
|
|
2836
|
+
if (addons.includes("wxt")) await runSetup(() => setupWxt(config, context));
|
|
2562
2837
|
if (addons.includes("ruler")) await runSetup(() => setupRuler(config));
|
|
2563
|
-
if (addons.includes("skills")) await runSetup(() => setupSkills(config));
|
|
2564
|
-
if (addons.includes("mcp")) await runSetup(() => setupMcp(config));
|
|
2838
|
+
if (addons.includes("skills")) await runSetup(() => setupSkills(config, context));
|
|
2839
|
+
if (addons.includes("mcp")) await runSetup(() => setupMcp(config, context));
|
|
2565
2840
|
}
|
|
2566
2841
|
async function setupBiome(projectDir) {
|
|
2567
2842
|
await addPackageDependency({
|
|
@@ -3543,7 +3818,7 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
|
|
|
3543
3818
|
|
|
3544
3819
|
//#endregion
|
|
3545
3820
|
//#region src/prompts/project-name.ts
|
|
3546
|
-
function isPathWithinCwd$
|
|
3821
|
+
function isPathWithinCwd$2(targetPath) {
|
|
3547
3822
|
const resolved = path.resolve(targetPath);
|
|
3548
3823
|
const rel = path.relative(process.cwd(), resolved);
|
|
3549
3824
|
return !rel.startsWith("..") && !path.isAbsolute(rel);
|
|
@@ -3557,7 +3832,7 @@ async function getProjectName(initialName) {
|
|
|
3557
3832
|
if (initialName) {
|
|
3558
3833
|
if (initialName === ".") return initialName;
|
|
3559
3834
|
if (!validateDirectoryName(path.basename(initialName))) {
|
|
3560
|
-
if (isPathWithinCwd$
|
|
3835
|
+
if (isPathWithinCwd$2(path.resolve(process.cwd(), initialName))) return initialName;
|
|
3561
3836
|
consola.error(pc.red("Project path must be within current directory"));
|
|
3562
3837
|
}
|
|
3563
3838
|
}
|
|
@@ -3580,7 +3855,7 @@ async function getProjectName(initialName) {
|
|
|
3580
3855
|
const validationError = validateDirectoryName(path.basename(nameToUse));
|
|
3581
3856
|
if (validationError) return validationError;
|
|
3582
3857
|
if (nameToUse !== ".") {
|
|
3583
|
-
if (!isPathWithinCwd$
|
|
3858
|
+
if (!isPathWithinCwd$2(path.resolve(process.cwd(), nameToUse))) return "Project path must be within current directory";
|
|
3584
3859
|
}
|
|
3585
3860
|
}
|
|
3586
3861
|
});
|
|
@@ -3601,7 +3876,7 @@ async function getProjectName(initialName) {
|
|
|
3601
3876
|
*/
|
|
3602
3877
|
function isTelemetryEnabled() {
|
|
3603
3878
|
const BTS_TELEMETRY_DISABLED = process.env.BTS_TELEMETRY_DISABLED;
|
|
3604
|
-
const BTS_TELEMETRY = "
|
|
3879
|
+
const BTS_TELEMETRY = "0";
|
|
3605
3880
|
if (BTS_TELEMETRY_DISABLED !== void 0) return BTS_TELEMETRY_DISABLED !== "1";
|
|
3606
3881
|
if (BTS_TELEMETRY !== void 0) return BTS_TELEMETRY === "1";
|
|
3607
3882
|
return true;
|
|
@@ -3609,17 +3884,7 @@ function isTelemetryEnabled() {
|
|
|
3609
3884
|
|
|
3610
3885
|
//#endregion
|
|
3611
3886
|
//#region src/utils/analytics.ts
|
|
3612
|
-
|
|
3613
|
-
async function sendConvexEvent(payload) {
|
|
3614
|
-
await Result.tryPromise({
|
|
3615
|
-
try: () => fetch(CONVEX_INGEST_URL, {
|
|
3616
|
-
method: "POST",
|
|
3617
|
-
headers: { "Content-Type": "application/json" },
|
|
3618
|
-
body: JSON.stringify(payload)
|
|
3619
|
-
}),
|
|
3620
|
-
catch: () => void 0
|
|
3621
|
-
});
|
|
3622
|
-
}
|
|
3887
|
+
async function sendConvexEvent(payload) {}
|
|
3623
3888
|
async function trackProjectCreation(config, disableAnalytics = false) {
|
|
3624
3889
|
if (!isTelemetryEnabled() || disableAnalytics) return;
|
|
3625
3890
|
const { projectName: _projectName, projectDir: _projectDir, relativePath: _relativePath, ...safeConfig } = config;
|
|
@@ -4205,6 +4470,19 @@ async function formatProject(projectDir) {
|
|
|
4205
4470
|
});
|
|
4206
4471
|
}
|
|
4207
4472
|
|
|
4473
|
+
//#endregion
|
|
4474
|
+
//#region src/helpers/addons/external-manifest.ts
|
|
4475
|
+
async function writeExternalAddonManifest(projectDir, reports) {
|
|
4476
|
+
if (reports.length === 0) return;
|
|
4477
|
+
const manifest = {
|
|
4478
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4479
|
+
reports
|
|
4480
|
+
};
|
|
4481
|
+
const btsDir = path.join(projectDir, ".bts");
|
|
4482
|
+
await fs.ensureDir(btsDir);
|
|
4483
|
+
await fs.writeJson(path.join(btsDir, "external-manifest.json"), manifest, { spaces: 2 });
|
|
4484
|
+
}
|
|
4485
|
+
|
|
4208
4486
|
//#endregion
|
|
4209
4487
|
//#region src/utils/env-utils.ts
|
|
4210
4488
|
async function addEnvVariablesToFile(envPath, variables) {
|
|
@@ -5647,6 +5925,7 @@ async function createProject(options, cliInput = {}) {
|
|
|
5647
5925
|
return Result.gen(async function* () {
|
|
5648
5926
|
const projectDir = options.projectDir;
|
|
5649
5927
|
const isConvex = options.backend === "convex";
|
|
5928
|
+
const externalReports = [];
|
|
5650
5929
|
yield* Result.await(Result.tryPromise({
|
|
5651
5930
|
try: () => fs.ensureDir(projectDir),
|
|
5652
5931
|
catch: (e) => new ProjectCreationError({
|
|
@@ -5678,11 +5957,32 @@ async function createProject(options, cliInput = {}) {
|
|
|
5678
5957
|
cause: e
|
|
5679
5958
|
})
|
|
5680
5959
|
}));
|
|
5681
|
-
if (options.addons.length > 0 && options.addons[0] !== "none")
|
|
5682
|
-
|
|
5960
|
+
if (options.addons.length > 0 && options.addons[0] !== "none") {
|
|
5961
|
+
const baseSetupContext = cliInput.addonSetupContext ?? {};
|
|
5962
|
+
const setupContext = {
|
|
5963
|
+
...baseSetupContext,
|
|
5964
|
+
collectExternalReport: (report) => {
|
|
5965
|
+
externalReports.push(report);
|
|
5966
|
+
baseSetupContext.collectExternalReport?.(report);
|
|
5967
|
+
}
|
|
5968
|
+
};
|
|
5969
|
+
yield* Result.await(Result.tryPromise({
|
|
5970
|
+
try: () => setupAddons(options, setupContext),
|
|
5971
|
+
catch: (e) => new ProjectCreationError({
|
|
5972
|
+
phase: "addons-setup",
|
|
5973
|
+
message: `Failed to setup addons: ${e instanceof Error ? e.message : String(e)}`,
|
|
5974
|
+
cause: e
|
|
5975
|
+
})
|
|
5976
|
+
}));
|
|
5977
|
+
}
|
|
5978
|
+
yield* Result.await(Result.tryPromise({
|
|
5979
|
+
try: async () => {
|
|
5980
|
+
await writeExternalAddonManifest(projectDir, externalReports);
|
|
5981
|
+
cliInput.onExternalAddonReports?.(externalReports);
|
|
5982
|
+
},
|
|
5683
5983
|
catch: (e) => new ProjectCreationError({
|
|
5684
|
-
phase: "addons-
|
|
5685
|
-
message: `Failed to
|
|
5984
|
+
phase: "addons-manifest",
|
|
5985
|
+
message: `Failed to write external addon manifest: ${e instanceof Error ? e.message : String(e)}`,
|
|
5686
5986
|
cause: e
|
|
5687
5987
|
})
|
|
5688
5988
|
}));
|
|
@@ -5917,7 +6217,7 @@ async function createProjectHandlerInternal(input, startTime, timeScaffolded) {
|
|
|
5917
6217
|
});
|
|
5918
6218
|
});
|
|
5919
6219
|
}
|
|
5920
|
-
function isPathWithinCwd(targetPath) {
|
|
6220
|
+
function isPathWithinCwd$1(targetPath) {
|
|
5921
6221
|
const resolved = path.resolve(targetPath);
|
|
5922
6222
|
const rel = path.relative(process.cwd(), resolved);
|
|
5923
6223
|
return !rel.startsWith("..") && !path.isAbsolute(rel);
|
|
@@ -5931,7 +6231,7 @@ async function resolveProjectNameForSilent(input) {
|
|
|
5931
6231
|
message: validationResult.error.message,
|
|
5932
6232
|
cause: validationResult.error
|
|
5933
6233
|
}));
|
|
5934
|
-
if (!isPathWithinCwd(candidate)) return Result.err(new CLIError({ message: "Project path must be within current directory" }));
|
|
6234
|
+
if (!isPathWithinCwd$1(candidate)) return Result.err(new CLIError({ message: "Project path must be within current directory" }));
|
|
5935
6235
|
return Result.ok(candidate);
|
|
5936
6236
|
}
|
|
5937
6237
|
async function handleDirectoryConflictResult(currentPathInput, strategy) {
|
|
@@ -5985,6 +6285,385 @@ async function handleDirectoryConflictProgrammatically(currentPathInput, strateg
|
|
|
5985
6285
|
}
|
|
5986
6286
|
}
|
|
5987
6287
|
|
|
6288
|
+
//#endregion
|
|
6289
|
+
//#region src/mcp/planning.ts
|
|
6290
|
+
const EXTERNAL_ADDON_KEYS = new Set([
|
|
6291
|
+
"fumadocs",
|
|
6292
|
+
"starlight",
|
|
6293
|
+
"wxt",
|
|
6294
|
+
"tauri",
|
|
6295
|
+
"mcp",
|
|
6296
|
+
"skills"
|
|
6297
|
+
]);
|
|
6298
|
+
function getPreferredTemplate(options, addon) {
|
|
6299
|
+
if (addon === "fumadocs") return options?.fumadocs?.template;
|
|
6300
|
+
if (addon === "wxt") return options?.wxt?.template;
|
|
6301
|
+
}
|
|
6302
|
+
function toResultError(message) {
|
|
6303
|
+
return new CLIError({ message });
|
|
6304
|
+
}
|
|
6305
|
+
function isPathWithinCwd(candidatePath) {
|
|
6306
|
+
const resolvedCwd = path.resolve(process.cwd());
|
|
6307
|
+
const resolvedPath = path.resolve(process.cwd(), candidatePath);
|
|
6308
|
+
const relative = path.relative(resolvedCwd, resolvedPath);
|
|
6309
|
+
return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
6310
|
+
}
|
|
6311
|
+
function buildPlannedExternalSteps(config, addonOptions) {
|
|
6312
|
+
return config.addons.filter((addon) => EXTERNAL_ADDON_KEYS.has(addon)).map((addon) => {
|
|
6313
|
+
const preferredTemplate = getPreferredTemplate(addonOptions, addon);
|
|
6314
|
+
return {
|
|
6315
|
+
addon,
|
|
6316
|
+
status: "planned",
|
|
6317
|
+
selectedOptions: preferredTemplate ? { template: preferredTemplate } : void 0
|
|
6318
|
+
};
|
|
6319
|
+
});
|
|
6320
|
+
}
|
|
6321
|
+
function resolveScaffoldPlan(input) {
|
|
6322
|
+
return Result.gen(function* () {
|
|
6323
|
+
const configInput = input.config ?? {};
|
|
6324
|
+
const defaultConfig = getDefaultConfig();
|
|
6325
|
+
const requestedTarget = input.directory ?? input.projectName ?? defaultConfig.relativePath;
|
|
6326
|
+
if (!isPathWithinCwd(requestedTarget)) yield* Result.err(toResultError("Project path must be within current directory"));
|
|
6327
|
+
const processedInput = {
|
|
6328
|
+
...configInput,
|
|
6329
|
+
projectName: requestedTarget,
|
|
6330
|
+
template: configInput.template,
|
|
6331
|
+
yes: false,
|
|
6332
|
+
yolo: false,
|
|
6333
|
+
install: configInput.install ?? defaultConfig.install,
|
|
6334
|
+
git: configInput.git ?? defaultConfig.git,
|
|
6335
|
+
packageManager: configInput.packageManager ?? defaultConfig.packageManager
|
|
6336
|
+
};
|
|
6337
|
+
let mergedInput = processedInput;
|
|
6338
|
+
if (configInput.template && configInput.template !== "none") {
|
|
6339
|
+
const templateConfig = getTemplateConfig(configInput.template);
|
|
6340
|
+
if (templateConfig) mergedInput = {
|
|
6341
|
+
...templateConfig,
|
|
6342
|
+
...processedInput,
|
|
6343
|
+
template: configInput.template
|
|
6344
|
+
};
|
|
6345
|
+
}
|
|
6346
|
+
const providedFlags = getProvidedFlags(mergedInput);
|
|
6347
|
+
const projectNameBase = path.basename(path.resolve(process.cwd(), requestedTarget));
|
|
6348
|
+
const flagConfig = yield* processAndValidateFlags({
|
|
6349
|
+
...mergedInput,
|
|
6350
|
+
projectDirectory: requestedTarget
|
|
6351
|
+
}, providedFlags, projectNameBase).mapError((error) => toResultError(error.message));
|
|
6352
|
+
const resolvedProjectDir = path.resolve(process.cwd(), requestedTarget);
|
|
6353
|
+
const normalizedConfig = {
|
|
6354
|
+
...defaultConfig,
|
|
6355
|
+
...flagConfig,
|
|
6356
|
+
projectName: projectNameBase,
|
|
6357
|
+
projectDir: resolvedProjectDir,
|
|
6358
|
+
relativePath: requestedTarget
|
|
6359
|
+
};
|
|
6360
|
+
yield* validateConfigCompatibility(normalizedConfig, providedFlags, mergedInput).mapError((error) => toResultError(error.message));
|
|
6361
|
+
const warnings = [];
|
|
6362
|
+
const plannedExternalSteps = buildPlannedExternalSteps(normalizedConfig, input.addonOptions);
|
|
6363
|
+
if (plannedExternalSteps.length > 0) warnings.push("This configuration includes addons that execute external generators. The project can complete with partial_success if upstream generators fail.");
|
|
6364
|
+
return Result.ok({
|
|
6365
|
+
config: normalizedConfig,
|
|
6366
|
+
reproducibleCommand: generateReproducibleCommand(normalizedConfig),
|
|
6367
|
+
plannedExternalSteps,
|
|
6368
|
+
warnings
|
|
6369
|
+
});
|
|
6370
|
+
});
|
|
6371
|
+
}
|
|
6372
|
+
function computeExternalStatusFromReports(reports) {
|
|
6373
|
+
return reports.some((report) => report.status === "warning" || report.status === "failed") ? "partial_success" : "success";
|
|
6374
|
+
}
|
|
6375
|
+
|
|
6376
|
+
//#endregion
|
|
6377
|
+
//#region src/mcp/tools/create-stack.ts
|
|
6378
|
+
function collectReportWarnings(reports) {
|
|
6379
|
+
return reports.flatMap((report) => {
|
|
6380
|
+
if (report.warning) return [report.warning];
|
|
6381
|
+
if (report.error) return [report.error];
|
|
6382
|
+
return [];
|
|
6383
|
+
});
|
|
6384
|
+
}
|
|
6385
|
+
async function resolveDirectoryConflict(currentPathInput, strategy) {
|
|
6386
|
+
const currentPath = path.resolve(process.cwd(), currentPathInput);
|
|
6387
|
+
if (!await fs.pathExists(currentPath)) return Result.ok({
|
|
6388
|
+
finalPathInput: currentPathInput,
|
|
6389
|
+
shouldClearDirectory: false
|
|
6390
|
+
});
|
|
6391
|
+
if (!((await fs.readdir(currentPath)).length > 0)) return Result.ok({
|
|
6392
|
+
finalPathInput: currentPathInput,
|
|
6393
|
+
shouldClearDirectory: false
|
|
6394
|
+
});
|
|
6395
|
+
switch (strategy) {
|
|
6396
|
+
case "overwrite": return Result.ok({
|
|
6397
|
+
finalPathInput: currentPathInput,
|
|
6398
|
+
shouldClearDirectory: true
|
|
6399
|
+
});
|
|
6400
|
+
case "merge": return Result.ok({
|
|
6401
|
+
finalPathInput: currentPathInput,
|
|
6402
|
+
shouldClearDirectory: false
|
|
6403
|
+
});
|
|
6404
|
+
case "increment": {
|
|
6405
|
+
let counter = 1;
|
|
6406
|
+
const baseName = currentPathInput;
|
|
6407
|
+
let finalPathInput = `${baseName}-${counter}`;
|
|
6408
|
+
while (await fs.pathExists(path.resolve(process.cwd(), finalPathInput)) && (await fs.readdir(path.resolve(process.cwd(), finalPathInput))).length > 0) {
|
|
6409
|
+
counter += 1;
|
|
6410
|
+
finalPathInput = `${baseName}-${counter}`;
|
|
6411
|
+
}
|
|
6412
|
+
return Result.ok({
|
|
6413
|
+
finalPathInput,
|
|
6414
|
+
shouldClearDirectory: false
|
|
6415
|
+
});
|
|
6416
|
+
}
|
|
6417
|
+
case "error": return Result.err(new DirectoryConflictError({ directory: currentPathInput }));
|
|
6418
|
+
default: return Result.err(new CLIError({ message: `Unknown directory conflict strategy: ${strategy}` }));
|
|
6419
|
+
}
|
|
6420
|
+
}
|
|
6421
|
+
async function createStack(input) {
|
|
6422
|
+
const planResult = resolveScaffoldPlan(input);
|
|
6423
|
+
if (planResult.isErr()) return {
|
|
6424
|
+
status: "failed",
|
|
6425
|
+
externalStepReports: [],
|
|
6426
|
+
warnings: [],
|
|
6427
|
+
errors: [planResult.error.message]
|
|
6428
|
+
};
|
|
6429
|
+
const strategy = input.directoryConflict ?? "error";
|
|
6430
|
+
const planned = planResult.value;
|
|
6431
|
+
const conflictResult = await resolveDirectoryConflict(planned.config.relativePath, strategy);
|
|
6432
|
+
if (conflictResult.isErr()) return {
|
|
6433
|
+
status: "failed",
|
|
6434
|
+
externalStepReports: [],
|
|
6435
|
+
warnings: planned.warnings,
|
|
6436
|
+
errors: [conflictResult.error.message]
|
|
6437
|
+
};
|
|
6438
|
+
const dirSetupResult = await Result.tryPromise({
|
|
6439
|
+
try: async () => setupProjectDirectory(conflictResult.value.finalPathInput, conflictResult.value.shouldClearDirectory),
|
|
6440
|
+
catch: (e) => new CLIError({
|
|
6441
|
+
message: e instanceof Error ? e.message : String(e),
|
|
6442
|
+
cause: e
|
|
6443
|
+
})
|
|
6444
|
+
});
|
|
6445
|
+
if (dirSetupResult.isErr()) return {
|
|
6446
|
+
status: "failed",
|
|
6447
|
+
externalStepReports: [],
|
|
6448
|
+
warnings: planned.warnings,
|
|
6449
|
+
errors: [dirSetupResult.error.message]
|
|
6450
|
+
};
|
|
6451
|
+
const { finalBaseName, finalResolvedPath } = dirSetupResult.value;
|
|
6452
|
+
const finalConfig = {
|
|
6453
|
+
...planned.config,
|
|
6454
|
+
projectName: finalBaseName,
|
|
6455
|
+
projectDir: finalResolvedPath,
|
|
6456
|
+
relativePath: conflictResult.value.finalPathInput
|
|
6457
|
+
};
|
|
6458
|
+
const reports = [];
|
|
6459
|
+
const addonOptions = input.addonOptions;
|
|
6460
|
+
const projectResult = await runWithContextAsync({ silent: true }, () => createProject(finalConfig, {
|
|
6461
|
+
manualDb: input.config?.manualDb ?? false,
|
|
6462
|
+
addonSetupContext: {
|
|
6463
|
+
interactive: false,
|
|
6464
|
+
addonOptions
|
|
6465
|
+
},
|
|
6466
|
+
onExternalAddonReports: (newReports) => reports.push(...newReports)
|
|
6467
|
+
}));
|
|
6468
|
+
if (projectResult.isErr()) return {
|
|
6469
|
+
status: "failed",
|
|
6470
|
+
externalStepReports: reports,
|
|
6471
|
+
warnings: [...planned.warnings, ...collectReportWarnings(reports)],
|
|
6472
|
+
errors: [projectResult.error.message]
|
|
6473
|
+
};
|
|
6474
|
+
const reproducibleCommand = generateReproducibleCommand(finalConfig);
|
|
6475
|
+
const status = computeExternalStatusFromReports(reports);
|
|
6476
|
+
const warnings = [...planned.warnings, ...collectReportWarnings(reports)];
|
|
6477
|
+
return {
|
|
6478
|
+
status,
|
|
6479
|
+
projectDirectory: finalConfig.projectDir,
|
|
6480
|
+
relativePath: finalConfig.relativePath,
|
|
6481
|
+
reproducibleCommand,
|
|
6482
|
+
externalStepReports: reports,
|
|
6483
|
+
warnings,
|
|
6484
|
+
errors: []
|
|
6485
|
+
};
|
|
6486
|
+
}
|
|
6487
|
+
|
|
6488
|
+
//#endregion
|
|
6489
|
+
//#region src/mcp/tools/plan-stack.ts
|
|
6490
|
+
function planStack(input) {
|
|
6491
|
+
const result = resolveScaffoldPlan({
|
|
6492
|
+
projectName: input.projectName,
|
|
6493
|
+
directory: input.directory,
|
|
6494
|
+
config: input.config,
|
|
6495
|
+
addonOptions: input.addonOptions
|
|
6496
|
+
});
|
|
6497
|
+
if (result.isErr()) return {
|
|
6498
|
+
success: false,
|
|
6499
|
+
warnings: [],
|
|
6500
|
+
errors: [result.error.message]
|
|
6501
|
+
};
|
|
6502
|
+
const plan = result.value;
|
|
6503
|
+
return {
|
|
6504
|
+
success: true,
|
|
6505
|
+
normalizedConfig: plan.config,
|
|
6506
|
+
reproducibleCommand: plan.reproducibleCommand,
|
|
6507
|
+
plannedExternalSteps: plan.plannedExternalSteps,
|
|
6508
|
+
warnings: plan.warnings,
|
|
6509
|
+
errors: []
|
|
6510
|
+
};
|
|
6511
|
+
}
|
|
6512
|
+
|
|
6513
|
+
//#endregion
|
|
6514
|
+
//#region src/mcp/server.ts
|
|
6515
|
+
const AddonOptionsSchema = z$1.object({
|
|
6516
|
+
fumadocs: z$1.object({ template: z$1.enum([
|
|
6517
|
+
"next-mdx",
|
|
6518
|
+
"next-mdx-static",
|
|
6519
|
+
"waku",
|
|
6520
|
+
"react-router",
|
|
6521
|
+
"react-router-spa",
|
|
6522
|
+
"tanstack-start",
|
|
6523
|
+
"tanstack-start-spa"
|
|
6524
|
+
]).optional() }).optional(),
|
|
6525
|
+
wxt: z$1.object({ template: z$1.enum([
|
|
6526
|
+
"vanilla",
|
|
6527
|
+
"vue",
|
|
6528
|
+
"react",
|
|
6529
|
+
"solid",
|
|
6530
|
+
"svelte"
|
|
6531
|
+
]).optional() }).optional(),
|
|
6532
|
+
mcp: z$1.object({
|
|
6533
|
+
scope: z$1.enum(["project", "global"]).optional(),
|
|
6534
|
+
agents: z$1.array(z$1.string()).optional(),
|
|
6535
|
+
serverKeys: z$1.array(z$1.string()).optional()
|
|
6536
|
+
}).optional(),
|
|
6537
|
+
skills: z$1.object({
|
|
6538
|
+
scope: z$1.enum(["project", "global"]).optional(),
|
|
6539
|
+
agents: z$1.array(z$1.string()).optional(),
|
|
6540
|
+
skillKeys: z$1.array(z$1.string()).optional()
|
|
6541
|
+
}).optional()
|
|
6542
|
+
});
|
|
6543
|
+
const SharedInputSchema = {
|
|
6544
|
+
projectName: z$1.string().optional(),
|
|
6545
|
+
directory: z$1.string().optional(),
|
|
6546
|
+
config: z$1.unknown().optional(),
|
|
6547
|
+
addonOptions: AddonOptionsSchema.optional()
|
|
6548
|
+
};
|
|
6549
|
+
function validateConfig(input) {
|
|
6550
|
+
const parsed = types_exports.CreateInputSchema.partial().safeParse(input ?? {});
|
|
6551
|
+
if (!parsed.success) {
|
|
6552
|
+
const issue = parsed.error.issues[0];
|
|
6553
|
+
throw new Error(`Invalid config: ${issue?.message ?? "unknown error"}`);
|
|
6554
|
+
}
|
|
6555
|
+
return parsed.data;
|
|
6556
|
+
}
|
|
6557
|
+
async function startMcpServer() {
|
|
6558
|
+
const server = new McpServer({
|
|
6559
|
+
name: "create-better-t-stack",
|
|
6560
|
+
version: getLatestCLIVersion()
|
|
6561
|
+
});
|
|
6562
|
+
server.registerTool("plan_stack", {
|
|
6563
|
+
description: "Validate and normalize a Better-T-Stack configuration without writing files. Returns reproducible command and planned external steps.",
|
|
6564
|
+
inputSchema: SharedInputSchema,
|
|
6565
|
+
outputSchema: {
|
|
6566
|
+
success: z$1.boolean(),
|
|
6567
|
+
normalizedConfig: z$1.record(z$1.string(), z$1.unknown()).optional(),
|
|
6568
|
+
reproducibleCommand: z$1.string().optional(),
|
|
6569
|
+
plannedExternalSteps: z$1.array(z$1.object({
|
|
6570
|
+
addon: z$1.string(),
|
|
6571
|
+
status: z$1.literal("planned"),
|
|
6572
|
+
selectedOptions: z$1.record(z$1.string(), z$1.unknown()).optional()
|
|
6573
|
+
})).optional(),
|
|
6574
|
+
warnings: z$1.array(z$1.string()),
|
|
6575
|
+
errors: z$1.array(z$1.string())
|
|
6576
|
+
}
|
|
6577
|
+
}, async (args) => {
|
|
6578
|
+
try {
|
|
6579
|
+
const result = planStack({
|
|
6580
|
+
projectName: args.projectName,
|
|
6581
|
+
directory: args.directory,
|
|
6582
|
+
config: validateConfig(args.config),
|
|
6583
|
+
addonOptions: args.addonOptions
|
|
6584
|
+
});
|
|
6585
|
+
return {
|
|
6586
|
+
content: [{
|
|
6587
|
+
type: "text",
|
|
6588
|
+
text: JSON.stringify(result, null, 2)
|
|
6589
|
+
}],
|
|
6590
|
+
structuredContent: result
|
|
6591
|
+
};
|
|
6592
|
+
} catch (error) {
|
|
6593
|
+
const result = {
|
|
6594
|
+
success: false,
|
|
6595
|
+
warnings: [],
|
|
6596
|
+
errors: [error instanceof Error ? error.message : String(error)]
|
|
6597
|
+
};
|
|
6598
|
+
return {
|
|
6599
|
+
content: [{
|
|
6600
|
+
type: "text",
|
|
6601
|
+
text: JSON.stringify(result, null, 2)
|
|
6602
|
+
}],
|
|
6603
|
+
structuredContent: result
|
|
6604
|
+
};
|
|
6605
|
+
}
|
|
6606
|
+
});
|
|
6607
|
+
server.registerTool("create_stack", {
|
|
6608
|
+
description: "Scaffold a Better-T-Stack project on disk. Returns success, partial_success, or failed along with external step reports.",
|
|
6609
|
+
inputSchema: {
|
|
6610
|
+
...SharedInputSchema,
|
|
6611
|
+
directoryConflict: z$1.enum([
|
|
6612
|
+
"merge",
|
|
6613
|
+
"overwrite",
|
|
6614
|
+
"increment",
|
|
6615
|
+
"error"
|
|
6616
|
+
]).optional()
|
|
6617
|
+
},
|
|
6618
|
+
outputSchema: {
|
|
6619
|
+
status: z$1.enum([
|
|
6620
|
+
"success",
|
|
6621
|
+
"partial_success",
|
|
6622
|
+
"failed"
|
|
6623
|
+
]),
|
|
6624
|
+
projectDirectory: z$1.string().optional(),
|
|
6625
|
+
relativePath: z$1.string().optional(),
|
|
6626
|
+
reproducibleCommand: z$1.string().optional(),
|
|
6627
|
+
externalStepReports: z$1.array(z$1.record(z$1.string(), z$1.unknown())),
|
|
6628
|
+
warnings: z$1.array(z$1.string()),
|
|
6629
|
+
errors: z$1.array(z$1.string())
|
|
6630
|
+
}
|
|
6631
|
+
}, async (args) => {
|
|
6632
|
+
try {
|
|
6633
|
+
const result = await createStack({
|
|
6634
|
+
projectName: args.projectName,
|
|
6635
|
+
directory: args.directory,
|
|
6636
|
+
config: validateConfig(args.config),
|
|
6637
|
+
addonOptions: args.addonOptions,
|
|
6638
|
+
directoryConflict: args.directoryConflict
|
|
6639
|
+
});
|
|
6640
|
+
return {
|
|
6641
|
+
content: [{
|
|
6642
|
+
type: "text",
|
|
6643
|
+
text: JSON.stringify(result, null, 2)
|
|
6644
|
+
}],
|
|
6645
|
+
structuredContent: result
|
|
6646
|
+
};
|
|
6647
|
+
} catch (error) {
|
|
6648
|
+
const result = {
|
|
6649
|
+
status: "failed",
|
|
6650
|
+
externalStepReports: [],
|
|
6651
|
+
warnings: [],
|
|
6652
|
+
errors: [error instanceof Error ? error.message : String(error)]
|
|
6653
|
+
};
|
|
6654
|
+
return {
|
|
6655
|
+
content: [{
|
|
6656
|
+
type: "text",
|
|
6657
|
+
text: JSON.stringify(result, null, 2)
|
|
6658
|
+
}],
|
|
6659
|
+
structuredContent: result
|
|
6660
|
+
};
|
|
6661
|
+
}
|
|
6662
|
+
});
|
|
6663
|
+
const transport = new StdioServerTransport();
|
|
6664
|
+
await server.connect(transport);
|
|
6665
|
+
}
|
|
6666
|
+
|
|
5988
6667
|
//#endregion
|
|
5989
6668
|
//#region src/index.ts
|
|
5990
6669
|
const router = os.router({
|
|
@@ -6042,6 +6721,9 @@ const router = os.router({
|
|
|
6042
6721
|
json: z.boolean().optional().default(false).describe("Output as JSON")
|
|
6043
6722
|
})).handler(async ({ input }) => {
|
|
6044
6723
|
await historyHandler(input);
|
|
6724
|
+
}),
|
|
6725
|
+
mcp: os.meta({ description: "Start Better-T-Stack MCP server over stdio" }).handler(async () => {
|
|
6726
|
+
await startMcpServer();
|
|
6045
6727
|
})
|
|
6046
6728
|
});
|
|
6047
6729
|
const caller = createRouterClient(router, { context: {} });
|
|
@@ -6184,4 +6866,4 @@ async function add(options = {}) {
|
|
|
6184
6866
|
}
|
|
6185
6867
|
|
|
6186
6868
|
//#endregion
|
|
6187
|
-
export {
|
|
6869
|
+
export { ValidationError as S, CompatibilityError as _, VirtualFileSystem$1 as a, ProjectCreationError as b, create as c, docs as d, generate$1 as f, CLIError as g, startMcpServer as h, TEMPLATE_COUNT as i, createBtsCli as l, sponsors as m, GeneratorError$1 as n, add as o, router as p, Result$1 as r, builder as s, EMBEDDED_TEMPLATES$1 as t, createVirtual as u, DatabaseSetupError as v, UserCancelledError as x, DirectoryConflictError as y };
|
package/dist/virtual.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { Result } from "better-result";
|
|
3
|
+
import { EMBEDDED_TEMPLATES, GeneratorError, GeneratorOptions, TEMPLATE_COUNT, VirtualDirectory, VirtualFile, VirtualFileSystem, VirtualFileTree, VirtualNode, generate } from "@better-t-stack/template-generator";
|
|
3
4
|
import { API, Addons, Auth, Backend, Database, DatabaseSetup, Examples, Frontend, ORM, PackageManager, Payments, ProjectConfig, Runtime, ServerDeploy, WebDeploy } from "@better-t-stack/types";
|
|
4
5
|
export { type API, type Addons, type Auth, type Backend, type Database, type DatabaseSetup, EMBEDDED_TEMPLATES, type Examples, type Frontend, GeneratorError, type GeneratorOptions, type ORM, type PackageManager, type Payments, type ProjectConfig, Result, type Runtime, type ServerDeploy, TEMPLATE_COUNT, type VirtualDirectory, type VirtualFile, VirtualFileSystem, type VirtualFileTree, type VirtualNode, type WebDeploy, generate };
|
package/dist/virtual.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { Result } from "better-result";
|
|
3
|
+
import { EMBEDDED_TEMPLATES, GeneratorError, TEMPLATE_COUNT, VirtualFileSystem, generate } from "@better-t-stack/template-generator";
|
|
3
4
|
|
|
4
5
|
export { EMBEDDED_TEMPLATES, GeneratorError, Result, TEMPLATE_COUNT, VirtualFileSystem, generate };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-better-t-stack",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.21.0-pr892.a7e1b0f",
|
|
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,10 +70,11 @@
|
|
|
70
70
|
"prepublishOnly": "npm run build"
|
|
71
71
|
},
|
|
72
72
|
"dependencies": {
|
|
73
|
-
"@better-t-stack/template-generator": "
|
|
74
|
-
"@better-t-stack/types": "
|
|
73
|
+
"@better-t-stack/template-generator": "3.21.0-pr892.a7e1b0f",
|
|
74
|
+
"@better-t-stack/types": "3.21.0-pr892.a7e1b0f",
|
|
75
75
|
"@clack/core": "^1.0.0",
|
|
76
76
|
"@clack/prompts": "^1.0.0",
|
|
77
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
77
78
|
"@orpc/server": "^1.13.4",
|
|
78
79
|
"better-result": "^2.7.0",
|
|
79
80
|
"consola": "^3.4.2",
|