@mintlify/link-rot 3.0.1449 → 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 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
- getHeadingSlugsForPath(pathString, nodeSet) {
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
- if (resolvedFiles) {
389
- for (const file of resolvedFiles) {
390
- if (nodeSet.has(file)) {
391
- const node = this.nodes[file];
392
- if (node && node.headingSlugs !== null)
393
- return node.headingSlugs;
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
- const preprocessed = preprocessCustomHeadingIds(content);
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
- throw new Error(`Syntax error - Unable to parse ${filePath} - ${err}`);
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
+ };