rspress-plugin-api-extractor 0.1.2 → 0.2.0
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/README.md +2 -2
- package/api-extracted-package.js +426 -0
- package/build-program.js +120 -0
- package/build-stages.js +664 -0
- package/category-resolver.js +50 -0
- package/code-post-processor.js +38 -0
- package/config-helpers.js +106 -0
- package/config-utils.js +258 -0
- package/content-hash.js +79 -0
- package/errors.js +29 -0
- package/formatter.js +69 -0
- package/hide-cut-transformer.js +96 -0
- package/index.d.ts +901 -831
- package/index.js +4 -6128
- package/layers/ConfigServiceLive.js +377 -0
- package/layers/ObservabilityLive.js +136 -0
- package/layers/PathDerivationServiceLive.js +16 -0
- package/layers/SnapshotServiceLive.js +94 -0
- package/layers/TypeRegistryServiceLive.js +46 -0
- package/llms-processing.js +270 -0
- package/llms-program.js +262 -0
- package/loader.js +186 -0
- package/markdown/cross-linker.js +156 -0
- package/markdown/helpers.js +364 -0
- package/markdown/index.js +11 -0
- package/markdown/page-generators/class-page.js +357 -0
- package/markdown/page-generators/enum-page.js +152 -0
- package/markdown/page-generators/function-page.js +127 -0
- package/markdown/page-generators/index-pages.js +27 -0
- package/markdown/page-generators/interface-page.js +307 -0
- package/markdown/page-generators/namespace-page.js +280 -0
- package/markdown/page-generators/type-alias-page.js +110 -0
- package/markdown/page-generators/variable-page.js +110 -0
- package/markdown/shiki-utils.js +48 -0
- package/migrations/001_create_snapshots.js +25 -0
- package/model-loader.js +95 -0
- package/multi-entry-resolver.js +70 -0
- package/og-resolver.js +271 -0
- package/package.json +63 -73
- package/path-derivation.js +48 -0
- package/plugin.js +218 -0
- package/prettier-formatter.js +73 -0
- package/public/tsconfig/rspress.json +44 -0
- package/remark-api-codeblocks.js +130 -0
- package/remark-with-api.js +172 -0
- package/route-collisions.js +52 -0
- package/runtime/components/ApiExample/index.js +29 -25
- package/runtime/components/ApiLlmsPackageActions/index.js +252 -323
- package/runtime/components/ApiLlmsViewOptions/index.js +269 -340
- package/runtime/components/ApiMember/index.js +49 -47
- package/runtime/components/ApiSignature/index.js +32 -28
- package/runtime/components/EnumMembersTable/{index_module.css → index.css} +18 -19
- package/runtime/components/EnumMembersTable/index.js +36 -67
- package/runtime/components/EnumMembersTable/index.module.js +9 -6
- package/runtime/components/ExampleBlock/{index_module.css → index.css} +2 -3
- package/runtime/components/ExampleBlock/index.js +23 -28
- package/runtime/components/ExampleBlock/index.module.js +8 -5
- package/runtime/components/MarkdownContent/index.js +26 -18
- package/runtime/components/MarkdownText/index.js +28 -22
- package/runtime/components/MemberSignature/{index_module.css → index.css} +5 -6
- package/runtime/components/MemberSignature/index.js +46 -46
- package/runtime/components/MemberSignature/index.module.js +9 -6
- package/runtime/components/ParametersTable/{index_module.css → index.css} +19 -20
- package/runtime/components/ParametersTable/index.js +36 -67
- package/runtime/components/ParametersTable/index.module.js +9 -6
- package/runtime/components/SignatureBlock/{index_module.css → index.css} +5 -6
- package/runtime/components/SignatureBlock/index.js +30 -29
- package/runtime/components/SignatureBlock/index.module.js +9 -6
- package/runtime/components/SignatureCode/{index_module.css → index.css} +9 -10
- package/runtime/components/SignatureCode/index.js +40 -32
- package/runtime/components/SignatureCode/index.module.js +9 -6
- package/runtime/components/SignatureToolbar/{index_module.css → index.css} +18 -20
- package/runtime/components/SignatureToolbar/index.js +48 -51
- package/runtime/components/SignatureToolbar/index.module.js +13 -10
- package/runtime/components/buttons/ButtonGroup.js +13 -6
- package/runtime/components/buttons/CopyCodeButton.js +40 -38
- package/runtime/components/buttons/WrapSignatureButton.js +20 -16
- package/runtime/components/buttons/{index_module.css → index.css} +5 -6
- package/runtime/components/buttons/index.module.js +8 -5
- package/runtime/components/icons/CheckIcon/index.js +20 -17
- package/runtime/components/icons/CopyIcon/index.js +20 -17
- package/runtime/components/icons/UnwrapIcon/index.js +22 -18
- package/runtime/components/icons/WrapIcon/index.js +20 -17
- package/runtime/components/shared/_twoslash.css +3 -10
- package/runtime/components/shared/variables.css +0 -3
- package/runtime/hooks/useWrapToggle.js +32 -9
- package/runtime/index.d.ts +513 -173
- package/runtime/index.js +11 -9
- package/runtime/utils/decode-hast.js +31 -16
- package/runtime/utils/hast-renderer.js +21 -7
- package/schemas/config.js +199 -0
- package/schemas/index.js +5 -0
- package/schemas/opengraph.js +26 -0
- package/schemas/performance.js +19 -0
- package/serve.js +133 -0
- package/services/ConfigService.js +7 -0
- package/services/PathDerivationService.js +7 -0
- package/services/SnapshotService.js +7 -0
- package/services/TypeRegistryService.js +7 -0
- package/shiki-transformer.js +758 -0
- package/tsconfig-parser.js +127 -0
- package/tsdoc-metadata.json +11 -11
- package/twoslash-patterns.js +87 -0
- package/twoslash-transformer.js +316 -0
- package/type-reference-extractor.js +201 -0
- package/typescript-config.js +168 -0
- package/vfs-registry.js +121 -0
- package/0~llms-program.js +0 -344
- package/runtime/components/ApiLlmsPackageActions/index.module.js +0 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/rspress-plugin-api-extractor)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
|
-
[](https://nodejs.org/)
|
|
6
6
|
[](https://www.typescriptlang.org/)
|
|
7
7
|
|
|
8
8
|
An [RSPress](https://rspress.dev/) 2.0 plugin that generates interactive API documentation from [Microsoft API Extractor](https://api-extractor.com/) models. Point it at your `.api.json` files and you get a documentation site: syntax-highlighted signatures, Twoslash hover tooltips, type references that cross-link between pages and copy-paste code examples.
|
|
@@ -57,7 +57,7 @@ The plugin reads your `.api.json` model and writes one MDX page per public API i
|
|
|
57
57
|
|
|
58
58
|
- [Getting started](https://github.com/spencerbeggs/rspress-plugin-api-extractor/blob/main/docs/01-getting-started.md) — Install, minimal config, first build.
|
|
59
59
|
- [Configuration](https://github.com/spencerbeggs/rspress-plugin-api-extractor/blob/main/docs/02-configuration.md) — Full plugin-options reference.
|
|
60
|
-
- [Config helpers](https://github.com/spencerbeggs/rspress-plugin-api-extractor/blob/main/docs/03-config-helpers.md) — `
|
|
60
|
+
- [Config helpers](https://github.com/spencerbeggs/rspress-plugin-api-extractor/blob/main/docs/03-config-helpers.md) — `api.fromDir` and `apis.fromDir` for discovering config from package folders.
|
|
61
61
|
- [Single package](https://github.com/spencerbeggs/rspress-plugin-api-extractor/blob/main/docs/04-single-package.md) — The single-API recipe.
|
|
62
62
|
- [Multi-package](https://github.com/spencerbeggs/rspress-plugin-api-extractor/blob/main/docs/05-multi-package.md) — The multi-API portal recipe.
|
|
63
63
|
- [Versioned](https://github.com/spencerbeggs/rspress-plugin-api-extractor/blob/main/docs/06-versioned.md) — Documenting major versions side by side.
|
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
import { ApiItemKind, ApiModel } from "@microsoft/api-extractor-model";
|
|
2
|
+
import { VirtualPackage } from "type-registry-effect";
|
|
3
|
+
|
|
4
|
+
//#region src/api-extracted-package.ts
|
|
5
|
+
const VirtualPackageClass = VirtualPackage.VirtualPackage;
|
|
6
|
+
/**
|
|
7
|
+
* Reconstructs TypeScript declaration files from an API Extractor model.
|
|
8
|
+
*
|
|
9
|
+
* Extends {@link VirtualPackage} with the ability to generate high-fidelity
|
|
10
|
+
* `.d.ts` output from API Extractor's `ApiPackage` — including enum values,
|
|
11
|
+
* full JSDoc, namespace members, and all interface member kinds.
|
|
12
|
+
*
|
|
13
|
+
* Use the factory methods {@link fromApiModel} or {@link fromPackage} to create instances.
|
|
14
|
+
*/
|
|
15
|
+
var ApiExtractedPackage = class ApiExtractedPackage extends VirtualPackageClass {
|
|
16
|
+
apiPackage;
|
|
17
|
+
constructor(apiPackage, packageName, entries) {
|
|
18
|
+
super(packageName, "1.0.0", entries);
|
|
19
|
+
this.apiPackage = apiPackage;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Create an ApiExtractedPackage from an API model JSON file path.
|
|
23
|
+
*/
|
|
24
|
+
static fromApiModel(modelPath) {
|
|
25
|
+
const apiPackage = new ApiModel().loadPackage(modelPath);
|
|
26
|
+
return ApiExtractedPackage.fromPackage(apiPackage, apiPackage.name);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Create an ApiExtractedPackage from an existing ApiPackage instance.
|
|
30
|
+
*/
|
|
31
|
+
static fromPackage(apiPackage, packageName) {
|
|
32
|
+
const tempEntries = /* @__PURE__ */ new Map();
|
|
33
|
+
const tempInstance = new ApiExtractedPackage(apiPackage, packageName, tempEntries);
|
|
34
|
+
for (const ep of apiPackage.entryPoints) {
|
|
35
|
+
const entryName = tempInstance.getEntryPointName(ep);
|
|
36
|
+
const fileName = entryName ? `${entryName}.d.ts` : "index.d.ts";
|
|
37
|
+
tempEntries.set(fileName, tempInstance.generateDeclarations(ep));
|
|
38
|
+
}
|
|
39
|
+
return tempInstance;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Generate the .d.ts content for a specific entry point.
|
|
43
|
+
*/
|
|
44
|
+
generateDeclarations(entryPoint) {
|
|
45
|
+
const ep = entryPoint ?? this.apiPackage.entryPoints[0];
|
|
46
|
+
if (!ep) return "";
|
|
47
|
+
const parts = [];
|
|
48
|
+
const packageDoc = this.extractPackageDocumentation();
|
|
49
|
+
if (packageDoc) {
|
|
50
|
+
parts.push(packageDoc);
|
|
51
|
+
parts.push("");
|
|
52
|
+
}
|
|
53
|
+
for (const member of ep.members) {
|
|
54
|
+
const decl = this.generateDeclaration(member);
|
|
55
|
+
if (decl) {
|
|
56
|
+
parts.push(decl);
|
|
57
|
+
parts.push("");
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
parts.push("export { }");
|
|
61
|
+
parts.push("");
|
|
62
|
+
return parts.join("\n");
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Generate a TypeScript declaration for a single API item.
|
|
66
|
+
*/
|
|
67
|
+
generateDeclaration(apiItem) {
|
|
68
|
+
switch (apiItem.kind) {
|
|
69
|
+
case ApiItemKind.Class: return this.generateClassDeclaration(apiItem);
|
|
70
|
+
case ApiItemKind.Interface: return this.generateInterfaceDeclaration(apiItem);
|
|
71
|
+
case ApiItemKind.TypeAlias: return this.generateTypeAliasDeclaration(apiItem);
|
|
72
|
+
case ApiItemKind.Function: return this.generateFunctionDeclaration(apiItem);
|
|
73
|
+
case ApiItemKind.Enum: return this.generateEnumDeclaration(apiItem);
|
|
74
|
+
case ApiItemKind.Variable: return this.generateVariableDeclaration(apiItem);
|
|
75
|
+
case ApiItemKind.Namespace: return this.generateNamespaceDeclaration(apiItem);
|
|
76
|
+
default: return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
generateClassDeclaration(apiClass) {
|
|
80
|
+
const lines = [];
|
|
81
|
+
const jsDoc = this.formatJSDoc(apiClass);
|
|
82
|
+
if (jsDoc) lines.push(jsDoc);
|
|
83
|
+
let name = apiClass.displayName;
|
|
84
|
+
if (apiClass.typeParameters?.length) name += this.formatTypeParameters(apiClass.typeParameters);
|
|
85
|
+
const headerParts = ["export declare class", name];
|
|
86
|
+
if (apiClass.extendsType) headerParts.push(`extends ${apiClass.extendsType.excerpt.text}`);
|
|
87
|
+
if (apiClass.implementsTypes?.length) {
|
|
88
|
+
const impl = apiClass.implementsTypes.map((t) => t.excerpt.text).join(", ");
|
|
89
|
+
headerParts.push(`implements ${impl}`);
|
|
90
|
+
}
|
|
91
|
+
lines.push(`${headerParts.join(" ")} {`);
|
|
92
|
+
for (const member of apiClass.members) {
|
|
93
|
+
const memberDecl = this.generateClassMember(member);
|
|
94
|
+
if (memberDecl) lines.push(memberDecl);
|
|
95
|
+
}
|
|
96
|
+
lines.push("}");
|
|
97
|
+
return lines.join("\n");
|
|
98
|
+
}
|
|
99
|
+
generateInterfaceDeclaration(apiInterface) {
|
|
100
|
+
const lines = [];
|
|
101
|
+
const jsDoc = this.formatJSDoc(apiInterface);
|
|
102
|
+
if (jsDoc) lines.push(jsDoc);
|
|
103
|
+
let name = apiInterface.displayName;
|
|
104
|
+
if (apiInterface.typeParameters?.length) name += this.formatTypeParameters(apiInterface.typeParameters);
|
|
105
|
+
const headerParts = ["export declare interface", name];
|
|
106
|
+
if (apiInterface.extendsTypes?.length) {
|
|
107
|
+
const ext = apiInterface.extendsTypes.map((t) => t.excerpt.text).join(", ");
|
|
108
|
+
headerParts.push(`extends ${ext}`);
|
|
109
|
+
}
|
|
110
|
+
lines.push(`${headerParts.join(" ")} {`);
|
|
111
|
+
for (const member of apiInterface.members) {
|
|
112
|
+
const memberDecl = this.generateInterfaceMember(member);
|
|
113
|
+
if (memberDecl) lines.push(memberDecl);
|
|
114
|
+
}
|
|
115
|
+
lines.push("}");
|
|
116
|
+
return lines.join("\n");
|
|
117
|
+
}
|
|
118
|
+
generateTypeAliasDeclaration(typeAlias) {
|
|
119
|
+
const lines = [];
|
|
120
|
+
const jsDoc = this.formatJSDoc(typeAlias);
|
|
121
|
+
if (jsDoc) lines.push(jsDoc);
|
|
122
|
+
let name = typeAlias.displayName;
|
|
123
|
+
if (typeAlias.typeParameters?.length) name += this.formatTypeParameters(typeAlias.typeParameters);
|
|
124
|
+
lines.push(`export declare type ${name} = ${typeAlias.typeExcerpt.text};`);
|
|
125
|
+
return lines.join("\n");
|
|
126
|
+
}
|
|
127
|
+
generateFunctionDeclaration(apiFunction) {
|
|
128
|
+
const lines = [];
|
|
129
|
+
const jsDoc = this.formatJSDoc(apiFunction);
|
|
130
|
+
if (jsDoc) lines.push(jsDoc);
|
|
131
|
+
const cleaned = this.cleanExcerpt(apiFunction.excerpt.text);
|
|
132
|
+
lines.push(`export declare ${cleaned};`);
|
|
133
|
+
return lines.join("\n");
|
|
134
|
+
}
|
|
135
|
+
generateEnumDeclaration(apiEnum) {
|
|
136
|
+
const lines = [];
|
|
137
|
+
const jsDoc = this.formatJSDoc(apiEnum);
|
|
138
|
+
if (jsDoc) lines.push(jsDoc);
|
|
139
|
+
lines.push(`export declare enum ${apiEnum.displayName} {`);
|
|
140
|
+
const enumMembers = apiEnum.members.filter((m) => m.kind === ApiItemKind.EnumMember);
|
|
141
|
+
for (let i = 0; i < enumMembers.length; i++) {
|
|
142
|
+
const enumMember = enumMembers[i];
|
|
143
|
+
const memberJsDoc = this.formatJSDoc(enumMember, " ");
|
|
144
|
+
if (memberJsDoc) lines.push(memberJsDoc);
|
|
145
|
+
const suffix = i === enumMembers.length - 1 ? "" : ",";
|
|
146
|
+
const initExcerpt = enumMember.initializerExcerpt;
|
|
147
|
+
if (initExcerpt?.text.trim()) lines.push(` ${enumMember.displayName} = ${initExcerpt.text.trim()}${suffix}`);
|
|
148
|
+
else lines.push(` ${enumMember.displayName}${suffix}`);
|
|
149
|
+
}
|
|
150
|
+
lines.push("}");
|
|
151
|
+
return lines.join("\n");
|
|
152
|
+
}
|
|
153
|
+
generateVariableDeclaration(apiVariable) {
|
|
154
|
+
const lines = [];
|
|
155
|
+
const jsDoc = this.formatJSDoc(apiVariable);
|
|
156
|
+
if (jsDoc) lines.push(jsDoc);
|
|
157
|
+
let cleaned = this.cleanExcerpt(apiVariable.excerpt.text);
|
|
158
|
+
if (!cleaned.startsWith("const ") && !cleaned.startsWith("let ") && !cleaned.startsWith("var ")) cleaned = `const ${cleaned}`;
|
|
159
|
+
lines.push(`export declare ${cleaned};`);
|
|
160
|
+
return lines.join("\n");
|
|
161
|
+
}
|
|
162
|
+
generateNamespaceDeclaration(apiNamespace) {
|
|
163
|
+
const lines = [];
|
|
164
|
+
const jsDoc = this.formatJSDoc(apiNamespace);
|
|
165
|
+
if (jsDoc) lines.push(jsDoc);
|
|
166
|
+
lines.push(`export declare namespace ${apiNamespace.displayName} {`);
|
|
167
|
+
for (const member of apiNamespace.members) {
|
|
168
|
+
const memberDecl = this.generateNamespaceMember(member);
|
|
169
|
+
if (memberDecl) lines.push(memberDecl);
|
|
170
|
+
}
|
|
171
|
+
lines.push("}");
|
|
172
|
+
return lines.join("\n");
|
|
173
|
+
}
|
|
174
|
+
generateNamespaceMember(apiItem) {
|
|
175
|
+
switch (apiItem.kind) {
|
|
176
|
+
case ApiItemKind.Function: return this.generateNamespaceFunction(apiItem);
|
|
177
|
+
case ApiItemKind.Interface: return this.generateNamespaceInterface(apiItem);
|
|
178
|
+
case ApiItemKind.Enum: return this.generateNamespaceEnum(apiItem);
|
|
179
|
+
case ApiItemKind.TypeAlias: return this.generateNamespaceTypeAlias(apiItem);
|
|
180
|
+
case ApiItemKind.Variable: return this.generateNamespaceVariable(apiItem);
|
|
181
|
+
case ApiItemKind.Class: return this.generateNamespaceClass(apiItem);
|
|
182
|
+
default: return null;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
generateNamespaceFunction(apiFunction) {
|
|
186
|
+
const lines = [];
|
|
187
|
+
const jsDoc = this.formatJSDoc(apiFunction, " ");
|
|
188
|
+
if (jsDoc) lines.push(jsDoc);
|
|
189
|
+
const cleaned = this.cleanExcerpt(apiFunction.excerpt.text);
|
|
190
|
+
lines.push(` export ${cleaned};`);
|
|
191
|
+
return lines.join("\n");
|
|
192
|
+
}
|
|
193
|
+
generateNamespaceInterface(apiInterface) {
|
|
194
|
+
const lines = [];
|
|
195
|
+
const jsDoc = this.formatJSDoc(apiInterface, " ");
|
|
196
|
+
if (jsDoc) lines.push(jsDoc);
|
|
197
|
+
let name = apiInterface.displayName;
|
|
198
|
+
if (apiInterface.typeParameters?.length) name += this.formatTypeParameters(apiInterface.typeParameters);
|
|
199
|
+
const headerParts = ["export interface", name];
|
|
200
|
+
if (apiInterface.extendsTypes?.length) {
|
|
201
|
+
const ext = apiInterface.extendsTypes.map((t) => t.excerpt.text).join(", ");
|
|
202
|
+
headerParts.push(`extends ${ext}`);
|
|
203
|
+
}
|
|
204
|
+
lines.push(` ${headerParts.join(" ")} {`);
|
|
205
|
+
for (const member of apiInterface.members) {
|
|
206
|
+
const memberDecl = this.generateInterfaceMember(member, " ");
|
|
207
|
+
if (memberDecl) lines.push(memberDecl);
|
|
208
|
+
}
|
|
209
|
+
lines.push(" }");
|
|
210
|
+
return lines.join("\n");
|
|
211
|
+
}
|
|
212
|
+
generateNamespaceEnum(apiEnum) {
|
|
213
|
+
const lines = [];
|
|
214
|
+
const jsDoc = this.formatJSDoc(apiEnum, " ");
|
|
215
|
+
if (jsDoc) lines.push(jsDoc);
|
|
216
|
+
lines.push(` export enum ${apiEnum.displayName} {`);
|
|
217
|
+
const enumMembers = apiEnum.members.filter((m) => m.kind === ApiItemKind.EnumMember);
|
|
218
|
+
for (let i = 0; i < enumMembers.length; i++) {
|
|
219
|
+
const enumMember = enumMembers[i];
|
|
220
|
+
const memberJsDoc = this.formatJSDoc(enumMember, " ");
|
|
221
|
+
if (memberJsDoc) lines.push(memberJsDoc);
|
|
222
|
+
const suffix = i === enumMembers.length - 1 ? "" : ",";
|
|
223
|
+
const initExcerpt = enumMember.initializerExcerpt;
|
|
224
|
+
if (initExcerpt?.text.trim()) lines.push(` ${enumMember.displayName} = ${initExcerpt.text.trim()}${suffix}`);
|
|
225
|
+
else lines.push(` ${enumMember.displayName}${suffix}`);
|
|
226
|
+
}
|
|
227
|
+
lines.push(" }");
|
|
228
|
+
return lines.join("\n");
|
|
229
|
+
}
|
|
230
|
+
generateNamespaceTypeAlias(typeAlias) {
|
|
231
|
+
const lines = [];
|
|
232
|
+
const jsDoc = this.formatJSDoc(typeAlias, " ");
|
|
233
|
+
if (jsDoc) lines.push(jsDoc);
|
|
234
|
+
let name = typeAlias.displayName;
|
|
235
|
+
if (typeAlias.typeParameters?.length) name += this.formatTypeParameters(typeAlias.typeParameters);
|
|
236
|
+
lines.push(` export type ${name} = ${typeAlias.typeExcerpt.text};`);
|
|
237
|
+
return lines.join("\n");
|
|
238
|
+
}
|
|
239
|
+
generateNamespaceVariable(apiVariable) {
|
|
240
|
+
const lines = [];
|
|
241
|
+
const jsDoc = this.formatJSDoc(apiVariable, " ");
|
|
242
|
+
if (jsDoc) lines.push(jsDoc);
|
|
243
|
+
let cleaned = this.cleanExcerpt(apiVariable.excerpt.text);
|
|
244
|
+
if (!cleaned.startsWith("const ") && !cleaned.startsWith("let ") && !cleaned.startsWith("var ")) cleaned = `const ${cleaned}`;
|
|
245
|
+
lines.push(` export ${cleaned};`);
|
|
246
|
+
return lines.join("\n");
|
|
247
|
+
}
|
|
248
|
+
generateNamespaceClass(apiClass) {
|
|
249
|
+
const decl = this.generateClassDeclaration(apiClass);
|
|
250
|
+
if (!decl) return "";
|
|
251
|
+
return decl.replace(/\bexport declare class\b/, "export class").split("\n").map((line) => line.trim() ? ` ${line}` : line).join("\n");
|
|
252
|
+
}
|
|
253
|
+
generateClassMember(member, indent = " ") {
|
|
254
|
+
switch (member.kind) {
|
|
255
|
+
case ApiItemKind.Constructor: return this.generateMemberFromExcerpt(member, indent);
|
|
256
|
+
case ApiItemKind.Method: return this.generateMemberFromExcerpt(member, indent);
|
|
257
|
+
case ApiItemKind.Property: return this.generateMemberFromExcerpt(member, indent);
|
|
258
|
+
default: return null;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
generateInterfaceMember(member, indent = " ") {
|
|
262
|
+
switch (member.kind) {
|
|
263
|
+
case ApiItemKind.MethodSignature: return this.generateMemberFromExcerpt(member, indent);
|
|
264
|
+
case ApiItemKind.PropertySignature: return this.generateMemberFromExcerpt(member, indent);
|
|
265
|
+
case ApiItemKind.CallSignature: return this.generateMemberFromExcerpt(member, indent);
|
|
266
|
+
case ApiItemKind.ConstructSignature: return this.generateMemberFromExcerpt(member, indent);
|
|
267
|
+
case ApiItemKind.IndexSignature: return this.generateMemberFromExcerpt(member, indent);
|
|
268
|
+
default: return null;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
generateMemberFromExcerpt(member, indent) {
|
|
272
|
+
const lines = [];
|
|
273
|
+
const jsDoc = this.formatJSDoc(member, indent);
|
|
274
|
+
if (jsDoc) lines.push(jsDoc);
|
|
275
|
+
const cleaned = this.cleanExcerpt(member.excerpt.text);
|
|
276
|
+
lines.push(`${indent}${cleaned};`);
|
|
277
|
+
return lines.join("\n");
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Clean an excerpt text: strip export/declare keywords and trailing semicolons/whitespace.
|
|
281
|
+
*/
|
|
282
|
+
cleanExcerpt(text) {
|
|
283
|
+
return text.replace(/^export\s+/, "").replace(/^declare\s+/, "").replace(/;+\s*$/, "").trim();
|
|
284
|
+
}
|
|
285
|
+
formatTypeParameters(typeParameters) {
|
|
286
|
+
if (!typeParameters.length) return "";
|
|
287
|
+
return `<${typeParameters.map((tp) => {
|
|
288
|
+
const parts = [tp.name];
|
|
289
|
+
if (tp.constraintExcerpt?.text.trim()) parts.push(`extends ${tp.constraintExcerpt.text.trim()}`);
|
|
290
|
+
if (tp.defaultTypeExcerpt?.text.trim()) parts.push(`= ${tp.defaultTypeExcerpt.text.trim()}`);
|
|
291
|
+
return parts.join(" ");
|
|
292
|
+
}).join(", ")}>`;
|
|
293
|
+
}
|
|
294
|
+
extractPackageDocumentation() {
|
|
295
|
+
const pkg = this.apiPackage;
|
|
296
|
+
if (!pkg.tsdocComment?.summarySection) return null;
|
|
297
|
+
const summary = this.extractPlainText(pkg.tsdocComment.summarySection).trim();
|
|
298
|
+
if (!summary) return null;
|
|
299
|
+
const lines = [];
|
|
300
|
+
for (const line of summary.split("\n")) lines.push(line);
|
|
301
|
+
lines.push("");
|
|
302
|
+
lines.push("@packageDocumentation");
|
|
303
|
+
return `/**\n${lines.map((line) => line ? ` * ${line}` : " *").join("\n")}\n */`;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Format JSDoc comment from an API item's TSDoc.
|
|
307
|
+
* Produces output matching the TypeScript compiler's JSDoc style.
|
|
308
|
+
*/
|
|
309
|
+
formatJSDoc(apiItem, indent = "") {
|
|
310
|
+
const item = apiItem;
|
|
311
|
+
if (!item.tsdocComment) return null;
|
|
312
|
+
const tsdoc = item.tsdocComment;
|
|
313
|
+
const lines = [];
|
|
314
|
+
if (tsdoc.summarySection) {
|
|
315
|
+
const summary = this.extractPlainText(tsdoc.summarySection).trim();
|
|
316
|
+
if (summary) for (const line of summary.split("\n")) lines.push(line);
|
|
317
|
+
}
|
|
318
|
+
const typeParamLines = [];
|
|
319
|
+
if (tsdoc.typeParams?.blocks) for (const block of tsdoc.typeParams.blocks) {
|
|
320
|
+
const blockAny = block;
|
|
321
|
+
const name = blockAny.parameterName || "";
|
|
322
|
+
const desc = this.extractPlainText(blockAny.content).replace(/\s+/g, " ").trim();
|
|
323
|
+
if (name && desc) typeParamLines.push(`@typeParam ${name} - ${desc}`);
|
|
324
|
+
}
|
|
325
|
+
const paramLines = [];
|
|
326
|
+
if (tsdoc.params?.blocks) for (const paramBlock of tsdoc.params.blocks) {
|
|
327
|
+
const param = paramBlock;
|
|
328
|
+
const name = param.parameterName || "";
|
|
329
|
+
const desc = this.extractPlainText(param.content).replace(/\s+/g, " ").trim();
|
|
330
|
+
if (name && desc) paramLines.push(`@param ${name} - ${desc}`);
|
|
331
|
+
}
|
|
332
|
+
let returnsLine = null;
|
|
333
|
+
if (tsdoc.returnsBlock) {
|
|
334
|
+
const desc = this.extractPlainText(tsdoc.returnsBlock.content).replace(/\s+/g, " ").trim();
|
|
335
|
+
if (desc) returnsLine = `@returns ${desc}`;
|
|
336
|
+
}
|
|
337
|
+
if (typeParamLines.length || paramLines.length || returnsLine) {
|
|
338
|
+
if (lines.length > 0) lines.push("");
|
|
339
|
+
lines.push(...typeParamLines);
|
|
340
|
+
lines.push(...paramLines);
|
|
341
|
+
if (returnsLine) lines.push(returnsLine);
|
|
342
|
+
}
|
|
343
|
+
if (tsdoc.deprecatedBlock) {
|
|
344
|
+
const msg = this.extractPlainText(tsdoc.deprecatedBlock.content).replace(/\s+/g, " ").trim();
|
|
345
|
+
if (msg) {
|
|
346
|
+
if (lines.length > 0) lines.push("");
|
|
347
|
+
lines.push(`@deprecated ${msg}`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
if (tsdoc.remarksBlock) {
|
|
351
|
+
const remarks = this.extractPlainText(tsdoc.remarksBlock.content).trim();
|
|
352
|
+
if (remarks) {
|
|
353
|
+
if (lines.length > 0) lines.push("");
|
|
354
|
+
lines.push("@remarks");
|
|
355
|
+
for (const line of remarks.split("\n")) lines.push(line);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
if (tsdoc.customBlocks) for (const block of tsdoc.customBlocks) {
|
|
359
|
+
const blockAny = block;
|
|
360
|
+
if (blockAny.blockTag?.tagName === "@example") {
|
|
361
|
+
const exampleText = this.extractPlainText(blockAny.content).trim();
|
|
362
|
+
if (exampleText) {
|
|
363
|
+
if (lines.length > 0) lines.push("");
|
|
364
|
+
lines.push("@example");
|
|
365
|
+
for (const line of exampleText.split("\n")) lines.push(line);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
try {
|
|
370
|
+
if (tsdoc.modifierTagSet?.isPublic?.()) {
|
|
371
|
+
if (lines.length > 0) lines.push("");
|
|
372
|
+
lines.push("@public");
|
|
373
|
+
}
|
|
374
|
+
} catch {}
|
|
375
|
+
if (lines.length === 0) return null;
|
|
376
|
+
if (lines.length === 1 && !lines[0].includes("\n")) return `${indent}/** ${lines[0]} */`;
|
|
377
|
+
return `${indent}/**\n${lines.map((line) => line ? `${indent} * ${line}` : `${indent} *`).join("\n")}\n${indent} */`;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Recursively extract plain text from a TSDoc DocNode tree.
|
|
381
|
+
*/
|
|
382
|
+
extractPlainText(node) {
|
|
383
|
+
const n = node;
|
|
384
|
+
if (n.kind === "PlainText") return n.text || "";
|
|
385
|
+
if (n.kind === "SoftBreak") return "\n";
|
|
386
|
+
if (n.kind === "CodeSpan") return `\`${n.code || ""}\``;
|
|
387
|
+
if (n.kind === "EscapedText") return n.encodedText || n.decodedText || "";
|
|
388
|
+
if (n.kind === "ErrorText") return n.text || "";
|
|
389
|
+
if (n.kind === "FencedCode") return `\`\`\`${n.language || ""}\n${(n.code || "").replace(/\n+$/, "")}\n\`\`\``;
|
|
390
|
+
if (n.kind === "LinkTag") {
|
|
391
|
+
let target = "";
|
|
392
|
+
if (n.codeDestination?.memberReferences) {
|
|
393
|
+
const identifiers = [];
|
|
394
|
+
for (const ref of n.codeDestination.memberReferences) if (ref.memberIdentifier?.identifier) identifiers.push(ref.memberIdentifier.identifier);
|
|
395
|
+
target = identifiers.join(".");
|
|
396
|
+
}
|
|
397
|
+
const displayText = typeof n.linkText === "string" ? n.linkText : "";
|
|
398
|
+
if (target && displayText) return `{@link ${target} | ${displayText}}`;
|
|
399
|
+
if (target) return `{@link ${target}}`;
|
|
400
|
+
if (displayText) return displayText;
|
|
401
|
+
return "";
|
|
402
|
+
}
|
|
403
|
+
if (n.kind === "Section") {
|
|
404
|
+
const children = n.getChildNodes?.() || [];
|
|
405
|
+
const paragraphs = [];
|
|
406
|
+
for (const child of children) {
|
|
407
|
+
const trimmed = this.extractPlainText(child).trim();
|
|
408
|
+
if (trimmed) paragraphs.push(trimmed);
|
|
409
|
+
}
|
|
410
|
+
return paragraphs.join("\n\n");
|
|
411
|
+
}
|
|
412
|
+
const parts = [];
|
|
413
|
+
if (n.getChildNodes && typeof n.getChildNodes === "function") for (const child of n.getChildNodes()) {
|
|
414
|
+
const text = this.extractPlainText(child);
|
|
415
|
+
if (text) parts.push(text);
|
|
416
|
+
}
|
|
417
|
+
return parts.join("");
|
|
418
|
+
}
|
|
419
|
+
getEntryPointName(entryPoint) {
|
|
420
|
+
if (entryPoint.displayName === "") return void 0;
|
|
421
|
+
return entryPoint.displayName;
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
//#endregion
|
|
426
|
+
export { ApiExtractedPackage };
|
package/build-program.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { markdownCrossLinker } from "./markdown/cross-linker.js";
|
|
2
|
+
import "./markdown/index.js";
|
|
3
|
+
import { SnapshotService } from "./services/SnapshotService.js";
|
|
4
|
+
import { buildPipelineForApi, cleanupAndCommit, prepareWorkItems, writeMetadata } from "./build-stages.js";
|
|
5
|
+
import { TwoslashManager } from "./twoslash-transformer.js";
|
|
6
|
+
import { VfsRegistry } from "./vfs-registry.js";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { Effect } from "effect";
|
|
9
|
+
import { FileSystem } from "@effect/platform";
|
|
10
|
+
|
|
11
|
+
//#region src/build-program.ts
|
|
12
|
+
/**
|
|
13
|
+
* Generate markdown documentation for a single API as a native Effect program.
|
|
14
|
+
*
|
|
15
|
+
* Orchestrates the 5 build stages:
|
|
16
|
+
* 1. prepareWorkItems — categorize items, build cross-link data, flatten work items
|
|
17
|
+
* 2. generatePages — generate page content, hash, resolve timestamps
|
|
18
|
+
* 3. writeFiles — write changed files to disk
|
|
19
|
+
* 4. writeMetadata — write root _meta.json, main index, category _meta.json files
|
|
20
|
+
* 5. cleanupAndCommit — batch upsert snapshots, delete stale/orphan files
|
|
21
|
+
*
|
|
22
|
+
* Returns build result metadata including CrossLinkData for cross-link merging
|
|
23
|
+
* and generated file paths for LLMs post-processing.
|
|
24
|
+
*/
|
|
25
|
+
function generateApiDocs(apiConfig, buildContext, fileContextMap) {
|
|
26
|
+
return Effect.gen(function* () {
|
|
27
|
+
const fileSystem = yield* FileSystem.FileSystem;
|
|
28
|
+
const snapshotSvc = yield* SnapshotService;
|
|
29
|
+
const { apiPackage, packageName, apiName, outputDir, baseRoute, categories, source, packageJson, llmsPlugin, siteUrl, ogImage } = apiConfig;
|
|
30
|
+
const suppressExampleErrors = apiConfig.suppressExampleErrors ?? true;
|
|
31
|
+
const { shikiCrossLinker, highlighter, hideCutTransformer, hideCutLinesTransformer, twoslashTransformer, ogResolver, pageConcurrency } = buildContext;
|
|
32
|
+
const resolvedOutputDir = path.resolve(process.cwd(), outputDir);
|
|
33
|
+
const buildTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
34
|
+
const allSnapshots = yield* snapshotSvc.getAllForDirectory(resolvedOutputDir).pipe(Effect.orDie);
|
|
35
|
+
const existingSnapshots = new Map(allSnapshots.map((s) => [s.filePath, s]));
|
|
36
|
+
yield* fileSystem.makeDirectory(resolvedOutputDir, { recursive: true }).pipe(Effect.orDie);
|
|
37
|
+
const { workItems, crossLinkData } = prepareWorkItems({
|
|
38
|
+
apiPackage,
|
|
39
|
+
categories,
|
|
40
|
+
baseRoute,
|
|
41
|
+
packageName
|
|
42
|
+
});
|
|
43
|
+
markdownCrossLinker.setRoutes(crossLinkData.routes);
|
|
44
|
+
const apiScope = baseRoute.replace(/^\//, "").split("/")[0] || packageName;
|
|
45
|
+
shikiCrossLinker.reinitialize(crossLinkData.routes, crossLinkData.kinds, apiScope);
|
|
46
|
+
TwoslashManager.addTypeRoutes(crossLinkData.routes);
|
|
47
|
+
if (highlighter) {
|
|
48
|
+
const vfsConfig = {
|
|
49
|
+
vfs: /* @__PURE__ */ new Map(),
|
|
50
|
+
highlighter,
|
|
51
|
+
crossLinker: shikiCrossLinker,
|
|
52
|
+
packageName,
|
|
53
|
+
apiScope
|
|
54
|
+
};
|
|
55
|
+
if (twoslashTransformer != null) vfsConfig.twoslashTransformer = twoslashTransformer;
|
|
56
|
+
if (hideCutTransformer != null) vfsConfig.hideCutTransformer = hideCutTransformer;
|
|
57
|
+
if (hideCutLinesTransformer != null) vfsConfig.hideCutLinesTransformer = hideCutLinesTransformer;
|
|
58
|
+
if (apiConfig.theme != null) vfsConfig.theme = apiConfig.theme;
|
|
59
|
+
VfsRegistry.register(apiScope, vfsConfig);
|
|
60
|
+
}
|
|
61
|
+
yield* Effect.logInfo(`Generating ${workItems.length} pages across ${Object.keys(categories).length} categories in parallel`);
|
|
62
|
+
const fileResults = yield* buildPipelineForApi({
|
|
63
|
+
workItems,
|
|
64
|
+
baseRoute,
|
|
65
|
+
packageName,
|
|
66
|
+
apiScope,
|
|
67
|
+
...apiName != null ? { apiName } : {},
|
|
68
|
+
...source != null ? { source } : {},
|
|
69
|
+
buildTime,
|
|
70
|
+
resolvedOutputDir,
|
|
71
|
+
pageConcurrency,
|
|
72
|
+
existingSnapshots,
|
|
73
|
+
...suppressExampleErrors != null ? { suppressExampleErrors } : {},
|
|
74
|
+
...llmsPlugin != null ? { llmsPlugin } : {},
|
|
75
|
+
...ogResolver !== void 0 ? { ogResolver } : {},
|
|
76
|
+
...siteUrl != null ? { siteUrl } : {},
|
|
77
|
+
...ogImage != null ? { ogImage } : {}
|
|
78
|
+
});
|
|
79
|
+
const changedCount = fileResults.filter((r) => r.status !== "unchanged").length;
|
|
80
|
+
yield* Effect.logInfo(`Generated ${changedCount} pages`);
|
|
81
|
+
const generatedFiles = /* @__PURE__ */ new Set();
|
|
82
|
+
for (const r of fileResults) {
|
|
83
|
+
generatedFiles.add(r.relativePathWithExt);
|
|
84
|
+
const ctx = { file: r.relativePathWithExt };
|
|
85
|
+
if (apiName != null) ctx.api = apiName;
|
|
86
|
+
if (packageJson?.version != null) ctx.version = packageJson.version;
|
|
87
|
+
fileContextMap.set(r.absolutePath, ctx);
|
|
88
|
+
}
|
|
89
|
+
yield* writeMetadata({
|
|
90
|
+
fileResults,
|
|
91
|
+
categories,
|
|
92
|
+
resolvedOutputDir,
|
|
93
|
+
existingSnapshots,
|
|
94
|
+
buildTime,
|
|
95
|
+
baseRoute,
|
|
96
|
+
packageName,
|
|
97
|
+
...apiName != null ? { apiName } : {},
|
|
98
|
+
generatedFiles
|
|
99
|
+
});
|
|
100
|
+
yield* cleanupAndCommit({
|
|
101
|
+
fileResults,
|
|
102
|
+
resolvedOutputDir,
|
|
103
|
+
generatedFiles
|
|
104
|
+
});
|
|
105
|
+
yield* Effect.logInfo(`Generated ${changedCount} API documentation files for ${packageName}`);
|
|
106
|
+
return {
|
|
107
|
+
crossLinkData,
|
|
108
|
+
generatedFiles,
|
|
109
|
+
resolvedOutputDir,
|
|
110
|
+
baseRoute,
|
|
111
|
+
packageName,
|
|
112
|
+
apiName: apiName ?? void 0,
|
|
113
|
+
packageVersion: packageJson?.version,
|
|
114
|
+
packageDescription: typeof packageJson?.description === "string" ? packageJson.description : void 0
|
|
115
|
+
};
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
//#endregion
|
|
120
|
+
export { generateApiDocs };
|