rspress-plugin-api-extractor 0.8.9 → 0.9.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 +11 -11
- package/build-program.js +3 -3
- package/build-stages.js +30 -31
- package/config-helpers.js +61 -21
- package/errors.js +1 -7
- package/frontmatter.js +149 -0
- package/index.d.ts +11 -2
- package/layers/ConfigServiceLive.js +53 -39
- package/layers/TypeRegistryServiceLive.js +10 -5
- package/markdown/helpers.js +40 -69
- package/markdown/index.js +2 -2
- package/markdown/page-generators/class-page.js +40 -42
- package/markdown/page-generators/enum-page.js +15 -15
- package/markdown/page-generators/function-page.js +18 -20
- package/markdown/page-generators/interface-page.js +41 -43
- package/markdown/page-generators/namespace-page.js +22 -24
- package/markdown/page-generators/type-alias-page.js +14 -16
- package/markdown/page-generators/variable-page.js +14 -16
- package/markdown/prose-linker.js +22 -0
- package/model-loader.js +59 -113
- package/package.json +11 -5
- package/plugin.js +1 -5
- package/shiki-transformer.js +3 -3
- package/sync-node-fs.js +80 -0
- package/twoslash-transformer.js +1 -1
- package/content-hash.js +0 -79
- package/formatter.js +0 -69
- package/layers/SnapshotServiceLive.js +0 -92
- package/loader.js +0 -200
- package/markdown/cross-linker.js +0 -157
- package/migrations/001_create_snapshots.js +0 -25
- package/multi-entry-resolver.js +0 -70
- package/route-collisions.js +0 -44
- package/services/SnapshotService.js +0 -7
- package/synthetic-bases.js +0 -74
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { ApiParser } from "../../loader.js";
|
|
2
1
|
import { TypeReferenceExtractor } from "../../type-reference-extractor.js";
|
|
3
2
|
import { escapeMdxGenerics, formatExampleCode, generateAvailableFrom, generateFrontmatter, prepareExampleCode, prependHiddenImports, stripTwoslashDirectives } from "../helpers.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { linkProse } from "../prose-linker.js";
|
|
4
|
+
import { ApiItems, Signature, Tsdoc } from "@tsdoctor/model";
|
|
6
5
|
|
|
7
6
|
//#region src/markdown/page-generators/type-alias-page.ts
|
|
8
7
|
/**
|
|
@@ -23,15 +22,14 @@ import { TypeSignatureFormatter } from "../../formatter.js";
|
|
|
23
22
|
*
|
|
24
23
|
* **Relationships:**
|
|
25
24
|
* - Created and invoked by {@link ApiExtractorPlugin} during page generation
|
|
26
|
-
* - Uses
|
|
27
|
-
* - Uses
|
|
28
|
-
* - Uses
|
|
25
|
+
* - Uses `Signature.format` from `@tsdoctor/model` for formatting type signatures
|
|
26
|
+
* - Uses the `Tsdoc` / `ApiItems` modules from `@tsdoctor/model` for extracting documentation
|
|
27
|
+
* - Uses the per-build prose linker (`linkProse`) for adding type reference links
|
|
29
28
|
*
|
|
30
29
|
* @see {@link InterfacePageGenerator} for interface documentation
|
|
31
30
|
* @see {@link EnumPageGenerator} for enum documentation
|
|
32
31
|
*/
|
|
33
32
|
var TypeAliasPageGenerator = class {
|
|
34
|
-
typeFormatter = new TypeSignatureFormatter();
|
|
35
33
|
/**
|
|
36
34
|
* Generate a markdown page for a type alias
|
|
37
35
|
*
|
|
@@ -40,22 +38,22 @@ var TypeAliasPageGenerator = class {
|
|
|
40
38
|
async generate(apiTypeAlias, baseRoute, packageName, singularName, apiScope, apiName, sourceConfig, suppressExampleErrors, llmsPlugin, availableFrom) {
|
|
41
39
|
const shouldSuppressErrors = suppressExampleErrors ?? true;
|
|
42
40
|
const name = apiTypeAlias.displayName;
|
|
43
|
-
const summary =
|
|
44
|
-
const releaseTag =
|
|
41
|
+
const summary = Tsdoc.summary(apiTypeAlias) || "No description available.";
|
|
42
|
+
const releaseTag = Tsdoc.releaseTag(apiTypeAlias);
|
|
45
43
|
let content = generateFrontmatter(name, summary, singularName, apiName);
|
|
46
44
|
content += `import { SourceCode } from "@rspress/core/theme";\n`;
|
|
47
45
|
content += `import { ParametersTable } from "rspress-plugin-api-extractor/runtime";\n`;
|
|
48
46
|
content += `import { ApiSignature, ApiExample } from "rspress-plugin-api-extractor/runtime";\n\n`;
|
|
49
47
|
content += `# ${name}\n\n`;
|
|
50
|
-
const deprecation =
|
|
48
|
+
const deprecation = Tsdoc.deprecation(apiTypeAlias);
|
|
51
49
|
if (deprecation) {
|
|
52
|
-
const message = escapeMdxGenerics(
|
|
50
|
+
const message = escapeMdxGenerics(linkProse(deprecation.message));
|
|
53
51
|
content += `> ⚠️ **Deprecated:** ${message}\n\n`;
|
|
54
52
|
}
|
|
55
53
|
if (releaseTag !== "Public") content += `\`${releaseTag}\`\n\n`;
|
|
56
54
|
content += `${summary}\n\n`;
|
|
57
55
|
content += generateAvailableFrom(packageName, availableFrom);
|
|
58
|
-
const sourceLink =
|
|
56
|
+
const sourceLink = ApiItems.sourceLink(apiTypeAlias, sourceConfig);
|
|
59
57
|
if (sourceLink) {
|
|
60
58
|
content += `<div className="api-docs-toolbar">\n`;
|
|
61
59
|
content += ` <div className="api-docs-toolbar-left">\n`;
|
|
@@ -68,7 +66,7 @@ var TypeAliasPageGenerator = class {
|
|
|
68
66
|
content += `</div>\n\n`;
|
|
69
67
|
}
|
|
70
68
|
if (apiTypeAlias.excerpt.text) {
|
|
71
|
-
const signature =
|
|
69
|
+
const signature = Signature.format(apiTypeAlias.excerpt).trim();
|
|
72
70
|
let signatureWithImports = signature;
|
|
73
71
|
const apiPackage = apiTypeAlias.getAssociatedPackage?.();
|
|
74
72
|
if (apiPackage) {
|
|
@@ -78,7 +76,7 @@ var TypeAliasPageGenerator = class {
|
|
|
78
76
|
const displayCode = stripTwoslashDirectives(signatureWithImports);
|
|
79
77
|
content += `<ApiSignature code={${JSON.stringify(displayCode)}} source={${JSON.stringify(signatureWithImports)}} apiScope={${JSON.stringify(apiScope)}} />\n\n`;
|
|
80
78
|
}
|
|
81
|
-
const examples =
|
|
79
|
+
const examples = Tsdoc.examples(apiTypeAlias);
|
|
82
80
|
if (examples.length > 0) {
|
|
83
81
|
content += `## Examples\n\n`;
|
|
84
82
|
for (const example of examples) {
|
|
@@ -93,11 +91,11 @@ var TypeAliasPageGenerator = class {
|
|
|
93
91
|
} else content += `\`\`\`${prepared.language}\n${formattedCode}\n\`\`\`\n\n`;
|
|
94
92
|
}
|
|
95
93
|
}
|
|
96
|
-
const seeReferences =
|
|
94
|
+
const seeReferences = Tsdoc.seeReferences(apiTypeAlias);
|
|
97
95
|
if (seeReferences.length > 0) {
|
|
98
96
|
content += `## See Also\n\n`;
|
|
99
97
|
for (const reference of seeReferences) {
|
|
100
|
-
const refText = escapeMdxGenerics(
|
|
98
|
+
const refText = escapeMdxGenerics(linkProse(reference.text));
|
|
101
99
|
content += `- ${refText}\n`;
|
|
102
100
|
}
|
|
103
101
|
content += `\n`;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { ApiParser } from "../../loader.js";
|
|
2
1
|
import { TypeReferenceExtractor } from "../../type-reference-extractor.js";
|
|
3
2
|
import { escapeMdxGenerics, formatExampleCode, generateAvailableFrom, generateFrontmatter, prepareExampleCode, prependHiddenImports, stripTwoslashDirectives } from "../helpers.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { linkProse } from "../prose-linker.js";
|
|
4
|
+
import { ApiItems, Signature, Tsdoc } from "@tsdoctor/model";
|
|
6
5
|
|
|
7
6
|
//#region src/markdown/page-generators/variable-page.ts
|
|
8
7
|
/**
|
|
@@ -23,15 +22,14 @@ import { TypeSignatureFormatter } from "../../formatter.js";
|
|
|
23
22
|
*
|
|
24
23
|
* **Relationships:**
|
|
25
24
|
* - Created and invoked by {@link ApiExtractorPlugin} during page generation
|
|
26
|
-
* - Uses
|
|
27
|
-
* - Uses
|
|
28
|
-
* - Uses
|
|
25
|
+
* - Uses `Signature.format` from `@tsdoctor/model` for formatting type signatures
|
|
26
|
+
* - Uses the `Tsdoc` / `ApiItems` modules from `@tsdoctor/model` for extracting documentation
|
|
27
|
+
* - Uses the per-build prose linker (`linkProse`) for adding type reference links
|
|
29
28
|
*
|
|
30
29
|
* @see {@link FunctionPageGenerator} for function documentation
|
|
31
30
|
* @see {@link EnumPageGenerator} for enum documentation
|
|
32
31
|
*/
|
|
33
32
|
var VariablePageGenerator = class {
|
|
34
|
-
typeFormatter = new TypeSignatureFormatter();
|
|
35
33
|
/**
|
|
36
34
|
* Generate a markdown page for a variable
|
|
37
35
|
*
|
|
@@ -40,22 +38,22 @@ var VariablePageGenerator = class {
|
|
|
40
38
|
async generate(apiVariable, baseRoute, packageName, singularName, apiScope, apiName, sourceConfig, suppressExampleErrors, llmsPlugin, availableFrom) {
|
|
41
39
|
const shouldSuppressErrors = suppressExampleErrors ?? true;
|
|
42
40
|
const name = apiVariable.displayName;
|
|
43
|
-
const summary =
|
|
44
|
-
const releaseTag =
|
|
41
|
+
const summary = Tsdoc.summary(apiVariable) || "No description available.";
|
|
42
|
+
const releaseTag = Tsdoc.releaseTag(apiVariable);
|
|
45
43
|
let content = generateFrontmatter(name, summary, singularName, apiName);
|
|
46
44
|
content += `import { SourceCode } from "@rspress/core/theme";\n`;
|
|
47
45
|
content += `import { ParametersTable } from "rspress-plugin-api-extractor/runtime";\n`;
|
|
48
46
|
content += `import { ApiSignature, ApiExample } from "rspress-plugin-api-extractor/runtime";\n\n`;
|
|
49
47
|
content += `# ${name}\n\n`;
|
|
50
|
-
const deprecation =
|
|
48
|
+
const deprecation = Tsdoc.deprecation(apiVariable);
|
|
51
49
|
if (deprecation) {
|
|
52
|
-
const message = escapeMdxGenerics(
|
|
50
|
+
const message = escapeMdxGenerics(linkProse(deprecation.message));
|
|
53
51
|
content += `> ⚠️ **Deprecated:** ${message}\n\n`;
|
|
54
52
|
}
|
|
55
53
|
if (releaseTag !== "Public") content += `\`${releaseTag}\`\n\n`;
|
|
56
54
|
content += `${summary}\n\n`;
|
|
57
55
|
content += generateAvailableFrom(packageName, availableFrom);
|
|
58
|
-
const sourceLink =
|
|
56
|
+
const sourceLink = ApiItems.sourceLink(apiVariable, sourceConfig);
|
|
59
57
|
if (sourceLink) {
|
|
60
58
|
content += `<div className="api-docs-toolbar">\n`;
|
|
61
59
|
content += ` <div className="api-docs-toolbar-left">\n`;
|
|
@@ -68,7 +66,7 @@ var VariablePageGenerator = class {
|
|
|
68
66
|
content += `</div>\n\n`;
|
|
69
67
|
}
|
|
70
68
|
if (apiVariable.excerpt.text) {
|
|
71
|
-
const signature =
|
|
69
|
+
const signature = Signature.format(apiVariable.excerpt).trim();
|
|
72
70
|
let signatureWithImports = signature;
|
|
73
71
|
const apiPackage = apiVariable.getAssociatedPackage?.();
|
|
74
72
|
if (apiPackage) {
|
|
@@ -78,7 +76,7 @@ var VariablePageGenerator = class {
|
|
|
78
76
|
const displayCode = stripTwoslashDirectives(signatureWithImports);
|
|
79
77
|
content += `<ApiSignature code={${JSON.stringify(displayCode)}} source={${JSON.stringify(signatureWithImports)}} apiScope={${JSON.stringify(apiScope)}} />\n\n`;
|
|
80
78
|
}
|
|
81
|
-
const examples =
|
|
79
|
+
const examples = Tsdoc.examples(apiVariable);
|
|
82
80
|
if (examples.length > 0) {
|
|
83
81
|
content += `## Examples\n\n`;
|
|
84
82
|
for (const example of examples) {
|
|
@@ -93,11 +91,11 @@ var VariablePageGenerator = class {
|
|
|
93
91
|
} else content += `\`\`\`${prepared.language}\n${formattedCode}\n\`\`\`\n\n`;
|
|
94
92
|
}
|
|
95
93
|
}
|
|
96
|
-
const seeReferences =
|
|
94
|
+
const seeReferences = Tsdoc.seeReferences(apiVariable);
|
|
97
95
|
if (seeReferences.length > 0) {
|
|
98
96
|
content += `## See Also\n\n`;
|
|
99
97
|
for (const reference of seeReferences) {
|
|
100
|
-
const refText = escapeMdxGenerics(
|
|
98
|
+
const refText = escapeMdxGenerics(linkProse(reference.text));
|
|
101
99
|
content += `- ${refText}\n`;
|
|
102
100
|
}
|
|
103
101
|
content += `\n`;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { CrossLinker } from "@tsdoctor/model";
|
|
2
|
+
|
|
3
|
+
//#region src/markdown/prose-linker.ts
|
|
4
|
+
/**
|
|
5
|
+
* Per-build prose cross-linker holder. Adapter wiring, not logic: the build
|
|
6
|
+
* program installs the immutable `@tsdoctor/model` CrossLinker built from the
|
|
7
|
+
* routes `prepareWorkItems` computed, and page generators link prose through
|
|
8
|
+
* it. Page generators run synchronously outside any service context, hence
|
|
9
|
+
* the module-level holder (the same shape as the sync-island event emitters).
|
|
10
|
+
*/
|
|
11
|
+
let current = CrossLinker.empty;
|
|
12
|
+
/** Install the cross-linker for the current API build from its route map. */
|
|
13
|
+
function setProseLinker(routes) {
|
|
14
|
+
current = CrossLinker.fromRoutes(routes);
|
|
15
|
+
}
|
|
16
|
+
/** Cross-link prose text with the currently installed linker. */
|
|
17
|
+
function linkProse(text) {
|
|
18
|
+
return current.link(text);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
//#endregion
|
|
22
|
+
export { linkProse, setProseLinker };
|
package/model-loader.js
CHANGED
|
@@ -1,128 +1,74 @@
|
|
|
1
|
-
import { PluginEvent } from "./observability/events.js";
|
|
2
1
|
import { isLoadedModel, isVersionConfig } from "./config-utils.js";
|
|
3
2
|
import fs from "node:fs";
|
|
4
3
|
import path from "node:path";
|
|
5
|
-
import {
|
|
4
|
+
import { Effect } from "effect";
|
|
5
|
+
import { Model } from "@tsdoctor/model";
|
|
6
6
|
|
|
7
7
|
//#region src/model-loader.ts
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
* `Effect.promise(async () => {...})` body in `ConfigServiceLive.ts`, so a
|
|
11
|
-
* load failure cannot `yield* emit(...)` — it mirrors the sync-island pattern
|
|
12
|
-
* used by `twoslash-transformer.ts` (`setEventEmitter`) and `loader.ts`
|
|
13
|
-
* (`setLoaderEventEmitter`, a DIFFERENT module — the ApiParser/TSDoc statics,
|
|
14
|
-
* not this one). Default is a no-op; wired in plugin.ts via
|
|
15
|
-
* `setModelLoaderEventEmitter(emitSync, buildId)` right after the runtime
|
|
16
|
-
* emitter is created.
|
|
9
|
+
* Load package.json from a path (string, URL, or Buffer).
|
|
17
10
|
*/
|
|
18
|
-
|
|
19
|
-
|
|
11
|
+
async function loadPackageJsonFromPath(pkgPath) {
|
|
12
|
+
const resolvedPath = path.resolve(pkgPath.toString());
|
|
13
|
+
if (!fs.existsSync(resolvedPath)) throw new Error(`Package.json file not found: ${resolvedPath}`);
|
|
14
|
+
const content = fs.readFileSync(resolvedPath, "utf-8");
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(content);
|
|
17
|
+
} catch (error) {
|
|
18
|
+
throw new Error(`Failed to parse package.json at ${resolvedPath}: ${error.message}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
20
21
|
/**
|
|
21
|
-
*
|
|
22
|
-
* Call this right after `makeRuntimeEmitter` in plugin.ts.
|
|
22
|
+
* Load package.json from PathLike or async function.
|
|
23
23
|
*/
|
|
24
|
-
function
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
async function loadPackageJson(loader) {
|
|
25
|
+
if (typeof loader === "function") return await loader();
|
|
26
|
+
return await loadPackageJsonFromPath(loader);
|
|
27
27
|
}
|
|
28
|
+
/** Extract the first package from a user-loader result (ApiModel-shaped object). */
|
|
29
|
+
const packageFromLoaderResult = (result) => {
|
|
30
|
+
if (result && typeof result === "object" && "packages" in result) return Model.firstPackage(result).pipe(Effect.mapError(() => new Model.EmptyModelError({ reason: "API model returned by function contains no packages" })));
|
|
31
|
+
return Effect.fail(new Model.EmptyModelError({ reason: "API model loader function must return an ApiModel" }));
|
|
32
|
+
};
|
|
28
33
|
/**
|
|
29
|
-
*
|
|
34
|
+
* Load an API model from a PathLike (via `Model.load`, typed errors) or a
|
|
35
|
+
* user-supplied async loader function (ApiModel or LoadedModel result).
|
|
30
36
|
*/
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
*
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
try {
|
|
42
|
-
if (!fs.existsSync(resolvedPath)) throw new Error(`API model file not found: ${resolvedPath}`);
|
|
43
|
-
return await loadApiModel(resolvedPath);
|
|
44
|
-
} catch (error) {
|
|
45
|
-
try {
|
|
46
|
-
emitEvent(PluginEvent.ModelLoadFailed({
|
|
47
|
-
ctx: { buildId: currentBuildId },
|
|
48
|
-
level: "error",
|
|
49
|
-
modelPath: resolvedPath,
|
|
50
|
-
reason: error instanceof Error ? error.message : String(error)
|
|
51
|
-
}));
|
|
52
|
-
} catch {}
|
|
53
|
-
throw error;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Load package.json from a path (string, URL, or Buffer)
|
|
58
|
-
*/
|
|
59
|
-
static async loadPackageJsonFromPath(pkgPath) {
|
|
60
|
-
const resolvedPath = path.resolve(pkgPath.toString());
|
|
61
|
-
if (!fs.existsSync(resolvedPath)) throw new Error(`Package.json file not found: ${resolvedPath}`);
|
|
62
|
-
const content = fs.readFileSync(resolvedPath, "utf-8");
|
|
63
|
-
try {
|
|
64
|
-
return JSON.parse(content);
|
|
65
|
-
} catch (error) {
|
|
66
|
-
throw new Error(`Failed to parse package.json at ${resolvedPath}: ${error.message}`);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Load package.json from PathLike or async function
|
|
71
|
-
*/
|
|
72
|
-
static async loadPackageJson(loader) {
|
|
73
|
-
if (typeof loader === "function") return await loader();
|
|
74
|
-
return await ApiModelLoader.loadPackageJsonFromPath(loader);
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Load an API model from PathLike or async function
|
|
78
|
-
*/
|
|
79
|
-
static async loadApiModel(loader) {
|
|
80
|
-
if (typeof loader === "function") {
|
|
81
|
-
const result = await loader();
|
|
82
|
-
if (isLoadedModel(result)) {
|
|
83
|
-
const model = result.model;
|
|
84
|
-
if (model && typeof model === "object" && "packages" in model) {
|
|
85
|
-
const packages = model.packages;
|
|
86
|
-
if (packages.length === 0) throw new Error("API model returned by function contains no packages");
|
|
87
|
-
const loadedResult = { apiPackage: packages[0] };
|
|
88
|
-
if (result.source != null) loadedResult.source = result.source;
|
|
89
|
-
return loadedResult;
|
|
90
|
-
}
|
|
91
|
-
throw new Error("API model loader function must return an ApiModel");
|
|
92
|
-
}
|
|
93
|
-
if (result && typeof result === "object" && "packages" in result) {
|
|
94
|
-
const packages = result.packages;
|
|
95
|
-
if (packages.length === 0) throw new Error("API model returned by function contains no packages");
|
|
96
|
-
return { apiPackage: packages[0] };
|
|
97
|
-
}
|
|
98
|
-
throw new Error("API model loader function must return an ApiModel or LoadedModel");
|
|
37
|
+
function loadApiModel(loader) {
|
|
38
|
+
if (typeof loader !== "function") return Model.load(loader.toString()).pipe(Effect.map((apiPackage) => ({ apiPackage })));
|
|
39
|
+
return Effect.gen(function* () {
|
|
40
|
+
const result = yield* Effect.promise(() => loader());
|
|
41
|
+
if (isLoadedModel(result)) {
|
|
42
|
+
const apiPackage = yield* packageFromLoaderResult(result.model);
|
|
43
|
+
return result.source != null ? {
|
|
44
|
+
apiPackage,
|
|
45
|
+
source: result.source
|
|
46
|
+
} : { apiPackage };
|
|
99
47
|
}
|
|
100
|
-
return { apiPackage:
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
};
|
|
48
|
+
return { apiPackage: yield* packageFromLoaderResult(result) };
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Resolve and load a version config (full VersionConfig, or a bare model
|
|
53
|
+
* path/loader).
|
|
54
|
+
*/
|
|
55
|
+
function loadVersionModel(versionValue) {
|
|
56
|
+
if (!isVersionConfig(versionValue)) return loadApiModel(versionValue);
|
|
57
|
+
return Effect.gen(function* () {
|
|
58
|
+
const { apiPackage, source: loaderSource } = yield* loadApiModel(versionValue.model);
|
|
59
|
+
const packageJson = versionValue.packageJson ? yield* Effect.promise(() => loadPackageJson(versionValue.packageJson)) : void 0;
|
|
60
|
+
const versionResult = { apiPackage };
|
|
61
|
+
if (packageJson != null) versionResult.packageJson = packageJson;
|
|
62
|
+
if (versionValue.categories != null) versionResult.categories = versionValue.categories;
|
|
63
|
+
const resolvedSource = loaderSource || versionValue.source;
|
|
64
|
+
if (resolvedSource != null) versionResult.source = resolvedSource;
|
|
65
|
+
if (versionValue.externalPackages != null) versionResult.externalPackages = versionValue.externalPackages;
|
|
66
|
+
if (versionValue.autoDetectDependencies != null) versionResult.autoDetectDependencies = versionValue.autoDetectDependencies;
|
|
67
|
+
if (versionValue.ogImage != null) versionResult.ogImage = versionValue.ogImage;
|
|
68
|
+
if (versionValue.llmsPlugin != null) versionResult.llmsPlugin = versionValue.llmsPlugin;
|
|
69
|
+
return versionResult;
|
|
70
|
+
});
|
|
71
|
+
}
|
|
126
72
|
|
|
127
73
|
//#endregion
|
|
128
|
-
export {
|
|
74
|
+
export { loadApiModel, loadPackageJson, loadVersionModel };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rspress-plugin-api-extractor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "RSPress plugin for generating API documentation from TypeScript API Extractor models",
|
|
6
6
|
"keywords": [
|
|
@@ -38,19 +38,25 @@
|
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@effect/platform-node": "4.0.0-rc.109",
|
|
41
|
-
"@
|
|
41
|
+
"@effected/github": "^0.8.0",
|
|
42
|
+
"@effected/glob": "^0.4.0",
|
|
43
|
+
"@effected/npm": "^0.12.0",
|
|
44
|
+
"@effected/package-json": "^0.11.0",
|
|
42
45
|
"@effected/semver": "^0.5.0",
|
|
43
46
|
"@effected/store": "^0.4.0",
|
|
44
47
|
"@effected/tsconfig-json": "^0.6.0",
|
|
48
|
+
"@effected/walker": "^0.5.0",
|
|
45
49
|
"@effected/xdg": "^0.3.0",
|
|
50
|
+
"@effected/yaml": "^0.11.0",
|
|
46
51
|
"@microsoft/api-extractor-model": "^7.33.11",
|
|
47
52
|
"@shikijs/twoslash": "^4.4.3",
|
|
48
|
-
"@tsdoctor/
|
|
49
|
-
"@tsdoctor/
|
|
53
|
+
"@tsdoctor/bundle": "0.1.0",
|
|
54
|
+
"@tsdoctor/model": "0.2.0",
|
|
55
|
+
"@tsdoctor/registry": "0.2.0",
|
|
56
|
+
"@tsdoctor/snapshot": "0.1.0",
|
|
50
57
|
"@typescript/vfs": "^1.6.4",
|
|
51
58
|
"clsx": "^2.1.1",
|
|
52
59
|
"effect": "4.0.0-rc.109",
|
|
53
|
-
"gray-matter": "^4.0.3",
|
|
54
60
|
"hast-util-to-jsx-runtime": "^2.3.6",
|
|
55
61
|
"image-size": "^2.0.2",
|
|
56
62
|
"ioredis": "^5.7.0",
|
package/plugin.js
CHANGED
|
@@ -3,7 +3,6 @@ import { emit, makeRuntimeEmitter } from "./observability/EventBus.js";
|
|
|
3
3
|
import { runHeartbeat } from "./observability/heartbeat.js";
|
|
4
4
|
import { writeIssuesJson } from "./observability/sinks/issues-sink.js";
|
|
5
5
|
import { buildEventBus, logBuildSummary, makeSummaryLoggerLayer } from "./layers/ObservabilityLive.js";
|
|
6
|
-
import { setLoaderEventEmitter } from "./loader.js";
|
|
7
6
|
import { setPrettierEventEmitter } from "./prettier-formatter.js";
|
|
8
7
|
import { setOgResolverEventEmitter } from "./og-resolver.js";
|
|
9
8
|
import { setBuildStagesEventEmitter } from "./build-stages.js";
|
|
@@ -14,14 +13,12 @@ import { deriveOutputPaths, normalizeBaseRoute, unscopedName } from "./path-deri
|
|
|
14
13
|
import { fromDir, fromParentDir } from "./config-helpers.js";
|
|
15
14
|
import { classifyApiConfig, mergeLlmsPluginConfig } from "./config-utils.js";
|
|
16
15
|
import { DEFAULT_SHIKI_THEMES, setShikiUtilsEventEmitter } from "./markdown/shiki-utils.js";
|
|
17
|
-
import { setModelLoaderEventEmitter } from "./model-loader.js";
|
|
18
16
|
import { resolveObservability } from "./schemas/observability.js";
|
|
19
17
|
import { PluginOptions } from "./schemas/config.js";
|
|
20
18
|
import "./schemas/index.js";
|
|
21
19
|
import { ConfigService } from "./services/ConfigService.js";
|
|
22
20
|
import { ConfigServiceLive } from "./layers/ConfigServiceLive.js";
|
|
23
21
|
import { PathDerivationServiceLive } from "./layers/PathDerivationServiceLive.js";
|
|
24
|
-
import { SnapshotServiceLive } from "./layers/SnapshotServiceLive.js";
|
|
25
22
|
import { TypeRegistryServiceLive } from "./layers/TypeRegistryServiceLive.js";
|
|
26
23
|
import { remarkApiCodeblocks, setRemarkApiCodeblocksEventEmitter } from "./remark-api-codeblocks.js";
|
|
27
24
|
import { remarkWithApi, setRemarkWithApiEventEmitter } from "./remark-with-api.js";
|
|
@@ -31,6 +28,7 @@ import fs from "node:fs";
|
|
|
31
28
|
import path from "node:path";
|
|
32
29
|
import { fileURLToPath } from "node:url";
|
|
33
30
|
import { NodeFileSystem } from "@effect/platform-node";
|
|
31
|
+
import { SnapshotServiceLive } from "@tsdoctor/snapshot";
|
|
34
32
|
import { Effect, FileSystem, Layer, ManagedRuntime, Ref, Schema } from "effect";
|
|
35
33
|
|
|
36
34
|
//#region src/plugin.ts
|
|
@@ -93,14 +91,12 @@ function ApiExtractorPluginImpl(rawOptions) {
|
|
|
93
91
|
const effectRuntime = ManagedRuntime.make(EffectAppLayer);
|
|
94
92
|
const emitSync = makeRuntimeEmitter(effectRuntime);
|
|
95
93
|
setEventEmitter(emitSync, buildId);
|
|
96
|
-
setLoaderEventEmitter(emitSync, buildId);
|
|
97
94
|
setShikiUtilsEventEmitter(emitSync, buildId);
|
|
98
95
|
setPrettierEventEmitter(emitSync, buildId);
|
|
99
96
|
setOgResolverEventEmitter(emitSync, buildId);
|
|
100
97
|
setRemarkWithApiEventEmitter(emitSync, buildId, obs.thresholds.slowCodeBlock);
|
|
101
98
|
setRemarkApiCodeblocksEventEmitter(emitSync, buildId);
|
|
102
99
|
setBuildStagesEventEmitter(emitSync, buildId);
|
|
103
|
-
setModelLoaderEventEmitter(emitSync, buildId);
|
|
104
100
|
const fileContextMap = /* @__PURE__ */ new Map();
|
|
105
101
|
let docsRoot;
|
|
106
102
|
let isFirstBuild = true;
|
package/shiki-transformer.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* class member references (e.g., `ClassName.methodName`).
|
|
9
9
|
*
|
|
10
10
|
* **How it works:**
|
|
11
|
-
* 1. The transformer is initialized with route and kind maps from
|
|
11
|
+
* 1. The transformer is initialized with route and kind maps from `prepareWorkItems`
|
|
12
12
|
* 2. During Shiki rendering, it walks the HAST tree looking for type names
|
|
13
13
|
* 3. When a match is found, it wraps the text node in an anchor tag with the route
|
|
14
14
|
* 4. Semantic CSS classes are added based on the API item kind (class, interface, etc.)
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
*
|
|
21
21
|
* **Relationships:**
|
|
22
22
|
* - Initialized by {@link ApiExtractorPlugin} during the beforeBuild hook
|
|
23
|
-
* - Receives route data from
|
|
23
|
+
* - Receives route data from the cross-link maps built in `prepareWorkItems`
|
|
24
24
|
* - Used alongside {@link TwoslashManager} for type-aware code blocks
|
|
25
25
|
* - Works with the hide-cut transformer for member signatures
|
|
26
26
|
*
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
* });
|
|
45
45
|
* ```
|
|
46
46
|
*
|
|
47
|
-
* @see
|
|
47
|
+
* @see the `@tsdoctor/model` CrossLinker for the markdown equivalent
|
|
48
48
|
* @see {@link TwoslashManager} for type-aware documentation features
|
|
49
49
|
*/
|
|
50
50
|
var ShikiCrossLinker = class {
|
package/sync-node-fs.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { Effect, FileSystem, Layer, Option, Path, PlatformError } from "effect";
|
|
3
|
+
|
|
4
|
+
//#region src/sync-node-fs.ts
|
|
5
|
+
/**
|
|
6
|
+
* A synchronous, read-only `FileSystem` implementation over `node:fs`'s sync
|
|
7
|
+
* API, covering exactly the members `@tsdoctor/bundle`'s discovery walks:
|
|
8
|
+
* `exists`, `stat`, `readFileString`, `readDirectory` and `readLink`.
|
|
9
|
+
*
|
|
10
|
+
* The config helpers (`fromDir`/`fromParentDir`) are a SYNC public API called
|
|
11
|
+
* at rspress.config evaluation time, while bundle discovery is an Effect
|
|
12
|
+
* program over the `FileSystem` service. `@effect/platform-node`'s
|
|
13
|
+
* `NodeFileSystem` is promise-backed, so running discovery under
|
|
14
|
+
* `Effect.runSync` needs this bridge; every other member stays
|
|
15
|
+
* `layerNoop`-denied, which is deliberate — a new discovery dependency on an
|
|
16
|
+
* unimplemented member should fail loudly here, not silently misbehave.
|
|
17
|
+
*/
|
|
18
|
+
const tagOf = (cause) => {
|
|
19
|
+
const code = cause?.code;
|
|
20
|
+
if (code === "ENOENT") return "NotFound";
|
|
21
|
+
if (code === "EACCES" || code === "EPERM") return "PermissionDenied";
|
|
22
|
+
return "Unknown";
|
|
23
|
+
};
|
|
24
|
+
const fail = (method, pathOrDescriptor, cause) => PlatformError.systemError({
|
|
25
|
+
_tag: tagOf(cause),
|
|
26
|
+
module: "FileSystem",
|
|
27
|
+
method,
|
|
28
|
+
pathOrDescriptor,
|
|
29
|
+
cause
|
|
30
|
+
});
|
|
31
|
+
const typeOf = (stats) => {
|
|
32
|
+
if (stats.isDirectory()) return "Directory";
|
|
33
|
+
if (stats.isSymbolicLink()) return "SymbolicLink";
|
|
34
|
+
if (stats.isFile()) return "File";
|
|
35
|
+
return "Unknown";
|
|
36
|
+
};
|
|
37
|
+
const infoFromStats = (stats) => ({
|
|
38
|
+
type: typeOf(stats),
|
|
39
|
+
mtime: Option.some(stats.mtime),
|
|
40
|
+
atime: Option.some(stats.atime),
|
|
41
|
+
birthtime: Option.some(stats.birthtime),
|
|
42
|
+
dev: stats.dev,
|
|
43
|
+
ino: Option.some(stats.ino),
|
|
44
|
+
mode: stats.mode,
|
|
45
|
+
nlink: Option.some(stats.nlink),
|
|
46
|
+
uid: Option.some(stats.uid),
|
|
47
|
+
gid: Option.some(stats.gid),
|
|
48
|
+
rdev: Option.some(stats.rdev),
|
|
49
|
+
size: FileSystem.Size(stats.size),
|
|
50
|
+
blksize: Option.some(FileSystem.Size(stats.blksize)),
|
|
51
|
+
blocks: Option.some(stats.blocks)
|
|
52
|
+
});
|
|
53
|
+
const syncFileSystem = FileSystem.layerNoop({
|
|
54
|
+
exists: (path) => Effect.sync(() => fs.existsSync(path)),
|
|
55
|
+
stat: (path) => Effect.try({
|
|
56
|
+
try: () => infoFromStats(fs.statSync(path)),
|
|
57
|
+
catch: (cause) => fail("stat", path, cause)
|
|
58
|
+
}),
|
|
59
|
+
readFileString: (path) => Effect.try({
|
|
60
|
+
try: () => fs.readFileSync(path, "utf8"),
|
|
61
|
+
catch: (cause) => fail("readFileString", path, cause)
|
|
62
|
+
}),
|
|
63
|
+
readDirectory: (path) => Effect.try({
|
|
64
|
+
try: () => fs.readdirSync(path),
|
|
65
|
+
catch: (cause) => fail("readDirectory", path, cause)
|
|
66
|
+
}),
|
|
67
|
+
readLink: (path) => Effect.try({
|
|
68
|
+
try: () => fs.readlinkSync(path),
|
|
69
|
+
catch: (cause) => fail("readLink", path, cause)
|
|
70
|
+
})
|
|
71
|
+
});
|
|
72
|
+
/**
|
|
73
|
+
* The full sync environment bundle discovery runs under from the config
|
|
74
|
+
* helpers: the sync `FileSystem` bridge plus the (already sync) `Path`
|
|
75
|
+
* service.
|
|
76
|
+
*/
|
|
77
|
+
const SyncDiscoveryLayer = Layer.mergeAll(syncFileSystem, Path.layer);
|
|
78
|
+
|
|
79
|
+
//#endregion
|
|
80
|
+
export { SyncDiscoveryLayer };
|
package/twoslash-transformer.js
CHANGED
|
@@ -204,7 +204,7 @@ function renderMarkdownInline(markdown, context) {
|
|
|
204
204
|
* **VFS Integration:**
|
|
205
205
|
* The VFS is populated by {@link TypeRegistryService} with:
|
|
206
206
|
* - The documented package's own type definitions (from API Extractor)
|
|
207
|
-
* - External package types (fetched via
|
|
207
|
+
* - External package types (fetched via @tsdoctor/registry)
|
|
208
208
|
*
|
|
209
209
|
* **Error Handling:**
|
|
210
210
|
* TypeScript errors in code blocks are captured (not thrown) and:
|