@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 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.push(path);
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.push(path);
440
+ brokenLinks.add(path);
438
441
  }
439
442
  }
440
443
  }
441
444
  }
442
445
  }
443
- return brokenLinks;
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<void>;
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, extractImportSources, generateParamFieldId, isMintIgnored, preprocessCustomHeadingIds, remarkComponentIds, remarkExtractTableOfContents, } from '@mintlify/common';
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,6 @@
1
+ import type { Root } from 'mdast';
2
+ export type SnippetReferences = {
3
+ imported: string[];
4
+ rendered: string[];
5
+ };
6
+ export declare const getSnippetReferences: (tree: Root) => SnippetReferences;
@@ -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,3 @@
1
+ import { Graph } from '../graph.js';
2
+ import type { SnippetReferences } from './getSnippetReferences.js';
3
+ export declare const resolveSnippetLinksFromRenderingPages: (graph: Graph, snippetReferencesByFile: Map<string, SnippetReferences>) => void;
@@ -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
+ };