@mintlify/link-rot 3.0.910 → 3.0.912
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/dist/graph.d.ts +1 -0
- package/dist/graph.js +10 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -1
- package/dist/static-checking/getBrokenExternalLinks.d.ts +11 -0
- package/dist/static-checking/getBrokenExternalLinks.js +92 -0
- package/dist/static-checking/getBrokenInternalLinks.d.ts +8 -7
- package/dist/static-checking/getBrokenInternalLinks.js +18 -18
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +6 -6
package/dist/graph.d.ts
CHANGED
package/dist/graph.js
CHANGED
|
@@ -402,6 +402,16 @@ export class Graph {
|
|
|
402
402
|
}
|
|
403
403
|
return brokenLinks;
|
|
404
404
|
}
|
|
405
|
+
getExternalPathsByNode() {
|
|
406
|
+
const result = new Map();
|
|
407
|
+
for (const [label, node] of Object.entries(this.nodes)) {
|
|
408
|
+
const externalPaths = node.paths.filter((p) => p.pathType === PathType.EXTERNAL);
|
|
409
|
+
if (externalPaths.length > 0) {
|
|
410
|
+
result.set(label, externalPaths);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return result;
|
|
414
|
+
}
|
|
405
415
|
// DEBUGGING
|
|
406
416
|
getAllInternalPaths() {
|
|
407
417
|
return Object.values(this.nodes)
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
|
-
export { getBrokenInternalLinks } from './static-checking/getBrokenInternalLinks.js';
|
|
1
|
+
export { buildGraph, getBrokenInternalLinks } from './static-checking/getBrokenInternalLinks.js';
|
|
2
|
+
export { getBrokenExternalLinks } from './static-checking/getBrokenExternalLinks.js';
|
|
3
|
+
export type { ExternalLinkResult } from './static-checking/getBrokenExternalLinks.js';
|
|
2
4
|
export { renameFilesAndUpdateLinksInContent } from './link-renaming/renameFileAndUpdateLinksInContent.js';
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
export { getBrokenInternalLinks } from './static-checking/getBrokenInternalLinks.js';
|
|
1
|
+
export { buildGraph, getBrokenInternalLinks } from './static-checking/getBrokenInternalLinks.js';
|
|
2
|
+
export { getBrokenExternalLinks } from './static-checking/getBrokenExternalLinks.js';
|
|
2
3
|
export { renameFilesAndUpdateLinksInContent } from './link-renaming/renameFileAndUpdateLinksInContent.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Graph } from '../graph.js';
|
|
2
|
+
export type ExternalLinkResult = {
|
|
3
|
+
url: string;
|
|
4
|
+
status: number | null;
|
|
5
|
+
error?: string;
|
|
6
|
+
sources: Array<{
|
|
7
|
+
file: string;
|
|
8
|
+
originalPath: string;
|
|
9
|
+
}>;
|
|
10
|
+
};
|
|
11
|
+
export declare const getBrokenExternalLinks: (graph: Graph) => Promise<ExternalLinkResult[]>;
|
|
@@ -0,0 +1,92 @@
|
|
|
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
|
+
const CONCURRENCY = 5;
|
|
11
|
+
const TIMEOUT_MS = 10000;
|
|
12
|
+
const USER_AGENT = 'Mintlify-LinkChecker/1.0';
|
|
13
|
+
const isHttpUrl = (url) => {
|
|
14
|
+
try {
|
|
15
|
+
const parsed = new URL(url);
|
|
16
|
+
return parsed.protocol === 'http:' || parsed.protocol === 'https:';
|
|
17
|
+
}
|
|
18
|
+
catch (_a) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
const checkExternalUrl = (url) => __awaiter(void 0, void 0, void 0, function* () {
|
|
23
|
+
const controller = new AbortController();
|
|
24
|
+
const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
25
|
+
try {
|
|
26
|
+
const response = yield fetch(url, {
|
|
27
|
+
method: 'HEAD',
|
|
28
|
+
signal: controller.signal,
|
|
29
|
+
headers: { 'User-Agent': USER_AGENT },
|
|
30
|
+
redirect: 'follow',
|
|
31
|
+
});
|
|
32
|
+
clearTimeout(timeoutId);
|
|
33
|
+
return {
|
|
34
|
+
ok: response.status !== 404,
|
|
35
|
+
status: response.status,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
clearTimeout(timeoutId);
|
|
40
|
+
return {
|
|
41
|
+
ok: false,
|
|
42
|
+
status: null,
|
|
43
|
+
error: err instanceof Error ? err.message : 'unknown error',
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
const pMap = (items, fn, concurrency) => __awaiter(void 0, void 0, void 0, function* () {
|
|
48
|
+
const results = [];
|
|
49
|
+
let index = 0;
|
|
50
|
+
const run = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
51
|
+
while (index < items.length) {
|
|
52
|
+
const currentIndex = index++;
|
|
53
|
+
results[currentIndex] = yield fn(items[currentIndex]);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
const workers = Array.from({ length: Math.min(concurrency, items.length) }, () => run());
|
|
57
|
+
yield Promise.all(workers);
|
|
58
|
+
return results;
|
|
59
|
+
});
|
|
60
|
+
export const getBrokenExternalLinks = (graph) => __awaiter(void 0, void 0, void 0, function* () {
|
|
61
|
+
const urlSourceMap = new Map();
|
|
62
|
+
for (const [nodeLabel, externalPaths] of graph.getExternalPathsByNode()) {
|
|
63
|
+
for (const mdxPath of externalPaths) {
|
|
64
|
+
if (isHttpUrl(mdxPath.originalPath)) {
|
|
65
|
+
const url = mdxPath.originalPath;
|
|
66
|
+
const existing = urlSourceMap.get(url);
|
|
67
|
+
const source = { file: nodeLabel, originalPath: mdxPath.originalPath };
|
|
68
|
+
if (existing) {
|
|
69
|
+
existing.push(source);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
urlSourceMap.set(url, [source]);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const uniqueUrls = [...urlSourceMap.keys()];
|
|
78
|
+
const results = yield pMap(uniqueUrls, (url) => __awaiter(void 0, void 0, void 0, function* () {
|
|
79
|
+
const result = yield checkExternalUrl(url);
|
|
80
|
+
if (result.ok)
|
|
81
|
+
return null;
|
|
82
|
+
const entry = {
|
|
83
|
+
url,
|
|
84
|
+
status: result.status,
|
|
85
|
+
sources: urlSourceMap.get(url),
|
|
86
|
+
};
|
|
87
|
+
if (result.error)
|
|
88
|
+
entry.error = result.error;
|
|
89
|
+
return entry;
|
|
90
|
+
}), CONCURRENCY);
|
|
91
|
+
return results.filter((r) => r !== null);
|
|
92
|
+
});
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import type { TableOfContentsSectionType } from '@mintlify/common';
|
|
2
2
|
import type { Root } from 'mdast';
|
|
3
|
-
import { Node } from '../graph.js';
|
|
3
|
+
import { Graph, Node } from '../graph.js';
|
|
4
4
|
export declare const flattenTableOfContentsSlugs: (sections: TableOfContentsSectionType[]) => Set<string>;
|
|
5
5
|
export declare const extractComponentAnchorIds: (tree: Root) => Set<string>;
|
|
6
|
-
export declare const decorateGraphNodeFromPageContent: (graphNode: Node, content: string
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
export declare const decorateGraphNodeFromPageContent: (graphNode: Node, content: string, options?: {
|
|
7
|
+
checkSnippets?: boolean;
|
|
8
|
+
}) => Promise<void>;
|
|
9
|
+
export declare const buildGraph: (repoPath?: string, options?: {
|
|
10
|
+
checkSnippets?: boolean;
|
|
11
|
+
}) => Promise<Graph>;
|
|
12
12
|
export declare const getBrokenInternalLinks: (repoPath?: string, options?: {
|
|
13
13
|
checkAnchors?: boolean;
|
|
14
|
+
checkSnippets?: boolean;
|
|
14
15
|
}) => Promise<import("../graph.js").MdxPath[]>;
|
|
@@ -7,7 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
-
import { cleanHeadingId, COMPONENTS_WITH_CLEANED_ID, COMPONENTS_WITHOUT_ID, coreRemark, generateParamFieldId, isMintIgnored, remarkComponentIds, remarkExtractTableOfContents, } from '@mintlify/common';
|
|
10
|
+
import { cleanHeadingId, COMPONENTS_WITH_CLEANED_ID, COMPONENTS_WITHOUT_ID, coreRemark, extractImportSources, generateParamFieldId, isMintIgnored, remarkComponentIds, remarkExtractTableOfContents, } from '@mintlify/common';
|
|
11
11
|
import { getMintIgnore } from '@mintlify/prebuild';
|
|
12
12
|
import fs from 'fs-extra';
|
|
13
13
|
import path from 'path';
|
|
@@ -56,16 +56,18 @@ export const extractComponentAnchorIds = (tree) => {
|
|
|
56
56
|
});
|
|
57
57
|
return ids;
|
|
58
58
|
};
|
|
59
|
-
export const decorateGraphNodeFromPageContent = (graphNode, content) => __awaiter(void 0, void 0, void 0, function* () {
|
|
59
|
+
export const decorateGraphNodeFromPageContent = (graphNode, content, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
60
60
|
const mdxExtracts = {};
|
|
61
61
|
const visitLinks = () => {
|
|
62
62
|
return (tree) => {
|
|
63
|
+
if (options === null || options === void 0 ? void 0 : options.checkSnippets) {
|
|
64
|
+
for (const source of extractImportSources(tree)) {
|
|
65
|
+
graphNode.addPath(source);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
63
68
|
visit(tree, (node) => {
|
|
64
69
|
var _a, _b;
|
|
65
|
-
if (
|
|
66
|
-
// ![]() format
|
|
67
|
-
node.type === 'link' ||
|
|
68
|
-
node.type === 'image') {
|
|
70
|
+
if (node.type === 'link' || node.type === 'image') {
|
|
69
71
|
graphNode.addPath(node.url, Wrapper.MD);
|
|
70
72
|
return;
|
|
71
73
|
}
|
|
@@ -102,25 +104,16 @@ export const decorateGraphNodeFromPageContent = (graphNode, content) => __awaite
|
|
|
102
104
|
const componentIds = extractComponentAnchorIds(tree);
|
|
103
105
|
graphNode.headingSlugs = new Set([...tocSlugs, ...componentIds]);
|
|
104
106
|
});
|
|
105
|
-
|
|
106
|
-
* Get all broken internal links used in the site
|
|
107
|
-
* Files matching .mintignore patterns are excluded from valid link targets,
|
|
108
|
-
* so links pointing to ignored files will be reported as broken.
|
|
109
|
-
*/
|
|
110
|
-
export const getBrokenInternalLinks = (repoPath, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
107
|
+
export const buildGraph = (repoPath, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
111
108
|
const baseDir = repoPath ? repoPath : process.cwd();
|
|
112
109
|
const mintIgnoreGlobs = yield getMintIgnore(baseDir);
|
|
113
110
|
const graph = new Graph(baseDir, mintIgnoreGlobs);
|
|
114
|
-
// add nodes for every page or media path in project (excluding mintignore'd files)
|
|
115
111
|
const allFilenames = getLinkPaths(baseDir);
|
|
116
112
|
const filenames = allFilenames.filter((file) => !isMintIgnored(file, mintIgnoreGlobs));
|
|
117
113
|
graph.addNodes(filenames);
|
|
118
|
-
// pull redirects from docs.json or mint.json if either exist
|
|
119
114
|
const redirectMappings = yield getRedirects(baseDir);
|
|
120
115
|
graph.setRedirects(redirectMappings);
|
|
121
|
-
// add nodes for every link alias in the project
|
|
122
116
|
graph.addAliasNodes();
|
|
123
|
-
// add virtual nodes for OpenAPI-generated pages
|
|
124
117
|
try {
|
|
125
118
|
const openApiPagePaths = yield getOpenApiPagePaths(baseDir);
|
|
126
119
|
graph.addVirtualNodes(openApiPagePaths);
|
|
@@ -143,14 +136,21 @@ export const getBrokenInternalLinks = (repoPath, options) => __awaiter(void 0, v
|
|
|
143
136
|
if (fileNode) {
|
|
144
137
|
const fileContent = fs.readFileSync(path.join(baseDir, filePath)).toString();
|
|
145
138
|
try {
|
|
146
|
-
yield decorateGraphNodeFromPageContent(fileNode, fileContent
|
|
139
|
+
yield decorateGraphNodeFromPageContent(fileNode, fileContent, {
|
|
140
|
+
checkSnippets: options === null || options === void 0 ? void 0 : options.checkSnippets,
|
|
141
|
+
});
|
|
147
142
|
}
|
|
148
143
|
catch (err) {
|
|
149
144
|
throw new Error(`Syntax error - Unable to parse ${filePath} - ${err}`);
|
|
150
145
|
}
|
|
151
146
|
}
|
|
152
147
|
})));
|
|
153
|
-
|
|
148
|
+
return graph;
|
|
149
|
+
});
|
|
150
|
+
export const getBrokenInternalLinks = (repoPath, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
151
|
+
const graph = yield buildGraph(repoPath, {
|
|
152
|
+
checkSnippets: options === null || options === void 0 ? void 0 : options.checkSnippets,
|
|
153
|
+
});
|
|
154
154
|
graph.precomputeFileResolutions();
|
|
155
155
|
return graph.getBrokenInternalLinks(options);
|
|
156
156
|
});
|