rspress-plugin-api-extractor 0.15.2 → 0.16.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/build-program.js +4 -2
- package/build-stages.js +8 -2
- package/layers/AppLayer.js +2 -2
- package/layers/config-resolution.js +102 -2
- package/package.json +5 -4
- package/services/OgService.js +3 -3
package/build-program.js
CHANGED
|
@@ -36,7 +36,7 @@ function generateApiDocs(apiConfig, fileContextMap) {
|
|
|
36
36
|
const pageConcurrency = yield* PageConcurrency;
|
|
37
37
|
const suppressExampleErrors = yield* SuppressExampleErrors;
|
|
38
38
|
const snapshotSvc = yield* SnapshotService;
|
|
39
|
-
const { apiPackage, packageName, apiName, outputDir, baseRoute, categories, source, packageJson, llmsPlugin, siteUrl, ogImage, manifest } = apiConfig;
|
|
39
|
+
const { apiPackage, packageName, apiName, outputDir, baseRoute, categories, source, packageJson, llmsPlugin, siteUrl, ogImage, manifest, siteName, bundleOgImage } = apiConfig;
|
|
40
40
|
const phaseCtx = { packageName };
|
|
41
41
|
const structuredDataPkg = manifest != null && siteUrl != null ? packageContext({
|
|
42
42
|
siteUrl,
|
|
@@ -91,7 +91,9 @@ function generateApiDocs(apiConfig, fileContextMap) {
|
|
|
91
91
|
...apiConfig.docsRoot != null ? { docsRoot: apiConfig.docsRoot } : {},
|
|
92
92
|
...siteUrl != null ? { siteUrl } : {},
|
|
93
93
|
...ogImage != null ? { ogImage } : {},
|
|
94
|
-
...structuredDataPkg != null ? { structuredDataPkg } : {}
|
|
94
|
+
...structuredDataPkg != null ? { structuredDataPkg } : {},
|
|
95
|
+
...siteName != null ? { siteName } : {},
|
|
96
|
+
...bundleOgImage != null ? { bundleOgImage } : {}
|
|
95
97
|
}));
|
|
96
98
|
const changedCount = fileResults.filter((r) => r.status !== "unchanged").length;
|
|
97
99
|
yield* Effect.logDebug(`Generated ${changedCount} pages`);
|
package/build-stages.js
CHANGED
|
@@ -163,7 +163,8 @@ function generateSinglePage(workItem, ctx) {
|
|
|
163
163
|
siteUrl,
|
|
164
164
|
docsRoot: ctx.docsRoot,
|
|
165
165
|
packageName,
|
|
166
|
-
...apiName != null ? { apiName } : {}
|
|
166
|
+
...apiName != null ? { apiName } : {},
|
|
167
|
+
fallbackAlt: `${apiName ?? packageName} API documentation`
|
|
167
168
|
}));
|
|
168
169
|
if (ogImageResult._tag === "Failure") {
|
|
169
170
|
const failure = ogImageResult.failure;
|
|
@@ -178,6 +179,7 @@ function generateSinglePage(workItem, ctx) {
|
|
|
178
179
|
level: "warn"
|
|
179
180
|
}));
|
|
180
181
|
} else if (Option.isSome(ogImageResult.success)) ogImageMetadata = ogImageResult.success.value;
|
|
182
|
+
if (ogImageMetadata == null && ctx.bundleOgImage != null) ogImageMetadata = ctx.bundleOgImage;
|
|
181
183
|
if (ctx.structuredDataPkg != null) {
|
|
182
184
|
const graphResult = deriveScriptBody(ctx.structuredDataPkg, {
|
|
183
185
|
pageRoute: page.routePath,
|
|
@@ -215,11 +217,13 @@ function generateSinglePage(workItem, ctx) {
|
|
|
215
217
|
const finalFrontmatter = (published, modified) => seoEnabled ? generateFrontmatter(item.displayName, description, categoryConfig.singularName, apiName, headTags({
|
|
216
218
|
siteUrl: ctx.siteUrl,
|
|
217
219
|
pageRoute: page.routePath,
|
|
220
|
+
title: item.displayName,
|
|
218
221
|
description,
|
|
219
222
|
publishedTime: published,
|
|
220
223
|
modifiedTime: modified,
|
|
221
224
|
section: categoryConfig.displayName,
|
|
222
225
|
packageName,
|
|
226
|
+
...ctx.siteName != null ? { siteName: ctx.siteName } : {},
|
|
223
227
|
...ogImageMetadata != null ? { ogImage: ogImageMetadata } : {},
|
|
224
228
|
...structuredData != null ? { structuredData } : {}
|
|
225
229
|
})) : stringifyFrontmatter("", frontmatterData);
|
|
@@ -649,7 +653,9 @@ function buildPipelineForApi(input) {
|
|
|
649
653
|
...input.docsRoot !== void 0 ? { docsRoot: input.docsRoot } : {},
|
|
650
654
|
...input.siteUrl != null ? { siteUrl: input.siteUrl } : {},
|
|
651
655
|
...input.ogImage != null ? { ogImage: input.ogImage } : {},
|
|
652
|
-
...input.structuredDataPkg != null ? { structuredDataPkg: input.structuredDataPkg } : {}
|
|
656
|
+
...input.structuredDataPkg != null ? { structuredDataPkg: input.structuredDataPkg } : {},
|
|
657
|
+
...input.siteName != null ? { siteName: input.siteName } : {},
|
|
658
|
+
...input.bundleOgImage != null ? { bundleOgImage: input.bundleOgImage } : {}
|
|
653
659
|
};
|
|
654
660
|
const writeCtx = {
|
|
655
661
|
buildId: input.buildId,
|
package/layers/AppLayer.js
CHANGED
|
@@ -9,7 +9,7 @@ import { TypeRegistryService } from "../services/TypeRegistryService.js";
|
|
|
9
9
|
import { TwoslashCacheService } from "../services/TwoslashCacheService.js";
|
|
10
10
|
import { ConfigService } from "../services/ConfigService.js";
|
|
11
11
|
import { makeSummaryLoggerLayer } from "./observability.js";
|
|
12
|
-
import { Layer } from "effect";
|
|
12
|
+
import { Layer, Path } from "effect";
|
|
13
13
|
import { SnapshotService } from "@tsdoctor/snapshot";
|
|
14
14
|
import { NodeFileSystem } from "@effect/platform-node";
|
|
15
15
|
|
|
@@ -58,7 +58,7 @@ function makeAppLayers(input) {
|
|
|
58
58
|
/** Services scoped to this build's configuration. */
|
|
59
59
|
const BuildLayer = Layer.mergeAll(Layer.succeed(PluginConfig, input.options), HighlighterLive, TwoslashEnvironments.layer, BuildEnvLayer);
|
|
60
60
|
return {
|
|
61
|
-
app: Layer.provideMerge(ConfigService.layer, Layer.mergeAll(BuildLayer, CoreLayer, ObservabilityLayer, NodeFileSystem.layer)),
|
|
61
|
+
app: Layer.provideMerge(ConfigService.layer, Layer.mergeAll(BuildLayer, CoreLayer, ObservabilityLayer, NodeFileSystem.layer, Path.layer)),
|
|
62
62
|
emitter: Layer.mergeAll(ObservabilityLayer, BuildEnvLayer)
|
|
63
63
|
};
|
|
64
64
|
}
|
|
@@ -15,11 +15,13 @@ import { TypeRegistryService } from "../services/TypeRegistryService.js";
|
|
|
15
15
|
import { emitVfsPayloadEvents, mergeApiResult } from "./api-results.js";
|
|
16
16
|
import { mergeExternalTypes } from "./external-types.js";
|
|
17
17
|
import { registerTypeEnvironments, resolveTsConfigTyped } from "./type-environment.js";
|
|
18
|
+
import path from "node:path";
|
|
18
19
|
import { apiScopeOf, normalizeBaseRoute, unscopedName } from "@tsdoctor/pages";
|
|
19
20
|
import { Effect, Metric } from "effect";
|
|
20
21
|
import { ApiExtractedPackage, TypeReferenceExtractor } from "@tsdoctor/model";
|
|
21
22
|
import { deriveSiteUrl } from "@tsdoctor/seo";
|
|
22
23
|
import { hashContent } from "@tsdoctor/snapshot";
|
|
24
|
+
import { loadBundle, publishBundleAssets, resolveBundleFrom } from "@tsdoctor/bundle";
|
|
23
25
|
import { PackageManifest } from "@effected/package-json";
|
|
24
26
|
|
|
25
27
|
//#region src/layers/config-resolution.ts
|
|
@@ -95,6 +97,83 @@ function decodeManifest(packageJson, buildId, packageName) {
|
|
|
95
97
|
});
|
|
96
98
|
}
|
|
97
99
|
/**
|
|
100
|
+
* Resolve an API's bundle — display identity and Open Graph images — from its
|
|
101
|
+
* model directory, with provenance across the manifest tiers.
|
|
102
|
+
*
|
|
103
|
+
* @remarks
|
|
104
|
+
* Every API resolves a bundle, even one declared with a loader function
|
|
105
|
+
* rather than a path: a loader has no directory to discover a `tsdoctor.json`
|
|
106
|
+
* sidecar beside, so resolution falls back to an inferred bundle carrying
|
|
107
|
+
* only the package name — the same floor {@link resolveBundleFrom} itself
|
|
108
|
+
* guarantees for `name`.
|
|
109
|
+
*
|
|
110
|
+
* A present-but-malformed `tsdoctor.json` fails the build typed, the same
|
|
111
|
+
* posture as a malformed tsconfig: {@link ConfigValidationError} with
|
|
112
|
+
* `field: "bundle"`. Asset publishing degrades instead — a malformed or
|
|
113
|
+
* unreadable image must not stop the docs from generating, it should just
|
|
114
|
+
* render without a bundle-supplied `og:image`.
|
|
115
|
+
*/
|
|
116
|
+
function resolveApiBundle(input) {
|
|
117
|
+
const inferredBundle = {
|
|
118
|
+
bundle: { name: {
|
|
119
|
+
value: input.packageName,
|
|
120
|
+
source: "inferred"
|
|
121
|
+
} },
|
|
122
|
+
siteName: input.packageName
|
|
123
|
+
};
|
|
124
|
+
return Effect.gen(function* () {
|
|
125
|
+
const modelPath = input.model;
|
|
126
|
+
if (typeof modelPath !== "string") return inferredBundle;
|
|
127
|
+
const modelDir = path.dirname(modelPath);
|
|
128
|
+
const degradeToWarning = (cause) => Effect.andThen(emit(PluginEvent.ConfigValidationWarning({
|
|
129
|
+
ctx: {
|
|
130
|
+
buildId: input.buildId,
|
|
131
|
+
packageName: input.packageName
|
|
132
|
+
},
|
|
133
|
+
field: "bundle",
|
|
134
|
+
value: cause.path,
|
|
135
|
+
reason: cause.message,
|
|
136
|
+
level: "warn"
|
|
137
|
+
})), Effect.succeed(void 0));
|
|
138
|
+
const bundle = yield* loadBundle(modelDir, { overrides: {
|
|
139
|
+
modelPath,
|
|
140
|
+
name: input.packageName
|
|
141
|
+
} }).pipe(Effect.catchTag("BundleManifestError", (cause) => Effect.fail(new ConfigValidationError({
|
|
142
|
+
field: "bundle",
|
|
143
|
+
reason: cause.message,
|
|
144
|
+
cause
|
|
145
|
+
}))), Effect.catchTags({
|
|
146
|
+
BundleDiscoveryError: degradeToWarning,
|
|
147
|
+
BundleLayerError: degradeToWarning
|
|
148
|
+
}));
|
|
149
|
+
if (bundle === void 0) return inferredBundle;
|
|
150
|
+
const resolved = resolveBundleFrom(bundle, {});
|
|
151
|
+
const siteName = resolved.project?.value.name ?? resolved.name.value;
|
|
152
|
+
const published = resolved.openGraph !== void 0 && input.siteUrl != null ? yield* publishBundleAssets({
|
|
153
|
+
bundleDir: bundle.descriptor.dir,
|
|
154
|
+
images: resolved.openGraph.value.images,
|
|
155
|
+
publicDir: path.join(input.rspressRoot, "public"),
|
|
156
|
+
siteUrl: input.siteUrl,
|
|
157
|
+
unscopedName: unscopedName(input.packageName),
|
|
158
|
+
...input.assetSubdir != null ? { subdir: input.assetSubdir } : {}
|
|
159
|
+
}).pipe(Effect.tapError((error) => emit(PluginEvent.ConfigValidationWarning({
|
|
160
|
+
ctx: {
|
|
161
|
+
buildId: input.buildId,
|
|
162
|
+
packageName: input.packageName
|
|
163
|
+
},
|
|
164
|
+
field: "openGraph",
|
|
165
|
+
value: error.path,
|
|
166
|
+
reason: error.message,
|
|
167
|
+
level: "warn"
|
|
168
|
+
}))), Effect.orElseSucceed(() => [])) : [];
|
|
169
|
+
return {
|
|
170
|
+
bundle: resolved,
|
|
171
|
+
siteName,
|
|
172
|
+
...published[0] != null ? { bundleOgImage: published[0] } : {}
|
|
173
|
+
};
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
98
177
|
* Validate plugin options and return an Effect that fails with ConfigValidationError.
|
|
99
178
|
*/
|
|
100
179
|
function validateOptions(options, rspressConfig) {
|
|
@@ -244,6 +323,13 @@ const makeConfigService = Effect.gen(function* () {
|
|
|
244
323
|
const vfsPayloads = prependImportsToVfs(vfs, apiPackage, api.packageName, wantTrace);
|
|
245
324
|
const resolvedOgImage = api.ogImage ?? options.ogImage;
|
|
246
325
|
const resolvedTheme = normalizeThemeConfig(api.theme);
|
|
326
|
+
const { bundle, siteName, bundleOgImage } = yield* resolveApiBundle({
|
|
327
|
+
model,
|
|
328
|
+
packageName: api.packageName,
|
|
329
|
+
buildId,
|
|
330
|
+
siteUrl,
|
|
331
|
+
rspressRoot
|
|
332
|
+
});
|
|
247
333
|
return {
|
|
248
334
|
vfs,
|
|
249
335
|
vfsPayloads,
|
|
@@ -262,7 +348,10 @@ const makeConfigService = Effect.gen(function* () {
|
|
|
262
348
|
...siteUrl != null ? { siteUrl } : {},
|
|
263
349
|
...resolvedOgImage != null ? { ogImage: resolvedOgImage } : {},
|
|
264
350
|
...docsRoot != null ? { docsRoot } : {},
|
|
265
|
-
...resolvedTheme != null ? { theme: resolvedTheme } : {}
|
|
351
|
+
...resolvedTheme != null ? { theme: resolvedTheme } : {},
|
|
352
|
+
bundle,
|
|
353
|
+
siteName,
|
|
354
|
+
...bundleOgImage != null ? { bundleOgImage } : {}
|
|
266
355
|
}
|
|
267
356
|
};
|
|
268
357
|
}
|
|
@@ -325,6 +414,14 @@ const makeConfigService = Effect.gen(function* () {
|
|
|
325
414
|
const resolvedTheme = normalizeThemeConfig(api.theme);
|
|
326
415
|
const outputDir = versionDp.outputDir;
|
|
327
416
|
const fullRoute = versionDp.routeBase;
|
|
417
|
+
const { bundle, siteName, bundleOgImage } = yield* resolveApiBundle({
|
|
418
|
+
model: versionConfig.model,
|
|
419
|
+
packageName: api.packageName,
|
|
420
|
+
buildId,
|
|
421
|
+
siteUrl,
|
|
422
|
+
rspressRoot,
|
|
423
|
+
assetSubdir: version
|
|
424
|
+
});
|
|
328
425
|
return {
|
|
329
426
|
vfs,
|
|
330
427
|
vfsPayloads,
|
|
@@ -343,7 +440,10 @@ const makeConfigService = Effect.gen(function* () {
|
|
|
343
440
|
...siteUrl != null ? { siteUrl } : {},
|
|
344
441
|
...resolvedOgImage != null ? { ogImage: resolvedOgImage } : {},
|
|
345
442
|
...docsRoot != null ? { docsRoot } : {},
|
|
346
|
-
...resolvedTheme != null ? { theme: resolvedTheme } : {}
|
|
443
|
+
...resolvedTheme != null ? { theme: resolvedTheme } : {},
|
|
444
|
+
bundle,
|
|
445
|
+
siteName,
|
|
446
|
+
...bundleOgImage != null ? { bundleOgImage } : {}
|
|
347
447
|
}
|
|
348
448
|
};
|
|
349
449
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rspress-plugin-api-extractor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "RSPress plugin for generating API documentation from TypeScript API Extractor models",
|
|
6
6
|
"keywords": [
|
|
@@ -52,11 +52,12 @@
|
|
|
52
52
|
"@effected/yaml": "^0.12.0",
|
|
53
53
|
"@microsoft/api-extractor-model": "^7.33.11",
|
|
54
54
|
"@shikijs/twoslash": "^4.4.3",
|
|
55
|
-
"@tsdoctor/bundle": "0.
|
|
55
|
+
"@tsdoctor/bundle": "0.3.0",
|
|
56
|
+
"@tsdoctor/manifest": "0.1.0",
|
|
56
57
|
"@tsdoctor/model": "0.6.1",
|
|
57
|
-
"@tsdoctor/pages": "0.1.
|
|
58
|
+
"@tsdoctor/pages": "0.1.2",
|
|
58
59
|
"@tsdoctor/registry": "0.3.2",
|
|
59
|
-
"@tsdoctor/seo": "0.
|
|
60
|
+
"@tsdoctor/seo": "0.2.0",
|
|
60
61
|
"@tsdoctor/snapshot": "0.2.4",
|
|
61
62
|
"@tsdoctor/vfs": "0.2.1",
|
|
62
63
|
"@typescript/vfs": "^1.6.4",
|
package/services/OgService.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { PluginEvent } from "../observability/events.js";
|
|
2
2
|
import { emit } from "../observability/EventBus.js";
|
|
3
3
|
import { Context, Data, Effect, FileSystem, Layer, Option, Path } from "effect";
|
|
4
|
-
import { imageMimeType,
|
|
4
|
+
import { imageMimeType, resolveUrl } from "@tsdoctor/seo";
|
|
5
5
|
import { imageSize } from "image-size";
|
|
6
6
|
|
|
7
7
|
//#region src/services/OgService.ts
|
|
@@ -127,7 +127,7 @@ const make = () => Effect.gen(function* () {
|
|
|
127
127
|
type: facts?.type,
|
|
128
128
|
width: facts?.width,
|
|
129
129
|
height: facts?.height,
|
|
130
|
-
alt:
|
|
130
|
+
alt: request.fallbackAlt
|
|
131
131
|
});
|
|
132
132
|
});
|
|
133
133
|
const resolveFromMetadata = (metadata, request) => Effect.gen(function* () {
|
|
@@ -162,7 +162,7 @@ const make = () => Effect.gen(function* () {
|
|
|
162
162
|
type,
|
|
163
163
|
width,
|
|
164
164
|
height,
|
|
165
|
-
alt: alt ??
|
|
165
|
+
alt: alt ?? request.fallbackAlt
|
|
166
166
|
});
|
|
167
167
|
});
|
|
168
168
|
return { resolveImage: (request) => {
|