@mintlify/scraping 4.0.1231 → 4.0.1232

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.1231",
3
+ "version": "4.0.1232",
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": "a037bb67d89d82707d5a9be6c5fcb8422f5b934e"
90
+ "gitHead": "bd9ae94bb1b82d591ce385a24bba824f937bea7a"
91
91
  }
@@ -3,7 +3,10 @@ import yaml from 'js-yaml';
3
3
  import path from 'node:path';
4
4
 
5
5
  import { htmlToMdx } from '../convert.js';
6
- import type { SdkNavGroup, SdkPage, SdkReference } from '../types.js';
6
+ import { identifierTokens, linkedTypeField } from '../linked-tokens.js';
7
+ import { inlineCode, linkedCode, responseField, truncate, type LinkedToken } from '../mdx.js';
8
+ import { nestNavGroups, type PathNavGroup } from '../navigation.js';
9
+ import type { SdkPage, SdkReference } from '../types.js';
7
10
 
8
11
  type DocfxParameter = { id?: string; type?: string; description?: string };
9
12
  type DocfxSyntax = {
@@ -82,7 +85,7 @@ export async function convertDocfx(sourcePath: string): Promise<SdkReference> {
82
85
  ];
83
86
 
84
87
  const pages: SdkPage[] = [];
85
- const groups: SdkNavGroup[] = [];
88
+ const groups: PathNavGroup[] = [];
86
89
  for (const namespace of namespaceOrder) {
87
90
  const ordered = orderByToc(
88
91
  typesByNamespace.get(namespace) ?? [],
@@ -91,12 +94,12 @@ export async function convertDocfx(sourcePath: string): Promise<SdkReference> {
91
94
  const namespacePages = ordered.map((type) => renderer.renderPage(type));
92
95
  pages.push(...namespacePages);
93
96
  groups.push({
94
- group: namespace || 'Reference',
95
- pages: namespacePages.map((page) => page.slug),
97
+ path: namespace ? namespace.split('.') : [],
98
+ group: { group: namespace || 'Reference', pages: namespacePages.map((page) => page.slug) },
96
99
  });
97
100
  }
98
101
 
99
- return { pages, groups };
102
+ return { pages, groups: nestNavGroups(groups) };
100
103
  }
101
104
 
102
105
  async function loadManagedReferenceFiles(sourcePath: string): Promise<DocfxFile[]> {
@@ -270,7 +273,7 @@ class DocfxRenderer {
270
273
  for (const member of this.members(item).filter((child) => child.type === 'Field')) {
271
274
  const value = member.syntax?.content?.split('=')[1]?.trim() ?? '';
272
275
  lines.push(
273
- this.responseField(member.name ?? member.uid, value, false, this.prose(member.summary))
276
+ responseField(member.name ?? member.uid, value, false, this.prose(member.summary))
274
277
  );
275
278
  }
276
279
  return lines;
@@ -292,16 +295,16 @@ class DocfxRenderer {
292
295
  if (syntax.typeParameters?.length) {
293
296
  lines.push(`${heading} Type Parameters`);
294
297
  for (const param of syntax.typeParameters) {
295
- lines.push(this.responseField(param.id ?? '', '', false, this.prose(param.description)));
298
+ lines.push(responseField(param.id ?? '', '', false, this.prose(param.description)));
296
299
  }
297
300
  }
298
301
  if (syntax.parameters?.length) {
299
302
  lines.push(`${heading} Parameters`);
300
303
  for (const param of syntax.parameters) {
301
304
  lines.push(
302
- this.responseField(
305
+ linkedTypeField(
303
306
  param.id ?? '',
304
- this.typeName(param.type),
307
+ this.typeTokens(param.type),
305
308
  isOptionalParameter(param.id, syntax.content),
306
309
  this.prose(param.description)
307
310
  )
@@ -312,7 +315,10 @@ class DocfxRenderer {
312
315
  if (returnType && returnType.toLowerCase() !== 'void') {
313
316
  lines.push(`${heading} Returns`);
314
317
  const description = this.prose(syntax.return?.description);
315
- lines.push(`\`${escapeInlineCode(returnType)}\`${description ? `\n\n${description}` : ''}`);
318
+ const linked =
319
+ linkedCode(this.typeTokens(syntax.return?.type)) ??
320
+ inlineCode(returnType.replace(/`/g, ''));
321
+ lines.push(`${linked}${description ? `\n\n${description}` : ''}`);
316
322
  }
317
323
  return lines;
318
324
  }
@@ -325,24 +331,14 @@ class DocfxRenderer {
325
331
  ]
326
332
  .filter(Boolean)
327
333
  .join('\n\n');
328
- return this.responseField(
334
+ return linkedTypeField(
329
335
  member.name ?? member.uid,
330
- this.typeName(member.syntax?.return?.type),
336
+ this.typeTokens(member.syntax?.return?.type),
331
337
  isNullable(member.syntax?.content),
332
338
  body
333
339
  );
334
340
  }
335
341
 
336
- private responseField(name: string, type: string, optional: boolean, body?: string): string {
337
- const typeAttr = type ? ` type=${this.jsxString(truncate(type, 80))}` : '';
338
- const requiredAttr = optional ? '' : ' required';
339
- return `<ResponseField name=${this.jsxString(name)}${typeAttr}${requiredAttr}>\n${indent(body || '')}\n</ResponseField>`;
340
- }
341
-
342
- private jsxString(value: string): string {
343
- return `{${JSON.stringify(value)}}`;
344
- }
345
-
346
342
  private codeBlock(code: string, lang = 'csharp'): string {
347
343
  return `\`\`\`${lang}\n${code}\n\`\`\``;
348
344
  }
@@ -413,6 +409,25 @@ class DocfxRenderer {
413
409
  return uid ? this.shortName(uid) : '';
414
410
  }
415
411
 
412
+ private typeTokens(uid?: string): LinkedToken[] {
413
+ if (!uid) return [];
414
+ const source = uid
415
+ .replace(/(^|[^\w.])`+\d+/g, '$1')
416
+ .replace(/\{/g, '<')
417
+ .replace(/\}/g, '>');
418
+ const tokens = identifierTokens(
419
+ source,
420
+ /[A-Za-z_]\w*(?:`+\d+)?(?:\.[A-Za-z_]\w*(?:`+\d+)?)*/g,
421
+ (name, index) => {
422
+ const arity = genericArity(source, index + name.length);
423
+ const slug = this.slugByUid.get(arity ? `${name}\`${arity}` : name);
424
+ const text = name.replace(/`+\d+/g, '').split('.').at(-1) ?? name;
425
+ return { text, href: slug ? `/${slug}` : undefined };
426
+ }
427
+ );
428
+ return tokens.some((token) => token.href) ? tokens : [{ text: this.typeName(uid) }];
429
+ }
430
+
416
431
  private firstSentence(summary?: string): string | undefined {
417
432
  if (!summary) return undefined;
418
433
  const text = decodeEntities(summary.replace(/<[^>]*>/g, ' '))
@@ -424,6 +439,19 @@ class DocfxRenderer {
424
439
  }
425
440
  }
426
441
 
442
+ function genericArity(text: string, start: number): number {
443
+ if (text[start] !== '<') return 0;
444
+ let depth = 0;
445
+ let arity = 1;
446
+ for (let index = start + 1; index < text.length; index += 1) {
447
+ const character = text[index];
448
+ if (character === '<') depth += 1;
449
+ else if (character === '>' && depth-- === 0) return arity;
450
+ else if (character === ',' && depth === 0) arity += 1;
451
+ }
452
+ return arity;
453
+ }
454
+
427
455
  function byName(left: DocfxItem, right: DocfxItem): number {
428
456
  return (left.name ?? '').localeCompare(right.name ?? '');
429
457
  }
@@ -474,18 +502,3 @@ function isOptionalParameter(id?: string, content?: string): boolean {
474
502
  function escapeRegExp(text: string): string {
475
503
  return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
476
504
  }
477
-
478
- function indent(text: string): string {
479
- return text
480
- .split('\n')
481
- .map((line) => (line ? ` ${line}` : line))
482
- .join('\n');
483
- }
484
-
485
- function truncate(text: string, max: number): string {
486
- return text.length > max ? `${text.slice(0, max - 1)}…` : text;
487
- }
488
-
489
- function escapeInlineCode(text: string): string {
490
- return text.replace(/`/g, '');
491
- }
@@ -22,6 +22,7 @@ import {
22
22
  truncate,
23
23
  type LinkedToken,
24
24
  } from '../mdx.js';
25
+ import { nestNavGroups, type PathNavGroup } from '../navigation.js';
25
26
  import { parseSdkReadme, unparseableMarkdown } from '../readme.js';
26
27
  import type { SdkNavGroup, SdkPage, SdkPageTarget, SdkReference } from '../types.js';
27
28
 
@@ -194,14 +195,18 @@ export async function convertGo(sourcePath: string): Promise<SdkReference> {
194
195
  const renderers = artifact.packages.map((pkg) => new GoRenderer(index, pkg));
195
196
 
196
197
  const pages: SdkPage[] = [];
197
- const groups: SdkNavGroup[] = [];
198
- for (const renderer of renderers) {
198
+ const groups: PathNavGroup[] = [];
199
+ for (const [position, renderer] of renderers.entries()) {
199
200
  const packagePages = renderer.packagePages();
200
201
  pages.push(...packagePages, ...renderer.memberPages());
201
- groups.push({ group: renderer.group, pages: packageEntries(packagePages) });
202
+ const dir = artifact.packages[position]?.dir ?? '.';
203
+ groups.push({
204
+ path: dir === '.' ? [] : dir.split('/'),
205
+ group: { group: renderer.group, pages: packageEntries(packagePages) },
206
+ });
202
207
  }
203
208
 
204
- return { pages, groups };
209
+ return { pages, groups: nestNavGroups(groups) };
205
210
  }
206
211
 
207
212
  const KIND_GROUPS = [
@@ -14,6 +14,7 @@ import {
14
14
  trimTokens,
15
15
  } from '../linked-tokens.js';
16
16
  import { linkedCode, type LinkedToken } from '../mdx.js';
17
+ import { nestNavGroups } from '../navigation.js';
17
18
  import type { SdkNavGroup, SdkPage, SdkReference } from '../types.js';
18
19
 
19
20
  type JavadocKind = 'interface' | 'class' | 'enum' | 'record' | 'exception' | 'error' | 'annotation';
@@ -63,9 +64,14 @@ export async function convertJavadoc(sourcePath: string): Promise<SdkReference>
63
64
  byPackage.set(type.pkg, siblings);
64
65
  }
65
66
 
66
- const groups: SdkNavGroup[] = [...byPackage.entries()]
67
- .sort(([left], [right]) => left.localeCompare(right))
68
- .map(([pkg, members]) => ({ group: pkg, pages: packageEntries(members) }));
67
+ const groups = nestNavGroups(
68
+ [...byPackage.entries()]
69
+ .sort(([left], [right]) => left.localeCompare(right))
70
+ .map(([pkg, members]) => ({
71
+ path: pkg.split('.'),
72
+ group: { group: pkg, pages: packageEntries(members) },
73
+ }))
74
+ );
69
75
 
70
76
  return { pages, groups };
71
77
  }
@@ -3,7 +3,10 @@ import fse from 'fs-extra';
3
3
  import path from 'node:path';
4
4
 
5
5
  import { markdownToMdx } from '../convert.js';
6
- import type { SdkNavGroup, SdkPage, SdkReference } from '../types.js';
6
+ import { identifierTokens, linkedTypeField } from '../linked-tokens.js';
7
+ import { inlineCode, linkedCode, truncate, type LinkedToken } from '../mdx.js';
8
+ import { nestNavGroups, type PathNavGroup } from '../navigation.js';
9
+ import type { SdkPage, SdkReference } from '../types.js';
7
10
 
8
11
  type DocTag = {
9
12
  '@_name'?: string;
@@ -118,17 +121,20 @@ export async function convertPhpdoc(sourcePath: string): Promise<SdkReference> {
118
121
  });
119
122
 
120
123
  const pages: SdkPage[] = [];
121
- const groups: SdkNavGroup[] = [];
124
+ const groups: PathNavGroup[] = [];
122
125
  for (const groupName of groupNames) {
123
126
  const members = (byGroup.get(groupName) ?? []).sort((left, right) =>
124
127
  left.name.localeCompare(right.name)
125
128
  );
126
129
  const groupPages = members.map((entry) => renderer.renderPage(entry));
127
130
  pages.push(...groupPages);
128
- groups.push({ group: groupName, pages: groupPages.map((page) => page.slug) });
131
+ groups.push({
132
+ path: members[0]?.namespace ?? [],
133
+ group: { group: groupName, pages: groupPages.map((page) => page.slug) },
134
+ });
129
135
  }
130
136
 
131
- return { pages, groups };
137
+ return { pages, groups: nestNavGroups(groups) };
132
138
  }
133
139
 
134
140
  function collectEntries(files: PhpFile[]): Entry[] {
@@ -229,7 +235,7 @@ class PhpdocRenderer {
229
235
  ]
230
236
  .filter(Boolean)
231
237
  .join('\n\n');
232
- lines.push(this.responseField(constant.name ?? '', type, false, body));
238
+ lines.push(linkedTypeField(constant.name ?? '', this.typeTokens(type), false, body));
233
239
  }
234
240
  }
235
241
 
@@ -254,7 +260,9 @@ class PhpdocRenderer {
254
260
  ]
255
261
  .filter(Boolean)
256
262
  .join('\n\n');
257
- lines.push(this.responseField(property.name ?? '', type, isNullable(type), body));
263
+ lines.push(
264
+ linkedTypeField(property.name ?? '', this.typeTokens(type), isNullable(type), body)
265
+ );
258
266
  }
259
267
  }
260
268
 
@@ -305,7 +313,7 @@ class PhpdocRenderer {
305
313
  const body = paramTag?.['@_description']
306
314
  ? markdownToMdx(this.resolveInline(paramTag['@_description']))
307
315
  : '';
308
- lines.push(this.responseField(arg.name ?? '', type, optional, body));
316
+ lines.push(linkedTypeField(arg.name ?? '', this.typeTokens(type), optional, body));
309
317
  }
310
318
  }
311
319
 
@@ -315,7 +323,8 @@ class PhpdocRenderer {
315
323
  const description = returnTag['@_description']
316
324
  ? `\n\n${markdownToMdx(this.resolveInline(returnTag['@_description']))}`
317
325
  : '';
318
- lines.push(`\`${escapeInlineCode(returnTag['@_type'])}\`${description}`);
326
+ const returns = this.typeTokens(returnTag['@_type']);
327
+ lines.push(`${linkedCode(returns) ?? inlineCode(returnTag['@_type'])}${description}`);
319
328
  }
320
329
 
321
330
  const throwsTags = tagsOf(method.docblock, 'throws');
@@ -326,7 +335,7 @@ class PhpdocRenderer {
326
335
  throwsTags
327
336
  .map((tag) => {
328
337
  const description = tag['@_description'] ? ` ${tag['@_description']}` : '';
329
- return `- \`${escapeInlineCode(tag['@_type'] ?? 'Exception')}\`${description}`;
338
+ return `- ${this.typeLink(tag['@_type'] ?? 'Exception')}${description}`;
330
339
  })
331
340
  .join('\n')
332
341
  )
@@ -415,14 +424,23 @@ class PhpdocRenderer {
415
424
  return truncate(sentence, 160);
416
425
  }
417
426
 
418
- private responseField(name: string, type: string, optional: boolean, body?: string): string {
419
- const typeAttr = type ? ` type=${this.jsxString(truncate(type, 80))}` : '';
420
- const requiredAttr = optional ? '' : ' required';
421
- return `<ResponseField name=${this.jsxString(name)}${typeAttr}${requiredAttr}>\n${indent(body || '')}\n</ResponseField>`;
427
+ private typeTokens(type: string): LinkedToken[] {
428
+ return identifierTokens(type, /\\?[A-Za-z_]\w*(?:\\[A-Za-z_]\w*)*/g, (name) => {
429
+ const slug = this.resolve(name);
430
+ return { text: name, href: slug ? `/${slug}` : undefined };
431
+ });
422
432
  }
423
433
 
424
- private jsxString(value: string): string {
425
- return `{${JSON.stringify(value)}}`;
434
+ private typeLink(type: string): string {
435
+ const spans: LinkedToken[] = [];
436
+ for (const token of this.typeTokens(type)) {
437
+ const last = spans.at(-1);
438
+ if (last && !last.href && !token.href) last.text += token.text;
439
+ else spans.push({ ...token });
440
+ }
441
+ return spans
442
+ .map(({ text, href }) => (href ? `[${inlineCode(text)}](${href})` : inlineCode(text)))
443
+ .join('');
426
444
  }
427
445
 
428
446
  private codeBlock(code: string): string {
@@ -430,17 +448,6 @@ class PhpdocRenderer {
430
448
  }
431
449
  }
432
450
 
433
- function indent(text: string): string {
434
- return text
435
- .split('\n')
436
- .map((line) => (line ? ` ${line}` : line))
437
- .join('\n');
438
- }
439
-
440
- function truncate(text: string, max: number): string {
441
- return text.length > max ? `${text.slice(0, max - 1)}…` : text;
442
- }
443
-
444
451
  function escapeInlineCode(text: string): string {
445
452
  return text.replace(/`/g, '');
446
453
  }
@@ -4,6 +4,7 @@ import path from 'node:path';
4
4
 
5
5
  import { escapeMdxText, markdownToMdx, rewriteMdastLinks, stringifyMdast } from '../convert.js';
6
6
  import { inlineCode, linkedCode, responseField, safeHref, truncate } from '../mdx.js';
7
+ import { nestNavGroups, type PathNavGroup } from '../navigation.js';
7
8
  import { parseSdkReadme } from '../readme.js';
8
9
  import type { SdkNavGroup, SdkPage, SdkPageTarget, SdkReference } from '../types.js';
9
10
 
@@ -166,9 +167,9 @@ export async function convertTypedoc(sourcePath: string): Promise<SdkReference>
166
167
  const renderer = new TypedocRenderer(slugById);
167
168
  const pages: SdkPage[] = [];
168
169
  const groups: SdkNavGroup[] = [];
170
+ const moduleGroupById = new Map<number, PathNavGroup>();
169
171
 
170
172
  for (const module of modules) {
171
- const prefix = multiModule ? `${module.name} ` : '';
172
173
  const declarations = collectDeclarations(module);
173
174
  const kindGroups: SdkNavGroup[] = [];
174
175
  for (const section of SECTIONS) {
@@ -196,7 +197,12 @@ export async function convertTypedoc(sourcePath: string): Promise<SdkReference>
196
197
  }
197
198
  const customGroups = hasCustomLayout(module) ? layoutGroups(module, slugById) : [];
198
199
  const moduleGroups = customGroups.length ? customGroups : kindGroups;
199
- groups.push(...moduleGroups.map((group) => ({ ...group, group: `${prefix}${group.group}` })));
200
+ if (multiModule) {
201
+ moduleGroupById.set(module.id, {
202
+ path: module.name.split('/'),
203
+ group: { group: module.name, pages: moduleGroups },
204
+ });
205
+ } else groups.push(...moduleGroups);
200
206
  }
201
207
 
202
208
  const overviewPages: SdkPage[] = [];
@@ -235,13 +241,20 @@ export async function convertTypedoc(sourcePath: string): Promise<SdkReference>
235
241
  isProject ? undefined : project.name
236
242
  );
237
243
  overviewPages.push(overview);
244
+ const moduleGroup = moduleGroupById.get(container.id)?.group;
238
245
  if (isProject) projectOverview = slug;
246
+ else if (moduleGroup) moduleGroup.pages.unshift(slug);
247
+ else if (!projectOverview) projectOverview = slug;
239
248
  else overviewGroups.push({ group: `${container.name} Overview`, pages: [slug] });
240
249
  }
241
250
 
242
251
  return {
243
252
  pages: [...overviewPages, ...pages],
244
- groups: [...overviewGroups, ...groups],
253
+ groups: [
254
+ ...overviewGroups,
255
+ ...groups,
256
+ ...nestNavGroups([...moduleGroupById.values()].filter(({ group }) => group.pages.length)),
257
+ ],
245
258
  ...(projectOverview ? { overview: projectOverview } : {}),
246
259
  };
247
260
  }
@@ -78,14 +78,34 @@ export function topLevelDelimiter(
78
78
  return -1;
79
79
  }
80
80
 
81
- export function linkedTypeField(name: string, tokens: LinkedToken[], optional: boolean): string {
81
+ export function linkedTypeField(
82
+ name: string,
83
+ tokens: LinkedToken[],
84
+ optional: boolean,
85
+ description?: string
86
+ ): string {
82
87
  const type = tokens.map((token) => token.text).join('');
83
88
  const typeHref = tokens.length === 1 ? tokens[0]?.href : undefined;
84
- let body: string | undefined;
89
+ let linked: string | undefined;
85
90
  if (!typeHref || type.length > 80) {
86
- const linked = linkedCode(tokens);
87
- if (linked) body = `**Type:** ${linked}`;
88
- else if (type.length > 80) body = `**Type:** ${inlineCode(type)}`;
91
+ linked = linkedCode(tokens) ?? (type.length > 80 ? inlineCode(type) : undefined);
89
92
  }
93
+ const body = [linked && `**Type:** ${linked}`, description].filter(Boolean).join('\n\n');
90
94
  return responseField(name, type, optional, body, typeHref);
91
95
  }
96
+
97
+ export function identifierTokens(
98
+ text: string,
99
+ identifier: RegExp,
100
+ link: (name: string, index: number) => LinkedToken
101
+ ): LinkedToken[] {
102
+ const tokens: LinkedToken[] = [];
103
+ let cursor = 0;
104
+ for (const match of text.matchAll(identifier)) {
105
+ if (match.index > cursor) tokens.push({ text: text.slice(cursor, match.index) });
106
+ tokens.push(link(match[0], match.index));
107
+ cursor = match.index + match[0].length;
108
+ }
109
+ if (cursor < text.length) tokens.push({ text: text.slice(cursor) });
110
+ return tokens;
111
+ }
@@ -0,0 +1,22 @@
1
+ import type { SdkNavGroup } from './types.js';
2
+
3
+ export type PathNavGroup = { path: string[]; group: SdkNavGroup };
4
+
5
+ export function nestNavGroups(entries: PathNavGroup[]): SdkNavGroup[] {
6
+ const byPath = new Map<string, SdkNavGroup>();
7
+ const nested = entries.map(({ path, group }) => {
8
+ const copy = { ...group, pages: [...group.pages] };
9
+ if (path.length) byPath.set(path.join('\u0000'), copy);
10
+ return { path, group: copy };
11
+ });
12
+ const roots: SdkNavGroup[] = [];
13
+ for (const { path, group } of nested) {
14
+ let parent: SdkNavGroup | undefined;
15
+ for (let length = path.length - 1; length > 0 && !parent; length -= 1) {
16
+ parent = byPath.get(path.slice(0, length).join('\u0000'));
17
+ }
18
+ if (parent) parent.pages.push(group);
19
+ else roots.push(group);
20
+ }
21
+ return roots;
22
+ }