@mintlify/cli 4.0.1791 → 4.0.1792

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.
@@ -66,6 +66,10 @@ describe('mint deploy', () => {
66
66
  expect(vi.mocked(trackEvent).mock.calls).toEqual([
67
67
  ['agent_signup.discovered', { registration_id: 'reg_1' }],
68
68
  ['agent_signup.registered', { registration_id: 'reg_1' }],
69
+ [
70
+ 'cli.deploy.provisioned',
71
+ { registration_id: 'reg_1', deployment_id: 'id', subdomain: 'allocated', created: true },
72
+ ],
69
73
  ]);
70
74
  });
71
75
  it('tracks only discovery for a saved registration', async () => {
@@ -73,8 +77,22 @@ describe('mint deploy', () => {
73
77
  await prepareAgentDeployment(options, warn);
74
78
  expect(vi.mocked(trackEvent).mock.calls).toEqual([
75
79
  ['agent_signup.discovered', { registration_id: 'reg_1' }],
80
+ [
81
+ 'cli.deploy.provisioned',
82
+ { registration_id: 'reg_1', deployment_id: 'id', subdomain: 'allocated', created: true },
83
+ ],
76
84
  ]);
77
85
  });
86
+ it('tracks a provisioned site when write access fails', async () => {
87
+ vi.mocked(deployment.getAgentWriteToken).mockRejectedValue(new Error('timeout'));
88
+ await expect(prepareAgentDeployment(options, warn)).rejects.toThrow('timeout');
89
+ expect(vi.mocked(trackEvent)).toHaveBeenCalledWith('cli.deploy.provisioned', {
90
+ registration_id: 'reg_1',
91
+ deployment_id: 'id',
92
+ subdomain: 'allocated',
93
+ created: true,
94
+ });
95
+ });
78
96
  it.each([
79
97
  ['https://leaves.mintlify.com', 'https://allocated.mintlify.site/'],
80
98
  ['https://leaves-dev.mintlify.com', 'https://allocated.mint-dev.mintlify.me/'],
@@ -0,0 +1,46 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
5
+
6
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'mint-session-'));
7
+ vi.mock('../src/constants.js', () => ({ CONFIG_DIR: dir }));
8
+
9
+ const file = path.join(dir, 'telemetry-session.json');
10
+ const now = Date.parse('2026-11-02T12:00:00.000Z');
11
+ const minutes = (n: number) => n * 60 * 1000;
12
+ const load = async () => (await import('../src/telemetry/sessionId.js')).getSessionId;
13
+ const save = (lastSeenAt: number) =>
14
+ fs.writeFileSync(file, JSON.stringify({ id: 'saved', lastSeenAt }));
15
+
16
+ beforeEach(() => {
17
+ vi.resetModules();
18
+ fs.rmSync(file, { force: true });
19
+ });
20
+
21
+ describe('getSessionId', () => {
22
+ it('starts a UUIDv7 session and saves it', async () => {
23
+ const id = (await load())(now);
24
+ expect(id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/);
25
+ expect(parseInt(id.replace(/-/g, '').slice(0, 12), 16)).toBe(now);
26
+ expect(JSON.parse(fs.readFileSync(file, 'utf-8'))).toEqual({ id, lastSeenAt: now });
27
+ });
28
+
29
+ it('resumes a session active in the last 30 minutes', async () => {
30
+ save(now - minutes(29));
31
+ expect((await load())(now)).toBe('saved');
32
+ expect(JSON.parse(fs.readFileSync(file, 'utf-8')).lastSeenAt).toBe(now);
33
+ });
34
+
35
+ it('rotates after 30 minutes idle', async () => {
36
+ save(now - minutes(31));
37
+ expect((await load())(now)).not.toBe('saved');
38
+ });
39
+
40
+ it('keeps one id per process and refreshes activity', async () => {
41
+ const getSessionId = await load();
42
+ const id = getSessionId(now);
43
+ expect(getSessionId(now + minutes(60))).toBe(id);
44
+ expect(JSON.parse(fs.readFileSync(file, 'utf-8')).lastSeenAt).toBe(now + minutes(60));
45
+ });
46
+ });
package/bin/cli.js CHANGED
@@ -42,7 +42,7 @@ import { buildOverlayRegistryForCheck, checkOpenApiFile } from './openApiCheck.j
42
42
  import { scoreHandler } from './score/index.js';
43
43
  import { signup } from './signup.js';
44
44
  import { status, getCliSubdomains } from './status.js';
45
- import { trackTelemetryPreferenceChange } from './telemetry/track.js';
45
+ import { trackEvent, trackTelemetryPreferenceChange } from './telemetry/track.js';
46
46
  import { update } from './update.js';
47
47
  import { splitList, validateHandler } from './validate/command.js';
48
48
  import { RULES } from './validate/rules/index.js';
@@ -737,6 +737,11 @@ export const cli = ({ packageName = 'mint' }) => {
737
737
  }), (argv) => __awaiter(void 0, void 0, void 0, function* () {
738
738
  var _a;
739
739
  const agent = (_a = argv.agent) !== null && _a !== void 0 ? _a : (argv.claude ? 'claude' : undefined);
740
+ void trackEvent('cli.migrate.executed', {
741
+ from: argv.from,
742
+ topic: argv.topic,
743
+ agent,
744
+ });
740
745
  if (agent) {
741
746
  try {
742
747
  return yield terminate(yield migrateWithAgent(agent, argv.from));
package/bin/deploy.js CHANGED
@@ -57,6 +57,12 @@ export function prepareAgentDeployment(options_1, warn_1) {
57
57
  saved = null;
58
58
  continue;
59
59
  }
60
+ void trackEvent('cli.deploy.provisioned', {
61
+ registration_id: credentials.registrationId,
62
+ deployment_id: site.deploymentId,
63
+ subdomain: site.subdomain,
64
+ created: site.created,
65
+ });
60
66
  yield setAgentSiteMetadata(metadataKey, {
61
67
  registrationId: credentials.registrationId,
62
68
  subdomain: site.subdomain,
@@ -0,0 +1,36 @@
1
+ import crypto from 'crypto';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { CONFIG_DIR } from '../constants.js';
5
+ const SESSION_FILE = path.join(CONFIG_DIR, 'telemetry-session.json');
6
+ const SESSION_IDLE_MS = 30 * 60 * 1000;
7
+ let current;
8
+ function uuidv7(now) {
9
+ const bytes = crypto.randomBytes(16);
10
+ bytes.writeUIntBE(now, 0, 6);
11
+ bytes[6] = (bytes[6] & 0x0f) | 0x70;
12
+ bytes[8] = (bytes[8] & 0x3f) | 0x80;
13
+ const hex = bytes.toString('hex');
14
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
15
+ }
16
+ function readSession() {
17
+ try {
18
+ const { id, lastSeenAt } = JSON.parse(fs.readFileSync(SESSION_FILE, 'utf-8'));
19
+ return typeof id === 'string' && typeof lastSeenAt === 'number' ? { id, lastSeenAt } : null;
20
+ }
21
+ catch (_a) {
22
+ return null;
23
+ }
24
+ }
25
+ export function getSessionId(now = Date.now()) {
26
+ if (!current) {
27
+ const saved = readSession();
28
+ current = saved && now - saved.lastSeenAt < SESSION_IDLE_MS ? saved.id : uuidv7(now);
29
+ }
30
+ try {
31
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
32
+ fs.writeFileSync(SESSION_FILE, JSON.stringify({ id: current, lastSeenAt: now }));
33
+ }
34
+ catch (_a) { }
35
+ return current;
36
+ }
@@ -16,6 +16,7 @@ import { getAccessToken, getUserEmail, storeUserEmail } from '../keyring.js';
16
16
  import { fetchCliStatus } from '../status.js';
17
17
  import { deferShutdownUntil, getPostHogClient } from './client.js';
18
18
  import { getDistinctId, isFirstRun } from './distinctId.js';
19
+ import { getSessionId } from './sessionId.js';
19
20
  function getEnvironmentProperties() {
20
21
  const agent = detectAgent();
21
22
  return {
@@ -27,6 +28,7 @@ function getEnvironmentProperties() {
27
28
  is_ci: isCI(),
28
29
  is_tty: Boolean(process.stdin.isTTY),
29
30
  is_first_run: isFirstRun(),
31
+ $session_id: getSessionId(),
30
32
  };
31
33
  }
32
34
  function captureWithTimeout(event, properties) {
@@ -69,14 +71,19 @@ function getIdentity() {
69
71
  }
70
72
  });
71
73
  }
74
+ function getIdentityProperties() {
75
+ return __awaiter(this, void 0, void 0, function* () {
76
+ const { loggedIn, email } = yield getIdentity();
77
+ return Object.assign({ is_logged_in: loggedIn }, (email ? { email, $set: { email } } : {}));
78
+ });
79
+ }
72
80
  export function trackCommand(_a) {
73
81
  return __awaiter(this, arguments, void 0, function* ({ command, cliVersion, subdomain, }) {
74
82
  if (!isTelemetryEnabled())
75
83
  return;
76
84
  const work = (() => __awaiter(this, void 0, void 0, function* () {
77
85
  try {
78
- const { loggedIn, email } = yield getIdentity();
79
- yield captureWithTimeout('cli.command.executed', Object.assign(Object.assign(Object.assign({ command, cli_version: cliVersion }, getEnvironmentProperties()), { is_logged_in: loggedIn, subdomain }), (email ? { email, $set: { email } } : {})));
86
+ yield captureWithTimeout('cli.command.executed', Object.assign(Object.assign(Object.assign({ command, cli_version: cliVersion }, getEnvironmentProperties()), (yield getIdentityProperties())), { subdomain }));
80
87
  }
81
88
  catch (_a) { }
82
89
  }))();
@@ -88,11 +95,15 @@ function trackLoginEvent(event, extra) {
88
95
  return __awaiter(this, void 0, void 0, function* () {
89
96
  if (!isTelemetryEnabled())
90
97
  return;
91
- try {
92
- const { cli: cliVersion } = getVersions();
93
- yield captureWithTimeout(event, Object.assign(Object.assign(Object.assign({}, extra), { cli_version: cliVersion }), getEnvironmentProperties()));
94
- }
95
- catch (_a) { }
98
+ const work = (() => __awaiter(this, void 0, void 0, function* () {
99
+ try {
100
+ const { cli: cliVersion } = getVersions();
101
+ yield captureWithTimeout(event, Object.assign(Object.assign(Object.assign(Object.assign({}, extra), { cli_version: cliVersion }), getEnvironmentProperties()), (yield getIdentityProperties())));
102
+ }
103
+ catch (_a) { }
104
+ }))();
105
+ deferShutdownUntil(work);
106
+ yield work;
96
107
  });
97
108
  }
98
109
  export function trackLoginAttempt() {
@@ -117,7 +128,7 @@ export function trackEvent(event, properties) {
117
128
  const work = (() => __awaiter(this, void 0, void 0, function* () {
118
129
  try {
119
130
  const { cli: cliVersion } = getVersions();
120
- yield captureWithTimeout(event, Object.assign(Object.assign(Object.assign({}, properties), { cli_version: cliVersion }), getEnvironmentProperties()));
131
+ yield captureWithTimeout(event, Object.assign(Object.assign(Object.assign(Object.assign({}, properties), { cli_version: cliVersion }), getEnvironmentProperties()), (yield getIdentityProperties())));
121
132
  }
122
133
  catch (_a) { }
123
134
  }))();