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
@@ -2,23 +2,22 @@ import { BuildId } from "../BuildEnv.js";
2
2
  import { BuildMetrics } from "./build-metrics.js";
3
3
  import { PluginEvent } from "../observability/events.js";
4
4
  import { emit, wantsLevel } from "../observability/EventBus.js";
5
- import { TypeReferenceExtractor } from "../type-reference-extractor.js";
6
5
  import { withPhase } from "../observability/spans.js";
7
6
  import { normalizeThemeConfig } from "../markdown/shiki-utils.js";
8
- import { apiScopeOf, deriveOutputPaths, normalizeBaseRoute, unscopedName } from "../path-derivation.js";
9
7
  import { classifyApiConfig, extractAutoDetectedPackages, isVersionConfig, mergeLlmsPluginConfig, validateExternalPackages } from "../config-utils.js";
10
- import { ApiExtractedPackage } from "../api-extracted-package.js";
11
8
  import { CategoryResolver } from "../category-resolver.js";
12
9
  import { ConfigValidationError } from "../errors.js";
13
10
  import { loadApiModel, loadPackageJson, loadVersionModel } from "../model-loader.js";
11
+ import { deriveOutputPaths } from "../path-derivation.js";
14
12
  import { DEFAULT_CATEGORIES } from "../schemas/config.js";
15
13
  import { PluginConfig } from "../services/PluginConfig.js";
16
14
  import { TypeRegistryService } from "../services/TypeRegistryService.js";
17
15
  import { emitVfsPayloadEvents, mergeApiResult } from "./api-results.js";
18
16
  import { mergeExternalTypes } from "./external-types.js";
19
17
  import { registerTypeEnvironments, resolveTsConfigTyped } from "./type-environment.js";
20
- import path from "node:path";
18
+ import { apiScopeOf, normalizeBaseRoute, unscopedName } from "@tsdoctor/pages";
21
19
  import { Effect, Metric } from "effect";
20
+ import { ApiExtractedPackage, TypeReferenceExtractor } from "@tsdoctor/model";
22
21
  import { deriveSiteUrl } from "@tsdoctor/seo";
23
22
  import { hashContent } from "@tsdoctor/snapshot";
24
23
  import { PackageManifest } from "@effected/package-json";
@@ -241,7 +240,7 @@ const makeConfigService = Effect.gen(function* () {
241
240
  });
242
241
  const externalPackages = api.externalPackages || extractAutoDetectedPackages(packageJson, api.autoDetectDependencies);
243
242
  if (externalPackages && externalPackages.length > 0) yield* Metric.update(BuildMetrics.externalPackagesTotal, externalPackages.length);
244
- const vfs = ApiExtractedPackage.fromPackage(apiPackage, api.packageName).generateVfs();
243
+ const vfs = ApiExtractedPackage.fromPackage(apiPackage, api.packageName).toVfs();
245
244
  const vfsPayloads = prependImportsToVfs(vfs, apiPackage, api.packageName, wantTrace);
246
245
  const resolvedOgImage = api.ogImage ?? options.ogImage;
247
246
  const resolvedTheme = normalizeThemeConfig(api.theme);
@@ -262,7 +261,6 @@ const makeConfigService = Effect.gen(function* () {
262
261
  ...resolvedLlms != null ? { llmsPlugin: resolvedLlms } : {},
263
262
  ...siteUrl != null ? { siteUrl } : {},
264
263
  ...resolvedOgImage != null ? { ogImage: resolvedOgImage } : {},
265
- docsDir: path.dirname(outputDir),
266
264
  ...docsRoot != null ? { docsRoot } : {},
267
265
  ...resolvedTheme != null ? { theme: resolvedTheme } : {}
268
266
  }
@@ -321,7 +319,7 @@ const makeConfigService = Effect.gen(function* () {
321
319
  const autoDetectOptions = versionAutoDetectDependencies || api.autoDetectDependencies;
322
320
  const externalPackages = versionExternalPackages || api.externalPackages || extractAutoDetectedPackages(packageJson, autoDetectOptions);
323
321
  if (externalPackages && externalPackages.length > 0) yield* Metric.update(BuildMetrics.externalPackagesTotal, externalPackages.length);
324
- const vfs = ApiExtractedPackage.fromPackage(apiPackage, api.packageName).generateVfs();
322
+ const vfs = ApiExtractedPackage.fromPackage(apiPackage, api.packageName).toVfs();
325
323
  const vfsPayloads = prependImportsToVfs(vfs, apiPackage, api.packageName, wantTrace);
326
324
  const resolvedOgImage = versionOgImage ?? api.ogImage ?? options.ogImage;
327
325
  const resolvedTheme = normalizeThemeConfig(api.theme);
@@ -344,7 +342,6 @@ const makeConfigService = Effect.gen(function* () {
344
342
  ...resolvedLlms != null ? { llmsPlugin: resolvedLlms } : {},
345
343
  ...siteUrl != null ? { siteUrl } : {},
346
344
  ...resolvedOgImage != null ? { ogImage: resolvedOgImage } : {},
347
- docsDir: path.dirname(outputDir),
348
345
  ...docsRoot != null ? { docsRoot } : {},
349
346
  ...resolvedTheme != null ? { theme: resolvedTheme } : {}
350
347
  }
@@ -1,11 +1,10 @@
1
1
  import { PluginEvent } from "../observability/events.js";
2
2
  import { emit } from "../observability/EventBus.js";
3
- import { resolveTypeScriptConfig } from "../typescript-config.js";
4
3
  import { TwoslashEnvironments } from "../services/TwoslashEnvironments.js";
5
4
  import { ConfigValidationError } from "../errors.js";
6
- import { twoslashEnvHash } from "../twoslash-cache.js";
7
5
  import { TwoslashCacheService } from "../services/TwoslashCacheService.js";
8
6
  import { Effect } from "effect";
7
+ import { resolveTypeScriptConfig, twoslashEnvHash } from "@tsdoctor/vfs";
9
8
  import ts from "typescript";
10
9
 
11
10
  //#region src/layers/type-environment.ts
@@ -64,12 +63,14 @@ const resolveTsConfigTyped = (projectRoot, config) => Effect.tryPromise({
64
63
  */
65
64
  const registerTypeEnvironments = (input) => Effect.gen(function* () {
66
65
  const twoslashEnv = twoslashEnvHash(input.combinedVfs, `typescript@${ts.version}`);
67
- const twoslashCache = yield* (yield* TwoslashCacheService).open(twoslashEnv);
66
+ const cacheSvc = yield* TwoslashCacheService;
67
+ const twoslashCache = yield* cacheSvc.open(twoslashEnv);
68
68
  yield* emit(PluginEvent.TwoslashCacheLoaded({
69
69
  ctx: {},
70
70
  level: "debug",
71
71
  envHash: twoslashEnv,
72
- entries: twoslashCache.entries().size
72
+ entries: twoslashCache.entries().size,
73
+ degraded: cacheSvc.degraded
73
74
  }));
74
75
  const twoslashStartMs = performance.now();
75
76
  const environments = yield* TwoslashEnvironments;
package/llms-program.js CHANGED
@@ -1,14 +1,14 @@
1
1
  import { PluginEvent } from "./observability/events.js";
2
2
  import { emit } from "./observability/EventBus.js";
3
- import { filterLlmsFullTxt, filterLlmsTxt, generatePackageLlmsFullTxt, generatePackageLlmsTxt, generateStructuredLlmsTxt, parseLlmsTxtLine } from "./llms-processing.js";
4
3
  import path from "node:path";
4
+ import { filterLlmsFullTxt, filterLlmsTxt, generatePackageLlmsFullTxt, generatePackageLlmsTxt, generateStructuredLlmsTxt, parseLlmsTxtLine } from "@tsdoctor/pages";
5
5
  import { Effect, FileSystem } from "effect";
6
6
 
7
7
  //#region src/llms-program.ts
8
8
  /**
9
9
  * Effect program for post-processing LLMs text files in afterBuild.
10
10
  *
11
- * Wires the pure processing functions from llms-processing.ts into the
11
+ * Wires the pure processing functions from @tsdoctor/pages into the
12
12
  * plugin lifecycle, handling file I/O via the core `effect` FileSystem service.
13
13
  *
14
14
  * Responsibilities:
@@ -89,7 +89,7 @@ function buildPackagePointers(buildResults, prefix, packageRoutes) {
89
89
  * Collect API page entries from the global llms.txt for a specific package.
90
90
  *
91
91
  * Parses the global llms.txt to find entries whose URLs match this package's
92
- * generated API routes, building the LlmsTxtEntry array for per-package files.
92
+ * generated API routes, building the `LlmsTxtEntry` array for per-package files.
93
93
  */
94
94
  function collectApiEntries(globalLlmsTxtContent, result) {
95
95
  const base = result.baseRoute.endsWith("/") ? result.baseRoute : `${result.baseRoute}/`;
@@ -1,44 +1,18 @@
1
- import { emitFrontmatterBlock } from "../frontmatter.js";
2
- import { classifyCutDirective, isTwoslashDirective } from "../twoslash-patterns.js";
3
- import { formatCode } from "../prettier-formatter.js";
4
- import { TypeReferenceExtractor } from "../type-reference-extractor.js";
1
+ import { emitFrontmatterBlock } from "@tsdoctor/model";
5
2
 
6
3
  //#region src/markdown/helpers.ts
7
4
  /**
8
- * Generate an "Available from" line for items exported from multiple entry points.
9
- * Returns empty string if only one entry point or none provided.
10
- */
11
- function generateAvailableFrom(packageName, availableFrom) {
12
- if (!availableFrom || availableFrom.length <= 1) return "";
13
- return `Available from: ${availableFrom.map((ep) => ep === "default" ? `\`${packageName}\`` : `\`${packageName}/${ep}\``).join(", ")}\n\n`;
14
- }
15
- /**
16
- * Prepare example code for Twoslash rendering.
5
+ * Frontmatter assembly for generated pages: the structured title and the
6
+ * rendering of neutral `@tsdoctor/seo` head tags into RSPress `head` pairs.
17
7
  *
18
- * Prepares the code with imports and error directives but does NOT render HTML.
19
- * Use this for raw markdown output or as input to pre-rendering.
8
+ * @remarks
9
+ * This stays adapter-side on purpose. The snapshot frontmatter hash is
10
+ * taken over the FINAL assembled block in the generate stage, and the
11
+ * `children` spelling for a JSON-LD script body is RSPress's — the IR
12
+ * carries facts and a `HeadTag[]`, not a frontmatter block.
20
13
  *
21
- * @param example - The example with language and code
22
- * @param apiItemName - The name of the API item being documented
23
- * @param packageName - The package name for imports
24
- * @param suppressErrors - Whether to suppress all TypeScript errors (default: true)
25
- * @returns Object with prepared code and whether it's TypeScript
14
+ * @packageDocumentation
26
15
  */
27
- function prepareExampleCode(example, apiItemName, packageName, suppressErrors = true) {
28
- const { language, code } = example;
29
- if (!(language === "typescript" || language === "ts" || language === "javascript" || language === "js")) return {
30
- code,
31
- isTypeScript: false,
32
- language
33
- };
34
- const importLine = `import { ${apiItemName} } from "${packageName}";`;
35
- const finalCode = code.includes(`from "${packageName}"`) || code.includes(`from '${packageName}'`) ? code : `${importLine}\n${code}`;
36
- return {
37
- code: `${suppressErrors ? "// @noErrors\n" : ""}${finalCode}`,
38
- isTypeScript: true,
39
- language: "typescript"
40
- };
41
- }
42
16
  /**
43
17
  * Collapse newlines and runs of whitespace to single spaces, and trim.
44
18
  *
@@ -53,28 +27,6 @@ function cleanYamlValue(value) {
53
27
  return value.replace(/[\r\n]+/g, " ").replace(/\s+/g, " ").trim();
54
28
  }
55
29
  /**
56
- * Escape generic type parameters in MDX by wrapping them in backticks.
57
- *
58
- * Prevents MDX from interpreting `<T>`, `<TEnv>`, etc. as JSX tags by
59
- * wrapping them in inline code backticks.
60
- *
61
- * @param text - The text containing generic type parameters
62
- * @returns Text with generics wrapped in backticks
63
- *
64
- * @example
65
- * ```ts
66
- * escapeMdxGenerics("Returns Promise<T>"); // "Returns Promise`<T>`"
67
- * escapeMdxGenerics("Map<K, V> extends..."); // "Map`<K, V>` extends..."
68
- * escapeMdxGenerics("`Pipeline<I, O>`"); // "`Pipeline<I, O>`" (unchanged)
69
- * ```
70
- */
71
- function escapeMdxGenerics(text) {
72
- return text.split(/(`[^`]+`)/g).map((part) => {
73
- if (part.startsWith("`") && part.endsWith("`")) return part;
74
- return part.replace(/<([A-Z][A-Za-z0-9_]*(?:\s+extends\s+[^>]+)?(?:,\s*[A-Z][A-Za-z0-9_]*(?:\s+extends\s+[^>]+)?)*)>/g, "`<$1>`");
75
- }).join("");
76
- }
77
- /**
78
30
  * Build a structured page title for API documentation.
79
31
  *
80
32
  * Creates a title in the format: `{entityName} | {singularName} | API | {apiName}`
@@ -142,125 +94,6 @@ function generateFrontmatter(entityName, description, singularName, apiName, tag
142
94
  if (headEntries.length > 0) data.head = headEntries;
143
95
  return emitFrontmatterBlock(data);
144
96
  }
145
- /**
146
- * Strip Twoslash directives from code for display purposes.
147
- *
148
- * Removes Twoslash directive comments like `// @noErrors`, `// @errors: 2304`,
149
- * `// @filename: ...`, etc. from code so users see clean output and don't
150
- * copy directives when using the copy button.
151
- *
152
- * Also handles cut directives:
153
- * - `// ---cut---` - Removes this line and all lines before it
154
- * - `// ---cut-before---` - Same as ---cut---
155
- * - `// ---cut-after---` - Removes this line and all lines after it
156
- *
157
- * @param code - The code containing Twoslash directives
158
- * @returns Code with Twoslash directives removed
159
- *
160
- * @example
161
- * ```ts
162
- * const display = stripTwoslashDirectives("// @noErrors\nconst x = 1;");
163
- * // Returns: "const x = 1;"
164
- * ```
165
- */
166
- function stripTwoslashDirectives(code) {
167
- const lines = code.split("\n");
168
- let cutBeforeIndex = -1;
169
- let cutAfterIndex = -1;
170
- const cutRanges = [];
171
- const cutStartStack = [];
172
- for (let i = 0; i < lines.length; i++) {
173
- const trimmed = lines[i].trim();
174
- const cutType = classifyCutDirective(trimmed);
175
- if (cutType === "cut-before") cutBeforeIndex = i;
176
- else if (cutType === "cut-after") cutAfterIndex = i;
177
- else if (cutType === "cut-start") cutStartStack.push(i);
178
- else if (cutType === "cut-end") {
179
- const startIdx = cutStartStack.pop();
180
- if (startIdx !== void 0) cutRanges.push([startIdx, i]);
181
- }
182
- }
183
- let filteredLines = lines;
184
- if (cutBeforeIndex >= 0) {
185
- filteredLines = filteredLines.slice(cutBeforeIndex + 1);
186
- if (cutAfterIndex >= 0) cutAfterIndex = cutAfterIndex - cutBeforeIndex - 1;
187
- for (const range of cutRanges) {
188
- range[0] -= cutBeforeIndex + 1;
189
- range[1] -= cutBeforeIndex + 1;
190
- }
191
- }
192
- if (cutAfterIndex >= 0) filteredLines = filteredLines.slice(0, cutAfterIndex);
193
- const excludedLines = /* @__PURE__ */ new Set();
194
- for (const [start, end] of cutRanges) for (let i = start; i <= end; i++) if (i >= 0 && i < filteredLines.length) excludedLines.add(i);
195
- return filteredLines.filter((line, i) => {
196
- if (excludedLines.has(i)) return false;
197
- const trimmed = line.trim();
198
- if (isTwoslashDirective(trimmed)) return false;
199
- return true;
200
- }).join("\n").trim();
201
- }
202
- /**
203
- * Format import statements with cut directive for hidden imports.
204
- *
205
- * Prepends import statements followed by `// ---cut---` so Twoslash can
206
- * resolve the types but the imports are hidden from rendered output.
207
- *
208
- * @param imports - Import statements to format
209
- * @returns Formatted import block with cut directive, or empty string if no imports
210
- *
211
- * @example
212
- * ```ts
213
- * const imports = [{ packageName: "zod", symbols: new Set(["ZodType"]), typeOnly: true }];
214
- * const block = formatImportsWithCut(imports);
215
- * // Returns:
216
- * // import type { ZodType } from "zod";
217
- * // // ---cut---
218
- * ```
219
- */
220
- function formatImportsWithCut(imports) {
221
- if (imports.length === 0) return "";
222
- return `${TypeReferenceExtractor.formatImports(imports).join("\n")}\n// ---cut---\n`;
223
- }
224
- /**
225
- * Prepend hidden imports to code using the Twoslash cut directive.
226
- *
227
- * This enables type resolution for external types while hiding the import
228
- * statements from rendered output. The existing `stripTwoslashDirectives()`
229
- * function handles removing the cut block for clipboard copying.
230
- *
231
- * @param code - The code to prepend imports to
232
- * @param imports - Import statements to add
233
- * @returns Code with imports prepended (if any), or original code if no imports
234
- *
235
- * @example
236
- * ```ts
237
- * const code = "function foo(): RsbuildPlugin";
238
- * const imports = [{ packageName: "@rsbuild/core", symbols: new Set(["RsbuildPlugin"]), typeOnly: true }];
239
- * const result = prependHiddenImports(code, imports);
240
- * // Returns:
241
- * // import type { RsbuildPlugin } from "@rsbuild/core";
242
- * // // ---cut---
243
- * // function foo(): RsbuildPlugin
244
- * ```
245
- */
246
- function prependHiddenImports(code, imports) {
247
- const importBlock = formatImportsWithCut(imports);
248
- return importBlock ? importBlock + code : code;
249
- }
250
- /**
251
- * Format example code using Prettier for consistent styling.
252
- *
253
- * Wraps the Prettier formatter with error handling and context tracking.
254
- * If formatting fails, returns the original code (fallthrough behavior).
255
- *
256
- * @param code - The code to format
257
- * @param language - The code fence language (e.g., "typescript", "ts")
258
- * @param _context - Optional context (reserved for future use)
259
- * @returns The formatted code (or original if formatting fails)
260
- */
261
- async function formatExampleCode(code, language, _context) {
262
- return (await formatCode(code, language)).code;
263
- }
264
97
 
265
98
  //#endregion
266
- export { escapeMdxGenerics, formatExampleCode, generateAvailableFrom, generateFrontmatter, prepareExampleCode, prependHiddenImports, stripTwoslashDirectives };
99
+ export { generateFrontmatter };
@@ -22,12 +22,10 @@ function render(event) {
22
22
  case "SlowOperation": return `slow ${event.operation}: ${event.durationMs}ms (>${event.threshold}ms)`;
23
23
  case "ConfigCascadeWarning": return event.ignored.length > 2 ? `${event.field}: using '${event.chosen}', ignoring ${event.ignored.length} alternatives (first configured value wins)` : `${event.field}: using '${event.chosen}', ignoring ${event.ignored.join(", ")}`;
24
24
  case "ConfigValidationWarning": return `${event.field}: rejected '${event.value}'${event.reason ? ` — ${event.reason}` : ""}`;
25
- case "DeprecatedConfigUsed": return `option '${event.key}' is deprecated; use ${event.replacement}`;
26
25
  case "ModelLoaded": return `loaded model: ${event.itemCount} items, ${event.entryPoints} entry point(s) (${event.durationMs}ms)`;
27
- case "ConfigResolved": return `resolved ${event.baseRoute}: ${event.categoryCount} categories, ${event.externalCount} external`;
28
26
  case "TwoslashDiagnostic": return `Twoslash TS${event.code} in ${event.file}:${event.line}:${event.col}: ${event.message}`;
29
27
  case "TwoslashCheckFailed": return `Twoslash check failed (TS${event.code}) in ${event.file}; ${event.fsMapKeys.length} VFS files`;
30
- case "TwoslashCacheLoaded": return event.entries > 0 ? `Twoslash cache: restored ${event.entries} cached result(s)` : "Twoslash cache: cold (no cached results for this type environment)";
28
+ case "TwoslashCacheLoaded": return event.degraded ? "Twoslash cache: DEGRADED (unusable cache directory) — every block will be type-checked, every build" : event.entries > 0 ? `Twoslash cache: restored ${event.entries} cached result(s)` : "Twoslash cache: cold (no cached results for this type environment)";
31
29
  case "TwoslashCacheSaved": {
32
30
  const total = event.hits + event.misses;
33
31
  const pct = total > 0 ? Math.round(event.hits / total * 100) : 0;
@@ -100,8 +100,6 @@ function makeMetricsSink(context) {
100
100
  case "PhaseCompleted":
101
101
  update(BuildMetrics.phaseDuration, event.durationMs);
102
102
  both(BuildMetrics.phaseTimeMs, event.durationMs, { phase: event.phase });
103
- break;
104
- case "DefaultApplied": update(BuildMetrics.configDefaultsApplied, 1);
105
103
  }
106
104
  }
107
105
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rspress-plugin-api-extractor",
3
- "version": "0.13.3",
3
+ "version": "0.15.0",
4
4
  "private": false,
5
5
  "description": "RSPress plugin for generating API documentation from TypeScript API Extractor models",
6
6
  "keywords": [
@@ -41,22 +41,24 @@
41
41
  "@effected/github": "^0.8.0",
42
42
  "@effected/glob": "^0.4.0",
43
43
  "@effected/jsonc": "^0.8.1",
44
- "@effected/markdown": "^0.7.0",
44
+ "@effected/markdown": "^0.8.0",
45
45
  "@effected/npm": "^0.12.1",
46
46
  "@effected/package-json": "^0.13.0",
47
47
  "@effected/semver": "^0.5.0",
48
- "@effected/store": "^0.5.0",
49
- "@effected/tsconfig-json": "^0.6.1",
48
+ "@effected/store": "^0.6.0",
49
+ "@effected/tsconfig-json": "^0.7.0",
50
50
  "@effected/walker": "^0.5.0",
51
51
  "@effected/xdg": "^0.3.0",
52
52
  "@effected/yaml": "^0.12.0",
53
53
  "@microsoft/api-extractor-model": "^7.33.11",
54
54
  "@shikijs/twoslash": "^4.4.3",
55
55
  "@tsdoctor/bundle": "0.2.2",
56
- "@tsdoctor/model": "0.4.1",
57
- "@tsdoctor/registry": "0.2.2",
56
+ "@tsdoctor/model": "0.6.0",
57
+ "@tsdoctor/pages": "0.1.0",
58
+ "@tsdoctor/registry": "0.3.1",
58
59
  "@tsdoctor/seo": "0.1.1",
59
- "@tsdoctor/snapshot": "0.2.2",
60
+ "@tsdoctor/snapshot": "0.2.3",
61
+ "@tsdoctor/vfs": "0.2.0",
60
62
  "@typescript/vfs": "^1.6.4",
61
63
  "clsx": "^2.1.1",
62
64
  "effect": "4.0.0-rc.109",
@@ -1,16 +1,6 @@
1
1
  import path from "node:path";
2
2
 
3
3
  //#region src/path-derivation.ts
4
- /** Extract unscoped name from a potentially scoped package name */
5
- function unscopedName(packageName) {
6
- return packageName.startsWith("@") ? packageName.split("/")[1] ?? packageName : packageName;
7
- }
8
- /** Normalize baseRoute: ensure leading slash, strip trailing slash, preserve root "/" */
9
- function normalizeBaseRoute(route) {
10
- const withSlash = route.startsWith("/") ? route : `/${route}`;
11
- const stripped = withSlash.endsWith("/") ? withSlash.slice(0, -1) : withSlash;
12
- return stripped === "" ? "/" : stripped;
13
- }
14
4
  function deriveOutputPaths(input) {
15
5
  const { docsRoot, baseRoute, apiFolder, locales, defaultLang, versions, defaultVersion } = input;
16
6
  const results = [];
@@ -43,24 +33,6 @@ function deriveOutputPaths(input) {
43
33
  }
44
34
  return results;
45
35
  }
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
- }
64
36
 
65
37
  //#endregion
66
- export { apiScopeOf, deriveOutputPaths, normalizeBaseRoute, unscopedName };
38
+ export { deriveOutputPaths };
package/plugin.js CHANGED
@@ -6,9 +6,9 @@ import { clearTypeRoutes } from "./twoslash-transformer.js";
6
6
  import { TwoslashEnvironments } from "./services/TwoslashEnvironments.js";
7
7
  import { VfsRegistry } from "./vfs-registry.js";
8
8
  import { generateApiDocs } from "./build-program.js";
9
- import { deriveOutputPaths, normalizeBaseRoute, unscopedName } from "./path-derivation.js";
10
9
  import { fromDir, fromParentDir } from "./config-helpers.js";
11
10
  import { classifyApiConfig, mergeLlmsPluginConfig } from "./config-utils.js";
11
+ import { deriveOutputPaths } from "./path-derivation.js";
12
12
  import { resolveObservability } from "./schemas/observability.js";
13
13
  import { PluginOptions } from "./schemas/config.js";
14
14
  import { TwoslashCacheService } from "./services/TwoslashCacheService.js";
@@ -27,6 +27,7 @@ import fsSync from "node:fs";
27
27
  import os from "node:os";
28
28
  import path from "node:path";
29
29
  import { fileURLToPath } from "node:url";
30
+ import { normalizeBaseRoute, unscopedName } from "@tsdoctor/pages";
30
31
  import { Effect, FileSystem, ManagedRuntime, Option, Ref, Schema } from "effect";
31
32
 
32
33
  //#region src/plugin.ts
@@ -54,10 +55,8 @@ function ApiExtractorPluginImpl(rawOptions) {
54
55
  const isInert = classifyApiConfig(options) === "disabled";
55
56
  const envLogLevel = process.env.LOG_LEVEL?.toLowerCase();
56
57
  const buildId = `${process.pid}-${performance.now().toString(36)}`;
57
- const { resolved: obs, deprecations } = resolveObservability({
58
+ const { resolved: obs } = resolveObservability({
58
59
  ...options.observability ? { observability: options.observability } : {},
59
- ...options.logLevel ? { logLevel: options.logLevel } : {},
60
- ...options.performance ? { performance: { ...options.performance.thresholds !== void 0 ? { thresholds: options.performance.thresholds } : {} } } : {},
61
60
  ...envLogLevel ? { envLogLevel } : {},
62
61
  cwd: process.cwd(),
63
62
  buildId
@@ -184,12 +183,6 @@ function ApiExtractorPluginImpl(rawOptions) {
184
183
  clearTypeRoutes();
185
184
  fileContextMap.clear();
186
185
  issuesSink.reset();
187
- for (const dep of deprecations) emitSync(PluginEvent.DeprecatedConfigUsed({
188
- ctx: { buildId },
189
- level: "warn",
190
- key: dep.key,
191
- replacement: dep.replacement
192
- }));
193
186
  if (!isInert) try {
194
187
  const rspressConfigSubset = {
195
188
  ...rspressMultiVersion != null ? { multiVersion: rspressMultiVersion } : {},
@@ -1,37 +1,17 @@
1
- import { addLogicalBlankLines } from "./code-post-processor.js";
2
1
  import { PluginEvent } from "./observability/events.js";
3
2
  import { emitSync, syncBuildId } from "./observability/sync-emitter.js";
4
- import { format } from "prettier";
3
+ import { formatExampleCode } from "@tsdoctor/pages";
4
+ import { Effect, Result } from "effect";
5
5
 
6
6
  //#region src/prettier-formatter.ts
7
7
  /* v8 ignore start -- Prettier integration wrapper, tested via page generator integration tests */
8
8
  /**
9
- * Map code fence languages to Prettier parsers
10
- */
11
- const LANGUAGE_TO_PARSER = {
12
- typescript: "typescript",
13
- ts: "typescript",
14
- tsx: "typescript",
15
- javascript: "babel",
16
- js: "babel",
17
- jsx: "babel",
18
- node: "babel"
19
- };
20
- /**
21
- * Default Prettier options for consistent formatting
22
- */
23
- const PRETTIER_OPTIONS = {
24
- printWidth: 80,
25
- tabWidth: 2,
26
- useTabs: false,
27
- semi: true,
28
- singleQuote: false,
29
- trailingComma: "es5",
30
- bracketSpacing: true,
31
- arrowParens: "always"
32
- };
33
- /**
34
- * Format code using Prettier
9
+ * Format code using Prettier.
10
+ *
11
+ * The formatting itself is `formatExampleCode` in `@tsdoctor/pages`, so both
12
+ * adapters format identically. This wrapper keeps the adapter's fallthrough
13
+ * contract: a typed `ExampleFormatError` becomes a `PrettierError` event on
14
+ * the bus and the original code is returned.
35
15
  *
36
16
  * @param code - The code to format
37
17
  * @param language - The code fence language (e.g., "typescript", "ts", "js")
@@ -39,39 +19,27 @@ const PRETTIER_OPTIONS = {
39
19
  */
40
20
  async function formatCode(code, language) {
41
21
  const start = performance.now();
42
- const parser = LANGUAGE_TO_PARSER[language.toLowerCase()];
43
- if (!parser) return {
44
- code,
22
+ const result = await Effect.runPromise(Effect.result(formatExampleCode(code, language)));
23
+ const formatTime = performance.now() - start;
24
+ if (Result.isSuccess(result)) return {
25
+ code: result.success,
45
26
  success: true,
46
- formatTime: performance.now() - start
27
+ formatTime
28
+ };
29
+ const cause = result.failure.cause;
30
+ const errorMsg = cause instanceof Error ? cause.message : String(cause);
31
+ emitSync(PluginEvent.PrettierError({
32
+ ctx: { buildId: syncBuildId() },
33
+ file: "unknown",
34
+ reason: errorMsg,
35
+ level: "warn"
36
+ }));
37
+ return {
38
+ code,
39
+ success: false,
40
+ error: errorMsg,
41
+ formatTime
47
42
  };
48
- try {
49
- const formatted = await format(code, {
50
- ...PRETTIER_OPTIONS,
51
- parser
52
- });
53
- const formatTime = performance.now() - start;
54
- return {
55
- code: addLogicalBlankLines(formatted.trim()),
56
- success: true,
57
- formatTime
58
- };
59
- } catch (error) {
60
- const formatTime = performance.now() - start;
61
- const errorMsg = error instanceof Error ? error.message : String(error);
62
- emitSync(PluginEvent.PrettierError({
63
- ctx: { buildId: syncBuildId() },
64
- file: "unknown",
65
- reason: errorMsg,
66
- level: "warn"
67
- }));
68
- return {
69
- code,
70
- success: false,
71
- error: errorMsg,
72
- formatTime
73
- };
74
- }
75
43
  }
76
44
 
77
45
  //#endregion
@@ -1,15 +1,16 @@
1
1
  import { PluginEvent } from "./observability/events.js";
2
2
  import { emitSync, syncBuildId, syncSlowCodeBlockMs } from "./observability/sync-emitter.js";
3
- import { formatCode } from "./prettier-formatter.js";
4
- import { stripTwoslashDirectives } from "./markdown/helpers.js";
5
3
  import { DEFAULT_SHIKI_THEMES } from "./markdown/shiki-utils.js";
6
4
  import { VfsRegistry } from "./vfs-registry.js";
7
5
  import { setTwoslashFile } from "./twoslash-access.js";
8
6
  import { createTwoslashTimingWrapper } from "./twoslash-timing-wrapper.js";
7
+ import { formatCode } from "./prettier-formatter.js";
8
+ import { stripTwoslashDirectives } from "@tsdoctor/pages";
9
9
  import { codeToHast, hastToHtml } from "shiki";
10
10
  import { visit } from "unist-util-visit";
11
11
 
12
12
  //#region src/remark-with-api.ts
13
+ /* v8 ignore start -- remark plugin, requires MDX compilation context */
13
14
  /**
14
15
  * Supported languages for with-api code blocks
15
16
  * Based on GitHub Linguist standard aliases: