@mintlify/cli 4.0.1067 → 4.0.1069

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.
@@ -0,0 +1,313 @@
1
+ import fs from 'fs';
2
+ import * as fsExtra from 'fs-extra';
3
+ import os from 'os';
4
+
5
+ import { isTelemetryEnabled, setTelemetryEnabled } from '../src/config.js';
6
+ import { TELEMETRY_ASYNC_TIMEOUT_MS } from '../src/constants.js';
7
+ import { getDistinctId } from '../src/telemetry/distinctId.js';
8
+ import {
9
+ createTelemetryMiddleware,
10
+ getSanitizedCommandForTelemetry,
11
+ } from '../src/telemetry/middleware.js';
12
+ import * as trackModule from '../src/telemetry/track.js';
13
+ import { trackCommand, trackTelemetryPreferenceChange } from '../src/telemetry/track.js';
14
+
15
+ vi.mock('fs-extra', () => ({ ensureDir: vi.fn().mockResolvedValue(undefined) }));
16
+
17
+ const mockCaptureImmediate = vi.fn().mockResolvedValue(undefined);
18
+
19
+ vi.mock('../src/telemetry/client.js', () => ({
20
+ getPostHogClient: () => ({ captureImmediate: mockCaptureImmediate }),
21
+ shutdownPostHog: vi.fn(),
22
+ }));
23
+
24
+ afterEach(() => {
25
+ vi.restoreAllMocks();
26
+ });
27
+
28
+ describe('createTelemetryMiddleware', () => {
29
+ beforeEach(() => {
30
+ vi.spyOn(trackModule, 'trackCommand').mockResolvedValue(undefined);
31
+ });
32
+
33
+ it('only tracks once when middleware runs multiple times for the same parse', async () => {
34
+ const middleware = createTelemetryMiddleware();
35
+ await middleware({ _: ['dev'] });
36
+ await middleware({ _: ['dev'] });
37
+ expect(trackModule.trackCommand).toHaveBeenCalledTimes(1);
38
+ });
39
+
40
+ it('tracks again for a new middleware instance (new cli invocation)', async () => {
41
+ await createTelemetryMiddleware()({ _: ['dev'] });
42
+ expect(trackModule.trackCommand).toHaveBeenCalledTimes(1);
43
+ await createTelemetryMiddleware()({ _: ['build'] });
44
+ expect(trackModule.trackCommand).toHaveBeenCalledTimes(2);
45
+ });
46
+ });
47
+
48
+ describe('getSanitizedCommandForTelemetry', () => {
49
+ it('includes known scrape subcommands', () => {
50
+ expect(getSanitizedCommandForTelemetry(['scrape', 'page', 'https://example.com'])).toBe(
51
+ 'scrape page'
52
+ );
53
+ expect(getSanitizedCommandForTelemetry(['scrape', 'site', 'https://example.com'])).toBe(
54
+ 'scrape site'
55
+ );
56
+ expect(getSanitizedCommandForTelemetry(['scrape', 'openapi', './spec.yaml'])).toBe(
57
+ 'scrape openapi'
58
+ );
59
+ });
60
+
61
+ it('strips positional args from all other commands', () => {
62
+ expect(getSanitizedCommandForTelemetry(['scrape', 'https://example.com'])).toBe('scrape');
63
+ expect(getSanitizedCommandForTelemetry(['openapi-check', 'https://swagger.io/spec.json'])).toBe(
64
+ 'openapi-check'
65
+ );
66
+ expect(getSanitizedCommandForTelemetry(['rename', 'a.mdx', 'b.mdx'])).toBe('rename');
67
+ expect(getSanitizedCommandForTelemetry(['new', './my-docs'])).toBe('new');
68
+ });
69
+ });
70
+
71
+ describe('isTelemetryEnabled', () => {
72
+ const savedEnv = process.env;
73
+
74
+ beforeEach(() => {
75
+ process.env = { ...savedEnv };
76
+ delete process.env.MINTLIFY_TELEMETRY_DISABLED;
77
+ delete process.env.DO_NOT_TRACK;
78
+ delete process.env.CLI_TEST_MODE;
79
+ });
80
+
81
+ afterEach(() => {
82
+ process.env = savedEnv;
83
+ });
84
+
85
+ it('is enabled by default when config file does not exist', () => {
86
+ vi.spyOn(fs, 'readFileSync').mockImplementation(() => {
87
+ throw Object.assign(new Error('ENOENT'), { code: 'ENOENT' });
88
+ });
89
+ expect(isTelemetryEnabled()).toBe(true);
90
+ });
91
+
92
+ it('is disabled when config file has telemetryEnabled: false', () => {
93
+ vi.spyOn(fs, 'readFileSync').mockReturnValue(JSON.stringify({ telemetryEnabled: false }));
94
+ expect(isTelemetryEnabled()).toBe(false);
95
+ });
96
+
97
+ it('is enabled when config file has telemetryEnabled: true', () => {
98
+ vi.spyOn(fs, 'readFileSync').mockReturnValue(JSON.stringify({ telemetryEnabled: true }));
99
+ expect(isTelemetryEnabled()).toBe(true);
100
+ });
101
+
102
+ it('is disabled via CLI_TEST_MODE=true', () => {
103
+ process.env.CLI_TEST_MODE = 'true';
104
+ expect(isTelemetryEnabled()).toBe(false);
105
+ });
106
+
107
+ it('is disabled via MINTLIFY_TELEMETRY_DISABLED=1', () => {
108
+ process.env.MINTLIFY_TELEMETRY_DISABLED = '1';
109
+ expect(isTelemetryEnabled()).toBe(false);
110
+ });
111
+
112
+ it('is disabled via DO_NOT_TRACK=1', () => {
113
+ process.env.DO_NOT_TRACK = '1';
114
+ expect(isTelemetryEnabled()).toBe(false);
115
+ });
116
+ });
117
+
118
+ describe('setTelemetryEnabled', () => {
119
+ beforeEach(() => {
120
+ vi.mocked(fsExtra.ensureDir).mockResolvedValue(undefined);
121
+ });
122
+
123
+ it('writes telemetryEnabled into config.json', async () => {
124
+ vi.spyOn(fs, 'readFileSync').mockImplementation(() => {
125
+ throw Object.assign(new Error('ENOENT'), { code: 'ENOENT' });
126
+ });
127
+ const writeSpy = vi.spyOn(fs, 'writeFileSync').mockImplementation(() => undefined);
128
+
129
+ await setTelemetryEnabled(false);
130
+
131
+ expect(fsExtra.ensureDir).toHaveBeenCalledWith(expect.stringContaining('mintlify'));
132
+ expect(writeSpy).toHaveBeenCalledWith(
133
+ expect.stringContaining('config.json'),
134
+ JSON.stringify({ telemetryEnabled: false }, null, 2)
135
+ );
136
+ });
137
+
138
+ it('merges with existing config fields', async () => {
139
+ vi.spyOn(fs, 'readFileSync').mockReturnValue(JSON.stringify({ someOtherSetting: true }));
140
+ const writeSpy = vi.spyOn(fs, 'writeFileSync').mockImplementation(() => undefined);
141
+
142
+ await setTelemetryEnabled(false);
143
+
144
+ expect(writeSpy).toHaveBeenCalledWith(
145
+ expect.stringContaining('config.json'),
146
+ JSON.stringify({ someOtherSetting: true, telemetryEnabled: false }, null, 2)
147
+ );
148
+ });
149
+ });
150
+
151
+ describe('getDistinctId', () => {
152
+ it('returns the persisted UUID', () => {
153
+ const id = '550e8400-e29b-41d4-a716-446655440000';
154
+ vi.spyOn(fs, 'readFileSync').mockReturnValue(id);
155
+ expect(getDistinctId()).toBe(id);
156
+ });
157
+
158
+ it('generates and persists a new UUID when none exists', () => {
159
+ vi.spyOn(fs, 'readFileSync').mockImplementation(() => {
160
+ throw new Error('ENOENT');
161
+ });
162
+ vi.spyOn(fs, 'mkdirSync').mockImplementation(() => undefined);
163
+ const writeSpy = vi.spyOn(fs, 'writeFileSync').mockImplementation(() => undefined);
164
+
165
+ const id = getDistinctId();
166
+ expect(id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);
167
+ expect(writeSpy).toHaveBeenCalledWith(expect.stringContaining('anonymous-id'), id, {
168
+ flag: 'wx',
169
+ });
170
+ });
171
+
172
+ it('overwrites anonymous-id when it exists but is not a valid UUID', () => {
173
+ vi.spyOn(fs, 'readFileSync').mockReturnValue('not-a-uuid');
174
+ vi.spyOn(fs, 'mkdirSync').mockImplementation(() => undefined);
175
+ const writeSpy = vi.spyOn(fs, 'writeFileSync').mockImplementation((...args) => {
176
+ const opts = args[2] as { flag?: string } | undefined;
177
+ if (opts?.flag === 'wx') {
178
+ throw Object.assign(new Error('EEXIST'), { code: 'EEXIST' });
179
+ }
180
+ });
181
+
182
+ const id = getDistinctId();
183
+ expect(id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);
184
+ expect(writeSpy).toHaveBeenCalledWith(expect.stringContaining('anonymous-id'), id, {
185
+ flag: 'wx',
186
+ });
187
+ expect(writeSpy).toHaveBeenCalledWith(expect.stringContaining('anonymous-id'), id);
188
+ });
189
+
190
+ it('reuses the same id when the file cannot be persisted (stable machine fallback)', () => {
191
+ vi.spyOn(fs, 'readFileSync').mockImplementation(() => {
192
+ throw new Error('ENOENT');
193
+ });
194
+ vi.spyOn(fs, 'mkdirSync').mockImplementation(() => undefined);
195
+ vi.spyOn(fs, 'writeFileSync').mockImplementation(() => {
196
+ throw new Error('EACCES');
197
+ });
198
+
199
+ const a = getDistinctId();
200
+ const b = getDistinctId();
201
+ expect(a).toBe(b);
202
+ expect(a).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);
203
+ });
204
+ });
205
+
206
+ describe('trackCommand', () => {
207
+ const savedEnv = process.env;
208
+
209
+ beforeEach(() => {
210
+ vi.clearAllMocks();
211
+ mockCaptureImmediate.mockResolvedValue(undefined);
212
+ process.env = { ...savedEnv };
213
+ delete process.env.CLI_TEST_MODE;
214
+ delete process.env.MINTLIFY_TELEMETRY_DISABLED;
215
+ delete process.env.DO_NOT_TRACK;
216
+ vi.spyOn(fs, 'readFileSync').mockReturnValue(JSON.stringify({ telemetryEnabled: true }));
217
+ });
218
+
219
+ afterEach(() => {
220
+ process.env = savedEnv;
221
+ });
222
+
223
+ it('captures event when telemetry is enabled', async () => {
224
+ await trackCommand({ command: 'dev', cliVersion: '1.0.0' });
225
+
226
+ expect(mockCaptureImmediate).toHaveBeenCalledWith(
227
+ expect.objectContaining({
228
+ event: 'cli.command.executed',
229
+ properties: expect.objectContaining({
230
+ command: 'dev',
231
+ cli_version: '1.0.0',
232
+ os: os.platform(),
233
+ }),
234
+ })
235
+ );
236
+ });
237
+
238
+ it('does not capture when config has telemetryEnabled: false', async () => {
239
+ vi.spyOn(fs, 'readFileSync').mockReturnValue(JSON.stringify({ telemetryEnabled: false }));
240
+ await trackCommand({ command: 'dev' });
241
+ expect(mockCaptureImmediate).not.toHaveBeenCalled();
242
+ });
243
+
244
+ it('does not capture when MINTLIFY_TELEMETRY_DISABLED is set', async () => {
245
+ process.env.MINTLIFY_TELEMETRY_DISABLED = '1';
246
+ await trackCommand({ command: 'dev' });
247
+ expect(mockCaptureImmediate).not.toHaveBeenCalled();
248
+ });
249
+
250
+ it('resolves when captureImmediate hangs after timeout', async () => {
251
+ mockCaptureImmediate.mockImplementation(() => new Promise(() => {}));
252
+ vi.useFakeTimers();
253
+ try {
254
+ const done = trackCommand({ command: 'dev' });
255
+ await vi.advanceTimersByTimeAsync(TELEMETRY_ASYNC_TIMEOUT_MS);
256
+ await expect(done).resolves.toBeUndefined();
257
+ } finally {
258
+ vi.useRealTimers();
259
+ }
260
+ });
261
+ });
262
+
263
+ describe('trackTelemetryPreferenceChange', () => {
264
+ const savedEnv = process.env;
265
+
266
+ beforeEach(() => {
267
+ vi.clearAllMocks();
268
+ mockCaptureImmediate.mockResolvedValue(undefined);
269
+ process.env = { ...savedEnv };
270
+ delete process.env.CLI_TEST_MODE;
271
+ delete process.env.MINTLIFY_TELEMETRY_DISABLED;
272
+ });
273
+
274
+ afterEach(() => {
275
+ process.env = savedEnv;
276
+ });
277
+
278
+ it('captures cli.telemetry.preference_changed when enabling', async () => {
279
+ await trackTelemetryPreferenceChange({ enabled: true });
280
+
281
+ expect(mockCaptureImmediate).toHaveBeenCalledWith(
282
+ expect.objectContaining({
283
+ event: 'cli.telemetry.preference_changed',
284
+ properties: expect.objectContaining({ enabled: true, os: os.platform() }),
285
+ })
286
+ );
287
+ });
288
+
289
+ it('captures cli.telemetry.preference_changed when disabling', async () => {
290
+ await trackTelemetryPreferenceChange({ enabled: false });
291
+
292
+ expect(mockCaptureImmediate).toHaveBeenCalledWith(
293
+ expect.objectContaining({
294
+ event: 'cli.telemetry.preference_changed',
295
+ properties: expect.objectContaining({ enabled: false, os: os.platform() }),
296
+ })
297
+ );
298
+ });
299
+
300
+ it('fires even when MINTLIFY_TELEMETRY_DISABLED is set (captures opt-out)', async () => {
301
+ process.env.MINTLIFY_TELEMETRY_DISABLED = '1';
302
+ await trackTelemetryPreferenceChange({ enabled: false });
303
+ expect(mockCaptureImmediate).toHaveBeenCalledWith(
304
+ expect.objectContaining({ event: 'cli.telemetry.preference_changed' })
305
+ );
306
+ });
307
+
308
+ it('does not capture when CLI_TEST_MODE is set', async () => {
309
+ process.env.CLI_TEST_MODE = 'true';
310
+ await trackTelemetryPreferenceChange({ enabled: true });
311
+ expect(mockCaptureImmediate).not.toHaveBeenCalled();
312
+ });
313
+ });
package/__test__/utils.ts CHANGED
@@ -1,13 +1,18 @@
1
1
  import { cli } from '../src/cli.js';
2
2
 
3
- /**
4
- * Programmatically set arguments and execute the CLI script
5
- *
6
- * @param {...string} args - Additional command arguments.
7
- */
8
3
  export async function runCommand(...args: string[]) {
4
+ const prevCliTestMode = process.env.CLI_TEST_MODE;
5
+ process.env.CLI_TEST_MODE = 'true';
9
6
  process.argv = ['node', 'cli.js', ...args];
10
- return cli({ packageName: 'mint' });
7
+ try {
8
+ return await cli({ packageName: 'mint' });
9
+ } finally {
10
+ if (prevCliTestMode === undefined) {
11
+ delete process.env.CLI_TEST_MODE;
12
+ } else {
13
+ process.env.CLI_TEST_MODE = prevCliTestMode;
14
+ }
15
+ }
11
16
  }
12
17
 
13
18
  export const mockValidOpenApiDocument = {
package/bin/cli.js CHANGED
@@ -17,19 +17,37 @@ import path from 'path';
17
17
  import yargs from 'yargs';
18
18
  import { hideBin } from 'yargs/helpers';
19
19
  import { accessibilityCheck } from './accessibilityCheck.js';
20
- import { checkPort, checkForMintJson, checkNodeVersion, upgradeConfig, checkForDocsJson, getVersions, suppressConsoleWarnings, terminate, readLocalOpenApiFile, } from './helpers.js';
20
+ import { setTelemetryEnabled } from './config.js';
21
+ import { checkPort, checkNodeVersion, autoUpgradeIfNeeded, getVersions, suppressConsoleWarnings, terminate, readLocalOpenApiFile, } from './helpers.js';
21
22
  import { init } from './init.js';
22
23
  import { mdxLinter } from './mdxLinter.js';
23
24
  import { migrateMdx } from './migrateMdx.js';
24
25
  import { scrapeSite, scrapePage, scrapeOpenApi } from './scrape.js';
26
+ import { createTelemetryMiddleware } from './telemetry/middleware.js';
27
+ import { trackTelemetryPreferenceChange } from './telemetry/track.js';
25
28
  import { update } from './update.js';
26
29
  import { addWorkflow } from './workflow.js';
27
30
  export const cli = ({ packageName = 'mint' }) => {
31
+ const telemetryMiddleware = createTelemetryMiddleware();
28
32
  render(_jsx(Logs, {}));
29
33
  return (yargs(hideBin(process.argv))
30
34
  .scriptName(packageName)
35
+ .option('telemetry', {
36
+ type: 'boolean',
37
+ alias: 't',
38
+ description: 'Enable or disable anonymous usage telemetry',
39
+ })
40
+ .middleware((argv) => __awaiter(void 0, void 0, void 0, function* () {
41
+ if (argv.telemetry !== undefined && argv._.length === 0) {
42
+ yield setTelemetryEnabled(argv.telemetry);
43
+ yield trackTelemetryPreferenceChange({ enabled: argv.telemetry });
44
+ addLog(_jsx(SuccessLog, { message: `telemetry ${argv.telemetry ? 'enabled' : 'disabled'}` }));
45
+ yield terminate(0);
46
+ }
47
+ }), true)
31
48
  .middleware(checkNodeVersion)
32
49
  .middleware(suppressConsoleWarnings)
50
+ .middleware(telemetryMiddleware)
33
51
  .command('dev', 'initialize a local preview environment', (yargs) => yargs
34
52
  .option('open', {
35
53
  type: 'boolean',
@@ -65,6 +83,7 @@ export const cli = ({ packageName = 'mint' }) => {
65
83
  .usage('usage: mintlify dev [options]')
66
84
  .example('mintlify dev', 'run with default settings (opens in browser)')
67
85
  .example('mintlify dev --no-open', 'run without opening in browser'), (argv) => __awaiter(void 0, void 0, void 0, function* () {
86
+ yield autoUpgradeIfNeeded();
68
87
  const port = yield checkPort(argv);
69
88
  const { cli: cliVersion } = getVersions();
70
89
  if (port != undefined) {
@@ -187,10 +206,7 @@ export const cli = ({ packageName = 'mint' }) => {
187
206
  default: false,
188
207
  description: 'also check links inside <Snippet> components',
189
208
  }), (argv) => __awaiter(void 0, void 0, void 0, function* () {
190
- const hasMintJson = yield checkForMintJson();
191
- if (!hasMintJson) {
192
- yield checkForDocsJson();
193
- }
209
+ yield autoUpgradeIfNeeded();
194
210
  addLog(_jsx(SpinnerLog, { message: "checking for broken links..." }));
195
211
  try {
196
212
  const graph = yield buildGraph(undefined, {
@@ -258,23 +274,13 @@ export const cli = ({ packageName = 'mint' }) => {
258
274
  description: 'rename files and skip errors',
259
275
  })
260
276
  .epilog('example: `mintlify rename introduction.mdx overview.mdx`'), (_a) => __awaiter(void 0, [_a], void 0, function* ({ from, to, force }) {
261
- const hasMintJson = yield checkForMintJson();
262
- if (!hasMintJson) {
263
- yield checkForDocsJson();
264
- }
277
+ yield autoUpgradeIfNeeded();
265
278
  yield renameFilesAndUpdateLinksInContent(from, to, force);
266
279
  yield terminate(0);
267
280
  }))
268
281
  .command('update', 'update the CLI to the latest version', () => undefined, () => __awaiter(void 0, void 0, void 0, function* () {
269
282
  yield update({ packageName });
270
283
  yield terminate(0);
271
- }))
272
- .command('upgrade', 'upgrade mint.json file to docs.json (current format)', () => undefined, () => __awaiter(void 0, void 0, void 0, function* () {
273
- const hasMintJson = yield checkForMintJson();
274
- if (!hasMintJson) {
275
- yield checkForDocsJson();
276
- }
277
- yield upgradeConfig();
278
284
  }))
279
285
  .command('migrate-mdx', 'migrate mdx openapi endpoint pages to x-mint extensions and docs.json', () => undefined, () => __awaiter(void 0, void 0, void 0, function* () {
280
286
  yield migrateMdx();
@@ -412,5 +418,5 @@ export const cli = ({ packageName = 'mint' }) => {
412
418
  // Alias option flags --help = -h, default --version = -v
413
419
  .alias('h', 'help')
414
420
  .alias('v', 'version')
415
- .parse());
421
+ .parseAsync());
416
422
  };
package/bin/config.js ADDED
@@ -0,0 +1,42 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import fs from 'fs';
11
+ import { ensureDir } from 'fs-extra';
12
+ import { CLI_CONFIG_FILE, CONFIG_DIR } from './constants.js';
13
+ function readConfig() {
14
+ try {
15
+ const raw = fs.readFileSync(CLI_CONFIG_FILE, 'utf-8');
16
+ return JSON.parse(raw);
17
+ }
18
+ catch (_a) {
19
+ return {};
20
+ }
21
+ }
22
+ function writeConfig(updates) {
23
+ return __awaiter(this, void 0, void 0, function* () {
24
+ yield ensureDir(CONFIG_DIR);
25
+ const existing = readConfig();
26
+ fs.writeFileSync(CLI_CONFIG_FILE, JSON.stringify(Object.assign(Object.assign({}, existing), updates), null, 2));
27
+ });
28
+ }
29
+ export function isTelemetryEnabled() {
30
+ if (process.env.CLI_TEST_MODE === 'true')
31
+ return false;
32
+ if (process.env.MINTLIFY_TELEMETRY_DISABLED === '1')
33
+ return false;
34
+ if (process.env.DO_NOT_TRACK === '1')
35
+ return false;
36
+ return readConfig().telemetryEnabled !== false;
37
+ }
38
+ export function setTelemetryEnabled(enabled) {
39
+ return __awaiter(this, void 0, void 0, function* () {
40
+ yield writeConfig({ telemetryEnabled: enabled });
41
+ });
42
+ }
package/bin/constants.js CHANGED
@@ -1,3 +1,7 @@
1
1
  import os from 'os';
2
+ import path from 'path';
2
3
  export const HOME_DIR = os.homedir();
3
4
  export const CMD_EXEC_PATH = process.cwd();
5
+ export const CONFIG_DIR = path.join(HOME_DIR, '.config', 'mintlify');
6
+ export const CLI_CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
7
+ export const TELEMETRY_ASYNC_TIMEOUT_MS = 10000;
package/bin/helpers.js CHANGED
@@ -10,7 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  import { jsx as _jsx } from "react/jsx-runtime";
11
11
  import { getConfigPath } from '@mintlify/prebuild';
12
12
  import { MintConfigUpdater } from '@mintlify/prebuild';
13
- import { addLog, ErrorLog, getClientVersion, SuccessLog, InfoLog, SpinnerLog, removeLastLog, LOCAL_LINKED_CLI_VERSION, } from '@mintlify/previewing';
13
+ import { addLog, ErrorLog, getClientVersion, SuccessLog, InfoLog, SpinnerLog, removeLastLog, LOCAL_LINKED_CLI_VERSION, WarningLog, } from '@mintlify/previewing';
14
14
  import { upgradeToDocsConfig, validatePathWithinCwd } from '@mintlify/validation';
15
15
  import detect from 'detect-port';
16
16
  import fse from 'fs-extra';
@@ -22,6 +22,7 @@ import { promisify } from 'node:util';
22
22
  import path from 'path';
23
23
  import yargs from 'yargs';
24
24
  import { CMD_EXEC_PATH } from './constants.js';
25
+ import { shutdownPostHog } from './telemetry/client.js';
25
26
  export const checkPort = (argv) => __awaiter(void 0, void 0, void 0, function* () {
26
27
  const initialPort = typeof argv.port === 'number' ? argv.port : 3000;
27
28
  if (initialPort === (yield detect(initialPort)))
@@ -77,6 +78,18 @@ export const checkForDocsJson = () => __awaiter(void 0, void 0, void 0, function
77
78
  }
78
79
  }
79
80
  });
81
+ export const autoUpgradeIfNeeded = () => __awaiter(void 0, void 0, void 0, function* () {
82
+ const hasMintJson = yield checkForMintJson();
83
+ if (!hasMintJson)
84
+ return;
85
+ const docsJsonPath = path.join(CMD_EXEC_PATH, 'docs.json');
86
+ const hasDocsJson = yield fse.pathExists(docsJsonPath);
87
+ if (!hasDocsJson) {
88
+ addLog(_jsx(WarningLog, { message: "Legacy mint.json detected, auto-upgrading to docs.json" }));
89
+ addLog(_jsx(SpinnerLog, { message: "upgrading mint.json to docs.json..." }));
90
+ yield upgradeConfig();
91
+ }
92
+ });
80
93
  export const upgradeConfig = () => __awaiter(void 0, void 0, void 0, function* () {
81
94
  try {
82
95
  const mintJsonPath = path.join(CMD_EXEC_PATH, 'mint.json');
@@ -157,6 +170,7 @@ export const readLocalOpenApiFile = (filename) => __awaiter(void 0, void 0, void
157
170
  export const terminate = (code) => __awaiter(void 0, void 0, void 0, function* () {
158
171
  // Wait for the logs to be fully rendered before exiting
159
172
  yield new Promise((resolve) => setTimeout(resolve, 50));
173
+ yield shutdownPostHog();
160
174
  process.exit(code);
161
175
  });
162
176
  export const execAsync = promisify(exec);
package/bin/start.js CHANGED
@@ -1,5 +1,36 @@
1
1
  #!/usr/bin/env node
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
2
11
  var _a;
3
12
  import { cli } from './cli.js';
13
+ import { shutdownPostHog } from './telemetry/client.js';
4
14
  const packageName = (_a = process.env.MINTLIFY_PACKAGE_NAME) !== null && _a !== void 0 ? _a : 'mint';
5
- void cli({ packageName });
15
+ cli({ packageName }).catch((err) => {
16
+ console.error(err instanceof Error ? err.message : err);
17
+ process.exit(1);
18
+ });
19
+ function shutdown(exitCode) {
20
+ return __awaiter(this, void 0, void 0, function* () {
21
+ yield shutdownPostHog();
22
+ process.exit(exitCode);
23
+ });
24
+ }
25
+ process.once('beforeExit', () => __awaiter(void 0, void 0, void 0, function* () {
26
+ try {
27
+ yield shutdownPostHog();
28
+ }
29
+ catch (_a) { }
30
+ }));
31
+ process.on('SIGINT', () => {
32
+ void shutdown(130);
33
+ });
34
+ process.on('SIGTERM', () => {
35
+ void shutdown(143);
36
+ });
@@ -0,0 +1,39 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { PostHog } from 'posthog-node';
11
+ import { TELEMETRY_ASYNC_TIMEOUT_MS } from '../constants.js';
12
+ const POSTHOG_API_KEY = 'phc_eNuN6Ojnk9O7uWfC17z12AK85fNR0BY6IiGVy0Gfwzw';
13
+ const POSTHOG_HOST = 'https://ph.mintlify.com';
14
+ let client = null;
15
+ export function getPostHogClient() {
16
+ if (!client) {
17
+ client = new PostHog(POSTHOG_API_KEY, {
18
+ host: POSTHOG_HOST,
19
+ });
20
+ }
21
+ return client;
22
+ }
23
+ export function shutdownPostHog() {
24
+ return __awaiter(this, void 0, void 0, function* () {
25
+ if (!client)
26
+ return;
27
+ try {
28
+ yield Promise.race([
29
+ client.shutdown(),
30
+ new Promise((resolve) => {
31
+ const t = setTimeout(resolve, TELEMETRY_ASYNC_TIMEOUT_MS);
32
+ t.unref();
33
+ }),
34
+ ]);
35
+ }
36
+ catch (_a) { }
37
+ client = null;
38
+ });
39
+ }
@@ -0,0 +1,62 @@
1
+ import crypto from 'crypto';
2
+ import fs from 'fs';
3
+ import os from 'os';
4
+ import path from 'path';
5
+ import { CONFIG_DIR } from '../constants.js';
6
+ const ID_FILE = path.join(CONFIG_DIR, 'anonymous-id');
7
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
8
+ function readValidIdFromFile() {
9
+ try {
10
+ const raw = fs.readFileSync(ID_FILE, 'utf-8').trim();
11
+ if (UUID_RE.test(raw))
12
+ return raw;
13
+ }
14
+ catch (_a) { }
15
+ return null;
16
+ }
17
+ function stableMachineDistinctId() {
18
+ var _a, _b;
19
+ const h = crypto.createHash('sha256');
20
+ h.update(os.hostname());
21
+ h.update('\0');
22
+ h.update(os.homedir());
23
+ h.update('\0');
24
+ h.update((_b = (_a = process.env.USER) !== null && _a !== void 0 ? _a : process.env.USERNAME) !== null && _b !== void 0 ? _b : '');
25
+ const hex = h.digest('hex').slice(0, 32);
26
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
27
+ }
28
+ function tryPersistId(id) {
29
+ fs.mkdirSync(path.dirname(ID_FILE), { recursive: true });
30
+ fs.writeFileSync(ID_FILE, id, { flag: 'wx' });
31
+ }
32
+ export function getDistinctId() {
33
+ var _a, _b, _c;
34
+ const existing = readValidIdFromFile();
35
+ if (existing)
36
+ return existing;
37
+ const randomId = crypto.randomUUID();
38
+ try {
39
+ tryPersistId(randomId);
40
+ return (_a = readValidIdFromFile()) !== null && _a !== void 0 ? _a : randomId;
41
+ }
42
+ catch (err) {
43
+ const code = err.code;
44
+ if (code === 'EEXIST') {
45
+ const concurrent = readValidIdFromFile();
46
+ if (concurrent)
47
+ return concurrent;
48
+ try {
49
+ fs.writeFileSync(ID_FILE, randomId);
50
+ }
51
+ catch (_d) { }
52
+ return (_b = readValidIdFromFile()) !== null && _b !== void 0 ? _b : randomId;
53
+ }
54
+ }
55
+ const stable = stableMachineDistinctId();
56
+ try {
57
+ fs.mkdirSync(path.dirname(ID_FILE), { recursive: true });
58
+ fs.writeFileSync(ID_FILE, stable);
59
+ }
60
+ catch (_e) { }
61
+ return (_c = readValidIdFromFile()) !== null && _c !== void 0 ? _c : stable;
62
+ }