@mintlify/link-rot 3.0.1450 → 3.0.1451
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 +10 -0
- package/dist/graph.js +43 -16
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/static-checking/getBrokenInternalLinks.d.ts +2 -0
- package/dist/static-checking/getBrokenInternalLinks.js +9 -3
- package/dist/static-checking/resolveSnippetLinksFromRenderingPages.d.ts +10 -0
- package/dist/static-checking/resolveSnippetLinksFromRenderingPages.js +35 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +6 -6
package/dist/graph.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ParsedPath } from 'path';
|
|
2
|
+
import type { SnippetReferences } from './static-checking/getSnippetReferences.js';
|
|
2
3
|
declare enum PathType {
|
|
3
4
|
INTERNAL = "internal",
|
|
4
5
|
EXTERNAL = "external",
|
|
@@ -78,6 +79,8 @@ export declare class Graph {
|
|
|
78
79
|
private configRedirects;
|
|
79
80
|
private mintIgnoreGlobs;
|
|
80
81
|
private virtualNodes;
|
|
82
|
+
/** Snippet imports and renders of every page that parsed, keyed by file. */
|
|
83
|
+
snippetReferencesByFile: Map<string, SnippetReferences>;
|
|
81
84
|
constructor(baseDir: string, mintIgnoreGlobs?: string[]);
|
|
82
85
|
addNode(label: string): Node;
|
|
83
86
|
addNodes(labels: string[]): void;
|
|
@@ -90,9 +93,16 @@ export declare class Graph {
|
|
|
90
93
|
applyWildcardRedirect(path: string, sourcePattern: string, destinationPattern: string): string;
|
|
91
94
|
addAliasNodes(): void;
|
|
92
95
|
getNode(label: string): Node | undefined;
|
|
96
|
+
/**
|
|
97
|
+
* Files whose headings the anchor links in `files` are checked against: each candidate
|
|
98
|
+
* getHeadingSlugsForPath would try, up to the first one that parsed.
|
|
99
|
+
*/
|
|
100
|
+
getAnchorTargetFiles(files: Set<string>): Set<string>;
|
|
93
101
|
private addEdge;
|
|
94
102
|
addEdgesBetweenNodes(): void;
|
|
95
103
|
precomputeFileResolutions(): void;
|
|
104
|
+
/** Files an anchor on `pathString` may be checked against, in the order they are tried. */
|
|
105
|
+
private getHeadingSlugCandidates;
|
|
96
106
|
private getHeadingSlugsForPath;
|
|
97
107
|
getBrokenInternalLinks(options?: {
|
|
98
108
|
checkAnchors?: boolean;
|
package/dist/graph.js
CHANGED
|
@@ -251,6 +251,8 @@ export class Graph {
|
|
|
251
251
|
this.configRedirects = [];
|
|
252
252
|
this.mintIgnoreGlobs = [];
|
|
253
253
|
this.virtualNodes = new Set();
|
|
254
|
+
/** Snippet imports and renders of every page that parsed, keyed by file. */
|
|
255
|
+
this.snippetReferencesByFile = new Map();
|
|
254
256
|
this.baseDir = resolve(baseDir);
|
|
255
257
|
this.mintIgnoreGlobs = mintIgnoreGlobs;
|
|
256
258
|
}
|
|
@@ -336,6 +338,34 @@ export class Graph {
|
|
|
336
338
|
getNode(label) {
|
|
337
339
|
return this.nodes[label];
|
|
338
340
|
}
|
|
341
|
+
/**
|
|
342
|
+
* Files whose headings the anchor links in `files` are checked against: each candidate
|
|
343
|
+
* getHeadingSlugsForPath would try, up to the first one that parsed.
|
|
344
|
+
*/
|
|
345
|
+
getAnchorTargetFiles(files) {
|
|
346
|
+
if (this.fileResolutionMap.size === 0) {
|
|
347
|
+
this.precomputeFileResolutions();
|
|
348
|
+
}
|
|
349
|
+
const nodeSet = new Set(Object.keys(this.nodes));
|
|
350
|
+
const targets = new Set();
|
|
351
|
+
for (const node of Object.values(this.nodes)) {
|
|
352
|
+
for (const path of node.paths.flatMap((path) => [path, ...path.pagePaths])) {
|
|
353
|
+
if (path.pathType !== PathType.INTERNAL || !path.anchorLink)
|
|
354
|
+
continue;
|
|
355
|
+
if (!files.has(join(path.relativeDir, path.filename)))
|
|
356
|
+
continue;
|
|
357
|
+
for (const label of this.getHeadingSlugCandidates(path.toString(), nodeSet)) {
|
|
358
|
+
const candidate = this.nodes[label];
|
|
359
|
+
if (!candidate)
|
|
360
|
+
continue;
|
|
361
|
+
targets.add(label);
|
|
362
|
+
if (candidate.headingSlugs !== null)
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return targets;
|
|
368
|
+
}
|
|
339
369
|
addEdge(source, target, edgeType = EdgeType.CONTENT) {
|
|
340
370
|
const newEdge = new Edge(source, target, edgeType);
|
|
341
371
|
const existingEdge = this.edges.find((edge) => {
|
|
@@ -382,24 +412,21 @@ export class Graph {
|
|
|
382
412
|
});
|
|
383
413
|
});
|
|
384
414
|
}
|
|
385
|
-
|
|
415
|
+
/** Files an anchor on `pathString` may be checked against, in the order they are tried. */
|
|
416
|
+
getHeadingSlugCandidates(pathString, nodeSet) {
|
|
386
417
|
var _a;
|
|
387
|
-
const resolvedFiles = this.fileResolutionMap.get(pathString);
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
418
|
+
const resolvedFiles = [...((_a = this.fileResolutionMap.get(pathString)) !== null && _a !== void 0 ? _a : [])].filter((file) => nodeSet.has(file));
|
|
419
|
+
// join, like node labels, so the index fallback also matches on Windows
|
|
420
|
+
const indexMdx = join(pathString, 'index.mdx');
|
|
421
|
+
const indexFile = this.nodes[indexMdx] ? indexMdx : join(pathString, 'index.md');
|
|
422
|
+
return [...resolvedFiles, pathString, indexFile];
|
|
423
|
+
}
|
|
424
|
+
getHeadingSlugsForPath(pathString, nodeSet) {
|
|
425
|
+
for (const label of this.getHeadingSlugCandidates(pathString, nodeSet)) {
|
|
426
|
+
const node = this.nodes[label];
|
|
427
|
+
if (node && node.headingSlugs !== null)
|
|
428
|
+
return node.headingSlugs;
|
|
396
429
|
}
|
|
397
|
-
const directNode = this.nodes[pathString];
|
|
398
|
-
if (directNode && directNode.headingSlugs !== null)
|
|
399
|
-
return directNode.headingSlugs;
|
|
400
|
-
const indexNode = (_a = this.nodes[`${pathString}/index.mdx`]) !== null && _a !== void 0 ? _a : this.nodes[`${pathString}/index.md`];
|
|
401
|
-
if (indexNode && indexNode.headingSlugs !== null)
|
|
402
|
-
return indexNode.headingSlugs;
|
|
403
430
|
return undefined;
|
|
404
431
|
}
|
|
405
432
|
getBrokenInternalLinks(options) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { buildGraph, getBrokenInternalLinks } from './static-checking/getBrokenInternalLinks.js';
|
|
2
2
|
export { getBrokenExternalLinks } from './static-checking/getBrokenExternalLinks.js';
|
|
3
3
|
export type { ExternalLinkResult } from './static-checking/getBrokenExternalLinks.js';
|
|
4
|
+
export { getFilesLinksDependOn } from './static-checking/resolveSnippetLinksFromRenderingPages.js';
|
|
4
5
|
export { getBrokenRedirects } from './static-checking/getBrokenRedirects.js';
|
|
5
6
|
export { renameFilesAndUpdateLinksInContent } from './link-renaming/renameFileAndUpdateLinksInContent.js';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { buildGraph, getBrokenInternalLinks } from './static-checking/getBrokenInternalLinks.js';
|
|
2
2
|
export { getBrokenExternalLinks } from './static-checking/getBrokenExternalLinks.js';
|
|
3
|
+
export { getFilesLinksDependOn } from './static-checking/resolveSnippetLinksFromRenderingPages.js';
|
|
3
4
|
export { getBrokenRedirects } from './static-checking/getBrokenRedirects.js';
|
|
4
5
|
export { renameFilesAndUpdateLinksInContent } from './link-renaming/renameFileAndUpdateLinksInContent.js';
|
|
@@ -11,6 +11,8 @@ export declare const buildGraph: (repoPath?: string, options?: {
|
|
|
11
11
|
checkSnippets?: boolean;
|
|
12
12
|
/** SSRF-safe fetch override for hosted spec and overlay URLs; supplied by server environments. */
|
|
13
13
|
fetchRemoteDocument?: (url: string) => Promise<unknown>;
|
|
14
|
+
/** Report a page that fails to parse and keep scanning; without it the first failure throws. */
|
|
15
|
+
onParseError?: (filePath: string, error: Error) => void;
|
|
14
16
|
}) => Promise<Graph>;
|
|
15
17
|
export declare const getBrokenInternalLinks: (repoPath?: string, options?: {
|
|
16
18
|
checkAnchors?: boolean;
|
|
@@ -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, preprocessCustomHeadingIds, remarkComponentIds, remarkExtractTableOfContents, } from '@mintlify/common';
|
|
10
|
+
import { cleanHeadingId, COMPONENTS_WITH_CLEANED_ID, COMPONENTS_WITHOUT_ID, coreRemark, generateParamFieldId, isMintIgnored, preprocessCustomHeadingIds, preprocessMdxFragmentCode, 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';
|
|
@@ -90,7 +90,9 @@ export const decorateGraphNodeFromPageContent = (graphNode, content, options) =>
|
|
|
90
90
|
return tree;
|
|
91
91
|
};
|
|
92
92
|
};
|
|
93
|
-
|
|
93
|
+
// Same pre-parse escaping as prebuild, so code fences inside `<MDX>` fragments
|
|
94
|
+
// (`<project-root>`, `{id}`) aren't read as JSX and the page parses like it renders
|
|
95
|
+
const preprocessed = preprocessCustomHeadingIds(preprocessMdxFragmentCode(content));
|
|
94
96
|
const processor = coreRemark()
|
|
95
97
|
.use(remarkSmartypants)
|
|
96
98
|
.use(visitLinks)
|
|
@@ -159,12 +161,16 @@ export const buildGraph = (repoPath, options) => __awaiter(void 0, void 0, void
|
|
|
159
161
|
snippetReferencesByFile.set(filePath, snippetReferences);
|
|
160
162
|
}
|
|
161
163
|
catch (err) {
|
|
162
|
-
|
|
164
|
+
const parseError = new Error(`Syntax error - Unable to parse ${filePath} - ${err}`);
|
|
165
|
+
if (!(options === null || options === void 0 ? void 0 : options.onParseError))
|
|
166
|
+
throw parseError;
|
|
167
|
+
options.onParseError(filePath, parseError);
|
|
163
168
|
}
|
|
164
169
|
}
|
|
165
170
|
});
|
|
166
171
|
yield Promise.all(Array.from({ length: Math.min(BUILD_GRAPH_CONCURRENCY, sitePages.length) }, processNext));
|
|
167
172
|
resolveSnippetLinksFromRenderingPages(graph, snippetReferencesByFile);
|
|
173
|
+
graph.snippetReferencesByFile = snippetReferencesByFile;
|
|
168
174
|
return graph;
|
|
169
175
|
});
|
|
170
176
|
export const getBrokenInternalLinks = (repoPath, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -1,3 +1,13 @@
|
|
|
1
1
|
import { Graph } from '../graph.js';
|
|
2
2
|
import type { SnippetReferences } from './getSnippetReferences.js';
|
|
3
3
|
export declare const resolveSnippetLinksFromRenderingPages: (graph: Graph, snippetReferencesByFile: Map<string, SnippetReferences>) => void;
|
|
4
|
+
/**
|
|
5
|
+
* Files that links in `files` can't be checked without: the pages their anchors point at and the
|
|
6
|
+
* snippets those pages and `files` render (nested ones too), whose headings and links are merged
|
|
7
|
+
* into the rendering page. A parse failure in any of these leaves some of those links unchecked.
|
|
8
|
+
* Paths use `/` separators.
|
|
9
|
+
*/
|
|
10
|
+
export declare const getFilesLinksDependOn: (graph: Graph, files: Set<string>, options: {
|
|
11
|
+
checkAnchors?: boolean;
|
|
12
|
+
checkSnippets?: boolean;
|
|
13
|
+
}) => Set<string>;
|
|
@@ -53,3 +53,38 @@ export const resolveSnippetLinksFromRenderingPages = (graph, snippetReferencesBy
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
};
|
|
56
|
+
/**
|
|
57
|
+
* Files that links in `files` can't be checked without: the pages their anchors point at and the
|
|
58
|
+
* snippets those pages and `files` render (nested ones too), whose headings and links are merged
|
|
59
|
+
* into the rendering page. A parse failure in any of these leaves some of those links unchecked.
|
|
60
|
+
* Paths use `/` separators.
|
|
61
|
+
*/
|
|
62
|
+
export const getFilesLinksDependOn = (graph, files, options) => {
|
|
63
|
+
const dependencies = new Set();
|
|
64
|
+
if (!options.checkAnchors && !options.checkSnippets)
|
|
65
|
+
return dependencies;
|
|
66
|
+
const anchorTargets = options.checkAnchors ? [...graph.getAnchorTargetFiles(files)] : [];
|
|
67
|
+
for (const file of anchorTargets)
|
|
68
|
+
dependencies.add(toKey(file));
|
|
69
|
+
const fileByKey = new Map([...graph.snippetReferencesByFile.keys()].map((file) => [toKey(file), file]));
|
|
70
|
+
const visited = new Set();
|
|
71
|
+
const visit = (key) => {
|
|
72
|
+
var _a, _b;
|
|
73
|
+
if (visited.has(key))
|
|
74
|
+
return;
|
|
75
|
+
visited.add(key);
|
|
76
|
+
const file = fileByKey.get(key);
|
|
77
|
+
const rendered = file ? ((_b = (_a = graph.snippetReferencesByFile.get(file)) === null || _a === void 0 ? void 0 : _a.rendered) !== null && _b !== void 0 ? _b : []) : [];
|
|
78
|
+
for (const source of rendered) {
|
|
79
|
+
const resolved = resolveImportPath(source, key);
|
|
80
|
+
if (!resolved)
|
|
81
|
+
continue;
|
|
82
|
+
const snippetKey = removeLeadingSlash(resolved);
|
|
83
|
+
dependencies.add(snippetKey);
|
|
84
|
+
visit(snippetKey);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
for (const file of [...files, ...anchorTargets])
|
|
88
|
+
visit(toKey(file));
|
|
89
|
+
return dependencies;
|
|
90
|
+
};
|