rspress-plugin-api-extractor 0.9.2 → 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.
Files changed (52) hide show
  1. package/BuildEnv.js +58 -0
  2. package/README.md +2 -1
  3. package/build-program.js +33 -30
  4. package/build-stages.js +47 -39
  5. package/errors.js +0 -1
  6. package/index.d.ts +22 -14
  7. package/layers/ConfigServiceLive.js +349 -400
  8. package/layers/HighlighterServiceLive.js +52 -0
  9. package/layers/ObservabilityLive.js +26 -7
  10. package/layers/OgServiceLive.js +134 -0
  11. package/layers/TwoslashCacheServiceLive.js +108 -0
  12. package/layers/TwoslashEnvironmentsLive.js +33 -0
  13. package/layers/TypeRegistryServiceLive.js +54 -47
  14. package/layers/build-metrics.js +32 -5
  15. package/layers/xdg.js +44 -0
  16. package/markdown/helpers.js +9 -55
  17. package/markdown/page-generators/class-page.js +8 -31
  18. package/markdown/page-generators/index-pages.js +6 -8
  19. package/markdown/page-generators/interface-page.js +7 -7
  20. package/markdown/shiki-utils.js +65 -10
  21. package/observability/EventBus.js +29 -9
  22. package/observability/heartbeat.js +1 -1
  23. package/observability/metric-report.js +124 -0
  24. package/observability/sinks/console-sink.js +6 -0
  25. package/observability/sinks/metrics-sink.js +64 -21
  26. package/observability/sinks/render-sink.js +86 -0
  27. package/observability/sinks/trace-sink.js +10 -17
  28. package/observability/spans.js +4 -2
  29. package/observability/sync-emitter.js +78 -0
  30. package/og-resolver.js +46 -287
  31. package/package.json +4 -5
  32. package/path-derivation.js +19 -1
  33. package/plugin.js +64 -52
  34. package/prettier-formatter.js +4 -10
  35. package/remark-api-codeblocks.js +33 -15
  36. package/remark-with-api.js +24 -27
  37. package/schemas/config.js +11 -7
  38. package/services/HighlighterService.js +30 -0
  39. package/services/OgService.js +23 -0
  40. package/services/PluginConfig.js +26 -0
  41. package/services/TwoslashCacheService.js +15 -0
  42. package/services/TwoslashEnvironments.js +7 -0
  43. package/shiki-transformer.js +55 -256
  44. package/twoslash-access.js +48 -0
  45. package/twoslash-cache.js +174 -0
  46. package/twoslash-patterns.js +1 -1
  47. package/twoslash-timing-wrapper.js +23 -0
  48. package/twoslash-transformer.js +153 -89
  49. package/vfs-registry.js +1 -31
  50. package/layers/PathDerivationServiceLive.js +0 -16
  51. package/runtime/components/MarkdownText/index.js +0 -34
  52. 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` → `ConfigServiceLive`'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 `TypeRegistryServiceLive` 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 `ConfigServiceLive` 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/README.md CHANGED
@@ -47,7 +47,8 @@ The plugin reads your `.api.json` model and writes one MDX page per public API i
47
47
  ## Features
48
48
 
49
49
  - Generates API docs from `.api.json` models for classes, interfaces, functions, type aliases, enums, variables and namespaces.
50
- - Type-checks code examples and adds Twoslash hover tooltips that show inferred types.
50
+ - Type-checks code examples and adds Twoslash hover tooltips that show inferred types, each documented package under its own `tsconfig`.
51
+ - Caches Twoslash results between builds, so repeat builds over an unchanged API render code blocks near-instantly.
51
52
  - Cross-links type references between pages, so a type named in a signature links to its own page.
52
53
  - Inlines compiler-generated base declarations (the `Foo_base` pattern from Effect `Schema.Class`, `Data.TaggedError` and mixin factories) in a "Base Class" section on the owning class page instead of documenting them as orphan variables.
53
54
  - Drives single-package sites, multi-package portals, RSPress multiVersion and i18n from one plugin.
package/build-program.js CHANGED
@@ -1,8 +1,13 @@
1
+ import { BuildId, PageConcurrency, SuppressExampleErrors } from "./BuildEnv.js";
1
2
  import { setProseLinker } from "./markdown/prose-linker.js";
2
3
  import "./markdown/index.js";
3
4
  import { buildPipelineForApi, cleanupAndCommit, prepareWorkItems, writeMetadata } from "./build-stages.js";
5
+ import { HideCutLinesTransformer, MemberFormatTransformer } from "./hide-cut-transformer.js";
4
6
  import { withPhase } from "./observability/spans.js";
5
- import { TwoslashManager } from "./twoslash-transformer.js";
7
+ import { HighlighterService } from "./services/HighlighterService.js";
8
+ import { TwoslashEnvironments } from "./services/TwoslashEnvironments.js";
9
+ import { ShikiCrossLinker } from "./shiki-transformer.js";
10
+ import { addTypeRoutes } from "./twoslash-transformer.js";
6
11
  import { VfsRegistry } from "./vfs-registry.js";
7
12
  import path from "node:path";
8
13
  import { SnapshotService } from "@tsdoctor/snapshot";
@@ -22,17 +27,17 @@ import { Effect, FileSystem } from "effect";
22
27
  * Returns build result metadata including CrossLinkData for cross-link merging
23
28
  * and generated file paths for LLMs post-processing.
24
29
  */
25
- function generateApiDocs(apiConfig, buildContext, fileContextMap) {
30
+ function generateApiDocs(apiConfig, fileContextMap) {
26
31
  return Effect.gen(function* () {
27
32
  const fileSystem = yield* FileSystem.FileSystem;
33
+ const { highlighter } = yield* HighlighterService;
34
+ const environments = yield* TwoslashEnvironments;
35
+ const buildId = yield* BuildId;
36
+ const pageConcurrency = yield* PageConcurrency;
37
+ const suppressExampleErrors = yield* SuppressExampleErrors;
28
38
  const snapshotSvc = yield* SnapshotService;
29
39
  const { apiPackage, packageName, apiName, outputDir, baseRoute, categories, source, packageJson, llmsPlugin, siteUrl, ogImage } = apiConfig;
30
- const suppressExampleErrors = apiConfig.suppressExampleErrors ?? true;
31
- const { shikiCrossLinker, highlighter, hideCutTransformer, hideCutLinesTransformer, twoslashTransformer, ogResolver, pageConcurrency, thresholds, buildId } = buildContext;
32
- const phaseCtx = {
33
- buildId,
34
- packageName
35
- };
40
+ const phaseCtx = { packageName };
36
41
  const resolvedOutputDir = path.resolve(process.cwd(), outputDir);
37
42
  const buildTime = (/* @__PURE__ */ new Date()).toISOString();
38
43
  const allSnapshots = yield* snapshotSvc.getAllForDirectory(resolvedOutputDir).pipe(Effect.orDie);
@@ -43,28 +48,27 @@ function generateApiDocs(apiConfig, buildContext, fileContextMap) {
43
48
  categories,
44
49
  baseRoute,
45
50
  packageName
46
- })), thresholds);
51
+ })));
47
52
  setProseLinker(crossLinkData.routes);
48
53
  const apiScope = baseRoute.replace(/^\//, "").split("/")[0] || packageName;
49
- shikiCrossLinker.reinitialize(crossLinkData.routes, crossLinkData.kinds, apiScope);
50
- TwoslashManager.addTypeRoutes(crossLinkData.routes);
51
- if (highlighter) {
52
- const vfsConfig = {
53
- vfs: /* @__PURE__ */ new Map(),
54
- highlighter,
55
- crossLinker: shikiCrossLinker,
56
- packageName,
57
- apiScope
58
- };
59
- if (twoslashTransformer != null) vfsConfig.twoslashTransformer = twoslashTransformer;
60
- if (hideCutTransformer != null) vfsConfig.hideCutTransformer = hideCutTransformer;
61
- if (hideCutLinesTransformer != null) vfsConfig.hideCutLinesTransformer = hideCutLinesTransformer;
62
- if (apiConfig.theme != null) vfsConfig.theme = apiConfig.theme;
63
- VfsRegistry.register(apiScope, vfsConfig);
64
- }
54
+ const shikiCrossLinker = ShikiCrossLinker.fromRoutes(crossLinkData.routes, crossLinkData.kinds, apiScope);
55
+ addTypeRoutes(crossLinkData.routes);
56
+ const vfsConfig = {
57
+ highlighter,
58
+ crossLinker: shikiCrossLinker,
59
+ packageName,
60
+ apiScope
61
+ };
62
+ const scopeTransformer = environments.transformerFor(apiScope);
63
+ if (scopeTransformer != null) vfsConfig.twoslashTransformer = scopeTransformer;
64
+ vfsConfig.hideCutTransformer = MemberFormatTransformer;
65
+ vfsConfig.hideCutLinesTransformer = HideCutLinesTransformer;
66
+ if (apiConfig.theme != null) vfsConfig.theme = apiConfig.theme;
67
+ VfsRegistry.register(apiScope, vfsConfig);
65
68
  yield* Effect.logDebug(`Generating ${workItems.length} pages across ${Object.keys(categories).length} categories in parallel`);
66
69
  const fileResults = yield* withPhase("generate", phaseCtx, buildPipelineForApi({
67
70
  buildId,
71
+ pageConcurrency,
68
72
  workItems,
69
73
  baseRoute,
70
74
  packageName,
@@ -73,14 +77,13 @@ function generateApiDocs(apiConfig, buildContext, fileContextMap) {
73
77
  ...source != null ? { source } : {},
74
78
  buildTime,
75
79
  resolvedOutputDir,
76
- pageConcurrency,
77
80
  existingSnapshots,
78
81
  ...suppressExampleErrors != null ? { suppressExampleErrors } : {},
79
82
  ...llmsPlugin != null ? { llmsPlugin } : {},
80
- ...ogResolver !== void 0 ? { ogResolver } : {},
83
+ ...apiConfig.docsRoot != null ? { docsRoot: apiConfig.docsRoot } : {},
81
84
  ...siteUrl != null ? { siteUrl } : {},
82
85
  ...ogImage != null ? { ogImage } : {}
83
- }), thresholds);
86
+ }));
84
87
  const changedCount = fileResults.filter((r) => r.status !== "unchanged").length;
85
88
  yield* Effect.logDebug(`Generated ${changedCount} pages`);
86
89
  const generatedFiles = /* @__PURE__ */ new Set();
@@ -102,13 +105,13 @@ function generateApiDocs(apiConfig, buildContext, fileContextMap) {
102
105
  packageName,
103
106
  ...apiName != null ? { apiName } : {},
104
107
  generatedFiles
105
- }), thresholds);
108
+ }));
106
109
  yield* withPhase("cleanup", phaseCtx, cleanupAndCommit({
107
110
  buildId,
108
111
  fileResults,
109
112
  resolvedOutputDir,
110
113
  generatedFiles
111
- }), thresholds);
114
+ }));
112
115
  yield* Effect.logDebug(`Generated ${changedCount} API documentation files for ${packageName}`);
113
116
  return {
114
117
  crossLinkData,
package/build-stages.js CHANGED
@@ -3,6 +3,7 @@ import { PluginEvent } from "./observability/events.js";
3
3
  import { emit } from "./observability/EventBus.js";
4
4
  import { BuildMetrics } from "./layers/build-metrics.js";
5
5
  import "./layers/ObservabilityLive.js";
6
+ import { emitSync, syncBuildId } from "./observability/sync-emitter.js";
6
7
  import { generateFrontmatter } from "./markdown/helpers.js";
7
8
  import { ClassPageGenerator } from "./markdown/page-generators/class-page.js";
8
9
  import { EnumPageGenerator } from "./markdown/page-generators/enum-page.js";
@@ -13,10 +14,11 @@ import { NamespacePageGenerator } from "./markdown/page-generators/namespace-pag
13
14
  import { TypeAliasPageGenerator } from "./markdown/page-generators/type-alias-page.js";
14
15
  import { VariablePageGenerator } from "./markdown/page-generators/variable-page.js";
15
16
  import "./markdown/index.js";
16
- import { OpenGraphResolver } from "./og-resolver.js";
17
+ import { createPageMetadata } from "./og-resolver.js";
18
+ import { OgService } from "./services/OgService.js";
17
19
  import path from "node:path";
18
20
  import { SnapshotService, hashContent, hashFrontmatter } from "@tsdoctor/snapshot";
19
- import { Effect, FileSystem, Metric, Stream } from "effect";
21
+ import { Effect, FileSystem, Metric, Option, Stream } from "effect";
20
22
  import { ApiItemKind } from "@microsoft/api-extractor-model";
21
23
  import { ApiItems, EntryPoints, Routes, SyntheticBases } from "@tsdoctor/model";
22
24
 
@@ -41,24 +43,6 @@ function crossLinkKindPriority(kind) {
41
43
  return CROSS_LINK_KIND_PRIORITY[kind] ?? 100;
42
44
  }
43
45
  /**
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
46
  * Prepare the flat list of WorkItems to process and the cross-link data maps.
63
47
  *
64
48
  * This function:
@@ -82,8 +66,8 @@ function prepareWorkItems(input) {
82
66
  resolvedLookup.set(key, resolved);
83
67
  }
84
68
  const { items, uncategorized } = ApiItems.categorize(docItems, categories);
85
- for (const skipped of uncategorized) emitEvent(PluginEvent.ItemSkipped({
86
- ctx: { buildId: currentBuildId },
69
+ for (const skipped of uncategorized) emitSync(PluginEvent.ItemSkipped({
70
+ ctx: { buildId: syncBuildId() },
87
71
  item: skipped.displayName,
88
72
  kind: String(skipped.kind),
89
73
  reason: "uncategorized",
@@ -115,9 +99,9 @@ function prepareWorkItems(input) {
115
99
  const collisions = Routes.detectCollisions(candidates);
116
100
  if (collisions.length > 0) {
117
101
  try {
118
- for (const collision of collisions) emitEvent(PluginEvent.RouteCollisionDetected({
102
+ for (const collision of collisions) emitSync(PluginEvent.RouteCollisionDetected({
119
103
  ctx: {
120
- buildId: currentBuildId,
104
+ buildId: syncBuildId(),
121
105
  route: collision.route
122
106
  },
123
107
  level: "error",
@@ -145,13 +129,14 @@ function prepareWorkItems(input) {
145
129
  }
146
130
  if (item.kind === "Class" || item.kind === "Interface") {
147
131
  const itemWithMembers = item;
148
- for (const member of itemWithMembers.members) {
149
- const memberName = member.displayName;
150
- const memberId = Routes.sanitizeId(memberName);
151
- const fullMemberName = `${item.displayName}.${memberName}`;
152
- const memberRoute = `${itemRoute}#${memberId}`;
153
- routes.set(fullMemberName, memberRoute);
154
- kinds.set(fullMemberName, member.kind);
132
+ const anchors = ApiItems.memberAnchors(itemWithMembers);
133
+ const byCanonicalRef = new Map(itemWithMembers.members.map((member) => [member.canonicalReference?.toString() ?? member.displayName, member]));
134
+ for (const [routeKey, memberId] of ApiItems.memberRouteKeys(itemWithMembers)) {
135
+ const member = byCanonicalRef.get(memberId);
136
+ if (!member) continue;
137
+ const anchor = anchors.get(memberId) ?? Routes.memberAnchor(member.displayName);
138
+ routes.set(routeKey, `${itemRoute}#${anchor}`);
139
+ kinds.set(routeKey, member.kind);
155
140
  }
156
141
  }
157
142
  }
@@ -190,12 +175,14 @@ function prepareWorkItems(input) {
190
175
  const lookupKey = `${item.displayName}::${item.kind}`;
191
176
  const resolved = resolvedLookup.get(lookupKey);
192
177
  const syntheticBase = syntheticBases.baseByOwner.get(item);
178
+ const memberAnchors = item.kind === "Class" || item.kind === "Interface" ? ApiItems.memberAnchors(item) : void 0;
193
179
  workItems.push({
194
180
  item,
195
181
  categoryKey,
196
182
  categoryConfig,
197
183
  ...resolved?.availableFrom != null ? { availableFrom: resolved.availableFrom } : {},
198
- ...syntheticBase != null ? { syntheticBase } : {}
184
+ ...syntheticBase != null ? { syntheticBase } : {},
185
+ ...memberAnchors != null ? { memberAnchors } : {}
199
186
  });
200
187
  }
201
188
  }
@@ -240,7 +227,7 @@ function generateSinglePage(workItem, ctx) {
240
227
  switch (item.kind) {
241
228
  case ApiItemKind.Class: {
242
229
  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));
230
+ page = yield* Effect.promise(() => generator.generate(item, baseRoute, packageName, categoryConfig.singularName, apiScope, apiName, source, suppressExampleErrors, llmsPlugin, workItem.availableFrom, workItem.syntheticBase, workItem.memberAnchors));
244
231
  page = {
245
232
  routePath: page.routePath.replace("/class/", `/${categoryConfig.folderName}/`),
246
233
  content: page.content
@@ -402,7 +389,7 @@ function generateSinglePage(workItem, ctx) {
402
389
  function writeSingleFile(result, ctx) {
403
390
  return Effect.gen(function* () {
404
391
  const fileSystem = yield* FileSystem.FileSystem;
405
- const { buildId, resolvedOutputDir, buildTime, ogResolver, siteUrl, ogImage, packageName, apiName } = ctx;
392
+ const { buildId, resolvedOutputDir, buildTime, siteUrl, docsRoot, ogImage, packageName, apiName } = ctx;
406
393
  const { workItem, bodyContent, frontmatter, contentHash, frontmatterHash, publishedTime, modifiedTime, isUnchanged, routePath, relativePathWithExt } = result;
407
394
  const { item, categoryKey, categoryConfig, namespaceMember } = workItem;
408
395
  const absolutePath = path.join(resolvedOutputDir, relativePathWithExt);
@@ -440,8 +427,29 @@ function writeSingleFile(result, ctx) {
440
427
  };
441
428
  }
442
429
  let finalContent = stringifyFrontmatter(bodyContent, frontmatter);
443
- if (ogResolver && siteUrl && packageName) {
444
- const ogImageMetadata = yield* Effect.promise(() => ogResolver.resolve(ogImage, packageName, apiName));
430
+ if (siteUrl && packageName) {
431
+ const ogSvc = yield* OgService;
432
+ const ogImageResult = yield* Effect.result(ogSvc.resolveImage({
433
+ config: ogImage,
434
+ siteUrl,
435
+ docsRoot,
436
+ packageName,
437
+ ...apiName != null ? { apiName } : {}
438
+ }));
439
+ if (ogImageResult._tag === "Failure") {
440
+ const failure = ogImageResult.failure;
441
+ yield* emit(PluginEvent.ConfigValidationWarning({
442
+ ctx: {
443
+ buildId,
444
+ packageName
445
+ },
446
+ field: failure.field,
447
+ value: failure.value,
448
+ reason: failure.message,
449
+ level: "warn"
450
+ }));
451
+ }
452
+ const ogImageMetadata = ogImageResult._tag === "Success" && Option.isSome(ogImageResult.success) ? ogImageResult.success.value : void 0;
445
453
  const ogMetadataOptions = {
446
454
  siteUrl,
447
455
  pageRoute: routePath,
@@ -452,7 +460,7 @@ function writeSingleFile(result, ctx) {
452
460
  packageName
453
461
  };
454
462
  if (ogImageMetadata) ogMetadataOptions.ogImage = ogImageMetadata;
455
- const ogMetadata = OpenGraphResolver.createPageMetadata(ogMetadataOptions);
463
+ const ogMetadata = createPageMetadata(ogMetadataOptions);
456
464
  finalContent = generateFrontmatter(item.displayName, frontmatter.description, categoryConfig.singularName, apiName, ogMetadata) + bodyContent;
457
465
  }
458
466
  const fileExisted = yield* fileSystem.exists(absolutePath).pipe(Effect.orElseSucceed(() => false));
@@ -793,7 +801,7 @@ function buildPipelineForApi(input) {
793
801
  buildId: input.buildId,
794
802
  resolvedOutputDir: input.resolvedOutputDir,
795
803
  buildTime: input.buildTime,
796
- ...input.ogResolver !== void 0 ? { ogResolver: input.ogResolver } : {},
804
+ ...input.docsRoot !== void 0 ? { docsRoot: input.docsRoot } : {},
797
805
  ...input.siteUrl != null ? { siteUrl: input.siteUrl } : {},
798
806
  ...input.ogImage != null ? { ogImage: input.ogImage } : {},
799
807
  ...input.packageName != null ? { packageName: input.packageName } : {},
@@ -803,4 +811,4 @@ function buildPipelineForApi(input) {
803
811
  }
804
812
 
805
813
  //#endregion
806
- export { buildPipelineForApi, cleanupAndCommit, crossLinkKindPriority, generateSinglePage, normalizeMarkdownSpacing, prepareWorkItems, setBuildStagesEventEmitter, writeMetadata, writeSingleFile };
814
+ export { buildPipelineForApi, cleanupAndCommit, crossLinkKindPriority, generateSinglePage, prepareWorkItems, writeMetadata, writeSingleFile };
package/errors.js CHANGED
@@ -8,7 +8,6 @@ var ConfigValidationError = class extends ConfigValidationErrorBase {
8
8
  }
9
9
  };
10
10
  const ApiModelLoadErrorBase = Data.TaggedError("ApiModelLoadError");
11
- const PathDerivationErrorBase = Data.TaggedError("PathDerivationError");
12
11
  const TypeRegistryErrorBase = Data.TaggedError("TypeRegistryError");
13
12
  var TypeRegistryError = class extends TypeRegistryErrorBase {
14
13
  get message() {
package/index.d.ts CHANGED
@@ -390,15 +390,19 @@ declare const MultiApiConfig: Schema.Struct<{
390
390
  * Path to a `tsconfig.json` for Twoslash.
391
391
  *
392
392
  * @remarks
393
- * Twoslash runs against a single shared TypeScript environment for the
394
- * whole build, so per-API tsconfigs are not honored in multi-API mode:
395
- * the first API that provides one wins and the rest are ignored (a
396
- * `ConfigCascadeWarning` is emitted when they differ). Ensure the
397
- * configured tsconfigs are equivalent, or set the intended one on the
398
- * first API only.
393
+ * This API's code blocks are type-checked under this config. APIs that
394
+ * declare the same config share one TypeScript environment; the file set
395
+ * is shared across all documented APIs either way, so a type owned by
396
+ * another documented package still resolves.
399
397
  */
400
398
  readonly tsconfig: Schema.optional<Schema.declare<string | URL | ((...args: Array<unknown>) => unknown), string | URL | ((...args: Array<unknown>) => unknown)>>;
401
- /** TypeScript compiler options for Twoslash. First API wins, as with `tsconfig`. */
399
+ /**
400
+ * TypeScript compiler options for Twoslash, applying to this API only.
401
+ *
402
+ * @remarks
403
+ * Merged on top of the defaults and of this API's `tsconfig`, so declaring
404
+ * a single option overrides just that one.
405
+ */
402
406
  readonly compilerOptions: Schema.optional<Schema.Unknown>;
403
407
  }>;
404
408
  /** @public */
@@ -650,15 +654,19 @@ declare const PluginOptions: Schema.Struct<{
650
654
  * Path to a `tsconfig.json` for Twoslash.
651
655
  *
652
656
  * @remarks
653
- * Twoslash runs against a single shared TypeScript environment for the
654
- * whole build, so per-API tsconfigs are not honored in multi-API mode:
655
- * the first API that provides one wins and the rest are ignored (a
656
- * `ConfigCascadeWarning` is emitted when they differ). Ensure the
657
- * configured tsconfigs are equivalent, or set the intended one on the
658
- * first API only.
657
+ * This API's code blocks are type-checked under this config. APIs that
658
+ * declare the same config share one TypeScript environment; the file set
659
+ * is shared across all documented APIs either way, so a type owned by
660
+ * another documented package still resolves.
659
661
  */
660
662
  readonly tsconfig: Schema.optional<Schema.declare<string | URL | ((...args: Array<unknown>) => unknown), string | URL | ((...args: Array<unknown>) => unknown)>>;
661
- /** TypeScript compiler options for Twoslash. First API wins, as with `tsconfig`. */
663
+ /**
664
+ * TypeScript compiler options for Twoslash, applying to this API only.
665
+ *
666
+ * @remarks
667
+ * Merged on top of the defaults and of this API's `tsconfig`, so declaring
668
+ * a single option overrides just that one.
669
+ */
662
670
  readonly compilerOptions: Schema.optional<Schema.Unknown>;
663
671
  }>>>>>;
664
672
  /** Canonical site URL used for Open Graph absolute URLs. */