@mintlify/cli 4.0.1469 → 4.0.1471

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mintlify/cli",
3
- "version": "4.0.1469",
3
+ "version": "4.0.1471",
4
4
  "description": "The Mintlify CLI",
5
5
  "engines": {
6
6
  "node": ">=18.0.0"
@@ -45,11 +45,11 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@inquirer/prompts": "7.9.0",
48
- "@mintlify/common": "1.0.1125",
49
- "@mintlify/link-rot": "3.0.1327",
48
+ "@mintlify/common": "1.0.1126",
49
+ "@mintlify/link-rot": "3.0.1328",
50
50
  "@mintlify/models": "0.0.351",
51
- "@mintlify/prebuild": "1.0.1279",
52
- "@mintlify/previewing": "4.0.1351",
51
+ "@mintlify/prebuild": "1.0.1280",
52
+ "@mintlify/previewing": "4.0.1352",
53
53
  "@mintlify/validation": "0.1.844",
54
54
  "adm-zip": "0.6.0",
55
55
  "chalk": "5.2.0",
@@ -86,7 +86,7 @@
86
86
  "keytar": "7.9.0"
87
87
  },
88
88
  "devDependencies": {
89
- "@mintlify/editor": "0.0.355",
89
+ "@mintlify/editor": "0.0.356",
90
90
  "@mintlify/ts-config": "2.0.2",
91
91
  "@tsconfig/recommended": "1.0.2",
92
92
  "@types/detect-port": "1.3.2",
@@ -105,5 +105,5 @@
105
105
  "vitest": "2.1.9",
106
106
  "vitest-mock-process": "1.0.4"
107
107
  },
108
- "gitHead": "38d9ac8a4f2425cb701131ef72de78da3ecc8677"
108
+ "gitHead": "c1d2437e2890e97075390d6160f6bcf72541e574"
109
109
  }
@@ -12,7 +12,12 @@ import {
12
12
  } from './manifest.js';
13
13
  import { mapWithConcurrency } from './mapWithConcurrency.js';
14
14
  import { checkFileTestability } from './tasks/checkTestability.js';
15
- import type { GeneratedTask, GeneratedTestManifest, TaskUpdate } from './types.js';
15
+ import type {
16
+ GeneratedTask,
17
+ GeneratedTestManifest,
18
+ TaskUpdate,
19
+ TestGenerationResult,
20
+ } from './types.js';
16
21
 
17
22
  const SCAFFOLD_FILENAMES = new Set([MANIFEST_FILENAME, 'package.json']);
18
23
  const IGNORED_GENERATED_DIRECTORIES = new Set(['.git', 'node_modules']);
@@ -228,6 +233,7 @@ export async function generateTestCode({
228
233
  concurrency,
229
234
  signal,
230
235
  onTaskUpdate,
236
+ onGenerationResult,
231
237
  }: {
232
238
  tasks: GeneratedTask[];
233
239
  docsRoot: string;
@@ -235,6 +241,7 @@ export async function generateTestCode({
235
241
  concurrency: number;
236
242
  signal: AbortSignal;
237
243
  onTaskUpdate: (update: TaskUpdate) => void;
244
+ onGenerationResult?: (result: TestGenerationResult) => void;
238
245
  }): Promise<GeneratedTask[]> {
239
246
  const generateTask = async (task: GeneratedTask): Promise<GeneratedTask> => {
240
247
  if (task.error || !task.testable) return task;
@@ -269,8 +276,18 @@ export async function generateTestCode({
269
276
  return { ...task, manifest: existing };
270
277
  }
271
278
 
279
+ const startedAt = Date.now();
280
+ const startingTokens = tokens;
281
+ let generationAttempts = 0;
282
+ const emitGenerationResult = (result: TestGenerationResult) => {
283
+ try {
284
+ onGenerationResult?.(result);
285
+ } catch {}
286
+ };
287
+
272
288
  for (let attempt = 1; attempt <= 2 && !signal.aborted; attempt++) {
273
289
  attempts += 1;
290
+ generationAttempts += 1;
274
291
  if (attempt === 1) emit('generating', tokens);
275
292
  try {
276
293
  const prompt = buildTaskPrompt(docsRoot, task.file, task.directory);
@@ -301,11 +318,19 @@ export async function generateTestCode({
301
318
  throw new Error([`${MANIFEST_FILENAME} has problems: ${first}`, ...rest].join('\n'));
302
319
  }
303
320
 
304
- emit(
305
- 'generated',
306
- tokens,
307
- manifest.generatedFiles.map((file) => path.resolve(task.directory, file))
321
+ const generatedFiles = manifest.generatedFiles.map((file) =>
322
+ path.resolve(task.directory, file)
308
323
  );
324
+ emit('generated', tokens, generatedFiles);
325
+ emitGenerationResult({
326
+ status: 'succeeded',
327
+ agent: task.agent,
328
+ model,
329
+ attemptCount: generationAttempts,
330
+ tokenCount: tokens - startingTokens,
331
+ durationMs: Date.now() - startedAt,
332
+ generatedFileCount: generatedFiles.length,
333
+ });
309
334
  return { ...task, attempts, tokens, manifest };
310
335
  } catch (error) {
311
336
  lastError = error instanceof Error ? error.message : String(error);
@@ -318,6 +343,15 @@ export async function generateTestCode({
318
343
  return { ...task, attempts, tokens, generatedFiles, error: 'run cancelled' };
319
344
  }
320
345
  const generatedFiles = await discoverGeneratedFiles(task.directory);
346
+ emitGenerationResult({
347
+ status: 'failed',
348
+ agent: task.agent,
349
+ model,
350
+ attemptCount: generationAttempts,
351
+ tokenCount: tokens - startingTokens,
352
+ durationMs: Date.now() - startedAt,
353
+ generatedFileCount: generatedFiles.length,
354
+ });
321
355
  const detailsPath = await writeErrorDetails(task.directory, attemptErrors);
322
356
  const error = detailsPath ? `${lastError}\nDetails: ${detailsPath}` : lastError;
323
357
  emit('agent_error', tokens, generatedFiles, error);
@@ -30,6 +30,7 @@ export type {
30
30
  TaskResult,
31
31
  TaskStatus,
32
32
  TaskUpdate,
33
+ TestGenerationResult,
33
34
  TestRunSummary,
34
35
  TestRunsIndex,
35
36
  } from './types.js';
@@ -45,6 +46,7 @@ export async function runCodeTests({
45
46
  commandTimeoutMs,
46
47
  signal,
47
48
  onTaskUpdate,
49
+ onGenerationResult,
48
50
  }: RunCodeTestsOptions): Promise<CodeTestReport> {
49
51
  const startedAt = new Date();
50
52
  const uniqueAgents = [...new Set(agents)];
@@ -85,6 +87,7 @@ export async function runCodeTests({
85
87
  concurrency,
86
88
  signal,
87
89
  onTaskUpdate,
90
+ onGenerationResult,
88
91
  });
89
92
 
90
93
  const results = await executeCodeBlocks({
@@ -105,6 +105,16 @@ export interface TaskUpdate {
105
105
  error?: string;
106
106
  }
107
107
 
108
+ export interface TestGenerationResult {
109
+ status: 'succeeded' | 'failed';
110
+ agent: HarnessAgent;
111
+ model: string;
112
+ attemptCount: number;
113
+ tokenCount: number;
114
+ durationMs: number;
115
+ generatedFileCount: number;
116
+ }
117
+
108
118
  export interface RunSave {
109
119
  version: 1;
110
120
  runId: string;
@@ -125,6 +135,7 @@ export interface RunCodeTestsOptions {
125
135
  commandTimeoutMs: number;
126
136
  signal: AbortSignal;
127
137
  onTaskUpdate: (update: TaskUpdate) => void;
138
+ onGenerationResult?: (result: TestGenerationResult) => void;
128
139
  }
129
140
 
130
141
  const identifier = z.string().trim().min(1).max(120);
package/src/mintTest.tsx CHANGED
@@ -14,6 +14,11 @@ import {
14
14
  import { CMD_EXEC_PATH, findDocsRoot, isAI, terminate } from './helpers.js';
15
15
  import { getAccessToken } from './keyring.js';
16
16
  import { fileInNavPaths, loadDocsNavScope } from './mintTestNav.js';
17
+ import {
18
+ trackMintTestGenerationResult,
19
+ trackMintTestRunCompleted,
20
+ trackMintTestRunStarted,
21
+ } from './mintTestTelemetry.js';
17
22
  import { firstLine, taskProblemLabel } from './mintTestText.js';
18
23
  import { formatDuration, runMintTestUi } from './mintTestUi.js';
19
24
 
@@ -41,6 +46,7 @@ export async function mintTestHandler(): Promise<void> {
41
46
  const autoSelectedFiles = (
42
47
  scope ? pages.filter((page) => fileInNavPaths(page.file, scope.autoSelectedPaths)) : pages
43
48
  ).map((page) => page.file);
49
+ const totalPages = scope ? scope.navPaths.size : pages.length;
44
50
  const outputDirectory = path.join(docsRoot, 'tests', 'mint-test');
45
51
 
46
52
  let save: RunSave | null = await loadRunSave(outputDirectory);
@@ -62,6 +68,7 @@ export async function mintTestHandler(): Promise<void> {
62
68
  docsRoot,
63
69
  pages,
64
70
  autoSelectedFiles,
71
+ totalPages,
65
72
  save,
66
73
  lastReport,
67
74
  });
@@ -69,18 +76,42 @@ export async function mintTestHandler(): Promise<void> {
69
76
  return;
70
77
  }
71
78
 
79
+ const agents = save?.agents ?? ['claude'];
80
+ const model = save?.model ?? 'claude-opus-4-8';
81
+ const selectedFiles = save?.selectedFiles ?? autoSelectedFiles;
82
+ const resumed = save !== null;
72
83
  if (save) process.stdout.write(`resuming run ${save.runId}\n`);
84
+ void trackMintTestRunStarted({
85
+ mode: 'non_interactive',
86
+ resumed,
87
+ agents,
88
+ model,
89
+ selectedPageCount: new Set(selectedFiles).size,
90
+ totalPageCount: totalPages,
91
+ });
73
92
  const report = await runCodeTests({
74
93
  path: targetPath,
75
- agents: save?.agents ?? ['claude'],
76
- model: save?.model ?? 'claude-opus-4-8',
77
- selectedFiles: save?.selectedFiles ?? autoSelectedFiles,
94
+ agents,
95
+ model,
96
+ selectedFiles,
78
97
  outputDirectory,
79
98
  runId: save?.runId ?? null,
80
99
  concurrency: DEFAULT_CONCURRENCY,
81
100
  commandTimeoutMs: DEFAULT_COMMAND_TIMEOUT_MS,
82
101
  signal: new AbortController().signal,
83
102
  onTaskUpdate: () => {},
103
+ onGenerationResult: (result) => {
104
+ void trackMintTestGenerationResult({
105
+ ...result,
106
+ mode: 'non_interactive',
107
+ resumed,
108
+ });
109
+ },
110
+ });
111
+ await trackMintTestRunCompleted(report, {
112
+ mode: 'non_interactive',
113
+ resumed,
114
+ totalPageCount: totalPages,
84
115
  });
85
116
  const counts = countReport(report.tasks);
86
117
  process.stdout.write(
@@ -0,0 +1,101 @@
1
+ import {
2
+ countReport,
3
+ type CodeTestReport,
4
+ type HarnessAgent,
5
+ type TestGenerationResult,
6
+ } from './agent-harness/index.js';
7
+ import { trackEvent } from './telemetry/track.js';
8
+
9
+ export type MintTestMode = 'interactive' | 'non_interactive';
10
+
11
+ export interface MintTestRunStartedOptions {
12
+ mode: MintTestMode;
13
+ resumed: boolean;
14
+ agents: HarnessAgent[];
15
+ model: string;
16
+ selectedPageCount: number;
17
+ totalPageCount: number;
18
+ }
19
+
20
+ export interface MintTestRunCompletedOptions {
21
+ mode: MintTestMode;
22
+ resumed: boolean;
23
+ totalPageCount: number;
24
+ }
25
+
26
+ export interface MintTestGenerationResultOptions extends TestGenerationResult {
27
+ mode: MintTestMode;
28
+ resumed: boolean;
29
+ }
30
+
31
+ export async function trackMintTestRunStarted({
32
+ mode,
33
+ resumed,
34
+ agents,
35
+ model,
36
+ selectedPageCount,
37
+ totalPageCount,
38
+ }: MintTestRunStartedOptions): Promise<void> {
39
+ await trackEvent('cli.test.started', {
40
+ mode,
41
+ resumed,
42
+ agents,
43
+ model,
44
+ selected_page_count: selectedPageCount,
45
+ total_page_count: totalPageCount,
46
+ });
47
+ }
48
+
49
+ export async function trackMintTestRunCompleted(
50
+ report: CodeTestReport,
51
+ { mode, resumed, totalPageCount }: MintTestRunCompletedOptions
52
+ ): Promise<void> {
53
+ const counts = countReport(report.tasks);
54
+ const skippedCheckCount = report.tasks.filter(
55
+ (task) => !task.testable && task.status === 'passed'
56
+ ).length;
57
+ const tokenCount = report.tasks.reduce((total, task) => total + task.tokens, 0);
58
+ const runnableCheckCount = report.tasks.filter((task) => task.testable).length;
59
+
60
+ await trackEvent('cli.test.completed', {
61
+ mode,
62
+ resumed,
63
+ status: report.status,
64
+ agents: report.agents,
65
+ model: report.model,
66
+ selected_page_count: new Set(report.selectedFiles).size,
67
+ total_page_count: totalPageCount,
68
+ total_check_count: report.tasks.length,
69
+ runnable_check_count: runnableCheckCount,
70
+ passed_check_count: counts.passed - skippedCheckCount,
71
+ failed_check_count: counts.failed,
72
+ skipped_check_count: skippedCheckCount,
73
+ agent_error_check_count: counts.agentErrors,
74
+ cancelled_check_count: counts.cancelled,
75
+ token_count: tokenCount,
76
+ duration_ms: report.durationMs,
77
+ });
78
+ }
79
+
80
+ export async function trackMintTestGenerationResult({
81
+ status,
82
+ mode,
83
+ resumed,
84
+ agent,
85
+ model,
86
+ attemptCount,
87
+ tokenCount,
88
+ durationMs,
89
+ generatedFileCount,
90
+ }: MintTestGenerationResultOptions): Promise<void> {
91
+ await trackEvent(`cli.test.generation.${status}`, {
92
+ mode,
93
+ resumed,
94
+ agent,
95
+ model,
96
+ attempt_count: attemptCount,
97
+ token_count: tokenCount,
98
+ duration_ms: durationMs,
99
+ generated_file_count: generatedFileCount,
100
+ });
101
+ }
@@ -15,6 +15,11 @@ import {
15
15
  type TaskUpdate,
16
16
  } from './agent-harness/index.js';
17
17
  import { readFilePreview } from './mintTestFilePreview.js';
18
+ import {
19
+ trackMintTestGenerationResult,
20
+ trackMintTestRunCompleted,
21
+ trackMintTestRunStarted,
22
+ } from './mintTestTelemetry.js';
18
23
  import { firstLine, footerSegments, taskProblemLabel } from './mintTestText.js';
19
24
  import { toggleScopeSelection, visibleScopeRows, type ScopeTreeRow } from './mintTestTree.js';
20
25
 
@@ -25,6 +30,7 @@ interface MintTestUiProps {
25
30
  docsRoot: string;
26
31
  pages: DocsPage[];
27
32
  autoSelectedFiles: string[];
33
+ totalPages: number;
28
34
  save: RunSave | null;
29
35
  lastReport: CodeTestReport | null;
30
36
  onFinish: (exitCode: number) => void;
@@ -827,6 +833,7 @@ function MintTestApp({
827
833
  docsRoot,
828
834
  pages,
829
835
  autoSelectedFiles,
836
+ totalPages,
830
837
  save,
831
838
  lastReport,
832
839
  onFinish,
@@ -998,6 +1005,7 @@ function MintTestApp({
998
1005
  runFiles: string[];
999
1006
  }) => {
1000
1007
  if (abortController.current) return;
1008
+ const resumed = runId !== null;
1001
1009
  const controller = new AbortController();
1002
1010
  abortController.current = controller;
1003
1011
  setTaskPhases(new Map());
@@ -1025,6 +1033,14 @@ function MintTestApp({
1025
1033
  );
1026
1034
  setScreen('running');
1027
1035
  setActiveRunStage('check');
1036
+ void trackMintTestRunStarted({
1037
+ mode: 'interactive',
1038
+ resumed,
1039
+ agents: runAgents,
1040
+ model: runModel,
1041
+ selectedPageCount: new Set(runFiles_).size,
1042
+ totalPageCount: totalPages,
1043
+ });
1028
1044
  void runCodeTests({
1029
1045
  path: targetPath,
1030
1046
  agents: runAgents,
@@ -1035,6 +1051,13 @@ function MintTestApp({
1035
1051
  concurrency: DEFAULT_CONCURRENCY,
1036
1052
  commandTimeoutMs: DEFAULT_COMMAND_TIMEOUT_MS,
1037
1053
  signal: controller.signal,
1054
+ onGenerationResult: (result) => {
1055
+ void trackMintTestGenerationResult({
1056
+ ...result,
1057
+ mode: 'interactive',
1058
+ resumed,
1059
+ });
1060
+ },
1038
1061
  onTaskUpdate: (update) => {
1039
1062
  const phaseStage =
1040
1063
  update.phase === 'running'
@@ -1060,6 +1083,11 @@ function MintTestApp({
1060
1083
  },
1061
1084
  })
1062
1085
  .then((nextReport) => {
1086
+ void trackMintTestRunCompleted(nextReport, {
1087
+ mode: 'interactive',
1088
+ resumed,
1089
+ totalPageCount: totalPages,
1090
+ });
1063
1091
  setReport(nextReport);
1064
1092
  const failedFiles = nextReport.tasks
1065
1093
  .filter((task) => task.status === 'failed' || task.status === 'agent_error')