@mintlify/cli 4.0.1705 → 4.0.1706
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/bin/brokenLinks.js +122 -0
- package/bin/cli.js +4 -110
- package/bin/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/brokenLinks.tsx +152 -0
- package/src/cli.tsx +2 -134
|
@@ -0,0 +1,122 @@
|
|
|
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 { jsx as _jsx } from "react/jsx-runtime";
|
|
11
|
+
import { buildGraph, getBrokenExternalLinks, getFilesLinksDependOn } from '@mintlify/link-rot';
|
|
12
|
+
import { addLog, clearLogs, BrokenLinksLog, ErrorLog, SpinnerLog, SuccessLog, WarningLog, } from '@mintlify/previewing';
|
|
13
|
+
import fs from 'node:fs/promises';
|
|
14
|
+
import path from 'path';
|
|
15
|
+
import { autoUpgradeIfNeeded, CMD_EXEC_PATH, findDocsRoot, terminate } from './helpers.js';
|
|
16
|
+
import { resolveExplicitFiles } from './resolveFiles.js';
|
|
17
|
+
export const brokenLinksHandler = (argv) => __awaiter(void 0, void 0, void 0, function* () {
|
|
18
|
+
var _a;
|
|
19
|
+
yield autoUpgradeIfNeeded();
|
|
20
|
+
addLog(_jsx(SpinnerLog, { message: "checking for broken links..." }));
|
|
21
|
+
try {
|
|
22
|
+
const docsRoot = yield findDocsRoot(CMD_EXEC_PATH);
|
|
23
|
+
const fileArgs = ((_a = argv.files) !== null && _a !== void 0 ? _a : []).map(String).filter(Boolean);
|
|
24
|
+
const sourceFiles = fileArgs.length > 0
|
|
25
|
+
? new Set((yield resolveExplicitFiles(CMD_EXEC_PATH, fileArgs)).map((file) => path.relative(docsRoot, path.resolve(CMD_EXEC_PATH, file))))
|
|
26
|
+
: undefined;
|
|
27
|
+
// One unparseable page shouldn't hide every other result: report it and keep going
|
|
28
|
+
const parseErrorsByFile = new Map();
|
|
29
|
+
const graph = yield buildGraph(docsRoot, {
|
|
30
|
+
checkSnippets: argv['check-snippets'],
|
|
31
|
+
onParseError: (filePath, error) => parseErrorsByFile.set(filePath, error.message),
|
|
32
|
+
});
|
|
33
|
+
graph.precomputeFileResolutions();
|
|
34
|
+
// With --files, a failure elsewhere only matters when a selected link needs that
|
|
35
|
+
// file: an anchor pointing at it, or a snippet the selected page renders
|
|
36
|
+
const dependencies = sourceFiles
|
|
37
|
+
? getFilesLinksDependOn(graph, sourceFiles, {
|
|
38
|
+
checkAnchors: argv['check-anchors'],
|
|
39
|
+
checkSnippets: argv['check-snippets'],
|
|
40
|
+
})
|
|
41
|
+
: undefined;
|
|
42
|
+
const parseErrors = [...parseErrorsByFile]
|
|
43
|
+
.filter(([filePath]) => !sourceFiles ||
|
|
44
|
+
sourceFiles.has(filePath) ||
|
|
45
|
+
(dependencies === null || dependencies === void 0 ? void 0 : dependencies.has(filePath.split(path.sep).join('/'))))
|
|
46
|
+
.map(([, message]) => message);
|
|
47
|
+
const brokenInternalLinks = graph
|
|
48
|
+
.getBrokenInternalLinks({
|
|
49
|
+
checkAnchors: argv['check-anchors'],
|
|
50
|
+
})
|
|
51
|
+
.filter(({ relativeDir, filename }) => !sourceFiles || sourceFiles.has(path.join(relativeDir, filename)));
|
|
52
|
+
const brokenLinksByFile = {};
|
|
53
|
+
brokenInternalLinks.forEach((mdxPath) => {
|
|
54
|
+
const filename = path.join(mdxPath.relativeDir, mdxPath.filename);
|
|
55
|
+
const existing = brokenLinksByFile[filename];
|
|
56
|
+
if (existing) {
|
|
57
|
+
existing.push(mdxPath.originalPath);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
brokenLinksByFile[filename] = [mdxPath.originalPath];
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
if (argv['check-external']) {
|
|
64
|
+
const brokenExternalLinks = yield getBrokenExternalLinks(graph, sourceFiles ? { sourceFiles } : undefined);
|
|
65
|
+
for (const result of brokenExternalLinks) {
|
|
66
|
+
for (const source of result.sources) {
|
|
67
|
+
const label = result.status
|
|
68
|
+
? `${result.url} (${result.status})`
|
|
69
|
+
: `${result.url} (${result.error})`;
|
|
70
|
+
const existing = brokenLinksByFile[source.file];
|
|
71
|
+
if (existing) {
|
|
72
|
+
existing.push(label);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
brokenLinksByFile[source.file] = [label];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (argv['check-redirects']) {
|
|
81
|
+
const brokenRedirects = graph.getBrokenRedirects();
|
|
82
|
+
if (brokenRedirects.length > 0) {
|
|
83
|
+
const configFilename = yield fs
|
|
84
|
+
.access(path.join(docsRoot, 'docs.json'))
|
|
85
|
+
.then(() => 'docs.json')
|
|
86
|
+
.catch(() => 'mint.json');
|
|
87
|
+
const labels = brokenRedirects.map(({ source, destination }) => `${source} → ${destination}`);
|
|
88
|
+
const existing = brokenLinksByFile[configFilename];
|
|
89
|
+
if (existing) {
|
|
90
|
+
existing.push(...labels);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
brokenLinksByFile[configFilename] = labels;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const hasBrokenLinks = Object.keys(brokenLinksByFile).length > 0;
|
|
98
|
+
if (!hasBrokenLinks && parseErrors.length === 0) {
|
|
99
|
+
clearLogs();
|
|
100
|
+
addLog(_jsx(SuccessLog, { message: "no broken links found" }));
|
|
101
|
+
yield terminate(0);
|
|
102
|
+
}
|
|
103
|
+
clearLogs();
|
|
104
|
+
if (hasBrokenLinks) {
|
|
105
|
+
addLog(_jsx(BrokenLinksLog, { brokenLinksByFile: brokenLinksByFile }));
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
addLog(_jsx(SuccessLog, { message: "no broken links found in the files that parsed" }));
|
|
109
|
+
}
|
|
110
|
+
for (const message of parseErrors) {
|
|
111
|
+
addLog(_jsx(ErrorLog, { message: message }));
|
|
112
|
+
}
|
|
113
|
+
if (parseErrors.length > 0) {
|
|
114
|
+
addLog(_jsx(WarningLog, { message: `skipped ${parseErrors.length} file(s) that could not be parsed; links in them were not checked` }));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
addLog(_jsx(ErrorLog, { message: err instanceof Error ? err.message : 'unknown error' }));
|
|
119
|
+
yield terminate(1);
|
|
120
|
+
}
|
|
121
|
+
yield terminate(1);
|
|
122
|
+
});
|
package/bin/cli.js
CHANGED
|
@@ -8,8 +8,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
10
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
11
|
-
import {
|
|
12
|
-
import { addLog, dev, exportSite, validateBuild, ErrorLog, SpinnerLog, SuccessLog, Logs, clearLogs, BrokenLinksLog, WarningLog, } from '@mintlify/previewing';
|
|
11
|
+
import { addLog, dev, exportSite, validateBuild, ErrorLog, SuccessLog, Logs, WarningLog, } from '@mintlify/previewing';
|
|
13
12
|
import { thumbnailOverrideSchema } from '@mintlify/validation';
|
|
14
13
|
import { render, Text } from 'ink';
|
|
15
14
|
import fs from 'node:fs/promises';
|
|
@@ -20,6 +19,7 @@ import { accessibilityCheck } from './accessibilityCheck.js';
|
|
|
20
19
|
import { addDomain } from './addDomain.js';
|
|
21
20
|
import { createAgentClaimLink } from './agent-claim.js';
|
|
22
21
|
import { analyticsBuilder } from './analytics/index.js';
|
|
22
|
+
import { brokenLinksHandler } from './brokenLinks.js';
|
|
23
23
|
import { comingSoon } from './comingSoon.js';
|
|
24
24
|
import { setTelemetryEnabled } from './config.js';
|
|
25
25
|
import { getConfigValue, setConfigValue, clearConfigValue } from './config.js';
|
|
@@ -27,7 +27,7 @@ import { API_URL } from './constants.js';
|
|
|
27
27
|
import { DEPLOY_HELP, deploymentInstructions } from './deploy-instructions.js';
|
|
28
28
|
import { formatDeployment, prepareAgentDeployment } from './deploy.js';
|
|
29
29
|
import { formatHandler } from './format.js';
|
|
30
|
-
import { CMD_EXEC_PATH, checkPort, checkNodeVersion, autoUpgradeIfNeeded,
|
|
30
|
+
import { CMD_EXEC_PATH, checkPort, checkNodeVersion, autoUpgradeIfNeeded, getVersions, isAI, suppressConsoleWarnings, terminate, } from './helpers.js';
|
|
31
31
|
import { init } from './init.js';
|
|
32
32
|
import { getAccessToken } from './keyring.js';
|
|
33
33
|
import { login } from './login.js';
|
|
@@ -36,7 +36,6 @@ import { mdxLinter } from './mdxLinter.js';
|
|
|
36
36
|
import { createTelemetryMiddleware } from './middlewares/telemetryMiddleware.js';
|
|
37
37
|
import { ogCommand } from './og.js';
|
|
38
38
|
import { buildOverlayRegistryForCheck, checkOpenApiFile, getOpenApiFilenamesFromDocsConfig, } from './openApiCheck.js';
|
|
39
|
-
import { resolveExplicitFiles } from './resolveFiles.js';
|
|
40
39
|
import { scoreHandler } from './score/index.js';
|
|
41
40
|
import { sendPublishPrompt, signup } from './signup.js';
|
|
42
41
|
import { status, getCliSubdomains } from './status.js';
|
|
@@ -362,112 +361,7 @@ export const cli = ({ packageName = 'mint' }) => {
|
|
|
362
361
|
description: 'also check that docs.json redirect destinations resolve to valid paths',
|
|
363
362
|
})
|
|
364
363
|
.example('mint broken-links --files introduction.mdx', 'Check a specific page')
|
|
365
|
-
.example('mint broken-links --files "guides/**/*.mdx"', 'Check pages matching a glob'), (argv) =>
|
|
366
|
-
var _a;
|
|
367
|
-
yield autoUpgradeIfNeeded();
|
|
368
|
-
addLog(_jsx(SpinnerLog, { message: "checking for broken links..." }));
|
|
369
|
-
try {
|
|
370
|
-
const docsRoot = yield findDocsRoot(CMD_EXEC_PATH);
|
|
371
|
-
const fileArgs = ((_a = argv.files) !== null && _a !== void 0 ? _a : []).map(String).filter(Boolean);
|
|
372
|
-
const sourceFiles = fileArgs.length > 0
|
|
373
|
-
? new Set((yield resolveExplicitFiles(CMD_EXEC_PATH, fileArgs)).map((file) => path.relative(docsRoot, path.resolve(CMD_EXEC_PATH, file))))
|
|
374
|
-
: undefined;
|
|
375
|
-
// One unparseable page shouldn't hide every other result: report it and keep going
|
|
376
|
-
const parseErrorsByFile = new Map();
|
|
377
|
-
const graph = yield buildGraph(docsRoot, {
|
|
378
|
-
checkSnippets: argv['check-snippets'],
|
|
379
|
-
onParseError: (filePath, error) => parseErrorsByFile.set(filePath, error.message),
|
|
380
|
-
});
|
|
381
|
-
graph.precomputeFileResolutions();
|
|
382
|
-
// With --files, a failure elsewhere only matters when a selected link needs that
|
|
383
|
-
// file: an anchor pointing at it, or a snippet the selected page renders
|
|
384
|
-
const dependencies = sourceFiles
|
|
385
|
-
? getFilesLinksDependOn(graph, sourceFiles, {
|
|
386
|
-
checkAnchors: argv['check-anchors'],
|
|
387
|
-
checkSnippets: argv['check-snippets'],
|
|
388
|
-
})
|
|
389
|
-
: undefined;
|
|
390
|
-
const parseErrors = [...parseErrorsByFile]
|
|
391
|
-
.filter(([filePath]) => !sourceFiles ||
|
|
392
|
-
sourceFiles.has(filePath) ||
|
|
393
|
-
(dependencies === null || dependencies === void 0 ? void 0 : dependencies.has(filePath.split(path.sep).join('/'))))
|
|
394
|
-
.map(([, message]) => message);
|
|
395
|
-
const brokenInternalLinks = graph
|
|
396
|
-
.getBrokenInternalLinks({
|
|
397
|
-
checkAnchors: argv['check-anchors'],
|
|
398
|
-
})
|
|
399
|
-
.filter(({ relativeDir, filename }) => !sourceFiles || sourceFiles.has(path.join(relativeDir, filename)));
|
|
400
|
-
const brokenLinksByFile = {};
|
|
401
|
-
brokenInternalLinks.forEach((mdxPath) => {
|
|
402
|
-
const filename = path.join(mdxPath.relativeDir, mdxPath.filename);
|
|
403
|
-
const existing = brokenLinksByFile[filename];
|
|
404
|
-
if (existing) {
|
|
405
|
-
existing.push(mdxPath.originalPath);
|
|
406
|
-
}
|
|
407
|
-
else {
|
|
408
|
-
brokenLinksByFile[filename] = [mdxPath.originalPath];
|
|
409
|
-
}
|
|
410
|
-
});
|
|
411
|
-
if (argv['check-external']) {
|
|
412
|
-
const brokenExternalLinks = yield getBrokenExternalLinks(graph, sourceFiles ? { sourceFiles } : undefined);
|
|
413
|
-
for (const result of brokenExternalLinks) {
|
|
414
|
-
for (const source of result.sources) {
|
|
415
|
-
const label = result.status
|
|
416
|
-
? `${result.url} (${result.status})`
|
|
417
|
-
: `${result.url} (${result.error})`;
|
|
418
|
-
const existing = brokenLinksByFile[source.file];
|
|
419
|
-
if (existing) {
|
|
420
|
-
existing.push(label);
|
|
421
|
-
}
|
|
422
|
-
else {
|
|
423
|
-
brokenLinksByFile[source.file] = [label];
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
if (argv['check-redirects']) {
|
|
429
|
-
const brokenRedirects = graph.getBrokenRedirects();
|
|
430
|
-
if (brokenRedirects.length > 0) {
|
|
431
|
-
const configFilename = yield fs
|
|
432
|
-
.access(path.join(docsRoot, 'docs.json'))
|
|
433
|
-
.then(() => 'docs.json')
|
|
434
|
-
.catch(() => 'mint.json');
|
|
435
|
-
const labels = brokenRedirects.map(({ source, destination }) => `${source} → ${destination}`);
|
|
436
|
-
const existing = brokenLinksByFile[configFilename];
|
|
437
|
-
if (existing) {
|
|
438
|
-
existing.push(...labels);
|
|
439
|
-
}
|
|
440
|
-
else {
|
|
441
|
-
brokenLinksByFile[configFilename] = labels;
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
const hasBrokenLinks = Object.keys(brokenLinksByFile).length > 0;
|
|
446
|
-
if (!hasBrokenLinks && parseErrors.length === 0) {
|
|
447
|
-
clearLogs();
|
|
448
|
-
addLog(_jsx(SuccessLog, { message: "no broken links found" }));
|
|
449
|
-
yield terminate(0);
|
|
450
|
-
}
|
|
451
|
-
clearLogs();
|
|
452
|
-
if (hasBrokenLinks) {
|
|
453
|
-
addLog(_jsx(BrokenLinksLog, { brokenLinksByFile: brokenLinksByFile }));
|
|
454
|
-
}
|
|
455
|
-
else {
|
|
456
|
-
addLog(_jsx(SuccessLog, { message: "no broken links found in the files that parsed" }));
|
|
457
|
-
}
|
|
458
|
-
for (const message of parseErrors) {
|
|
459
|
-
addLog(_jsx(ErrorLog, { message: message }));
|
|
460
|
-
}
|
|
461
|
-
if (parseErrors.length > 0) {
|
|
462
|
-
addLog(_jsx(WarningLog, { message: `skipped ${parseErrors.length} file(s) that could not be parsed; links in them were not checked` }));
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
catch (err) {
|
|
466
|
-
addLog(_jsx(ErrorLog, { message: err instanceof Error ? err.message : 'unknown error' }));
|
|
467
|
-
yield terminate(1);
|
|
468
|
-
}
|
|
469
|
-
yield terminate(1);
|
|
470
|
-
}))
|
|
364
|
+
.example('mint broken-links --files "guides/**/*.mdx"', 'Check pages matching a glob'), (argv) => brokenLinksHandler(argv))
|
|
471
365
|
.command('status', 'View current authentication status', () => undefined, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
472
366
|
yield status();
|
|
473
367
|
yield terminate(0);
|