rspress-plugin-api-extractor 0.1.2 → 0.2.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 (109) hide show
  1. package/README.md +2 -2
  2. package/api-extracted-package.js +426 -0
  3. package/build-program.js +120 -0
  4. package/build-stages.js +664 -0
  5. package/category-resolver.js +50 -0
  6. package/code-post-processor.js +38 -0
  7. package/config-helpers.js +106 -0
  8. package/config-utils.js +258 -0
  9. package/content-hash.js +79 -0
  10. package/errors.js +29 -0
  11. package/formatter.js +69 -0
  12. package/hide-cut-transformer.js +96 -0
  13. package/index.d.ts +901 -831
  14. package/index.js +4 -6128
  15. package/layers/ConfigServiceLive.js +377 -0
  16. package/layers/ObservabilityLive.js +136 -0
  17. package/layers/PathDerivationServiceLive.js +16 -0
  18. package/layers/SnapshotServiceLive.js +94 -0
  19. package/layers/TypeRegistryServiceLive.js +46 -0
  20. package/llms-processing.js +270 -0
  21. package/llms-program.js +262 -0
  22. package/loader.js +186 -0
  23. package/markdown/cross-linker.js +156 -0
  24. package/markdown/helpers.js +364 -0
  25. package/markdown/index.js +11 -0
  26. package/markdown/page-generators/class-page.js +357 -0
  27. package/markdown/page-generators/enum-page.js +152 -0
  28. package/markdown/page-generators/function-page.js +127 -0
  29. package/markdown/page-generators/index-pages.js +27 -0
  30. package/markdown/page-generators/interface-page.js +307 -0
  31. package/markdown/page-generators/namespace-page.js +280 -0
  32. package/markdown/page-generators/type-alias-page.js +110 -0
  33. package/markdown/page-generators/variable-page.js +110 -0
  34. package/markdown/shiki-utils.js +48 -0
  35. package/migrations/001_create_snapshots.js +25 -0
  36. package/model-loader.js +95 -0
  37. package/multi-entry-resolver.js +70 -0
  38. package/og-resolver.js +271 -0
  39. package/package.json +63 -73
  40. package/path-derivation.js +48 -0
  41. package/plugin.js +218 -0
  42. package/prettier-formatter.js +73 -0
  43. package/public/tsconfig/rspress.json +44 -0
  44. package/remark-api-codeblocks.js +130 -0
  45. package/remark-with-api.js +172 -0
  46. package/route-collisions.js +52 -0
  47. package/runtime/components/ApiExample/index.js +29 -25
  48. package/runtime/components/ApiLlmsPackageActions/index.js +252 -323
  49. package/runtime/components/ApiLlmsViewOptions/index.js +269 -340
  50. package/runtime/components/ApiMember/index.js +49 -47
  51. package/runtime/components/ApiSignature/index.js +32 -28
  52. package/runtime/components/EnumMembersTable/{index_module.css → index.css} +18 -19
  53. package/runtime/components/EnumMembersTable/index.js +36 -67
  54. package/runtime/components/EnumMembersTable/index.module.js +9 -6
  55. package/runtime/components/ExampleBlock/{index_module.css → index.css} +2 -3
  56. package/runtime/components/ExampleBlock/index.js +23 -28
  57. package/runtime/components/ExampleBlock/index.module.js +8 -5
  58. package/runtime/components/MarkdownContent/index.js +26 -18
  59. package/runtime/components/MarkdownText/index.js +28 -22
  60. package/runtime/components/MemberSignature/{index_module.css → index.css} +5 -6
  61. package/runtime/components/MemberSignature/index.js +46 -46
  62. package/runtime/components/MemberSignature/index.module.js +9 -6
  63. package/runtime/components/ParametersTable/{index_module.css → index.css} +19 -20
  64. package/runtime/components/ParametersTable/index.js +36 -67
  65. package/runtime/components/ParametersTable/index.module.js +9 -6
  66. package/runtime/components/SignatureBlock/{index_module.css → index.css} +5 -6
  67. package/runtime/components/SignatureBlock/index.js +30 -29
  68. package/runtime/components/SignatureBlock/index.module.js +9 -6
  69. package/runtime/components/SignatureCode/{index_module.css → index.css} +9 -10
  70. package/runtime/components/SignatureCode/index.js +40 -32
  71. package/runtime/components/SignatureCode/index.module.js +9 -6
  72. package/runtime/components/SignatureToolbar/{index_module.css → index.css} +18 -20
  73. package/runtime/components/SignatureToolbar/index.js +48 -51
  74. package/runtime/components/SignatureToolbar/index.module.js +13 -10
  75. package/runtime/components/buttons/ButtonGroup.js +13 -6
  76. package/runtime/components/buttons/CopyCodeButton.js +40 -38
  77. package/runtime/components/buttons/WrapSignatureButton.js +20 -16
  78. package/runtime/components/buttons/{index_module.css → index.css} +5 -6
  79. package/runtime/components/buttons/index.module.js +8 -5
  80. package/runtime/components/icons/CheckIcon/index.js +20 -17
  81. package/runtime/components/icons/CopyIcon/index.js +20 -17
  82. package/runtime/components/icons/UnwrapIcon/index.js +22 -18
  83. package/runtime/components/icons/WrapIcon/index.js +20 -17
  84. package/runtime/components/shared/_twoslash.css +3 -10
  85. package/runtime/components/shared/variables.css +0 -3
  86. package/runtime/hooks/useWrapToggle.js +32 -9
  87. package/runtime/index.d.ts +513 -173
  88. package/runtime/index.js +11 -9
  89. package/runtime/utils/decode-hast.js +31 -16
  90. package/runtime/utils/hast-renderer.js +21 -7
  91. package/schemas/config.js +199 -0
  92. package/schemas/index.js +5 -0
  93. package/schemas/opengraph.js +26 -0
  94. package/schemas/performance.js +19 -0
  95. package/serve.js +133 -0
  96. package/services/ConfigService.js +7 -0
  97. package/services/PathDerivationService.js +7 -0
  98. package/services/SnapshotService.js +7 -0
  99. package/services/TypeRegistryService.js +7 -0
  100. package/shiki-transformer.js +758 -0
  101. package/tsconfig-parser.js +127 -0
  102. package/tsdoc-metadata.json +11 -11
  103. package/twoslash-patterns.js +87 -0
  104. package/twoslash-transformer.js +316 -0
  105. package/type-reference-extractor.js +201 -0
  106. package/typescript-config.js +168 -0
  107. package/vfs-registry.js +121 -0
  108. package/0~llms-program.js +0 -344
  109. package/runtime/components/ApiLlmsPackageActions/index.module.js +0 -2
package/loader.js ADDED
@@ -0,0 +1,186 @@
1
+ import { ApiDocumentedItem, ApiItemKind } from "@microsoft/api-extractor-model";
2
+ import { extractPlainText, getDeprecation, getExamples, getParams, getReleaseTag, getReturns, getSummary, hasModifierTag } from "api-extractor-llms";
3
+
4
+ //#region src/loader.ts
5
+ /**
6
+ * Parser for extracting and analyzing information from API Extractor models and TSDoc comments
7
+ */
8
+ var ApiParser = class ApiParser {
9
+ /**
10
+ * Private constructor to prevent instantiation
11
+ */
12
+ constructor() {}
13
+ /**
14
+ * Check if an API item has a custom modifier tag
15
+ */
16
+ static hasModifierTag(item, tagName) {
17
+ return hasModifierTag(item, tagName);
18
+ }
19
+ /**
20
+ * Extract all API items from a package (or resolved entry items) and categorize them based on configuration.
21
+ *
22
+ * When passed a `ResolvedEntryItem[]`, uses the items directly (multi-entry support).
23
+ * When passed an `ApiPackage`, reads from `entryPoints[0]` (legacy single-entry behavior).
24
+ */
25
+ static categorizeApiItems(source, categories) {
26
+ const items = {};
27
+ for (const categoryKey of Object.keys(categories)) items[categoryKey] = [];
28
+ let members;
29
+ if (Array.isArray(source)) members = source.map((r) => r.item);
30
+ else {
31
+ const entryPoint = source.entryPoints[0];
32
+ if (!entryPoint) return items;
33
+ members = entryPoint.members;
34
+ }
35
+ const sortedCategories = Object.entries(categories).sort((a, b) => {
36
+ const [, configA] = a;
37
+ const [, configB] = b;
38
+ if (configA.tsdocModifier && !configB.tsdocModifier) return -1;
39
+ if (!configA.tsdocModifier && configB.tsdocModifier) return 1;
40
+ return 0;
41
+ });
42
+ for (const member of members) {
43
+ let categorized = false;
44
+ for (const [categoryKey, config] of sortedCategories) {
45
+ if (config.tsdocModifier && ApiParser.hasModifierTag(member, config.tsdocModifier)) {
46
+ items[categoryKey].push(member);
47
+ categorized = true;
48
+ break;
49
+ }
50
+ if (config.itemKinds?.includes(member.kind)) {
51
+ items[categoryKey].push(member);
52
+ categorized = true;
53
+ break;
54
+ }
55
+ }
56
+ if (!categorized && typeof process !== "undefined" && !process.env.VITEST) console.warn(`⚠️ API item "${member.displayName}" (kind: ${member.kind}) not categorized`);
57
+ }
58
+ return items;
59
+ }
60
+ /**
61
+ * Extract all members from namespaces in a package (or resolved entry items).
62
+ * Returns a flat list of namespace members with their qualified names.
63
+ *
64
+ * When passed a `ResolvedEntryItem[]`, scans those items for namespaces (multi-entry support).
65
+ * When passed an `ApiPackage`, reads from `entryPoints[0]` (legacy single-entry behavior).
66
+ *
67
+ * @param source - The API package or resolved entry items to extract from
68
+ * @returns Array of namespace members with qualified names
69
+ */
70
+ static extractNamespaceMembers(source) {
71
+ const members = [];
72
+ let topLevelItems;
73
+ if (Array.isArray(source)) topLevelItems = source.map((r) => r.item);
74
+ else {
75
+ const entryPoint = source.entryPoints[0];
76
+ if (!entryPoint) return members;
77
+ topLevelItems = entryPoint.members;
78
+ }
79
+ for (const item of topLevelItems) if (item.kind === ApiItemKind.Namespace) {
80
+ const namespace = item;
81
+ for (const member of namespace.members) members.push({
82
+ item: member,
83
+ namespace,
84
+ qualifiedName: `${namespace.displayName}.${member.displayName}`
85
+ });
86
+ }
87
+ return members;
88
+ }
89
+ /**
90
+ * Extract plain text from a TSDoc DocNode tree (prose form).
91
+ *
92
+ * Delegates to api-extractor-llms `extractPlainText`. Used internally for
93
+ * `@see` reference text, where `{@link}` targets are flattened to display text.
94
+ */
95
+ static extractPlainText(node) {
96
+ return extractPlainText(node);
97
+ }
98
+ /**
99
+ * Get the summary text from an API item's TSDoc comment
100
+ */
101
+ static getSummary(item) {
102
+ return getSummary(item);
103
+ }
104
+ /**
105
+ * Get the release tag (public, beta, alpha, internal) from an API item
106
+ */
107
+ static getReleaseTag(item) {
108
+ return getReleaseTag(item);
109
+ }
110
+ /**
111
+ * Get parameter documentation from an API item's TSDoc comment
112
+ */
113
+ static getParams(item) {
114
+ return getParams(item);
115
+ }
116
+ /**
117
+ * Get return value documentation from an API item's TSDoc comment
118
+ */
119
+ static getReturns(item) {
120
+ return getReturns(item);
121
+ }
122
+ /**
123
+ * Get code examples from an API item's TSDoc comment
124
+ */
125
+ static getExamples(item) {
126
+ return getExamples(item);
127
+ }
128
+ /**
129
+ * Get deprecation message from an API item's TSDoc comment
130
+ */
131
+ static getDeprecation(item) {
132
+ return getDeprecation(item);
133
+ }
134
+ /**
135
+ * Get inheritance information from a class or interface
136
+ */
137
+ static getInheritance(item) {
138
+ const result = {};
139
+ if (item.kind === ApiItemKind.Class) {
140
+ const apiClass = item;
141
+ if (apiClass.extendsType) result.extends = [apiClass.extendsType.excerpt.text];
142
+ const implementsTypes = apiClass.implementsTypes || [];
143
+ if (implementsTypes.length > 0) result.implements = implementsTypes.map((type) => type.excerpt.text);
144
+ } else if (item.kind === ApiItemKind.Interface) {
145
+ const extendsTypes = item.extendsTypes || [];
146
+ if (extendsTypes.length > 0) result.extends = extendsTypes.map((type) => type.excerpt.text);
147
+ }
148
+ return result;
149
+ }
150
+ /**
151
+ * Get see also references from an API item's TSDoc comment
152
+ */
153
+ static getSeeReferences(item) {
154
+ if (item instanceof ApiDocumentedItem) {
155
+ const tsdoc = item.tsdocComment;
156
+ const references = [];
157
+ for (const seeBlock of tsdoc?.seeBlocks || []) {
158
+ const content = seeBlock.content;
159
+ const text = ApiParser.extractPlainText(content);
160
+ if (text.trim()) references.push({ text: text.replace(/\s+/g, " ").trim() });
161
+ }
162
+ return references;
163
+ }
164
+ return [];
165
+ }
166
+ /**
167
+ * Get source code link for an API item
168
+ * @param item - The API item
169
+ * @param sourceConfig - Source configuration with repository URL and ref
170
+ * @returns Source code URL with line number, or null if not available
171
+ */
172
+ static getSourceLink(item, sourceConfig) {
173
+ if (!sourceConfig) return null;
174
+ const itemAny = item;
175
+ const filePath = itemAny.fileUrlPath || itemAny.filePath;
176
+ if (!filePath) return null;
177
+ const lineNumber = itemAny.fileLineNumber || itemAny.line;
178
+ const ref = sourceConfig.ref || "blob/main";
179
+ const baseUrl = `${sourceConfig.url}/${ref}`;
180
+ if (lineNumber) return `${baseUrl}/${filePath}#L${lineNumber}`;
181
+ return `${baseUrl}/${filePath}`;
182
+ }
183
+ };
184
+
185
+ //#endregion
186
+ export { ApiParser };
@@ -0,0 +1,156 @@
1
+ import { CrossLinker } from "api-extractor-llms";
2
+
3
+ //#region src/markdown/cross-linker.ts
4
+ /**
5
+ * A cross-linking utility for markdown API documentation.
6
+ *
7
+ * This class maintains a mapping of API item names to their documentation routes,
8
+ * enabling automatic cross-linking of type references in markdown content. It supports
9
+ * both top-level exports and class/interface members.
10
+ *
11
+ * **How it works:**
12
+ * 1. During initialization, it builds a route map from all API items in a package
13
+ * 2. For classes and interfaces, it also maps their members (e.g., `ClassName.methodName`)
14
+ * 3. When processing text, it replaces type names with markdown or HTML links
15
+ *
16
+ * **Relationships:**
17
+ * - Initialized by {@link ApiExtractorPlugin} with categorized API items
18
+ * - Used by page generators to add cross-links in documentation text
19
+ * - Provides route/kind data to {@link ShikiCrossLinker} for code block linking
20
+ *
21
+ * **Link Formats:**
22
+ * - Markdown: `[TypeName](/path/to/type)` - for use in .mdx content
23
+ * - HTML: anchor tags with href - for use in JSX/components
24
+ *
25
+ * @example Initialization
26
+ * ```ts
27
+ * const crossLinker = new MarkdownCrossLinker();
28
+ * const { routes, kinds } = crossLinker.initialize(
29
+ * categorizedItems,
30
+ * "/api/my-package",
31
+ * categories
32
+ * );
33
+ * ```
34
+ *
35
+ * @example Adding cross-links
36
+ * ```ts
37
+ * // Markdown format
38
+ * const text = crossLinker.addCrossLinks("Returns a MyClass instance");
39
+ * // Result: "Returns a [MyClass](/api/my-package/class/myclass) instance"
40
+ *
41
+ * // HTML format (for JSX)
42
+ * const html = crossLinker.addCrossLinksHtml("Returns a MyClass instance");
43
+ * // Result: "Returns a anchor-linked MyClass instance"
44
+ * ```
45
+ *
46
+ * @see {@link ShikiCrossLinker} for code block cross-linking
47
+ */
48
+ var MarkdownCrossLinker = class {
49
+ /**
50
+ * Map of API item names to their route paths for cross-linking
51
+ */
52
+ apiItemRoutes = /* @__PURE__ */ new Map();
53
+ /**
54
+ * Clear all accumulated routes. Call at the start of each build.
55
+ */
56
+ clear() {
57
+ this.apiItemRoutes.clear();
58
+ }
59
+ /**
60
+ * Add routes for API items. Accumulates across multiple calls.
61
+ * Call clear() first if starting a fresh build.
62
+ * @returns Object with routes map and kinds map for semantic highlighting
63
+ */
64
+ addRoutes(items, baseRoute, categories) {
65
+ const apiItemKinds = /* @__PURE__ */ new Map();
66
+ for (const [categoryKey, categoryConfig] of Object.entries(categories)) {
67
+ const categoryItems = items[categoryKey] || [];
68
+ for (const item of categoryItems) {
69
+ const itemRoute = `${baseRoute}/${categoryConfig.folderName}/${item.displayName.toLowerCase()}`;
70
+ this.apiItemRoutes.set(item.displayName, itemRoute);
71
+ apiItemKinds.set(item.displayName, item.kind);
72
+ if ((item.kind === "Class" || item.kind === "Interface") && item.members) for (const member of item.members) {
73
+ const memberName = member.displayName;
74
+ const memberId = this.sanitizeId(memberName);
75
+ const fullMemberName = `${item.displayName}.${memberName}`;
76
+ const memberRoute = `${itemRoute}#${memberId}`;
77
+ this.apiItemRoutes.set(fullMemberName, memberRoute);
78
+ apiItemKinds.set(fullMemberName, member.kind);
79
+ }
80
+ }
81
+ }
82
+ return {
83
+ routes: this.apiItemRoutes,
84
+ kinds: apiItemKinds
85
+ };
86
+ }
87
+ /**
88
+ * Initialize the cross-link map with all API items.
89
+ * @deprecated Use clear() + addRoutes() instead.
90
+ * @returns Object with routes map and kinds map for semantic highlighting
91
+ */
92
+ initialize(items, baseRoute, categories) {
93
+ this.clear();
94
+ return this.addRoutes(items, baseRoute, categories);
95
+ }
96
+ /**
97
+ * Set routes directly from pre-built route maps (e.g., from prepareWorkItems).
98
+ * Replaces all existing routes.
99
+ */
100
+ setRoutes(routes) {
101
+ this.apiItemRoutes.clear();
102
+ for (const [name, route] of routes) this.apiItemRoutes.set(name, route);
103
+ }
104
+ /**
105
+ * Add cross-links to type references in code (markdown format).
106
+ *
107
+ * Skips matches inside backtick code spans and existing markdown links.
108
+ */
109
+ addCrossLinks(text) {
110
+ if (this.apiItemRoutes.size === 0) return text;
111
+ return new CrossLinker(Array.from(this.apiItemRoutes.keys()).map((name) => ({
112
+ name,
113
+ kind: "type",
114
+ slug: name.toLowerCase()
115
+ })), (ref) => this.apiItemRoutes.get(ref.name) ?? "").addLinks(text);
116
+ }
117
+ /**
118
+ * Add cross-links to type references in code (HTML format)
119
+ * Use this when the text will be rendered as HTML (e.g., in React components)
120
+ *
121
+ * Note: intentionally hand-rolled and test-only — the upstream library's
122
+ * CrossLinker emits markdown links only, so the HTML path has no library
123
+ * equivalent.
124
+ */
125
+ addCrossLinksHtml(text) {
126
+ let result = text;
127
+ const sortedNames = Array.from(this.apiItemRoutes.keys()).sort((a, b) => b.length - a.length);
128
+ for (const name of sortedNames) {
129
+ const route = this.apiItemRoutes.get(name);
130
+ if (!route) continue;
131
+ const regex = new RegExp(`\\b${name}\\b(?![a-zA-Z])`, "g");
132
+ result = result.replace(regex, (match, offset) => {
133
+ const beforeMatch = result.substring(0, offset);
134
+ if (beforeMatch.includes("<a") && !beforeMatch.includes("</a>")) return match;
135
+ return `<a href="${route}">${match}</a>`;
136
+ });
137
+ }
138
+ return result;
139
+ }
140
+ /**
141
+ * Sanitize a display name to create a valid HTML ID
142
+ * Converts to lowercase, replaces spaces/special chars with hyphens
143
+ */
144
+ sanitizeId(displayName, prefix = "") {
145
+ const sanitized = displayName.toLowerCase().replace(/[\s_]+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/^-+|-+$/g, "");
146
+ return prefix ? `${prefix}-${sanitized}` : sanitized;
147
+ }
148
+ };
149
+ /**
150
+ * Module-level instance used by internal generator functions.
151
+ * External callers should create their own instance or use this one.
152
+ */
153
+ const markdownCrossLinker = new MarkdownCrossLinker();
154
+
155
+ //#endregion
156
+ export { MarkdownCrossLinker, markdownCrossLinker };
@@ -0,0 +1,364 @@
1
+ import { classifyCutDirective, isTwoslashDirective } from "../twoslash-patterns.js";
2
+ import { formatCode } from "../prettier-formatter.js";
3
+ import { TypeReferenceExtractor } from "../type-reference-extractor.js";
4
+
5
+ //#region src/markdown/helpers.ts
6
+ /* v8 ignore start -- markdown generation helpers, tested via page generator integration tests */
7
+ /**
8
+ * Helper utilities for generating markdown API documentation.
9
+ *
10
+ * This module provides shared utility functions used by the page generators
11
+ * for common tasks like preparing Twoslash examples, generating frontmatter,
12
+ * escaping special characters, and sanitizing IDs.
13
+ *
14
+ * @packageDocumentation
15
+ */
16
+ /**
17
+ * Generate an "Available from" line for items exported from multiple entry points.
18
+ * Returns empty string if only one entry point or none provided.
19
+ */
20
+ function generateAvailableFrom(packageName, availableFrom) {
21
+ if (!availableFrom || availableFrom.length <= 1) return "";
22
+ return `Available from: ${availableFrom.map((ep) => ep === "default" ? `\`${packageName}\`` : `\`${packageName}/${ep}\``).join(", ")}\n\n`;
23
+ }
24
+ /**
25
+ * Prepare example code for Twoslash rendering.
26
+ *
27
+ * Prepares the code with imports and error directives but does NOT render HTML.
28
+ * Use this for raw markdown output or as input to pre-rendering.
29
+ *
30
+ * @param example - The example with language and code
31
+ * @param apiItemName - The name of the API item being documented
32
+ * @param packageName - The package name for imports
33
+ * @param suppressErrors - Whether to suppress all TypeScript errors (default: true)
34
+ * @returns Object with prepared code and whether it's TypeScript
35
+ */
36
+ function prepareExampleCode(example, apiItemName, packageName, suppressErrors = true) {
37
+ const { language, code } = example;
38
+ if (!(language === "typescript" || language === "ts" || language === "javascript" || language === "js")) return {
39
+ code,
40
+ isTypeScript: false,
41
+ language
42
+ };
43
+ const importLine = `import { ${apiItemName} } from "${packageName}";`;
44
+ const finalCode = code.includes(`from "${packageName}"`) || code.includes(`from '${packageName}'`) ? code : `${importLine}\n${code}`;
45
+ return {
46
+ code: `${suppressErrors ? "// @noErrors\n" : ""}${finalCode}`,
47
+ isTypeScript: true,
48
+ language: "typescript"
49
+ };
50
+ }
51
+ /**
52
+ * Sanitize a display name to create a URL-safe HTML ID.
53
+ *
54
+ * Converts a display name (e.g., method or property name) into a valid
55
+ * HTML ID suitable for anchor links. Handles special characters, quotes,
56
+ * and optionally adds a prefix for disambiguation.
57
+ *
58
+ * @param displayName - The original display name
59
+ * @param prefix - Optional prefix to add (e.g., "static-property")
60
+ * @returns URL-safe ID string
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * sanitizeId("myMethod"); // "mymethod"
65
+ * sanitizeId("get value"); // "get-value"
66
+ * sanitizeId("run", "static"); // "static-run"
67
+ * ```
68
+ */
69
+ function sanitizeId(displayName, prefix = "") {
70
+ const baseName = displayName.replace(/["']/g, "").replace(/[^\w-]/g, "-").replace(/^-+|-+$/g, "").toLowerCase();
71
+ return prefix ? `${prefix}-${baseName}` : baseName;
72
+ }
73
+ /**
74
+ * Escape a YAML string value by handling special characters.
75
+ *
76
+ * Normalizes whitespace and wraps strings in double quotes if they contain
77
+ * characters that could break YAML parsing (colons, quotes, hashes, pipes,
78
+ * brackets, braces, Unicode characters, etc.).
79
+ *
80
+ * @param value - The string value to escape
81
+ * @returns YAML-safe string
82
+ *
83
+ * @example
84
+ * ```ts
85
+ * escapeYamlString("Hello World"); // "Hello World"
86
+ * escapeYamlString("Type: string"); // "\"Type: string\""
87
+ * escapeYamlString("He said \"hello\""); // "\"He said \\\"hello\\\"\""
88
+ * escapeYamlString("@pkg/name。:"); // "\"@pkg/name。:\""
89
+ * ```
90
+ */
91
+ function escapeYamlString(value) {
92
+ const cleaned = value.replace(/[\r\n]+/g, " ").replace(/\s+/g, " ").trim();
93
+ if (/["':#|>&*!%@`[\]{},?-]/.test(cleaned) || /[\u0080-\uFFFF]/.test(cleaned) || /^(true|false|null|~|yes|no|on|off)$/i.test(cleaned) || /^-?\d+(\.\d+)?([eE][+-]?\d+)?$/.test(cleaned)) return `"${cleaned.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
94
+ return cleaned;
95
+ }
96
+ /**
97
+ * Escape generic type parameters in MDX by wrapping them in backticks.
98
+ *
99
+ * Prevents MDX from interpreting `<T>`, `<TEnv>`, etc. as JSX tags by
100
+ * wrapping them in inline code backticks.
101
+ *
102
+ * @param text - The text containing generic type parameters
103
+ * @returns Text with generics wrapped in backticks
104
+ *
105
+ * @example
106
+ * ```ts
107
+ * escapeMdxGenerics("Returns Promise<T>"); // "Returns Promise`<T>`"
108
+ * escapeMdxGenerics("Map<K, V> extends..."); // "Map`<K, V>` extends..."
109
+ * escapeMdxGenerics("`Pipeline<I, O>`"); // "`Pipeline<I, O>`" (unchanged)
110
+ * ```
111
+ */
112
+ function escapeMdxGenerics(text) {
113
+ return text.split(/(`[^`]+`)/g).map((part) => {
114
+ if (part.startsWith("`") && part.endsWith("`")) return part;
115
+ return part.replace(/<([A-Z][A-Za-z0-9_]*(?:\s+extends\s+[^>]+)?(?:,\s*[A-Z][A-Za-z0-9_]*(?:\s+extends\s+[^>]+)?)*)>/g, "`<$1>`");
116
+ }).join("");
117
+ }
118
+ /**
119
+ * Build a structured page title for API documentation.
120
+ *
121
+ * Creates a title in the format: `{entityName} | {singularName} | API | {apiName}`
122
+ *
123
+ * @param entityName - The specific entity name (e.g., "MyClass")
124
+ * @param singularName - The category singular name (e.g., "Class")
125
+ * @param apiName - Optional API/package display name
126
+ * @returns Formatted page title
127
+ *
128
+ * @example
129
+ * ```ts
130
+ * buildPageTitle("MyClass", "Class", "My Package");
131
+ * // Returns: "MyClass | Class | API | My Package"
132
+ * ```
133
+ */
134
+ function buildPageTitle(entityName, singularName, apiName) {
135
+ const parts = [
136
+ entityName,
137
+ singularName,
138
+ "API"
139
+ ];
140
+ if (apiName) parts.push(apiName);
141
+ return parts.join(" | ");
142
+ }
143
+ /**
144
+ * Generate markdown frontmatter with optional Open Graph metadata.
145
+ *
146
+ * Creates YAML frontmatter for MDX files including title, description,
147
+ * and comprehensive Open Graph meta tags for social sharing.
148
+ *
149
+ * @param entityName - The specific entity name (e.g., "MyClass")
150
+ * @param description - Page description for SEO
151
+ * @param singularName - The category singular name (e.g., "Class")
152
+ * @param apiName - Optional API/package display name
153
+ * @param ogMetadata - Optional Open Graph metadata for social sharing
154
+ * @returns YAML frontmatter string
155
+ *
156
+ * @example
157
+ * ```ts
158
+ * const frontmatter = generateFrontmatter(
159
+ * "MyClass",
160
+ * "A utility class for...",
161
+ * "Class",
162
+ * "My Package"
163
+ * );
164
+ * // Returns:
165
+ * // ---
166
+ * // title: MyClass | Class | API | My Package
167
+ * // description: A utility class for...
168
+ * // ---
169
+ * ```
170
+ */
171
+ function generateFrontmatter(entityName, description, singularName, apiName, ogMetadata) {
172
+ const title = buildPageTitle(entityName, singularName, apiName);
173
+ const headEntries = [];
174
+ if (ogMetadata) {
175
+ headEntries.push(["meta", {
176
+ property: "og:url",
177
+ content: `${ogMetadata.siteUrl}${ogMetadata.pageRoute}`
178
+ }]);
179
+ headEntries.push(["meta", {
180
+ property: "og:type",
181
+ content: ogMetadata.ogType
182
+ }]);
183
+ headEntries.push(["meta", {
184
+ property: "og:description",
185
+ content: ogMetadata.description
186
+ }]);
187
+ if (ogMetadata.ogImage) {
188
+ headEntries.push(["meta", {
189
+ property: "og:image",
190
+ content: ogMetadata.ogImage.url
191
+ }]);
192
+ if (ogMetadata.ogImage.secureUrl) headEntries.push(["meta", {
193
+ property: "og:image:secure_url",
194
+ content: ogMetadata.ogImage.secureUrl
195
+ }]);
196
+ if (ogMetadata.ogImage.type) headEntries.push(["meta", {
197
+ property: "og:image:type",
198
+ content: ogMetadata.ogImage.type
199
+ }]);
200
+ if (ogMetadata.ogImage.width) headEntries.push(["meta", {
201
+ property: "og:image:width",
202
+ content: String(ogMetadata.ogImage.width)
203
+ }]);
204
+ if (ogMetadata.ogImage.height) headEntries.push(["meta", {
205
+ property: "og:image:height",
206
+ content: String(ogMetadata.ogImage.height)
207
+ }]);
208
+ if (ogMetadata.ogImage.alt) headEntries.push(["meta", {
209
+ property: "og:image:alt",
210
+ content: ogMetadata.ogImage.alt
211
+ }]);
212
+ }
213
+ headEntries.push(["meta", {
214
+ property: "article:published_time",
215
+ content: ogMetadata.publishedTime
216
+ }]);
217
+ headEntries.push(["meta", {
218
+ property: "article:modified_time",
219
+ content: ogMetadata.modifiedTime
220
+ }]);
221
+ headEntries.push(["meta", {
222
+ property: "article:section",
223
+ content: ogMetadata.section
224
+ }]);
225
+ for (const tag of ogMetadata.tags) headEntries.push(["meta", {
226
+ property: "article:tag",
227
+ content: tag
228
+ }]);
229
+ }
230
+ let frontmatter = `---\ntitle: ${escapeYamlString(title)}\n`;
231
+ frontmatter += `description: ${escapeYamlString(description)}\n`;
232
+ if (headEntries.length > 0) {
233
+ frontmatter += "head:\n";
234
+ for (const [tag, attrs] of headEntries) {
235
+ frontmatter += ` - - ${tag}\n`;
236
+ frontmatter += " - ";
237
+ const attrPairs = Object.entries(attrs).map(([key, value]) => `${key}: ${escapeYamlString(value)}`);
238
+ frontmatter += attrPairs.join("\n ");
239
+ frontmatter += "\n";
240
+ }
241
+ }
242
+ frontmatter += "---\n\n";
243
+ return frontmatter;
244
+ }
245
+ /**
246
+ * Strip Twoslash directives from code for display purposes.
247
+ *
248
+ * Removes Twoslash directive comments like `// @noErrors`, `// @errors: 2304`,
249
+ * `// @filename: ...`, etc. from code so users see clean output and don't
250
+ * copy directives when using the copy button.
251
+ *
252
+ * Also handles cut directives:
253
+ * - `// ---cut---` - Removes this line and all lines before it
254
+ * - `// ---cut-before---` - Same as ---cut---
255
+ * - `// ---cut-after---` - Removes this line and all lines after it
256
+ *
257
+ * @param code - The code containing Twoslash directives
258
+ * @returns Code with Twoslash directives removed
259
+ *
260
+ * @example
261
+ * ```ts
262
+ * const display = stripTwoslashDirectives("// @noErrors\nconst x = 1;");
263
+ * // Returns: "const x = 1;"
264
+ * ```
265
+ */
266
+ function stripTwoslashDirectives(code) {
267
+ const lines = code.split("\n");
268
+ let cutBeforeIndex = -1;
269
+ let cutAfterIndex = -1;
270
+ const cutRanges = [];
271
+ const cutStartStack = [];
272
+ for (let i = 0; i < lines.length; i++) {
273
+ const cutType = classifyCutDirective(lines[i].trim());
274
+ if (cutType === "cut-before") cutBeforeIndex = i;
275
+ else if (cutType === "cut-after") cutAfterIndex = i;
276
+ else if (cutType === "cut-start") cutStartStack.push(i);
277
+ else if (cutType === "cut-end") {
278
+ const startIdx = cutStartStack.pop();
279
+ if (startIdx !== void 0) cutRanges.push([startIdx, i]);
280
+ }
281
+ }
282
+ let filteredLines = lines;
283
+ if (cutBeforeIndex >= 0) {
284
+ filteredLines = filteredLines.slice(cutBeforeIndex + 1);
285
+ if (cutAfterIndex >= 0) cutAfterIndex = cutAfterIndex - cutBeforeIndex - 1;
286
+ for (const range of cutRanges) {
287
+ range[0] -= cutBeforeIndex + 1;
288
+ range[1] -= cutBeforeIndex + 1;
289
+ }
290
+ }
291
+ if (cutAfterIndex >= 0) filteredLines = filteredLines.slice(0, cutAfterIndex);
292
+ const excludedLines = /* @__PURE__ */ new Set();
293
+ for (const [start, end] of cutRanges) for (let i = start; i <= end; i++) if (i >= 0 && i < filteredLines.length) excludedLines.add(i);
294
+ return filteredLines.filter((line, i) => {
295
+ if (excludedLines.has(i)) return false;
296
+ if (isTwoslashDirective(line.trim())) return false;
297
+ return true;
298
+ }).join("\n").trim();
299
+ }
300
+ /**
301
+ * Format import statements with cut directive for hidden imports.
302
+ *
303
+ * Prepends import statements followed by `// ---cut---` so Twoslash can
304
+ * resolve the types but the imports are hidden from rendered output.
305
+ *
306
+ * @param imports - Import statements to format
307
+ * @returns Formatted import block with cut directive, or empty string if no imports
308
+ *
309
+ * @example
310
+ * ```ts
311
+ * const imports = [{ packageName: "zod", symbols: new Set(["ZodType"]), typeOnly: true }];
312
+ * const block = formatImportsWithCut(imports);
313
+ * // Returns:
314
+ * // import type { ZodType } from "zod";
315
+ * // // ---cut---
316
+ * ```
317
+ */
318
+ function formatImportsWithCut(imports) {
319
+ if (imports.length === 0) return "";
320
+ return `${TypeReferenceExtractor.formatImports(imports).join("\n")}\n// ---cut---\n`;
321
+ }
322
+ /**
323
+ * Prepend hidden imports to code using the Twoslash cut directive.
324
+ *
325
+ * This enables type resolution for external types while hiding the import
326
+ * statements from rendered output. The existing `stripTwoslashDirectives()`
327
+ * function handles removing the cut block for clipboard copying.
328
+ *
329
+ * @param code - The code to prepend imports to
330
+ * @param imports - Import statements to add
331
+ * @returns Code with imports prepended (if any), or original code if no imports
332
+ *
333
+ * @example
334
+ * ```ts
335
+ * const code = "function foo(): RsbuildPlugin";
336
+ * const imports = [{ packageName: "@rsbuild/core", symbols: new Set(["RsbuildPlugin"]), typeOnly: true }];
337
+ * const result = prependHiddenImports(code, imports);
338
+ * // Returns:
339
+ * // import type { RsbuildPlugin } from "@rsbuild/core";
340
+ * // // ---cut---
341
+ * // function foo(): RsbuildPlugin
342
+ * ```
343
+ */
344
+ function prependHiddenImports(code, imports) {
345
+ const importBlock = formatImportsWithCut(imports);
346
+ return importBlock ? importBlock + code : code;
347
+ }
348
+ /**
349
+ * Format example code using Prettier for consistent styling.
350
+ *
351
+ * Wraps the Prettier formatter with error handling and context tracking.
352
+ * If formatting fails, returns the original code (fallthrough behavior).
353
+ *
354
+ * @param code - The code to format
355
+ * @param language - The code fence language (e.g., "typescript", "ts")
356
+ * @param _context - Optional context (reserved for future use)
357
+ * @returns The formatted code (or original if formatting fails)
358
+ */
359
+ async function formatExampleCode(code, language, _context) {
360
+ return (await formatCode(code, language)).code;
361
+ }
362
+
363
+ //#endregion
364
+ export { escapeMdxGenerics, escapeYamlString, formatExampleCode, generateAvailableFrom, generateFrontmatter, prepareExampleCode, prependHiddenImports, sanitizeId, stripTwoslashDirectives };