@mintlify/cli 4.0.1110 → 4.0.1112
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/__test__/telemetry.test.ts +106 -1
- package/bin/middlewares/telemetryMiddleware.js +20 -0
- package/bin/score/index.js +6 -0
- package/bin/telemetry/track.js +11 -0
- package/bin/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/src/middlewares/telemetryMiddleware.ts +24 -0
- package/src/score/index.tsx +6 -0
- package/src/telemetry/track.ts +19 -0
|
@@ -10,7 +10,11 @@ import {
|
|
|
10
10
|
} from '../src/middlewares/telemetryMiddleware.js';
|
|
11
11
|
import { getDistinctId } from '../src/telemetry/distinctId.js';
|
|
12
12
|
import * as trackModule from '../src/telemetry/track.js';
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
trackCommand,
|
|
15
|
+
trackEvent,
|
|
16
|
+
trackTelemetryPreferenceChange,
|
|
17
|
+
} from '../src/telemetry/track.js';
|
|
14
18
|
|
|
15
19
|
vi.mock('fs-extra', () => ({ ensureDir: vi.fn().mockResolvedValue(undefined) }));
|
|
16
20
|
|
|
@@ -66,6 +70,41 @@ describe('getSanitizedCommandForTelemetry', () => {
|
|
|
66
70
|
expect(getSanitizedCommandForTelemetry(['rename', 'a.mdx', 'b.mdx'])).toBe('rename');
|
|
67
71
|
expect(getSanitizedCommandForTelemetry(['new', './my-docs'])).toBe('new');
|
|
68
72
|
});
|
|
73
|
+
|
|
74
|
+
it('includes known analytics subcommands', () => {
|
|
75
|
+
expect(getSanitizedCommandForTelemetry(['analytics', 'stats'])).toBe('analytics stats');
|
|
76
|
+
expect(getSanitizedCommandForTelemetry(['analytics', 'search'])).toBe('analytics search');
|
|
77
|
+
expect(getSanitizedCommandForTelemetry(['analytics', 'feedback'])).toBe('analytics feedback');
|
|
78
|
+
expect(getSanitizedCommandForTelemetry(['analytics', 'conversation'])).toBe(
|
|
79
|
+
'analytics conversation'
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('includes analytics conversation subcommands', () => {
|
|
84
|
+
expect(getSanitizedCommandForTelemetry(['analytics', 'conversation', 'list'])).toBe(
|
|
85
|
+
'analytics conversation list'
|
|
86
|
+
);
|
|
87
|
+
expect(getSanitizedCommandForTelemetry(['analytics', 'conversation', 'view', 'abc123'])).toBe(
|
|
88
|
+
'analytics conversation view'
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('includes analytics conversation buckets subcommands', () => {
|
|
93
|
+
expect(getSanitizedCommandForTelemetry(['analytics', 'conversation', 'buckets', 'list'])).toBe(
|
|
94
|
+
'analytics conversation buckets list'
|
|
95
|
+
);
|
|
96
|
+
expect(
|
|
97
|
+
getSanitizedCommandForTelemetry(['analytics', 'conversation', 'buckets', 'view', 'abc123'])
|
|
98
|
+
).toBe('analytics conversation buckets view');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('includes known config subcommands', () => {
|
|
102
|
+
expect(getSanitizedCommandForTelemetry(['config', 'set', 'subdomain', 'my-docs'])).toBe(
|
|
103
|
+
'config set'
|
|
104
|
+
);
|
|
105
|
+
expect(getSanitizedCommandForTelemetry(['config', 'get', 'subdomain'])).toBe('config get');
|
|
106
|
+
expect(getSanitizedCommandForTelemetry(['config', 'clear', 'subdomain'])).toBe('config clear');
|
|
107
|
+
});
|
|
69
108
|
});
|
|
70
109
|
|
|
71
110
|
describe('isTelemetryEnabled', () => {
|
|
@@ -260,6 +299,72 @@ describe('trackCommand', () => {
|
|
|
260
299
|
});
|
|
261
300
|
});
|
|
262
301
|
|
|
302
|
+
describe('trackEvent', () => {
|
|
303
|
+
const savedEnv = process.env;
|
|
304
|
+
|
|
305
|
+
beforeEach(() => {
|
|
306
|
+
vi.clearAllMocks();
|
|
307
|
+
mockCaptureImmediate.mockResolvedValue(undefined);
|
|
308
|
+
process.env = { ...savedEnv };
|
|
309
|
+
delete process.env.CLI_TEST_MODE;
|
|
310
|
+
delete process.env.MINTLIFY_TELEMETRY_DISABLED;
|
|
311
|
+
delete process.env.DO_NOT_TRACK;
|
|
312
|
+
vi.spyOn(fs, 'readFileSync').mockReturnValue(JSON.stringify({ telemetryEnabled: true }));
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
afterEach(() => {
|
|
316
|
+
process.env = savedEnv;
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('captures a custom event with standard metadata', async () => {
|
|
320
|
+
await trackEvent('cli.dev.started', { subdomain: 'test-docs', port: 3000 });
|
|
321
|
+
|
|
322
|
+
expect(mockCaptureImmediate).toHaveBeenCalledWith(
|
|
323
|
+
expect.objectContaining({
|
|
324
|
+
event: 'cli.dev.started',
|
|
325
|
+
properties: expect.objectContaining({
|
|
326
|
+
subdomain: 'test-docs',
|
|
327
|
+
port: 3000,
|
|
328
|
+
cli_version: expect.any(String),
|
|
329
|
+
os: os.platform(),
|
|
330
|
+
arch: os.arch(),
|
|
331
|
+
node_version: process.version,
|
|
332
|
+
}),
|
|
333
|
+
})
|
|
334
|
+
);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it('captures event without extra properties', async () => {
|
|
338
|
+
await trackEvent('cli.validate.executed');
|
|
339
|
+
|
|
340
|
+
expect(mockCaptureImmediate).toHaveBeenCalledWith(
|
|
341
|
+
expect.objectContaining({
|
|
342
|
+
event: 'cli.validate.executed',
|
|
343
|
+
properties: expect.objectContaining({
|
|
344
|
+
os: os.platform(),
|
|
345
|
+
}),
|
|
346
|
+
})
|
|
347
|
+
);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('does not capture when telemetry is disabled', async () => {
|
|
351
|
+
vi.spyOn(fs, 'readFileSync').mockReturnValue(JSON.stringify({ telemetryEnabled: false }));
|
|
352
|
+
await trackEvent('cli.dev.started', { subdomain: 'test-docs' });
|
|
353
|
+
expect(mockCaptureImmediate).not.toHaveBeenCalled();
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it('does not capture when MINTLIFY_TELEMETRY_DISABLED is set', async () => {
|
|
357
|
+
process.env.MINTLIFY_TELEMETRY_DISABLED = '1';
|
|
358
|
+
await trackEvent('cli.dev.started');
|
|
359
|
+
expect(mockCaptureImmediate).not.toHaveBeenCalled();
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it('does not throw when captureImmediate rejects', async () => {
|
|
363
|
+
mockCaptureImmediate.mockRejectedValue(new Error('network error'));
|
|
364
|
+
await expect(trackEvent('cli.dev.started')).resolves.toBeUndefined();
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
|
|
263
368
|
describe('trackTelemetryPreferenceChange', () => {
|
|
264
369
|
const savedEnv = process.env;
|
|
265
370
|
|
|
@@ -11,15 +11,35 @@ import { getConfigValue } from '../config.js';
|
|
|
11
11
|
import { getVersions } from '../helpers.js';
|
|
12
12
|
import { trackCommand } from '../telemetry/track.js';
|
|
13
13
|
const SCRAPE_SUBCOMMANDS = new Set(['page', 'site', 'openapi']);
|
|
14
|
+
const ANALYTICS_SUBCOMMANDS = new Set(['stats', 'search', 'feedback', 'conversation']);
|
|
15
|
+
const CONFIG_SUBCOMMANDS = new Set(['set', 'get', 'clear']);
|
|
16
|
+
const CONVERSATION_SUBCOMMANDS = new Set(['list', 'view', 'buckets']);
|
|
17
|
+
const BUCKETS_SUBCOMMANDS = new Set(['list', 'view']);
|
|
14
18
|
export function getSanitizedCommandForTelemetry(_) {
|
|
15
19
|
const parts = _.filter((p) => typeof p === 'string');
|
|
16
20
|
if (parts.length === 0)
|
|
17
21
|
return '';
|
|
18
22
|
const first = parts[0];
|
|
19
23
|
const second = parts[1];
|
|
24
|
+
const third = parts[2];
|
|
20
25
|
if (first === 'scrape' && second !== undefined && SCRAPE_SUBCOMMANDS.has(second)) {
|
|
21
26
|
return `scrape ${second}`;
|
|
22
27
|
}
|
|
28
|
+
if (first === 'config' && second !== undefined && CONFIG_SUBCOMMANDS.has(second)) {
|
|
29
|
+
return `config ${second}`;
|
|
30
|
+
}
|
|
31
|
+
if (first === 'analytics' && second !== undefined && ANALYTICS_SUBCOMMANDS.has(second)) {
|
|
32
|
+
if (second === 'conversation' && third !== undefined) {
|
|
33
|
+
if (CONVERSATION_SUBCOMMANDS.has(third)) {
|
|
34
|
+
const fourth = parts[3];
|
|
35
|
+
if (third === 'buckets' && fourth !== undefined && BUCKETS_SUBCOMMANDS.has(fourth)) {
|
|
36
|
+
return `analytics conversation buckets ${fourth}`;
|
|
37
|
+
}
|
|
38
|
+
return `analytics conversation ${third}`;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return `analytics ${second}`;
|
|
42
|
+
}
|
|
23
43
|
return first;
|
|
24
44
|
}
|
|
25
45
|
export function createTelemetryMiddleware() {
|
package/bin/score/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import { addLog, ErrorLog, SpinnerLog, removeLastLog } from '@mintlify/previewin
|
|
|
12
12
|
import chalk from 'chalk';
|
|
13
13
|
import { Text } from 'ink';
|
|
14
14
|
import { terminate } from '../helpers.js';
|
|
15
|
+
import { trackEvent } from '../telemetry/track.js';
|
|
15
16
|
import { getScore } from './client.js';
|
|
16
17
|
function resolveFormat(argv) {
|
|
17
18
|
if (argv.format === 'table' || argv.format === 'plain' || argv.format === 'json')
|
|
@@ -62,6 +63,11 @@ export const scoreHandler = (argv) => __awaiter(void 0, void 0, void 0, function
|
|
|
62
63
|
if (format === 'table')
|
|
63
64
|
addLog(_jsx(SpinnerLog, { message: "Running agent readiness checks..." }));
|
|
64
65
|
const data = yield getScore(argv.url);
|
|
66
|
+
void trackEvent('cli.score.executed', {
|
|
67
|
+
url: argv.url,
|
|
68
|
+
score: data.overallScore,
|
|
69
|
+
format,
|
|
70
|
+
});
|
|
65
71
|
if (format === 'table')
|
|
66
72
|
removeLastLog();
|
|
67
73
|
if (format === 'json') {
|
package/bin/telemetry/track.js
CHANGED
|
@@ -74,6 +74,17 @@ export function trackLoginFailed(reason) {
|
|
|
74
74
|
return trackLoginEvent('cli.login.failed', { reason });
|
|
75
75
|
});
|
|
76
76
|
}
|
|
77
|
+
export function trackEvent(event, properties) {
|
|
78
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
79
|
+
if (!isTelemetryEnabled())
|
|
80
|
+
return;
|
|
81
|
+
try {
|
|
82
|
+
const { cli: cliVersion } = getVersions();
|
|
83
|
+
yield captureWithTimeout(event, Object.assign(Object.assign({}, properties), { cli_version: cliVersion, os: os.platform(), arch: os.arch(), node_version: process.version, is_ai_agent: isAI() }));
|
|
84
|
+
}
|
|
85
|
+
catch (_a) { }
|
|
86
|
+
});
|
|
87
|
+
}
|
|
77
88
|
export function trackTelemetryPreferenceChange(options) {
|
|
78
89
|
return __awaiter(this, void 0, void 0, function* () {
|
|
79
90
|
if (process.env.CLI_TEST_MODE === 'true')
|