@mintlify/scraping 4.0.1190 → 4.0.1191

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mintlify/scraping",
3
- "version": "4.0.1190",
3
+ "version": "4.0.1191",
4
4
  "description": "Scrape documentation frameworks to Mintlify docs",
5
5
  "engines": {
6
6
  "node": ">=18.0.0"
@@ -87,5 +87,5 @@
87
87
  "optional": true
88
88
  }
89
89
  },
90
- "gitHead": "6bc228d07c5f946e1ede787856918963e868b3b8"
90
+ "gitHead": "78b82add935dc4b66178be74d41b67a59712367c"
91
91
  }
@@ -1,5 +1,5 @@
1
1
  import fse from 'fs-extra';
2
- import type { Heading, ImageReference, LinkReference, Paragraph, Root as MdastRoot } from 'mdast';
2
+ import type { ImageReference, LinkReference, Paragraph, Root as MdastRoot } from 'mdast';
3
3
  import path from 'node:path';
4
4
  import { visit } from 'unist-util-visit';
5
5
  import { z } from 'zod';
@@ -22,6 +22,7 @@ import {
22
22
  truncate,
23
23
  type LinkedToken,
24
24
  } from '../mdx.js';
25
+ import { parseSdkReadme, unparseableMarkdown } from '../readme.js';
25
26
  import type { SdkNavGroup, SdkPage, SdkPageTarget, SdkReference } from '../types.js';
26
27
 
27
28
  const SUPPORTED_SCHEMA_VERSION = 1;
@@ -29,7 +30,6 @@ const PACKAGE_PAGE = 'package-overview';
29
30
  const MEMBER_HEADING_DEPTH = 3;
30
31
  const MIN_COMMENT_HEADING_DEPTH = 4;
31
32
  const MAX_TYPE_ATTR = 80;
32
- const MIN_README_HEADING_DEPTH = 2;
33
33
  const GITHUB_MODULE = /^github\.com\/([^/]+)\/([^/]+)((?:\/[^/]+)*?)(?:\/v(?:[2-9]|[1-9]\d+))?$/;
34
34
 
35
35
  const MAX_TEXT = 1 << 20;
@@ -450,13 +450,8 @@ class GoRenderer {
450
450
  // Renders a README the way pkg.go.dev shows it, minus the sentence the page
451
451
  // shows as its description (see takeLeadingSentence).
452
452
  private readme(markdown: string, synopsis?: string): { intro: string; description?: string } {
453
- if (unparseable(markdown)) return { intro: codeBlock(markdown, 'text') };
454
- const tree = parseMarkdown(markdown);
455
- const first = tree.children[0];
456
- if (first?.type === 'heading' && first.depth === 1) tree.children.shift();
457
- visit(tree, 'heading', (node: Heading) => {
458
- node.depth = Math.max(node.depth, MIN_README_HEADING_DEPTH) as Heading['depth'];
459
- });
453
+ if (unparseableMarkdown(markdown)) return { intro: codeBlock(markdown, 'text') };
454
+ const tree = parseSdkReadme(markdown);
460
455
  const imageDefinitions = new Set<string>();
461
456
  const linkDefinitions = new Set<string>();
462
457
  visit(tree, 'imageReference', (node: ImageReference) => {
@@ -777,7 +772,7 @@ class GoRenderer {
777
772
  }
778
773
 
779
774
  private parseDoc(markdown: string): GoDoc {
780
- if (unparseable(markdown)) {
775
+ if (unparseableMarkdown(markdown)) {
781
776
  return {
782
777
  tree: { type: 'root', children: [{ type: 'code', lang: 'text', value: markdown }] },
783
778
  };
@@ -906,40 +901,6 @@ function dropLeadingSentence(paragraph: Paragraph): void {
906
901
  paragraph.children = [];
907
902
  }
908
903
 
909
- // Text past either limit is rendered as a text block instead of Markdown.
910
- // micromark is quadratic in unclosed brackets (80k `[a](` takes about 45 s)
911
- // and in balanced link and emphasis markup (40k nested brackets, `[]` pairs,
912
- // `[a]` references, or `*a` runs take 5 to 36 s), and remark-stringify is
913
- // quadratic in the characters it escapes (40k `<` or `{` take 0.7 s, minutes
914
- // at the 1 MiB cap). At 10k either costs well under 2 s. The largest real doc
915
- // seen (openai-go's README, 60 KB) has about 1,500 such characters and no more
916
- // than 3 unclosed brackets.
917
- const MAX_UNCLOSED = 1000;
918
- const MAX_MARKUP = 10000;
919
- const MARKUP = new Set(['[', ']', '*', '_', '<', '>', '{', '}', '`', '&']);
920
-
921
- function unparseable(markdown: string): boolean {
922
- let square = 0;
923
- let round = 0;
924
- let unclosed = 0;
925
- let markup = 0;
926
- for (let i = 0; i < markdown.length; i += 1) {
927
- const char = markdown[i];
928
- if (char !== undefined && MARKUP.has(char)) markup += 1;
929
- if (char === '\\') i += 1;
930
- else if (char === '[') square += 1;
931
- else if (char === '(') round += 1;
932
- else if (char === ']') {
933
- if (square > 0) square -= 1;
934
- else unclosed += 1;
935
- } else if (char === ')') {
936
- if (round > 0) round -= 1;
937
- else unclosed += 1;
938
- }
939
- }
940
- return square + round + unclosed > MAX_UNCLOSED || markup > MAX_MARKUP;
941
- }
942
-
943
904
  const MAX_DESCRIPTION = 160;
944
905
 
945
906
  // The text up to and including the first full stop that ends a sentence, or
@@ -2,8 +2,9 @@ import type { SdkSymbolKind } from '@mintlify/models';
2
2
  import fse from 'fs-extra';
3
3
  import path from 'node:path';
4
4
 
5
- import { escapeMdxText, markdownToMdx } from '../convert.js';
6
- import { inlineCode, linkedCode, responseField, truncate } from '../mdx.js';
5
+ import { escapeMdxText, markdownToMdx, rewriteMdastLinks, stringifyMdast } from '../convert.js';
6
+ import { inlineCode, linkedCode, responseField, safeHref, truncate } from '../mdx.js';
7
+ import { parseSdkReadme } from '../readme.js';
7
8
  import type { SdkNavGroup, SdkPage, SdkPageTarget, SdkReference } from '../types.js';
8
9
 
9
10
  const KIND = {
@@ -60,6 +61,7 @@ type TypedocNode = {
60
61
  name: string;
61
62
  kind: number;
62
63
  comment?: Comment;
64
+ readme?: CommentPart[];
63
65
  children?: TypedocNode[];
64
66
  groups?: TypedocGroup[];
65
67
  categories?: TypedocCategory[];
@@ -197,7 +199,48 @@ export async function convertTypedoc(sourcePath: string): Promise<SdkReference>
197
199
  groups.push(...moduleGroups.map((group) => ({ ...group, group: `${prefix}${group.group}` })));
198
200
  }
199
201
 
200
- return { pages, groups };
202
+ const overviewPages: SdkPage[] = [];
203
+ const overviewGroups: SdkNavGroup[] = [];
204
+ const reservedSlugs = new Set(pages.map((page) => page.slug.toLowerCase()));
205
+ const containers = [project, ...modules.filter((module) => module !== project)];
206
+ const overviews: { node: TypedocNode; slug: string }[] = [];
207
+ for (const container of containers) {
208
+ if (!container.readme?.some((part) => part.text.trim())) continue;
209
+ const isProject = container === project;
210
+ const prefix = isProject ? '' : slugify(container.name).replace(/^\.+$/, 'item');
211
+ const base = path.posix.join(prefix, 'package-overview');
212
+ let slug = base;
213
+ let suffix = 2;
214
+ while (reservedSlugs.has(slug.toLowerCase())) {
215
+ slug = `${base}-${suffix}`;
216
+ suffix += 1;
217
+ }
218
+ reservedSlugs.add(slug.toLowerCase());
219
+ slugById.set(container.id, slug);
220
+ overviews.push({ node: container, slug });
221
+ }
222
+ for (const { node: container, slug } of overviews) {
223
+ const isProject = container === project;
224
+ const declarationSlugs = new Set(
225
+ collectDeclarations(container).map(({ node }) => slugById.get(node.id))
226
+ );
227
+ const indexPages = pages.filter(
228
+ (page) => !page.target?.parent && (isProject || declarationSlugs.has(page.slug))
229
+ );
230
+ const overview = renderer.renderOverview(
231
+ container,
232
+ slug,
233
+ indexPages,
234
+ isProject ? undefined : project.name
235
+ );
236
+ overviewPages.push(overview);
237
+ overviewGroups.push({
238
+ group: isProject ? 'Overview' : `${container.name} Overview`,
239
+ pages: [slug],
240
+ });
241
+ }
242
+
243
+ return { pages: [...overviewPages, ...pages], groups: [...overviewGroups, ...groups] };
201
244
  }
202
245
 
203
246
  function hasCustomLayout(container: TypedocNode): boolean {
@@ -296,6 +339,31 @@ function slugify(name: string): string {
296
339
  class TypedocRenderer {
297
340
  constructor(private slugById: Map<number, string>) {}
298
341
 
342
+ renderOverview(node: TypedocNode, slug: string, pages: SdkPage[], namespace?: string): SdkPage {
343
+ const tree = parseSdkReadme(this.partsMarkdown(node.readme ?? []));
344
+ rewriteMdastLinks(tree, (link) => safeHref(link.url));
345
+ const lines: string[] = [];
346
+ for (const section of SECTIONS) {
347
+ const entries = pages.filter((page) => page.target?.kind === section.symbolKind);
348
+ if (entries.length === 0) continue;
349
+ lines.push(
350
+ `## ${section.group}`,
351
+ entries.map((page) => `- [${inlineCode(page.title)}](/${page.slug})`).join('\n')
352
+ );
353
+ }
354
+ return {
355
+ slug,
356
+ title: node.name,
357
+ intro: stringifyMdast(tree),
358
+ content: lines.join('\n\n'),
359
+ target: {
360
+ kind: 'package',
361
+ name: node.name,
362
+ ...(namespace ? { namespace } : {}),
363
+ },
364
+ };
365
+ }
366
+
299
367
  renderPage(
300
368
  node: TypedocNode,
301
369
  section: { dir: string; tag: string; symbolKind: SdkSymbolKind },
@@ -570,7 +638,11 @@ class TypedocRenderer {
570
638
  }
571
639
 
572
640
  private parts(parts: CommentPart[]): string {
573
- const markdown = parts
641
+ return markdownToMdx(this.partsMarkdown(parts));
642
+ }
643
+
644
+ private partsMarkdown(parts: CommentPart[]): string {
645
+ return parts
574
646
  .map((part) => {
575
647
  if (part.kind === 'inline-tag') {
576
648
  if (typeof part.target === 'number' && this.slugById.has(part.target)) {
@@ -582,7 +654,6 @@ class TypedocRenderer {
582
654
  return part.text;
583
655
  })
584
656
  .join('');
585
- return markdownToMdx(markdown);
586
657
  }
587
658
 
588
659
  private firstSentence(comment?: Comment): string | undefined {
@@ -0,0 +1,51 @@
1
+ import type { Root as MdastRoot } from 'mdast';
2
+ import { visit } from 'unist-util-visit';
3
+
4
+ import { parseMarkdown } from './convert.js';
5
+
6
+ export function parseSdkReadme(markdown: string): MdastRoot {
7
+ if (unparseableMarkdown(markdown)) {
8
+ return { type: 'root', children: [{ type: 'code', lang: 'text', value: markdown }] };
9
+ }
10
+ const tree = parseMarkdown(markdown);
11
+ const first = tree.children[0];
12
+ if (first?.type === 'heading' && first.depth === 1) tree.children.shift();
13
+ visit(tree, 'heading', (node) => {
14
+ if (node.depth < 2) node.depth = 2;
15
+ });
16
+ return tree;
17
+ }
18
+
19
+ // Text past either limit is rendered as a text block instead of Markdown.
20
+ // micromark is quadratic in unclosed brackets (80k `[a](` takes about 45 s)
21
+ // and in balanced link and emphasis markup (40k nested brackets, `[]` pairs,
22
+ // `[a]` references, or `*a` runs take 5 to 36 s), and remark-stringify is
23
+ // quadratic in the characters it escapes (40k `<` or `{` take 0.7 s, minutes
24
+ // at the 1 MiB cap). At 10k either costs well under 2 s. The largest real doc
25
+ // seen (openai-go's README, 60 KB) has about 1,500 such characters and no more
26
+ // than 3 unclosed brackets.
27
+ const MAX_UNCLOSED = 1000;
28
+ const MAX_MARKUP = 10000;
29
+ const MARKUP = new Set(['[', ']', '*', '_', '<', '>', '{', '}', '`', '&']);
30
+
31
+ export function unparseableMarkdown(markdown: string): boolean {
32
+ let square = 0;
33
+ let round = 0;
34
+ let unclosed = 0;
35
+ let markup = 0;
36
+ for (let i = 0; i < markdown.length; i += 1) {
37
+ const char = markdown[i];
38
+ if (char !== undefined && MARKUP.has(char)) markup += 1;
39
+ if (char === '\\') i += 1;
40
+ else if (char === '[') square += 1;
41
+ else if (char === '(') round += 1;
42
+ else if (char === ']') {
43
+ if (square > 0) square -= 1;
44
+ else unclosed += 1;
45
+ } else if (char === ')') {
46
+ if (round > 0) round -= 1;
47
+ else unclosed += 1;
48
+ }
49
+ }
50
+ return square + round + unclosed > MAX_UNCLOSED || markup > MAX_MARKUP;
51
+ }