@mintlify/scraping 4.0.1110 → 4.0.1111

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.1110",
3
+ "version": "4.0.1111",
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": "f9869196da1eaa018c94b2e8d0c8c2cd6e082274"
90
+ "gitHead": "acb0c81cdceb361f88c6b238623fbf13a7457edb"
91
91
  }
@@ -2,7 +2,7 @@ import type { SdkSymbolKind } from '@mintlify/models';
2
2
  import fse from 'fs-extra';
3
3
  import path from 'node:path';
4
4
 
5
- import { markdownToMdx } from '../convert.js';
5
+ import { escapeMdxText, markdownToMdx } from '../convert.js';
6
6
  import type { SdkNavGroup, SdkPage, SdkPageTarget, SdkReference } from '../types.js';
7
7
 
8
8
  const KIND = {
@@ -52,12 +52,16 @@ type TypedocType = {
52
52
  head?: string;
53
53
  tail?: [TypedocType, string][];
54
54
  };
55
+ type TypedocCategory = { title: string; children?: number[] };
56
+ type TypedocGroup = TypedocCategory & { categories?: TypedocCategory[] };
55
57
  type TypedocNode = {
56
58
  id: number;
57
59
  name: string;
58
60
  kind: number;
59
61
  comment?: Comment;
60
62
  children?: TypedocNode[];
63
+ groups?: TypedocGroup[];
64
+ categories?: TypedocCategory[];
61
65
  signatures?: TypedocNode[];
62
66
  parameters?: TypedocNode[];
63
67
  type?: TypedocType;
@@ -70,6 +74,11 @@ type TypedocNode = {
70
74
  inheritedFrom?: { name: string };
71
75
  };
72
76
  type TypeToken = { text: string; href?: string };
77
+ type MemberSection = {
78
+ title: string;
79
+ members: TypedocNode[];
80
+ categories?: { title: string; members: TypedocNode[] }[];
81
+ };
73
82
 
74
83
  const SECTIONS: {
75
84
  kind: number;
@@ -104,6 +113,32 @@ const SECTIONS: {
104
113
  },
105
114
  ];
106
115
 
116
+ const MAX_PLACEMENTS_PER_PAGE = 8;
117
+ const MAX_DECLARATION_NAME_LENGTH = 512;
118
+
119
+ const MEMBER_KINDS = new Set<number>([KIND.constructor, KIND.property, KIND.accessor, KIND.method]);
120
+
121
+ const DEFAULT_GROUP_TITLE_BY_KIND = new Map<number, string>([
122
+ ...SECTIONS.map((section) => [section.kind, section.group] as const),
123
+ [KIND.namespace, 'Namespaces'],
124
+ [KIND.constructor, 'Constructors'],
125
+ [KIND.property, 'Properties'],
126
+ [KIND.accessor, 'Accessors'],
127
+ [KIND.method, 'Methods'],
128
+ ]);
129
+
130
+ function hasCustomGroups(container: TypedocNode): boolean {
131
+ const kindById = new Map((container.children ?? []).map((child) => [child.id, child.kind]));
132
+ return (container.groups ?? []).some(
133
+ (group) =>
134
+ group.categories?.length ||
135
+ (group.children ?? []).some((id) => {
136
+ const defaultTitle = DEFAULT_GROUP_TITLE_BY_KIND.get(kindById.get(id) ?? -1);
137
+ return defaultTitle !== undefined && defaultTitle !== group.title;
138
+ })
139
+ );
140
+ }
141
+
107
142
  export async function convertTypedoc(sourcePath: string): Promise<SdkReference> {
108
143
  const project = (await fse.readJson(sourcePath)) as TypedocNode;
109
144
  const modules =
@@ -132,6 +167,7 @@ export async function convertTypedoc(sourcePath: string): Promise<SdkReference>
132
167
  for (const module of modules) {
133
168
  const prefix = multiModule ? `${module.name} ` : '';
134
169
  const declarations = collectDeclarations(module);
170
+ const kindGroups: SdkNavGroup[] = [];
135
171
  for (const section of SECTIONS) {
136
172
  const entries = declarations
137
173
  .filter(({ node }) => node.kind === section.kind && !node.flags?.isPrivate)
@@ -153,13 +189,76 @@ export async function convertTypedoc(sourcePath: string): Promise<SdkReference>
153
189
  );
154
190
  pages.push(...renderer.renderMemberPages(entry.node, parentSlug, qualifiedName(entry)));
155
191
  }
156
- groups.push({ group: `${prefix}${section.group}`, pages: sectionPages.map((p) => p.slug) });
192
+ kindGroups.push({ group: section.group, pages: sectionPages.map((p) => p.slug) });
157
193
  }
194
+ const customGroups = hasCustomLayout(module) ? layoutGroups(module, slugById) : [];
195
+ const moduleGroups = customGroups.length ? customGroups : kindGroups;
196
+ groups.push(...moduleGroups.map((group) => ({ ...group, group: `${prefix}${group.group}` })));
158
197
  }
159
198
 
160
199
  return { pages, groups };
161
200
  }
162
201
 
202
+ function hasCustomLayout(container: TypedocNode): boolean {
203
+ if (container.categories?.length) return true;
204
+ if (hasCustomGroups(container)) return true;
205
+ return (container.children ?? []).some(
206
+ (child) => child.kind === KIND.namespace && hasCustomLayout(child)
207
+ );
208
+ }
209
+
210
+ function layoutGroups(container: TypedocNode, slugById: Map<number, string>): SdkNavGroup[] {
211
+ return layoutEntries(container, slugById, new Map()).filter(
212
+ (entry): entry is SdkNavGroup => typeof entry !== 'string'
213
+ );
214
+ }
215
+
216
+ function layoutEntries(
217
+ container: TypedocNode,
218
+ slugById: Map<number, string>,
219
+ placements: Map<number, number>
220
+ ): (string | SdkNavGroup)[] {
221
+ const childById = new Map((container.children ?? []).map((child) => [child.id, child]));
222
+ const entries = (ids: number[] = []): (string | SdkNavGroup)[] =>
223
+ [...new Set(ids)].flatMap((id): (string | SdkNavGroup)[] => {
224
+ const child = childById.get(id);
225
+ if (!child) return [];
226
+ const count = placements.get(id) ?? 0;
227
+ if (child.kind === KIND.namespace) {
228
+ if (count > 0) return [];
229
+ placements.set(id, 1);
230
+ return group(child.name, layoutEntries(child, slugById, placements));
231
+ }
232
+ const slug = slugById.get(id);
233
+ if (!slug) return [];
234
+ if (count >= MAX_PLACEMENTS_PER_PAGE) {
235
+ throw new Error(
236
+ `TypeDoc declaration "${truncate(child.name, 64)}" is listed in more than ${MAX_PLACEMENTS_PER_PAGE} navigation groups`
237
+ );
238
+ }
239
+ placements.set(id, count + 1);
240
+ return [slug];
241
+ });
242
+ const group = (title: unknown, pages: (string | SdkNavGroup)[]): SdkNavGroup[] => {
243
+ const label = singleLine(title);
244
+ return label && pages.length ? [{ group: label, pages }] : [];
245
+ };
246
+
247
+ if (container.categories?.length) {
248
+ return container.categories.flatMap((category) =>
249
+ group(category.title, entries(category.children))
250
+ );
251
+ }
252
+ return (container.groups ?? []).flatMap((entry) =>
253
+ group(
254
+ entry.title,
255
+ entry.categories?.length
256
+ ? entry.categories.flatMap((category) => group(category.title, entries(category.children)))
257
+ : entries(entry.children)
258
+ )
259
+ );
260
+ }
261
+
163
262
  type Declaration = { node: TypedocNode; namespacePath: string[] };
164
263
 
165
264
  function qualifiedName({ node, namespacePath }: Declaration): string {
@@ -178,7 +277,18 @@ function collectDeclarations(container: TypedocNode, namespacePath: string[] = [
178
277
  return declarations;
179
278
  }
180
279
 
280
+ function singleLine(value: unknown): string {
281
+ return String(value ?? '')
282
+ .replace(/\s+/g, ' ')
283
+ .trim();
284
+ }
285
+
181
286
  function slugify(name: string): string {
287
+ if (name.length > MAX_DECLARATION_NAME_LENGTH) {
288
+ throw new Error(
289
+ `TypeDoc declaration name "${truncate(name, 64)}" exceeds ${MAX_DECLARATION_NAME_LENGTH} characters`
290
+ );
291
+ }
182
292
  return name.replace(/[^a-zA-Z0-9-_.]+/g, '-').replace(/^-+|-+$/g, '') || 'item';
183
293
  }
184
294
 
@@ -284,6 +394,22 @@ class TypedocRenderer {
284
394
  }
285
395
 
286
396
  const members = (node.children ?? []).filter((child) => !child.flags?.isPrivate);
397
+ const layout = this.memberLayout(node, members);
398
+ if (layout) {
399
+ for (const section of layout) {
400
+ lines.push(`## ${headingText(section.title)}`);
401
+ if (section.categories) {
402
+ for (const category of section.categories) {
403
+ lines.push(`### ${headingText(category.title)}`);
404
+ lines.push(...this.renderMembers(category.members, 4));
405
+ }
406
+ } else {
407
+ lines.push(...this.renderMembers(section.members, 3));
408
+ }
409
+ }
410
+ return lines;
411
+ }
412
+
287
413
  const constructors = members.filter((child) => child.kind === KIND.constructor);
288
414
  const properties = members
289
415
  .filter((child) => child.kind === KIND.property || child.kind === KIND.accessor)
@@ -294,36 +420,71 @@ class TypedocRenderer {
294
420
 
295
421
  for (const ctor of constructors) {
296
422
  lines.push('## Constructor');
297
- lines.push(...this.renderSignatures(ctor, 3));
423
+ lines.push(...this.renderMembers([ctor], 3));
298
424
  }
299
-
300
425
  if (properties.length) {
301
426
  lines.push('## Properties');
302
- for (const property of properties) {
303
- const type = property.kind === KIND.accessor ? property.getSignature?.type : property.type;
304
- const optional = property.flags?.isOptional === true;
305
- lines.push(
306
- this.typeField(
307
- property.name,
308
- type,
309
- optional,
310
- this.comment(property.comment ?? property.getSignature?.comment)
311
- )
312
- );
313
- }
427
+ lines.push(...this.renderMembers(properties, 3));
314
428
  }
315
-
316
429
  if (methods.length) {
317
430
  lines.push('## Methods');
318
- for (const method of methods) {
319
- lines.push(`### ${method.name}()`);
320
- lines.push(...this.renderSignatures(method, 4));
321
- }
431
+ lines.push(...this.renderMembers(methods, 3));
322
432
  }
323
433
 
324
434
  return lines;
325
435
  }
326
436
 
437
+ private memberLayout(node: TypedocNode, members: TypedocNode[]): MemberSection[] | undefined {
438
+ const memberById = new Map(members.map((member) => [member.id, member]));
439
+ const placed = new Set<number>();
440
+ const resolve = (ids: number[] = []) =>
441
+ ids.flatMap((id) => {
442
+ const member = memberById.get(id);
443
+ if (!member || !MEMBER_KINDS.has(member.kind) || placed.has(id)) return [];
444
+ placed.add(id);
445
+ return [member];
446
+ });
447
+ const sections = (categories: TypedocCategory[]) =>
448
+ categories
449
+ .map((category) => ({ title: category.title, members: resolve(category.children) }))
450
+ .filter((section) => section.members.length > 0);
451
+
452
+ if (node.categories?.length) return sections(node.categories);
453
+ if (!hasCustomGroups(node)) return undefined;
454
+ return (node.groups ?? [])
455
+ .map((group): MemberSection => {
456
+ if (!group.categories?.length)
457
+ return { title: group.title, members: resolve(group.children) };
458
+ return { title: group.title, members: [], categories: sections(group.categories) };
459
+ })
460
+ .filter((section) => section.members.length > 0 || section.categories?.length);
461
+ }
462
+
463
+ private renderMembers(members: TypedocNode[], depth: number): string[] {
464
+ const lines: string[] = [];
465
+ for (const ctor of members.filter((member) => member.kind === KIND.constructor)) {
466
+ lines.push(...this.renderSignatures(ctor, depth));
467
+ }
468
+ for (const property of members.filter(
469
+ (member) => member.kind === KIND.property || member.kind === KIND.accessor
470
+ )) {
471
+ const type = property.kind === KIND.accessor ? property.getSignature?.type : property.type;
472
+ lines.push(
473
+ this.typeField(
474
+ property.name,
475
+ type,
476
+ property.flags?.isOptional === true,
477
+ this.comment(property.comment ?? property.getSignature?.comment)
478
+ )
479
+ );
480
+ }
481
+ for (const method of members.filter((member) => member.kind === KIND.method)) {
482
+ lines.push(`${'#'.repeat(depth)} ${headingText(method.name)}()`);
483
+ lines.push(...this.renderSignatures(method, depth + 1));
484
+ }
485
+ return lines;
486
+ }
487
+
327
488
  private renderEnum(node: TypedocNode): string[] {
328
489
  const lines: string[] = ['## Members'];
329
490
  for (const member of node.children ?? []) {
@@ -352,7 +513,7 @@ class TypedocRenderer {
352
513
  const comment = this.comment(signature.comment);
353
514
  if (comment) lines.push(comment);
354
515
  if (signature.parameters?.length) {
355
- lines.push(`${'#'.repeat(depth)} Parameters`);
516
+ lines.push(detailHeading('Parameters', depth));
356
517
  for (const param of signature.parameters) {
357
518
  lines.push(
358
519
  this.typeField(
@@ -367,7 +528,7 @@ class TypedocRenderer {
367
528
  const returns = signature.comment?.blockTags?.find((tag) => tag.tag === '@returns');
368
529
  const returnType = this.type(signature.type);
369
530
  if (returnType && returnType !== 'void') {
370
- lines.push(`${'#'.repeat(depth)} Returns`);
531
+ lines.push(detailHeading('Returns', depth));
371
532
  const linked = this.linkedType(signature.type) ?? inlineCode(returnType);
372
533
  lines.push(`${linked}${returns ? `\n\n${this.parts(returns.content)}` : ''}`);
373
534
  }
@@ -555,6 +716,16 @@ class TypedocRenderer {
555
716
  }
556
717
  }
557
718
 
719
+ const MAX_HEADING_DEPTH = 4;
720
+
721
+ function headingText(title: unknown): string {
722
+ return escapeMdxText(singleLine(title));
723
+ }
724
+
725
+ function detailHeading(title: string, depth: number): string {
726
+ return depth <= MAX_HEADING_DEPTH ? `${'#'.repeat(depth)} ${title}` : `**${title}**`;
727
+ }
728
+
558
729
  function indent(text: string): string {
559
730
  return text
560
731
  .split('\n')
package/src/sdk/types.ts CHANGED
@@ -21,7 +21,7 @@ export type SdkPage = {
21
21
 
22
22
  export type SdkNavGroup = {
23
23
  group: string;
24
- pages: string[];
24
+ pages: (string | SdkNavGroup)[];
25
25
  };
26
26
 
27
27
  export type SdkReference = {