@mintlify/scraping 4.0.907 → 4.0.908
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/__test__/sdk-docfx.test.ts +204 -0
- package/__test__/sdk-javadoc.test.ts +150 -0
- package/__test__/sdk-phpdoc.test.ts +134 -0
- package/__test__/sdk-sphinx.test.ts +84 -0
- package/__test__/sdk-typedoc.test.ts +138 -0
- package/bin/index.d.ts +3 -0
- package/bin/index.js +2 -0
- package/bin/index.js.map +1 -1
- package/bin/sdk/convert.d.ts +6 -0
- package/bin/sdk/convert.js +90 -0
- package/bin/sdk/convert.js.map +1 -0
- package/bin/sdk/converters/docfx.d.ts +2 -0
- package/bin/sdk/converters/docfx.js +402 -0
- package/bin/sdk/converters/docfx.js.map +1 -0
- package/bin/sdk/converters/javadoc.d.ts +2 -0
- package/bin/sdk/converters/javadoc.js +318 -0
- package/bin/sdk/converters/javadoc.js.map +1 -0
- package/bin/sdk/converters/phpdoc.d.ts +2 -0
- package/bin/sdk/converters/phpdoc.js +351 -0
- package/bin/sdk/converters/phpdoc.js.map +1 -0
- package/bin/sdk/converters/sphinx.d.ts +2 -0
- package/bin/sdk/converters/sphinx.js +267 -0
- package/bin/sdk/converters/sphinx.js.map +1 -0
- package/bin/sdk/converters/typedoc.d.ts +2 -0
- package/bin/sdk/converters/typedoc.js +318 -0
- package/bin/sdk/converters/typedoc.js.map +1 -0
- package/bin/sdk/generateSdkReference.d.ts +2 -0
- package/bin/sdk/generateSdkReference.js +20 -0
- package/bin/sdk/generateSdkReference.js.map +1 -0
- package/bin/sdk/types.d.ts +19 -0
- package/bin/sdk/types.js +2 -0
- package/bin/sdk/types.js.map +1 -0
- package/bin/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +5 -4
- package/src/index.ts +3 -0
- package/src/sdk/convert.ts +101 -0
- package/src/sdk/converters/docfx.ts +491 -0
- package/src/sdk/converters/javadoc.ts +330 -0
- package/src/sdk/converters/phpdoc.ts +446 -0
- package/src/sdk/converters/sphinx.ts +277 -0
- package/src/sdk/converters/typedoc.ts +423 -0
- package/src/sdk/generateSdkReference.ts +25 -0
- package/src/sdk/types.ts +24 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import fse from 'fs-extra';
|
|
2
|
+
import type { Element, ElementContent, Root as HastRoot } from 'hast';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { visit, SKIP } from 'unist-util-visit';
|
|
5
|
+
|
|
6
|
+
import { htmlToHast } from '../../pipeline/root.js';
|
|
7
|
+
import { turnChildrenIntoMdx } from '../../utils/children.js';
|
|
8
|
+
import { removeHastComments } from '../../utils/hastComments.js';
|
|
9
|
+
import { hastToMdx } from '../convert.js';
|
|
10
|
+
import type { SdkNavGroup, SdkPage, SdkReference } from '../types.js';
|
|
11
|
+
|
|
12
|
+
const SKIP_PAGES = new Set(['genindex', 'search', 'py-modindex']);
|
|
13
|
+
const LANG_MARKER = '@@mintlang:';
|
|
14
|
+
const FENCE_MARKER = /(`{3,})[^\S\n]*\n[> ]*@@mintlang:([\w+-]+)@@[^\S\n]*\n/g;
|
|
15
|
+
const ADMONITION_TAGS: Record<string, string> = {
|
|
16
|
+
note: 'Note',
|
|
17
|
+
hint: 'Tip',
|
|
18
|
+
tip: 'Tip',
|
|
19
|
+
important: 'Info',
|
|
20
|
+
seealso: 'Info',
|
|
21
|
+
warning: 'Warning',
|
|
22
|
+
caution: 'Warning',
|
|
23
|
+
danger: 'Warning',
|
|
24
|
+
error: 'Warning',
|
|
25
|
+
attention: 'Warning',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type SphinxDoc = { slug: string; title: string; body: string; tag?: string };
|
|
29
|
+
|
|
30
|
+
export async function convertSphinx(sourcePath: string): Promise<SdkReference> {
|
|
31
|
+
const docs = await loadDocs(sourcePath);
|
|
32
|
+
const slugs = new Set(docs.map((doc) => doc.slug));
|
|
33
|
+
|
|
34
|
+
const guides = docs
|
|
35
|
+
.filter((doc) => !doc.tag)
|
|
36
|
+
.sort((left, right) =>
|
|
37
|
+
left.slug === 'index' ? -1 : right.slug === 'index' ? 1 : left.slug.localeCompare(right.slug)
|
|
38
|
+
);
|
|
39
|
+
const modules = docs
|
|
40
|
+
.filter((doc) => doc.tag)
|
|
41
|
+
.sort((left, right) => left.slug.localeCompare(right.slug));
|
|
42
|
+
|
|
43
|
+
const pages = [...guides, ...modules].map((doc) => renderPage(doc, slugs));
|
|
44
|
+
const groups: SdkNavGroup[] = [];
|
|
45
|
+
if (guides.length) groups.push({ group: 'Getting Started', pages: guides.map((d) => d.slug) });
|
|
46
|
+
if (modules.length) groups.push({ group: 'API Reference', pages: modules.map((d) => d.slug) });
|
|
47
|
+
|
|
48
|
+
return { pages, groups };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function loadDocs(sourcePath: string): Promise<SphinxDoc[]> {
|
|
52
|
+
const docs: SphinxDoc[] = [];
|
|
53
|
+
for (const file of await collectFjsonFiles(sourcePath)) {
|
|
54
|
+
const raw = (await fse.readJson(file)) as {
|
|
55
|
+
body?: string;
|
|
56
|
+
title?: string;
|
|
57
|
+
current_page_name?: string;
|
|
58
|
+
};
|
|
59
|
+
const slug =
|
|
60
|
+
raw.current_page_name ??
|
|
61
|
+
path
|
|
62
|
+
.relative(sourcePath, file)
|
|
63
|
+
.split(path.sep)
|
|
64
|
+
.join('/')
|
|
65
|
+
.replace(/\.fjson$/, '');
|
|
66
|
+
if (SKIP_PAGES.has(slug) || slug.startsWith('_modules')) continue;
|
|
67
|
+
if (!raw.body?.trim()) continue;
|
|
68
|
+
docs.push({
|
|
69
|
+
slug,
|
|
70
|
+
title: cleanTitle(raw.title ?? slug),
|
|
71
|
+
body: raw.body,
|
|
72
|
+
tag: /<dl class="py[ "]/.test(raw.body) ? 'MODULE' : undefined,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return docs;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function collectFjsonFiles(dir: string): Promise<string[]> {
|
|
79
|
+
const files: string[] = [];
|
|
80
|
+
for (const entry of await fse.readdir(dir, { withFileTypes: true })) {
|
|
81
|
+
if (entry.name.startsWith('_')) continue;
|
|
82
|
+
const fullPath = path.join(dir, entry.name);
|
|
83
|
+
if (entry.isDirectory()) files.push(...(await collectFjsonFiles(fullPath)));
|
|
84
|
+
else if (entry.name.endsWith('.fjson')) files.push(fullPath);
|
|
85
|
+
}
|
|
86
|
+
return files.sort();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function renderPage(doc: SphinxDoc, slugs: Set<string>): SdkPage {
|
|
90
|
+
const hast = htmlToHast(doc.body);
|
|
91
|
+
removeHastComments(hast);
|
|
92
|
+
removeSphinxChrome(hast);
|
|
93
|
+
const description = extractDescription(hast);
|
|
94
|
+
rewriteLinks(hast, doc.slug, slugs);
|
|
95
|
+
transformAdmonitions(hast);
|
|
96
|
+
markHighlightLanguages(hast);
|
|
97
|
+
transformPyDls(hast, 0);
|
|
98
|
+
const content = hastToMdx(hast)
|
|
99
|
+
.replace(FENCE_MARKER, '$1$2\n')
|
|
100
|
+
.replace(/^[> ]*@@mintlang:[\w+-]+@@\n?/gm, '');
|
|
101
|
+
return {
|
|
102
|
+
slug: doc.slug,
|
|
103
|
+
title: doc.title,
|
|
104
|
+
description,
|
|
105
|
+
tag: doc.tag,
|
|
106
|
+
content,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function removeSphinxChrome(hast: HastRoot): void {
|
|
111
|
+
let removedH1 = false;
|
|
112
|
+
visit(hast, 'element', (node: Element, index, parent) => {
|
|
113
|
+
if (!parent || typeof index !== 'number') return;
|
|
114
|
+
const isHeaderlink = node.tagName === 'a' && classList(node).includes('headerlink');
|
|
115
|
+
const isViewcode =
|
|
116
|
+
node.tagName === 'a' &&
|
|
117
|
+
node.children.some(
|
|
118
|
+
(child) => child.type === 'element' && classList(child).includes('viewcode-link')
|
|
119
|
+
);
|
|
120
|
+
const isFirstH1 = node.tagName === 'h1' && !removedH1;
|
|
121
|
+
if (isFirstH1) removedH1 = true;
|
|
122
|
+
if (isHeaderlink || isViewcode || isFirstH1) {
|
|
123
|
+
parent.children.splice(index, 1);
|
|
124
|
+
return index;
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function rewriteLinks(hast: HastRoot, fromSlug: string, slugs: Set<string>): void {
|
|
130
|
+
visit(hast, 'element', (node: Element) => {
|
|
131
|
+
if (node.tagName !== 'a' || typeof node.properties.href !== 'string') return;
|
|
132
|
+
const rewritten = rewriteHref(node.properties.href, fromSlug, slugs);
|
|
133
|
+
if (rewritten === undefined) delete node.properties.href;
|
|
134
|
+
else node.properties.href = rewritten;
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function rewriteHref(href: string, fromSlug: string, slugs: Set<string>): string | undefined {
|
|
139
|
+
if (/^(https?:|mailto:|#)/.test(href)) return href;
|
|
140
|
+
const [target = ''] = href.split('#');
|
|
141
|
+
let resolved = path.posix
|
|
142
|
+
.normalize(path.posix.join(path.posix.dirname(fromSlug), target))
|
|
143
|
+
.replace(/\/$/, '')
|
|
144
|
+
.replace(/\.html?$/, '');
|
|
145
|
+
if (resolved === '.' || resolved === '') resolved = 'index';
|
|
146
|
+
return slugs.has(resolved) ? `/${resolved}` : undefined;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function transformAdmonitions(hast: HastRoot): void {
|
|
150
|
+
visit(hast, 'element', (node: Element) => {
|
|
151
|
+
if (node.tagName !== 'div' || !classList(node).includes('admonition')) return;
|
|
152
|
+
const type = classList(node).find((name) => name in ADMONITION_TAGS);
|
|
153
|
+
const children = node.children.filter(
|
|
154
|
+
(child) => !(child.type === 'element' && classList(child).includes('admonition-title'))
|
|
155
|
+
);
|
|
156
|
+
node.tagName = ADMONITION_TAGS[type ?? ''] ?? 'Note';
|
|
157
|
+
node.properties = {};
|
|
158
|
+
node.children = turnChildrenIntoMdx(children) as ElementContent[];
|
|
159
|
+
return SKIP;
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function markHighlightLanguages(hast: HastRoot): void {
|
|
164
|
+
visit(hast, 'element', (node: Element) => {
|
|
165
|
+
const lang = classList(node)
|
|
166
|
+
.find((name) => name.startsWith('highlight-'))
|
|
167
|
+
?.slice('highlight-'.length);
|
|
168
|
+
if (!lang || lang === 'default' || lang === 'none') return;
|
|
169
|
+
visit(node, 'element', (inner: Element) => {
|
|
170
|
+
if (inner.tagName !== 'pre') return;
|
|
171
|
+
inner.children.unshift({ type: 'text', value: `${LANG_MARKER}${lang}@@\n` });
|
|
172
|
+
return SKIP;
|
|
173
|
+
});
|
|
174
|
+
return SKIP;
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function transformPyDls(node: HastRoot | Element, depth: number): void {
|
|
179
|
+
const children: ElementContent[] = [];
|
|
180
|
+
for (const child of node.children as ElementContent[]) {
|
|
181
|
+
if (child.type === 'element' && child.tagName === 'dl' && classList(child).includes('py')) {
|
|
182
|
+
children.push(...explodePyDl(child, depth));
|
|
183
|
+
} else {
|
|
184
|
+
if (child.type === 'element') transformPyDls(child, depth);
|
|
185
|
+
children.push(child);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
node.children = children;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function explodePyDl(dl: Element, depth: number): ElementContent[] {
|
|
192
|
+
const callable = classList(dl).some((name) => ['method', 'function'].includes(name));
|
|
193
|
+
const out: ElementContent[] = [];
|
|
194
|
+
for (const child of dl.children) {
|
|
195
|
+
if (child.type !== 'element') continue;
|
|
196
|
+
if (child.tagName === 'dt' && classList(child).includes('sig')) {
|
|
197
|
+
const name = findDescName(child);
|
|
198
|
+
if (name) out.push(heading(Math.min(3 + depth, 6), callable ? `${name}()` : name));
|
|
199
|
+
out.push(pythonCodeBlock(textOf(child).replace(/\s+/g, ' ').trim()));
|
|
200
|
+
} else if (child.tagName === 'dd') {
|
|
201
|
+
transformPyDls(child, depth + 1);
|
|
202
|
+
out.push(...child.children);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return out;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function findDescName(node: Element): string | undefined {
|
|
209
|
+
let name: string | undefined;
|
|
210
|
+
visit(node, 'element', (inner: Element) => {
|
|
211
|
+
if (!classList(inner).includes('descname')) return;
|
|
212
|
+
name = textOf(inner).trim();
|
|
213
|
+
return SKIP;
|
|
214
|
+
});
|
|
215
|
+
return name;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function heading(level: number, text: string): Element {
|
|
219
|
+
return {
|
|
220
|
+
type: 'element',
|
|
221
|
+
tagName: `h${level}`,
|
|
222
|
+
properties: {},
|
|
223
|
+
children: [{ type: 'text', value: text }],
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function pythonCodeBlock(code: string): Element {
|
|
228
|
+
return {
|
|
229
|
+
type: 'element',
|
|
230
|
+
tagName: 'pre',
|
|
231
|
+
properties: {},
|
|
232
|
+
children: [{ type: 'text', value: `${LANG_MARKER}python@@\n${code}` }],
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function extractDescription(hast: HastRoot): string | undefined {
|
|
237
|
+
let description: string | undefined;
|
|
238
|
+
visit(hast, 'element', (node: Element, _index, parent) => {
|
|
239
|
+
if (description || node.tagName !== 'p') return;
|
|
240
|
+
if (parent && parent.type === 'element' && parent.tagName === 'li') return SKIP;
|
|
241
|
+
const text = textOf(node).replace(/\s+/g, ' ').trim();
|
|
242
|
+
if (!text || text.startsWith('Bases:')) return;
|
|
243
|
+
const sentence = text.split(/(?<=\.)\s/)[0] ?? text;
|
|
244
|
+
description = sentence.length > 160 ? `${sentence.slice(0, 159)}…` : sentence;
|
|
245
|
+
});
|
|
246
|
+
return description;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function textOf(node: ElementContent): string {
|
|
250
|
+
if (node.type === 'text') return node.value;
|
|
251
|
+
if ('children' in node) return node.children.map(textOf).join('');
|
|
252
|
+
return '';
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function classList(node: Element): string[] {
|
|
256
|
+
const value = node.properties.className;
|
|
257
|
+
return Array.isArray(value)
|
|
258
|
+
? value.map(String)
|
|
259
|
+
: typeof value === 'string'
|
|
260
|
+
? value.split(' ')
|
|
261
|
+
: [];
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function cleanTitle(title: string): string {
|
|
265
|
+
return title
|
|
266
|
+
.replace(/<[^>]+>/g, '')
|
|
267
|
+
.replace(/&/g, '&')
|
|
268
|
+
.replace(/</g, '<')
|
|
269
|
+
.replace(/>/g, '>')
|
|
270
|
+
.replace(/"/g, '"')
|
|
271
|
+
.replace(/&#(\d+);/g, (_, code: string) => {
|
|
272
|
+
const codePoint = Number(code);
|
|
273
|
+
return codePoint <= 0x10ffff ? String.fromCodePoint(codePoint) : `&#${code};`;
|
|
274
|
+
})
|
|
275
|
+
.replace(/\s+/g, ' ')
|
|
276
|
+
.trim();
|
|
277
|
+
}
|
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
import fse from 'fs-extra';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { markdownToMdx } from '../convert.js';
|
|
5
|
+
import type { SdkNavGroup, SdkPage, SdkReference } from '../types.js';
|
|
6
|
+
|
|
7
|
+
const KIND = {
|
|
8
|
+
project: 1,
|
|
9
|
+
module: 2,
|
|
10
|
+
namespace: 4,
|
|
11
|
+
enum: 8,
|
|
12
|
+
enumMember: 16,
|
|
13
|
+
variable: 32,
|
|
14
|
+
function: 64,
|
|
15
|
+
class: 128,
|
|
16
|
+
interface: 256,
|
|
17
|
+
constructor: 512,
|
|
18
|
+
property: 1024,
|
|
19
|
+
method: 2048,
|
|
20
|
+
callSignature: 4096,
|
|
21
|
+
indexSignature: 8192,
|
|
22
|
+
constructorSignature: 16384,
|
|
23
|
+
parameter: 32768,
|
|
24
|
+
typeLiteral: 65536,
|
|
25
|
+
accessor: 262144,
|
|
26
|
+
getSignature: 524288,
|
|
27
|
+
setSignature: 1048576,
|
|
28
|
+
typeAlias: 2097152,
|
|
29
|
+
} as const;
|
|
30
|
+
|
|
31
|
+
type CommentPart = { kind: string; text: string; target?: number | string };
|
|
32
|
+
type Comment = { summary?: CommentPart[]; blockTags?: { tag: string; content: CommentPart[] }[] };
|
|
33
|
+
type TypedocType = {
|
|
34
|
+
type?: string;
|
|
35
|
+
name?: string;
|
|
36
|
+
value?: unknown;
|
|
37
|
+
types?: TypedocType[];
|
|
38
|
+
elementType?: TypedocType;
|
|
39
|
+
typeArguments?: TypedocType[];
|
|
40
|
+
target?: number | TypedocType;
|
|
41
|
+
declaration?: TypedocNode;
|
|
42
|
+
elements?: TypedocType[];
|
|
43
|
+
checkType?: TypedocType;
|
|
44
|
+
extendsType?: TypedocType;
|
|
45
|
+
trueType?: TypedocType;
|
|
46
|
+
falseType?: TypedocType;
|
|
47
|
+
operator?: string;
|
|
48
|
+
indexType?: TypedocType;
|
|
49
|
+
objectType?: TypedocType;
|
|
50
|
+
qualifiedName?: string;
|
|
51
|
+
head?: string;
|
|
52
|
+
tail?: [TypedocType, string][];
|
|
53
|
+
};
|
|
54
|
+
type TypedocNode = {
|
|
55
|
+
id: number;
|
|
56
|
+
name: string;
|
|
57
|
+
kind: number;
|
|
58
|
+
comment?: Comment;
|
|
59
|
+
children?: TypedocNode[];
|
|
60
|
+
signatures?: TypedocNode[];
|
|
61
|
+
parameters?: TypedocNode[];
|
|
62
|
+
type?: TypedocType;
|
|
63
|
+
flags?: { isOptional?: boolean; isStatic?: boolean; isReadonly?: boolean; isPrivate?: boolean };
|
|
64
|
+
defaultValue?: string;
|
|
65
|
+
getSignature?: TypedocNode;
|
|
66
|
+
setSignature?: TypedocNode;
|
|
67
|
+
extendedTypes?: TypedocType[];
|
|
68
|
+
implementedTypes?: TypedocType[];
|
|
69
|
+
inheritedFrom?: { name: string };
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const SECTIONS: { kind: number; group: string; dir: string; tag: string }[] = [
|
|
73
|
+
{ kind: KIND.class, group: 'Classes', dir: 'classes', tag: 'CLASS' },
|
|
74
|
+
{ kind: KIND.interface, group: 'Interfaces', dir: 'interfaces', tag: 'INTERFACE' },
|
|
75
|
+
{ kind: KIND.enum, group: 'Enumerations', dir: 'enums', tag: 'ENUM' },
|
|
76
|
+
{ kind: KIND.function, group: 'Functions', dir: 'functions', tag: 'FUNCTION' },
|
|
77
|
+
{ kind: KIND.typeAlias, group: 'Type Aliases', dir: 'types', tag: 'TYPE' },
|
|
78
|
+
{ kind: KIND.variable, group: 'Variables', dir: 'variables', tag: 'VARIABLE' },
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
export async function convertTypedoc(sourcePath: string): Promise<SdkReference> {
|
|
82
|
+
const project = (await fse.readJson(sourcePath)) as TypedocNode;
|
|
83
|
+
const modules =
|
|
84
|
+
project.children?.some((child) => child.kind === KIND.module) === true
|
|
85
|
+
? (project.children?.filter((child) => child.kind === KIND.module) ?? [])
|
|
86
|
+
: [project];
|
|
87
|
+
const multiModule = modules.length > 1;
|
|
88
|
+
|
|
89
|
+
const slugById = new Map<number, string>();
|
|
90
|
+
for (const module of modules) {
|
|
91
|
+
const prefix = multiModule ? slugify(module.name) : '';
|
|
92
|
+
for (const { node, namespacePath } of collectDeclarations(module)) {
|
|
93
|
+
const section = SECTIONS.find((entry) => entry.kind === node.kind);
|
|
94
|
+
if (!section) continue;
|
|
95
|
+
slugById.set(
|
|
96
|
+
node.id,
|
|
97
|
+
path.posix.join(prefix, ...namespacePath.map(slugify), section.dir, slugify(node.name))
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const renderer = new TypedocRenderer(slugById);
|
|
103
|
+
const pages: SdkPage[] = [];
|
|
104
|
+
const groups: SdkNavGroup[] = [];
|
|
105
|
+
|
|
106
|
+
for (const module of modules) {
|
|
107
|
+
const prefix = multiModule ? `${module.name} ` : '';
|
|
108
|
+
const declarations = collectDeclarations(module);
|
|
109
|
+
for (const section of SECTIONS) {
|
|
110
|
+
const entries = declarations
|
|
111
|
+
.filter(({ node }) => node.kind === section.kind && !node.flags?.isPrivate)
|
|
112
|
+
.sort((left, right) => qualifiedName(left).localeCompare(qualifiedName(right)));
|
|
113
|
+
if (entries.length === 0) continue;
|
|
114
|
+
const sectionPages = entries.map((entry) =>
|
|
115
|
+
renderer.renderPage(entry.node, section, qualifiedName(entry))
|
|
116
|
+
);
|
|
117
|
+
pages.push(...sectionPages);
|
|
118
|
+
groups.push({ group: `${prefix}${section.group}`, pages: sectionPages.map((p) => p.slug) });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return { pages, groups };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
type Declaration = { node: TypedocNode; namespacePath: string[] };
|
|
126
|
+
|
|
127
|
+
function qualifiedName({ node, namespacePath }: Declaration): string {
|
|
128
|
+
return [...namespacePath, node.name].join('.');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function collectDeclarations(container: TypedocNode, namespacePath: string[] = []): Declaration[] {
|
|
132
|
+
const declarations: Declaration[] = [];
|
|
133
|
+
for (const child of container.children ?? []) {
|
|
134
|
+
if (child.kind === KIND.namespace) {
|
|
135
|
+
declarations.push(...collectDeclarations(child, [...namespacePath, child.name]));
|
|
136
|
+
} else {
|
|
137
|
+
declarations.push({ node: child, namespacePath });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return declarations;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function slugify(name: string): string {
|
|
144
|
+
return name.replace(/[^a-zA-Z0-9-_.]+/g, '-').replace(/^-+|-+$/g, '') || 'item';
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
class TypedocRenderer {
|
|
148
|
+
constructor(private slugById: Map<number, string>) {}
|
|
149
|
+
|
|
150
|
+
renderPage(node: TypedocNode, section: { dir: string; tag: string }, title?: string): SdkPage {
|
|
151
|
+
const slug = this.slugById.get(node.id) ?? path.posix.join(section.dir, slugify(node.name));
|
|
152
|
+
const lines: string[] = [];
|
|
153
|
+
const summary = this.comment(node.comment);
|
|
154
|
+
if (summary) lines.push(summary);
|
|
155
|
+
|
|
156
|
+
switch (node.kind) {
|
|
157
|
+
case KIND.class:
|
|
158
|
+
case KIND.interface:
|
|
159
|
+
lines.push(...this.renderClassLike(node));
|
|
160
|
+
break;
|
|
161
|
+
case KIND.enum:
|
|
162
|
+
lines.push(...this.renderEnum(node));
|
|
163
|
+
break;
|
|
164
|
+
case KIND.function:
|
|
165
|
+
lines.push(...this.renderSignatures(node, 2));
|
|
166
|
+
break;
|
|
167
|
+
case KIND.typeAlias:
|
|
168
|
+
case KIND.variable:
|
|
169
|
+
lines.push(
|
|
170
|
+
this.codeBlock(
|
|
171
|
+
`${node.kind === KIND.variable ? 'const' : 'type'} ${node.name}${node.kind === KIND.typeAlias ? ' =' : ':'} ${this.type(node.type)}`
|
|
172
|
+
)
|
|
173
|
+
);
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
slug,
|
|
179
|
+
title: title ?? node.name,
|
|
180
|
+
description: this.firstSentence(node.comment),
|
|
181
|
+
tag: section.tag,
|
|
182
|
+
content: lines.filter(Boolean).join('\n\n'),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private renderClassLike(node: TypedocNode): string[] {
|
|
187
|
+
const lines: string[] = [];
|
|
188
|
+
const heritage = [
|
|
189
|
+
...(node.extendedTypes?.length
|
|
190
|
+
? [`extends ${node.extendedTypes.map((t) => this.type(t)).join(', ')}`]
|
|
191
|
+
: []),
|
|
192
|
+
...(node.implementedTypes?.length
|
|
193
|
+
? [`implements ${node.implementedTypes.map((t) => this.type(t)).join(', ')}`]
|
|
194
|
+
: []),
|
|
195
|
+
];
|
|
196
|
+
if (heritage.length) {
|
|
197
|
+
lines.push(
|
|
198
|
+
this.codeBlock(
|
|
199
|
+
`${node.kind === KIND.class ? 'class' : 'interface'} ${node.name} ${heritage.join(' ')}`
|
|
200
|
+
)
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const members = (node.children ?? []).filter((child) => !child.flags?.isPrivate);
|
|
205
|
+
const constructors = members.filter((child) => child.kind === KIND.constructor);
|
|
206
|
+
const properties = members
|
|
207
|
+
.filter((child) => child.kind === KIND.property || child.kind === KIND.accessor)
|
|
208
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
209
|
+
const methods = members
|
|
210
|
+
.filter((child) => child.kind === KIND.method)
|
|
211
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
212
|
+
|
|
213
|
+
for (const ctor of constructors) {
|
|
214
|
+
lines.push('## Constructor');
|
|
215
|
+
lines.push(...this.renderSignatures(ctor, 3));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (properties.length) {
|
|
219
|
+
lines.push('## Properties');
|
|
220
|
+
for (const property of properties) {
|
|
221
|
+
const type = property.kind === KIND.accessor ? property.getSignature?.type : property.type;
|
|
222
|
+
const optional = property.flags?.isOptional === true;
|
|
223
|
+
lines.push(
|
|
224
|
+
this.responseField(
|
|
225
|
+
property.name,
|
|
226
|
+
this.type(type),
|
|
227
|
+
optional,
|
|
228
|
+
this.comment(property.comment ?? property.getSignature?.comment)
|
|
229
|
+
)
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (methods.length) {
|
|
235
|
+
lines.push('## Methods');
|
|
236
|
+
for (const method of methods) {
|
|
237
|
+
lines.push(`### ${method.name}()`);
|
|
238
|
+
lines.push(...this.renderSignatures(method, 4));
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return lines;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
private renderEnum(node: TypedocNode): string[] {
|
|
246
|
+
const lines: string[] = ['## Members'];
|
|
247
|
+
for (const member of node.children ?? []) {
|
|
248
|
+
lines.push(
|
|
249
|
+
this.responseField(
|
|
250
|
+
member.name,
|
|
251
|
+
member.type?.type === 'literal' ? JSON.stringify(member.type.value) : '',
|
|
252
|
+
false,
|
|
253
|
+
this.comment(member.comment)
|
|
254
|
+
)
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
return lines;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private renderSignatures(node: TypedocNode, depth: number): string[] {
|
|
261
|
+
const lines: string[] = [];
|
|
262
|
+
for (const signature of node.signatures ?? []) {
|
|
263
|
+
const params = (signature.parameters ?? [])
|
|
264
|
+
.map(
|
|
265
|
+
(param) =>
|
|
266
|
+
`${param.name}${param.flags?.isOptional || param.defaultValue !== undefined ? '?' : ''}: ${this.type(param.type)}`
|
|
267
|
+
)
|
|
268
|
+
.join(', ');
|
|
269
|
+
lines.push(this.codeBlock(`${signature.name}(${params}): ${this.type(signature.type)}`));
|
|
270
|
+
const comment = this.comment(signature.comment);
|
|
271
|
+
if (comment) lines.push(comment);
|
|
272
|
+
if (signature.parameters?.length) {
|
|
273
|
+
lines.push(`${'#'.repeat(depth)} Parameters`);
|
|
274
|
+
for (const param of signature.parameters) {
|
|
275
|
+
lines.push(
|
|
276
|
+
this.responseField(
|
|
277
|
+
param.name,
|
|
278
|
+
this.type(param.type),
|
|
279
|
+
param.flags?.isOptional === true || param.defaultValue !== undefined,
|
|
280
|
+
this.comment(param.comment)
|
|
281
|
+
)
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
const returns = signature.comment?.blockTags?.find((tag) => tag.tag === '@returns');
|
|
286
|
+
const returnType = this.type(signature.type);
|
|
287
|
+
if (returnType && returnType !== 'void') {
|
|
288
|
+
lines.push(`${'#'.repeat(depth)} Returns`);
|
|
289
|
+
lines.push(
|
|
290
|
+
`\`${escapeInlineCode(returnType)}\`${returns ? `\n\n${this.parts(returns.content)}` : ''}`
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return lines;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
private responseField(name: string, type: string, optional: boolean, body?: string): string {
|
|
298
|
+
const typeAttr = type ? ` type=${this.jsxString(truncate(type, 80))}` : '';
|
|
299
|
+
const requiredAttr = optional ? '' : ' required';
|
|
300
|
+
return `<ResponseField name=${this.jsxString(name)}${typeAttr}${requiredAttr}>\n${indent(body || '')}\n</ResponseField>`;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
private jsxString(value: string): string {
|
|
304
|
+
return `{${JSON.stringify(value)}}`;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
private codeBlock(code: string): string {
|
|
308
|
+
return `\`\`\`typescript\n${code}\n\`\`\``;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
private comment(comment?: Comment): string {
|
|
312
|
+
if (!comment?.summary?.length) return '';
|
|
313
|
+
const markdown = this.parts(comment.summary);
|
|
314
|
+
const deprecated = comment.blockTags?.find((tag) => tag.tag === '@deprecated');
|
|
315
|
+
const example = comment.blockTags?.find((tag) => tag.tag === '@example');
|
|
316
|
+
const chunks = [markdown];
|
|
317
|
+
if (deprecated)
|
|
318
|
+
chunks.push(
|
|
319
|
+
`<Warning>Deprecated${deprecated.content.length ? `: ${this.parts(deprecated.content)}` : ''}</Warning>`
|
|
320
|
+
);
|
|
321
|
+
if (example) chunks.push(this.parts(example.content));
|
|
322
|
+
return chunks.filter(Boolean).join('\n\n');
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private parts(parts: CommentPart[]): string {
|
|
326
|
+
const markdown = parts
|
|
327
|
+
.map((part) => {
|
|
328
|
+
if (part.kind === 'inline-tag') {
|
|
329
|
+
if (typeof part.target === 'number' && this.slugById.has(part.target)) {
|
|
330
|
+
return `[${part.text}](/${this.slugById.get(part.target)})`;
|
|
331
|
+
}
|
|
332
|
+
if (typeof part.target === 'string') return `[${part.text}](${part.target})`;
|
|
333
|
+
return `\`${part.text}\``;
|
|
334
|
+
}
|
|
335
|
+
return part.text;
|
|
336
|
+
})
|
|
337
|
+
.join('');
|
|
338
|
+
return markdownToMdx(markdown);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private firstSentence(comment?: Comment): string | undefined {
|
|
342
|
+
if (!comment?.summary?.length) return undefined;
|
|
343
|
+
const text = comment.summary
|
|
344
|
+
.filter((part) => part.kind === 'text')
|
|
345
|
+
.map((part) => part.text)
|
|
346
|
+
.join('')
|
|
347
|
+
.replace(/!?\[([^\]]*)\]\([^)]*\)/g, '$1')
|
|
348
|
+
.replace(/[`*_]/g, '')
|
|
349
|
+
.replace(/\s+/g, ' ')
|
|
350
|
+
.trim();
|
|
351
|
+
if (!text) return undefined;
|
|
352
|
+
const sentence = text.split(/(?<=\.)\s/)[0] ?? text;
|
|
353
|
+
return truncate(sentence, 160);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
type(type?: TypedocType): string {
|
|
357
|
+
if (!type) return 'void';
|
|
358
|
+
switch (type.type) {
|
|
359
|
+
case 'intrinsic':
|
|
360
|
+
return type.name ?? 'unknown';
|
|
361
|
+
case 'reference': {
|
|
362
|
+
const args = type.typeArguments?.length
|
|
363
|
+
? `<${type.typeArguments.map((arg) => this.type(arg)).join(', ')}>`
|
|
364
|
+
: '';
|
|
365
|
+
return `${type.name ?? 'unknown'}${args}`;
|
|
366
|
+
}
|
|
367
|
+
case 'union':
|
|
368
|
+
return (type.types ?? []).map((entry) => this.type(entry)).join(' | ');
|
|
369
|
+
case 'intersection':
|
|
370
|
+
return (type.types ?? []).map((entry) => this.type(entry)).join(' & ');
|
|
371
|
+
case 'array':
|
|
372
|
+
return `${this.type(type.elementType)}[]`;
|
|
373
|
+
case 'literal':
|
|
374
|
+
return JSON.stringify(type.value);
|
|
375
|
+
case 'tuple':
|
|
376
|
+
return `[${(type.elements ?? []).map((entry) => this.type(entry)).join(', ')}]`;
|
|
377
|
+
case 'reflection':
|
|
378
|
+
return this.reflection(type.declaration);
|
|
379
|
+
case 'typeOperator':
|
|
380
|
+
return `${type.operator} ${this.type(type.target as TypedocType)}`;
|
|
381
|
+
case 'indexedAccess':
|
|
382
|
+
return `${this.type(type.objectType)}[${this.type(type.indexType)}]`;
|
|
383
|
+
case 'conditional':
|
|
384
|
+
return `${this.type(type.checkType)} extends ${this.type(type.extendsType)} ? ${this.type(type.trueType)} : ${this.type(type.falseType)}`;
|
|
385
|
+
case 'query':
|
|
386
|
+
return `typeof ${this.type(type.target as TypedocType)}`;
|
|
387
|
+
case 'templateLiteral':
|
|
388
|
+
return 'string';
|
|
389
|
+
default:
|
|
390
|
+
return type.name ?? 'unknown';
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
private reflection(declaration?: TypedocNode): string {
|
|
395
|
+
if (!declaration) return 'object';
|
|
396
|
+
if (declaration.signatures?.length) {
|
|
397
|
+
const signature = declaration.signatures[0]!;
|
|
398
|
+
const params = (signature.parameters ?? [])
|
|
399
|
+
.map((param) => `${param.name}: ${this.type(param.type)}`)
|
|
400
|
+
.join(', ');
|
|
401
|
+
return `(${params}) => ${this.type(signature.type)}`;
|
|
402
|
+
}
|
|
403
|
+
if (declaration.children?.length) {
|
|
404
|
+
return `{ ${declaration.children.map((child) => `${child.name}: ${this.type(child.type)}`).join('; ')} }`;
|
|
405
|
+
}
|
|
406
|
+
return 'object';
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function indent(text: string): string {
|
|
411
|
+
return text
|
|
412
|
+
.split('\n')
|
|
413
|
+
.map((line) => (line ? ` ${line}` : line))
|
|
414
|
+
.join('\n');
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function truncate(text: string, max: number): string {
|
|
418
|
+
return text.length > max ? `${text.slice(0, max - 1)}…` : text;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function escapeInlineCode(text: string): string {
|
|
422
|
+
return text.replace(/`/g, '');
|
|
423
|
+
}
|