rspress-plugin-api-extractor 0.10.0 → 0.12.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/BuildEnv.js +58 -0
- package/build-program.js +34 -33
- package/build-stages.js +48 -42
- package/config-helpers.js +7 -7
- package/errors.js +1 -6
- package/index.d.ts +84 -86
- package/layers/AppLayer.js +67 -0
- package/layers/api-results.js +83 -0
- package/layers/build-metrics.js +1 -1
- package/layers/config-resolution.js +407 -0
- package/layers/external-types.js +74 -0
- package/layers/{ObservabilityLive.js → observability.js} +3 -3
- package/layers/type-environment.js +109 -0
- package/layers/xdg.js +44 -0
- package/markdown/helpers.js +9 -55
- package/markdown/page-generators/class-page.js +8 -31
- package/markdown/page-generators/index-pages.js +6 -8
- package/markdown/page-generators/interface-page.js +7 -7
- package/markdown/shiki-utils.js +65 -10
- package/model-loader.js +3 -3
- package/observability/EventBus.js +29 -7
- package/observability/heartbeat.js +1 -1
- package/observability/sinks/metrics-sink.js +1 -1
- package/observability/sinks/trace-sink.js +4 -4
- package/observability/spans.js +3 -1
- package/observability/sync-emitter.js +78 -0
- package/og-resolver.js +74 -284
- package/package.json +3 -4
- package/path-derivation.js +19 -1
- package/plugin.js +63 -91
- package/prettier-formatter.js +5 -11
- package/remark-api-codeblocks.js +11 -19
- package/remark-with-api.js +11 -21
- package/schemas/config.js +0 -2
- package/services/ConfigService.js +37 -2
- package/services/HighlighterService.js +75 -0
- package/services/OgService.js +190 -0
- package/services/PluginConfig.js +26 -0
- package/services/TwoslashCacheService.js +128 -2
- package/services/TwoslashEnvironments.js +35 -0
- package/services/TypeRegistryService.js +178 -2
- package/shiki-transformer.js +53 -234
- package/sync-node-fs.js +6 -6
- package/tsconfig-parser.js +77 -95
- package/twoslash-access.js +48 -0
- package/twoslash-transformer.js +106 -83
- package/vfs-registry.js +1 -31
- package/layers/ConfigServiceLive.js +0 -600
- package/layers/PathDerivationServiceLive.js +0 -16
- package/layers/TwoslashCacheServiceLive.js +0 -53
- package/layers/TypeRegistryServiceLive.js +0 -155
- package/markdown/index.js +0 -11
- package/schemas/index.js +0 -6
- package/services/PathDerivationService.js +0 -7
package/BuildEnv.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Context } from "effect";
|
|
2
|
+
|
|
3
|
+
//#region src/BuildEnv.ts
|
|
4
|
+
/**
|
|
5
|
+
* Per-build configuration, as `Context.Reference` values rather than threaded
|
|
6
|
+
* parameters.
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* Each of these used to travel by hand. `thresholds` made a four-hop journey —
|
|
10
|
+
* `plugin.ts` → `ConfigService.layer`'s fourth constructor argument →
|
|
11
|
+
* a `ResolvedBuildContext` field → destructured in `build-program.ts` → every
|
|
12
|
+
* `withPhase` call — while `obs.thresholds` already held the same value one
|
|
13
|
+
* scope away. `buildId` was worse: 24 event emit sites wrote
|
|
14
|
+
* `ctx: { buildId: "" }` because the value was not reachable from where they
|
|
15
|
+
* stood, and in `TypeRegistryService.layer` it genuinely was not — that layer is
|
|
16
|
+
* module-level and has no build to name. A Reference is the fix precisely
|
|
17
|
+
* because it reaches module-level code that no parameter can.
|
|
18
|
+
*
|
|
19
|
+
* A `Context.Reference` carries a default, so nothing has to provide these for
|
|
20
|
+
* a program to run. That is convenient and it is also the hazard: a wiring
|
|
21
|
+
* mistake does not fail, it silently substitutes the default. The tests for
|
|
22
|
+
* this module provide NON-default values and assert the provided value is the
|
|
23
|
+
* one observed, which is the only way to tell the two apart.
|
|
24
|
+
*
|
|
25
|
+
* @packageDocumentation
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* Identifier correlating every event emitted by one build.
|
|
29
|
+
*
|
|
30
|
+
* @remarks
|
|
31
|
+
* The empty-string default is deliberate: an event emitted outside a build —
|
|
32
|
+
* a test, a stray sync callback — is better tagged empty than crashing. Every
|
|
33
|
+
* production path provides a real id.
|
|
34
|
+
*/
|
|
35
|
+
const BuildId = Context.Reference("rspress-plugin-api-extractor/BuildId", { defaultValue: () => "" });
|
|
36
|
+
/** Duration thresholds above which an operation is reported as slow. */
|
|
37
|
+
const Thresholds = Context.Reference("rspress-plugin-api-extractor/Thresholds", { defaultValue: () => ({
|
|
38
|
+
slowCodeBlock: 100,
|
|
39
|
+
slowPageGeneration: 500,
|
|
40
|
+
slowApiLoad: 1e3,
|
|
41
|
+
slowFileOperation: 50,
|
|
42
|
+
slowHttpRequest: 2e3,
|
|
43
|
+
slowDbOperation: 100
|
|
44
|
+
}) });
|
|
45
|
+
/**
|
|
46
|
+
* How many pages the build pipeline generates concurrently.
|
|
47
|
+
*
|
|
48
|
+
* @remarks
|
|
49
|
+
* Defaults to the CPU count, which is what `ConfigService.layer` computed
|
|
50
|
+
* inline. Kept a Reference rather than a constant so a consumer with a
|
|
51
|
+
* constrained CI runner can lower it without a code change.
|
|
52
|
+
*/
|
|
53
|
+
const PageConcurrency = Context.Reference("rspress-plugin-api-extractor/PageConcurrency", { defaultValue: () => 1 });
|
|
54
|
+
/** Whether Twoslash diagnostics inside `@example` blocks are suppressed. */
|
|
55
|
+
const SuppressExampleErrors = Context.Reference("rspress-plugin-api-extractor/SuppressExampleErrors", { defaultValue: () => true });
|
|
56
|
+
|
|
57
|
+
//#endregion
|
|
58
|
+
export { BuildId, PageConcurrency, SuppressExampleErrors, Thresholds };
|
package/build-program.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
|
+
import { BuildId, PageConcurrency, SuppressExampleErrors } from "./BuildEnv.js";
|
|
1
2
|
import { setProseLinker } from "./markdown/prose-linker.js";
|
|
2
|
-
import "./markdown/index.js";
|
|
3
3
|
import { buildPipelineForApi, cleanupAndCommit, prepareWorkItems, writeMetadata } from "./build-stages.js";
|
|
4
|
+
import { HideCutLinesTransformer, MemberFormatTransformer } from "./hide-cut-transformer.js";
|
|
4
5
|
import { withPhase } from "./observability/spans.js";
|
|
5
|
-
import {
|
|
6
|
+
import { HighlighterService } from "./services/HighlighterService.js";
|
|
7
|
+
import { addTypeRoutes } from "./twoslash-transformer.js";
|
|
8
|
+
import { TwoslashEnvironments } from "./services/TwoslashEnvironments.js";
|
|
9
|
+
import { ShikiCrossLinker } from "./shiki-transformer.js";
|
|
6
10
|
import { VfsRegistry } from "./vfs-registry.js";
|
|
7
11
|
import path from "node:path";
|
|
8
|
-
import { SnapshotService } from "@tsdoctor/snapshot";
|
|
9
12
|
import { Effect, FileSystem } from "effect";
|
|
13
|
+
import { SnapshotService } from "@tsdoctor/snapshot";
|
|
10
14
|
|
|
11
15
|
//#region src/build-program.ts
|
|
12
16
|
/**
|
|
@@ -22,17 +26,17 @@ import { Effect, FileSystem } from "effect";
|
|
|
22
26
|
* Returns build result metadata including CrossLinkData for cross-link merging
|
|
23
27
|
* and generated file paths for LLMs post-processing.
|
|
24
28
|
*/
|
|
25
|
-
function generateApiDocs(apiConfig,
|
|
29
|
+
function generateApiDocs(apiConfig, fileContextMap) {
|
|
26
30
|
return Effect.gen(function* () {
|
|
27
31
|
const fileSystem = yield* FileSystem.FileSystem;
|
|
32
|
+
const { highlighter } = yield* HighlighterService;
|
|
33
|
+
const environments = yield* TwoslashEnvironments;
|
|
34
|
+
const buildId = yield* BuildId;
|
|
35
|
+
const pageConcurrency = yield* PageConcurrency;
|
|
36
|
+
const suppressExampleErrors = yield* SuppressExampleErrors;
|
|
28
37
|
const snapshotSvc = yield* SnapshotService;
|
|
29
38
|
const { apiPackage, packageName, apiName, outputDir, baseRoute, categories, source, packageJson, llmsPlugin, siteUrl, ogImage } = apiConfig;
|
|
30
|
-
const
|
|
31
|
-
const { shikiCrossLinker, highlighter, hideCutTransformer, hideCutLinesTransformer, twoslashTransformer, ogResolver, pageConcurrency, thresholds, buildId } = buildContext;
|
|
32
|
-
const phaseCtx = {
|
|
33
|
-
buildId,
|
|
34
|
-
packageName
|
|
35
|
-
};
|
|
39
|
+
const phaseCtx = { packageName };
|
|
36
40
|
const resolvedOutputDir = path.resolve(process.cwd(), outputDir);
|
|
37
41
|
const buildTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
38
42
|
const allSnapshots = yield* snapshotSvc.getAllForDirectory(resolvedOutputDir).pipe(Effect.orDie);
|
|
@@ -43,29 +47,27 @@ function generateApiDocs(apiConfig, buildContext, fileContextMap) {
|
|
|
43
47
|
categories,
|
|
44
48
|
baseRoute,
|
|
45
49
|
packageName
|
|
46
|
-
}))
|
|
50
|
+
})));
|
|
47
51
|
setProseLinker(crossLinkData.routes);
|
|
48
52
|
const apiScope = baseRoute.replace(/^\//, "").split("/")[0] || packageName;
|
|
49
|
-
shikiCrossLinker.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (apiConfig.theme != null) vfsConfig.theme = apiConfig.theme;
|
|
64
|
-
VfsRegistry.register(apiScope, vfsConfig);
|
|
65
|
-
}
|
|
53
|
+
const shikiCrossLinker = ShikiCrossLinker.fromRoutes(crossLinkData.routes, crossLinkData.kinds, apiScope);
|
|
54
|
+
addTypeRoutes(crossLinkData.routes);
|
|
55
|
+
const vfsConfig = {
|
|
56
|
+
highlighter,
|
|
57
|
+
crossLinker: shikiCrossLinker,
|
|
58
|
+
packageName,
|
|
59
|
+
apiScope
|
|
60
|
+
};
|
|
61
|
+
const scopeTransformer = environments.transformerFor(apiScope);
|
|
62
|
+
if (scopeTransformer != null) vfsConfig.twoslashTransformer = scopeTransformer;
|
|
63
|
+
vfsConfig.hideCutTransformer = MemberFormatTransformer;
|
|
64
|
+
vfsConfig.hideCutLinesTransformer = HideCutLinesTransformer;
|
|
65
|
+
if (apiConfig.theme != null) vfsConfig.theme = apiConfig.theme;
|
|
66
|
+
VfsRegistry.register(apiScope, vfsConfig);
|
|
66
67
|
yield* Effect.logDebug(`Generating ${workItems.length} pages across ${Object.keys(categories).length} categories in parallel`);
|
|
67
68
|
const fileResults = yield* withPhase("generate", phaseCtx, buildPipelineForApi({
|
|
68
69
|
buildId,
|
|
70
|
+
pageConcurrency,
|
|
69
71
|
workItems,
|
|
70
72
|
baseRoute,
|
|
71
73
|
packageName,
|
|
@@ -74,14 +76,13 @@ function generateApiDocs(apiConfig, buildContext, fileContextMap) {
|
|
|
74
76
|
...source != null ? { source } : {},
|
|
75
77
|
buildTime,
|
|
76
78
|
resolvedOutputDir,
|
|
77
|
-
pageConcurrency,
|
|
78
79
|
existingSnapshots,
|
|
79
80
|
...suppressExampleErrors != null ? { suppressExampleErrors } : {},
|
|
80
81
|
...llmsPlugin != null ? { llmsPlugin } : {},
|
|
81
|
-
...
|
|
82
|
+
...apiConfig.docsRoot != null ? { docsRoot: apiConfig.docsRoot } : {},
|
|
82
83
|
...siteUrl != null ? { siteUrl } : {},
|
|
83
84
|
...ogImage != null ? { ogImage } : {}
|
|
84
|
-
})
|
|
85
|
+
}));
|
|
85
86
|
const changedCount = fileResults.filter((r) => r.status !== "unchanged").length;
|
|
86
87
|
yield* Effect.logDebug(`Generated ${changedCount} pages`);
|
|
87
88
|
const generatedFiles = /* @__PURE__ */ new Set();
|
|
@@ -103,13 +104,13 @@ function generateApiDocs(apiConfig, buildContext, fileContextMap) {
|
|
|
103
104
|
packageName,
|
|
104
105
|
...apiName != null ? { apiName } : {},
|
|
105
106
|
generatedFiles
|
|
106
|
-
})
|
|
107
|
+
}));
|
|
107
108
|
yield* withPhase("cleanup", phaseCtx, cleanupAndCommit({
|
|
108
109
|
buildId,
|
|
109
110
|
fileResults,
|
|
110
111
|
resolvedOutputDir,
|
|
111
112
|
generatedFiles
|
|
112
|
-
})
|
|
113
|
+
}));
|
|
113
114
|
yield* Effect.logDebug(`Generated ${changedCount} API documentation files for ${packageName}`);
|
|
114
115
|
return {
|
|
115
116
|
crossLinkData,
|
package/build-stages.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { parseFrontmatter, stringifyFrontmatter } from "./frontmatter.js";
|
|
2
|
+
import { BuildMetrics } from "./layers/build-metrics.js";
|
|
2
3
|
import { PluginEvent } from "./observability/events.js";
|
|
3
4
|
import { emit } from "./observability/EventBus.js";
|
|
4
|
-
import {
|
|
5
|
-
import "./layers/ObservabilityLive.js";
|
|
5
|
+
import { emitSync, syncBuildId } from "./observability/sync-emitter.js";
|
|
6
6
|
import { generateFrontmatter } from "./markdown/helpers.js";
|
|
7
7
|
import { ClassPageGenerator } from "./markdown/page-generators/class-page.js";
|
|
8
8
|
import { EnumPageGenerator } from "./markdown/page-generators/enum-page.js";
|
|
@@ -12,11 +12,11 @@ import { InterfacePageGenerator } from "./markdown/page-generators/interface-pag
|
|
|
12
12
|
import { NamespacePageGenerator } from "./markdown/page-generators/namespace-page.js";
|
|
13
13
|
import { TypeAliasPageGenerator } from "./markdown/page-generators/type-alias-page.js";
|
|
14
14
|
import { VariablePageGenerator } from "./markdown/page-generators/variable-page.js";
|
|
15
|
-
import "./
|
|
16
|
-
import {
|
|
15
|
+
import { createPageMetadata } from "./og-resolver.js";
|
|
16
|
+
import { OgService } from "./services/OgService.js";
|
|
17
17
|
import path from "node:path";
|
|
18
|
+
import { Effect, FileSystem, Metric, Option, Stream } from "effect";
|
|
18
19
|
import { SnapshotService, hashContent, hashFrontmatter } from "@tsdoctor/snapshot";
|
|
19
|
-
import { Effect, FileSystem, Metric, Stream } from "effect";
|
|
20
20
|
import { ApiItemKind } from "@microsoft/api-extractor-model";
|
|
21
21
|
import { ApiItems, EntryPoints, Routes, SyntheticBases } from "@tsdoctor/model";
|
|
22
22
|
|
|
@@ -41,24 +41,6 @@ function crossLinkKindPriority(kind) {
|
|
|
41
41
|
return CROSS_LINK_KIND_PRIORITY[kind] ?? 100;
|
|
42
42
|
}
|
|
43
43
|
/**
|
|
44
|
-
* Module-level emitter seam. `prepareWorkItems` runs synchronously outside any
|
|
45
|
-
* Effect fiber, so a route collision cannot `yield* emit(...)` — it mirrors the
|
|
46
|
-
* sync-island pattern used by `twoslash-transformer.ts` (`setEventEmitter`) and
|
|
47
|
-
* `loader.ts` (`setLoaderEventEmitter`). Default is a no-op; wired in plugin.ts
|
|
48
|
-
* via `setBuildStagesEventEmitter(emitSync, buildId)` right after the runtime
|
|
49
|
-
* emitter is created.
|
|
50
|
-
*/
|
|
51
|
-
let emitEvent = () => {};
|
|
52
|
-
let currentBuildId = "";
|
|
53
|
-
/**
|
|
54
|
-
* Inject the runtime-bound emitter into the build-stages module.
|
|
55
|
-
* Call this right after `makeRuntimeEmitter` in plugin.ts.
|
|
56
|
-
*/
|
|
57
|
-
function setBuildStagesEventEmitter(fn, buildId = "") {
|
|
58
|
-
emitEvent = fn;
|
|
59
|
-
currentBuildId = buildId;
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
44
|
* Prepare the flat list of WorkItems to process and the cross-link data maps.
|
|
63
45
|
*
|
|
64
46
|
* This function:
|
|
@@ -82,8 +64,8 @@ function prepareWorkItems(input) {
|
|
|
82
64
|
resolvedLookup.set(key, resolved);
|
|
83
65
|
}
|
|
84
66
|
const { items, uncategorized } = ApiItems.categorize(docItems, categories);
|
|
85
|
-
for (const skipped of uncategorized)
|
|
86
|
-
ctx: { buildId:
|
|
67
|
+
for (const skipped of uncategorized) emitSync(PluginEvent.ItemSkipped({
|
|
68
|
+
ctx: { buildId: syncBuildId() },
|
|
87
69
|
item: skipped.displayName,
|
|
88
70
|
kind: String(skipped.kind),
|
|
89
71
|
reason: "uncategorized",
|
|
@@ -115,9 +97,9 @@ function prepareWorkItems(input) {
|
|
|
115
97
|
const collisions = Routes.detectCollisions(candidates);
|
|
116
98
|
if (collisions.length > 0) {
|
|
117
99
|
try {
|
|
118
|
-
for (const collision of collisions)
|
|
100
|
+
for (const collision of collisions) emitSync(PluginEvent.RouteCollisionDetected({
|
|
119
101
|
ctx: {
|
|
120
|
-
buildId:
|
|
102
|
+
buildId: syncBuildId(),
|
|
121
103
|
route: collision.route
|
|
122
104
|
},
|
|
123
105
|
level: "error",
|
|
@@ -145,13 +127,14 @@ function prepareWorkItems(input) {
|
|
|
145
127
|
}
|
|
146
128
|
if (item.kind === "Class" || item.kind === "Interface") {
|
|
147
129
|
const itemWithMembers = item;
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
130
|
+
const anchors = ApiItems.memberAnchors(itemWithMembers);
|
|
131
|
+
const byCanonicalRef = new Map(itemWithMembers.members.map((member) => [member.canonicalReference?.toString() ?? member.displayName, member]));
|
|
132
|
+
for (const [routeKey, memberId] of ApiItems.memberRouteKeys(itemWithMembers)) {
|
|
133
|
+
const member = byCanonicalRef.get(memberId);
|
|
134
|
+
if (!member) continue;
|
|
135
|
+
const anchor = anchors.get(memberId) ?? Routes.memberAnchor(member.displayName);
|
|
136
|
+
routes.set(routeKey, `${itemRoute}#${anchor}`);
|
|
137
|
+
kinds.set(routeKey, member.kind);
|
|
155
138
|
}
|
|
156
139
|
}
|
|
157
140
|
}
|
|
@@ -190,12 +173,14 @@ function prepareWorkItems(input) {
|
|
|
190
173
|
const lookupKey = `${item.displayName}::${item.kind}`;
|
|
191
174
|
const resolved = resolvedLookup.get(lookupKey);
|
|
192
175
|
const syntheticBase = syntheticBases.baseByOwner.get(item);
|
|
176
|
+
const memberAnchors = item.kind === "Class" || item.kind === "Interface" ? ApiItems.memberAnchors(item) : void 0;
|
|
193
177
|
workItems.push({
|
|
194
178
|
item,
|
|
195
179
|
categoryKey,
|
|
196
180
|
categoryConfig,
|
|
197
181
|
...resolved?.availableFrom != null ? { availableFrom: resolved.availableFrom } : {},
|
|
198
|
-
...syntheticBase != null ? { syntheticBase } : {}
|
|
182
|
+
...syntheticBase != null ? { syntheticBase } : {},
|
|
183
|
+
...memberAnchors != null ? { memberAnchors } : {}
|
|
199
184
|
});
|
|
200
185
|
}
|
|
201
186
|
}
|
|
@@ -240,7 +225,7 @@ function generateSinglePage(workItem, ctx) {
|
|
|
240
225
|
switch (item.kind) {
|
|
241
226
|
case ApiItemKind.Class: {
|
|
242
227
|
const generator = new ClassPageGenerator();
|
|
243
|
-
page = yield* Effect.promise(() => generator.generate(item, baseRoute, packageName, categoryConfig.singularName, apiScope, apiName, source, suppressExampleErrors, llmsPlugin, workItem.availableFrom, workItem.syntheticBase));
|
|
228
|
+
page = yield* Effect.promise(() => generator.generate(item, baseRoute, packageName, categoryConfig.singularName, apiScope, apiName, source, suppressExampleErrors, llmsPlugin, workItem.availableFrom, workItem.syntheticBase, workItem.memberAnchors));
|
|
244
229
|
page = {
|
|
245
230
|
routePath: page.routePath.replace("/class/", `/${categoryConfig.folderName}/`),
|
|
246
231
|
content: page.content
|
|
@@ -402,7 +387,7 @@ function generateSinglePage(workItem, ctx) {
|
|
|
402
387
|
function writeSingleFile(result, ctx) {
|
|
403
388
|
return Effect.gen(function* () {
|
|
404
389
|
const fileSystem = yield* FileSystem.FileSystem;
|
|
405
|
-
const { buildId, resolvedOutputDir, buildTime,
|
|
390
|
+
const { buildId, resolvedOutputDir, buildTime, siteUrl, docsRoot, ogImage, packageName, apiName } = ctx;
|
|
406
391
|
const { workItem, bodyContent, frontmatter, contentHash, frontmatterHash, publishedTime, modifiedTime, isUnchanged, routePath, relativePathWithExt } = result;
|
|
407
392
|
const { item, categoryKey, categoryConfig, namespaceMember } = workItem;
|
|
408
393
|
const absolutePath = path.join(resolvedOutputDir, relativePathWithExt);
|
|
@@ -440,8 +425,29 @@ function writeSingleFile(result, ctx) {
|
|
|
440
425
|
};
|
|
441
426
|
}
|
|
442
427
|
let finalContent = stringifyFrontmatter(bodyContent, frontmatter);
|
|
443
|
-
if (
|
|
444
|
-
const
|
|
428
|
+
if (siteUrl != null && packageName) {
|
|
429
|
+
const ogSvc = yield* OgService;
|
|
430
|
+
const ogImageResult = yield* Effect.result(ogSvc.resolveImage({
|
|
431
|
+
config: ogImage,
|
|
432
|
+
siteUrl,
|
|
433
|
+
docsRoot,
|
|
434
|
+
packageName,
|
|
435
|
+
...apiName != null ? { apiName } : {}
|
|
436
|
+
}));
|
|
437
|
+
if (ogImageResult._tag === "Failure") {
|
|
438
|
+
const failure = ogImageResult.failure;
|
|
439
|
+
yield* emit(PluginEvent.ConfigValidationWarning({
|
|
440
|
+
ctx: {
|
|
441
|
+
buildId,
|
|
442
|
+
packageName
|
|
443
|
+
},
|
|
444
|
+
field: failure.field,
|
|
445
|
+
value: failure.value,
|
|
446
|
+
reason: failure.message,
|
|
447
|
+
level: "warn"
|
|
448
|
+
}));
|
|
449
|
+
}
|
|
450
|
+
const ogImageMetadata = ogImageResult._tag === "Success" && Option.isSome(ogImageResult.success) ? ogImageResult.success.value : void 0;
|
|
445
451
|
const ogMetadataOptions = {
|
|
446
452
|
siteUrl,
|
|
447
453
|
pageRoute: routePath,
|
|
@@ -452,7 +458,7 @@ function writeSingleFile(result, ctx) {
|
|
|
452
458
|
packageName
|
|
453
459
|
};
|
|
454
460
|
if (ogImageMetadata) ogMetadataOptions.ogImage = ogImageMetadata;
|
|
455
|
-
const ogMetadata =
|
|
461
|
+
const ogMetadata = createPageMetadata(ogMetadataOptions);
|
|
456
462
|
finalContent = generateFrontmatter(item.displayName, frontmatter.description, categoryConfig.singularName, apiName, ogMetadata) + bodyContent;
|
|
457
463
|
}
|
|
458
464
|
const fileExisted = yield* fileSystem.exists(absolutePath).pipe(Effect.orElseSucceed(() => false));
|
|
@@ -793,7 +799,7 @@ function buildPipelineForApi(input) {
|
|
|
793
799
|
buildId: input.buildId,
|
|
794
800
|
resolvedOutputDir: input.resolvedOutputDir,
|
|
795
801
|
buildTime: input.buildTime,
|
|
796
|
-
...input.
|
|
802
|
+
...input.docsRoot !== void 0 ? { docsRoot: input.docsRoot } : {},
|
|
797
803
|
...input.siteUrl != null ? { siteUrl: input.siteUrl } : {},
|
|
798
804
|
...input.ogImage != null ? { ogImage: input.ogImage } : {},
|
|
799
805
|
...input.packageName != null ? { packageName: input.packageName } : {},
|
|
@@ -803,4 +809,4 @@ function buildPipelineForApi(input) {
|
|
|
803
809
|
}
|
|
804
810
|
|
|
805
811
|
//#endregion
|
|
806
|
-
export { buildPipelineForApi, cleanupAndCommit, crossLinkKindPriority, generateSinglePage, prepareWorkItems,
|
|
812
|
+
export { buildPipelineForApi, cleanupAndCommit, crossLinkKindPriority, generateSinglePage, prepareWorkItems, writeMetadata, writeSingleFile };
|
package/config-helpers.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { normalizeBaseRoute } from "./path-derivation.js";
|
|
2
2
|
import { SyncDiscoveryLayer } from "./sync-node-fs.js";
|
|
3
|
-
import
|
|
3
|
+
import fsSync from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { Effect, Result } from "effect";
|
|
6
6
|
import { discoverBundle } from "@tsdoctor/bundle";
|
|
@@ -18,14 +18,14 @@ const PREFIX = "[rspress-plugin-api-extractor]";
|
|
|
18
18
|
function requirePackageJson(dir) {
|
|
19
19
|
let stat;
|
|
20
20
|
try {
|
|
21
|
-
stat =
|
|
21
|
+
stat = fsSync.statSync(dir);
|
|
22
22
|
} catch {
|
|
23
23
|
throw new Error(`${PREFIX} api.fromDir: directory not found: ${dir}`);
|
|
24
24
|
}
|
|
25
25
|
if (!stat.isDirectory()) throw new Error(`${PREFIX} api.fromDir: not a directory: ${dir}`);
|
|
26
26
|
let pkg;
|
|
27
27
|
try {
|
|
28
|
-
pkg = JSON.parse(
|
|
28
|
+
pkg = JSON.parse(fsSync.readFileSync(path.join(dir, "package.json"), "utf8"));
|
|
29
29
|
} catch {
|
|
30
30
|
throw new Error(`${PREFIX} api.fromDir: missing or unreadable package.json in ${dir}`);
|
|
31
31
|
}
|
|
@@ -103,9 +103,9 @@ function fromDir(dir, overrides = {}) {
|
|
|
103
103
|
};
|
|
104
104
|
}
|
|
105
105
|
function isModelFolder(dir) {
|
|
106
|
-
if (!
|
|
106
|
+
if (!fsSync.existsSync(path.join(dir, "package.json"))) return false;
|
|
107
107
|
try {
|
|
108
|
-
return
|
|
108
|
+
return fsSync.readdirSync(dir).some((f) => f.endsWith(".api.json"));
|
|
109
109
|
} catch {
|
|
110
110
|
return false;
|
|
111
111
|
}
|
|
@@ -124,12 +124,12 @@ function fromParentDir(parentDir, options = {}) {
|
|
|
124
124
|
const absParent = path.resolve(cwd ?? process.cwd(), parentDir);
|
|
125
125
|
let stat;
|
|
126
126
|
try {
|
|
127
|
-
stat =
|
|
127
|
+
stat = fsSync.statSync(absParent);
|
|
128
128
|
} catch {
|
|
129
129
|
throw new Error(`${PREFIX} apis.fromDir: directory not found: ${absParent}`);
|
|
130
130
|
}
|
|
131
131
|
if (!stat.isDirectory()) throw new Error(`${PREFIX} apis.fromDir: not a directory: ${absParent}`);
|
|
132
|
-
const subdirs =
|
|
132
|
+
const subdirs = fsSync.readdirSync(absParent, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name).sort();
|
|
133
133
|
const configs = [];
|
|
134
134
|
for (const name of subdirs) {
|
|
135
135
|
const subdir = path.join(absParent, name);
|
package/errors.js
CHANGED
|
@@ -7,17 +7,12 @@ var ConfigValidationError = class extends ConfigValidationErrorBase {
|
|
|
7
7
|
return `Config validation failed for '${this.field}': ${this.reason}`;
|
|
8
8
|
}
|
|
9
9
|
};
|
|
10
|
-
const ApiModelLoadErrorBase = Data.TaggedError("ApiModelLoadError");
|
|
11
|
-
const PathDerivationErrorBase = Data.TaggedError("PathDerivationError");
|
|
12
10
|
const TypeRegistryErrorBase = Data.TaggedError("TypeRegistryError");
|
|
13
11
|
var TypeRegistryError = class extends TypeRegistryErrorBase {
|
|
14
12
|
get message() {
|
|
15
13
|
return `Type registry error for '${this.packageName}@${this.version}': ${this.reason}`;
|
|
16
14
|
}
|
|
17
15
|
};
|
|
18
|
-
const PageGenerationErrorBase = Data.TaggedError("PageGenerationError");
|
|
19
|
-
const TwoslashProcessingErrorBase = Data.TaggedError("TwoslashProcessingError");
|
|
20
|
-
const PrettierFormatErrorBase = Data.TaggedError("PrettierFormatError");
|
|
21
16
|
|
|
22
17
|
//#endregion
|
|
23
|
-
export { ConfigValidationError,
|
|
18
|
+
export { ConfigValidationError, TypeRegistryError };
|
package/index.d.ts
CHANGED
|
@@ -669,8 +669,6 @@ declare const PluginOptions: Schema.Struct<{
|
|
|
669
669
|
*/
|
|
670
670
|
readonly compilerOptions: Schema.optional<Schema.Unknown>;
|
|
671
671
|
}>>>>>;
|
|
672
|
-
/** Canonical site URL used for Open Graph absolute URLs. */
|
|
673
|
-
readonly siteUrl: Schema.optional<Schema.String>;
|
|
674
672
|
/** Global Open Graph image configuration (overridden per-API). */
|
|
675
673
|
readonly ogImage: Schema.optional<Schema.Union<readonly [Schema.String, Schema.Struct<{
|
|
676
674
|
readonly url: Schema.String;
|
|
@@ -746,90 +744,6 @@ declare const PluginOptions: Schema.Struct<{
|
|
|
746
744
|
/** @public */
|
|
747
745
|
type PluginOptions = typeof PluginOptions.Encoded;
|
|
748
746
|
//#endregion
|
|
749
|
-
//#region src/schemas/opengraph.d.ts
|
|
750
|
-
/**
|
|
751
|
-
* Structured Open Graph image metadata (alternative to a plain URL string).
|
|
752
|
-
*
|
|
753
|
-
* @public
|
|
754
|
-
*/
|
|
755
|
-
declare const OpenGraphImageMetadata: Schema.Struct<{
|
|
756
|
-
/** Absolute URL of the image. */
|
|
757
|
-
readonly url: Schema.String;
|
|
758
|
-
/** HTTPS URL of the image (for secure contexts). */
|
|
759
|
-
readonly secureUrl: Schema.optional<Schema.String>;
|
|
760
|
-
/** MIME type of the image (e.g. `"image/png"`). */
|
|
761
|
-
readonly type: Schema.optional<Schema.String>;
|
|
762
|
-
/** Image width in pixels. */
|
|
763
|
-
readonly width: Schema.optional<Schema.Number>;
|
|
764
|
-
/** Image height in pixels. */
|
|
765
|
-
readonly height: Schema.optional<Schema.Number>;
|
|
766
|
-
/** Alt text for the image. */
|
|
767
|
-
readonly alt: Schema.optional<Schema.String>;
|
|
768
|
-
}>;
|
|
769
|
-
/** @public */
|
|
770
|
-
type OpenGraphImageMetadata = typeof OpenGraphImageMetadata.Type;
|
|
771
|
-
/**
|
|
772
|
-
* Open Graph image: either a plain URL string or structured `OpenGraphImageMetadata`.
|
|
773
|
-
*
|
|
774
|
-
* @public
|
|
775
|
-
*/
|
|
776
|
-
declare const OpenGraphImageConfig: Schema.Union<readonly [Schema.String, Schema.Struct<{
|
|
777
|
-
/** Absolute URL of the image. */
|
|
778
|
-
readonly url: Schema.String;
|
|
779
|
-
/** HTTPS URL of the image (for secure contexts). */
|
|
780
|
-
readonly secureUrl: Schema.optional<Schema.String>;
|
|
781
|
-
/** MIME type of the image (e.g. `"image/png"`). */
|
|
782
|
-
readonly type: Schema.optional<Schema.String>;
|
|
783
|
-
/** Image width in pixels. */
|
|
784
|
-
readonly width: Schema.optional<Schema.Number>;
|
|
785
|
-
/** Image height in pixels. */
|
|
786
|
-
readonly height: Schema.optional<Schema.Number>;
|
|
787
|
-
/** Alt text for the image. */
|
|
788
|
-
readonly alt: Schema.optional<Schema.String>;
|
|
789
|
-
}>]>;
|
|
790
|
-
/** @public */
|
|
791
|
-
type OpenGraphImageConfig = typeof OpenGraphImageConfig.Type;
|
|
792
|
-
/**
|
|
793
|
-
* Resolved Open Graph metadata emitted into each generated page's frontmatter.
|
|
794
|
-
*
|
|
795
|
-
* @public
|
|
796
|
-
*/
|
|
797
|
-
declare const OpenGraphMetadata: Schema.Struct<{
|
|
798
|
-
/** Canonical site base URL. */
|
|
799
|
-
readonly siteUrl: Schema.String;
|
|
800
|
-
/** Page route path (e.g. `/api/classes/myclass`). */
|
|
801
|
-
readonly pageRoute: Schema.String;
|
|
802
|
-
/** Page description for the `og:description` tag. */
|
|
803
|
-
readonly description: Schema.String;
|
|
804
|
-
/** ISO 8601 date string for `article:published_time`. */
|
|
805
|
-
readonly publishedTime: Schema.String;
|
|
806
|
-
/** ISO 8601 date string for `article:modified_time`. */
|
|
807
|
-
readonly modifiedTime: Schema.String;
|
|
808
|
-
/** Article section label (e.g. `"API"`). */
|
|
809
|
-
readonly section: Schema.String;
|
|
810
|
-
/** Article tag keywords. */
|
|
811
|
-
readonly tags: Schema.mutable<Schema.$Array<Schema.String>>;
|
|
812
|
-
/** Optional structured image metadata. */
|
|
813
|
-
readonly ogImage: Schema.optional<Schema.Struct<{
|
|
814
|
-
/** Absolute URL of the image. */
|
|
815
|
-
readonly url: Schema.String;
|
|
816
|
-
/** HTTPS URL of the image (for secure contexts). */
|
|
817
|
-
readonly secureUrl: Schema.optional<Schema.String>;
|
|
818
|
-
/** MIME type of the image (e.g. `"image/png"`). */
|
|
819
|
-
readonly type: Schema.optional<Schema.String>;
|
|
820
|
-
/** Image width in pixels. */
|
|
821
|
-
readonly width: Schema.optional<Schema.Number>;
|
|
822
|
-
/** Image height in pixels. */
|
|
823
|
-
readonly height: Schema.optional<Schema.Number>;
|
|
824
|
-
/** Alt text for the image. */
|
|
825
|
-
readonly alt: Schema.optional<Schema.String>;
|
|
826
|
-
}>>;
|
|
827
|
-
/** Open Graph object type (e.g. `"article"`). */
|
|
828
|
-
readonly ogType: Schema.String;
|
|
829
|
-
}>;
|
|
830
|
-
/** @public */
|
|
831
|
-
type OpenGraphMetadata = typeof OpenGraphMetadata.Type;
|
|
832
|
-
//#endregion
|
|
833
747
|
//#region src/config-helpers.d.ts
|
|
834
748
|
/**
|
|
835
749
|
* Metadata discovered from a single rslib-builder localPaths package folder.
|
|
@@ -940,6 +854,90 @@ declare const ApiExtractorPlugin: typeof ApiExtractorPluginImpl & {
|
|
|
940
854
|
};
|
|
941
855
|
};
|
|
942
856
|
//#endregion
|
|
857
|
+
//#region src/schemas/opengraph.d.ts
|
|
858
|
+
/**
|
|
859
|
+
* Structured Open Graph image metadata (alternative to a plain URL string).
|
|
860
|
+
*
|
|
861
|
+
* @public
|
|
862
|
+
*/
|
|
863
|
+
declare const OpenGraphImageMetadata: Schema.Struct<{
|
|
864
|
+
/** Absolute URL of the image. */
|
|
865
|
+
readonly url: Schema.String;
|
|
866
|
+
/** HTTPS URL of the image (for secure contexts). */
|
|
867
|
+
readonly secureUrl: Schema.optional<Schema.String>;
|
|
868
|
+
/** MIME type of the image (e.g. `"image/png"`). */
|
|
869
|
+
readonly type: Schema.optional<Schema.String>;
|
|
870
|
+
/** Image width in pixels. */
|
|
871
|
+
readonly width: Schema.optional<Schema.Number>;
|
|
872
|
+
/** Image height in pixels. */
|
|
873
|
+
readonly height: Schema.optional<Schema.Number>;
|
|
874
|
+
/** Alt text for the image. */
|
|
875
|
+
readonly alt: Schema.optional<Schema.String>;
|
|
876
|
+
}>;
|
|
877
|
+
/** @public */
|
|
878
|
+
type OpenGraphImageMetadata = typeof OpenGraphImageMetadata.Type;
|
|
879
|
+
/**
|
|
880
|
+
* Open Graph image: either a plain URL string or structured `OpenGraphImageMetadata`.
|
|
881
|
+
*
|
|
882
|
+
* @public
|
|
883
|
+
*/
|
|
884
|
+
declare const OpenGraphImageConfig: Schema.Union<readonly [Schema.String, Schema.Struct<{
|
|
885
|
+
/** Absolute URL of the image. */
|
|
886
|
+
readonly url: Schema.String;
|
|
887
|
+
/** HTTPS URL of the image (for secure contexts). */
|
|
888
|
+
readonly secureUrl: Schema.optional<Schema.String>;
|
|
889
|
+
/** MIME type of the image (e.g. `"image/png"`). */
|
|
890
|
+
readonly type: Schema.optional<Schema.String>;
|
|
891
|
+
/** Image width in pixels. */
|
|
892
|
+
readonly width: Schema.optional<Schema.Number>;
|
|
893
|
+
/** Image height in pixels. */
|
|
894
|
+
readonly height: Schema.optional<Schema.Number>;
|
|
895
|
+
/** Alt text for the image. */
|
|
896
|
+
readonly alt: Schema.optional<Schema.String>;
|
|
897
|
+
}>]>;
|
|
898
|
+
/** @public */
|
|
899
|
+
type OpenGraphImageConfig = typeof OpenGraphImageConfig.Type;
|
|
900
|
+
/**
|
|
901
|
+
* Resolved Open Graph metadata emitted into each generated page's frontmatter.
|
|
902
|
+
*
|
|
903
|
+
* @public
|
|
904
|
+
*/
|
|
905
|
+
declare const OpenGraphMetadata: Schema.Struct<{
|
|
906
|
+
/** Canonical site base URL. */
|
|
907
|
+
readonly siteUrl: Schema.String;
|
|
908
|
+
/** Page route path (e.g. `/api/classes/myclass`). */
|
|
909
|
+
readonly pageRoute: Schema.String;
|
|
910
|
+
/** Page description for the `og:description` tag. */
|
|
911
|
+
readonly description: Schema.String;
|
|
912
|
+
/** ISO 8601 date string for `article:published_time`. */
|
|
913
|
+
readonly publishedTime: Schema.String;
|
|
914
|
+
/** ISO 8601 date string for `article:modified_time`. */
|
|
915
|
+
readonly modifiedTime: Schema.String;
|
|
916
|
+
/** Article section label (e.g. `"API"`). */
|
|
917
|
+
readonly section: Schema.String;
|
|
918
|
+
/** Article tag keywords. */
|
|
919
|
+
readonly tags: Schema.mutable<Schema.$Array<Schema.String>>;
|
|
920
|
+
/** Optional structured image metadata. */
|
|
921
|
+
readonly ogImage: Schema.optional<Schema.Struct<{
|
|
922
|
+
/** Absolute URL of the image. */
|
|
923
|
+
readonly url: Schema.String;
|
|
924
|
+
/** HTTPS URL of the image (for secure contexts). */
|
|
925
|
+
readonly secureUrl: Schema.optional<Schema.String>;
|
|
926
|
+
/** MIME type of the image (e.g. `"image/png"`). */
|
|
927
|
+
readonly type: Schema.optional<Schema.String>;
|
|
928
|
+
/** Image width in pixels. */
|
|
929
|
+
readonly width: Schema.optional<Schema.Number>;
|
|
930
|
+
/** Image height in pixels. */
|
|
931
|
+
readonly height: Schema.optional<Schema.Number>;
|
|
932
|
+
/** Alt text for the image. */
|
|
933
|
+
readonly alt: Schema.optional<Schema.String>;
|
|
934
|
+
}>>;
|
|
935
|
+
/** Open Graph object type (e.g. `"article"`). */
|
|
936
|
+
readonly ogType: Schema.String;
|
|
937
|
+
}>;
|
|
938
|
+
/** @public */
|
|
939
|
+
type OpenGraphMetadata = typeof OpenGraphMetadata.Type;
|
|
940
|
+
//#endregion
|
|
943
941
|
//#region src/serve.d.ts
|
|
944
942
|
/**
|
|
945
943
|
* Which RSPress server {@link serve} runs.
|