rspress-plugin-api-extractor 0.13.3 → 0.15.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.
Files changed (44) hide show
  1. package/BuildEnv.js +0 -1
  2. package/build-program.js +5 -5
  3. package/build-stages.js +98 -282
  4. package/config-helpers.js +1 -1
  5. package/emit/mdx.js +311 -0
  6. package/emit/meta.js +62 -0
  7. package/index.d.ts +6 -70
  8. package/layers/build-metrics.js +1 -2
  9. package/layers/config-resolution.js +5 -8
  10. package/layers/type-environment.js +5 -4
  11. package/llms-program.js +3 -3
  12. package/markdown/helpers.js +10 -177
  13. package/observability/sinks/console-sink.js +1 -3
  14. package/observability/sinks/metrics-sink.js +0 -2
  15. package/package.json +9 -7
  16. package/path-derivation.js +1 -29
  17. package/plugin.js +3 -10
  18. package/prettier-formatter.js +27 -59
  19. package/remark-with-api.js +3 -2
  20. package/schemas/config.js +3 -29
  21. package/schemas/observability.js +8 -23
  22. package/schemas/performance.js +1 -7
  23. package/services/TwoslashCacheService.js +18 -14
  24. package/services/TypeRegistryService.js +4 -4
  25. package/shiki-transformer.js +12 -51
  26. package/twoslash-transformer.js +16 -49
  27. package/api-extracted-package.js +0 -471
  28. package/code-post-processor.js +0 -38
  29. package/frontmatter.js +0 -176
  30. package/llms-processing.js +0 -270
  31. package/markdown/page-generators/class-page.js +0 -364
  32. package/markdown/page-generators/enum-page.js +0 -152
  33. package/markdown/page-generators/function-page.js +0 -128
  34. package/markdown/page-generators/index-pages.js +0 -25
  35. package/markdown/page-generators/interface-page.js +0 -311
  36. package/markdown/page-generators/namespace-page.js +0 -278
  37. package/markdown/page-generators/type-alias-page.js +0 -111
  38. package/markdown/page-generators/variable-page.js +0 -111
  39. package/markdown/prose-linker.js +0 -22
  40. package/tsconfig-parser.js +0 -115
  41. package/twoslash-cache.js +0 -174
  42. package/twoslash-patterns.js +0 -87
  43. package/type-reference-extractor.js +0 -199
  44. package/typescript-config.js +0 -170
package/schemas/config.js CHANGED
@@ -1,4 +1,3 @@
1
- import { PerformanceConfig } from "./performance.js";
2
1
  import { ObservabilityConfig } from "./observability.js";
3
2
  import { Effect, Schema } from "effect";
4
3
  import { OpenGraphImageConfig } from "@tsdoctor/seo";
@@ -10,24 +9,9 @@ import { ApiItemKind } from "@microsoft/api-extractor-model";
10
9
  * an async loader function, or a URL.
11
10
  */
12
11
  const ModelInput = Schema.declare((input) => typeof input === "string" || typeof input === "function" || input instanceof URL);
13
- /**
14
- * Verbosity level for plugin build output.
15
- *
16
- * @public
17
- */
18
- const LogLevel = Schema.Literals([
19
- "none",
20
- "info",
21
- "verbose",
22
- "debug",
23
- "warn",
24
- "error"
25
- ]);
26
12
  const ExternalPackageSpec = Schema.Struct({
27
13
  name: Schema.String,
28
- version: Schema.String,
29
- tsconfig: Schema.optional(ModelInput),
30
- compilerOptions: Schema.optional(Schema.Unknown)
14
+ version: Schema.String
31
15
  });
32
16
  const AutoDetectDependencies = Schema.Struct({
33
17
  dependencies: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))),
@@ -68,8 +52,6 @@ const CategoryConfig = Schema.Struct({
68
52
  folderName: Schema.String,
69
53
  /** API item kinds included in this category. */
70
54
  itemKinds: Schema.optional(Schema.mutable(Schema.Array(ApiItemKindSchema))),
71
- /** TSDoc modifier tag that marks items for this category. */
72
- tsdocModifier: Schema.optional(Schema.String),
73
55
  /** Whether the sidebar section is collapsible. Defaults to `true`. */
74
56
  collapsible: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))),
75
57
  /** Whether the sidebar section starts collapsed. Defaults to `true`. */
@@ -190,11 +172,7 @@ const VersionConfig = Schema.Struct({
190
172
  /** Open Graph image configuration for this version. */
191
173
  ogImage: Schema.optional(OpenGraphImageConfig),
192
174
  /** LLMs integration options for this version. */
193
- llmsPlugin: Schema.optional(LlmsPlugin),
194
- /** Path to a `tsconfig.json` for this version. */
195
- tsconfig: Schema.optional(ModelInput),
196
- /** TypeScript compiler options for Twoslash. */
197
- compilerOptions: Schema.optional(Schema.Unknown)
175
+ llmsPlugin: Schema.optional(LlmsPlugin)
198
176
  });
199
177
  /** Union for the versions record value: can be a path/function OR a full VersionConfig */
200
178
  const VersionValue = Schema.Union([ModelInput, VersionConfig]);
@@ -312,13 +290,9 @@ const PluginOptions = Schema.Struct({
312
290
  errors: Schema.optional(ErrorConfig),
313
291
  /** LLMs integration options, or `false` to disable. */
314
292
  llmsPlugin: Schema.optional(Schema.Union([Schema.Boolean, LlmsPlugin])),
315
- /** Verbosity level for plugin build output. @deprecated Use `observability.logLevel`. */
316
- logLevel: Schema.optional(LogLevel),
317
- /** Performance tuning options. @deprecated Use `observability.thresholds`. */
318
- performance: Schema.optional(PerformanceConfig),
319
293
  /** Unified observability configuration (logLevel, trace artifact, thresholds). */
320
294
  observability: Schema.optional(ObservabilityConfig)
321
295
  });
322
296
 
323
297
  //#endregion
324
- export { AutoDetectDependencies, CategoryConfig, DEFAULT_CATEGORIES, ErrorConfig, ExternalPackageSpec, LlmsPlugin, LogLevel, ModelInput, MultiApiConfig, PluginOptions, SingleApiConfig, SourceConfig, ThemeConfig, VersionConfig };
298
+ export { AutoDetectDependencies, CategoryConfig, DEFAULT_CATEGORIES, ErrorConfig, ExternalPackageSpec, LlmsPlugin, ModelInput, MultiApiConfig, PluginOptions, SingleApiConfig, SourceConfig, ThemeConfig, VersionConfig };
@@ -22,7 +22,6 @@ const DEFAULT_THRESHOLDS = {
22
22
  slowPageGeneration: 500,
23
23
  slowApiLoad: 1e3,
24
24
  slowFileOperation: 50,
25
- slowHttpRequest: 2e3,
26
25
  slowDbOperation: 100
27
26
  };
28
27
  function normalizeLevel(value) {
@@ -31,21 +30,11 @@ function normalizeLevel(value) {
31
30
  return value;
32
31
  }
33
32
  function resolveObservability(input) {
34
- const deprecations = [];
35
- if (input.logLevel !== void 0) deprecations.push({
36
- key: "logLevel",
37
- replacement: "observability.logLevel"
38
- });
39
- if (input.performance !== void 0) deprecations.push({
40
- key: "performance",
41
- replacement: "observability.thresholds"
42
- });
43
- const level = normalizeLevel(input.envLogLevel) ?? normalizeLevel(input.observability?.logLevel) ?? normalizeLevel(input.logLevel) ?? "info";
33
+ const level = normalizeLevel(input.envLogLevel) ?? normalizeLevel(input.observability?.logLevel) ?? "info";
44
34
  const traceOpt = input.observability?.trace;
45
35
  const tracePath = typeof traceOpt === "string" ? traceOpt : traceOpt === true ? `${input.cwd}/.api-docs/build/trace-${input.buildId}.jsonl` : null;
46
36
  const merged = {
47
37
  ...DEFAULT_THRESHOLDS,
48
- ...input.performance?.thresholds ?? {},
49
38
  ...input.observability?.thresholds ?? {}
50
39
  };
51
40
  const thresholds = {
@@ -53,22 +42,18 @@ function resolveObservability(input) {
53
42
  slowPageGeneration: merged.slowPageGeneration ?? DEFAULT_THRESHOLDS.slowPageGeneration,
54
43
  slowApiLoad: merged.slowApiLoad ?? DEFAULT_THRESHOLDS.slowApiLoad,
55
44
  slowFileOperation: merged.slowFileOperation ?? DEFAULT_THRESHOLDS.slowFileOperation,
56
- slowHttpRequest: merged.slowHttpRequest ?? DEFAULT_THRESHOLDS.slowHttpRequest,
57
45
  slowDbOperation: merged.slowDbOperation ?? DEFAULT_THRESHOLDS.slowDbOperation
58
46
  };
59
47
  const pi = input.observability?.progressInterval;
60
48
  const seconds = pi === false ? null : typeof pi === "number" ? pi : 10;
61
49
  const progressIntervalMs = seconds !== null && Number.isFinite(seconds) && seconds > 0 ? seconds * 1e3 : null;
62
- return {
63
- resolved: {
64
- logLevel: level,
65
- json: level === "debug",
66
- tracePath,
67
- progressIntervalMs,
68
- thresholds
69
- },
70
- deprecations
71
- };
50
+ return { resolved: {
51
+ logLevel: level,
52
+ json: level === "debug",
53
+ tracePath,
54
+ progressIntervalMs,
55
+ thresholds
56
+ } };
72
57
  }
73
58
 
74
59
  //#endregion
@@ -6,14 +6,8 @@ const PerformanceThresholds = Schema.Struct({
6
6
  slowPageGeneration: Schema.Number.pipe(Schema.withDecodingDefault(Effect.succeed(500))),
7
7
  slowApiLoad: Schema.Number.pipe(Schema.withDecodingDefault(Effect.succeed(1e3))),
8
8
  slowFileOperation: Schema.Number.pipe(Schema.withDecodingDefault(Effect.succeed(50))),
9
- slowHttpRequest: Schema.Number.pipe(Schema.withDecodingDefault(Effect.succeed(2e3))),
10
9
  slowDbOperation: Schema.Number.pipe(Schema.withDecodingDefault(Effect.succeed(100)))
11
10
  });
12
- const PerformanceConfig = Schema.Struct({
13
- thresholds: Schema.optional(PerformanceThresholds),
14
- showInsights: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))),
15
- trackDetailedMetrics: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false)))
16
- });
17
11
 
18
12
  //#endregion
19
- export { PerformanceConfig, PerformanceThresholds };
13
+ export { PerformanceThresholds };
@@ -1,6 +1,6 @@
1
1
  import { AppDirsLive, PlatformLive } from "../layers/xdg.js";
2
- import { decodeTwoslashCache, encodeTwoslashCache, makeTwoslashCache, twoslashBlobKey } from "../twoslash-cache.js";
3
2
  import { Context, Effect, Layer, Option, Path } from "effect";
3
+ import { decodeTwoslashCache, encodeTwoslashCache, makeTwoslashCache, twoslashBlobKey } from "@tsdoctor/vfs";
4
4
  import { Cache } from "@effected/store";
5
5
  import { AppDirs } from "@effected/xdg";
6
6
 
@@ -20,23 +20,29 @@ var TwoslashCacheService = class TwoslashCacheService extends Context.Service()(
20
20
  * @remarks
21
21
  * Failure is absorbed at TWO levels, and both are load-bearing. Inside the
22
22
  * service, a failed read or write degrades that one operation. Around the
23
- * layer, a failed CONSTRUCTION — no HOME for XDG, an unwritable cache
24
- * directory, a corrupt database — degrades to {@link DegradedLive}.
23
+ * cache layer, a failed CONSTRUCTION — no HOME for XDG, an unwritable cache
24
+ * directory, a corrupt database — degrades to a cache that always misses,
25
+ * via `Cache.degrading` (see {@link CacheLive}).
25
26
  *
26
- * The second is why `Layer.catchCause` wraps this at all. While the sqlite
27
- * layer was provided inside each method, a construction failure surfaced as
28
- * that method's failure and the in-method handler swallowed it. Hoisting
27
+ * The second is why anything wraps this at all. While the sqlite layer was
28
+ * provided inside each method, a construction failure surfaced as that
29
+ * method's failure and the in-method handler swallowed it. Hoisting
29
30
  * acquisition to layer construction moved the failure to `ManagedRuntime`
30
31
  * build time, where it would abort the entire build — breaking the contract
31
32
  * this service documents, that an unreachable cache must never fail a build
32
- * that would otherwise succeed. `catchCause` rather than a failure-only catch
33
- * because a defect thrown by the sqlite driver must degrade too.
33
+ * that would otherwise succeed.
34
+ *
35
+ * There is no separate degraded implementation of this service any more.
36
+ * Degrading one level down, at the `Cache`, means the ordinary
37
+ * implementation running over an always-missing cache IS the degraded
38
+ * behaviour, so a second implementation would only be a way for the two to
39
+ * disagree.
34
40
  *
35
41
  * `Layer.suspend` because the composition below is declared after this class:
36
42
  * a static initializer runs while the module body is still evaluating, so
37
43
  * naming those consts directly throws at import time with a clean typecheck.
38
44
  */
39
- static layer = Layer.suspend(() => CacheBackedLive.pipe(Layer.catchCause(() => DegradedLive)));
45
+ static layer = Layer.suspend(() => CacheBackedLive);
40
46
  /**
41
47
  * An always-cold in-memory double.
42
48
  *
@@ -50,6 +56,7 @@ var TwoslashCacheService = class TwoslashCacheService extends Context.Service()(
50
56
  * the render path's SHAPE rather than merely dropping its persistence.
51
57
  */
52
58
  static makeTest = (overrides = {}) => ({
59
+ degraded: overrides.degraded ?? false,
53
60
  load: overrides.load ?? (() => Effect.succeed(/* @__PURE__ */ new Map())),
54
61
  save: overrides.save ?? (() => Effect.void),
55
62
  open: overrides.open ?? (() => Effect.succeed(makeTwoslashCache())),
@@ -117,6 +124,7 @@ function withGeneration(base) {
117
124
  const CacheBackedLive = Layer.effect(TwoslashCacheService, Effect.gen(function* () {
118
125
  const cache = yield* Cache;
119
126
  return withGeneration({
127
+ degraded: cache.degraded,
120
128
  load: (envHash) => cache.get(twoslashBlobKey(envHash)).pipe(Effect.map((entry) => Option.isSome(entry) ? decodeTwoslashCache(entry.value.value) : /* @__PURE__ */ new Map()), Effect.catch(() => Effect.succeed(/* @__PURE__ */ new Map()))),
121
129
  save: (envHash, entries) => cache.set({
122
130
  key: twoslashBlobKey(envHash),
@@ -124,7 +132,7 @@ const CacheBackedLive = Layer.effect(TwoslashCacheService, Effect.gen(function*
124
132
  tags: ["twoslash"]
125
133
  }).pipe(Effect.catch(() => Effect.void))
126
134
  });
127
- })).pipe(Layer.provide(CacheLive));
135
+ })).pipe(Layer.provide(Cache.degrading(CacheLive)));
128
136
  /**
129
137
  * A cache that holds nothing, for when the real one cannot be opened.
130
138
  *
@@ -132,10 +140,6 @@ const CacheBackedLive = Layer.effect(TwoslashCacheService, Effect.gen(function*
132
140
  * `load` returns empty and `save` discards, which is precisely the behaviour
133
141
  * before this cache existed: type-check everything, persist nothing.
134
142
  */
135
- const DegradedLive = Layer.succeed(TwoslashCacheService, withGeneration({
136
- load: () => Effect.succeed(/* @__PURE__ */ new Map()),
137
- save: () => Effect.void
138
- }));
139
143
 
140
144
  //#endregion
141
145
  export { TwoslashCacheService };
@@ -3,11 +3,11 @@ import { emit } from "../observability/EventBus.js";
3
3
  import { resolveExternalPackageVersions } from "../config-utils.js";
4
4
  import { TypeRegistryError } from "../errors.js";
5
5
  import { AppDirsLive, PlatformLive } from "../layers/xdg.js";
6
- import { Context, Duration, Effect, Layer, Path } from "effect";
6
+ import { Cause, Context, Duration, Effect, Layer, Path } from "effect";
7
7
  import { NodeHttpClient } from "@effect/platform-node";
8
- import { PackageFetcher, PackageSpec, RegistryObserver, TypeCache, TypeRegistry } from "@tsdoctor/registry";
9
8
  import { Cache } from "@effected/store";
10
9
  import { AppDirs } from "@effected/xdg";
10
+ import { PackageFetcher, PackageSpec, RegistryObserver, TypeCache, TypeRegistry } from "@tsdoctor/registry";
11
11
 
12
12
  //#region src/services/TypeRegistryService.ts
13
13
  var TypeRegistryService = class TypeRegistryService extends Context.Service()("rspress-plugin-api-extractor/TypeRegistryService") {
@@ -27,7 +27,7 @@ var TypeRegistryService = class TypeRegistryService extends Context.Service()("r
27
27
  * static initializer runs while the module body is still evaluating, so naming
28
28
  * those consts directly throws at import time with a clean typecheck.
29
29
  */
30
- static layer = Layer.suspend(() => RegistryBackedLive.pipe(Layer.catchCause(() => DegradedLive)));
30
+ static layer = Layer.suspend(() => RegistryBackedLive.pipe(Layer.catchCause((cause) => Cause.hasInterrupts(cause) ? Layer.effectContext(Effect.failCause(Cause.interrupt([...Cause.interruptors(cause)][0]))) : DegradedLive)));
31
31
  /**
32
32
  * An in-memory double: no network, no XDG cache, no sqlite.
33
33
  *
@@ -132,7 +132,7 @@ const MetadataCacheLive = Layer.unwrap(Effect.gen(function* () {
132
132
  const path = yield* Path.Path;
133
133
  const cacheDir = yield* appDirs.ensureCache;
134
134
  return Cache.layerSqlite({ filename: path.join(cacheDir, "metadata.sqlite") });
135
- })).pipe(Layer.provide(Layer.mergeAll(AppDirsLive, PlatformLive)));
135
+ })).pipe(Layer.provide(Layer.mergeAll(AppDirsLive, PlatformLive)), Cache.degrading);
136
136
  /**
137
137
  * The full registry runtime: TypeRegistry over an XDG-rooted TypeCache and the
138
138
  * jsDelivr PackageFetcher, with the observer that forwards registry events to
@@ -33,7 +33,7 @@
33
33
  * @example Basic usage
34
34
  * ```ts
35
35
  * const crossLinker = new ShikiCrossLinker();
36
- * crossLinker.reinitialize(routes, kinds, "my-api");
36
+ * const crossLinker = ShikiCrossLinker.fromRoutes(routes, "my-api");
37
37
  * crossLinker.setApiScope("my-api");
38
38
  *
39
39
  * // Cross-link the finalized HAST, after Shiki and Twoslash have run
@@ -46,16 +46,13 @@
46
46
  var ShikiCrossLinker = class ShikiCrossLinker {
47
47
  /** API item name to route, for THIS scope. */
48
48
  apiItemRoutes;
49
- /** API item name to kind (Class, Interface, …), for THIS scope. */
50
- apiItemKinds;
51
49
  /** Parent name to its member names, longest first, for THIS scope. */
52
50
  classMembersMap;
53
51
  /** The scope this linker links for. Read-only after construction. */
54
52
  apiScope;
55
- constructor(apiScope, apiItemRoutes, apiItemKinds, classMembersMap) {
53
+ constructor(apiScope, apiItemRoutes, classMembersMap) {
56
54
  this.apiScope = apiScope;
57
55
  this.apiItemRoutes = apiItemRoutes;
58
- this.apiItemKinds = apiItemKinds;
59
56
  this.classMembersMap = classMembersMap;
60
57
  }
61
58
  /**
@@ -74,7 +71,7 @@ var ShikiCrossLinker = class ShikiCrossLinker {
74
71
  * property of the instance, which cannot be pointed at another package's
75
72
  * routes at all.
76
73
  */
77
- static fromRoutes(routes, kinds, apiScope) {
74
+ static fromRoutes(routes, apiScope) {
78
75
  const classMembersMap = /* @__PURE__ */ new Map();
79
76
  for (const [name] of routes.entries()) {
80
77
  const dotIndex = name.indexOf(".");
@@ -86,10 +83,10 @@ var ShikiCrossLinker = class ShikiCrossLinker {
86
83
  else if (!members.includes(memberName)) members.push(memberName);
87
84
  }
88
85
  for (const members of classMembersMap.values()) members.sort((a, b) => b.length - a.length);
89
- return new ShikiCrossLinker(apiScope, new Map(routes), new Map(kinds), classMembersMap);
86
+ return new ShikiCrossLinker(apiScope, new Map(routes), classMembersMap);
90
87
  }
91
88
  /** A linker that links nothing — for a scope with no documented routes. */
92
- static empty = new ShikiCrossLinker("", /* @__PURE__ */ new Map(), /* @__PURE__ */ new Map(), /* @__PURE__ */ new Map());
89
+ static empty = new ShikiCrossLinker("", /* @__PURE__ */ new Map(), /* @__PURE__ */ new Map());
93
90
  /**
94
91
  * Transform a finalized HAST tree to add cross-links to type references.
95
92
  *
@@ -118,7 +115,6 @@ var ShikiCrossLinker = class ShikiCrossLinker {
118
115
  }
119
116
  transformRoot(node) {
120
117
  const apiItemRoutes = this.apiItemRoutes;
121
- const apiItemKinds = this.apiItemKinds;
122
118
  const classMembersMap = this.classMembersMap;
123
119
  const scopeStack = [];
124
120
  const preElement = node.children.find((child) => child.type === "element" && child.tagName === "pre");
@@ -148,12 +144,9 @@ var ShikiCrossLinker = class ShikiCrossLinker {
148
144
  const fullMemberName = `${currentScope}.${content}`;
149
145
  const memberRoute = apiItemRoutes.get(fullMemberName);
150
146
  if (memberRoute) {
151
- const memberKind = apiItemKinds.get(fullMemberName);
152
- const memberSemanticClass = memberKind ? this.getSemanticClass(memberKind) : null;
153
147
  const leadingSpace = rawContent.match(/^\s*/)?.[0] || "";
154
148
  const trailingSpace = rawContent.match(/\s*$/)?.[0] || "";
155
149
  const classNames = ["api-type-link"];
156
- if (memberSemanticClass) classNames.push(memberSemanticClass);
157
150
  const newChildren = [];
158
151
  if (leadingSpace) newChildren.push({
159
152
  type: "text",
@@ -209,10 +202,7 @@ var ShikiCrossLinker = class ShikiCrossLinker {
209
202
  const fullMemberName = `${className}.${methodName}`;
210
203
  const memberRoute = apiItemRoutes.get(fullMemberName);
211
204
  if (!memberRoute) continue;
212
- const memberKind = apiItemKinds.get(fullMemberName);
213
- const memberSemanticClass = memberKind ? this.getSemanticClass(memberKind) : null;
214
205
  const memberClassNames = ["api-type-link"];
215
- if (memberSemanticClass) memberClassNames.push(memberSemanticClass);
216
206
  const textContent = this.extractTextFromTwoslash(twoslashSpan);
217
207
  if (!textContent) continue;
218
208
  this.wrapTwoslashTextInAnchor(twoslashSpan, textContent.trim(), memberRoute, memberClassNames);
@@ -230,11 +220,7 @@ var ShikiCrossLinker = class ShikiCrossLinker {
230
220
  const content = text.trim();
231
221
  const route = apiItemRoutes.get(content);
232
222
  if (!route) continue;
233
- const kind = apiItemKinds.get(content);
234
- const semanticClass = kind ? this.getSemanticClass(kind) : null;
235
- const classNames = ["api-type-link"];
236
- if (semanticClass) classNames.push(semanticClass);
237
- this.wrapTwoslashTextInAnchor(twoslashSpan, content, route, classNames);
223
+ this.wrapTwoslashTextInAnchor(twoslashSpan, content, route, ["api-type-link"]);
238
224
  twoslashSpan.properties = {
239
225
  ...twoslashSpan.properties,
240
226
  "data-api-processed": "true"
@@ -244,7 +230,7 @@ var ShikiCrossLinker = class ShikiCrossLinker {
244
230
  const typePattern = new RegExp(`\\b(${escapedNames.join("|")})\\b`, "g");
245
231
  for (const lineElement of codeElement.children) {
246
232
  if (lineElement.type !== "element" || lineElement.tagName !== "span") continue;
247
- this.linkTypeReferencesInLine(lineElement, typePattern, apiItemRoutes, apiItemKinds);
233
+ this.linkTypeReferencesInLine(lineElement, typePattern, apiItemRoutes);
248
234
  }
249
235
  }
250
236
  return node;
@@ -254,7 +240,6 @@ var ShikiCrossLinker = class ShikiCrossLinker {
254
240
  */
255
241
  transformLine(node) {
256
242
  const apiItemRoutes = this.apiItemRoutes;
257
- const apiItemKinds = this.apiItemKinds;
258
243
  const classMembersMap = this.classMembersMap;
259
244
  if (!node.children) return;
260
245
  for (let i = 0; i < node.children.length; i++) {
@@ -290,10 +275,7 @@ var ShikiCrossLinker = class ShikiCrossLinker {
290
275
  const fullMemberName = `${trimmedContent}.${matchedMember}`;
291
276
  const memberRoute = apiItemRoutes.get(fullMemberName);
292
277
  if (!memberRoute) continue;
293
- const memberKind = apiItemKinds.get(fullMemberName);
294
- const memberSemanticClass = memberKind ? this.getSemanticClass(memberKind) : null;
295
278
  const memberClassNames = ["api-type-link"];
296
- if (memberSemanticClass) memberClassNames.push(memberSemanticClass);
297
279
  if (methodSpan.properties?.class && String(methodSpan.properties.class).includes("twoslash-hover")) this.wrapTwoslashTextInAnchor(methodSpan, methodText.trim(), memberRoute, memberClassNames);
298
280
  else {
299
281
  const textNode = methodSpan.children.find((c) => c.type === "text");
@@ -341,7 +323,6 @@ var ShikiCrossLinker = class ShikiCrossLinker {
341
323
  */
342
324
  transformSpan(node, _line, _col) {
343
325
  const apiItemRoutes = this.apiItemRoutes;
344
- const apiItemKinds = this.apiItemKinds;
345
326
  if (node.properties?.["data-api-processed"] === "true") return;
346
327
  const firstChild = node.children?.[0];
347
328
  if (firstChild && firstChild.type === "element" && firstChild.tagName === "a") return;
@@ -352,11 +333,7 @@ var ShikiCrossLinker = class ShikiCrossLinker {
352
333
  if (!content) return;
353
334
  const route = apiItemRoutes.get(content);
354
335
  if (route) {
355
- const kind = apiItemKinds.get(content);
356
- const semanticClass = kind ? this.getSemanticClass(kind) : null;
357
- const classNames = ["api-type-link", "rp-link"];
358
- if (semanticClass) classNames.push(semanticClass);
359
- this.wrapTwoslashTextInAnchor(firstChild, content, route, classNames);
336
+ this.wrapTwoslashTextInAnchor(firstChild, content, route, ["api-type-link", "rp-link"]);
360
337
  node.properties = {
361
338
  ...node.properties,
362
339
  "data-api-processed": "true"
@@ -371,12 +348,9 @@ var ShikiCrossLinker = class ShikiCrossLinker {
371
348
  if (!content) return;
372
349
  const route = apiItemRoutes.get(content);
373
350
  if (route) {
374
- const kind = apiItemKinds.get(content);
375
- const semanticClass = kind ? this.getSemanticClass(kind) : null;
376
351
  const leadingSpace = rawContent.match(/^\s*/)?.[0] || "";
377
352
  const trailingSpace = rawContent.match(/\s*$/)?.[0] || "";
378
353
  const classNames = ["api-type-link", "rp-link"];
379
- if (semanticClass) classNames.push(semanticClass);
380
354
  const newChildren = [];
381
355
  if (leadingSpace) newChildren.push({
382
356
  type: "text",
@@ -470,7 +444,7 @@ var ShikiCrossLinker = class ShikiCrossLinker {
470
444
  * Iterates child spans, skipping already-processed and Twoslash-containing spans,
471
445
  * and splits text nodes at type name boundaries.
472
446
  */
473
- linkTypeReferencesInLine(lineElement, typePattern, apiItemRoutes, apiItemKinds) {
447
+ linkTypeReferencesInLine(lineElement, typePattern, apiItemRoutes) {
474
448
  for (const child of lineElement.children) {
475
449
  if (child.type !== "element" || child.tagName !== "span") continue;
476
450
  if (child.properties?.["data-api-processed"] === "true") continue;
@@ -482,7 +456,7 @@ var ShikiCrossLinker = class ShikiCrossLinker {
482
456
  newChildren.push(textChild);
483
457
  continue;
484
458
  }
485
- const fragments = this.splitTextAtTypeReferences(textChild.value, typePattern, apiItemRoutes, apiItemKinds);
459
+ const fragments = this.splitTextAtTypeReferences(textChild.value, typePattern, apiItemRoutes);
486
460
  if (fragments.length === 1 && fragments[0].type === "text") newChildren.push(textChild);
487
461
  else {
488
462
  newChildren.push(...fragments);
@@ -502,7 +476,7 @@ var ShikiCrossLinker = class ShikiCrossLinker {
502
476
  * Split a text string at type reference boundaries, returning an array of
503
477
  * text nodes and anchor elements for matched type names.
504
478
  */
505
- splitTextAtTypeReferences(text, typePattern, apiItemRoutes, apiItemKinds) {
479
+ splitTextAtTypeReferences(text, typePattern, apiItemRoutes) {
506
480
  typePattern.lastIndex = 0;
507
481
  const result = [];
508
482
  let lastIndex = 0;
@@ -514,16 +488,12 @@ var ShikiCrossLinker = class ShikiCrossLinker {
514
488
  type: "text",
515
489
  value: text.slice(lastIndex, match.index)
516
490
  });
517
- const kind = apiItemKinds.get(matchedName);
518
- const semanticClass = kind ? this.getSemanticClass(kind) : null;
519
- const classNames = ["api-type-link"];
520
- if (semanticClass) classNames.push(semanticClass);
521
491
  result.push({
522
492
  type: "element",
523
493
  tagName: "a",
524
494
  properties: {
525
495
  href: route,
526
- class: classNames.join(" ")
496
+ class: ["api-type-link"].join(" ")
527
497
  },
528
498
  children: [{
529
499
  type: "text",
@@ -542,15 +512,6 @@ var ShikiCrossLinker = class ShikiCrossLinker {
542
512
  });
543
513
  return result;
544
514
  }
545
- /**
546
- * Get the semantic CSS class name for an API item kind.
547
- *
548
- * @deprecated Semantic token colors are now handled by Shiki's theme CSS variables.
549
- * This method always returns null - only api-type-link is used for underline styling.
550
- */
551
- getSemanticClass(_kind) {
552
- return null;
553
- }
554
515
  };
555
516
 
556
517
  //#endregion
@@ -1,19 +1,18 @@
1
1
  import { PluginEvent } from "./observability/events.js";
2
2
  import { emitSync, syncBuildId } from "./observability/sync-emitter.js";
3
- import { DEFAULT_COMPILER_OPTIONS } from "./typescript-config.js";
4
3
  import { Result } from "effect";
5
4
  import { Markdown, Mdast } from "@effected/markdown";
6
- import { TsEnumCodec } from "@effected/tsconfig-json";
7
5
  import { rendererRich, transformerTwoslash } from "@shikijs/twoslash";
6
+ import { DEFAULT_COMPILER_OPTIONS, toProgrammaticCompilerOptions } from "@tsdoctor/vfs";
8
7
  import { toHast } from "mdast-util-to-hast";
9
8
 
10
9
  //#region src/twoslash-transformer.ts
11
10
  /* v8 ignore start -- Shiki/Twoslash integration, requires full highlighter setup for testing */
12
11
  /**
13
12
  * Module-level type routes map for resolving link references.
14
- * This is set by TwoslashManager.setTypeRoutes() before initialization.
13
+ * Populated per build by `addTypeRoutes` and reset by `clearTypeRoutes`.
15
14
  */
16
- let typeRoutes = /* @__PURE__ */ new Map();
15
+ const typeRoutes = /* @__PURE__ */ new Map();
17
16
  /**
18
17
  * Transform TSDoc link tag syntax to markdown links or plain text.
19
18
  *
@@ -181,47 +180,6 @@ function renderMarkdownInline(markdown, context) {
181
180
  }];
182
181
  }
183
182
  /**
184
- * Singleton manager for the Twoslash transformer, enabling type-aware documentation.
185
- *
186
- * The TwoslashManager initializes and manages a Shiki transformer that provides
187
- * TypeScript IntelliSense features (hover types, error highlighting, completions)
188
- * in documentation code blocks. It uses a virtual file system (VFS) to provide
189
- * type definitions without requiring actual file system access.
190
- *
191
- * **How it works:**
192
- * 1. Plugin initializes the manager with a VFS containing all package type definitions
193
- * 2. Code blocks marked with `twoslash` are processed by the transformer
194
- * 3. TypeScript language services provide hover information and error checking
195
- * 4. Results are rendered as HTML with interactive hover popups
196
- *
197
- * **VFS Integration:**
198
- * The VFS is populated by {@link TypeRegistryService} with:
199
- * - The documented package's own type definitions (from API Extractor)
200
- * - External package types (fetched via @tsdoctor/registry)
201
- *
202
- * **Error Handling:**
203
- * TypeScript errors in code blocks are captured (not thrown) and:
204
- * - Counted via Effect Metric (BuildMetrics.twoslashErrors)
205
- * - Logged inline via console.error
206
- * - Displayed in the rendered output as error annotations
207
- *
208
- * **Relationships:**
209
- * - Initialized by {@link ApiExtractorPlugin} in the beforeBuild hook
210
- * - Receives VFS from {@link TypeRegistryService}
211
- * - The transformer is used by page generators for rendering code blocks
212
- *
213
- * @example
214
- * ```ts
215
- * const manager = TwoslashManager.getInstance();
216
- * manager.initialize(vfs, undefined, logger);
217
- *
218
- * const transformer = manager.getTransformer();
219
- * // Use transformer with Shiki highlighter
220
- * ```
221
- *
222
- * @see {@link TypeRegistryService} for VFS generation
223
- */
224
- /**
225
183
  * Fingerprint a compiler configuration so environments can be deduped and code
226
184
  * blocks routed to the right one. Keys are sorted, so two configurations that
227
185
  * differ only in property order share an environment.
@@ -244,9 +202,6 @@ function renderMarkdownInline(markdown, context) {
244
202
  * environment, not the raw default), so a synthetic test compiling each
245
203
  * resolution path through the real compiler is the only verification there is.
246
204
  */
247
- function toProgrammaticCompilerOptions(options) {
248
- return TsEnumCodec.encodeCompilerOptions(options);
249
- }
250
205
  /**
251
206
  * Fingerprint a compiler configuration, for keying the environment map.
252
207
  *
@@ -441,6 +396,18 @@ var TwoslashEnvironmentRegistry = class {
441
396
  this.handleTwoslashError(error, code, file);
442
397
  }
443
398
  };
399
+ /**
400
+ * Cross-link routes used to turn type names in hover docs into links.
401
+ *
402
+ * @remarks
403
+ * These were `static` members of the old singleton, but they are cross-link
404
+ * DATA, not type-checking state — they only ever read and wrote the
405
+ * module-level `typeRoutes` map above, and they share their concern with
406
+ * the prose cross-linker rather than with the environment registry. They
407
+ * are deliberately NOT part of {@link TwoslashEnvironments}: folding them in
408
+ * would widen the service's surface with state that has nothing to do with
409
+ * compiler configurations.
410
+ */
444
411
  /** Merge routes in, so a multi-API build accumulates every scope's names. */
445
412
  function addTypeRoutes(routes) {
446
413
  for (const [name, route] of routes) typeRoutes.set(name, route);
@@ -463,4 +430,4 @@ function clearTypeRoutes() {
463
430
  }
464
431
 
465
432
  //#endregion
466
- export { TwoslashEnvironmentRegistry, addTypeRoutes, clearTypeRoutes, toProgrammaticCompilerOptions };
433
+ export { TwoslashEnvironmentRegistry, addTypeRoutes, clearTypeRoutes };