create-better-t-stack 3.40.5 → 3.41.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/README.md +5 -0
- package/dist/cli.mjs +61 -21
- package/dist/index.d.mts +3 -0
- package/dist/index.mjs +1 -1
- package/dist/{src-E3yd8mxg.mjs → src-pRksmzD7.mjs} +314 -51
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -128,6 +128,9 @@ This CLI collects anonymous usage data to help improve the tool. The data collec
|
|
|
128
128
|
- CLI version
|
|
129
129
|
- Node.js version
|
|
130
130
|
- Platform (OS)
|
|
131
|
+
- How the CLI was driven (prompts, flags, `--yes`, JSON, the programmatic API, or the MCP server)
|
|
132
|
+
|
|
133
|
+
Separately, a small number of anonymous diagnostic events are sent for failures (stage and error class, never full messages or paths), cancelled prompts, other commands such as `add` and `history`, slow runs, and MCP tool usage. See the [analytics documentation](https://better-t-stack.dev/docs/analytics) for the exact list.
|
|
131
134
|
|
|
132
135
|
**Telemetry is enabled by default in published versions** to help us understand usage patterns and improve the tool.
|
|
133
136
|
|
|
@@ -143,6 +146,8 @@ BTS_TELEMETRY_DISABLED=1 npx create-better-t-stack
|
|
|
143
146
|
export BTS_TELEMETRY_DISABLED=1
|
|
144
147
|
```
|
|
145
148
|
|
|
149
|
+
The CLI also honors the cross-tool `DO_NOT_TRACK=1` convention (https://consoledonottrack.com), and `--disable-analytics` skips telemetry for a single run.
|
|
150
|
+
|
|
146
151
|
## Examples
|
|
147
152
|
|
|
148
153
|
Create a project with default configuration:
|
package/dist/cli.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { i as SchemaNameSchema, l as create, m as getSchemaResult, s as add, u as createBtsCli, v as types_exports, y as
|
|
2
|
+
import { A as setProcessMode, S as getLatestCLIVersion, b as scrubReason, i as SchemaNameSchema, l as create, m as getSchemaResult, s as add, u as createBtsCli, v as durationBucket, x as types_exports, y as reportDiagnostic } from "./src-pRksmzD7.mjs";
|
|
3
3
|
import z from "zod";
|
|
4
|
-
import { McpServer } from "@modelcontextprotocol/
|
|
5
|
-
import {
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/server";
|
|
5
|
+
import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
6
6
|
//#region src/mcp.ts
|
|
7
7
|
const ToolResponseSchema = z.object({
|
|
8
8
|
ok: z.boolean(),
|
|
@@ -58,6 +58,44 @@ function formatToolError(cause) {
|
|
|
58
58
|
isError: true
|
|
59
59
|
};
|
|
60
60
|
}
|
|
61
|
+
const reportedSessions = /* @__PURE__ */ new WeakSet();
|
|
62
|
+
/** One session event per connection, sent when the client identity is first known. */
|
|
63
|
+
function reportMcpSession(server) {
|
|
64
|
+
if (reportedSessions.has(server)) return;
|
|
65
|
+
reportedSessions.add(server);
|
|
66
|
+
const client = server.server.getClientVersion();
|
|
67
|
+
reportDiagnostic("mcp_session", {
|
|
68
|
+
client: client?.name ?? "unknown",
|
|
69
|
+
clientVersion: client?.version ?? "unknown"
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Times every tool call and reports the outcome. Diagnostics are fire-and-forget here
|
|
74
|
+
* because the server process is long-lived and must not add latency to tool results.
|
|
75
|
+
*/
|
|
76
|
+
function instrumentTool(server, tool, handler, isOptedOut) {
|
|
77
|
+
return async (...args) => {
|
|
78
|
+
if (isOptedOut?.(...args)) return handler(...args);
|
|
79
|
+
reportMcpSession(server);
|
|
80
|
+
const startTime = Date.now();
|
|
81
|
+
const result = await handler(...args);
|
|
82
|
+
const ok = !("isError" in result);
|
|
83
|
+
reportDiagnostic("mcp_tool", {
|
|
84
|
+
tool,
|
|
85
|
+
ok,
|
|
86
|
+
duration: durationBucket(Date.now() - startTime)
|
|
87
|
+
});
|
|
88
|
+
if (!ok) reportDiagnostic("mcp_tool_error", {
|
|
89
|
+
tool,
|
|
90
|
+
error: "ToolError",
|
|
91
|
+
reason: scrubReason(result.structuredContent.error)
|
|
92
|
+
});
|
|
93
|
+
return result;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function isCreateOptedOut(input) {
|
|
97
|
+
return input.disableAnalytics === true;
|
|
98
|
+
}
|
|
61
99
|
function getProjectToolAnnotations() {
|
|
62
100
|
return {
|
|
63
101
|
destructiveHint: true,
|
|
@@ -128,10 +166,14 @@ function getStackGuidance() {
|
|
|
128
166
|
};
|
|
129
167
|
}
|
|
130
168
|
function createBtsMcpServer() {
|
|
169
|
+
setProcessMode("mcp");
|
|
131
170
|
const server = new McpServer({
|
|
132
171
|
name: "create-better-t-stack",
|
|
133
172
|
version: getLatestCLIVersion()
|
|
134
|
-
}, {
|
|
173
|
+
}, { cacheHints: { "tools/list": {
|
|
174
|
+
ttlMs: 3600 * 1e3,
|
|
175
|
+
cacheScope: "public"
|
|
176
|
+
} } });
|
|
135
177
|
server.registerTool("bts_get_stack_guidance", {
|
|
136
178
|
title: "Get Better T Stack MCP Guidance",
|
|
137
179
|
description: "Read MCP-specific guidance for choosing valid Better T Stack configurations. Use this before planning when user intent is ambiguous. This explains the full explicit config required by MCP project creation, plus important field semantics and ambiguity rules.",
|
|
@@ -143,13 +185,13 @@ function createBtsMcpServer() {
|
|
|
143
185
|
idempotentHint: true,
|
|
144
186
|
openWorldHint: false
|
|
145
187
|
}
|
|
146
|
-
}, async () => {
|
|
188
|
+
}, instrumentTool(server, "bts_get_stack_guidance", async () => {
|
|
147
189
|
try {
|
|
148
190
|
return formatToolSuccess(getStackGuidance());
|
|
149
191
|
} catch (error) {
|
|
150
192
|
return formatToolError(error);
|
|
151
193
|
}
|
|
152
|
-
});
|
|
194
|
+
}));
|
|
153
195
|
server.registerTool("bts_get_schema", {
|
|
154
196
|
title: "Get Better T Stack Schemas",
|
|
155
197
|
description: "Inspect Better T Stack CLI and input schemas so agents can plan valid create/add requests. Use this together with bts_get_stack_guidance before creating a project if any part of the request is ambiguous.",
|
|
@@ -162,13 +204,13 @@ function createBtsMcpServer() {
|
|
|
162
204
|
idempotentHint: true,
|
|
163
205
|
openWorldHint: false
|
|
164
206
|
}
|
|
165
|
-
}, async ({ name }) => {
|
|
207
|
+
}, instrumentTool(server, "bts_get_schema", async ({ name }) => {
|
|
166
208
|
try {
|
|
167
209
|
return formatToolSuccess(getSchemaResult(name ?? "all"));
|
|
168
210
|
} catch (error) {
|
|
169
211
|
return formatToolError(error);
|
|
170
212
|
}
|
|
171
|
-
});
|
|
213
|
+
}));
|
|
172
214
|
server.registerTool("bts_plan_project", {
|
|
173
215
|
title: "Plan Better T Stack Project",
|
|
174
216
|
description: "Validate and preview a Better T Stack project creation without writing files or provisioning resources. Always use this before bts_create_project. This tool requires an explicit full stack config rather than a partial payload with inferred defaults.",
|
|
@@ -181,7 +223,7 @@ function createBtsMcpServer() {
|
|
|
181
223
|
idempotentHint: true,
|
|
182
224
|
openWorldHint: false
|
|
183
225
|
}
|
|
184
|
-
}, async (input) => {
|
|
226
|
+
}, instrumentTool(server, "bts_plan_project", async (input) => {
|
|
185
227
|
try {
|
|
186
228
|
const result = await create(input.projectName, {
|
|
187
229
|
...input,
|
|
@@ -200,7 +242,7 @@ function createBtsMcpServer() {
|
|
|
200
242
|
} catch (error) {
|
|
201
243
|
return formatToolError(error);
|
|
202
244
|
}
|
|
203
|
-
});
|
|
245
|
+
}, isCreateOptedOut));
|
|
204
246
|
server.registerTool("bts_create_project", {
|
|
205
247
|
title: "Create Better T Stack Project",
|
|
206
248
|
description: "Create a Better T Stack project on disk using the same silent programmatic flow as the CLI JSON API. Call this only after bts_plan_project succeeds and the plan clearly matches the user's intent. This tool requires an explicit full stack config.",
|
|
@@ -210,19 +252,19 @@ function createBtsMcpServer() {
|
|
|
210
252
|
title: "Create Better T Stack Project",
|
|
211
253
|
...getProjectToolAnnotations()
|
|
212
254
|
}
|
|
213
|
-
}, async (input) => {
|
|
255
|
+
}, instrumentTool(server, "bts_create_project", async (input) => {
|
|
214
256
|
try {
|
|
215
257
|
if (input.install) return formatToolError(getMcpInstallTimeoutMessage(input.packageManager));
|
|
216
258
|
const result = await create(input.projectName, {
|
|
217
259
|
...input,
|
|
218
|
-
disableAnalytics:
|
|
260
|
+
disableAnalytics: input.disableAnalytics ?? false
|
|
219
261
|
});
|
|
220
262
|
if (result.isErr()) return formatToolError(result.error);
|
|
221
263
|
return formatToolSuccess(result.value);
|
|
222
264
|
} catch (error) {
|
|
223
265
|
return formatToolError(error);
|
|
224
266
|
}
|
|
225
|
-
});
|
|
267
|
+
}, isCreateOptedOut));
|
|
226
268
|
server.registerTool("bts_plan_addons", {
|
|
227
269
|
title: "Plan Better T Stack Project Additions",
|
|
228
270
|
description: "Validate and preview addon installation or workspace package scaffolding for an existing Better T Stack project without writing files. Always use this before bts_add_addons.",
|
|
@@ -235,7 +277,7 @@ function createBtsMcpServer() {
|
|
|
235
277
|
idempotentHint: true,
|
|
236
278
|
openWorldHint: false
|
|
237
279
|
}
|
|
238
|
-
}, async (input) => {
|
|
280
|
+
}, instrumentTool(server, "bts_plan_addons", async (input) => {
|
|
239
281
|
try {
|
|
240
282
|
const result = await add({
|
|
241
283
|
...input,
|
|
@@ -246,7 +288,7 @@ function createBtsMcpServer() {
|
|
|
246
288
|
} catch (error) {
|
|
247
289
|
return formatToolError(error);
|
|
248
290
|
}
|
|
249
|
-
});
|
|
291
|
+
}));
|
|
250
292
|
server.registerTool("bts_add_addons", {
|
|
251
293
|
title: "Apply Better T Stack Project Additions",
|
|
252
294
|
description: "Install addons or scaffold a workspace package in an existing Better T Stack project using the same silent flow as add-json. Call this only after bts_plan_addons succeeds and the planned changes match the user's intent.",
|
|
@@ -258,7 +300,7 @@ function createBtsMcpServer() {
|
|
|
258
300
|
idempotentHint: false,
|
|
259
301
|
openWorldHint: true
|
|
260
302
|
}
|
|
261
|
-
}, async (input) => {
|
|
303
|
+
}, instrumentTool(server, "bts_add_addons", async (input) => {
|
|
262
304
|
try {
|
|
263
305
|
const result = await add(input);
|
|
264
306
|
if (!result?.success) return formatToolError(result?.error ?? "Failed to update project");
|
|
@@ -266,13 +308,11 @@ function createBtsMcpServer() {
|
|
|
266
308
|
} catch (error) {
|
|
267
309
|
return formatToolError(error);
|
|
268
310
|
}
|
|
269
|
-
});
|
|
311
|
+
}));
|
|
270
312
|
return server;
|
|
271
313
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
const transport = new StdioServerTransport();
|
|
275
|
-
await server.connect(transport);
|
|
314
|
+
function startBtsMcpServer() {
|
|
315
|
+
return serveStdio(() => createBtsMcpServer());
|
|
276
316
|
}
|
|
277
317
|
//#endregion
|
|
278
318
|
//#region src/cli.ts
|
package/dist/index.d.mts
CHANGED
|
@@ -12,9 +12,12 @@ declare const UserCancelledError_base: import("better-result").TaggedErrorClass<
|
|
|
12
12
|
*/
|
|
13
13
|
declare class UserCancelledError extends UserCancelledError_base<{
|
|
14
14
|
message: string;
|
|
15
|
+
/** Name of the prompt that was open when the user cancelled, when known. */
|
|
16
|
+
prompt?: string;
|
|
15
17
|
}> {
|
|
16
18
|
constructor(args?: {
|
|
17
19
|
message?: string;
|
|
20
|
+
prompt?: string;
|
|
18
21
|
});
|
|
19
22
|
}
|
|
20
23
|
declare const CLIError_base: import("better-result").TaggedErrorClass<"CLIError">;
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { C as
|
|
2
|
+
import { C as CLIError, D as ProjectCreationError, E as DirectoryConflictError, O as UserCancelledError, T as DatabaseSetupError, _ as ProjectLauncherSchema, a as TEMPLATE_COUNT, c as builder, d as createVirtual, f as docs, g as sponsors, h as router, i as SchemaNameSchema, k as ValidationError, l as create, m as getSchemaResult, n as GeneratorError, o as VirtualFileSystem, p as generate, r as Result, s as add, t as EMBEDDED_TEMPLATES, u as createBtsCli, w as CompatibilityError } from "./src-pRksmzD7.mjs";
|
|
3
3
|
export { CLIError, CompatibilityError, DatabaseSetupError, DirectoryConflictError, EMBEDDED_TEMPLATES, GeneratorError, ProjectCreationError, ProjectLauncherSchema, Result, SchemaNameSchema, TEMPLATE_COUNT, UserCancelledError, ValidationError, VirtualFileSystem, add, builder, create, createBtsCli, createVirtual, docs, generate, getSchemaResult, router, sponsors };
|
|
@@ -234,6 +234,14 @@ const ADDON_COMPATIBILITY = {
|
|
|
234
234
|
//#endregion
|
|
235
235
|
//#region src/utils/context.ts
|
|
236
236
|
const cliStorage = new AsyncLocalStorage();
|
|
237
|
+
/** Process-wide mode for hosts that own the whole process, such as the MCP server. */
|
|
238
|
+
let processMode;
|
|
239
|
+
function setProcessMode(mode) {
|
|
240
|
+
processMode = mode;
|
|
241
|
+
}
|
|
242
|
+
function getProcessMode() {
|
|
243
|
+
return processMode;
|
|
244
|
+
}
|
|
237
245
|
function defaultContext() {
|
|
238
246
|
return {
|
|
239
247
|
navigation: {
|
|
@@ -241,7 +249,9 @@ function defaultContext() {
|
|
|
241
249
|
lastPromptShownUI: false
|
|
242
250
|
},
|
|
243
251
|
silent: false,
|
|
244
|
-
verbose: false
|
|
252
|
+
verbose: false,
|
|
253
|
+
promptShown: false,
|
|
254
|
+
analyticsDisabled: false
|
|
245
255
|
};
|
|
246
256
|
}
|
|
247
257
|
function getContext() {
|
|
@@ -264,6 +274,20 @@ function didLastPromptShowUI() {
|
|
|
264
274
|
function getPromptProgress() {
|
|
265
275
|
return getContext().navigation.promptProgress;
|
|
266
276
|
}
|
|
277
|
+
function markPromptShown() {
|
|
278
|
+
const ctx = tryGetContext();
|
|
279
|
+
if (ctx) ctx.promptShown = true;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* How this run is being driven. Hosts that know (json, api, mcp) set it up front;
|
|
283
|
+
* otherwise it is derived from whether prompts were rendered.
|
|
284
|
+
*/
|
|
285
|
+
function resolveInvocationMode(yes) {
|
|
286
|
+
const ctx = getContext();
|
|
287
|
+
if (ctx.mode) return ctx.mode;
|
|
288
|
+
if (yes) return "yes";
|
|
289
|
+
return ctx.promptShown ? "interactive" : "flags";
|
|
290
|
+
}
|
|
267
291
|
function setIsFirstPrompt$1(value) {
|
|
268
292
|
const ctx = tryGetContext();
|
|
269
293
|
if (ctx) ctx.navigation.isFirstPrompt = value;
|
|
@@ -276,19 +300,24 @@ function setPromptProgress(value) {
|
|
|
276
300
|
const ctx = tryGetContext();
|
|
277
301
|
if (ctx) ctx.navigation.promptProgress = value;
|
|
278
302
|
}
|
|
279
|
-
|
|
280
|
-
|
|
303
|
+
function createContext(options) {
|
|
304
|
+
return {
|
|
281
305
|
navigation: {
|
|
282
306
|
isFirstPrompt: false,
|
|
283
307
|
lastPromptShownUI: false
|
|
284
308
|
},
|
|
285
309
|
silent: options.silent ?? false,
|
|
286
310
|
verbose: options.verbose ?? false,
|
|
311
|
+
mode: options.mode ?? processMode,
|
|
312
|
+
promptShown: false,
|
|
313
|
+
analyticsDisabled: options.analyticsDisabled ?? false,
|
|
287
314
|
projectDir: options.projectDir,
|
|
288
315
|
projectName: options.projectName,
|
|
289
316
|
packageManager: options.packageManager
|
|
290
317
|
};
|
|
291
|
-
|
|
318
|
+
}
|
|
319
|
+
async function runWithContextAsync(options, fn) {
|
|
320
|
+
return cliStorage.run(createContext(options), fn);
|
|
292
321
|
}
|
|
293
322
|
//#endregion
|
|
294
323
|
//#region src/utils/terminal-output.ts
|
|
@@ -348,7 +377,10 @@ const cliConsola = {
|
|
|
348
377
|
*/
|
|
349
378
|
var UserCancelledError = class extends TaggedError("UserCancelledError") {
|
|
350
379
|
constructor(args) {
|
|
351
|
-
super({
|
|
380
|
+
super({
|
|
381
|
+
message: args?.message ?? "Operation cancelled",
|
|
382
|
+
prompt: args?.prompt
|
|
383
|
+
});
|
|
352
384
|
}
|
|
353
385
|
};
|
|
354
386
|
/**
|
|
@@ -633,34 +665,36 @@ function formatHistoryEntry(entry, index) {
|
|
|
633
665
|
const details = rows.map(({ label, value }) => `${pc.dim(label.padEnd(labelWidth))} ${value}`).join("\n");
|
|
634
666
|
return `${pc.cyan(pc.bold(`${index + 1}. ${entry.projectName}`))}\n${details}\n${pc.dim("Recreate")}\n${pc.cyan(entry.reproducibleCommand)}`;
|
|
635
667
|
}
|
|
668
|
+
/** Resolves to whether the command did what was asked; failures are reported to the user here. */
|
|
636
669
|
async function historyHandler(input) {
|
|
637
670
|
if (input.clear) {
|
|
638
671
|
const clearResult = await clearHistory();
|
|
639
672
|
if (clearResult.isErr()) {
|
|
640
673
|
log.warn(pc.yellow(clearResult.error.message));
|
|
641
|
-
return;
|
|
674
|
+
return false;
|
|
642
675
|
}
|
|
643
676
|
log.success(pc.green("Project history cleared."));
|
|
644
|
-
return;
|
|
677
|
+
return true;
|
|
645
678
|
}
|
|
646
679
|
const historyResult = await getHistory(input.limit);
|
|
647
680
|
if (historyResult.isErr()) {
|
|
648
681
|
log.warn(pc.yellow(historyResult.error.message));
|
|
649
|
-
return;
|
|
682
|
+
return false;
|
|
650
683
|
}
|
|
651
684
|
const entries = historyResult.value;
|
|
652
685
|
if (input.json) {
|
|
653
686
|
console.log(JSON.stringify(entries, null, 2));
|
|
654
|
-
return;
|
|
687
|
+
return true;
|
|
655
688
|
}
|
|
656
689
|
renderTitle();
|
|
657
690
|
intro(pc.magenta(`Project history · ${entries.length}`));
|
|
658
691
|
if (entries.length === 0) {
|
|
659
692
|
outro(`${pc.dim("No saved projects yet · create one with")} ${pc.cyan("create-better-t-stack my-app")}`);
|
|
660
|
-
return;
|
|
693
|
+
return true;
|
|
661
694
|
}
|
|
662
695
|
log.message(entries.map(formatHistoryEntry).join("\n\n"));
|
|
663
696
|
outro(pc.dim("Run a command above to recreate that project"));
|
|
697
|
+
return true;
|
|
664
698
|
}
|
|
665
699
|
//#endregion
|
|
666
700
|
//#region src/utils/open-url.ts
|
|
@@ -1704,6 +1738,125 @@ async function updateBtsConfig(projectDir, updates) {
|
|
|
1704
1738
|
} catch {}
|
|
1705
1739
|
}
|
|
1706
1740
|
//#endregion
|
|
1741
|
+
//#region src/utils/telemetry.ts
|
|
1742
|
+
/**
|
|
1743
|
+
* Returns true if telemetry/analytics should be enabled, false otherwise.
|
|
1744
|
+
*
|
|
1745
|
+
* - DO_NOT_TRACK=1 (the cross-tool convention from consoledonottrack.com) disables analytics.
|
|
1746
|
+
* - If BTS_TELEMETRY_DISABLED is present and "1", disables analytics.
|
|
1747
|
+
* - Otherwise, BTS_TELEMETRY: "0" disables, "1" enables (default: enabled).
|
|
1748
|
+
*/
|
|
1749
|
+
function isTelemetryEnabled() {
|
|
1750
|
+
const DO_NOT_TRACK = process.env.DO_NOT_TRACK;
|
|
1751
|
+
const BTS_TELEMETRY_DISABLED = process.env.BTS_TELEMETRY_DISABLED;
|
|
1752
|
+
if (DO_NOT_TRACK === "1" || DO_NOT_TRACK?.toLowerCase() === "true") return false;
|
|
1753
|
+
if (BTS_TELEMETRY_DISABLED !== void 0) return BTS_TELEMETRY_DISABLED !== "1";
|
|
1754
|
+
return true;
|
|
1755
|
+
}
|
|
1756
|
+
//#endregion
|
|
1757
|
+
//#region src/utils/diagnostics.ts
|
|
1758
|
+
/**
|
|
1759
|
+
* Diagnostic events go to the self-hosted Umami instance (a separate "CLI" website),
|
|
1760
|
+
* not to the Convex project dataset. They cover what the project-creation event
|
|
1761
|
+
* cannot: failures, cancellations, non-create commands, slow stages, and MCP usage.
|
|
1762
|
+
* Everything is a no-op until UMAMI_CLI_WEBSITE_ID is baked in at build time.
|
|
1763
|
+
*/
|
|
1764
|
+
const UMAMI_HOST_URL = "https://umami.amanv.cloud";
|
|
1765
|
+
const UMAMI_CLI_WEBSITE_ID = "e658611d-dbcc-4d3a-bc4e-182b9f5b0d5d";
|
|
1766
|
+
const SEND_TIMEOUT_MS$1 = 3e3;
|
|
1767
|
+
const MAX_STRING_LENGTH = 500;
|
|
1768
|
+
const MAX_REASON_LENGTH = 160;
|
|
1769
|
+
const DURATION_BUCKETS = [
|
|
1770
|
+
[1e3, "<1s"],
|
|
1771
|
+
[5e3, "1-5s"],
|
|
1772
|
+
[15e3, "5-15s"],
|
|
1773
|
+
[6e4, "15-60s"],
|
|
1774
|
+
[3e5, "1-5m"]
|
|
1775
|
+
];
|
|
1776
|
+
function durationBucket(elapsedMs) {
|
|
1777
|
+
for (const [limit, label] of DURATION_BUCKETS) if (elapsedMs < limit) return label;
|
|
1778
|
+
return ">5m";
|
|
1779
|
+
}
|
|
1780
|
+
/** Class name of the failure, which is stable and never carries user content. */
|
|
1781
|
+
function errorClass(cause) {
|
|
1782
|
+
if (cause instanceof Error) return cause.name || cause.constructor.name || "Error";
|
|
1783
|
+
return "UnknownError";
|
|
1784
|
+
}
|
|
1785
|
+
/** Coarse stage a create/add failure belongs to, derived from the tagged error types. */
|
|
1786
|
+
function failureStage(cause) {
|
|
1787
|
+
if (ProjectCreationError.is(cause)) return cause.phase;
|
|
1788
|
+
if (DatabaseSetupError.is(cause)) return "database-setup";
|
|
1789
|
+
if (AddonSetupError.is(cause)) return "addons-setup";
|
|
1790
|
+
if (DirectoryConflictError.is(cause)) return "directory";
|
|
1791
|
+
if (ValidationError.is(cause) || CompatibilityError.is(cause)) return "validate";
|
|
1792
|
+
if (CLIError.is(cause)) return "config";
|
|
1793
|
+
return "unknown";
|
|
1794
|
+
}
|
|
1795
|
+
/**
|
|
1796
|
+
* First line of an error message with anything that could identify a machine or
|
|
1797
|
+
* project replaced by placeholders: file paths, URLs, emails, and quoted names.
|
|
1798
|
+
*/
|
|
1799
|
+
function scrubReason(cause) {
|
|
1800
|
+
const scrubbed = ((cause instanceof Error ? cause.message : String(cause)).split(/\r?\n/, 1)[0] ?? "").replaceAll(/(["'`])(?:(?!\1).)*\1/g, "<name>").replaceAll(/https?:\/\/[^\s)\]"'>]+/gi, "<url>").replaceAll(/[\w.+-]+@[\w-]+\.[\w.-]+/g, "<email>").replaceAll(/(?:[a-zA-Z]:)?(?:[\\/][\w.@+ -]+)+[\\/][\w.@+-]+(?:[ \w.@+-]*\.\w+)?/g, "<path>").replaceAll(/\S*[\\/]\S*/g, "<path>").replaceAll(/\s+/g, " ").trim();
|
|
1801
|
+
return scrubbed.length > MAX_REASON_LENGTH ? scrubbed.slice(0, MAX_REASON_LENGTH) : scrubbed;
|
|
1802
|
+
}
|
|
1803
|
+
function clampValue(value) {
|
|
1804
|
+
const text = String(value);
|
|
1805
|
+
return text.length > MAX_STRING_LENGTH ? text.slice(0, MAX_STRING_LENGTH) : value;
|
|
1806
|
+
}
|
|
1807
|
+
function isDiagnosticsEnabled() {
|
|
1808
|
+
return Boolean(UMAMI_HOST_URL) && Boolean(UMAMI_CLI_WEBSITE_ID) && isTelemetryEnabled() && !getContext().analyticsDisabled;
|
|
1809
|
+
}
|
|
1810
|
+
function eventUrl(name, data) {
|
|
1811
|
+
if (name.startsWith("mcp_")) return "/mcp";
|
|
1812
|
+
const command = data.command;
|
|
1813
|
+
return command === void 0 ? `/${name}` : `/${String(command)}`;
|
|
1814
|
+
}
|
|
1815
|
+
function buildDiagnosticPayload(name, data) {
|
|
1816
|
+
const eventData = {};
|
|
1817
|
+
for (const [key, value] of Object.entries(data)) if (value !== void 0) eventData[key] = clampValue(value);
|
|
1818
|
+
return {
|
|
1819
|
+
type: "event",
|
|
1820
|
+
payload: {
|
|
1821
|
+
website: UMAMI_CLI_WEBSITE_ID,
|
|
1822
|
+
hostname: "cli",
|
|
1823
|
+
url: eventUrl(name, eventData),
|
|
1824
|
+
title: name,
|
|
1825
|
+
name,
|
|
1826
|
+
data: eventData
|
|
1827
|
+
}
|
|
1828
|
+
};
|
|
1829
|
+
}
|
|
1830
|
+
function diagnosticUserAgent() {
|
|
1831
|
+
return `Mozilla/5.0 (compatible; create-better-t-stack/${getLatestCLIVersion()})`;
|
|
1832
|
+
}
|
|
1833
|
+
async function reportDiagnostic(name, data) {
|
|
1834
|
+
if (!isDiagnosticsEnabled()) return;
|
|
1835
|
+
await Result.tryPromise({
|
|
1836
|
+
try: () => fetch(`${UMAMI_HOST_URL}/api/send`, {
|
|
1837
|
+
method: "POST",
|
|
1838
|
+
headers: {
|
|
1839
|
+
"Content-Type": "application/json",
|
|
1840
|
+
"User-Agent": diagnosticUserAgent()
|
|
1841
|
+
},
|
|
1842
|
+
body: JSON.stringify(buildDiagnosticPayload(name, data)),
|
|
1843
|
+
signal: AbortSignal.timeout(SEND_TIMEOUT_MS$1),
|
|
1844
|
+
keepalive: true
|
|
1845
|
+
}),
|
|
1846
|
+
catch: () => void 0
|
|
1847
|
+
});
|
|
1848
|
+
}
|
|
1849
|
+
/** Reports a stage only when it crossed the slow threshold, so the event is a signal. */
|
|
1850
|
+
async function reportSlowStage(command, stage, elapsedMs, packageManager) {
|
|
1851
|
+
if (elapsedMs < 6e4) return;
|
|
1852
|
+
await reportDiagnostic("cli_slow", {
|
|
1853
|
+
command,
|
|
1854
|
+
stage,
|
|
1855
|
+
duration: durationBucket(elapsedMs),
|
|
1856
|
+
packageManager
|
|
1857
|
+
});
|
|
1858
|
+
}
|
|
1859
|
+
//#endregion
|
|
1707
1860
|
//#region src/utils/input-hardening.ts
|
|
1708
1861
|
function hasControlCharacters(value) {
|
|
1709
1862
|
for (const char of value) {
|
|
@@ -2561,6 +2714,7 @@ async function navigableGroup(prompts, opts) {
|
|
|
2561
2714
|
setIsFirstPrompt$1(currentIndex === 0);
|
|
2562
2715
|
setLastPromptShownUI(false);
|
|
2563
2716
|
const presetResult = opts?.preselected?.[name];
|
|
2717
|
+
if (presetResult === void 0) markPromptShown();
|
|
2564
2718
|
const result = presetResult !== void 0 ? presetResult : await prompt({
|
|
2565
2719
|
results,
|
|
2566
2720
|
previousAnswer: previousAnswers[name]
|
|
@@ -2577,7 +2731,10 @@ async function navigableGroup(prompts, opts) {
|
|
|
2577
2731
|
if (isCancel$1(result)) {
|
|
2578
2732
|
if (opts?.onCancel) {
|
|
2579
2733
|
results[name] = "canceled";
|
|
2580
|
-
opts.onCancel({
|
|
2734
|
+
opts.onCancel({
|
|
2735
|
+
results,
|
|
2736
|
+
prompt: String(name)
|
|
2737
|
+
});
|
|
2581
2738
|
}
|
|
2582
2739
|
return results;
|
|
2583
2740
|
}
|
|
@@ -4759,9 +4916,14 @@ function updateViteConfigImportsForVitePlus(vfs) {
|
|
|
4759
4916
|
}
|
|
4760
4917
|
}
|
|
4761
4918
|
async function addHandler(input, options = {}) {
|
|
4762
|
-
const { silent = false } = options;
|
|
4763
|
-
return runWithContextAsync({
|
|
4919
|
+
const { silent = false, mode } = options;
|
|
4920
|
+
return runWithContextAsync({
|
|
4921
|
+
silent,
|
|
4922
|
+
mode
|
|
4923
|
+
}, async () => {
|
|
4924
|
+
const startTime = Date.now();
|
|
4764
4925
|
const result = await addHandlerInternal(input);
|
|
4926
|
+
await reportAddOutcome(input, result, Date.now() - startTime);
|
|
4765
4927
|
if (result.isOk()) return result.value;
|
|
4766
4928
|
const error = result.error;
|
|
4767
4929
|
if (UserCancelledError.is(error)) {
|
|
@@ -4783,6 +4945,42 @@ async function addHandler(input, options = {}) {
|
|
|
4783
4945
|
process.exit(1);
|
|
4784
4946
|
});
|
|
4785
4947
|
}
|
|
4948
|
+
async function reportAddOutcome(input, result, elapsedMs) {
|
|
4949
|
+
const mode = resolveInvocationMode(false);
|
|
4950
|
+
const duration = durationBucket(elapsedMs);
|
|
4951
|
+
if (result.isOk()) {
|
|
4952
|
+
await reportDiagnostic("cli_command", {
|
|
4953
|
+
command: "add",
|
|
4954
|
+
mode,
|
|
4955
|
+
ok: true,
|
|
4956
|
+
duration
|
|
4957
|
+
});
|
|
4958
|
+
return;
|
|
4959
|
+
}
|
|
4960
|
+
const error = result.error;
|
|
4961
|
+
if (UserCancelledError.is(error)) {
|
|
4962
|
+
await reportDiagnostic("cli_cancelled", {
|
|
4963
|
+
command: "add",
|
|
4964
|
+
mode,
|
|
4965
|
+
prompt: error.prompt ?? "unknown"
|
|
4966
|
+
});
|
|
4967
|
+
return;
|
|
4968
|
+
}
|
|
4969
|
+
await reportDiagnostic("cli_command", {
|
|
4970
|
+
command: "add",
|
|
4971
|
+
mode,
|
|
4972
|
+
ok: false,
|
|
4973
|
+
duration
|
|
4974
|
+
});
|
|
4975
|
+
await reportDiagnostic("cli_failed", {
|
|
4976
|
+
command: "add",
|
|
4977
|
+
mode,
|
|
4978
|
+
stage: failureStage(error),
|
|
4979
|
+
error: errorClass(error),
|
|
4980
|
+
reason: scrubReason(error),
|
|
4981
|
+
packageManager: input.packageManager
|
|
4982
|
+
});
|
|
4983
|
+
}
|
|
4786
4984
|
async function addHandlerInternal(input) {
|
|
4787
4985
|
const projectDir = input.projectDir || process.cwd();
|
|
4788
4986
|
const hardeningResult = validateAgentSafePathInput(projectDir, "projectDir");
|
|
@@ -4795,7 +4993,7 @@ async function addHandlerInternal(input) {
|
|
|
4795
4993
|
intro(pc.magenta("Add to your project"));
|
|
4796
4994
|
}
|
|
4797
4995
|
const existingConfig = await detectProjectConfig(projectDir);
|
|
4798
|
-
if (!existingConfig) return Result.err(new CLIError({ message: `No Better-T-Stack project found in ${projectDir}. Make sure bts.jsonc exists.` }));
|
|
4996
|
+
if (!existingConfig) return Result.err(new CLIError({ message: `No Better-T-Stack project found in "${projectDir}". Make sure bts.jsonc exists.` }));
|
|
4799
4997
|
if (!isSilent()) log.info(pc.dim(`Detected project: ${existingConfig.projectName}`));
|
|
4800
4998
|
let addonsToAdd;
|
|
4801
4999
|
if (input.addons && input.addons.length > 0) {
|
|
@@ -6080,8 +6278,11 @@ async function gatherConfig(flags, projectName, projectDir, relativePath, option
|
|
|
6080
6278
|
]
|
|
6081
6279
|
}
|
|
6082
6280
|
],
|
|
6083
|
-
onCancel: () => {
|
|
6084
|
-
throw new UserCancelledError({
|
|
6281
|
+
onCancel: ({ prompt }) => {
|
|
6282
|
+
throw new UserCancelledError({
|
|
6283
|
+
message: "Operation cancelled",
|
|
6284
|
+
prompt
|
|
6285
|
+
});
|
|
6085
6286
|
}
|
|
6086
6287
|
});
|
|
6087
6288
|
return {
|
|
@@ -6151,6 +6352,7 @@ async function getProjectName(initialName) {
|
|
|
6151
6352
|
counter++;
|
|
6152
6353
|
}
|
|
6153
6354
|
while (!isValid) {
|
|
6355
|
+
markPromptShown();
|
|
6154
6356
|
const response = await text({
|
|
6155
6357
|
message: "Where should we create your project?",
|
|
6156
6358
|
placeholder: defaultName,
|
|
@@ -6165,40 +6367,34 @@ async function getProjectName(initialName) {
|
|
|
6165
6367
|
}
|
|
6166
6368
|
}
|
|
6167
6369
|
});
|
|
6168
|
-
if (isCancel(response)) throw new UserCancelledError({
|
|
6370
|
+
if (isCancel(response)) throw new UserCancelledError({
|
|
6371
|
+
message: "Operation cancelled.",
|
|
6372
|
+
prompt: "projectName"
|
|
6373
|
+
});
|
|
6169
6374
|
projectPath = response || defaultName;
|
|
6170
6375
|
isValid = true;
|
|
6171
6376
|
}
|
|
6172
6377
|
return projectPath;
|
|
6173
6378
|
}
|
|
6174
6379
|
//#endregion
|
|
6175
|
-
//#region src/utils/telemetry.ts
|
|
6176
|
-
/**
|
|
6177
|
-
* Returns true if telemetry/analytics should be enabled, false otherwise.
|
|
6178
|
-
*
|
|
6179
|
-
* - If BTS_TELEMETRY_DISABLED is present and "1", disables analytics.
|
|
6180
|
-
* - Otherwise, BTS_TELEMETRY: "0" disables, "1" enables (default: enabled).
|
|
6181
|
-
*/
|
|
6182
|
-
function isTelemetryEnabled() {
|
|
6183
|
-
const BTS_TELEMETRY_DISABLED = process.env.BTS_TELEMETRY_DISABLED;
|
|
6184
|
-
if (BTS_TELEMETRY_DISABLED !== void 0) return BTS_TELEMETRY_DISABLED !== "1";
|
|
6185
|
-
return true;
|
|
6186
|
-
}
|
|
6187
|
-
//#endregion
|
|
6188
6380
|
//#region src/utils/analytics.ts
|
|
6189
6381
|
const CONVEX_INGEST_URL = "https://striped-seahorse-863.convex.site/api/analytics/ingest";
|
|
6382
|
+
const SEND_TIMEOUT_MS = 3e3;
|
|
6190
6383
|
async function sendConvexEvent(payload) {
|
|
6191
6384
|
await Result.tryPromise({
|
|
6192
6385
|
try: () => fetch(CONVEX_INGEST_URL, {
|
|
6193
6386
|
method: "POST",
|
|
6194
6387
|
headers: { "Content-Type": "application/json" },
|
|
6195
|
-
body: JSON.stringify(payload)
|
|
6388
|
+
body: JSON.stringify(payload),
|
|
6389
|
+
signal: AbortSignal.timeout(SEND_TIMEOUT_MS),
|
|
6390
|
+
keepalive: true
|
|
6196
6391
|
}),
|
|
6197
6392
|
catch: () => void 0
|
|
6198
6393
|
});
|
|
6199
6394
|
}
|
|
6200
|
-
function buildAnalyticsEvent(config) {
|
|
6395
|
+
function buildAnalyticsEvent(config, mode) {
|
|
6201
6396
|
return types_exports.AnalyticsEventSchema.parse({
|
|
6397
|
+
mode,
|
|
6202
6398
|
database: config.database,
|
|
6203
6399
|
orm: config.orm,
|
|
6204
6400
|
backend: config.backend,
|
|
@@ -6220,10 +6416,10 @@ function buildAnalyticsEvent(config) {
|
|
|
6220
6416
|
platform: process.platform
|
|
6221
6417
|
});
|
|
6222
6418
|
}
|
|
6223
|
-
async function trackProjectCreation(config, disableAnalytics = false) {
|
|
6419
|
+
async function trackProjectCreation(config, disableAnalytics = false, mode) {
|
|
6224
6420
|
if (!isTelemetryEnabled() || disableAnalytics) return;
|
|
6225
6421
|
await Result.tryPromise({
|
|
6226
|
-
try: () => sendConvexEvent(buildAnalyticsEvent(config)),
|
|
6422
|
+
try: () => sendConvexEvent(buildAnalyticsEvent(config, mode)),
|
|
6227
6423
|
catch: () => void 0
|
|
6228
6424
|
});
|
|
6229
6425
|
}
|
|
@@ -8768,6 +8964,7 @@ async function createProject(options, cliInput) {
|
|
|
8768
8964
|
return Result.gen(async function* () {
|
|
8769
8965
|
const projectDir = options.projectDir;
|
|
8770
8966
|
const isConvex = options.backend === "convex";
|
|
8967
|
+
const scaffoldStartTime = Date.now();
|
|
8771
8968
|
yield* Result.await(Result.tryPromise({
|
|
8772
8969
|
try: () => fs.ensureDir(projectDir),
|
|
8773
8970
|
catch: (e) => new ProjectCreationError({
|
|
@@ -8809,10 +9006,15 @@ async function createProject(options, cliInput) {
|
|
|
8809
9006
|
}));
|
|
8810
9007
|
yield* Result.await(formatProject(projectDir));
|
|
8811
9008
|
if (!isSilent()) log.success("Project scaffolded");
|
|
8812
|
-
|
|
8813
|
-
|
|
8814
|
-
|
|
8815
|
-
|
|
9009
|
+
await reportSlowStage("create", "scaffold", Date.now() - scaffoldStartTime, options.packageManager);
|
|
9010
|
+
if (options.install) {
|
|
9011
|
+
const installStartTime = Date.now();
|
|
9012
|
+
yield* Result.await(installDependencies({
|
|
9013
|
+
projectDir,
|
|
9014
|
+
packageManager: options.packageManager
|
|
9015
|
+
}));
|
|
9016
|
+
await reportSlowStage("create", "install", Date.now() - installStartTime, options.packageManager);
|
|
9017
|
+
}
|
|
8816
9018
|
yield* Result.await(initializeGit(projectDir, options.git));
|
|
8817
9019
|
if (!isSilent()) await displayPostInstallInstructions({
|
|
8818
9020
|
...options,
|
|
@@ -8875,17 +9077,46 @@ function createEmptyResult(timeScaffolded, elapsedTimeMs, error) {
|
|
|
8875
9077
|
};
|
|
8876
9078
|
}
|
|
8877
9079
|
async function executeCreateProjectHandler(input, options) {
|
|
8878
|
-
const { silent = false } = options;
|
|
8879
|
-
return runWithContextAsync({
|
|
9080
|
+
const { silent = false, mode } = options;
|
|
9081
|
+
return runWithContextAsync({
|
|
9082
|
+
silent,
|
|
9083
|
+
mode,
|
|
9084
|
+
analyticsDisabled: input.disableAnalytics
|
|
9085
|
+
}, async () => {
|
|
8880
9086
|
const startTime = Date.now();
|
|
8881
9087
|
const timeScaffolded = (/* @__PURE__ */ new Date()).toISOString();
|
|
9088
|
+
const result = await createProjectHandlerInternal(input, startTime, timeScaffolded);
|
|
9089
|
+
await reportCreateOutcome(input, result);
|
|
8882
9090
|
return {
|
|
8883
|
-
result
|
|
9091
|
+
result,
|
|
8884
9092
|
startTime,
|
|
8885
9093
|
timeScaffolded
|
|
8886
9094
|
};
|
|
8887
9095
|
});
|
|
8888
9096
|
}
|
|
9097
|
+
/** Diagnostics for what the success-only project event cannot show; awaited so it beats process.exit. */
|
|
9098
|
+
async function reportCreateOutcome(input, result) {
|
|
9099
|
+
const mode = resolveInvocationMode(input.yes);
|
|
9100
|
+
if (result.isOk()) return;
|
|
9101
|
+
const error = result.error;
|
|
9102
|
+
if (UserCancelledError.is(error)) {
|
|
9103
|
+
await reportDiagnostic("cli_cancelled", {
|
|
9104
|
+
command: "create",
|
|
9105
|
+
mode,
|
|
9106
|
+
prompt: error.prompt ?? "unknown"
|
|
9107
|
+
});
|
|
9108
|
+
return;
|
|
9109
|
+
}
|
|
9110
|
+
await reportDiagnostic("cli_failed", {
|
|
9111
|
+
command: "create",
|
|
9112
|
+
mode,
|
|
9113
|
+
stage: failureStage(error),
|
|
9114
|
+
error: errorClass(error),
|
|
9115
|
+
reason: scrubReason(error),
|
|
9116
|
+
packageManager: input.packageManager,
|
|
9117
|
+
backend: input.backend
|
|
9118
|
+
});
|
|
9119
|
+
}
|
|
8889
9120
|
async function createProjectHandlerResult(input, options = {}) {
|
|
8890
9121
|
return (await executeCreateProjectHandler(input, options)).result;
|
|
8891
9122
|
}
|
|
@@ -9058,7 +9289,7 @@ async function createProjectHandlerInternal(input, startTime, timeScaffolded) {
|
|
|
9058
9289
|
dbSetupOptions: effectiveDbSetupOptions,
|
|
9059
9290
|
packageManagerVersion: localRequirements.packageManagerVersion
|
|
9060
9291
|
}));
|
|
9061
|
-
await trackProjectCreation(config, input.disableAnalytics);
|
|
9292
|
+
await trackProjectCreation(config, input.disableAnalytics, resolveInvocationMode(input.yes));
|
|
9062
9293
|
const historyResult = await addToHistory(config, reproducibleCommand);
|
|
9063
9294
|
if (historyResult.isErr() && !isSilent()) {
|
|
9064
9295
|
log.warn(pc.yellow(historyResult.error.message));
|
|
@@ -9259,15 +9490,18 @@ const router = t.router({
|
|
|
9259
9490
|
description: "Create a project from a raw JSON payload (agent-friendly)",
|
|
9260
9491
|
jsonInput: "always"
|
|
9261
9492
|
}).input(types_exports.CreateInputSchema).mutation(async ({ input }) => {
|
|
9262
|
-
const result = await createProjectHandler(input, {
|
|
9493
|
+
const result = await createProjectHandler(input, {
|
|
9494
|
+
silent: true,
|
|
9495
|
+
mode: "json"
|
|
9496
|
+
});
|
|
9263
9497
|
if (!result) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
9264
9498
|
if (!result.success) throw new CLIError({ message: result.error || "Unknown error occurred" });
|
|
9265
9499
|
return result;
|
|
9266
9500
|
}),
|
|
9267
9501
|
schema: t.procedure.meta({ description: "Show runtime CLI and input schemas as JSON" }).input(z.object({ name: SchemaNameSchema.describe("Schema name to inspect") })).query(({ input }) => getSchemaResult(input.name)),
|
|
9268
|
-
sponsors: t.procedure.meta({ description: "Show Better-T-Stack sponsors" }).mutation(() => showSponsorsCommand()),
|
|
9269
|
-
docs: t.procedure.meta({ description: "Open Better-T-Stack documentation" }).mutation(() => openDocsCommand()),
|
|
9270
|
-
builder: t.procedure.meta({ description: "Open the web-based stack builder" }).mutation(() => openBuilderCommand()),
|
|
9502
|
+
sponsors: t.procedure.meta({ description: "Show Better-T-Stack sponsors" }).mutation(() => trackCommand("sponsors", () => showSponsorsCommand())),
|
|
9503
|
+
docs: t.procedure.meta({ description: "Open Better-T-Stack documentation" }).mutation(() => trackCommand("docs", () => openDocsCommand())),
|
|
9504
|
+
builder: t.procedure.meta({ description: "Open the web-based stack builder" }).mutation(() => trackCommand("builder", () => openBuilderCommand())),
|
|
9271
9505
|
add: t.procedure.meta({ description: "Add addons or a workspace package to an existing Better-T-Stack project" }).input(z.object({
|
|
9272
9506
|
addons: z.array(types_exports.AddonsSchema).optional().describe("Addons to add"),
|
|
9273
9507
|
package: types_exports.WorkspacePackageNameSchema.optional(),
|
|
@@ -9282,7 +9516,10 @@ const router = t.router({
|
|
|
9282
9516
|
description: "Add addons or a workspace package from a raw JSON payload (agent-friendly)",
|
|
9283
9517
|
jsonInput: "always"
|
|
9284
9518
|
}).input(types_exports.AddInputSchema).mutation(async ({ input }) => {
|
|
9285
|
-
const result = await addHandler(input, {
|
|
9519
|
+
const result = await addHandler(input, {
|
|
9520
|
+
silent: true,
|
|
9521
|
+
mode: "json"
|
|
9522
|
+
});
|
|
9286
9523
|
if (!result) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
9287
9524
|
if (!result.success) throw new CLIError({ message: result.error || "Unknown error occurred" });
|
|
9288
9525
|
return result;
|
|
@@ -9292,9 +9529,29 @@ const router = t.router({
|
|
|
9292
9529
|
clear: z.boolean().optional().default(false).describe("Clear all history"),
|
|
9293
9530
|
json: z.boolean().optional().default(false).describe("Output as JSON")
|
|
9294
9531
|
})).mutation(async ({ input }) => {
|
|
9295
|
-
await historyHandler(input);
|
|
9532
|
+
await trackCommand("history", () => historyHandler(input), (ok) => ok);
|
|
9296
9533
|
})
|
|
9297
9534
|
});
|
|
9535
|
+
/** Usage diagnostics for commands that have no project event of their own. */
|
|
9536
|
+
async function trackCommand(command, run, isOk = () => true) {
|
|
9537
|
+
const startTime = Date.now();
|
|
9538
|
+
try {
|
|
9539
|
+
const value = await run();
|
|
9540
|
+
await reportDiagnostic("cli_command", {
|
|
9541
|
+
command,
|
|
9542
|
+
ok: isOk(value),
|
|
9543
|
+
duration: durationBucket(Date.now() - startTime)
|
|
9544
|
+
});
|
|
9545
|
+
return value;
|
|
9546
|
+
} catch (cause) {
|
|
9547
|
+
await reportDiagnostic("cli_command", {
|
|
9548
|
+
command,
|
|
9549
|
+
ok: false,
|
|
9550
|
+
duration: durationBucket(Date.now() - startTime)
|
|
9551
|
+
});
|
|
9552
|
+
throw cause;
|
|
9553
|
+
}
|
|
9554
|
+
}
|
|
9298
9555
|
function createBtsCli() {
|
|
9299
9556
|
return createCli({
|
|
9300
9557
|
router,
|
|
@@ -9352,7 +9609,10 @@ async function create(projectName, options) {
|
|
|
9352
9609
|
};
|
|
9353
9610
|
return Result.tryPromise({
|
|
9354
9611
|
try: async () => {
|
|
9355
|
-
const result = await createProjectHandlerResult(input, {
|
|
9612
|
+
const result = await createProjectHandlerResult(input, {
|
|
9613
|
+
silent: true,
|
|
9614
|
+
mode: getProcessMode() ?? "api"
|
|
9615
|
+
});
|
|
9356
9616
|
if (result.isErr()) throw result.error;
|
|
9357
9617
|
return result.value;
|
|
9358
9618
|
},
|
|
@@ -9467,7 +9727,10 @@ async function add(options = {}) {
|
|
|
9467
9727
|
projectDir: "",
|
|
9468
9728
|
error: formatInputValidationError("add", parsedInput.error)
|
|
9469
9729
|
};
|
|
9470
|
-
return await addHandler(parsedInput.data, {
|
|
9730
|
+
return await addHandler(parsedInput.data, {
|
|
9731
|
+
silent: true,
|
|
9732
|
+
mode: getProcessMode() ?? "api"
|
|
9733
|
+
}) ?? {
|
|
9471
9734
|
success: false,
|
|
9472
9735
|
addedAddons: [],
|
|
9473
9736
|
projectDir: parsedInput.data.projectDir ?? "",
|
|
@@ -9475,4 +9738,4 @@ async function add(options = {}) {
|
|
|
9475
9738
|
};
|
|
9476
9739
|
}
|
|
9477
9740
|
//#endregion
|
|
9478
|
-
export {
|
|
9741
|
+
export { setProcessMode as A, CLIError as C, ProjectCreationError as D, DirectoryConflictError as E, UserCancelledError as O, getLatestCLIVersion as S, DatabaseSetupError as T, ProjectLauncherSchema as _, TEMPLATE_COUNT as a, scrubReason as b, builder as c, createVirtual as d, docs as f, sponsors as g, router as h, SchemaNameSchema as i, ValidationError as k, create as l, getSchemaResult as m, GeneratorError$1 as n, VirtualFileSystem$1 as o, generate$1 as p, Result$1 as r, add as s, EMBEDDED_TEMPLATES$1 as t, createBtsCli as u, durationBucket as v, CompatibilityError as w, types_exports as x, reportDiagnostic as y };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-better-t-stack",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.41.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",
|
|
@@ -69,11 +69,11 @@
|
|
|
69
69
|
"prepublishOnly": "npm run build"
|
|
70
70
|
},
|
|
71
71
|
"dependencies": {
|
|
72
|
-
"@better-t-stack/template-generator": "^3.
|
|
73
|
-
"@better-t-stack/types": "^3.
|
|
72
|
+
"@better-t-stack/template-generator": "^3.41.1",
|
|
73
|
+
"@better-t-stack/types": "^3.41.1",
|
|
74
74
|
"@clack/core": "^1.4.3",
|
|
75
75
|
"@clack/prompts": "^1.7.0",
|
|
76
|
-
"@modelcontextprotocol/
|
|
76
|
+
"@modelcontextprotocol/server": "2.0.0",
|
|
77
77
|
"@trpc/server": "^11.18.0",
|
|
78
78
|
"better-result": "^3.0.0",
|
|
79
79
|
"consola": "^3.4.2",
|
|
@@ -93,6 +93,7 @@
|
|
|
93
93
|
"zod": "^4.4.3"
|
|
94
94
|
},
|
|
95
95
|
"devDependencies": {
|
|
96
|
+
"@modelcontextprotocol/client": "2.0.0",
|
|
96
97
|
"@types/bun": "^1.3.14",
|
|
97
98
|
"@types/fs-extra": "^11.0.4",
|
|
98
99
|
"@types/node": "^26.1.2",
|