@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 CHANGED
@@ -88,6 +88,7 @@ export declare class Graph {
88
88
  getBrokenInternalLinks(options?: {
89
89
  checkAnchors?: boolean;
90
90
  }): MdxPath[];
91
+ getExternalPathsByNode(): Map<string, MdxPath[]>;
91
92
  getAllInternalPaths(): string[];
92
93
  }
93
94
  export {};
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) => Promise<void>;
7
- /**
8
- * Get all broken internal links used in the site
9
- * Files matching .mintignore patterns are excluded from valid link targets,
10
- * so links pointing to ignored files will be reported as broken.
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
- // Precompute file resolutions before checking for broken links
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
  });