@mintlify/scraping 4.0.1161 → 4.0.1162
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__/findSdkPage.test.ts +106 -0
- package/__test__/sdk-go.test.ts +1671 -0
- package/bin/sdk/convert.d.ts +9 -1
- package/bin/sdk/convert.js +173 -8
- package/bin/sdk/convert.js.map +1 -1
- package/bin/sdk/converters/go.d.ts +2 -0
- package/bin/sdk/converters/go.js +828 -0
- package/bin/sdk/converters/go.js.map +1 -0
- package/bin/sdk/converters/typedoc.js +7 -45
- package/bin/sdk/converters/typedoc.js.map +1 -1
- package/bin/sdk/findSdkPage.d.ts +5 -3
- package/bin/sdk/findSdkPage.js +59 -5
- package/bin/sdk/findSdkPage.js.map +1 -1
- package/bin/sdk/generateSdkReference.js +2 -0
- package/bin/sdk/generateSdkReference.js.map +1 -1
- package/bin/sdk/mdx.d.ts +14 -0
- package/bin/sdk/mdx.js +103 -0
- package/bin/sdk/mdx.js.map +1 -0
- package/bin/sdk/types.d.ts +2 -0
- package/bin/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/src/sdk/convert.ts +196 -9
- package/src/sdk/converters/go.ts +925 -0
- package/src/sdk/converters/typedoc.ts +9 -52
- package/src/sdk/findSdkPage.ts +70 -14
- package/src/sdk/generateSdkReference.ts +2 -0
- package/src/sdk/mdx.ts +123 -0
- package/src/sdk/types.ts +2 -0
|
@@ -3,6 +3,7 @@ import fse from 'fs-extra';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
|
|
5
5
|
import { escapeMdxText, markdownToMdx } from '../convert.js';
|
|
6
|
+
import { inlineCode, linkedCode, responseField, truncate } from '../mdx.js';
|
|
6
7
|
import type { SdkNavGroup, SdkPage, SdkPageTarget, SdkReference } from '../types.js';
|
|
7
8
|
|
|
8
9
|
const KIND = {
|
|
@@ -489,7 +490,7 @@ class TypedocRenderer {
|
|
|
489
490
|
const lines: string[] = ['## Members'];
|
|
490
491
|
for (const member of node.children ?? []) {
|
|
491
492
|
lines.push(
|
|
492
|
-
|
|
493
|
+
responseField(
|
|
493
494
|
member.name,
|
|
494
495
|
member.type?.type === 'literal' ? JSON.stringify(member.type.value) : '',
|
|
495
496
|
false,
|
|
@@ -547,24 +548,7 @@ class TypedocRenderer {
|
|
|
547
548
|
const typeHref = tokens.length === 1 && only?.href ? `/${only.href}` : undefined;
|
|
548
549
|
const linked = typeHref ? undefined : this.linkedType(type);
|
|
549
550
|
const content = [linked && `**Type:** ${linked}`, body].filter(Boolean).join('\n\n');
|
|
550
|
-
return
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
private responseField(
|
|
554
|
-
name: string,
|
|
555
|
-
type: string,
|
|
556
|
-
optional: boolean,
|
|
557
|
-
body?: string,
|
|
558
|
-
typeHref?: string
|
|
559
|
-
): string {
|
|
560
|
-
const typeAttr = type ? ` type=${this.jsxString(truncate(type, 80))}` : '';
|
|
561
|
-
const typeHrefAttr = typeHref ? ` typeHref=${this.jsxString(typeHref)}` : '';
|
|
562
|
-
const requiredAttr = optional ? '' : ' required';
|
|
563
|
-
return `<ResponseField name=${this.jsxString(name)}${typeAttr}${typeHrefAttr}${requiredAttr}>\n${indent(body || '')}\n</ResponseField>`;
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
private jsxString(value: string): string {
|
|
567
|
-
return `{${JSON.stringify(value)}}`;
|
|
551
|
+
return responseField(name, this.type(type), optional, content, typeHref);
|
|
568
552
|
}
|
|
569
553
|
|
|
570
554
|
private codeBlock(code: string): string {
|
|
@@ -623,18 +607,12 @@ class TypedocRenderer {
|
|
|
623
607
|
}
|
|
624
608
|
|
|
625
609
|
private linkedType(type?: TypedocType): string | undefined {
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
else merged.push({ ...token });
|
|
633
|
-
}
|
|
634
|
-
const children = merged
|
|
635
|
-
.map((token) => (token.href ? `[${token.text}](/${token.href})` : this.jsxString(token.text)))
|
|
636
|
-
.join('');
|
|
637
|
-
return `<code>${children}</code>`;
|
|
610
|
+
return linkedCode(
|
|
611
|
+
this.typeTokens(type).map((token) => ({
|
|
612
|
+
text: token.text,
|
|
613
|
+
...(token.href ? { href: `/${token.href}` } : {}),
|
|
614
|
+
}))
|
|
615
|
+
);
|
|
638
616
|
}
|
|
639
617
|
|
|
640
618
|
private typeTokens(type?: TypedocType): TypeToken[] {
|
|
@@ -725,24 +703,3 @@ function headingText(title: unknown): string {
|
|
|
725
703
|
function detailHeading(title: string, depth: number): string {
|
|
726
704
|
return depth <= MAX_HEADING_DEPTH ? `${'#'.repeat(depth)} ${title}` : `**${title}**`;
|
|
727
705
|
}
|
|
728
|
-
|
|
729
|
-
function indent(text: string): string {
|
|
730
|
-
return text
|
|
731
|
-
.split('\n')
|
|
732
|
-
.map((line) => (line ? ` ${line}` : line))
|
|
733
|
-
.join('\n');
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
function truncate(text: string, max: number): string {
|
|
737
|
-
return text.length > max ? `${text.slice(0, max - 1)}…` : text;
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
function inlineCode(text: string): string {
|
|
741
|
-
const code = text.trim();
|
|
742
|
-
if (!code) return text;
|
|
743
|
-
const start = text.indexOf(code);
|
|
744
|
-
const longestRun = Math.max(0, ...(code.match(/`+/g) ?? []).map((run) => run.length));
|
|
745
|
-
const fence = '`'.repeat(longestRun + 1);
|
|
746
|
-
const pad = code.startsWith('`') || code.endsWith('`') ? ' ' : '';
|
|
747
|
-
return `${text.slice(0, start)}${fence}${pad}${code}${pad}${fence}${text.slice(start + code.length)}`;
|
|
748
|
-
}
|
package/src/sdk/findSdkPage.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { ResolvedSdkTarget, SdkSymbolKind } from '@mintlify/models';
|
|
|
3
3
|
import type { SdkPage, SdkPageTarget, SdkReference } from './types.js';
|
|
4
4
|
|
|
5
5
|
const KIND_DIRS: Record<SdkSymbolKind, string> = {
|
|
6
|
+
package: 'packages',
|
|
6
7
|
class: 'classes',
|
|
7
8
|
interface: 'interfaces',
|
|
8
9
|
enum: 'enums',
|
|
@@ -13,20 +14,41 @@ const KIND_DIRS: Record<SdkSymbolKind, string> = {
|
|
|
13
14
|
property: 'properties',
|
|
14
15
|
};
|
|
15
16
|
|
|
17
|
+
type SdkTargetRequest = Pick<ResolvedSdkTarget, 'kind' | 'name' | 'parent' | 'namespace'>;
|
|
18
|
+
|
|
19
|
+
// An explicit namespace (object-form frontmatter) must match the page's exactly;
|
|
20
|
+
// a dotted shorthand such as `audio.client.Foo` matches any trailing part of
|
|
21
|
+
// the page's namespace.
|
|
16
22
|
export function matchesSdkPageTarget(
|
|
17
23
|
pageTarget: SdkPageTarget | undefined,
|
|
18
|
-
target:
|
|
24
|
+
target: SdkTargetRequest
|
|
19
25
|
): boolean {
|
|
20
26
|
if (!pageTarget) return false;
|
|
21
|
-
if (pageTarget.kind !== target.kind
|
|
22
|
-
if (target.
|
|
27
|
+
if (pageTarget.kind !== target.kind) return false;
|
|
28
|
+
if (target.namespace !== undefined && pageTarget.namespace !== target.namespace) return false;
|
|
29
|
+
const exact = target.namespace !== undefined;
|
|
30
|
+
if (!matchesSdkName(pageTarget.name, pageTarget.namespace, target.name, exact)) return false;
|
|
31
|
+
if (target.parent) {
|
|
32
|
+
return (
|
|
33
|
+
pageTarget.parent !== undefined &&
|
|
34
|
+
matchesSdkName(pageTarget.parent, pageTarget.namespace, target.parent, exact)
|
|
35
|
+
);
|
|
36
|
+
}
|
|
23
37
|
return pageTarget.parent === undefined;
|
|
24
38
|
}
|
|
25
39
|
|
|
26
|
-
function
|
|
27
|
-
|
|
28
|
-
|
|
40
|
+
function matchesSdkName(
|
|
41
|
+
name: string,
|
|
42
|
+
namespace: string | undefined,
|
|
43
|
+
requested: string,
|
|
44
|
+
exact: boolean
|
|
29
45
|
): boolean {
|
|
46
|
+
if (requested === name) return true;
|
|
47
|
+
if (exact || namespace === undefined) return false;
|
|
48
|
+
return `.${namespace}.${name}`.endsWith(`.${requested}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function matchesSdkPageHeuristic(page: SdkPage, target: SdkTargetRequest): boolean {
|
|
30
52
|
if (target.parent) {
|
|
31
53
|
return (
|
|
32
54
|
page.title === `${target.parent}.${target.name}` ||
|
|
@@ -46,20 +68,54 @@ function slugify(name: string): string {
|
|
|
46
68
|
return name.replace(/[^a-zA-Z0-9-_.]+/g, '-').replace(/^-+|-+$/g, '') || 'item';
|
|
47
69
|
}
|
|
48
70
|
|
|
71
|
+
function qualifiedName(target: SdkPageTarget): string {
|
|
72
|
+
const name = target.parent ? `${target.parent}.${target.name}` : target.name;
|
|
73
|
+
return target.namespace ? `${target.namespace}.${name}` : name;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Several pages can match a bare name. When their targets carry namespaces the
|
|
77
|
+
// request can be qualified, so that is an error listing the candidates; when
|
|
78
|
+
// they do not (TypeDoc has no namespaces), the first match stands as before.
|
|
49
79
|
export function findSdkPage(
|
|
50
80
|
reference: SdkReference,
|
|
51
|
-
target:
|
|
81
|
+
target: SdkTargetRequest
|
|
52
82
|
): SdkPage | undefined {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
83
|
+
const matches = reference.pages.filter((page) => matchesSdkPageTarget(page.target, target));
|
|
84
|
+
if (matches.length > 1) {
|
|
85
|
+
// `package client` names the root package `client` outright even when a
|
|
86
|
+
// `client/` directory also ends in that name; only the shorthand that fits
|
|
87
|
+
// several pages equally is ambiguous.
|
|
88
|
+
const requested = target.parent ? `${target.parent}.${target.name}` : target.name;
|
|
89
|
+
const exact = matches.filter(
|
|
90
|
+
(page) => qualifiedName(page.target as SdkPageTarget) === requested
|
|
91
|
+
);
|
|
92
|
+
if (exact.length === 1) return exact[0];
|
|
93
|
+
}
|
|
94
|
+
if (matches.length > 1 && matches.some((page) => page.target?.namespace)) {
|
|
95
|
+
const candidates = matches.map((page) => qualifiedName(page.target as SdkPageTarget));
|
|
96
|
+
const requested = target.parent ? `${target.parent}.${target.name}` : target.name;
|
|
97
|
+
throw new Error(
|
|
98
|
+
`SDK symbol is ambiguous: ${target.kind} ${requested} matches ${candidates.join(', ')}; qualify it with its package`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
if (matches[0] || target.namespace !== undefined) return matches[0];
|
|
102
|
+
// The slug/title heuristic covers converters that emit no target metadata
|
|
103
|
+
// and TypeDoc members addressed through their namespace (`Auth.Client.login`
|
|
104
|
+
// is the title, while the target only knows `parent: Client`). A page whose
|
|
105
|
+
// target names a different symbol is never picked up by title.
|
|
106
|
+
return reference.pages.find(
|
|
107
|
+
(page) => agreesWithTarget(page.target, target) && matchesSdkPageHeuristic(page, target)
|
|
56
108
|
);
|
|
57
109
|
}
|
|
58
110
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
)
|
|
111
|
+
function agreesWithTarget(pageTarget: SdkPageTarget | undefined, target: SdkTargetRequest) {
|
|
112
|
+
if (!pageTarget) return true;
|
|
113
|
+
if (pageTarget.kind !== target.kind || pageTarget.name !== target.name) return false;
|
|
114
|
+
if (!target.parent) return pageTarget.parent === undefined;
|
|
115
|
+
return pageTarget.parent !== undefined && `.${target.parent}`.endsWith(`.${pageTarget.parent}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function renderSdkTarget(reference: SdkReference, target: SdkTargetRequest): SdkPage {
|
|
63
119
|
const page = findSdkPage(reference, target);
|
|
64
120
|
if (!page) {
|
|
65
121
|
const qualified = target.parent ? `${target.parent}.${target.name}` : target.name;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { convertDocfx } from './converters/docfx.js';
|
|
2
|
+
import { convertGo } from './converters/go.js';
|
|
2
3
|
import { convertJavadoc } from './converters/javadoc.js';
|
|
3
4
|
import { convertPhpdoc } from './converters/phpdoc.js';
|
|
4
5
|
import { convertSphinx } from './converters/sphinx.js';
|
|
@@ -11,6 +12,7 @@ const converters: Record<SdkFormat, SdkConverter> = {
|
|
|
11
12
|
javadoc: convertJavadoc,
|
|
12
13
|
sphinx: convertSphinx,
|
|
13
14
|
phpdoc: convertPhpdoc,
|
|
15
|
+
go: convertGo,
|
|
14
16
|
};
|
|
15
17
|
|
|
16
18
|
export async function generateSdkReference(
|
package/src/sdk/mdx.ts
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
export type LinkedToken = { text: string; href?: string };
|
|
2
|
+
|
|
3
|
+
export function jsxString(value: string): string {
|
|
4
|
+
return `{${JSON.stringify(value)}}`;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function responseField(
|
|
8
|
+
name: string,
|
|
9
|
+
type: string,
|
|
10
|
+
optional: boolean,
|
|
11
|
+
body?: string,
|
|
12
|
+
typeHref?: string,
|
|
13
|
+
id?: string
|
|
14
|
+
): string {
|
|
15
|
+
const idAttr = id ? ` id=${jsxString(id)}` : '';
|
|
16
|
+
const typeAttr = type ? ` type=${jsxString(truncate(type, 80))}` : '';
|
|
17
|
+
const typeHrefAttr = typeHref ? ` typeHref=${jsxString(typeHref)}` : '';
|
|
18
|
+
const requiredAttr = optional ? '' : ' required';
|
|
19
|
+
return `<ResponseField name=${jsxString(name)}${idAttr}${typeAttr}${typeHrefAttr}${requiredAttr}>\n${indent(body || '')}\n</ResponseField>`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function escapeInline(text: string): string {
|
|
23
|
+
return collapseWhitespace(text).replace(/[\\`*_{}[\]<>]/g, '\\$&');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Code spans and link text are inline; a blank line inside them would end the
|
|
27
|
+
// paragraph and let the remainder parse as MDX. Line endings (\n, \r, and
|
|
28
|
+
// \r\n, which CommonMark treats alike) become one space along with the
|
|
29
|
+
// indentation that follows them; other whitespace is kept, so a string literal
|
|
30
|
+
// such as "a b" keeps its spacing. The pattern cannot backtrack, so a long
|
|
31
|
+
// run of spaces costs linear time.
|
|
32
|
+
export function collapseWhitespace(text: string): string {
|
|
33
|
+
return text.replace(/[\r\n]+[ \t]*/g, ' ');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const SAFE_SCHEMES = /^(?:https?|mailto):/i;
|
|
37
|
+
|
|
38
|
+
// Keeps http(s), mailto, fragments and relative paths; drops javascript:,
|
|
39
|
+
// data:, and every other scheme so artifact text cannot produce an active link.
|
|
40
|
+
// Browsers drop C0 controls, spaces, and DEL while parsing a URL and read a
|
|
41
|
+
// backslash as a slash, so the check runs on the URL as a browser would see it.
|
|
42
|
+
// The returned URL keeps its interior spaces (a file can be named "Getting
|
|
43
|
+
// Started.md") and loses only what a browser drops: surrounding controls and
|
|
44
|
+
// spaces, and tabs or newlines anywhere.
|
|
45
|
+
//
|
|
46
|
+
// A character reference before the first `/`, `?`, or `#` (after any leading
|
|
47
|
+
// slashes, so `//evil.com` cannot become `//evil.com`) is refused: Markdown
|
|
48
|
+
// leaves `:` and `:` in reference definitions for MDX to decode, so
|
|
49
|
+
// `javascript:x` would become `javascript:x` after this check ran.
|
|
50
|
+
export function safeHref(url: string): string | undefined {
|
|
51
|
+
const cleaned = url.replace(/[\u0000-\u0020\u007f]/g, '');
|
|
52
|
+
const kept = trimControls(url).replace(/[\t\n\r]/g, '');
|
|
53
|
+
if (cleaned === '' || cleaned.startsWith('#')) return kept;
|
|
54
|
+
if (/&[#a-z]/i.test(cleaned.replace(/^[\\/]+/, '').split(/[/?#]/, 1)[0] ?? '')) return undefined;
|
|
55
|
+
if (
|
|
56
|
+
/^[a-z][a-z0-9+.-]*:/i.test(cleaned) ||
|
|
57
|
+
/^[\\/][\\/]/.test(cleaned) ||
|
|
58
|
+
cleaned.startsWith('\\')
|
|
59
|
+
) {
|
|
60
|
+
return SAFE_SCHEMES.test(cleaned) ? kept : undefined;
|
|
61
|
+
}
|
|
62
|
+
return kept;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Trims C0 controls and spaces from both ends with two index scans; a regex
|
|
66
|
+
// with a `$` alternative retries from every position of a long interior run.
|
|
67
|
+
function trimControls(url: string): string {
|
|
68
|
+
let start = 0;
|
|
69
|
+
let end = url.length;
|
|
70
|
+
while (start < end && url.charCodeAt(start) <= 0x20) start += 1;
|
|
71
|
+
while (end > start && url.charCodeAt(end - 1) <= 0x20) end -= 1;
|
|
72
|
+
return url.slice(start, end);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function encodeHref(text: string): string {
|
|
76
|
+
return text
|
|
77
|
+
.split('/')
|
|
78
|
+
.map((segment) =>
|
|
79
|
+
encodeURIComponent(segment).replace(
|
|
80
|
+
/[()!'*]/g,
|
|
81
|
+
(char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
.join('/');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function linkedCode(tokens: LinkedToken[]): string | undefined {
|
|
88
|
+
if (!tokens.some((token) => token.href)) return undefined;
|
|
89
|
+
const merged: LinkedToken[] = [];
|
|
90
|
+
for (const token of tokens) {
|
|
91
|
+
const last = merged.at(-1);
|
|
92
|
+
if (last && !last.href && !token.href) last.text += token.text;
|
|
93
|
+
else merged.push({ ...token });
|
|
94
|
+
}
|
|
95
|
+
const children = merged
|
|
96
|
+
.map((token) =>
|
|
97
|
+
token.href
|
|
98
|
+
? `[${escapeInline(token.text)}](${token.href})`
|
|
99
|
+
: jsxString(collapseWhitespace(token.text))
|
|
100
|
+
)
|
|
101
|
+
.join('');
|
|
102
|
+
return `<code>${children}</code>`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function indent(text: string): string {
|
|
106
|
+
return text
|
|
107
|
+
.split('\n')
|
|
108
|
+
.map((line) => (line ? ` ${line}` : line))
|
|
109
|
+
.join('\n');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function truncate(text: string, max: number): string {
|
|
113
|
+
return text.length > max ? `${text.slice(0, max - 1)}…` : text;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function inlineCode(text: string): string {
|
|
117
|
+
const code = collapseWhitespace(text).trim();
|
|
118
|
+
if (!code) return text;
|
|
119
|
+
const longestRun = Math.max(0, ...(code.match(/`+/g) ?? []).map((run) => run.length));
|
|
120
|
+
const fence = '`'.repeat(longestRun + 1);
|
|
121
|
+
const pad = code.startsWith('`') || code.endsWith('`') ? ' ' : '';
|
|
122
|
+
return `${fence}${pad}${code}${pad}${fence}`;
|
|
123
|
+
}
|
package/src/sdk/types.ts
CHANGED
|
@@ -7,6 +7,7 @@ export type SdkPageTarget = {
|
|
|
7
7
|
kind: SdkSymbolKind;
|
|
8
8
|
name: string;
|
|
9
9
|
parent?: string;
|
|
10
|
+
namespace?: string;
|
|
10
11
|
};
|
|
11
12
|
|
|
12
13
|
export type SdkPage = {
|
|
@@ -15,6 +16,7 @@ export type SdkPage = {
|
|
|
15
16
|
description?: string;
|
|
16
17
|
tag?: string;
|
|
17
18
|
icon?: string;
|
|
19
|
+
intro?: string;
|
|
18
20
|
content: string;
|
|
19
21
|
target?: SdkPageTarget;
|
|
20
22
|
};
|