@mintlify/scraping 4.0.907 → 4.0.909

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.
Files changed (43) hide show
  1. package/__test__/sdk-docfx.test.ts +204 -0
  2. package/__test__/sdk-javadoc.test.ts +150 -0
  3. package/__test__/sdk-phpdoc.test.ts +134 -0
  4. package/__test__/sdk-sphinx.test.ts +84 -0
  5. package/__test__/sdk-typedoc.test.ts +138 -0
  6. package/bin/index.d.ts +3 -0
  7. package/bin/index.js +2 -0
  8. package/bin/index.js.map +1 -1
  9. package/bin/sdk/convert.d.ts +6 -0
  10. package/bin/sdk/convert.js +90 -0
  11. package/bin/sdk/convert.js.map +1 -0
  12. package/bin/sdk/converters/docfx.d.ts +2 -0
  13. package/bin/sdk/converters/docfx.js +402 -0
  14. package/bin/sdk/converters/docfx.js.map +1 -0
  15. package/bin/sdk/converters/javadoc.d.ts +2 -0
  16. package/bin/sdk/converters/javadoc.js +318 -0
  17. package/bin/sdk/converters/javadoc.js.map +1 -0
  18. package/bin/sdk/converters/phpdoc.d.ts +2 -0
  19. package/bin/sdk/converters/phpdoc.js +351 -0
  20. package/bin/sdk/converters/phpdoc.js.map +1 -0
  21. package/bin/sdk/converters/sphinx.d.ts +2 -0
  22. package/bin/sdk/converters/sphinx.js +267 -0
  23. package/bin/sdk/converters/sphinx.js.map +1 -0
  24. package/bin/sdk/converters/typedoc.d.ts +2 -0
  25. package/bin/sdk/converters/typedoc.js +318 -0
  26. package/bin/sdk/converters/typedoc.js.map +1 -0
  27. package/bin/sdk/generateSdkReference.d.ts +2 -0
  28. package/bin/sdk/generateSdkReference.js +20 -0
  29. package/bin/sdk/generateSdkReference.js.map +1 -0
  30. package/bin/sdk/types.d.ts +19 -0
  31. package/bin/sdk/types.js +2 -0
  32. package/bin/sdk/types.js.map +1 -0
  33. package/bin/tsconfig.build.tsbuildinfo +1 -1
  34. package/package.json +5 -4
  35. package/src/index.ts +3 -0
  36. package/src/sdk/convert.ts +101 -0
  37. package/src/sdk/converters/docfx.ts +491 -0
  38. package/src/sdk/converters/javadoc.ts +330 -0
  39. package/src/sdk/converters/phpdoc.ts +446 -0
  40. package/src/sdk/converters/sphinx.ts +277 -0
  41. package/src/sdk/converters/typedoc.ts +423 -0
  42. package/src/sdk/generateSdkReference.ts +25 -0
  43. package/src/sdk/types.ts +24 -0
@@ -0,0 +1,330 @@
1
+ import fse from 'fs-extra';
2
+ import type { Element, ElementContent, Root as HastRoot, RootContent } from 'hast';
3
+ import path from 'node:path';
4
+
5
+ import { htmlToHast } from '../../pipeline/root.js';
6
+ import { removeHastComments } from '../../utils/hastComments.js';
7
+ import { hastToMdx, type RewriteLink } from '../convert.js';
8
+ import type { SdkNavGroup, SdkPage, SdkReference } from '../types.js';
9
+
10
+ type JavadocType = { pkg: string; name: string; file: string; slug: string };
11
+
12
+ const TYPE_NAME_PATTERN = /^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*)*$/;
13
+ const DROPPED_CLASSES = new Set([
14
+ 'summary',
15
+ 'inherited-list',
16
+ 'sub-title',
17
+ 'header',
18
+ 'inheritance',
19
+ ]);
20
+
21
+ export async function convertJavadoc(sourcePath: string): Promise<SdkReference> {
22
+ const types = await discoverTypes(sourcePath);
23
+ if (types.length === 0) {
24
+ throw new Error(`No javadoc types found in ${sourcePath}`);
25
+ }
26
+ const slugByFile = new Map(types.map((type) => [type.file, type.slug]));
27
+
28
+ const pages: SdkPage[] = [];
29
+ const byPackage = new Map<string, JavadocType[]>();
30
+ for (const type of types) {
31
+ const html = await fse.readFile(path.join(sourcePath, type.file), 'utf8');
32
+ pages.push(renderPage(type, html, slugByFile));
33
+ const siblings = byPackage.get(type.pkg) ?? [];
34
+ siblings.push(type);
35
+ byPackage.set(type.pkg, siblings);
36
+ }
37
+
38
+ const groups: SdkNavGroup[] = [...byPackage.entries()]
39
+ .sort(([left], [right]) => left.localeCompare(right))
40
+ .map(([pkg, members]) => ({
41
+ group: pkg,
42
+ pages: members
43
+ .sort((left, right) => left.name.localeCompare(right.name))
44
+ .map((member) => member.slug),
45
+ }));
46
+
47
+ return { pages, groups };
48
+ }
49
+
50
+ async function discoverTypes(root: string): Promise<JavadocType[]> {
51
+ const entries = (await typesFromSearchIndex(root)) ?? (await typesFromPackageDirs(root));
52
+ const seen = new Set<string>();
53
+ const types: JavadocType[] = [];
54
+ for (const entry of entries) {
55
+ if (seen.has(entry.file)) continue;
56
+ seen.add(entry.file);
57
+ if (await fse.pathExists(path.join(root, entry.file))) types.push(entry);
58
+ }
59
+ return types;
60
+ }
61
+
62
+ async function typesFromSearchIndex(root: string): Promise<JavadocType[] | undefined> {
63
+ const indexPath = path.join(root, 'type-search-index.js');
64
+ if (!(await fse.pathExists(indexPath))) return undefined;
65
+ const raw = await fse.readFile(indexPath, 'utf8');
66
+ const json = raw
67
+ .slice(raw.indexOf('=') + 1)
68
+ .trim()
69
+ .replace(/;$/, '');
70
+ let parsed: unknown;
71
+ try {
72
+ parsed = JSON.parse(json);
73
+ } catch {
74
+ return undefined;
75
+ }
76
+ if (!Array.isArray(parsed)) return undefined;
77
+ const types: JavadocType[] = [];
78
+ for (const item of parsed as { p?: string; l?: string }[]) {
79
+ const pkg = item.p;
80
+ const name = item.l?.replace(/<.*$/, '');
81
+ if (!pkg || !name || !TYPE_NAME_PATTERN.test(pkg) || !TYPE_NAME_PATTERN.test(name)) continue;
82
+ types.push(makeType(pkg, name));
83
+ }
84
+ return types.length > 0 ? types : undefined;
85
+ }
86
+
87
+ async function typesFromPackageDirs(root: string): Promise<JavadocType[]> {
88
+ const packages = await readPackageList(root);
89
+ const types: JavadocType[] = [];
90
+ for (const pkg of packages) {
91
+ if (!TYPE_NAME_PATTERN.test(pkg)) continue;
92
+ const dir = path.join(root, ...pkg.split('.'));
93
+ if (!(await fse.pathExists(dir))) continue;
94
+ for (const file of await fse.readdir(dir)) {
95
+ if (!file.endsWith('.html')) continue;
96
+ const name = file.slice(0, -'.html'.length);
97
+ if (name.startsWith('package-') || !TYPE_NAME_PATTERN.test(name)) continue;
98
+ types.push(makeType(pkg, name));
99
+ }
100
+ }
101
+ return types;
102
+ }
103
+
104
+ async function readPackageList(root: string): Promise<string[]> {
105
+ for (const listFile of ['element-list', 'package-list']) {
106
+ const listPath = path.join(root, listFile);
107
+ if (!(await fse.pathExists(listPath))) continue;
108
+ const raw = await fse.readFile(listPath, 'utf8');
109
+ return raw
110
+ .split('\n')
111
+ .map((line) => line.trim())
112
+ .filter((line) => line.length > 0 && !line.startsWith('module:'));
113
+ }
114
+ throw new Error(`No element-list or package-list found in ${root}`);
115
+ }
116
+
117
+ function makeType(pkg: string, name: string): JavadocType {
118
+ return {
119
+ pkg,
120
+ name,
121
+ file: `${pkg.split('.').join('/')}/${name}.html`,
122
+ slug: `${pkg.replace(/\./g, '-')}/${name}`,
123
+ };
124
+ }
125
+
126
+ function renderPage(type: JavadocType, html: string, slugByFile: Map<string, string>): SdkPage {
127
+ const hast = htmlToHast(html);
128
+ removeHastComments(hast);
129
+ const main =
130
+ findElement(hast, (el) => el.tagName === 'main') ??
131
+ findElement(hast, (el) => el.properties?.id === 'main-content') ??
132
+ findElement(hast, (el) => hasClass(el, 'contentContainer')) ??
133
+ hast;
134
+
135
+ const declaration =
136
+ findElement(main, (el) => hasClass(el, 'class-description')) ??
137
+ findElement(main, (el) => hasClass(el, 'description'));
138
+ const details = findElement(main, (el) => hasClass(el, 'details'));
139
+
140
+ const description = extractDescription(declaration);
141
+ const tag = extractTag(main);
142
+
143
+ const kept = [declaration, details].filter((node): node is Element => node !== undefined);
144
+ const anchors = collectDetailAnchors(kept);
145
+ const resolveHref = makeHrefResolver(type, slugByFile, anchors);
146
+ const signatures: string[] = [];
147
+ for (const node of kept) {
148
+ pruneChrome(node);
149
+ unwrapLists(node);
150
+ resolveLinks(node, resolveHref);
151
+ signatures.push(...codifySignatures(node));
152
+ }
153
+
154
+ const root: HastRoot = { type: 'root', children: kept as RootContent[] };
155
+ let content = hastToMdx(root);
156
+ for (const signature of signatures) {
157
+ content = content
158
+ .split(`\`\`\`\n${signature}\n\`\`\``)
159
+ .join(`\`\`\`java\n${signature}\n\`\`\``);
160
+ }
161
+
162
+ return { slug: type.slug, title: type.name, description, tag, content };
163
+ }
164
+
165
+ function makeHrefResolver(
166
+ type: JavadocType,
167
+ slugByFile: Map<string, string>,
168
+ anchors: Set<string>
169
+ ): RewriteLink {
170
+ const pkgDir = type.pkg.split('.').join('/');
171
+ return (href) => {
172
+ if (/^[a-z][a-z0-9+.-]*:/i.test(href) || href.startsWith('//')) return href;
173
+ if (href.startsWith('#')) {
174
+ const name = decodeFragment(href.slice(1)).split('(')[0] ?? '';
175
+ return anchors.has(name) ? `#${name.toLowerCase()}` : undefined;
176
+ }
177
+ const target = href.split('#')[0] ?? '';
178
+ if (!target.endsWith('.html')) return undefined;
179
+ const resolved = path.posix.normalize(path.posix.join(pkgDir, target));
180
+ const slug = slugByFile.get(resolved);
181
+ return slug === undefined ? undefined : `/${slug}`;
182
+ };
183
+ }
184
+
185
+ function decodeFragment(fragment: string): string {
186
+ try {
187
+ return decodeURIComponent(fragment);
188
+ } catch {
189
+ return fragment;
190
+ }
191
+ }
192
+
193
+ function collectDetailAnchors(nodes: Element[]): Set<string> {
194
+ const anchors = new Set<string>();
195
+ for (const node of nodes) {
196
+ visitElements(node, (el) => {
197
+ if (!hasClass(el, 'detail')) return;
198
+ const id = el.properties.id;
199
+ if (typeof id === 'string') anchors.add(id.split('(')[0] ?? id);
200
+ });
201
+ }
202
+ return anchors;
203
+ }
204
+
205
+ function resolveLinks(node: Element, resolveHref: RewriteLink): void {
206
+ node.children = node.children.flatMap((child): ElementContent[] => {
207
+ if (child.type !== 'element') return [child];
208
+ resolveLinks(child, resolveHref);
209
+ if (child.tagName !== 'a' || typeof child.properties.href !== 'string') return [child];
210
+ const resolved = resolveHref(child.properties.href);
211
+ if (resolved === undefined) return child.children;
212
+ child.properties.href = resolved;
213
+ return [child];
214
+ });
215
+ }
216
+
217
+ function pruneChrome(node: Element): void {
218
+ node.children = node.children.filter((child) => {
219
+ if (child.type !== 'element') return true;
220
+ if (child.tagName === 'hr') return false;
221
+ return !elementClasses(child).some((name) => DROPPED_CLASSES.has(name));
222
+ });
223
+ for (const child of node.children) {
224
+ if (child.type !== 'element') continue;
225
+ if (child.tagName === 'a') delete child.properties.title;
226
+ pruneChrome(child);
227
+ }
228
+ }
229
+
230
+ const UNWRAPPED_LISTS = new Set(['details-list', 'member-list', 'detail-list']);
231
+
232
+ function unwrapLists(node: Element): void {
233
+ node.children = node.children.flatMap((child): ElementContent[] => {
234
+ if (
235
+ child.type !== 'element' ||
236
+ child.tagName !== 'ul' ||
237
+ !elementClasses(child).some((name) => UNWRAPPED_LISTS.has(name))
238
+ ) {
239
+ return [child];
240
+ }
241
+ return child.children.flatMap((item) =>
242
+ item.type === 'element' && item.tagName === 'li' ? item.children : []
243
+ );
244
+ });
245
+ for (const child of node.children) {
246
+ if (child.type === 'element') unwrapLists(child);
247
+ }
248
+ }
249
+
250
+ function codifySignatures(node: Element): string[] {
251
+ const signatures: string[] = [];
252
+ visitElements(node, (el) => {
253
+ if (!hasClass(el, 'member-signature') && !hasClass(el, 'type-signature')) return;
254
+ const signature = textOf(el)
255
+ .replace(/\u00a0/g, ' ')
256
+ .trim();
257
+ signatures.push(signature);
258
+ el.tagName = 'pre';
259
+ el.properties = {};
260
+ el.children = [
261
+ {
262
+ type: 'element',
263
+ tagName: 'code',
264
+ properties: {},
265
+ children: [{ type: 'text', value: signature }],
266
+ },
267
+ ];
268
+ });
269
+ return signatures;
270
+ }
271
+
272
+ function extractTag(scope: Element | HastRoot): string {
273
+ const heading = findElement(scope, (el) => el.tagName === 'h1');
274
+ const title = heading ? textOf(heading).trim() : '';
275
+ if (title.startsWith('Annotation')) return 'ANNOTATION';
276
+ if (title.startsWith('Enum')) return 'ENUM';
277
+ if (title.startsWith('Interface')) return 'INTERFACE';
278
+ return 'CLASS';
279
+ }
280
+
281
+ function extractDescription(declaration: Element | undefined): string | undefined {
282
+ if (!declaration) return undefined;
283
+ const block = findElement(declaration, (el) => hasClass(el, 'block'));
284
+ if (!block) return undefined;
285
+ const text = textOf(block).replace(/\s+/g, ' ').trim();
286
+ if (!text) return undefined;
287
+ const sentence = text.split(/(?<=\.)\s/)[0] ?? text;
288
+ return sentence.length > 160 ? `${sentence.slice(0, 159)}…` : sentence;
289
+ }
290
+
291
+ function elementClasses(el: Element): string[] {
292
+ const className = el.properties?.className;
293
+ return Array.isArray(className) ? className.map(String) : [];
294
+ }
295
+
296
+ function hasClass(el: Element, name: string): boolean {
297
+ return elementClasses(el).includes(name);
298
+ }
299
+
300
+ function findElement(
301
+ scope: Element | HastRoot,
302
+ predicate: (el: Element) => boolean
303
+ ): Element | undefined {
304
+ for (const child of scope.children as (RootContent | ElementContent)[]) {
305
+ if (child.type !== 'element') continue;
306
+ if (predicate(child)) return child;
307
+ const found = findElement(child, predicate);
308
+ if (found) return found;
309
+ }
310
+ return undefined;
311
+ }
312
+
313
+ function visitElements(scope: Element | HastRoot, visitor: (el: Element) => void): void {
314
+ for (const child of scope.children as (RootContent | ElementContent)[]) {
315
+ if (child.type !== 'element') continue;
316
+ visitor(child);
317
+ visitElements(child, visitor);
318
+ }
319
+ }
320
+
321
+ function textOf(node: Element): string {
322
+ let text = '';
323
+ for (const child of node.children) {
324
+ if (child.type === 'text') text += child.value;
325
+ else if (child.type === 'element' && child.tagName !== 'script' && child.tagName !== 'style') {
326
+ text += textOf(child);
327
+ }
328
+ }
329
+ return text;
330
+ }