@mintlify/link-rot 3.0.1442 → 3.0.1444
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 +3 -1
- package/dist/graph.js +12 -5
- package/dist/static-checking/getBrokenInternalLinks.d.ts +2 -1
- package/dist/static-checking/getBrokenInternalLinks.js +14 -7
- package/dist/static-checking/getSnippetReferences.d.ts +6 -0
- package/dist/static-checking/getSnippetReferences.js +42 -0
- package/dist/static-checking/resolveSnippetLinksFromRenderingPages.d.ts +3 -0
- package/dist/static-checking/resolveSnippetLinksFromRenderingPages.js +55 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +8 -8
package/dist/graph.d.ts
CHANGED
|
@@ -8,7 +8,8 @@ export declare enum Wrapper {
|
|
|
8
8
|
MD = "md",// ![]()
|
|
9
9
|
SRC = "src",// <img src="" />
|
|
10
10
|
HREF = "href",// <a href="" />
|
|
11
|
-
CARD = "card"
|
|
11
|
+
CARD = "card",// <Card to="" />
|
|
12
|
+
IMPORT = "import"
|
|
12
13
|
}
|
|
13
14
|
export declare enum EdgeType {
|
|
14
15
|
CONTENT = "content",
|
|
@@ -26,6 +27,7 @@ export declare class MdxPath {
|
|
|
26
27
|
pathType: PathType;
|
|
27
28
|
anchorLink?: string;
|
|
28
29
|
queryParams?: URLSearchParams;
|
|
30
|
+
pagePaths: MdxPath[];
|
|
29
31
|
static getPathType(url: string): PathType;
|
|
30
32
|
/**
|
|
31
33
|
* Generate all the computed fields in an MdxPath
|
package/dist/graph.js
CHANGED
|
@@ -26,6 +26,7 @@ export var Wrapper;
|
|
|
26
26
|
Wrapper["SRC"] = "src";
|
|
27
27
|
Wrapper["HREF"] = "href";
|
|
28
28
|
Wrapper["CARD"] = "card";
|
|
29
|
+
Wrapper["IMPORT"] = "import";
|
|
29
30
|
})(Wrapper || (Wrapper = {}));
|
|
30
31
|
export var EdgeType;
|
|
31
32
|
(function (EdgeType) {
|
|
@@ -105,6 +106,7 @@ export class MdxPath {
|
|
|
105
106
|
this.relativeDir = relativeDir;
|
|
106
107
|
this.filename = filename;
|
|
107
108
|
this.wrapper = wrapper;
|
|
109
|
+
this.pagePaths = [];
|
|
108
110
|
this.relativeDir = normalizePath(relativeDir);
|
|
109
111
|
const { path, pathType, anchorLink, queryParams } = this.constructParsedPath();
|
|
110
112
|
this.path = path;
|
|
@@ -368,6 +370,7 @@ export class Graph {
|
|
|
368
370
|
// Build resolution map for all internal paths
|
|
369
371
|
Object.values(this.nodes).forEach((node) => {
|
|
370
372
|
node.paths
|
|
373
|
+
.flatMap((path) => [path, ...path.pagePaths])
|
|
371
374
|
.filter((path) => path.pathType === PathType.INTERNAL)
|
|
372
375
|
.forEach((path) => {
|
|
373
376
|
const pathKey = path.toString();
|
|
@@ -403,11 +406,11 @@ export class Graph {
|
|
|
403
406
|
if (this.fileResolutionMap.size === 0) {
|
|
404
407
|
this.precomputeFileResolutions();
|
|
405
408
|
}
|
|
406
|
-
const brokenLinks =
|
|
409
|
+
const brokenLinks = new Set();
|
|
407
410
|
const nodeSet = new Set(Object.values(this.nodes).map((node) => node.toString()));
|
|
408
411
|
const wildcardRedirects = [...this.redirectMap].filter(([source]) => /[:*]/.test(source));
|
|
409
412
|
for (const node of Object.values(this.nodes)) {
|
|
410
|
-
for (const path of node.paths) {
|
|
413
|
+
for (const path of node.paths.flatMap((path) => [path, ...path.pagePaths])) {
|
|
411
414
|
if (path.pathType === PathType.INTERNAL) {
|
|
412
415
|
const pathString = path.toString();
|
|
413
416
|
const resolvedFiles = this.fileResolutionMap.get(pathString);
|
|
@@ -428,19 +431,23 @@ export class Graph {
|
|
|
428
431
|
}
|
|
429
432
|
}
|
|
430
433
|
if (!hasExistingFile && !hasValidRedirect) {
|
|
431
|
-
brokenLinks.
|
|
434
|
+
brokenLinks.add(path);
|
|
432
435
|
}
|
|
433
436
|
else if ((options === null || options === void 0 ? void 0 : options.checkAnchors) && path.anchorLink) {
|
|
434
437
|
const anchor = safeCleanHeadingId(path.anchorLink.slice(1));
|
|
435
438
|
const headingSlugs = this.getHeadingSlugsForPath(pathString, nodeSet);
|
|
436
439
|
if (headingSlugs && !headingSlugs.has(anchor)) {
|
|
437
|
-
brokenLinks.
|
|
440
|
+
brokenLinks.add(path);
|
|
438
441
|
}
|
|
439
442
|
}
|
|
440
443
|
}
|
|
441
444
|
}
|
|
442
445
|
}
|
|
443
|
-
return
|
|
446
|
+
return Object.values(this.nodes)
|
|
447
|
+
.flatMap((node) => node.paths)
|
|
448
|
+
.filter((path) => brokenLinks.has(path) &&
|
|
449
|
+
(path.pagePaths.length === 0 ||
|
|
450
|
+
path.pagePaths.some((pagePath) => brokenLinks.has(pagePath))));
|
|
444
451
|
}
|
|
445
452
|
getBrokenRedirects() {
|
|
446
453
|
const nodeSet = new Set(Object.values(this.nodes).map((node) => node.toString()));
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { TableOfContentsSectionType } from '@mintlify/common';
|
|
2
2
|
import type { Root } from 'mdast';
|
|
3
3
|
import { Graph, Node } from '../graph.js';
|
|
4
|
+
import { type SnippetReferences } from './getSnippetReferences.js';
|
|
4
5
|
export declare const flattenTableOfContentsSlugs: (sections: TableOfContentsSectionType[]) => Set<string>;
|
|
5
6
|
export declare const extractComponentAnchorIds: (tree: Root) => Set<string>;
|
|
6
7
|
export declare const decorateGraphNodeFromPageContent: (graphNode: Node, content: string, options?: {
|
|
7
8
|
checkSnippets?: boolean;
|
|
8
|
-
}) => Promise<
|
|
9
|
+
}) => Promise<SnippetReferences>;
|
|
9
10
|
export declare const buildGraph: (repoPath?: string, options?: {
|
|
10
11
|
checkSnippets?: boolean;
|
|
11
12
|
/** SSRF-safe fetch override for hosted spec and overlay URLs; supplied by server environments. */
|
|
@@ -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,
|
|
10
|
+
import { cleanHeadingId, COMPONENTS_WITH_CLEANED_ID, COMPONENTS_WITHOUT_ID, coreRemark, generateParamFieldId, isMintIgnored, preprocessCustomHeadingIds, 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';
|
|
@@ -18,6 +18,8 @@ import { getLinkPaths, getPagePaths } from '../prebuild.js';
|
|
|
18
18
|
import { getNavigationHrefs } from './getNavigationHrefs.js';
|
|
19
19
|
import { getOpenApiPagePaths } from './getOpenApiPagePaths.js';
|
|
20
20
|
import { getRedirects } from './getRedirects.js';
|
|
21
|
+
import { getSnippetReferences } from './getSnippetReferences.js';
|
|
22
|
+
import { resolveSnippetLinksFromRenderingPages } from './resolveSnippetLinksFromRenderingPages.js';
|
|
21
23
|
export const flattenTableOfContentsSlugs = (sections) => {
|
|
22
24
|
const slugs = new Set();
|
|
23
25
|
for (const section of sections) {
|
|
@@ -61,11 +63,6 @@ export const decorateGraphNodeFromPageContent = (graphNode, content, options) =>
|
|
|
61
63
|
const mdxExtracts = {};
|
|
62
64
|
const visitLinks = () => {
|
|
63
65
|
return (tree) => {
|
|
64
|
-
if (options === null || options === void 0 ? void 0 : options.checkSnippets) {
|
|
65
|
-
for (const source of extractImportSources(tree)) {
|
|
66
|
-
graphNode.addPath(source);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
66
|
visit(tree, (node) => {
|
|
70
67
|
var _a, _b;
|
|
71
68
|
if (node.type === 'link' || node.type === 'image') {
|
|
@@ -100,12 +97,19 @@ export const decorateGraphNodeFromPageContent = (graphNode, content, options) =>
|
|
|
100
97
|
.use(remarkComponentIds)
|
|
101
98
|
.use(remarkExtractTableOfContents, mdxExtracts);
|
|
102
99
|
const tree = processor.parse(preprocessed);
|
|
100
|
+
const snippetReferences = getSnippetReferences(tree);
|
|
101
|
+
if (options === null || options === void 0 ? void 0 : options.checkSnippets) {
|
|
102
|
+
for (const source of snippetReferences.imported) {
|
|
103
|
+
graphNode.addPath(source, Wrapper.IMPORT);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
103
106
|
yield processor.run(tree);
|
|
104
107
|
const tocSlugs = mdxExtracts.tableOfContents
|
|
105
108
|
? flattenTableOfContentsSlugs(mdxExtracts.tableOfContents)
|
|
106
109
|
: new Set();
|
|
107
110
|
const componentIds = extractComponentAnchorIds(tree);
|
|
108
111
|
graphNode.headingSlugs = new Set([...tocSlugs, ...componentIds]);
|
|
112
|
+
return snippetReferences;
|
|
109
113
|
});
|
|
110
114
|
export const buildGraph = (repoPath, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
111
115
|
const baseDir = repoPath ? repoPath : process.cwd();
|
|
@@ -136,6 +140,7 @@ export const buildGraph = (repoPath, options) => __awaiter(void 0, void 0, void
|
|
|
136
140
|
}
|
|
137
141
|
const allSitePages = getPagePaths(baseDir, mintIgnoreGlobs);
|
|
138
142
|
const sitePages = allSitePages.filter((file) => !isMintIgnored(file, mintIgnoreGlobs));
|
|
143
|
+
const snippetReferencesByFile = new Map();
|
|
139
144
|
const BUILD_GRAPH_CONCURRENCY = 8;
|
|
140
145
|
let cursor = 0;
|
|
141
146
|
const processNext = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -148,9 +153,10 @@ export const buildGraph = (repoPath, options) => __awaiter(void 0, void 0, void
|
|
|
148
153
|
continue;
|
|
149
154
|
const fileContent = fs.readFileSync(path.join(baseDir, filePath)).toString();
|
|
150
155
|
try {
|
|
151
|
-
yield decorateGraphNodeFromPageContent(fileNode, fileContent, {
|
|
156
|
+
const snippetReferences = yield decorateGraphNodeFromPageContent(fileNode, fileContent, {
|
|
152
157
|
checkSnippets: options === null || options === void 0 ? void 0 : options.checkSnippets,
|
|
153
158
|
});
|
|
159
|
+
snippetReferencesByFile.set(filePath, snippetReferences);
|
|
154
160
|
}
|
|
155
161
|
catch (err) {
|
|
156
162
|
throw new Error(`Syntax error - Unable to parse ${filePath} - ${err}`);
|
|
@@ -158,6 +164,7 @@ export const buildGraph = (repoPath, options) => __awaiter(void 0, void 0, void
|
|
|
158
164
|
}
|
|
159
165
|
});
|
|
160
166
|
yield Promise.all(Array.from({ length: Math.min(BUILD_GRAPH_CONCURRENCY, sitePages.length) }, processNext));
|
|
167
|
+
resolveSnippetLinksFromRenderingPages(graph, snippetReferencesByFile);
|
|
161
168
|
return graph;
|
|
162
169
|
});
|
|
163
170
|
export const getBrokenInternalLinks = (repoPath, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { estreeIsProgram, isImportDeclaration, isMdxJsEsm, isMdxJsxFlowElement, } from '@mintlify/common';
|
|
2
|
+
import { visit } from 'unist-util-visit';
|
|
3
|
+
import { removeLeadingSlash } from '../prebuild.js';
|
|
4
|
+
const CONTENT_SPECIFIERS = ['ImportDefaultSpecifier', 'ImportNamespaceSpecifier'];
|
|
5
|
+
const LEGACY_SNIPPET_COMPONENT = 'Snippet';
|
|
6
|
+
const LEGACY_SNIPPET_DIRECTORY = '/_snippets/';
|
|
7
|
+
export const getSnippetReferences = (tree) => {
|
|
8
|
+
const imported = [];
|
|
9
|
+
const rendered = [];
|
|
10
|
+
const contentImports = [];
|
|
11
|
+
const componentNames = new Set();
|
|
12
|
+
visit(tree, (node) => {
|
|
13
|
+
var _a;
|
|
14
|
+
if (isMdxJsEsm(node) && estreeIsProgram(node)) {
|
|
15
|
+
for (const bodyChild of node.data.estree.body) {
|
|
16
|
+
if (!isImportDeclaration(bodyChild) || typeof bodyChild.source.value !== 'string')
|
|
17
|
+
continue;
|
|
18
|
+
const source = bodyChild.source.value;
|
|
19
|
+
imported.push(source);
|
|
20
|
+
for (const specifier of bodyChild.specifiers) {
|
|
21
|
+
if (CONTENT_SPECIFIERS.includes(specifier.type)) {
|
|
22
|
+
contentImports.push({ source, name: specifier.local.name });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (!isMdxJsxFlowElement(node) || !node.name)
|
|
29
|
+
return;
|
|
30
|
+
componentNames.add(node.name);
|
|
31
|
+
if (node.name !== LEGACY_SNIPPET_COMPONENT)
|
|
32
|
+
return;
|
|
33
|
+
const file = (_a = node.attributes.find((attribute) => attribute.type === 'mdxJsxAttribute' && attribute.name === 'file')) === null || _a === void 0 ? void 0 : _a.value;
|
|
34
|
+
if (typeof file === 'string')
|
|
35
|
+
rendered.push(LEGACY_SNIPPET_DIRECTORY + removeLeadingSlash(file));
|
|
36
|
+
});
|
|
37
|
+
for (const { source, name } of contentImports) {
|
|
38
|
+
if (componentNames.has(name))
|
|
39
|
+
rendered.push(source);
|
|
40
|
+
}
|
|
41
|
+
return { imported, rendered };
|
|
42
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { resolveImportPath } from '@mintlify/common';
|
|
2
|
+
import { sep as WINDOWS_SEPARATOR } from 'path/win32';
|
|
3
|
+
import { MdxPath, Wrapper } from '../graph.js';
|
|
4
|
+
import { removeLeadingSlash } from '../prebuild.js';
|
|
5
|
+
const SNIPPET_DIRECTORIES = ['snippets/', '_snippets/'];
|
|
6
|
+
const toKey = (file) => file.replaceAll(WINDOWS_SEPARATOR, '/');
|
|
7
|
+
const isPageRelativeLink = ({ originalPath, wrapper }) => wrapper !== Wrapper.IMPORT && (originalPath.startsWith('.') || originalPath.startsWith('#'));
|
|
8
|
+
export const resolveSnippetLinksFromRenderingPages = (graph, snippetReferencesByFile) => {
|
|
9
|
+
var _a, _b, _c;
|
|
10
|
+
const fileByKey = new Map([...snippetReferencesByFile.keys()].map((file) => [toKey(file), file]));
|
|
11
|
+
const resolveFiles = (sources, importingFile) => sources.flatMap((source) => {
|
|
12
|
+
const resolved = resolveImportPath(source, toKey(importingFile));
|
|
13
|
+
const file = resolved && fileByKey.get(removeLeadingSlash(resolved));
|
|
14
|
+
return file ? [file] : [];
|
|
15
|
+
});
|
|
16
|
+
const snippets = new Set();
|
|
17
|
+
for (const [file, { imported }] of snippetReferencesByFile) {
|
|
18
|
+
const key = toKey(file).toLowerCase();
|
|
19
|
+
if (SNIPPET_DIRECTORIES.some((directory) => key.startsWith(directory)))
|
|
20
|
+
snippets.add(file);
|
|
21
|
+
for (const importedFile of resolveFiles(imported, file))
|
|
22
|
+
snippets.add(importedFile);
|
|
23
|
+
}
|
|
24
|
+
const collectRenderedSnippets = (file, collected) => {
|
|
25
|
+
var _a, _b;
|
|
26
|
+
const rendered = (_b = (_a = snippetReferencesByFile.get(file)) === null || _a === void 0 ? void 0 : _a.rendered) !== null && _b !== void 0 ? _b : [];
|
|
27
|
+
for (const snippet of resolveFiles(rendered, file)) {
|
|
28
|
+
if (collected.has(snippet) || !snippets.has(snippet))
|
|
29
|
+
continue;
|
|
30
|
+
collected.add(snippet);
|
|
31
|
+
collectRenderedSnippets(snippet, collected);
|
|
32
|
+
}
|
|
33
|
+
return collected;
|
|
34
|
+
};
|
|
35
|
+
const renderedSnippetsByPage = new Map();
|
|
36
|
+
for (const file of snippetReferencesByFile.keys()) {
|
|
37
|
+
if (!snippets.has(file)) {
|
|
38
|
+
renderedSnippetsByPage.set(file, collectRenderedSnippets(file, new Set()));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
for (const [page, renderedSnippets] of renderedSnippetsByPage) {
|
|
42
|
+
const pageNode = graph.getNode(page);
|
|
43
|
+
if (!pageNode)
|
|
44
|
+
continue;
|
|
45
|
+
for (const snippet of renderedSnippets) {
|
|
46
|
+
const snippetNode = graph.getNode(snippet);
|
|
47
|
+
for (const link of (_a = snippetNode === null || snippetNode === void 0 ? void 0 : snippetNode.paths.filter(isPageRelativeLink)) !== null && _a !== void 0 ? _a : []) {
|
|
48
|
+
link.pagePaths.push(new MdxPath(link.originalPath, pageNode.relativeDir, pageNode.filename, link.wrapper));
|
|
49
|
+
}
|
|
50
|
+
for (const slug of (_b = snippetNode === null || snippetNode === void 0 ? void 0 : snippetNode.headingSlugs) !== null && _b !== void 0 ? _b : []) {
|
|
51
|
+
(_c = pageNode.headingSlugs) === null || _c === void 0 ? void 0 : _c.add(slug);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|