@mintlify/link-rot 3.0.1517 → 3.0.1518
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
CHANGED
|
@@ -76,6 +76,7 @@ export declare class Graph {
|
|
|
76
76
|
private edges;
|
|
77
77
|
private fileResolutionMap;
|
|
78
78
|
private redirectMap;
|
|
79
|
+
private wildcardRedirects;
|
|
79
80
|
private configRedirects;
|
|
80
81
|
private mintIgnoreGlobs;
|
|
81
82
|
private virtualNodes;
|
|
@@ -107,6 +108,12 @@ export declare class Graph {
|
|
|
107
108
|
getBrokenInternalLinks(options?: {
|
|
108
109
|
checkAnchors?: boolean;
|
|
109
110
|
}): MdxPath[];
|
|
111
|
+
private getPageCandidates;
|
|
112
|
+
private isPageRoute;
|
|
113
|
+
private resolvesToValidPath;
|
|
114
|
+
private reachesPage;
|
|
115
|
+
private getExactRedirectTarget;
|
|
116
|
+
private getRedirectTarget;
|
|
110
117
|
getBrokenRedirects(): {
|
|
111
118
|
source: string;
|
|
112
119
|
destination: string;
|
package/dist/graph.js
CHANGED
|
@@ -20,6 +20,7 @@ const decodeUrlComponent = (value) => {
|
|
|
20
20
|
return value;
|
|
21
21
|
}
|
|
22
22
|
};
|
|
23
|
+
const hasWildcard = (pattern) => /[:*]/.test(pattern);
|
|
23
24
|
export var Wrapper;
|
|
24
25
|
(function (Wrapper) {
|
|
25
26
|
Wrapper["MD"] = "md";
|
|
@@ -244,10 +245,11 @@ export class Edge {
|
|
|
244
245
|
export class Graph {
|
|
245
246
|
constructor(baseDir, mintIgnoreGlobs = []) {
|
|
246
247
|
this.baseDir = baseDir;
|
|
247
|
-
this.nodes =
|
|
248
|
+
this.nodes = Object.create(null);
|
|
248
249
|
this.edges = [];
|
|
249
250
|
this.fileResolutionMap = new Map();
|
|
250
251
|
this.redirectMap = new Map();
|
|
252
|
+
this.wildcardRedirects = [];
|
|
251
253
|
this.configRedirects = [];
|
|
252
254
|
this.mintIgnoreGlobs = [];
|
|
253
255
|
this.virtualNodes = new Set();
|
|
@@ -277,8 +279,12 @@ export class Graph {
|
|
|
277
279
|
this.configRedirects = redirects;
|
|
278
280
|
this.redirectMap.clear();
|
|
279
281
|
for (const redirect of redirects) {
|
|
280
|
-
|
|
282
|
+
const source = normalizePath(redirect.source);
|
|
283
|
+
if (!this.redirectMap.has(source)) {
|
|
284
|
+
this.redirectMap.set(source, normalizePath(redirect.destination));
|
|
285
|
+
}
|
|
281
286
|
}
|
|
287
|
+
this.wildcardRedirects = [...this.redirectMap].filter(([source]) => hasWildcard(source));
|
|
282
288
|
}
|
|
283
289
|
matchesWildcardPattern(path, pattern) {
|
|
284
290
|
// Escape special regex characters except : and *
|
|
@@ -435,7 +441,6 @@ export class Graph {
|
|
|
435
441
|
}
|
|
436
442
|
const brokenLinks = new Set();
|
|
437
443
|
const nodeSet = new Set(Object.values(this.nodes).map((node) => node.toString()));
|
|
438
|
-
const wildcardRedirects = [...this.redirectMap].filter(([source]) => /[:*]/.test(source));
|
|
439
444
|
for (const node of Object.values(this.nodes)) {
|
|
440
445
|
for (const path of node.paths.flatMap((path) => [path, ...path.pagePaths])) {
|
|
441
446
|
if (path.pathType === PathType.INTERNAL) {
|
|
@@ -443,21 +448,7 @@ export class Graph {
|
|
|
443
448
|
const resolvedFiles = this.fileResolutionMap.get(pathString);
|
|
444
449
|
const hasExistingFile = (resolvedFiles && [...resolvedFiles].some((file) => nodeSet.has(file))) ||
|
|
445
450
|
this.virtualNodes.has(pathString);
|
|
446
|
-
|
|
447
|
-
let hasValidRedirect = redirectDestination &&
|
|
448
|
-
(nodeSet.has(`${redirectDestination}.mdx`) || nodeSet.has(redirectDestination));
|
|
449
|
-
if (!hasExistingFile && !hasValidRedirect) {
|
|
450
|
-
for (const [sourcePattern, destinationPattern] of wildcardRedirects) {
|
|
451
|
-
if (this.matchesWildcardPattern(pathString, sourcePattern)) {
|
|
452
|
-
redirectDestination = this.applyWildcardRedirect(pathString, sourcePattern, destinationPattern);
|
|
453
|
-
hasValidRedirect =
|
|
454
|
-
nodeSet.has(`${redirectDestination}.mdx`) || nodeSet.has(redirectDestination);
|
|
455
|
-
if (hasValidRedirect)
|
|
456
|
-
break;
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
if (!hasExistingFile && !hasValidRedirect) {
|
|
451
|
+
if (!hasExistingFile && !this.reachesPage(pathString)) {
|
|
461
452
|
brokenLinks.add(path);
|
|
462
453
|
}
|
|
463
454
|
else if ((options === null || options === void 0 ? void 0 : options.checkAnchors) && path.anchorLink) {
|
|
@@ -476,58 +467,75 @@ export class Graph {
|
|
|
476
467
|
(path.pagePaths.length === 0 ||
|
|
477
468
|
path.pagePaths.some((pagePath) => brokenLinks.has(pagePath))));
|
|
478
469
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
return true;
|
|
512
|
-
}
|
|
513
|
-
const chained = this.redirectMap.get(normalized);
|
|
514
|
-
if (chained !== undefined)
|
|
515
|
-
return resolvesToValidPath(chained, visited);
|
|
516
|
-
for (const [sourcePattern, destinationPattern] of this.redirectMap.entries()) {
|
|
517
|
-
if (hasWildcard(sourcePattern) && this.matchesWildcardPattern(normalized, sourcePattern)) {
|
|
518
|
-
const resolved = this.applyWildcardRedirect(normalized, sourcePattern, destinationPattern);
|
|
519
|
-
return resolvesToValidPath(resolved, visited);
|
|
520
|
-
}
|
|
521
|
-
}
|
|
470
|
+
getPageCandidates(normalized) {
|
|
471
|
+
// normalizePath('/') yields '.' — resolve lookups against node labels (which use
|
|
472
|
+
// path.join, so `join('.', 'index.mdx') === 'index.mdx'`) rather than naive
|
|
473
|
+
// string concatenation that would produce './index.mdx' and never match.
|
|
474
|
+
return normalized === '.'
|
|
475
|
+
? ['index.mdx', 'index.md']
|
|
476
|
+
: [
|
|
477
|
+
`${normalized}.mdx`,
|
|
478
|
+
`${normalized}.md`,
|
|
479
|
+
join(normalized, 'index.mdx'),
|
|
480
|
+
join(normalized, 'index.md'),
|
|
481
|
+
];
|
|
482
|
+
}
|
|
483
|
+
isPageRoute(normalized) {
|
|
484
|
+
return (this.getPageCandidates(normalized).some((candidate) => this.nodes[candidate]) ||
|
|
485
|
+
this.virtualNodes.has(normalized));
|
|
486
|
+
}
|
|
487
|
+
resolvesToValidPath(rawDestination, visited = new Set()) {
|
|
488
|
+
if (URL.canParse(rawDestination))
|
|
489
|
+
return true;
|
|
490
|
+
const pathOnly = removeSelectors(rawDestination);
|
|
491
|
+
if (!pathOnly)
|
|
492
|
+
return true;
|
|
493
|
+
// Wildcard path destinations can't be validated as concrete paths — treat as valid,
|
|
494
|
+
// matching the top-level skip semantics. Checked after stripping so `:` or `*` in
|
|
495
|
+
// the fragment/query doesn't cause a false skip.
|
|
496
|
+
if (hasWildcard(pathOnly))
|
|
497
|
+
return true;
|
|
498
|
+
return this.reachesPage(normalizePath(pathOnly), visited);
|
|
499
|
+
}
|
|
500
|
+
reachesPage(normalized, visited = new Set()) {
|
|
501
|
+
if (visited.has(normalized))
|
|
522
502
|
return false;
|
|
523
|
-
|
|
503
|
+
visited.add(normalized);
|
|
504
|
+
const exact = this.getExactRedirectTarget(normalized);
|
|
505
|
+
if (exact !== undefined)
|
|
506
|
+
return this.resolvesToValidPath(exact, visited);
|
|
507
|
+
if ((normalized !== '.' && this.nodes[normalized]) || this.isPageRoute(normalized))
|
|
508
|
+
return true;
|
|
509
|
+
const redirected = this.getRedirectTarget(normalized);
|
|
510
|
+
return redirected !== undefined && this.resolvesToValidPath(redirected, visited);
|
|
511
|
+
}
|
|
512
|
+
getExactRedirectTarget(normalized) {
|
|
513
|
+
return this.redirectMap.get(normalized);
|
|
514
|
+
}
|
|
515
|
+
getRedirectTarget(normalized) {
|
|
516
|
+
const exact = this.getExactRedirectTarget(normalized);
|
|
517
|
+
if (exact !== undefined)
|
|
518
|
+
return exact;
|
|
519
|
+
for (const [sourcePattern, destinationPattern] of this.wildcardRedirects) {
|
|
520
|
+
if (this.matchesWildcardPattern(normalized, sourcePattern)) {
|
|
521
|
+
return this.applyWildcardRedirect(normalized, sourcePattern, destinationPattern);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
return undefined;
|
|
525
|
+
}
|
|
526
|
+
getBrokenRedirects() {
|
|
524
527
|
const broken = [];
|
|
528
|
+
const seenSources = new Set();
|
|
525
529
|
for (const { source, destination } of this.configRedirects) {
|
|
530
|
+
const sourcePath = normalizePath(source);
|
|
531
|
+
if (seenSources.has(sourcePath))
|
|
532
|
+
continue;
|
|
533
|
+
seenSources.add(sourcePath);
|
|
526
534
|
if (URL.canParse(destination))
|
|
527
535
|
continue;
|
|
528
536
|
if (hasWildcard(removeSelectors(destination)))
|
|
529
537
|
continue;
|
|
530
|
-
if (!resolvesToValidPath(destination
|
|
538
|
+
if (!this.resolvesToValidPath(destination)) {
|
|
531
539
|
broken.push({ source, destination });
|
|
532
540
|
}
|
|
533
541
|
}
|
|
@@ -7,7 +7,12 @@ 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 { parseGraphqlSelector, potentiallyParseOpenApiString } from '@mintlify/common';
|
|
10
11
|
import { readResolvedConfigJson } from './readConfig.js';
|
|
12
|
+
const isPagePath = (entry) => typeof entry === 'string' &&
|
|
13
|
+
entry !== '' &&
|
|
14
|
+
potentiallyParseOpenApiString(entry) === undefined &&
|
|
15
|
+
parseGraphqlSelector(entry) === undefined;
|
|
11
16
|
const collectHrefs = (obj) => {
|
|
12
17
|
if (typeof obj !== 'object' || obj === null)
|
|
13
18
|
return [];
|
|
@@ -18,6 +23,15 @@ const collectHrefs = (obj) => {
|
|
|
18
23
|
if (typeof record.href === 'string') {
|
|
19
24
|
hrefs.push(record.href);
|
|
20
25
|
}
|
|
26
|
+
if (isPagePath(record.root)) {
|
|
27
|
+
hrefs.push(record.root.startsWith('/') ? record.root : `/${record.root}`);
|
|
28
|
+
}
|
|
29
|
+
if (Array.isArray(record.pages)) {
|
|
30
|
+
for (const entry of record.pages) {
|
|
31
|
+
if (isPagePath(entry))
|
|
32
|
+
hrefs.push(entry.startsWith('/') ? entry : `/${entry}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
21
35
|
for (const value of Object.values(record)) {
|
|
22
36
|
hrefs.push(...collectHrefs(value));
|
|
23
37
|
}
|