rspress-plugin-api-extractor 0.10.0 → 0.11.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 +33 -31
- package/build-stages.js +47 -39
- package/errors.js +0 -1
- package/layers/ConfigServiceLive.js +339 -433
- package/layers/HighlighterServiceLive.js +52 -0
- package/layers/OgServiceLive.js +134 -0
- package/layers/TwoslashCacheServiceLive.js +74 -19
- package/layers/TwoslashEnvironmentsLive.js +33 -0
- package/layers/TypeRegistryServiceLive.js +54 -47
- 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/observability/EventBus.js +29 -7
- package/observability/spans.js +3 -1
- package/observability/sync-emitter.js +78 -0
- package/og-resolver.js +46 -287
- package/package.json +2 -3
- package/path-derivation.js +19 -1
- package/plugin.js +48 -73
- package/prettier-formatter.js +4 -10
- package/remark-api-codeblocks.js +10 -18
- package/remark-with-api.js +11 -21
- package/services/HighlighterService.js +30 -0
- package/services/OgService.js +23 -0
- package/services/PluginConfig.js +26 -0
- package/services/TwoslashEnvironments.js +7 -0
- package/shiki-transformer.js +53 -234
- package/twoslash-access.js +48 -0
- package/twoslash-transformer.js +106 -83
- package/vfs-registry.js +1 -31
- package/layers/PathDerivationServiceLive.js +0 -16
- package/services/PathDerivationService.js +0 -7
package/twoslash-transformer.js
CHANGED
|
@@ -1,26 +1,14 @@
|
|
|
1
1
|
import { PluginEvent } from "./observability/events.js";
|
|
2
|
+
import { emitSync, syncBuildId } from "./observability/sync-emitter.js";
|
|
2
3
|
import { DEFAULT_COMPILER_OPTIONS } from "./typescript-config.js";
|
|
4
|
+
import { Result } from "effect";
|
|
5
|
+
import { Markdown, Mdast } from "@effected/markdown";
|
|
6
|
+
import { TsEnumCodec } from "@effected/tsconfig-json";
|
|
3
7
|
import { rendererRich, transformerTwoslash } from "@shikijs/twoslash";
|
|
4
|
-
import { fromMarkdown } from "mdast-util-from-markdown";
|
|
5
8
|
import { toHast } from "mdast-util-to-hast";
|
|
6
9
|
|
|
7
10
|
//#region src/twoslash-transformer.ts
|
|
8
|
-
|
|
9
|
-
* Module-level emitter seam. Default is a no-op; wire in `setEventEmitter(emitSync)`
|
|
10
|
-
* from plugin.ts right after the runtime emitter is created so that Twoslash error
|
|
11
|
-
* events flow through the EventBus even though they fire in a sync Shiki callback
|
|
12
|
-
* outside any Effect fiber.
|
|
13
|
-
*/
|
|
14
|
-
let emitEvent = () => {};
|
|
15
|
-
let currentBuildId = "";
|
|
16
|
-
/**
|
|
17
|
-
* Inject the runtime-bound emitter into the Twoslash module.
|
|
18
|
-
* Call this right after `makeRuntimeEmitter` in plugin.ts.
|
|
19
|
-
*/
|
|
20
|
-
function setEventEmitter(fn, buildId = "") {
|
|
21
|
-
emitEvent = fn;
|
|
22
|
-
currentBuildId = buildId;
|
|
23
|
-
}
|
|
11
|
+
/* v8 ignore start -- Shiki/Twoslash integration, requires full highlighter setup for testing */
|
|
24
12
|
/**
|
|
25
13
|
* Module-level type routes map for resolving link references.
|
|
26
14
|
* This is set by TwoslashManager.setTypeRoutes() before initialization.
|
|
@@ -115,8 +103,10 @@ function addLinkClasses(node) {
|
|
|
115
103
|
* Render markdown content to HAST (Hypertext Abstract Syntax Tree) elements.
|
|
116
104
|
*
|
|
117
105
|
* This function converts markdown strings (from TSDoc comments) into HAST nodes
|
|
118
|
-
* that can be rendered in Twoslash hover popups. It
|
|
119
|
-
*
|
|
106
|
+
* that can be rendered in Twoslash hover popups. It parses with
|
|
107
|
+
* `@effected/markdown` (CommonMark dialect) and converts to HAST with
|
|
108
|
+
* `mdast-util-to-hast`, which stays because markdown-to-HTML is permanently
|
|
109
|
+
* out of scope for the kit.
|
|
120
110
|
*
|
|
121
111
|
* TSDoc link references are transformed to markdown links before parsing.
|
|
122
112
|
* Whitespace is normalized for proper inline display.
|
|
@@ -130,8 +120,12 @@ function renderMarkdown(markdown) {
|
|
|
130
120
|
try {
|
|
131
121
|
let transformed = transformTsDocLinks(markdown);
|
|
132
122
|
transformed = transformed.split(/\n\n+/).map((para) => para.replace(/\s+/g, " ").trim()).filter((para) => para.length > 0).join("\n\n");
|
|
133
|
-
const
|
|
134
|
-
|
|
123
|
+
const parsed = Markdown.parseResult(transformed, { dialect: "commonmark" });
|
|
124
|
+
if (Result.isFailure(parsed)) return [{
|
|
125
|
+
type: "text",
|
|
126
|
+
value: markdown
|
|
127
|
+
}];
|
|
128
|
+
const hast = toHast(Mdast.toMdast(parsed.success));
|
|
135
129
|
if (hast && "children" in hast) {
|
|
136
130
|
const children = hast.children;
|
|
137
131
|
for (const child of children) addLinkClasses(child);
|
|
@@ -232,12 +226,63 @@ function renderMarkdownInline(markdown, context) {
|
|
|
232
226
|
* blocks routed to the right one. Keys are sorted, so two configurations that
|
|
233
227
|
* differ only in property order share an environment.
|
|
234
228
|
*/
|
|
229
|
+
/**
|
|
230
|
+
* Convert resolved compiler options from the tsconfig JSON spelling to the
|
|
231
|
+
* programmatic one a real compiler expects.
|
|
232
|
+
*
|
|
233
|
+
* @remarks
|
|
234
|
+
* Two spellings meet here, and only here. `tsconfig.json` writes
|
|
235
|
+
* `lib: ["ESNext", "DOM"]` and `target: "esnext"`; `ts.CompilerOptions` wants
|
|
236
|
+
* lib FILE NAMES (`lib.esnext.d.ts`) and numeric enums. `DEFAULT_COMPILER_OPTIONS`
|
|
237
|
+
* is authored in the tsconfig spelling, and a tsconfig discovered from disk
|
|
238
|
+
* arrives already converted by `ts.parseJsonConfigFileContent`, so both forms
|
|
239
|
+
* reach this function — which is why the conversion must be idempotent rather
|
|
240
|
+
* than one-directional.
|
|
241
|
+
*
|
|
242
|
+
* Exported for the four-path regression test: no runtime path in this repo
|
|
243
|
+
* reaches the broken spelling (an unscoped block inherits the first registered
|
|
244
|
+
* environment, not the raw default), so a synthetic test compiling each
|
|
245
|
+
* resolution path through the real compiler is the only verification there is.
|
|
246
|
+
*/
|
|
247
|
+
function toProgrammaticCompilerOptions(options) {
|
|
248
|
+
return TsEnumCodec.encodeCompilerOptions(options);
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Fingerprint a compiler configuration, for keying the environment map.
|
|
252
|
+
*
|
|
253
|
+
* @remarks
|
|
254
|
+
* INVARIANT: every call site must pass options that have already been through
|
|
255
|
+
* {@link toProgrammaticCompilerOptions}. There are two — `initialize`, which
|
|
256
|
+
* stores an environment under this key, and `registerScope`, which looks one
|
|
257
|
+
* up by it. Both must encode, and encode the same way.
|
|
258
|
+
*
|
|
259
|
+
* The failure mode is SILENT. Encode at one site and not the other and the
|
|
260
|
+
* keys stop matching, so `getTransformer(scope)` finds nothing and falls back
|
|
261
|
+
* to the default environment: per-scope type-checking quietly degrades to
|
|
262
|
+
* build-wide, no error is raised, and nothing in the output looks wrong.
|
|
263
|
+
*
|
|
264
|
+
* This is not hypothetical. Task 1.2 moved the `initialize` fingerprint behind
|
|
265
|
+
* the encoder as a step specified — and reviewed — as a no-op, left
|
|
266
|
+
* `registerScope` on the raw options, and the full 994-test suite stayed green
|
|
267
|
+
* over the defect. The mutation that should have caught it (fingerprinting the
|
|
268
|
+
* pre-encoded value) also survived that suite. What caught it was a test
|
|
269
|
+
* written specifically for the hazard, which then failed for this second,
|
|
270
|
+
* unanticipated reason. `__test__/twoslash-transformer.test.ts` now pins both
|
|
271
|
+
* halves; keep that test whenever this code moves.
|
|
272
|
+
*/
|
|
235
273
|
function twoslashConfigKey(options) {
|
|
236
274
|
const entries = Object.entries(options).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
237
275
|
return JSON.stringify(entries);
|
|
238
276
|
}
|
|
239
|
-
|
|
240
|
-
|
|
277
|
+
/**
|
|
278
|
+
* The mutable registry behind {@link TwoslashEnvironments}.
|
|
279
|
+
*
|
|
280
|
+
* @remarks
|
|
281
|
+
* A plain class the Layer constructs, not a singleton. `getInstance()` and the
|
|
282
|
+
* static `reset()` that stood in for layer substitution are gone: a test that
|
|
283
|
+
* wants a different environment set provides a different layer.
|
|
284
|
+
*/
|
|
285
|
+
var TwoslashEnvironmentRegistry = class {
|
|
241
286
|
/**
|
|
242
287
|
* Transformers keyed by compiler-config fingerprint.
|
|
243
288
|
*
|
|
@@ -265,7 +310,7 @@ var TwoslashManager = class TwoslashManager {
|
|
|
265
310
|
* Resolved compiler options captured at initialize() time.
|
|
266
311
|
* Returned by compilerOptionsSnapshot() for TwoslashCheckFailed events.
|
|
267
312
|
*/
|
|
268
|
-
_resolvedCompilerOptions = DEFAULT_COMPILER_OPTIONS;
|
|
313
|
+
_resolvedCompilerOptions = toProgrammaticCompilerOptions(DEFAULT_COMPILER_OPTIONS);
|
|
269
314
|
/**
|
|
270
315
|
* Path of the file whose code block is currently being processed.
|
|
271
316
|
* Used to attribute Twoslash error events to a source file. Defaults to
|
|
@@ -274,31 +319,20 @@ var TwoslashManager = class TwoslashManager {
|
|
|
274
319
|
*/
|
|
275
320
|
currentFilePath = "unknown";
|
|
276
321
|
/**
|
|
277
|
-
*
|
|
278
|
-
*/
|
|
279
|
-
constructor() {}
|
|
280
|
-
/**
|
|
281
|
-
* Get the singleton instance of TwoslashManager
|
|
282
|
-
*/
|
|
283
|
-
static getInstance() {
|
|
284
|
-
if (!TwoslashManager.instance) TwoslashManager.instance = new TwoslashManager();
|
|
285
|
-
return TwoslashManager.instance;
|
|
286
|
-
}
|
|
287
|
-
/**
|
|
288
|
-
* Initialize the Twoslash transformer with a TypeScript environment cache.
|
|
289
|
-
* This enables type-aware documentation with hover information and IntelliSense.
|
|
322
|
+
* Build an environment for a configuration, or return if one exists.
|
|
290
323
|
*
|
|
291
|
-
* @
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
*
|
|
324
|
+
* @remarks
|
|
325
|
+
* The signature this replaces took six positional parameters, two named
|
|
326
|
+
* `_reserved`/`_reserved2` and one (`tsEnvCache`) that every call site
|
|
327
|
+
* passed as `undefined`. Dropping `tsEnvCache` changes nothing at runtime
|
|
328
|
+
* for exactly that reason — Twoslash was never handed a shared environment
|
|
329
|
+
* cache.
|
|
296
330
|
*/
|
|
297
|
-
|
|
331
|
+
registerEnvironment({ vfs, compilerOptions, typesCache }) {
|
|
298
332
|
const extraFiles = {};
|
|
299
333
|
for (const [path, content] of vfs.entries()) extraFiles[path] = content;
|
|
300
334
|
this._vfsKeys = Array.from(vfs.keys());
|
|
301
|
-
const resolvedOptions = compilerOptions ?? DEFAULT_COMPILER_OPTIONS;
|
|
335
|
+
const resolvedOptions = toProgrammaticCompilerOptions(compilerOptions ?? DEFAULT_COMPILER_OPTIONS);
|
|
302
336
|
this._resolvedCompilerOptions = resolvedOptions;
|
|
303
337
|
const configKey = twoslashConfigKey(resolvedOptions);
|
|
304
338
|
if (this.defaultConfigKey === null) this.defaultConfigKey = configKey;
|
|
@@ -312,7 +346,6 @@ var TwoslashManager = class TwoslashManager {
|
|
|
312
346
|
renderMarkdown,
|
|
313
347
|
renderMarkdownInline
|
|
314
348
|
}),
|
|
315
|
-
...tsEnvCache != null ? { cache: tsEnvCache } : {},
|
|
316
349
|
...typesCache != null ? { typesCache } : {},
|
|
317
350
|
twoslashOptions: {
|
|
318
351
|
extraFiles,
|
|
@@ -332,7 +365,7 @@ var TwoslashManager = class TwoslashManager {
|
|
|
332
365
|
* under, so its code blocks are type-checked with that configuration.
|
|
333
366
|
*/
|
|
334
367
|
registerScope(apiScope, compilerOptions) {
|
|
335
|
-
this.scopeConfigs.set(apiScope, twoslashConfigKey(compilerOptions));
|
|
368
|
+
this.scopeConfigs.set(apiScope, twoslashConfigKey(toProgrammaticCompilerOptions(compilerOptions)));
|
|
336
369
|
}
|
|
337
370
|
/**
|
|
338
371
|
* Get the Twoslash transformer for an API scope.
|
|
@@ -342,7 +375,7 @@ var TwoslashManager = class TwoslashManager {
|
|
|
342
375
|
* route, and type-checking it under some configuration beats not checking it.
|
|
343
376
|
* Returns null before any environment is initialized.
|
|
344
377
|
*/
|
|
345
|
-
|
|
378
|
+
transformerFor(apiScope) {
|
|
346
379
|
const key = (apiScope != null ? this.scopeConfigs.get(apiScope) : void 0) ?? this.defaultConfigKey;
|
|
347
380
|
return key != null ? this.environments.get(key) ?? null : null;
|
|
348
381
|
}
|
|
@@ -363,12 +396,6 @@ var TwoslashManager = class TwoslashManager {
|
|
|
363
396
|
this.scopeConfigs.clear();
|
|
364
397
|
this.defaultConfigKey = null;
|
|
365
398
|
}
|
|
366
|
-
/**
|
|
367
|
-
* Reset the singleton instance (useful for testing)
|
|
368
|
-
*/
|
|
369
|
-
static reset() {
|
|
370
|
-
TwoslashManager.instance = null;
|
|
371
|
-
}
|
|
372
399
|
/** Returns VFS keys snapshotted at initialize() time. Empty array before initialize(). */
|
|
373
400
|
vfsKeysSnapshot() {
|
|
374
401
|
return this._vfsKeys;
|
|
@@ -381,9 +408,9 @@ var TwoslashManager = class TwoslashManager {
|
|
|
381
408
|
const message = error instanceof Error ? error.message : String(error);
|
|
382
409
|
const match = /TS(\d+)/.exec(message);
|
|
383
410
|
const tsCode = match ? Number(match[1]) : 0;
|
|
384
|
-
|
|
411
|
+
emitSync(PluginEvent.TwoslashDiagnostic({
|
|
385
412
|
ctx: {
|
|
386
|
-
buildId:
|
|
413
|
+
buildId: syncBuildId(),
|
|
387
414
|
file
|
|
388
415
|
},
|
|
389
416
|
level: "warn",
|
|
@@ -394,9 +421,9 @@ var TwoslashManager = class TwoslashManager {
|
|
|
394
421
|
message,
|
|
395
422
|
snippet: ""
|
|
396
423
|
}));
|
|
397
|
-
|
|
424
|
+
emitSync(PluginEvent.TwoslashCheckFailed({
|
|
398
425
|
ctx: {
|
|
399
|
-
buildId:
|
|
426
|
+
buildId: syncBuildId(),
|
|
400
427
|
file
|
|
401
428
|
},
|
|
402
429
|
level: "trace",
|
|
@@ -410,34 +437,30 @@ var TwoslashManager = class TwoslashManager {
|
|
|
410
437
|
* Test seam: drive `handleTwoslashError` directly without going through the Shiki transformer.
|
|
411
438
|
* @internal
|
|
412
439
|
*/
|
|
413
|
-
|
|
440
|
+
reportErrorForTest(error, code, file) {
|
|
414
441
|
this.handleTwoslashError(error, code, file);
|
|
415
442
|
}
|
|
416
|
-
/**
|
|
417
|
-
* Set the type routes map for resolving link references in hover docs.
|
|
418
|
-
* This should be called before initialize() to enable type linking.
|
|
419
|
-
*
|
|
420
|
-
* @param routes - Map of type names to their documentation URLs
|
|
421
|
-
*/
|
|
422
|
-
static setTypeRoutes(routes) {
|
|
423
|
-
typeRoutes = routes;
|
|
424
|
-
}
|
|
425
|
-
/**
|
|
426
|
-
* Add routes to the existing type routes map.
|
|
427
|
-
* Useful for adding routes from multiple packages.
|
|
428
|
-
*
|
|
429
|
-
* @param routes - Map of type names to their documentation URLs
|
|
430
|
-
*/
|
|
431
|
-
static addTypeRoutes(routes) {
|
|
432
|
-
for (const [name, route] of routes) typeRoutes.set(name, route);
|
|
433
|
-
}
|
|
434
|
-
/**
|
|
435
|
-
* Clear the type routes map (useful for testing)
|
|
436
|
-
*/
|
|
437
|
-
static clearTypeRoutes() {
|
|
438
|
-
typeRoutes.clear();
|
|
439
|
-
}
|
|
440
443
|
};
|
|
444
|
+
/** Merge routes in, so a multi-API build accumulates every scope's names. */
|
|
445
|
+
function addTypeRoutes(routes) {
|
|
446
|
+
for (const [name, route] of routes) typeRoutes.set(name, route);
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Clear the accumulated type routes.
|
|
450
|
+
*
|
|
451
|
+
* @remarks
|
|
452
|
+
* Called from `config()` at the start of every build. `addTypeRoutes` only
|
|
453
|
+
* ever adds, so without this a dev session keeps routes for items that have
|
|
454
|
+
* since been renamed or removed, and a multi-API build leaks every scope's
|
|
455
|
+
* names into one map.
|
|
456
|
+
*
|
|
457
|
+
* Clears ONLY the routes. The environments are per-build too, but they are
|
|
458
|
+
* owned by the layer now, so nothing here can discard the per-scope
|
|
459
|
+
* transformers `ConfigServiceLive` just built.
|
|
460
|
+
*/
|
|
461
|
+
function clearTypeRoutes() {
|
|
462
|
+
typeRoutes.clear();
|
|
463
|
+
}
|
|
441
464
|
|
|
442
465
|
//#endregion
|
|
443
|
-
export {
|
|
466
|
+
export { TwoslashEnvironmentRegistry, addTypeRoutes, clearTypeRoutes, toProgrammaticCompilerOptions };
|
package/vfs-registry.js
CHANGED
|
@@ -34,35 +34,6 @@ var VfsRegistryImpl = class {
|
|
|
34
34
|
return this.configs.get(apiScope);
|
|
35
35
|
}
|
|
36
36
|
/**
|
|
37
|
-
* Get the VFS configuration by matching a file path to an API scope.
|
|
38
|
-
*
|
|
39
|
-
* This method extracts the API scope from a file path and returns
|
|
40
|
-
* the corresponding VFS configuration.
|
|
41
|
-
*
|
|
42
|
-
* @param filePath - The absolute file path being processed
|
|
43
|
-
* @returns The VFS configuration, or undefined if not found
|
|
44
|
-
*/
|
|
45
|
-
getByFilePath(filePath) {
|
|
46
|
-
const apiScope = this.extractApiScope(filePath);
|
|
47
|
-
if (!apiScope) return;
|
|
48
|
-
return this.get(apiScope);
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Extract the API scope from a file path.
|
|
52
|
-
*
|
|
53
|
-
* Path patterns:
|
|
54
|
-
* - `docs/en/api-scope/rest.mdx`
|
|
55
|
-
* - `website/docs/en/api-scope/rest.mdx`
|
|
56
|
-
*
|
|
57
|
-
* @param filePath - The file path to extract from
|
|
58
|
-
* @returns The API scope, or undefined if not matched
|
|
59
|
-
*/
|
|
60
|
-
extractApiScope(filePath) {
|
|
61
|
-
const match = filePath.replace(/\\/g, "/").match(/(?:^|\/)(docs\/en|website\/docs\/en)\/([^/]+)(?:\/|$)/);
|
|
62
|
-
if (!match) return;
|
|
63
|
-
return match[2];
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
37
|
* Check if any VFS configurations are registered.
|
|
67
38
|
*
|
|
68
39
|
* @returns True if at least one configuration is registered
|
|
@@ -96,7 +67,6 @@ var VfsRegistryImpl = class {
|
|
|
96
67
|
* ```ts
|
|
97
68
|
* // In beforeBuild hook:
|
|
98
69
|
* VfsRegistry.register("claude-binary-plugin", {
|
|
99
|
-
* vfs: combinedVfs,
|
|
100
70
|
* highlighter,
|
|
101
71
|
* twoslashTransformer,
|
|
102
72
|
* crossLinker: shikiCrossLinker,
|
|
@@ -105,7 +75,7 @@ var VfsRegistryImpl = class {
|
|
|
105
75
|
* });
|
|
106
76
|
*
|
|
107
77
|
* // In remark plugin:
|
|
108
|
-
* const config = VfsRegistry.
|
|
78
|
+
* const config = VfsRegistry.get(apiScope);
|
|
109
79
|
* if (config) {
|
|
110
80
|
* // Generate HAST with Shiki, then post-process with cross-linker
|
|
111
81
|
* let hast = await generateShikiHast(code, config.highlighter, transformers);
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { deriveOutputPaths, normalizeBaseRoute } from "../path-derivation.js";
|
|
2
|
-
import { PathDerivationService } from "../services/PathDerivationService.js";
|
|
3
|
-
import { Effect, Layer } from "effect";
|
|
4
|
-
|
|
5
|
-
//#region src/layers/PathDerivationServiceLive.ts
|
|
6
|
-
const PathDerivationServiceLive = Layer.succeed(PathDerivationService, {
|
|
7
|
-
derivePaths: (input) => Effect.succeed(deriveOutputPaths({
|
|
8
|
-
...input,
|
|
9
|
-
locales: [...input.locales],
|
|
10
|
-
versions: [...input.versions]
|
|
11
|
-
})),
|
|
12
|
-
normalizeBaseRoute: (route) => Effect.succeed(normalizeBaseRoute(route))
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
//#endregion
|
|
16
|
-
export { PathDerivationServiceLive };
|