@mintlify/cli 4.0.1338 → 4.0.1340
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__/brokenLinks.test.ts +59 -1
- package/bin/cli.js +23 -4
- package/bin/format.js +110 -0
- package/bin/tsconfig.build.tsbuildinfo +1 -1
- package/bin/vendor/converter.js +8691 -0
- package/package.json +15 -4
- package/scripts/bundle-converter.mjs +24 -0
- package/src/cli.tsx +40 -5
- package/src/format.tsx +67 -0
- package/src/vendor/converter.ts +1 -0
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { buildGraph, getBrokenExternalLinks } from '@mintlify/link-rot';
|
|
2
2
|
import { MdxPath } from '@mintlify/link-rot/dist/graph.js';
|
|
3
3
|
import { addLog, clearLogs } from '@mintlify/previewing';
|
|
4
|
+
import path from 'node:path';
|
|
4
5
|
import { mockProcessExit } from 'vitest-mock-process';
|
|
5
6
|
|
|
6
|
-
import {
|
|
7
|
+
import { resolveExplicitFiles } from '../src/deslop/resolveFiles.js';
|
|
8
|
+
import { checkForMintJson, CMD_EXEC_PATH } from '../src/helpers.js';
|
|
7
9
|
import { runCommand } from './utils.js';
|
|
8
10
|
|
|
9
11
|
vi.mock('@mintlify/previewing', async (importOriginal) => {
|
|
@@ -19,6 +21,11 @@ vi.mock('../src/helpers.js', async (importOriginal) => {
|
|
|
19
21
|
};
|
|
20
22
|
});
|
|
21
23
|
|
|
24
|
+
vi.mock('../src/deslop/resolveFiles.js', () => ({
|
|
25
|
+
resolveChangedFiles: vi.fn(),
|
|
26
|
+
resolveExplicitFiles: vi.fn(),
|
|
27
|
+
}));
|
|
28
|
+
|
|
22
29
|
const mockGraph = {
|
|
23
30
|
precomputeFileResolutions: vi.fn(),
|
|
24
31
|
getBrokenInternalLinks: vi.fn().mockReturnValue([]),
|
|
@@ -42,6 +49,7 @@ describe('brokenLinks', () => {
|
|
|
42
49
|
beforeEach(() => {
|
|
43
50
|
vi.clearAllMocks();
|
|
44
51
|
vi.mocked(buildGraph).mockResolvedValue(mockGraph as never);
|
|
52
|
+
vi.mocked(resolveExplicitFiles).mockResolvedValue([]);
|
|
45
53
|
mockGraph.getBrokenInternalLinks.mockReturnValue([]);
|
|
46
54
|
});
|
|
47
55
|
|
|
@@ -94,6 +102,42 @@ describe('brokenLinks', () => {
|
|
|
94
102
|
expect(processExitMock).toHaveBeenCalledWith(1);
|
|
95
103
|
});
|
|
96
104
|
|
|
105
|
+
it('checks only files matching the provided option', async () => {
|
|
106
|
+
vi.mocked(checkForMintJson).mockResolvedValueOnce(true);
|
|
107
|
+
vi.mocked(resolveExplicitFiles).mockResolvedValueOnce(['guides/selected.mdx']);
|
|
108
|
+
mockGraph.getBrokenInternalLinks.mockReturnValue([
|
|
109
|
+
{
|
|
110
|
+
relativeDir: 'guides',
|
|
111
|
+
filename: 'selected.mdx',
|
|
112
|
+
originalPath: '/api/selected-invalid',
|
|
113
|
+
pathType: 'internal',
|
|
114
|
+
} as MdxPath,
|
|
115
|
+
{
|
|
116
|
+
relativeDir: 'guides',
|
|
117
|
+
filename: 'other.mdx',
|
|
118
|
+
originalPath: '/api/other-invalid',
|
|
119
|
+
pathType: 'internal',
|
|
120
|
+
} as MdxPath,
|
|
121
|
+
]);
|
|
122
|
+
|
|
123
|
+
await runCommand('broken-links', '--files', 'guides/selected.mdx', 'guides/**/*.mdx');
|
|
124
|
+
|
|
125
|
+
expect(resolveExplicitFiles).toHaveBeenCalledWith(CMD_EXEC_PATH, [
|
|
126
|
+
'guides/selected.mdx',
|
|
127
|
+
'guides/**/*.mdx',
|
|
128
|
+
]);
|
|
129
|
+
expect(addLogSpy).toHaveBeenCalledWith(
|
|
130
|
+
expect.objectContaining({
|
|
131
|
+
props: {
|
|
132
|
+
brokenLinksByFile: {
|
|
133
|
+
[path.join('guides', 'selected.mdx')]: ['/api/selected-invalid'],
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
})
|
|
137
|
+
);
|
|
138
|
+
expect(processExitMock).toHaveBeenCalledWith(1);
|
|
139
|
+
});
|
|
140
|
+
|
|
97
141
|
it('fails when checking throws error', async () => {
|
|
98
142
|
vi.mocked(checkForMintJson).mockResolvedValueOnce(true);
|
|
99
143
|
vi.mocked(buildGraph).mockRejectedValueOnce(new Error('some error'));
|
|
@@ -118,6 +162,7 @@ describe('brokenLinks --check-external', () => {
|
|
|
118
162
|
beforeEach(() => {
|
|
119
163
|
vi.clearAllMocks();
|
|
120
164
|
vi.mocked(buildGraph).mockResolvedValue(mockGraph as never);
|
|
165
|
+
vi.mocked(resolveExplicitFiles).mockResolvedValue([]);
|
|
121
166
|
mockGraph.getBrokenInternalLinks.mockReturnValue([]);
|
|
122
167
|
});
|
|
123
168
|
|
|
@@ -160,6 +205,19 @@ describe('brokenLinks --check-external', () => {
|
|
|
160
205
|
expect(processExitMock).toHaveBeenCalledWith(1);
|
|
161
206
|
});
|
|
162
207
|
|
|
208
|
+
it('checks external links only from matching files', async () => {
|
|
209
|
+
vi.mocked(checkForMintJson).mockResolvedValueOnce(true);
|
|
210
|
+
vi.mocked(resolveExplicitFiles).mockResolvedValueOnce(['selected.mdx']);
|
|
211
|
+
vi.mocked(getBrokenExternalLinks).mockResolvedValueOnce([]);
|
|
212
|
+
|
|
213
|
+
await runCommand('broken-links', '--files', 'selected.mdx', '--check-external');
|
|
214
|
+
|
|
215
|
+
expect(getBrokenExternalLinks).toHaveBeenCalledWith(mockGraph, {
|
|
216
|
+
sourceFiles: new Set(['selected.mdx']),
|
|
217
|
+
});
|
|
218
|
+
expect(processExitMock).toHaveBeenCalledWith(0);
|
|
219
|
+
});
|
|
220
|
+
|
|
163
221
|
it('fails when external link checking throws error', async () => {
|
|
164
222
|
vi.mocked(checkForMintJson).mockResolvedValueOnce(true);
|
|
165
223
|
vi.mocked(getBrokenExternalLinks).mockRejectedValueOnce(new Error('network error'));
|
package/bin/cli.js
CHANGED
|
@@ -21,6 +21,8 @@ import { setTelemetryEnabled } from './config.js';
|
|
|
21
21
|
import { getConfigValue, setConfigValue, clearConfigValue } from './config.js';
|
|
22
22
|
import { API_URL } from './constants.js';
|
|
23
23
|
import { deslopHandler } from './deslop/index.js';
|
|
24
|
+
import { resolveExplicitFiles } from './deslop/resolveFiles.js';
|
|
25
|
+
import { formatHandler } from './format.js';
|
|
24
26
|
import { CMD_EXEC_PATH, checkPort, checkNodeVersion, autoUpgradeIfNeeded, getVersions, isAI, suppressConsoleWarnings, terminate, } from './helpers.js';
|
|
25
27
|
import { init } from './init.js';
|
|
26
28
|
import { getAccessToken } from './keyring.js';
|
|
@@ -232,6 +234,11 @@ export const cli = ({ packageName = 'mint' }) => {
|
|
|
232
234
|
yield terminate(valid ? 0 : 1);
|
|
233
235
|
}))
|
|
234
236
|
.command('broken-links', 'Check for broken links', (yargs) => yargs
|
|
237
|
+
.option('files', {
|
|
238
|
+
type: 'string',
|
|
239
|
+
array: true,
|
|
240
|
+
description: 'Files or globs to check (defaults to the whole site)',
|
|
241
|
+
})
|
|
235
242
|
.option('check-anchors', {
|
|
236
243
|
type: 'boolean',
|
|
237
244
|
default: false,
|
|
@@ -251,17 +258,26 @@ export const cli = ({ packageName = 'mint' }) => {
|
|
|
251
258
|
type: 'boolean',
|
|
252
259
|
default: false,
|
|
253
260
|
description: 'also check that docs.json redirect destinations resolve to valid paths',
|
|
254
|
-
})
|
|
261
|
+
})
|
|
262
|
+
.example('mint broken-links --files introduction.mdx', 'Check a specific page')
|
|
263
|
+
.example('mint broken-links --files "guides/**/*.mdx"', 'Check pages matching a glob'), (argv) => __awaiter(void 0, void 0, void 0, function* () {
|
|
264
|
+
var _a;
|
|
255
265
|
yield autoUpgradeIfNeeded();
|
|
256
266
|
addLog(_jsx(SpinnerLog, { message: "checking for broken links..." }));
|
|
257
267
|
try {
|
|
268
|
+
const fileArgs = ((_a = argv.files) !== null && _a !== void 0 ? _a : []).map(String).filter(Boolean);
|
|
269
|
+
const sourceFiles = fileArgs.length > 0
|
|
270
|
+
? new Set((yield resolveExplicitFiles(CMD_EXEC_PATH, fileArgs)).map((file) => path.normalize(file)))
|
|
271
|
+
: undefined;
|
|
258
272
|
const graph = yield buildGraph(undefined, {
|
|
259
273
|
checkSnippets: argv['check-snippets'],
|
|
260
274
|
});
|
|
261
275
|
graph.precomputeFileResolutions();
|
|
262
|
-
const brokenInternalLinks = graph
|
|
276
|
+
const brokenInternalLinks = graph
|
|
277
|
+
.getBrokenInternalLinks({
|
|
263
278
|
checkAnchors: argv['check-anchors'],
|
|
264
|
-
})
|
|
279
|
+
})
|
|
280
|
+
.filter(({ relativeDir, filename }) => !sourceFiles || sourceFiles.has(path.join(relativeDir, filename)));
|
|
265
281
|
const brokenLinksByFile = {};
|
|
266
282
|
brokenInternalLinks.forEach((mdxPath) => {
|
|
267
283
|
const filename = path.join(mdxPath.relativeDir, mdxPath.filename);
|
|
@@ -274,7 +290,7 @@ export const cli = ({ packageName = 'mint' }) => {
|
|
|
274
290
|
}
|
|
275
291
|
});
|
|
276
292
|
if (argv['check-external']) {
|
|
277
|
-
const brokenExternalLinks = yield getBrokenExternalLinks(graph);
|
|
293
|
+
const brokenExternalLinks = yield getBrokenExternalLinks(graph, sourceFiles ? { sourceFiles } : undefined);
|
|
278
294
|
for (const result of brokenExternalLinks) {
|
|
279
295
|
for (const source of result.sources) {
|
|
280
296
|
const label = result.status
|
|
@@ -522,6 +538,9 @@ export const cli = ({ packageName = 'mint' }) => {
|
|
|
522
538
|
.example('mint deslop', 'Check git-changed pages for AI-sounding prose')
|
|
523
539
|
.example('mint deslop docs/guide.mdx', 'Check a specific page')
|
|
524
540
|
.example('mint deslop "docs/**/*.mdx" --format json', 'Check pages by glob with agent-friendly JSON output'), deslopHandler)
|
|
541
|
+
.command('format', 'Format MDX files in the current directory', (yargs) => yargs
|
|
542
|
+
.usage('usage: mint format')
|
|
543
|
+
.example('mint format', 'format all MDX files in the current directory'), formatHandler)
|
|
525
544
|
// Coming soon commands — visible in help, tracked via telemetry to gauge interest.
|
|
526
545
|
.command('ai', '[Coming soon] AI-powered documentation (run mint ai to vote)', () => undefined, comingSoon('ai', packageName))
|
|
527
546
|
.command('test', '[Coming soon] Test your documentation (run mint test to vote)', () => undefined, comingSoon('test', packageName))
|
package/bin/format.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
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
|
+
var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
|
|
11
|
+
var __asyncValues = (this && this.__asyncValues) || function (o) {
|
|
12
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
13
|
+
var m = o[Symbol.asyncIterator], i;
|
|
14
|
+
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
|
15
|
+
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
|
16
|
+
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
|
17
|
+
};
|
|
18
|
+
var __asyncDelegator = (this && this.__asyncDelegator) || function (o) {
|
|
19
|
+
var i, p;
|
|
20
|
+
return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i;
|
|
21
|
+
function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: false } : f ? f(v) : v; } : f; }
|
|
22
|
+
};
|
|
23
|
+
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
|
|
24
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
25
|
+
var g = generator.apply(thisArg, _arguments || []), i, q = [];
|
|
26
|
+
return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
|
|
27
|
+
function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
|
|
28
|
+
function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
|
|
29
|
+
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
|
|
30
|
+
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
|
|
31
|
+
function fulfill(value) { resume("next", value); }
|
|
32
|
+
function reject(value) { resume("throw", value); }
|
|
33
|
+
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
|
|
34
|
+
};
|
|
35
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
36
|
+
import { isMintIgnored, processMintIgnoreString } from '@mintlify/common';
|
|
37
|
+
import { getMintIgnore } from '@mintlify/prebuild';
|
|
38
|
+
import { addLog, ErrorLog, SuccessLog } from '@mintlify/previewing';
|
|
39
|
+
import fs from 'node:fs/promises';
|
|
40
|
+
import path from 'node:path';
|
|
41
|
+
import { CMD_EXEC_PATH, terminate } from './helpers.js';
|
|
42
|
+
function getGitIgnore() {
|
|
43
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
44
|
+
try {
|
|
45
|
+
const content = yield fs.readFile(path.join(CMD_EXEC_PATH, '.gitignore'), 'utf-8');
|
|
46
|
+
return processMintIgnoreString(content);
|
|
47
|
+
}
|
|
48
|
+
catch (_a) {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
function walk(dir, ignores) {
|
|
54
|
+
return __asyncGenerator(this, arguments, function* walk_1() {
|
|
55
|
+
const entries = yield __await(fs.readdir(dir, { withFileTypes: true }));
|
|
56
|
+
for (const entry of entries) {
|
|
57
|
+
const full = path.join(dir, entry.name);
|
|
58
|
+
const relative = path.relative(CMD_EXEC_PATH, full).split(path.sep).join('/');
|
|
59
|
+
if (entry.isDirectory()) {
|
|
60
|
+
if (isMintIgnored(`${relative}/`, ignores))
|
|
61
|
+
continue;
|
|
62
|
+
yield __await(yield* __asyncDelegator(__asyncValues(walk(full, ignores))));
|
|
63
|
+
}
|
|
64
|
+
else if (entry.name.endsWith('.mdx')) {
|
|
65
|
+
if (isMintIgnored(relative, ignores))
|
|
66
|
+
continue;
|
|
67
|
+
yield yield __await(full);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
export const formatHandler = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
73
|
+
var _a, e_1, _b, _c;
|
|
74
|
+
const { mdxToPm, pmToMdx } = yield import('./vendor/converter.js');
|
|
75
|
+
const ignores = [...(yield getGitIgnore()), ...(yield getMintIgnore(CMD_EXEC_PATH))];
|
|
76
|
+
let total = 0;
|
|
77
|
+
let changed = 0;
|
|
78
|
+
let failed = 0;
|
|
79
|
+
try {
|
|
80
|
+
for (var _d = true, _e = __asyncValues(walk(CMD_EXEC_PATH, ignores)), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
|
|
81
|
+
_c = _f.value;
|
|
82
|
+
_d = false;
|
|
83
|
+
const file = _c;
|
|
84
|
+
total++;
|
|
85
|
+
const relative = path.relative(CMD_EXEC_PATH, file);
|
|
86
|
+
try {
|
|
87
|
+
const raw = yield fs.readFile(file, 'utf-8');
|
|
88
|
+
const { doc, frontmatter } = mdxToPm(raw, { singleDollarTextMath: false });
|
|
89
|
+
const formatted = `${pmToMdx(doc, { frontmatter })}\n`;
|
|
90
|
+
if (formatted !== raw) {
|
|
91
|
+
yield fs.writeFile(file, formatted);
|
|
92
|
+
changed++;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
failed++;
|
|
97
|
+
addLog(_jsx(ErrorLog, { message: `${relative}: ${error instanceof Error ? error.message : 'unknown error'}` }));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
102
|
+
finally {
|
|
103
|
+
try {
|
|
104
|
+
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
|
|
105
|
+
}
|
|
106
|
+
finally { if (e_1) throw e_1.error; }
|
|
107
|
+
}
|
|
108
|
+
addLog(_jsx(SuccessLog, { message: `formatted ${changed} of ${total} mdx file${total === 1 ? '' : 's'}${failed > 0 ? ` (${failed} failed)` : ''}` }));
|
|
109
|
+
yield terminate(failed > 0 ? 1 : 0);
|
|
110
|
+
});
|