create-better-t-stack 3.40.4 → 3.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/dist/cli.mjs +70 -30
- package/dist/index.d.mts +8 -2
- package/dist/index.mjs +1 -1
- package/dist/{src-CpGAduw0.mjs → src-NAkPN6DN.mjs} +456 -91
- 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-NAkPN6DN.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,
|
|
@@ -79,7 +117,7 @@ function getStackGuidance() {
|
|
|
79
117
|
"For project creation, build a full explicit config before calling bts_plan_project.",
|
|
80
118
|
"Always call bts_plan_project before bts_create_project.",
|
|
81
119
|
"Only call bts_create_project after the plan succeeds and matches the user's intent.",
|
|
82
|
-
"Use bts_plan_addons before bts_add_addons
|
|
120
|
+
"Use bts_plan_addons before bts_add_addons when adding addons or scaffolding a workspace package in an existing project."
|
|
83
121
|
],
|
|
84
122
|
createContract: {
|
|
85
123
|
requiresExplicitFields: [
|
|
@@ -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,69 +252,67 @@ 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
|
-
title: "Plan Better T Stack
|
|
228
|
-
description: "Validate and preview addon installation for an existing Better T Stack project without writing files. Always use this before bts_add_addons
|
|
269
|
+
title: "Plan Better T Stack Project Additions",
|
|
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.",
|
|
229
271
|
inputSchema: types_exports.AddInputSchema,
|
|
230
272
|
outputSchema: ToolResponseSchema,
|
|
231
273
|
annotations: {
|
|
232
|
-
title: "Plan Better T Stack
|
|
274
|
+
title: "Plan Better T Stack Project Additions",
|
|
233
275
|
readOnlyHint: true,
|
|
234
276
|
destructiveHint: false,
|
|
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,
|
|
242
284
|
dryRun: true
|
|
243
285
|
});
|
|
244
|
-
if (!result?.success) return formatToolError(result?.error ?? "Failed to plan
|
|
286
|
+
if (!result?.success) return formatToolError(result?.error ?? "Failed to plan project additions");
|
|
245
287
|
return formatToolSuccess(result);
|
|
246
288
|
} catch (error) {
|
|
247
289
|
return formatToolError(error);
|
|
248
290
|
}
|
|
249
|
-
});
|
|
291
|
+
}));
|
|
250
292
|
server.registerTool("bts_add_addons", {
|
|
251
|
-
title: "
|
|
252
|
-
description: "Install addons
|
|
293
|
+
title: "Apply Better T Stack Project Additions",
|
|
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.",
|
|
253
295
|
inputSchema: types_exports.AddInputSchema,
|
|
254
296
|
outputSchema: ToolResponseSchema,
|
|
255
297
|
annotations: {
|
|
256
|
-
title: "
|
|
298
|
+
title: "Apply Better T Stack Project Additions",
|
|
257
299
|
destructiveHint: true,
|
|
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
|
-
if (!result?.success) return formatToolError(result?.error ?? "Failed to
|
|
306
|
+
if (!result?.success) return formatToolError(result?.error ?? "Failed to update project");
|
|
265
307
|
return formatToolSuccess(result);
|
|
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">;
|
|
@@ -153,6 +156,7 @@ interface AddResult {
|
|
|
153
156
|
projectDir: string;
|
|
154
157
|
dryRun?: boolean;
|
|
155
158
|
plannedFileCount?: number;
|
|
159
|
+
addedPackage?: string;
|
|
156
160
|
error?: string;
|
|
157
161
|
}
|
|
158
162
|
//#endregion
|
|
@@ -349,6 +353,7 @@ declare const router: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
349
353
|
add: import("@trpc/server").TRPCMutationProcedure<{
|
|
350
354
|
input: {
|
|
351
355
|
addons?: ("none" | "pwa" | "tauri" | "electrobun" | "starlight" | "biome" | "lefthook" | "husky" | "mcp" | "turborepo" | "nx" | "vite-plus" | "fumadocs" | "ultracite" | "oxlint" | "opentui" | "wxt" | "skills" | "evlog")[] | undefined;
|
|
356
|
+
package?: string | undefined;
|
|
352
357
|
install?: boolean | undefined;
|
|
353
358
|
packageManager?: "bun" | "npm" | "pnpm" | undefined;
|
|
354
359
|
projectDir?: string | undefined;
|
|
@@ -360,6 +365,7 @@ declare const router: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
360
365
|
addJson: import("@trpc/server").TRPCMutationProcedure<{
|
|
361
366
|
input: {
|
|
362
367
|
addons?: ("none" | "pwa" | "tauri" | "electrobun" | "starlight" | "biome" | "lefthook" | "husky" | "mcp" | "turborepo" | "nx" | "vite-plus" | "fumadocs" | "ultracite" | "oxlint" | "opentui" | "wxt" | "skills" | "evlog")[] | undefined;
|
|
368
|
+
package?: string | undefined;
|
|
363
369
|
addonOptions?: {
|
|
364
370
|
wxt?: {
|
|
365
371
|
template: "svelte" | "solid" | "vanilla" | "vue" | "react";
|
|
@@ -473,9 +479,9 @@ declare function builder(): Promise<void>;
|
|
|
473
479
|
* ```
|
|
474
480
|
*/
|
|
475
481
|
declare function createVirtual(options: Partial<Omit<types_d_exports.ProjectConfig, "projectDir" | "relativePath">>): Promise<Result$1<VirtualFileTree$1, GeneratorError$1>>;
|
|
476
|
-
type AddOptions = Pick<types_d_exports.AddInput, "addons" | "addonOptions" | "install" | "packageManager" | "projectDir" | "dryRun">;
|
|
482
|
+
type AddOptions = Pick<types_d_exports.AddInput, "addons" | "addonOptions" | "package" | "install" | "packageManager" | "projectDir" | "dryRun">;
|
|
477
483
|
/**
|
|
478
|
-
* Programmatic API to add addons to an existing Better-T-Stack project.
|
|
484
|
+
* Programmatic API to add addons or a workspace package to an existing Better-T-Stack project.
|
|
479
485
|
*
|
|
480
486
|
* @example
|
|
481
487
|
* ```typescript
|
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-NAkPN6DN.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
|
}
|
|
@@ -4653,6 +4810,68 @@ const TASK_RUNNER_ADDONS = [
|
|
|
4653
4810
|
"nx",
|
|
4654
4811
|
"vite-plus"
|
|
4655
4812
|
];
|
|
4813
|
+
const fileExistsErrorSchema = z.object({ code: z.literal("EEXIST") });
|
|
4814
|
+
const configPackageScopeSchema = z.object({ name: z.string().regex(/^@[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?\/config$/) }).transform(({ name }) => name.slice(0, -7));
|
|
4815
|
+
const rootTypescriptVersionSchema = z.object({ devDependencies: z.object({ typescript: z.string().min(1) }) }).transform(({ devDependencies }) => devDependencies.typescript);
|
|
4816
|
+
function isFileExistsError(cause) {
|
|
4817
|
+
return fileExistsErrorSchema.safeParse(cause).success;
|
|
4818
|
+
}
|
|
4819
|
+
async function reserveWorkspacePackage(projectDir, packageName) {
|
|
4820
|
+
const packageDir = path.join(projectDir, "packages", packageName);
|
|
4821
|
+
return Result.tryPromise({
|
|
4822
|
+
try: async () => {
|
|
4823
|
+
await fs.mkdir(packageDir);
|
|
4824
|
+
return packageDir;
|
|
4825
|
+
},
|
|
4826
|
+
catch: (cause) => new CLIError({
|
|
4827
|
+
message: isFileExistsError(cause) ? `Workspace package already exists: packages/${packageName}` : `Failed to reserve workspace package: packages/${packageName}`,
|
|
4828
|
+
cause
|
|
4829
|
+
})
|
|
4830
|
+
});
|
|
4831
|
+
}
|
|
4832
|
+
async function addWorkspacePackage(vfs, projectDir, packageName, packageManager) {
|
|
4833
|
+
const packageDir = path.join(projectDir, "packages", packageName);
|
|
4834
|
+
if (await fs.pathExists(packageDir)) return Result.err(new CLIError({ message: `Workspace package already exists: packages/${packageName}` }));
|
|
4835
|
+
const configPackagePath = path.join(projectDir, "packages", "config", "package.json");
|
|
4836
|
+
const packageScopeResult = await Result.tryPromise({
|
|
4837
|
+
try: async () => configPackageScopeSchema.parse(await fs.readJson(configPackagePath)),
|
|
4838
|
+
catch: (cause) => new CLIError({
|
|
4839
|
+
message: "Cannot determine the workspace package scope. Expected packages/config/package.json to have a name like @my-app/config.",
|
|
4840
|
+
cause
|
|
4841
|
+
})
|
|
4842
|
+
});
|
|
4843
|
+
if (packageScopeResult.isErr()) return Result.err(packageScopeResult.error);
|
|
4844
|
+
const typescriptVersionResult = await Result.tryPromise({
|
|
4845
|
+
try: async () => rootTypescriptVersionSchema.parse(await fs.readJson(path.join(projectDir, "package.json"))),
|
|
4846
|
+
catch: (cause) => new CLIError({
|
|
4847
|
+
message: "Cannot determine the TypeScript version. Expected package.json to declare devDependencies.typescript.",
|
|
4848
|
+
cause
|
|
4849
|
+
})
|
|
4850
|
+
});
|
|
4851
|
+
if (typescriptVersionResult.isErr()) return Result.err(typescriptVersionResult.error);
|
|
4852
|
+
const packageScope = packageScopeResult.value;
|
|
4853
|
+
const fullPackageName = `${packageScope}/${packageName}`;
|
|
4854
|
+
if (fullPackageName.length > 214) return Result.err(new CLIError({ message: "Workspace package name must not exceed 214 characters including its scope." }));
|
|
4855
|
+
const packagePath = `packages/${packageName}`;
|
|
4856
|
+
vfs.writeFile(`${packagePath}/package.json`, `${JSON.stringify({
|
|
4857
|
+
name: fullPackageName,
|
|
4858
|
+
version: "0.0.0",
|
|
4859
|
+
private: true,
|
|
4860
|
+
type: "module",
|
|
4861
|
+
exports: { ".": "./src/index.ts" },
|
|
4862
|
+
scripts: { "check-types": "tsc --noEmit" },
|
|
4863
|
+
devDependencies: {
|
|
4864
|
+
[`${packageScope}/config`]: packageManager === "npm" ? "*" : "workspace:*",
|
|
4865
|
+
typescript: typescriptVersionResult.value
|
|
4866
|
+
}
|
|
4867
|
+
}, null, 2)}\n`);
|
|
4868
|
+
vfs.writeFile(`${packagePath}/tsconfig.json`, `${JSON.stringify({
|
|
4869
|
+
extends: `${packageScope}/config/tsconfig.base.json`,
|
|
4870
|
+
include: ["src/**/*.ts"]
|
|
4871
|
+
}, null, 2)}\n`);
|
|
4872
|
+
vfs.writeFile(`${packagePath}/src/index.ts`, "export {};\n");
|
|
4873
|
+
return Result.ok(void 0);
|
|
4874
|
+
}
|
|
4656
4875
|
function mergeAddonOptions(existingAddonOptions, nextAddonOptions) {
|
|
4657
4876
|
if (!existingAddonOptions && !nextAddonOptions) return;
|
|
4658
4877
|
const mergeOption = (existing, next) => {
|
|
@@ -4697,9 +4916,14 @@ function updateViteConfigImportsForVitePlus(vfs) {
|
|
|
4697
4916
|
}
|
|
4698
4917
|
}
|
|
4699
4918
|
async function addHandler(input, options = {}) {
|
|
4700
|
-
const { silent = false } = options;
|
|
4701
|
-
return runWithContextAsync({
|
|
4919
|
+
const { silent = false, mode } = options;
|
|
4920
|
+
return runWithContextAsync({
|
|
4921
|
+
silent,
|
|
4922
|
+
mode
|
|
4923
|
+
}, async () => {
|
|
4924
|
+
const startTime = Date.now();
|
|
4702
4925
|
const result = await addHandlerInternal(input);
|
|
4926
|
+
await reportAddOutcome(input, result, Date.now() - startTime);
|
|
4703
4927
|
if (result.isOk()) return result.value;
|
|
4704
4928
|
const error = result.error;
|
|
4705
4929
|
if (UserCancelledError.is(error)) {
|
|
@@ -4721,6 +4945,42 @@ async function addHandler(input, options = {}) {
|
|
|
4721
4945
|
process.exit(1);
|
|
4722
4946
|
});
|
|
4723
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
|
+
}
|
|
4724
4984
|
async function addHandlerInternal(input) {
|
|
4725
4985
|
const projectDir = input.projectDir || process.cwd();
|
|
4726
4986
|
const hardeningResult = validateAgentSafePathInput(projectDir, "projectDir");
|
|
@@ -4733,12 +4993,12 @@ async function addHandlerInternal(input) {
|
|
|
4733
4993
|
intro(pc.magenta("Add to your project"));
|
|
4734
4994
|
}
|
|
4735
4995
|
const existingConfig = await detectProjectConfig(projectDir);
|
|
4736
|
-
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.` }));
|
|
4737
4997
|
if (!isSilent()) log.info(pc.dim(`Detected project: ${existingConfig.projectName}`));
|
|
4738
4998
|
let addonsToAdd;
|
|
4739
4999
|
if (input.addons && input.addons.length > 0) {
|
|
4740
5000
|
addonsToAdd = input.addons.filter((addon) => addon !== "none" && !existingConfig.addons.includes(addon));
|
|
4741
|
-
if (addonsToAdd.length === 0) {
|
|
5001
|
+
if (addonsToAdd.length === 0 && !input.package) {
|
|
4742
5002
|
if (!isSilent()) {
|
|
4743
5003
|
log.warn(pc.yellow("Nothing to add — those addons are already installed"));
|
|
4744
5004
|
outro(pc.dim("Project unchanged"));
|
|
@@ -4749,7 +5009,8 @@ async function addHandlerInternal(input) {
|
|
|
4749
5009
|
projectDir
|
|
4750
5010
|
});
|
|
4751
5011
|
}
|
|
4752
|
-
} else if (
|
|
5012
|
+
} else if (input.package) addonsToAdd = [];
|
|
5013
|
+
else if (isSilent()) return Result.err(new CLIError({ message: "Addons or a package are required in silent mode." }));
|
|
4753
5014
|
else {
|
|
4754
5015
|
const promptResult = await Result.tryPromise({
|
|
4755
5016
|
try: () => getAddonsToAdd(existingConfig),
|
|
@@ -4776,7 +5037,10 @@ async function addHandlerInternal(input) {
|
|
|
4776
5037
|
const updatedAddons = [...existingConfig.addons, ...addonsToAdd];
|
|
4777
5038
|
const addonsValidationResult = validateAddonsAgainstConfig(updatedAddons, existingConfig);
|
|
4778
5039
|
if (addonsValidationResult.isErr()) return Result.err(new CLIError({ message: addonsValidationResult.error.message }));
|
|
4779
|
-
if (!isSilent())
|
|
5040
|
+
if (!isSilent()) {
|
|
5041
|
+
const additions = [addonsToAdd.length > 0 ? formatConfigValue(addonsToAdd) : void 0, input.package ? `package ${input.package}` : void 0].filter(Boolean);
|
|
5042
|
+
log.info(`${pc.dim("Adding")} ${pc.cyan(additions.join(" and "))}`);
|
|
5043
|
+
}
|
|
4780
5044
|
const mergedAddonOptions = mergeAddonOptions(existingConfig.addonOptions, input.addonOptions);
|
|
4781
5045
|
const config = {
|
|
4782
5046
|
projectName: existingConfig.projectName,
|
|
@@ -4804,33 +5068,41 @@ async function addHandlerInternal(input) {
|
|
|
4804
5068
|
...config,
|
|
4805
5069
|
addons: updatedAddons
|
|
4806
5070
|
};
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
5071
|
+
if (addonsToAdd.length > 0) {
|
|
5072
|
+
const requirementsResult = await checkLocalRequirements(updatedConfig);
|
|
5073
|
+
if (requirementsResult.isErr()) return Result.err(requirementsResult.error);
|
|
5074
|
+
if (!isSilent()) for (const warning of requirementsResult.value.warnings) log.warn(pc.yellow(warning));
|
|
5075
|
+
}
|
|
5076
|
+
if (!isSilent()) log.info(pc.dim("Preparing files…"));
|
|
4811
5077
|
const vfs = new VirtualFileSystem();
|
|
4812
|
-
|
|
4813
|
-
const
|
|
4814
|
-
if (
|
|
4815
|
-
|
|
4816
|
-
|
|
5078
|
+
if (input.package) {
|
|
5079
|
+
const packageResult = await addWorkspacePackage(vfs, projectDir, input.package, existingConfig.packageManager);
|
|
5080
|
+
if (packageResult.isErr()) return Result.err(packageResult.error);
|
|
5081
|
+
}
|
|
5082
|
+
if (addonsToAdd.length > 0) {
|
|
5083
|
+
for (const pkgPath of ADD_PACKAGE_JSON_PATHS) {
|
|
5084
|
+
const fullPath = path.join(projectDir, pkgPath);
|
|
5085
|
+
if (await fs.pathExists(fullPath)) {
|
|
5086
|
+
const content = await fs.readFile(fullPath, "utf-8");
|
|
5087
|
+
vfs.writeFile(pkgPath, content);
|
|
5088
|
+
}
|
|
4817
5089
|
}
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
5090
|
+
for (const filePath of ADD_TEXT_FILE_PATHS) {
|
|
5091
|
+
const fullPath = path.join(projectDir, filePath);
|
|
5092
|
+
if (await fs.pathExists(fullPath)) {
|
|
5093
|
+
const content = await fs.readFile(fullPath, "utf-8");
|
|
5094
|
+
vfs.writeFile(filePath, content);
|
|
5095
|
+
}
|
|
4824
5096
|
}
|
|
5097
|
+
await processAddonTemplates(vfs, EMBEDDED_TEMPLATES, config);
|
|
5098
|
+
processAddonsDeps(vfs, config);
|
|
5099
|
+
if (addonsToAdd.includes("turborepo")) processTurboConfig(vfs, updatedConfig);
|
|
5100
|
+
if (addonsToAdd.includes("nx")) processNxConfig(vfs, updatedConfig);
|
|
5101
|
+
if (addonsToAdd.includes("vite-plus")) processVitePlusConfig(vfs, updatedConfig);
|
|
5102
|
+
if (updatedAddons.some((addon) => TASK_RUNNER_ADDONS.includes(addon))) processPackageConfigs(vfs, updatedConfig);
|
|
5103
|
+
if (updatedAddons.includes("vite-plus")) updateViteConfigImportsForVitePlus(vfs);
|
|
5104
|
+
if (shouldRefreshLefthook(addonsToAdd, updatedAddons)) refreshLefthookTemplate(vfs, updatedConfig);
|
|
4825
5105
|
}
|
|
4826
|
-
await processAddonTemplates(vfs, EMBEDDED_TEMPLATES, config);
|
|
4827
|
-
processAddonsDeps(vfs, config);
|
|
4828
|
-
if (addonsToAdd.includes("turborepo")) processTurboConfig(vfs, updatedConfig);
|
|
4829
|
-
if (addonsToAdd.includes("nx")) processNxConfig(vfs, updatedConfig);
|
|
4830
|
-
if (addonsToAdd.includes("vite-plus")) processVitePlusConfig(vfs, updatedConfig);
|
|
4831
|
-
if (updatedAddons.some((addon) => TASK_RUNNER_ADDONS.includes(addon))) processPackageConfigs(vfs, updatedConfig);
|
|
4832
|
-
if (updatedAddons.includes("vite-plus")) updateViteConfigImportsForVitePlus(vfs);
|
|
4833
|
-
if (shouldRefreshLefthook(addonsToAdd, updatedAddons)) refreshLefthookTemplate(vfs, updatedConfig);
|
|
4834
5106
|
const tree = {
|
|
4835
5107
|
root: vfs.toTree(""),
|
|
4836
5108
|
fileCount: vfs.getFileCount(),
|
|
@@ -4840,7 +5112,7 @@ async function addHandlerInternal(input) {
|
|
|
4840
5112
|
if (input.dryRun) {
|
|
4841
5113
|
if (!isSilent()) {
|
|
4842
5114
|
log.success(pc.green("Dry run passed · no files written"));
|
|
4843
|
-
log.message(pc.dim(`${vfs.getFileCount()}
|
|
5115
|
+
log.message(pc.dim(`${vfs.getFileCount()} files planned`));
|
|
4844
5116
|
outro(pc.dim("Project unchanged"));
|
|
4845
5117
|
}
|
|
4846
5118
|
return Result.ok({
|
|
@@ -4848,17 +5120,41 @@ async function addHandlerInternal(input) {
|
|
|
4848
5120
|
addedAddons: addonsToAdd,
|
|
4849
5121
|
projectDir,
|
|
4850
5122
|
dryRun: true,
|
|
4851
|
-
plannedFileCount: vfs.getFileCount()
|
|
5123
|
+
plannedFileCount: vfs.getFileCount(),
|
|
5124
|
+
addedPackage: input.package
|
|
4852
5125
|
});
|
|
4853
5126
|
}
|
|
5127
|
+
let reservedPackageDir;
|
|
5128
|
+
if (input.package) {
|
|
5129
|
+
const reservationResult = await reserveWorkspacePackage(projectDir, input.package);
|
|
5130
|
+
if (reservationResult.isErr()) return Result.err(reservationResult.error);
|
|
5131
|
+
reservedPackageDir = reservationResult.value;
|
|
5132
|
+
}
|
|
4854
5133
|
const writeResult = await writeTree(tree, projectDir);
|
|
4855
|
-
if (writeResult.isErr())
|
|
4856
|
-
|
|
5134
|
+
if (writeResult.isErr()) {
|
|
5135
|
+
if (reservedPackageDir && input.package) {
|
|
5136
|
+
const cleanupResult = await Result.tryPromise({
|
|
5137
|
+
try: () => fs.remove(reservedPackageDir),
|
|
5138
|
+
catch: (cause) => new CLIError({
|
|
5139
|
+
message: `Failed to clean up incomplete workspace package: packages/${input.package}`,
|
|
5140
|
+
cause
|
|
5141
|
+
})
|
|
5142
|
+
});
|
|
5143
|
+
if (cleanupResult.isErr()) return Result.err(new CLIError({
|
|
5144
|
+
message: `Failed to write files: ${writeResult.error.message}. ${cleanupResult.error.message}`,
|
|
5145
|
+
cause: cleanupResult.error
|
|
5146
|
+
}));
|
|
5147
|
+
}
|
|
5148
|
+
return Result.err(new CLIError({ message: `Failed to write files: ${writeResult.error.message}` }));
|
|
5149
|
+
}
|
|
5150
|
+
if (vfs.getFileCount() > 0 && !isSilent()) log.info(pc.dim(`Wrote ${vfs.getFileCount()} files`));
|
|
4857
5151
|
const setupResult = await Result.tryPromise({
|
|
4858
|
-
try: () =>
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
5152
|
+
try: async () => {
|
|
5153
|
+
if (addonsToAdd.length > 0) await setupAddons({
|
|
5154
|
+
...config,
|
|
5155
|
+
addons: getSetupAddons(addonsToAdd, updatedAddons)
|
|
5156
|
+
});
|
|
5157
|
+
},
|
|
4862
5158
|
catch: (cause) => {
|
|
4863
5159
|
if (UserCancelledError.is(cause)) return cause;
|
|
4864
5160
|
return new CLIError({
|
|
@@ -4868,7 +5164,7 @@ async function addHandlerInternal(input) {
|
|
|
4868
5164
|
}
|
|
4869
5165
|
});
|
|
4870
5166
|
if (setupResult.isErr()) return Result.err(setupResult.error);
|
|
4871
|
-
await updateBtsConfig(projectDir, {
|
|
5167
|
+
if (addonsToAdd.length > 0) await updateBtsConfig(projectDir, {
|
|
4872
5168
|
addons: updatedAddons,
|
|
4873
5169
|
addonOptions: updatedConfig.addonOptions
|
|
4874
5170
|
});
|
|
@@ -4877,7 +5173,8 @@ async function addHandlerInternal(input) {
|
|
|
4877
5173
|
packageManager: config.packageManager
|
|
4878
5174
|
});
|
|
4879
5175
|
if (!isSilent()) {
|
|
4880
|
-
|
|
5176
|
+
const additions = [addonsToAdd.length > 0 ? formatConfigValue(addonsToAdd) : void 0, input.package ? `package ${input.package}` : void 0].filter(Boolean);
|
|
5177
|
+
log.success(pc.green(`Added ${additions.join(" and ")}`));
|
|
4881
5178
|
if (!input.install) {
|
|
4882
5179
|
const installCommand = config.packageManager === "npm" ? "npm install" : `${config.packageManager} install`;
|
|
4883
5180
|
log.message(`${pc.dim("Next step")}\n${pc.cyan(installCommand)}`);
|
|
@@ -4888,7 +5185,8 @@ async function addHandlerInternal(input) {
|
|
|
4888
5185
|
success: true,
|
|
4889
5186
|
addedAddons: addonsToAdd,
|
|
4890
5187
|
projectDir,
|
|
4891
|
-
plannedFileCount: vfs.getFileCount()
|
|
5188
|
+
plannedFileCount: vfs.getFileCount(),
|
|
5189
|
+
addedPackage: input.package
|
|
4892
5190
|
});
|
|
4893
5191
|
}
|
|
4894
5192
|
//#endregion
|
|
@@ -5980,8 +6278,11 @@ async function gatherConfig(flags, projectName, projectDir, relativePath, option
|
|
|
5980
6278
|
]
|
|
5981
6279
|
}
|
|
5982
6280
|
],
|
|
5983
|
-
onCancel: () => {
|
|
5984
|
-
throw new UserCancelledError({
|
|
6281
|
+
onCancel: ({ prompt }) => {
|
|
6282
|
+
throw new UserCancelledError({
|
|
6283
|
+
message: "Operation cancelled",
|
|
6284
|
+
prompt
|
|
6285
|
+
});
|
|
5985
6286
|
}
|
|
5986
6287
|
});
|
|
5987
6288
|
return {
|
|
@@ -6051,6 +6352,7 @@ async function getProjectName(initialName) {
|
|
|
6051
6352
|
counter++;
|
|
6052
6353
|
}
|
|
6053
6354
|
while (!isValid) {
|
|
6355
|
+
markPromptShown();
|
|
6054
6356
|
const response = await text({
|
|
6055
6357
|
message: "Where should we create your project?",
|
|
6056
6358
|
placeholder: defaultName,
|
|
@@ -6065,40 +6367,34 @@ async function getProjectName(initialName) {
|
|
|
6065
6367
|
}
|
|
6066
6368
|
}
|
|
6067
6369
|
});
|
|
6068
|
-
if (isCancel(response)) throw new UserCancelledError({
|
|
6370
|
+
if (isCancel(response)) throw new UserCancelledError({
|
|
6371
|
+
message: "Operation cancelled.",
|
|
6372
|
+
prompt: "projectName"
|
|
6373
|
+
});
|
|
6069
6374
|
projectPath = response || defaultName;
|
|
6070
6375
|
isValid = true;
|
|
6071
6376
|
}
|
|
6072
6377
|
return projectPath;
|
|
6073
6378
|
}
|
|
6074
6379
|
//#endregion
|
|
6075
|
-
//#region src/utils/telemetry.ts
|
|
6076
|
-
/**
|
|
6077
|
-
* Returns true if telemetry/analytics should be enabled, false otherwise.
|
|
6078
|
-
*
|
|
6079
|
-
* - If BTS_TELEMETRY_DISABLED is present and "1", disables analytics.
|
|
6080
|
-
* - Otherwise, BTS_TELEMETRY: "0" disables, "1" enables (default: enabled).
|
|
6081
|
-
*/
|
|
6082
|
-
function isTelemetryEnabled() {
|
|
6083
|
-
const BTS_TELEMETRY_DISABLED = process.env.BTS_TELEMETRY_DISABLED;
|
|
6084
|
-
if (BTS_TELEMETRY_DISABLED !== void 0) return BTS_TELEMETRY_DISABLED !== "1";
|
|
6085
|
-
return true;
|
|
6086
|
-
}
|
|
6087
|
-
//#endregion
|
|
6088
6380
|
//#region src/utils/analytics.ts
|
|
6089
6381
|
const CONVEX_INGEST_URL = "https://striped-seahorse-863.convex.site/api/analytics/ingest";
|
|
6382
|
+
const SEND_TIMEOUT_MS = 3e3;
|
|
6090
6383
|
async function sendConvexEvent(payload) {
|
|
6091
6384
|
await Result.tryPromise({
|
|
6092
6385
|
try: () => fetch(CONVEX_INGEST_URL, {
|
|
6093
6386
|
method: "POST",
|
|
6094
6387
|
headers: { "Content-Type": "application/json" },
|
|
6095
|
-
body: JSON.stringify(payload)
|
|
6388
|
+
body: JSON.stringify(payload),
|
|
6389
|
+
signal: AbortSignal.timeout(SEND_TIMEOUT_MS),
|
|
6390
|
+
keepalive: true
|
|
6096
6391
|
}),
|
|
6097
6392
|
catch: () => void 0
|
|
6098
6393
|
});
|
|
6099
6394
|
}
|
|
6100
|
-
function buildAnalyticsEvent(config) {
|
|
6395
|
+
function buildAnalyticsEvent(config, mode) {
|
|
6101
6396
|
return types_exports.AnalyticsEventSchema.parse({
|
|
6397
|
+
mode,
|
|
6102
6398
|
database: config.database,
|
|
6103
6399
|
orm: config.orm,
|
|
6104
6400
|
backend: config.backend,
|
|
@@ -6120,10 +6416,10 @@ function buildAnalyticsEvent(config) {
|
|
|
6120
6416
|
platform: process.platform
|
|
6121
6417
|
});
|
|
6122
6418
|
}
|
|
6123
|
-
async function trackProjectCreation(config, disableAnalytics = false) {
|
|
6419
|
+
async function trackProjectCreation(config, disableAnalytics = false, mode) {
|
|
6124
6420
|
if (!isTelemetryEnabled() || disableAnalytics) return;
|
|
6125
6421
|
await Result.tryPromise({
|
|
6126
|
-
try: () => sendConvexEvent(buildAnalyticsEvent(config)),
|
|
6422
|
+
try: () => sendConvexEvent(buildAnalyticsEvent(config, mode)),
|
|
6127
6423
|
catch: () => void 0
|
|
6128
6424
|
});
|
|
6129
6425
|
}
|
|
@@ -8709,10 +9005,14 @@ async function createProject(options, cliInput) {
|
|
|
8709
9005
|
}));
|
|
8710
9006
|
yield* Result.await(formatProject(projectDir));
|
|
8711
9007
|
if (!isSilent()) log.success("Project scaffolded");
|
|
8712
|
-
if (options.install)
|
|
8713
|
-
|
|
8714
|
-
|
|
8715
|
-
|
|
9008
|
+
if (options.install) {
|
|
9009
|
+
const installStartTime = Date.now();
|
|
9010
|
+
yield* Result.await(installDependencies({
|
|
9011
|
+
projectDir,
|
|
9012
|
+
packageManager: options.packageManager
|
|
9013
|
+
}));
|
|
9014
|
+
await reportSlowStage("create", "install", Date.now() - installStartTime, options.packageManager);
|
|
9015
|
+
}
|
|
8716
9016
|
yield* Result.await(initializeGit(projectDir, options.git));
|
|
8717
9017
|
if (!isSilent()) await displayPostInstallInstructions({
|
|
8718
9018
|
...options,
|
|
@@ -8775,17 +9075,49 @@ function createEmptyResult(timeScaffolded, elapsedTimeMs, error) {
|
|
|
8775
9075
|
};
|
|
8776
9076
|
}
|
|
8777
9077
|
async function executeCreateProjectHandler(input, options) {
|
|
8778
|
-
const { silent = false } = options;
|
|
8779
|
-
return runWithContextAsync({
|
|
9078
|
+
const { silent = false, mode } = options;
|
|
9079
|
+
return runWithContextAsync({
|
|
9080
|
+
silent,
|
|
9081
|
+
mode,
|
|
9082
|
+
analyticsDisabled: input.disableAnalytics
|
|
9083
|
+
}, async () => {
|
|
8780
9084
|
const startTime = Date.now();
|
|
8781
9085
|
const timeScaffolded = (/* @__PURE__ */ new Date()).toISOString();
|
|
9086
|
+
const result = await createProjectHandlerInternal(input, startTime, timeScaffolded);
|
|
9087
|
+
await reportCreateOutcome(input, result, Date.now() - startTime);
|
|
8782
9088
|
return {
|
|
8783
|
-
result
|
|
9089
|
+
result,
|
|
8784
9090
|
startTime,
|
|
8785
9091
|
timeScaffolded
|
|
8786
9092
|
};
|
|
8787
9093
|
});
|
|
8788
9094
|
}
|
|
9095
|
+
/** Diagnostics for what the success-only project event cannot show; awaited so it beats process.exit. */
|
|
9096
|
+
async function reportCreateOutcome(input, result, elapsedMs) {
|
|
9097
|
+
const mode = resolveInvocationMode(input.yes);
|
|
9098
|
+
if (result.isOk()) {
|
|
9099
|
+
if (!input.dryRun) await reportSlowStage("create", "create", elapsedMs, input.packageManager ?? "unknown");
|
|
9100
|
+
return;
|
|
9101
|
+
}
|
|
9102
|
+
const error = result.error;
|
|
9103
|
+
if (UserCancelledError.is(error)) {
|
|
9104
|
+
await reportDiagnostic("cli_cancelled", {
|
|
9105
|
+
command: "create",
|
|
9106
|
+
mode,
|
|
9107
|
+
prompt: error.prompt ?? "unknown"
|
|
9108
|
+
});
|
|
9109
|
+
return;
|
|
9110
|
+
}
|
|
9111
|
+
await reportDiagnostic("cli_failed", {
|
|
9112
|
+
command: "create",
|
|
9113
|
+
mode,
|
|
9114
|
+
stage: failureStage(error),
|
|
9115
|
+
error: errorClass(error),
|
|
9116
|
+
reason: scrubReason(error),
|
|
9117
|
+
packageManager: input.packageManager,
|
|
9118
|
+
backend: input.backend
|
|
9119
|
+
});
|
|
9120
|
+
}
|
|
8789
9121
|
async function createProjectHandlerResult(input, options = {}) {
|
|
8790
9122
|
return (await executeCreateProjectHandler(input, options)).result;
|
|
8791
9123
|
}
|
|
@@ -8958,7 +9290,7 @@ async function createProjectHandlerInternal(input, startTime, timeScaffolded) {
|
|
|
8958
9290
|
dbSetupOptions: effectiveDbSetupOptions,
|
|
8959
9291
|
packageManagerVersion: localRequirements.packageManagerVersion
|
|
8960
9292
|
}));
|
|
8961
|
-
await trackProjectCreation(config, input.disableAnalytics);
|
|
9293
|
+
await trackProjectCreation(config, input.disableAnalytics, resolveInvocationMode(input.yes));
|
|
8962
9294
|
const historyResult = await addToHistory(config, reproducibleCommand);
|
|
8963
9295
|
if (historyResult.isErr() && !isSilent()) {
|
|
8964
9296
|
log.warn(pc.yellow(historyResult.error.message));
|
|
@@ -9159,17 +9491,21 @@ const router = t.router({
|
|
|
9159
9491
|
description: "Create a project from a raw JSON payload (agent-friendly)",
|
|
9160
9492
|
jsonInput: "always"
|
|
9161
9493
|
}).input(types_exports.CreateInputSchema).mutation(async ({ input }) => {
|
|
9162
|
-
const result = await createProjectHandler(input, {
|
|
9494
|
+
const result = await createProjectHandler(input, {
|
|
9495
|
+
silent: true,
|
|
9496
|
+
mode: "json"
|
|
9497
|
+
});
|
|
9163
9498
|
if (!result) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
9164
9499
|
if (!result.success) throw new CLIError({ message: result.error || "Unknown error occurred" });
|
|
9165
9500
|
return result;
|
|
9166
9501
|
}),
|
|
9167
9502
|
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)),
|
|
9168
|
-
sponsors: t.procedure.meta({ description: "Show Better-T-Stack sponsors" }).mutation(() => showSponsorsCommand()),
|
|
9169
|
-
docs: t.procedure.meta({ description: "Open Better-T-Stack documentation" }).mutation(() => openDocsCommand()),
|
|
9170
|
-
builder: t.procedure.meta({ description: "Open the web-based stack builder" }).mutation(() => openBuilderCommand()),
|
|
9171
|
-
add: t.procedure.meta({ description: "Add addons to an existing Better-T-Stack project" }).input(z.object({
|
|
9503
|
+
sponsors: t.procedure.meta({ description: "Show Better-T-Stack sponsors" }).mutation(() => trackCommand("sponsors", () => showSponsorsCommand())),
|
|
9504
|
+
docs: t.procedure.meta({ description: "Open Better-T-Stack documentation" }).mutation(() => trackCommand("docs", () => openDocsCommand())),
|
|
9505
|
+
builder: t.procedure.meta({ description: "Open the web-based stack builder" }).mutation(() => trackCommand("builder", () => openBuilderCommand())),
|
|
9506
|
+
add: t.procedure.meta({ description: "Add addons or a workspace package to an existing Better-T-Stack project" }).input(z.object({
|
|
9172
9507
|
addons: z.array(types_exports.AddonsSchema).optional().describe("Addons to add"),
|
|
9508
|
+
package: types_exports.WorkspacePackageNameSchema.optional(),
|
|
9173
9509
|
install: z.boolean().optional().default(false).describe("Install dependencies after adding"),
|
|
9174
9510
|
packageManager: types_exports.PackageManagerSchema.optional().describe("Package manager to use"),
|
|
9175
9511
|
projectDir: z.string().optional().describe("Project directory (defaults to current)"),
|
|
@@ -9178,10 +9514,13 @@ const router = t.router({
|
|
|
9178
9514
|
await addHandler(input);
|
|
9179
9515
|
}),
|
|
9180
9516
|
addJson: t.procedure.meta({
|
|
9181
|
-
description: "Add addons from a raw JSON payload (agent-friendly)",
|
|
9517
|
+
description: "Add addons or a workspace package from a raw JSON payload (agent-friendly)",
|
|
9182
9518
|
jsonInput: "always"
|
|
9183
9519
|
}).input(types_exports.AddInputSchema).mutation(async ({ input }) => {
|
|
9184
|
-
const result = await addHandler(input, {
|
|
9520
|
+
const result = await addHandler(input, {
|
|
9521
|
+
silent: true,
|
|
9522
|
+
mode: "json"
|
|
9523
|
+
});
|
|
9185
9524
|
if (!result) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
9186
9525
|
if (!result.success) throw new CLIError({ message: result.error || "Unknown error occurred" });
|
|
9187
9526
|
return result;
|
|
@@ -9191,9 +9530,29 @@ const router = t.router({
|
|
|
9191
9530
|
clear: z.boolean().optional().default(false).describe("Clear all history"),
|
|
9192
9531
|
json: z.boolean().optional().default(false).describe("Output as JSON")
|
|
9193
9532
|
})).mutation(async ({ input }) => {
|
|
9194
|
-
await historyHandler(input);
|
|
9533
|
+
await trackCommand("history", () => historyHandler(input), (ok) => ok);
|
|
9195
9534
|
})
|
|
9196
9535
|
});
|
|
9536
|
+
/** Usage diagnostics for commands that have no project event of their own. */
|
|
9537
|
+
async function trackCommand(command, run, isOk = () => true) {
|
|
9538
|
+
const startTime = Date.now();
|
|
9539
|
+
try {
|
|
9540
|
+
const value = await run();
|
|
9541
|
+
await reportDiagnostic("cli_command", {
|
|
9542
|
+
command,
|
|
9543
|
+
ok: isOk(value),
|
|
9544
|
+
duration: durationBucket(Date.now() - startTime)
|
|
9545
|
+
});
|
|
9546
|
+
return value;
|
|
9547
|
+
} catch (cause) {
|
|
9548
|
+
await reportDiagnostic("cli_command", {
|
|
9549
|
+
command,
|
|
9550
|
+
ok: false,
|
|
9551
|
+
duration: durationBucket(Date.now() - startTime)
|
|
9552
|
+
});
|
|
9553
|
+
throw cause;
|
|
9554
|
+
}
|
|
9555
|
+
}
|
|
9197
9556
|
function createBtsCli() {
|
|
9198
9557
|
return createCli({
|
|
9199
9558
|
router,
|
|
@@ -9251,7 +9610,10 @@ async function create(projectName, options) {
|
|
|
9251
9610
|
};
|
|
9252
9611
|
return Result.tryPromise({
|
|
9253
9612
|
try: async () => {
|
|
9254
|
-
const result = await createProjectHandlerResult(input, {
|
|
9613
|
+
const result = await createProjectHandlerResult(input, {
|
|
9614
|
+
silent: true,
|
|
9615
|
+
mode: getProcessMode() ?? "api"
|
|
9616
|
+
});
|
|
9255
9617
|
if (result.isErr()) throw result.error;
|
|
9256
9618
|
return result.value;
|
|
9257
9619
|
},
|
|
@@ -9342,7 +9704,7 @@ async function createVirtual(options) {
|
|
|
9342
9704
|
});
|
|
9343
9705
|
}
|
|
9344
9706
|
/**
|
|
9345
|
-
* Programmatic API to add addons to an existing Better-T-Stack project.
|
|
9707
|
+
* Programmatic API to add addons or a workspace package to an existing Better-T-Stack project.
|
|
9346
9708
|
*
|
|
9347
9709
|
* @example
|
|
9348
9710
|
* ```typescript
|
|
@@ -9366,7 +9728,10 @@ async function add(options = {}) {
|
|
|
9366
9728
|
projectDir: "",
|
|
9367
9729
|
error: formatInputValidationError("add", parsedInput.error)
|
|
9368
9730
|
};
|
|
9369
|
-
return await addHandler(parsedInput.data, {
|
|
9731
|
+
return await addHandler(parsedInput.data, {
|
|
9732
|
+
silent: true,
|
|
9733
|
+
mode: getProcessMode() ?? "api"
|
|
9734
|
+
}) ?? {
|
|
9370
9735
|
success: false,
|
|
9371
9736
|
addedAddons: [],
|
|
9372
9737
|
projectDir: parsedInput.data.projectDir ?? "",
|
|
@@ -9374,4 +9739,4 @@ async function add(options = {}) {
|
|
|
9374
9739
|
};
|
|
9375
9740
|
}
|
|
9376
9741
|
//#endregion
|
|
9377
|
-
export {
|
|
9742
|
+
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.0",
|
|
4
4
|
"description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"better-auth",
|
|
@@ -69,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.0",
|
|
73
|
+
"@better-t-stack/types": "^3.41.0",
|
|
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",
|