@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.
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,204 @@
1
+ import fse from 'fs-extra';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
5
+
6
+ import { convertDocfx } from '../src/sdk/converters/docfx.js';
7
+ import type { SdkReference } from '../src/sdk/types.js';
8
+
9
+ const clientYml = `### YamlMime:ManagedReference
10
+ items:
11
+ - uid: Acme.Widgets.Client
12
+ id: Client
13
+ parent: Acme.Widgets
14
+ children:
15
+ - Acme.Widgets.Client.Name
16
+ - Acme.Widgets.Client.Timeout
17
+ - Acme.Widgets.Client.PaintAsync(Acme.Widgets.Models.Color,System.Int32)
18
+ name: Client
19
+ fullName: Acme.Widgets.Client
20
+ type: Class
21
+ namespace: Acme.Widgets
22
+ summary: A widget client. Prefer <xref href="Acme.Widgets.Models.Color" data-throw-if-not-resolved="false"></xref> over raw values.
23
+ syntax:
24
+ content: public class Client
25
+ - uid: Acme.Widgets.Client.Name
26
+ id: Name
27
+ parent: Acme.Widgets.Client
28
+ name: Name
29
+ type: Property
30
+ namespace: Acme.Widgets
31
+ summary: The client name.
32
+ syntax:
33
+ content: public string Name { get; set; }
34
+ return:
35
+ type: System.String
36
+ - uid: Acme.Widgets.Client.Timeout
37
+ id: Timeout
38
+ parent: Acme.Widgets.Client
39
+ name: Timeout
40
+ type: Property
41
+ namespace: Acme.Widgets
42
+ summary: Timeout backed by <xref href="System.TimeSpan" data-throw-if-not-resolved="false"></xref>.
43
+ syntax:
44
+ content: public TimeSpan? Timeout { get; set; }
45
+ return:
46
+ type: System.TimeSpan
47
+ - uid: Acme.Widgets.Client.PaintAsync(Acme.Widgets.Models.Color,System.Int32)
48
+ id: PaintAsync(Acme.Widgets.Models.Color,System.Int32)
49
+ parent: Acme.Widgets.Client
50
+ name: PaintAsync(Color, int)
51
+ type: Method
52
+ namespace: Acme.Widgets
53
+ summary: Paints using a <code>Color</code>.
54
+ syntax:
55
+ content: public Task<bool> PaintAsync(Color color, int coats = 1)
56
+ parameters:
57
+ - id: color
58
+ type: Acme.Widgets.Models.Color
59
+ description: The <xref href="Acme.Widgets.Models.Color" data-throw-if-not-resolved="false"></xref> to apply.
60
+ - id: coats
61
+ type: System.Int32
62
+ description: Number of coats.
63
+ return:
64
+ type: System.Threading.Tasks.Task{System.Boolean}
65
+ references:
66
+ - uid: System.String
67
+ name: string
68
+ - uid: System.TimeSpan
69
+ name: TimeSpan
70
+ - uid: System.Int32
71
+ name: int
72
+ - uid: Acme.Widgets.Models.Color
73
+ name: Color
74
+ - uid: System.Threading.Tasks.Task{System.Boolean}
75
+ name: Task<bool>
76
+ `;
77
+
78
+ const colorYml = `### YamlMime:ManagedReference
79
+ items:
80
+ - uid: Acme.Widgets.Models.Color
81
+ id: Color
82
+ parent: Acme.Widgets.Models
83
+ children:
84
+ - Acme.Widgets.Models.Color.Red
85
+ name: Color
86
+ fullName: Acme.Widgets.Models.Color
87
+ type: Enum
88
+ namespace: Acme.Widgets.Models
89
+ summary: Available colors for <code>IDictionary&lt;string, string&gt;</code> lookups.
90
+ syntax:
91
+ content: public enum Color
92
+ - uid: Acme.Widgets.Models.Color.Red
93
+ id: Red
94
+ parent: Acme.Widgets.Models.Color
95
+ name: Red
96
+ type: Field
97
+ namespace: Acme.Widgets.Models
98
+ summary: The color red.
99
+ syntax:
100
+ content: Red = 0
101
+ `;
102
+
103
+ const tocYml = `### YamlMime:TableOfContent
104
+ items:
105
+ - uid: Acme.Widgets
106
+ name: Acme.Widgets
107
+ type: Namespace
108
+ items:
109
+ - uid: Acme.Widgets.Client
110
+ name: Client
111
+ type: Class
112
+ - uid: Acme.Widgets.Models
113
+ name: Acme.Widgets.Models
114
+ type: Namespace
115
+ items:
116
+ - uid: Acme.Widgets.Models.Color
117
+ name: Color
118
+ type: Enum
119
+ `;
120
+
121
+ describe('convertDocfx', () => {
122
+ let sourcePath: string;
123
+ let reference: SdkReference;
124
+
125
+ beforeAll(async () => {
126
+ sourcePath = await fse.mkdtemp(path.join(os.tmpdir(), 'docfx-test-'));
127
+ await fse.writeFile(path.join(sourcePath, 'Acme.Widgets.Client.yml'), clientYml);
128
+ await fse.writeFile(path.join(sourcePath, 'Acme.Widgets.Models.Color.yml'), colorYml);
129
+ await fse.writeFile(path.join(sourcePath, 'toc.yml'), tocYml);
130
+ reference = await convertDocfx(sourcePath);
131
+ });
132
+
133
+ afterAll(async () => {
134
+ await fse.remove(sourcePath);
135
+ });
136
+
137
+ it('creates one page per top-level type with namespace-scoped slugs', () => {
138
+ expect(reference.pages.map((page) => page.slug)).toEqual(['widgets/client', 'models/color']);
139
+ expect(reference.pages.map((page) => page.tag)).toEqual(['CLASS', 'ENUM']);
140
+ expect(reference.pages.map((page) => page.title)).toEqual(['Client', 'Color']);
141
+ });
142
+
143
+ it('groups pages by namespace in toc order', () => {
144
+ expect(reference.groups).toEqual([
145
+ { group: 'Acme.Widgets', pages: ['widgets/client'] },
146
+ { group: 'Acme.Widgets.Models', pages: ['models/color'] },
147
+ ]);
148
+ });
149
+
150
+ it('renders properties as ResponseFields with nullability-driven required', () => {
151
+ const content = reference.pages[0]?.content ?? '';
152
+ expect(content).toContain('<ResponseField name={"Name"} type={"string"} required>');
153
+ expect(content).toContain('<ResponseField name={"Timeout"} type={"TimeSpan"}>');
154
+ });
155
+
156
+ it('renders method signatures, parameters, and returns', () => {
157
+ const content = reference.pages[0]?.content ?? '';
158
+ expect(content).toContain('### PaintAsync()');
159
+ expect(content).toContain(
160
+ '```csharp\npublic Task<bool> PaintAsync(Color color, int coats = 1)\n```'
161
+ );
162
+ expect(content).toContain('<ResponseField name={"color"} type={"Color"} required>');
163
+ expect(content).toContain('<ResponseField name={"coats"} type={"int"}>');
164
+ expect(content).toContain('`Task<bool>`');
165
+ });
166
+
167
+ it('resolves xrefs to page links or inline code', () => {
168
+ const content = reference.pages[0]?.content ?? '';
169
+ expect(content).toContain('Prefer [Color](/models/color) over raw values.');
170
+ expect(content).toContain('Timeout backed by `TimeSpan`.');
171
+ expect(content).toContain('The [Color](/models/color) to apply.');
172
+ });
173
+
174
+ it('keeps namespace slugs unique when names differ only by separator', async () => {
175
+ const dir = await fse.mkdtemp(path.join(os.tmpdir(), 'docfx-collide-'));
176
+ const typeYml = (namespace: string) => `### YamlMime:ManagedReference
177
+ items:
178
+ - uid: ${namespace}.Thing
179
+ id: Thing
180
+ name: Thing
181
+ fullName: ${namespace}.Thing
182
+ type: Class
183
+ namespace: ${namespace}
184
+ syntax:
185
+ content: public class Thing
186
+ `;
187
+ await fse.writeFile(path.join(dir, 'a.yml'), typeYml('Acme.Sub.Zone'));
188
+ await fse.writeFile(path.join(dir, 'b.yml'), typeYml('Acme.Sub-Zone'));
189
+ const result = await convertDocfx(dir);
190
+ const slugs = result.pages.map((page) => page.slug);
191
+ expect(new Set(slugs).size).toBe(slugs.length);
192
+ expect(result.pages).toHaveLength(2);
193
+ await fse.rm(dir, { recursive: true, force: true });
194
+ });
195
+
196
+ it('escapes generic angle brackets in prose', () => {
197
+ const content = reference.pages[1]?.content ?? '';
198
+ expect(content).toContain('`IDictionary<string, string>`');
199
+ expect(content).toContain('<ResponseField name={"Red"} type={"0"} required>');
200
+ expect(reference.pages[1]?.description).toBe(
201
+ 'Available colors for IDictionary<string, string> lookups.'
202
+ );
203
+ });
204
+ });
@@ -0,0 +1,150 @@
1
+ import fse from 'fs-extra';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
5
+
6
+ import { convertJavadoc } from '../src/sdk/converters/javadoc.js';
7
+
8
+ const TRAVERSAL_INDEX = 'typeSearchIndex = [{"p":"com/../../../etc","l":"Passwd"}];';
9
+ import type { SdkReference } from '../src/sdk/types.js';
10
+
11
+ const page = (title: string, body: string) => `<!DOCTYPE HTML>
12
+ <html lang="en">
13
+ <head><title>${title} (fake API)</title></head>
14
+ <body class="class-declaration-page">
15
+ <main role="main">
16
+ ${body}
17
+ </main>
18
+ </body>
19
+ </html>`;
20
+
21
+ const greeterHtml = page(
22
+ 'Greeter',
23
+ `<div class="header">
24
+ <h1 title="Class Greeter" class="title">Class Greeter</h1>
25
+ </div>
26
+ <section class="class-description" id="class-description">
27
+ <hr>
28
+ <div class="type-signature"><span class="modifiers">public class </span><span class="element-name type-name-label">Greeter</span></div>
29
+ <div class="block">Greets people politely. Supports many languages.</div>
30
+ </section>
31
+ <section class="summary">
32
+ <ul class="summary-list">
33
+ <li>
34
+ <section class="method-summary" id="method-summary">
35
+ <h2>Method Summary</h2>
36
+ <div class="summary-table three-column-summary">
37
+ <div class="col-first even-row-color"><code>String</code></div>
38
+ <div class="col-second even-row-color"><code><a href="#greet(java.lang.String)" class="member-name-link">greet</a>(String name)</code></div>
39
+ <div class="col-last even-row-color"><div class="block">SUMMARY_ROW_MARKER greet a person.</div></div>
40
+ </div>
41
+ <div class="inherited-list">
42
+ <h3>Methods inherited from class java.lang.Object</h3>
43
+ <code>equals, hashCode, toString</code>
44
+ </div>
45
+ </section>
46
+ </li>
47
+ </ul>
48
+ </section>
49
+ <section class="details">
50
+ <ul class="details-list">
51
+ <li>
52
+ <section class="method-details" id="method-detail">
53
+ <h2>Method Details</h2>
54
+ <ul class="member-list">
55
+ <li>
56
+ <section class="detail" id="greet(java.lang.String)">
57
+ <h3>greet</h3>
58
+ <div class="member-signature"><span class="modifiers">public</span>&nbsp;<span class="return-type">String</span>&nbsp;<span class="element-name">greet</span><span class="parameters">(String&nbsp;name)</span></div>
59
+ <div class="block">Builds a greeting for the given <a href="Person.html" title="interface in com.example">Person</a> name.</div>
60
+ <dl class="notes">
61
+ <dt>Parameters:</dt>
62
+ <dd><code>name</code> - who to greet.</dd>
63
+ </dl>
64
+ </section>
65
+ </li>
66
+ </ul>
67
+ </section>
68
+ </li>
69
+ </ul>
70
+ </section>`
71
+ );
72
+
73
+ const personHtml = page(
74
+ 'Person',
75
+ `<div class="header">
76
+ <h1 title="Interface Person" class="title">Interface Person</h1>
77
+ </div>
78
+ <section class="class-description" id="class-description">
79
+ <hr>
80
+ <div class="type-signature"><span class="modifiers">public interface </span><span class="element-name type-name-label">Person</span></div>
81
+ <div class="block">A person that can be greeted.</div>
82
+ </section>`
83
+ );
84
+
85
+ let dir: string;
86
+ let ref: SdkReference;
87
+
88
+ beforeAll(async () => {
89
+ dir = await fse.mkdtemp(path.join(os.tmpdir(), 'javadoc-test-'));
90
+ await fse.writeFile(path.join(dir, 'element-list'), 'com.example\n');
91
+ const pkgDir = path.join(dir, 'com', 'example');
92
+ await fse.mkdirp(pkgDir);
93
+ await fse.writeFile(path.join(pkgDir, 'Greeter.html'), greeterHtml);
94
+ await fse.writeFile(path.join(pkgDir, 'Person.html'), personHtml);
95
+ await fse.writeFile(
96
+ path.join(pkgDir, 'package-summary.html'),
97
+ page('com.example', '<h1>Package com.example</h1>')
98
+ );
99
+ ref = await convertJavadoc(dir);
100
+ });
101
+
102
+ afterAll(async () => {
103
+ await fse.remove(dir);
104
+ });
105
+
106
+ describe('convertJavadoc', () => {
107
+ it('emits one page per type with package-based slugs', () => {
108
+ expect(ref.pages.map((p) => p.slug).sort()).toEqual([
109
+ 'com-example/Greeter',
110
+ 'com-example/Person',
111
+ ]);
112
+ });
113
+
114
+ it('groups types by package', () => {
115
+ expect(ref.groups).toEqual([
116
+ { group: 'com.example', pages: ['com-example/Greeter', 'com-example/Person'] },
117
+ ]);
118
+ });
119
+
120
+ it('extracts title, tag, and first-sentence description', () => {
121
+ const greeter = ref.pages.find((p) => p.slug === 'com-example/Greeter');
122
+ expect(greeter?.title).toBe('Greeter');
123
+ expect(greeter?.tag).toBe('CLASS');
124
+ expect(greeter?.description).toBe('Greets people politely.');
125
+ const person = ref.pages.find((p) => p.slug === 'com-example/Person');
126
+ expect(person?.tag).toBe('INTERFACE');
127
+ });
128
+
129
+ it('keeps method detail sections and rewrites type links', () => {
130
+ const content = ref.pages.find((p) => p.slug === 'com-example/Greeter')?.content ?? '';
131
+ expect(content).toContain('### greet');
132
+ expect(content).toContain('public String greet(String name)');
133
+ expect(content).toContain('`name` - who to greet.');
134
+ expect(content).toContain('[Person](/com-example/Person)');
135
+ });
136
+
137
+ it('drops summary tables and inherited-method boilerplate', () => {
138
+ const content = ref.pages.find((p) => p.slug === 'com-example/Greeter')?.content ?? '';
139
+ expect(content).not.toContain('SUMMARY_ROW_MARKER');
140
+ expect(content).not.toContain('Method Summary');
141
+ expect(content).not.toContain('Methods inherited from');
142
+ });
143
+
144
+ it('ignores search-index entries with invalid package names', async () => {
145
+ const evil = await fse.mkdtemp(path.join(os.tmpdir(), 'javadoc-evil-'));
146
+ await fse.writeFile(path.join(evil, 'type-search-index.js'), TRAVERSAL_INDEX);
147
+ await expect(convertJavadoc(evil)).rejects.toThrow(/No element-list or package-list found/);
148
+ await fse.rm(evil, { recursive: true, force: true });
149
+ });
150
+ });
@@ -0,0 +1,134 @@
1
+ import fse from 'fs-extra';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
5
+
6
+ import { convertPhpdoc } from '../src/sdk/converters/phpdoc.js';
7
+ import type { SdkReference } from '../src/sdk/types.js';
8
+
9
+ const STRUCTURE_XML = `<?xml version="1.0"?>
10
+ <project name="Acme-PHP">
11
+ <file path="Widget/Greeter.php" hash="abc">
12
+ <namespace-alias name="\\Acme\\SDK\\Widget"/>
13
+ <class final="true" abstract="false" namespace="\\Acme\\SDK\\Widget" line="10">
14
+ <name>Greeter</name>
15
+ <full_name>\\Acme\\SDK\\Widget\\Greeter</full_name>
16
+ <docblock line="10">
17
+ <description>Greets people politely.</description>
18
+ <long-description>Supports optional shouting.</long-description>
19
+ </docblock>
20
+ <implements>\\Acme\\SDK\\Contract\\GreeterInterface</implements>
21
+ <property namespace="\\Acme\\SDK\\Widget\\Greeter" line="12" visibility="public">
22
+ <name>salutation</name>
23
+ <default>'Hello'</default>
24
+ <docblock line="12">
25
+ <description></description>
26
+ <long-description></long-description>
27
+ <tag name="var" description="The salutation to use." type="string"/>
28
+ </docblock>
29
+ </property>
30
+ <property namespace="\\Acme\\SDK\\Widget\\Greeter" line="13" visibility="private">
31
+ <name>secret</name>
32
+ <default></default>
33
+ </property>
34
+ <method final="false" abstract="false" static="false" namespace="\\Acme\\SDK\\Widget" line="20" visibility="public" returnByReference="false">
35
+ <name>greet</name>
36
+ <full_name>\\Acme\\SDK\\Widget\\Greeter::greet()</full_name>
37
+ <argument line="20" by_reference="false">
38
+ <name>name</name>
39
+ <default></default>
40
+ <type>string</type>
41
+ </argument>
42
+ <argument line="20" by_reference="false">
43
+ <name>shout</name>
44
+ <default>false</default>
45
+ <type>?bool</type>
46
+ </argument>
47
+ <docblock line="20">
48
+ <description>Greet a person by name.</description>
49
+ <long-description></long-description>
50
+ <tag name="param" description="Name of the person." variable="name" type="string"/>
51
+ <tag name="param" description="Whether to shout." variable="shout" type="?bool"/>
52
+ <tag name="return" description="The greeting." type="string"/>
53
+ <tag name="throws" description="when name is empty" type="\\Acme\\SDK\\Exception\\ArgumentException"/>
54
+ </docblock>
55
+ </method>
56
+ <method final="false" abstract="false" static="false" namespace="\\Acme\\SDK\\Widget" line="30" visibility="private" returnByReference="false">
57
+ <name>hidden</name>
58
+ <full_name>\\Acme\\SDK\\Widget\\Greeter::hidden()</full_name>
59
+ </method>
60
+ </class>
61
+ </file>
62
+ </project>
63
+ `;
64
+
65
+ describe('convertPhpdoc', () => {
66
+ let dir: string;
67
+ let reference: SdkReference;
68
+
69
+ beforeAll(async () => {
70
+ dir = await fse.mkdtemp(path.join(os.tmpdir(), 'phpdoc-test-'));
71
+ await fse.writeFile(path.join(dir, 'structure.xml'), STRUCTURE_XML);
72
+ reference = await convertPhpdoc(dir);
73
+ });
74
+
75
+ afterAll(async () => {
76
+ await fse.remove(dir);
77
+ });
78
+
79
+ it('creates one page per class with a namespace-derived slug', () => {
80
+ expect(reference.pages).toHaveLength(1);
81
+ const page = reference.pages[0]!;
82
+ expect(page.slug).toBe('greeter');
83
+ expect(page.title).toBe('Greeter');
84
+ expect(page.tag).toBe('CLASS');
85
+ expect(page.description).toBe('Greets people politely.');
86
+ });
87
+
88
+ it('groups pages by namespace', () => {
89
+ expect(reference.groups).toEqual([{ group: 'Acme\\SDK\\Widget', pages: ['greeter'] }]);
90
+ });
91
+
92
+ it('reconstructs the method signature', () => {
93
+ const content = reference.pages[0]!.content;
94
+ expect(content).toContain(
95
+ '```php\npublic function greet(string $name, ?bool $shout = false): string\n```'
96
+ );
97
+ });
98
+
99
+ it('renders class heritage and sections', () => {
100
+ const content = reference.pages[0]!.content;
101
+ expect(content).toContain(
102
+ '```php\nfinal class Greeter implements \\Acme\\SDK\\Contract\\GreeterInterface\n```'
103
+ );
104
+ expect(content).toContain('## Properties');
105
+ expect(content).toContain('## Methods');
106
+ expect(content).toContain('### greet()');
107
+ expect(content).toContain('#### Returns');
108
+ expect(content).toContain('#### Throws');
109
+ });
110
+
111
+ it('emits ResponseField entries for properties and parameters', () => {
112
+ const content = reference.pages[0]!.content;
113
+ expect(content).toContain(
114
+ '<ResponseField name={"salutation"} type={"string"} required>\n The salutation to use.\n\n Default: `\'Hello\'`\n</ResponseField>'
115
+ );
116
+ expect(content).toContain(
117
+ '<ResponseField name={"name"} type={"string"} required>\n Name of the person.\n</ResponseField>'
118
+ );
119
+ expect(content).toContain(
120
+ '<ResponseField name={"shout"} type={"?bool"}>\n Whether to shout.\n</ResponseField>'
121
+ );
122
+ });
123
+
124
+ it('excludes private members', () => {
125
+ const content = reference.pages[0]!.content;
126
+ expect(content).not.toContain('secret');
127
+ expect(content).not.toContain('hidden');
128
+ });
129
+
130
+ it('accepts the structure.xml file path directly', async () => {
131
+ const direct = await convertPhpdoc(path.join(dir, 'structure.xml'));
132
+ expect(direct.pages[0]?.slug).toBe('greeter');
133
+ });
134
+ });
@@ -0,0 +1,84 @@
1
+ import fse from 'fs-extra';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
5
+
6
+ import { convertSphinx } from '../src/sdk/converters/sphinx.js';
7
+ import type { SdkReference } from '../src/sdk/types.js';
8
+
9
+ const indexPage = {
10
+ current_page_name: 'index',
11
+ title: 'Example SDK documentation',
12
+ body: '<section><h1>Example SDK documentation<a class="headerlink" href="#example" title="Link to this heading"></a></h1><p>Welcome to the example SDK. It does things.</p><p>See the <a class="reference internal" href="widgets.html#widgets.Widget">Widget docs</a> and the <a class="reference external" href="https://example.com/docs">docs site</a>.</p><div class="admonition warning"><p class="admonition-title">Warning</p><p>Handle with care.</p></div></section>',
13
+ };
14
+
15
+ const widgetsPage = {
16
+ current_page_name: 'widgets',
17
+ title: 'widgets module',
18
+ body: '<section><h1>widgets module<a class="headerlink" href="#widgets" title="Link to this heading"></a></h1><dl class="py class"><dt class="sig sig-object py" id="widgets.Widget"><em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">widgets.</span></span><span class="sig-name descname"><span class="pre">Widget</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">name</span></span></em><span class="sig-paren">)</span><a class="reference internal" href="_modules/widgets/#Widget"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#widgets.Widget" title="Link to this definition"></a></dt><dd><p>A widget.</p><dl class="py method"><dt class="sig sig-object py" id="widgets.Widget.spin"><span class="sig-name descname"><span class="pre">spin</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">speed</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">1</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#widgets.Widget.spin" title="Link to this definition"></a></dt><dd><p>Spins the widget.</p></dd></dl></dd></dl></section>',
19
+ };
20
+
21
+ const skippedPage = { current_page_name: 'genindex', title: 'Index', body: '<h1>Index</h1>' };
22
+
23
+ describe('convertSphinx', () => {
24
+ let dir: string;
25
+ let reference: SdkReference;
26
+
27
+ beforeAll(async () => {
28
+ dir = await fse.mkdtemp(path.join(os.tmpdir(), 'sphinx-test-'));
29
+ await fse.writeJson(path.join(dir, 'index.fjson'), indexPage);
30
+ await fse.writeJson(path.join(dir, 'widgets.fjson'), widgetsPage);
31
+ await fse.writeJson(path.join(dir, 'genindex.fjson'), skippedPage);
32
+ reference = await convertSphinx(dir);
33
+ });
34
+
35
+ afterAll(async () => {
36
+ await fse.remove(dir);
37
+ });
38
+
39
+ it('emits one page per content file with slugs from current_page_name', () => {
40
+ expect(reference.pages.map((page) => page.slug).sort()).toEqual(['index', 'widgets']);
41
+ });
42
+
43
+ it('groups guide pages and autodoc module pages separately', () => {
44
+ expect(reference.groups).toEqual([
45
+ { group: 'Getting Started', pages: ['index'] },
46
+ { group: 'API Reference', pages: ['widgets'] },
47
+ ]);
48
+ });
49
+
50
+ it('tags autodoc pages as MODULE', () => {
51
+ const widgets = reference.pages.find((page) => page.slug === 'widgets');
52
+ expect(widgets?.tag).toBe('MODULE');
53
+ expect(reference.pages.find((page) => page.slug === 'index')?.tag).toBeUndefined();
54
+ });
55
+
56
+ it('converts signatures to python code blocks with headings', () => {
57
+ const content = reference.pages.find((page) => page.slug === 'widgets')?.content ?? '';
58
+ expect(content).toContain('### Widget');
59
+ expect(content).toContain('```python\nclass widgets.Widget(name)\n```');
60
+ expect(content).toContain('#### spin()');
61
+ expect(content).toContain('```python\nspin(speed=1)\n```');
62
+ });
63
+
64
+ it('strips headerlinks and viewcode links', () => {
65
+ for (const page of reference.pages) {
66
+ expect(page.content).not.toContain('');
67
+ expect(page.content).not.toContain('[source]');
68
+ expect(page.content).not.toContain('_modules');
69
+ }
70
+ });
71
+
72
+ it('rewrites internal links and keeps external ones', () => {
73
+ const index = reference.pages.find((page) => page.slug === 'index');
74
+ expect(index?.content).toContain('[Widget docs](/widgets)');
75
+ expect(index?.content).toContain('https://example.com/docs');
76
+ });
77
+
78
+ it('converts admonitions to callouts and extracts descriptions', () => {
79
+ const index = reference.pages.find((page) => page.slug === 'index');
80
+ expect(index?.content).toContain('<Warning>');
81
+ expect(index?.content).toContain('Handle with care.');
82
+ expect(index?.description).toBe('Welcome to the example SDK.');
83
+ });
84
+ });