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
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { BuildId, Thresholds } from "../BuildEnv.js";
|
|
2
|
+
import { emit } from "./EventBus.js";
|
|
3
|
+
import { Effect } from "effect";
|
|
4
|
+
|
|
5
|
+
//#region src/observability/sync-emitter.ts
|
|
6
|
+
/**
|
|
7
|
+
* The one bridge from synchronous, fiber-less code to the EventBus.
|
|
8
|
+
*
|
|
9
|
+
* @remarks
|
|
10
|
+
* Seven modules run outside any Effect fiber — remark visitors, Shiki's
|
|
11
|
+
* `preprocess` hook, Prettier callbacks, the page-generation stages — and each
|
|
12
|
+
* carried its own byte-identical copy of this seam: a module-level
|
|
13
|
+
* `emitEvent`, a module-level `currentBuildId`, and a `setXEventEmitter(fn,
|
|
14
|
+
* buildId)` for `plugin.ts` to call. Two of them had already grown a third
|
|
15
|
+
* parameter for `slowCodeBlockMs`, which is how a duplicated seam decays: the
|
|
16
|
+
* copies stop being identical one caller at a time.
|
|
17
|
+
*
|
|
18
|
+
* The seam itself is forced. The duplication was not, and neither was the
|
|
19
|
+
* threading: every value those setters carried is now a `Context.Reference`
|
|
20
|
+
* read from the runtime, so the signature is one runtime and nothing else.
|
|
21
|
+
*
|
|
22
|
+
* **The runtime handed here must be synchronously buildable.** `runSync`
|
|
23
|
+
* builds the runtime's layer before running anything, so a runtime whose layer
|
|
24
|
+
* opens a database fails with `AsyncFiberError` at the first emit — from a
|
|
25
|
+
* remark plugin, during RSPress's render pass, invisible to every unit test.
|
|
26
|
+
* `plugin.ts` builds a small observability-only runtime for exactly this
|
|
27
|
+
* reason.
|
|
28
|
+
*
|
|
29
|
+
* @packageDocumentation
|
|
30
|
+
*/
|
|
31
|
+
const NOOP = {
|
|
32
|
+
emit: () => {},
|
|
33
|
+
buildId: "",
|
|
34
|
+
slowCodeBlockMs: Number.POSITIVE_INFINITY
|
|
35
|
+
};
|
|
36
|
+
let current = NOOP;
|
|
37
|
+
/**
|
|
38
|
+
* Bind the sync islands to a runtime.
|
|
39
|
+
*
|
|
40
|
+
* @remarks
|
|
41
|
+
* Call once, immediately after constructing the observability runtime. The
|
|
42
|
+
* References are read here rather than per emit: an emit happens per code
|
|
43
|
+
* block on a large site, and these values are fixed for the build.
|
|
44
|
+
*/
|
|
45
|
+
function installSyncEmitter(runtime) {
|
|
46
|
+
const env = runtime.runSync(Effect.gen(function* () {
|
|
47
|
+
return {
|
|
48
|
+
buildId: yield* BuildId,
|
|
49
|
+
slowCodeBlockMs: (yield* Thresholds).slowCodeBlock
|
|
50
|
+
};
|
|
51
|
+
}));
|
|
52
|
+
current = {
|
|
53
|
+
emit: (event) => runtime.runSync(emit(event)),
|
|
54
|
+
buildId: env.buildId,
|
|
55
|
+
slowCodeBlockMs: env.slowCodeBlockMs
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/** Emit an event from synchronous code. A no-op when nothing is installed. */
|
|
59
|
+
function emitSync(event) {
|
|
60
|
+
current.emit(event);
|
|
61
|
+
}
|
|
62
|
+
/** The current build's id, for a sync site assembling an `EventContext`. */
|
|
63
|
+
function syncBuildId() {
|
|
64
|
+
return current.buildId;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The slow-code-block threshold, for the two remark plugins that time blocks.
|
|
68
|
+
*
|
|
69
|
+
* @remarks
|
|
70
|
+
* The only piece of configuration a sync island needs beyond the build id, and
|
|
71
|
+
* the reason the old seams had begun growing divergent signatures.
|
|
72
|
+
*/
|
|
73
|
+
function syncSlowCodeBlockMs() {
|
|
74
|
+
return current.slowCodeBlockMs;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
//#endregion
|
|
78
|
+
export { emitSync, installSyncEmitter, syncBuildId, syncSlowCodeBlockMs };
|
package/og-resolver.js
CHANGED
|
@@ -1,19 +1,6 @@
|
|
|
1
|
-
import { PluginEvent } from "./observability/events.js";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { imageSizeFromFile } from "image-size/fromFile";
|
|
5
|
-
|
|
6
1
|
//#region src/og-resolver.ts
|
|
7
|
-
/** Module-level emitter injected by plugin.ts at startup. */
|
|
8
|
-
let emitEvent = () => {};
|
|
9
|
-
let currentBuildId = "";
|
|
10
|
-
function setOgResolverEventEmitter(fn, buildId = "") {
|
|
11
|
-
emitEvent = fn;
|
|
12
|
-
currentBuildId = buildId;
|
|
13
|
-
}
|
|
14
2
|
/**
|
|
15
|
-
* MIME type mappings for common image formats
|
|
16
|
-
* Used to determine the `og:image:type` meta tag value.
|
|
3
|
+
* MIME type mappings for common image formats, used for `og:image:type`.
|
|
17
4
|
*/
|
|
18
5
|
const IMAGE_MIME_TYPES = {
|
|
19
6
|
jpg: "image/jpeg",
|
|
@@ -24,282 +11,85 @@ const IMAGE_MIME_TYPES = {
|
|
|
24
11
|
svg: "image/svg+xml"
|
|
25
12
|
};
|
|
26
13
|
/**
|
|
27
|
-
*
|
|
14
|
+
* The `og:image:type` value for a detected image format, or `undefined` for a
|
|
15
|
+
* format with no mapping.
|
|
16
|
+
*/
|
|
17
|
+
function imageMimeType(type) {
|
|
18
|
+
if (type == null) return void 0;
|
|
19
|
+
return IMAGE_MIME_TYPES[type.toLowerCase()];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Turn a configured image URL into an absolute one.
|
|
28
23
|
*
|
|
29
|
-
*
|
|
30
|
-
* (
|
|
31
|
-
*
|
|
24
|
+
* @returns The absolute URL, or `undefined` when the input is neither an
|
|
25
|
+
* absolute `http(s)` URL nor a site-root-relative path. A bare relative path
|
|
26
|
+
* is deliberately rejected rather than guessed at — there is no base to
|
|
27
|
+
* resolve it against that would not silently produce a broken link.
|
|
28
|
+
*/
|
|
29
|
+
function resolveOgUrl(siteUrl, url) {
|
|
30
|
+
if (url.startsWith("http://") || url.startsWith("https://")) return url;
|
|
31
|
+
if (url.startsWith("/")) return `${siteUrl}${url}`;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Derive the site URL prefix from RSPress's own config.
|
|
32
35
|
*
|
|
33
36
|
* @remarks
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* -
|
|
38
|
-
*
|
|
37
|
+
* Replaces the plugin's former `siteUrl` option. RSPress already knows where a
|
|
38
|
+
* site is deployed — {@link https://rspress.rs/api/config/config-basic#siteorigin | `siteOrigin`}
|
|
39
|
+
* plus `base` — so asking for it a second time invited the two to disagree, and
|
|
40
|
+
* a plugin-level answer that contradicted the site's own would silently emit
|
|
41
|
+
* canonical and `og:url` tags pointing at a host the site is not served from.
|
|
39
42
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* docsRoot: "/path/to/docs"
|
|
45
|
-
* });
|
|
43
|
+
* RSPress concatenates as `siteOrigin + base + routePath`, and **this follows
|
|
44
|
+
* its documented fallback exactly**: with no `siteOrigin`, RSPress uses
|
|
45
|
+
* `base + routePath`. So an unset origin yields a ROOT-RELATIVE prefix rather
|
|
46
|
+
* than nothing.
|
|
46
47
|
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* )
|
|
52
|
-
* // Result: { url: "https://example.com/images/og-api.png", width: 1200, height: 630, ... }
|
|
53
|
-
* ```
|
|
48
|
+
* That fallback is what makes the tags inspectable in `rspress dev`, where the
|
|
49
|
+
* site is served from `localhost` and no configured origin could be correct
|
|
50
|
+
* anyway. A root-relative `/images/og.png` resolves against the page's own
|
|
51
|
+
* origin in the browser; it is a *relative* path (`images/og.png`, no leading
|
|
52
|
+
* slash) that has no base to resolve against, and this never emits one.
|
|
54
53
|
*
|
|
55
|
-
* @
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* url: "/images/og.png",
|
|
60
|
-
* alt: "Custom alt text",
|
|
61
|
-
* width: 1200,
|
|
62
|
-
* height: 630
|
|
63
|
-
* },
|
|
64
|
-
* "my-package"
|
|
65
|
-
* );
|
|
66
|
-
* ```
|
|
54
|
+
* @returns The prefix to put in front of a route that already begins with `/`.
|
|
55
|
+
* `""` when the site declares neither `siteOrigin` nor a non-root `base`, which
|
|
56
|
+
* leaves every URL root-relative. Never has a trailing slash, since every
|
|
57
|
+
* caller appends a route starting with `/`.
|
|
67
58
|
*/
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
* ```typescript
|
|
103
|
-
* const metadata = await resolver.resolve("/images/og.png", "my-lib");
|
|
104
|
-
* // Returns: { url: "https://example.com/images/og.png", alt: "my-lib API Documentation", ... }
|
|
105
|
-
* ```
|
|
106
|
-
*
|
|
107
|
-
* @example Resolve an absolute URL
|
|
108
|
-
* ```typescript
|
|
109
|
-
* const metadata = await resolver.resolve("https://cdn.example.com/og.png", "my-lib");
|
|
110
|
-
* // Returns: { url: "https://cdn.example.com/og.png", alt: "my-lib API Documentation" }
|
|
111
|
-
* ```
|
|
112
|
-
*/
|
|
113
|
-
async resolve(config, packageName, apiName) {
|
|
114
|
-
if (!config) return;
|
|
115
|
-
if (typeof config === "object") return this.resolveFromMetadata(config, packageName, apiName);
|
|
116
|
-
return this.resolveFromString(config, packageName, apiName);
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* Resolves a metadata object configuration into complete OG image metadata.
|
|
120
|
-
*
|
|
121
|
-
* Validates and resolves URLs in the provided metadata object, ensuring all
|
|
122
|
-
* URLs are absolute and properly formatted.
|
|
123
|
-
*
|
|
124
|
-
* @param metadata - The metadata object containing OG image properties
|
|
125
|
-
* @param packageName - Package name for default alt text generation
|
|
126
|
-
* @param apiName - Optional API name for more descriptive alt text
|
|
127
|
-
* @returns Resolved metadata with absolute URLs, or `undefined` if URL is invalid
|
|
128
|
-
*/
|
|
129
|
-
async resolveFromMetadata(metadata, packageName, apiName) {
|
|
130
|
-
const { url, secureUrl, type, width, height, alt } = metadata;
|
|
131
|
-
const resolvedUrl = this.resolveUrl(url);
|
|
132
|
-
if (!resolvedUrl) {
|
|
133
|
-
emitEvent(PluginEvent.ConfigValidationWarning({
|
|
134
|
-
ctx: { buildId: currentBuildId },
|
|
135
|
-
field: "ogImage.url",
|
|
136
|
-
value: url,
|
|
137
|
-
reason: "invalid URL format",
|
|
138
|
-
level: "warn"
|
|
139
|
-
}));
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
let resolvedSecureUrl;
|
|
143
|
-
if (secureUrl) {
|
|
144
|
-
if (secureUrl.startsWith("https://")) resolvedSecureUrl = secureUrl;
|
|
145
|
-
else emitEvent(PluginEvent.ConfigValidationWarning({
|
|
146
|
-
ctx: { buildId: currentBuildId },
|
|
147
|
-
field: "ogImage.secureUrl",
|
|
148
|
-
value: secureUrl,
|
|
149
|
-
reason: "secureUrl must be absolute HTTPS",
|
|
150
|
-
level: "warn"
|
|
151
|
-
}));
|
|
152
|
-
}
|
|
153
|
-
return {
|
|
154
|
-
url: resolvedUrl,
|
|
155
|
-
secureUrl: resolvedSecureUrl,
|
|
156
|
-
type,
|
|
157
|
-
width,
|
|
158
|
-
height,
|
|
159
|
-
alt: alt ?? this.generateAltText(packageName, apiName)
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
/**
|
|
163
|
-
* Resolves a string URL/path into complete OG image metadata.
|
|
164
|
-
*
|
|
165
|
-
* For relative paths pointing to local files, this method will attempt to:
|
|
166
|
-
* 1. Locate the file in the docs `public` directory
|
|
167
|
-
* 2. Read the image dimensions using `image-size`
|
|
168
|
-
* 3. Determine the MIME type from the file extension
|
|
169
|
-
*
|
|
170
|
-
* @param imageUrl - The image URL or path to resolve
|
|
171
|
-
* @param packageName - Package name for alt text generation
|
|
172
|
-
* @param apiName - Optional API name for more descriptive alt text
|
|
173
|
-
* @returns Resolved metadata with auto-detected dimensions for local files,
|
|
174
|
-
* or `undefined` if the URL format is invalid
|
|
175
|
-
*/
|
|
176
|
-
async resolveFromString(imageUrl, packageName, apiName) {
|
|
177
|
-
const resolvedUrl = this.resolveUrl(imageUrl);
|
|
178
|
-
if (!resolvedUrl) {
|
|
179
|
-
emitEvent(PluginEvent.ConfigValidationWarning({
|
|
180
|
-
ctx: { buildId: currentBuildId },
|
|
181
|
-
field: "ogImage",
|
|
182
|
-
value: imageUrl,
|
|
183
|
-
reason: "invalid URL format",
|
|
184
|
-
level: "warn"
|
|
185
|
-
}));
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
const localPath = this.findLocalImage(imageUrl);
|
|
189
|
-
const dimensions = localPath ? await this.readImageDimensions(localPath) : void 0;
|
|
190
|
-
return {
|
|
191
|
-
url: resolvedUrl,
|
|
192
|
-
type: dimensions?.type,
|
|
193
|
-
width: dimensions?.width,
|
|
194
|
-
height: dimensions?.height,
|
|
195
|
-
alt: this.generateAltText(packageName, apiName)
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
/**
|
|
199
|
-
* Resolves a URL string to an absolute URL.
|
|
200
|
-
*
|
|
201
|
-
* @param url - The URL to resolve (absolute URL or relative path)
|
|
202
|
-
* @returns The absolute URL, or `undefined` if the format is invalid
|
|
203
|
-
*/
|
|
204
|
-
resolveUrl(url) {
|
|
205
|
-
if (url.startsWith("http://") || url.startsWith("https://")) return url;
|
|
206
|
-
if (url.startsWith("/")) return `${this.siteUrl}${url}`;
|
|
207
|
-
}
|
|
208
|
-
/**
|
|
209
|
-
* Attempts to find a local image file in the docs public directory.
|
|
210
|
-
*
|
|
211
|
-
* @param imagePath - The relative image path (starting with `/`)
|
|
212
|
-
* @returns The absolute file path if found, or `undefined` if not found
|
|
213
|
-
* or if `docsRoot` is not configured
|
|
214
|
-
*/
|
|
215
|
-
findLocalImage(imagePath) {
|
|
216
|
-
if (!this.docsRoot || !imagePath.startsWith("/")) return;
|
|
217
|
-
const publicPath = path.join(this.docsRoot, "public", imagePath);
|
|
218
|
-
if (fs.existsSync(publicPath)) return publicPath;
|
|
219
|
-
}
|
|
220
|
-
/**
|
|
221
|
-
* Reads image dimensions and type from a local file.
|
|
222
|
-
*
|
|
223
|
-
* @param filePath - Absolute path to the image file
|
|
224
|
-
* @returns Object containing width, height, and MIME type if successful,
|
|
225
|
-
* or `undefined` if the file cannot be read or analyzed
|
|
226
|
-
*/
|
|
227
|
-
async readImageDimensions(filePath) {
|
|
228
|
-
try {
|
|
229
|
-
const dimensions = await imageSizeFromFile(filePath);
|
|
230
|
-
let mimeType;
|
|
231
|
-
if (dimensions.type) mimeType = IMAGE_MIME_TYPES[dimensions.type.toLowerCase()];
|
|
232
|
-
return {
|
|
233
|
-
width: dimensions.width,
|
|
234
|
-
height: dimensions.height,
|
|
235
|
-
...mimeType != null ? { type: mimeType } : {}
|
|
236
|
-
};
|
|
237
|
-
} catch (error) {
|
|
238
|
-
emitEvent(PluginEvent.ConfigValidationWarning({
|
|
239
|
-
ctx: { buildId: currentBuildId },
|
|
240
|
-
field: "ogImage",
|
|
241
|
-
value: filePath,
|
|
242
|
-
reason: error.message ?? String(error),
|
|
243
|
-
level: "warn"
|
|
244
|
-
}));
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
/**
|
|
249
|
-
* Generates descriptive alt text for the OG image.
|
|
250
|
-
*
|
|
251
|
-
* @param packageName - The package name
|
|
252
|
-
* @param apiName - Optional API name for more specific text
|
|
253
|
-
* @returns Generated alt text string
|
|
254
|
-
*/
|
|
255
|
-
generateAltText(packageName, apiName) {
|
|
256
|
-
if (apiName) return `${apiName} - ${packageName} API Documentation`;
|
|
257
|
-
return `${packageName} API Documentation`;
|
|
258
|
-
}
|
|
259
|
-
/**
|
|
260
|
-
* Creates complete Open Graph metadata for an API documentation page.
|
|
261
|
-
*
|
|
262
|
-
* This static factory method builds a complete `OpenGraphMetadata` object
|
|
263
|
-
* suitable for inclusion in page frontmatter, combining resolved image
|
|
264
|
-
* metadata with article-specific information.
|
|
265
|
-
*
|
|
266
|
-
* @param options - Configuration for the OG metadata.
|
|
267
|
-
* Includes `siteUrl`, `pageRoute`, `description`, `publishedTime`,
|
|
268
|
-
* `modifiedTime`, `section`, `packageName`, and optional `ogImage`.
|
|
269
|
-
* @returns Complete Open Graph metadata object
|
|
270
|
-
*
|
|
271
|
-
* @example
|
|
272
|
-
* ```typescript
|
|
273
|
-
* const ogMetadata = OpenGraphResolver.createPageMetadata({
|
|
274
|
-
* siteUrl: "https://example.com",
|
|
275
|
-
* pageRoute: "/api/classes/MyClass",
|
|
276
|
-
* description: "MyClass provides...",
|
|
277
|
-
* publishedTime: "2024-01-15T10:00:00Z",
|
|
278
|
-
* modifiedTime: "2024-01-20T15:30:00Z",
|
|
279
|
-
* section: "Classes",
|
|
280
|
-
* packageName: "my-library",
|
|
281
|
-
* ogImage: resolvedImageMetadata
|
|
282
|
-
* });
|
|
283
|
-
* ```
|
|
284
|
-
*/
|
|
285
|
-
static createPageMetadata(options) {
|
|
286
|
-
return {
|
|
287
|
-
siteUrl: options.siteUrl,
|
|
288
|
-
pageRoute: options.pageRoute,
|
|
289
|
-
description: options.description,
|
|
290
|
-
publishedTime: options.publishedTime,
|
|
291
|
-
modifiedTime: options.modifiedTime,
|
|
292
|
-
section: options.section,
|
|
293
|
-
tags: [
|
|
294
|
-
"TypeScript",
|
|
295
|
-
"API",
|
|
296
|
-
options.packageName
|
|
297
|
-
],
|
|
298
|
-
...options.ogImage != null ? { ogImage: options.ogImage } : {},
|
|
299
|
-
ogType: "article"
|
|
300
|
-
};
|
|
301
|
-
}
|
|
302
|
-
};
|
|
59
|
+
function deriveSiteUrl(siteOrigin, base) {
|
|
60
|
+
const origin = (siteOrigin ?? "").trim().replace(/\/+$/, "");
|
|
61
|
+
const path = (base ?? "/").trim();
|
|
62
|
+
return `${origin}${path === "" || path === "/" ? "" : `/${path.replace(/^\/+/, "").replace(/\/+$/, "")}`}`;
|
|
63
|
+
}
|
|
64
|
+
/** Descriptive alt text for a package's (or one API's) OG image. */
|
|
65
|
+
function ogAltText(packageName, apiName) {
|
|
66
|
+
return apiName ? `${apiName} - ${packageName} API Documentation` : `${packageName} API Documentation`;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Assemble the complete Open Graph metadata for one documentation page.
|
|
70
|
+
*
|
|
71
|
+
* @remarks
|
|
72
|
+
* Was `OpenGraphResolver.createPageMetadata`. It never touched the resolver's
|
|
73
|
+
* instance state, so it is a free function now rather than a static on a class
|
|
74
|
+
* that no longer exists.
|
|
75
|
+
*/
|
|
76
|
+
function createPageMetadata(options) {
|
|
77
|
+
return {
|
|
78
|
+
siteUrl: options.siteUrl,
|
|
79
|
+
pageRoute: options.pageRoute,
|
|
80
|
+
description: options.description,
|
|
81
|
+
publishedTime: options.publishedTime,
|
|
82
|
+
modifiedTime: options.modifiedTime,
|
|
83
|
+
section: options.section,
|
|
84
|
+
tags: [
|
|
85
|
+
"TypeScript",
|
|
86
|
+
"API",
|
|
87
|
+
options.packageName
|
|
88
|
+
],
|
|
89
|
+
...options.ogImage != null ? { ogImage: options.ogImage } : {},
|
|
90
|
+
ogType: "article"
|
|
91
|
+
};
|
|
92
|
+
}
|
|
303
93
|
|
|
304
94
|
//#endregion
|
|
305
|
-
export {
|
|
95
|
+
export { createPageMetadata, deriveSiteUrl, imageMimeType, ogAltText, resolveOgUrl };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rspress-plugin-api-extractor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "RSPress plugin for generating API documentation from TypeScript API Extractor models",
|
|
6
6
|
"keywords": [
|
|
@@ -53,16 +53,15 @@
|
|
|
53
53
|
"@microsoft/api-extractor-model": "^7.33.11",
|
|
54
54
|
"@shikijs/twoslash": "^4.4.3",
|
|
55
55
|
"@tsdoctor/bundle": "0.2.0",
|
|
56
|
-
"@tsdoctor/model": "0.
|
|
56
|
+
"@tsdoctor/model": "0.3.0",
|
|
57
57
|
"@tsdoctor/registry": "0.2.1",
|
|
58
|
-
"@tsdoctor/snapshot": "0.
|
|
58
|
+
"@tsdoctor/snapshot": "0.2.0",
|
|
59
59
|
"@typescript/vfs": "^1.6.4",
|
|
60
60
|
"clsx": "^2.1.1",
|
|
61
61
|
"effect": "4.0.0-rc.109",
|
|
62
62
|
"hast-util-to-jsx-runtime": "^2.3.6",
|
|
63
63
|
"image-size": "^2.0.2",
|
|
64
64
|
"ioredis": "^5.7.0",
|
|
65
|
-
"mdast-util-from-markdown": "^2.0.3",
|
|
66
65
|
"mdast-util-to-hast": "^13.2.1",
|
|
67
66
|
"open": "^11.0.0",
|
|
68
67
|
"prettier": "^3.9.6",
|
package/path-derivation.js
CHANGED
|
@@ -43,6 +43,24 @@ function deriveOutputPaths(input) {
|
|
|
43
43
|
}
|
|
44
44
|
return results;
|
|
45
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* The API scope key derived from a base route.
|
|
48
|
+
*
|
|
49
|
+
* @remarks
|
|
50
|
+
* Load-bearing and previously duplicated. Config resolution registers each
|
|
51
|
+
* API's Twoslash environment under this key and the build program looks it up
|
|
52
|
+
* by the same key; if the two derivations disagree, every lookup misses and
|
|
53
|
+
* `getTransformer` falls back to the build-wide environment. Per-scope
|
|
54
|
+
* type-checking degrades to build-wide with no error and nothing visibly
|
|
55
|
+
* wrong in the output — the failure mode is silent, which is why one
|
|
56
|
+
* definition matters more here than the duplication was costing.
|
|
57
|
+
*
|
|
58
|
+
* Falls back to the package name so a single-API site mounted at `/` still
|
|
59
|
+
* gets a non-empty scope.
|
|
60
|
+
*/
|
|
61
|
+
function apiScopeOf(baseRoute, packageName) {
|
|
62
|
+
return baseRoute.replace(/^\//, "").split("/")[0] || packageName;
|
|
63
|
+
}
|
|
46
64
|
|
|
47
65
|
//#endregion
|
|
48
|
-
export { deriveOutputPaths, normalizeBaseRoute, unscopedName };
|
|
66
|
+
export { apiScopeOf, deriveOutputPaths, normalizeBaseRoute, unscopedName };
|