@cosmocoder/mcp-web-docs 1.0.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 (240) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +368 -0
  3. package/build/__mocks__/embeddings.d.ts +17 -0
  4. package/build/__mocks__/embeddings.js +66 -0
  5. package/build/__mocks__/embeddings.js.map +1 -0
  6. package/build/config.d.ts +44 -0
  7. package/build/config.js +158 -0
  8. package/build/config.js.map +1 -0
  9. package/build/config.test.d.ts +1 -0
  10. package/build/config.test.js +165 -0
  11. package/build/config.test.js.map +1 -0
  12. package/build/crawler/auth.d.ts +128 -0
  13. package/build/crawler/auth.js +546 -0
  14. package/build/crawler/auth.js.map +1 -0
  15. package/build/crawler/auth.test.d.ts +1 -0
  16. package/build/crawler/auth.test.js +174 -0
  17. package/build/crawler/auth.test.js.map +1 -0
  18. package/build/crawler/base.d.ts +24 -0
  19. package/build/crawler/base.js +149 -0
  20. package/build/crawler/base.js.map +1 -0
  21. package/build/crawler/base.test.d.ts +1 -0
  22. package/build/crawler/base.test.js +234 -0
  23. package/build/crawler/base.test.js.map +1 -0
  24. package/build/crawler/browser-config.d.ts +2 -0
  25. package/build/crawler/browser-config.js +29 -0
  26. package/build/crawler/browser-config.js.map +1 -0
  27. package/build/crawler/browser-config.test.d.ts +1 -0
  28. package/build/crawler/browser-config.test.js +56 -0
  29. package/build/crawler/browser-config.test.js.map +1 -0
  30. package/build/crawler/cheerio.d.ts +11 -0
  31. package/build/crawler/cheerio.js +134 -0
  32. package/build/crawler/cheerio.js.map +1 -0
  33. package/build/crawler/chromium.d.ts +21 -0
  34. package/build/crawler/chromium.js +596 -0
  35. package/build/crawler/chromium.js.map +1 -0
  36. package/build/crawler/content-extractor-types.d.ts +25 -0
  37. package/build/crawler/content-extractor-types.js +2 -0
  38. package/build/crawler/content-extractor-types.js.map +1 -0
  39. package/build/crawler/content-extractors.d.ts +9 -0
  40. package/build/crawler/content-extractors.js +9 -0
  41. package/build/crawler/content-extractors.js.map +1 -0
  42. package/build/crawler/content-utils.d.ts +2 -0
  43. package/build/crawler/content-utils.js +22 -0
  44. package/build/crawler/content-utils.js.map +1 -0
  45. package/build/crawler/content-utils.test.d.ts +1 -0
  46. package/build/crawler/content-utils.test.js +99 -0
  47. package/build/crawler/content-utils.test.js.map +1 -0
  48. package/build/crawler/crawlee-crawler.d.ts +63 -0
  49. package/build/crawler/crawlee-crawler.js +342 -0
  50. package/build/crawler/crawlee-crawler.js.map +1 -0
  51. package/build/crawler/crawlee-crawler.test.d.ts +1 -0
  52. package/build/crawler/crawlee-crawler.test.js +280 -0
  53. package/build/crawler/crawlee-crawler.test.js.map +1 -0
  54. package/build/crawler/default-extractor.d.ts +4 -0
  55. package/build/crawler/default-extractor.js +26 -0
  56. package/build/crawler/default-extractor.js.map +1 -0
  57. package/build/crawler/default-extractor.test.d.ts +1 -0
  58. package/build/crawler/default-extractor.test.js +200 -0
  59. package/build/crawler/default-extractor.test.js.map +1 -0
  60. package/build/crawler/default.d.ts +11 -0
  61. package/build/crawler/default.js +138 -0
  62. package/build/crawler/default.js.map +1 -0
  63. package/build/crawler/docs-crawler.d.ts +26 -0
  64. package/build/crawler/docs-crawler.js +97 -0
  65. package/build/crawler/docs-crawler.js.map +1 -0
  66. package/build/crawler/docs-crawler.test.d.ts +1 -0
  67. package/build/crawler/docs-crawler.test.js +185 -0
  68. package/build/crawler/docs-crawler.test.js.map +1 -0
  69. package/build/crawler/factory.d.ts +6 -0
  70. package/build/crawler/factory.js +83 -0
  71. package/build/crawler/factory.js.map +1 -0
  72. package/build/crawler/github-pages-extractor.d.ts +4 -0
  73. package/build/crawler/github-pages-extractor.js +33 -0
  74. package/build/crawler/github-pages-extractor.js.map +1 -0
  75. package/build/crawler/github-pages-extractor.test.d.ts +1 -0
  76. package/build/crawler/github-pages-extractor.test.js +184 -0
  77. package/build/crawler/github-pages-extractor.test.js.map +1 -0
  78. package/build/crawler/github.d.ts +20 -0
  79. package/build/crawler/github.js +181 -0
  80. package/build/crawler/github.js.map +1 -0
  81. package/build/crawler/github.test.d.ts +1 -0
  82. package/build/crawler/github.test.js +326 -0
  83. package/build/crawler/github.test.js.map +1 -0
  84. package/build/crawler/puppeteer.d.ts +16 -0
  85. package/build/crawler/puppeteer.js +191 -0
  86. package/build/crawler/puppeteer.js.map +1 -0
  87. package/build/crawler/queue-manager.d.ts +43 -0
  88. package/build/crawler/queue-manager.js +169 -0
  89. package/build/crawler/queue-manager.js.map +1 -0
  90. package/build/crawler/queue-manager.test.d.ts +1 -0
  91. package/build/crawler/queue-manager.test.js +509 -0
  92. package/build/crawler/queue-manager.test.js.map +1 -0
  93. package/build/crawler/site-rules.d.ts +11 -0
  94. package/build/crawler/site-rules.js +104 -0
  95. package/build/crawler/site-rules.js.map +1 -0
  96. package/build/crawler/site-rules.test.d.ts +1 -0
  97. package/build/crawler/site-rules.test.js +139 -0
  98. package/build/crawler/site-rules.test.js.map +1 -0
  99. package/build/crawler/storybook-extractor.d.ts +34 -0
  100. package/build/crawler/storybook-extractor.js +767 -0
  101. package/build/crawler/storybook-extractor.js.map +1 -0
  102. package/build/crawler/storybook-extractor.test.d.ts +1 -0
  103. package/build/crawler/storybook-extractor.test.js +491 -0
  104. package/build/crawler/storybook-extractor.test.js.map +1 -0
  105. package/build/embeddings/fastembed.d.ts +25 -0
  106. package/build/embeddings/fastembed.js +188 -0
  107. package/build/embeddings/fastembed.js.map +1 -0
  108. package/build/embeddings/fastembed.test.d.ts +1 -0
  109. package/build/embeddings/fastembed.test.js +307 -0
  110. package/build/embeddings/fastembed.test.js.map +1 -0
  111. package/build/embeddings/openai.d.ts +8 -0
  112. package/build/embeddings/openai.js +56 -0
  113. package/build/embeddings/openai.js.map +1 -0
  114. package/build/embeddings/types.d.ts +4 -0
  115. package/build/embeddings/types.js +2 -0
  116. package/build/embeddings/types.js.map +1 -0
  117. package/build/index.d.ts +2 -0
  118. package/build/index.js +1007 -0
  119. package/build/index.js.map +1 -0
  120. package/build/index.test.d.ts +1 -0
  121. package/build/index.test.js +364 -0
  122. package/build/index.test.js.map +1 -0
  123. package/build/indexing/queue-manager.d.ts +36 -0
  124. package/build/indexing/queue-manager.js +86 -0
  125. package/build/indexing/queue-manager.js.map +1 -0
  126. package/build/indexing/queue-manager.test.d.ts +1 -0
  127. package/build/indexing/queue-manager.test.js +257 -0
  128. package/build/indexing/queue-manager.test.js.map +1 -0
  129. package/build/indexing/status.d.ts +39 -0
  130. package/build/indexing/status.js +207 -0
  131. package/build/indexing/status.js.map +1 -0
  132. package/build/indexing/status.test.d.ts +1 -0
  133. package/build/indexing/status.test.js +246 -0
  134. package/build/indexing/status.test.js.map +1 -0
  135. package/build/processor/content.d.ts +16 -0
  136. package/build/processor/content.js +286 -0
  137. package/build/processor/content.js.map +1 -0
  138. package/build/processor/content.test.d.ts +1 -0
  139. package/build/processor/content.test.js +369 -0
  140. package/build/processor/content.test.js.map +1 -0
  141. package/build/processor/markdown.d.ts +11 -0
  142. package/build/processor/markdown.js +256 -0
  143. package/build/processor/markdown.js.map +1 -0
  144. package/build/processor/markdown.test.d.ts +1 -0
  145. package/build/processor/markdown.test.js +312 -0
  146. package/build/processor/markdown.test.js.map +1 -0
  147. package/build/processor/metadata-parser.d.ts +37 -0
  148. package/build/processor/metadata-parser.js +245 -0
  149. package/build/processor/metadata-parser.js.map +1 -0
  150. package/build/processor/metadata-parser.test.d.ts +1 -0
  151. package/build/processor/metadata-parser.test.js +357 -0
  152. package/build/processor/metadata-parser.test.js.map +1 -0
  153. package/build/processor/processor.d.ts +8 -0
  154. package/build/processor/processor.js +190 -0
  155. package/build/processor/processor.js.map +1 -0
  156. package/build/processor/processor.test.d.ts +1 -0
  157. package/build/processor/processor.test.js +357 -0
  158. package/build/processor/processor.test.js.map +1 -0
  159. package/build/rag/cache.d.ts +10 -0
  160. package/build/rag/cache.js +10 -0
  161. package/build/rag/cache.js.map +1 -0
  162. package/build/rag/code-generator.d.ts +11 -0
  163. package/build/rag/code-generator.js +30 -0
  164. package/build/rag/code-generator.js.map +1 -0
  165. package/build/rag/context-assembler.d.ts +23 -0
  166. package/build/rag/context-assembler.js +113 -0
  167. package/build/rag/context-assembler.js.map +1 -0
  168. package/build/rag/docs-search.d.ts +55 -0
  169. package/build/rag/docs-search.js +380 -0
  170. package/build/rag/docs-search.js.map +1 -0
  171. package/build/rag/pipeline.d.ts +26 -0
  172. package/build/rag/pipeline.js +91 -0
  173. package/build/rag/pipeline.js.map +1 -0
  174. package/build/rag/query-processor.d.ts +14 -0
  175. package/build/rag/query-processor.js +57 -0
  176. package/build/rag/query-processor.js.map +1 -0
  177. package/build/rag/reranker.d.ts +55 -0
  178. package/build/rag/reranker.js +210 -0
  179. package/build/rag/reranker.js.map +1 -0
  180. package/build/rag/response-generator.d.ts +20 -0
  181. package/build/rag/response-generator.js +101 -0
  182. package/build/rag/response-generator.js.map +1 -0
  183. package/build/rag/retriever.d.ts +19 -0
  184. package/build/rag/retriever.js +111 -0
  185. package/build/rag/retriever.js.map +1 -0
  186. package/build/rag/validator.d.ts +22 -0
  187. package/build/rag/validator.js +128 -0
  188. package/build/rag/validator.js.map +1 -0
  189. package/build/rag/version-manager.d.ts +23 -0
  190. package/build/rag/version-manager.js +98 -0
  191. package/build/rag/version-manager.js.map +1 -0
  192. package/build/setupTests.d.ts +4 -0
  193. package/build/setupTests.js +50 -0
  194. package/build/setupTests.js.map +1 -0
  195. package/build/storage/storage.d.ts +38 -0
  196. package/build/storage/storage.js +700 -0
  197. package/build/storage/storage.js.map +1 -0
  198. package/build/storage/storage.test.d.ts +1 -0
  199. package/build/storage/storage.test.js +338 -0
  200. package/build/storage/storage.test.js.map +1 -0
  201. package/build/types/rag.d.ts +27 -0
  202. package/build/types/rag.js +2 -0
  203. package/build/types/rag.js.map +1 -0
  204. package/build/types.d.ts +120 -0
  205. package/build/types.js +2 -0
  206. package/build/types.js.map +1 -0
  207. package/build/util/content-utils.d.ts +31 -0
  208. package/build/util/content-utils.js +120 -0
  209. package/build/util/content-utils.js.map +1 -0
  210. package/build/util/content.d.ts +1 -0
  211. package/build/util/content.js +16 -0
  212. package/build/util/content.js.map +1 -0
  213. package/build/util/docs.d.ts +1 -0
  214. package/build/util/docs.js +26 -0
  215. package/build/util/docs.js.map +1 -0
  216. package/build/util/docs.test.d.ts +1 -0
  217. package/build/util/docs.test.js +49 -0
  218. package/build/util/docs.test.js.map +1 -0
  219. package/build/util/favicon.d.ts +6 -0
  220. package/build/util/favicon.js +88 -0
  221. package/build/util/favicon.js.map +1 -0
  222. package/build/util/favicon.test.d.ts +1 -0
  223. package/build/util/favicon.test.js +140 -0
  224. package/build/util/favicon.test.js.map +1 -0
  225. package/build/util/logger.d.ts +17 -0
  226. package/build/util/logger.js +72 -0
  227. package/build/util/logger.js.map +1 -0
  228. package/build/util/logger.test.d.ts +1 -0
  229. package/build/util/logger.test.js +46 -0
  230. package/build/util/logger.test.js.map +1 -0
  231. package/build/util/security.d.ts +312 -0
  232. package/build/util/security.js +719 -0
  233. package/build/util/security.js.map +1 -0
  234. package/build/util/security.test.d.ts +1 -0
  235. package/build/util/security.test.js +524 -0
  236. package/build/util/security.test.js.map +1 -0
  237. package/build/util/site-detector.d.ts +22 -0
  238. package/build/util/site-detector.js +42 -0
  239. package/build/util/site-detector.js.map +1 -0
  240. package/package.json +112 -0
@@ -0,0 +1,767 @@
1
+ // NOTE: This class is serialized and runs in the browser via Playwright's evaluate()
2
+ // Do NOT import Node.js modules here - use console.error for logging
3
+ export class StorybookExtractor {
4
+ addContentToSections(content, sections, addedSections) {
5
+ const trimmed = content.trim();
6
+ if (trimmed && !addedSections.has(trimmed)) {
7
+ sections.push(trimmed);
8
+ addedSections.add(trimmed);
9
+ }
10
+ }
11
+ isElementVisible(element) {
12
+ const style = window.getComputedStyle(element);
13
+ return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0';
14
+ }
15
+ formatCodeBlock(code, language) {
16
+ // Clean up the code
17
+ const cleanCode = code
18
+ .replace(/^\s+|\s+$/g, '') // Trim whitespace
19
+ .replace(/\t/g, ' ') // Convert tabs to spaces
20
+ .replace(/\n{3,}/g, '\n\n') // Reduce multiple blank lines
21
+ .replace(/\u00A0/g, ' '); // Replace non-breaking spaces
22
+ return `\`\`\`${language}\n${cleanCode}\n\`\`\``;
23
+ }
24
+ extractLinks(element) {
25
+ let content = element.innerHTML;
26
+ // Handle links
27
+ const links = element.querySelectorAll('a');
28
+ links.forEach((link) => {
29
+ const text = link.textContent?.trim();
30
+ const href = link.getAttribute('href');
31
+ if (text && href) {
32
+ const linkHtml = link.outerHTML;
33
+ content = content.replace(linkHtml, `[${text}](${href})`);
34
+ }
35
+ });
36
+ // Handle inline code elements
37
+ const codeElements = element.querySelectorAll('code, [class*="code"], [class*="inline-code"], [class*="monospace"]');
38
+ codeElements.forEach((code) => {
39
+ const text = code.textContent?.trim();
40
+ if (text) {
41
+ const codeHtml = code.outerHTML;
42
+ content = content.replace(codeHtml, `\`${text}\``);
43
+ }
44
+ });
45
+ const div = element.ownerDocument.createElement('div');
46
+ div.innerHTML = content;
47
+ return div.textContent?.trim() || '';
48
+ }
49
+ async processSection(section, sections, addedSections) {
50
+ try {
51
+ const heading = section.querySelector('h2, h3, h4') || section.closest('section')?.querySelector('h2, h3, h4');
52
+ if (heading) {
53
+ const title = heading.textContent?.trim();
54
+ if (title && !addedSections.has(`## ${title}`)) {
55
+ this.addContentToSections(`## ${title}`, sections, addedSections);
56
+ this.addContentToSections('', sections, addedSections);
57
+ let current = heading.nextElementSibling;
58
+ while (current && !current.matches('h2, h3, h4')) {
59
+ await this.processSectionContent(current, sections, addedSections);
60
+ current = current.nextElementSibling;
61
+ }
62
+ }
63
+ }
64
+ const remainingContent = Array.from(section.children).filter((el) => !el.matches('h2, h3, h4') && (!heading || !heading.contains(el)));
65
+ for (const content of remainingContent) {
66
+ await this.processSectionContent(content, sections, addedSections);
67
+ }
68
+ }
69
+ catch (error) {
70
+ console.error('Error processing section:', error);
71
+ }
72
+ }
73
+ async processSectionContent(element, sections, addedSections) {
74
+ if (!element)
75
+ return;
76
+ try {
77
+ if (!this.isElementVisible(element))
78
+ return;
79
+ if (element.matches('p, div[class*="description"], [class*="markdown"], [class*="text"], [class*="content"], [class*="docblock-text"]')) {
80
+ const text = this.extractLinks(element);
81
+ if (text) {
82
+ this.addContentToSections(text, sections, addedSections);
83
+ this.addContentToSections('', sections, addedSections);
84
+ }
85
+ }
86
+ if (element.matches('pre.prismjs')) {
87
+ const code = element.textContent?.trim() || '';
88
+ if (code) {
89
+ const language = element.className.match(/language-(\w+)/)?.[1] || 'typescript';
90
+ const formattedCode = this.formatCodeBlock(code, language);
91
+ this.addContentToSections(formattedCode, sections, addedSections);
92
+ this.addContentToSections('', sections, addedSections);
93
+ }
94
+ }
95
+ if (element.matches('table')) {
96
+ await this.processTableContent(element, sections, addedSections);
97
+ }
98
+ if (element.matches('div')) {
99
+ const name = element.textContent?.trim();
100
+ const nextElement = element.nextElementSibling;
101
+ if (name && nextElement) {
102
+ const hexValue = nextElement.textContent?.trim();
103
+ if (hexValue && hexValue.startsWith('#')) {
104
+ this.addContentToSections(`${name}: ${hexValue}`, sections, addedSections);
105
+ }
106
+ }
107
+ }
108
+ const button = element.querySelector('button');
109
+ if (button?.textContent?.trim() === 'Expand') {
110
+ try {
111
+ button.click();
112
+ await new Promise((resolve) => setTimeout(resolve, 500));
113
+ }
114
+ catch (error) {
115
+ console.error('Error clicking expand button:', error);
116
+ }
117
+ }
118
+ const codeButtons = Array.from(element.querySelectorAll('button')).filter((button) => {
119
+ const text = button.textContent?.toLowerCase() || '';
120
+ return text.includes('show code');
121
+ });
122
+ for (const button of codeButtons) {
123
+ try {
124
+ if (!this.isElementVisible(button))
125
+ continue;
126
+ button.click();
127
+ await new Promise((resolve) => setTimeout(resolve, 500));
128
+ const codeBlocks = element.querySelectorAll('pre.prismjs');
129
+ for (const block of codeBlocks) {
130
+ if (!this.isElementVisible(block))
131
+ continue;
132
+ const code = block.textContent?.trim() || '';
133
+ if (code) {
134
+ const language = block.className.match(/language-(\w+)/)?.[1] || 'typescript';
135
+ const formattedCode = this.formatCodeBlock(code, language);
136
+ this.addContentToSections(formattedCode, sections, addedSections);
137
+ this.addContentToSections('', sections, addedSections);
138
+ }
139
+ }
140
+ button.click();
141
+ }
142
+ catch (error) {
143
+ console.error('Error handling code button:', error);
144
+ }
145
+ }
146
+ const children = Array.from(element.children).filter((el) => {
147
+ if (el.matches('h1, h2, h3, h4'))
148
+ return false;
149
+ if (el.matches('script, style, iframe'))
150
+ return false;
151
+ if (element.matches('pre, code') && el.matches('pre, code'))
152
+ return false;
153
+ return true;
154
+ });
155
+ for (const child of children) {
156
+ await this.processSectionContent(child, sections, addedSections);
157
+ }
158
+ }
159
+ catch (error) {
160
+ console.error('Error processing section content:', error);
161
+ }
162
+ }
163
+ async processPropsTable(table, sections, addedSections, componentName) {
164
+ // First, expand all "Show more" buttons in the table to reveal all type values
165
+ await this.expandAllTypeValues(table);
166
+ // Check if there's already a heading before this table that includes a component name
167
+ // This handles cases like "TableHead Props", "TableBody Props" etc.
168
+ let propsHeading = '## Props';
169
+ // Look for the closest preceding heading
170
+ const closestHeading = this.findClosestPrecedingHeading(table);
171
+ if (closestHeading) {
172
+ const headingText = closestHeading.trim();
173
+ // If the heading already contains "Props" with a component name, use it as-is
174
+ if (/\w+\s+Props$/i.test(headingText)) {
175
+ propsHeading = `## ${headingText}`;
176
+ }
177
+ else if (/^Props$/i.test(headingText) && componentName) {
178
+ // If it's just "Props", prepend the component name
179
+ propsHeading = `## ${componentName} Props`;
180
+ }
181
+ else if (componentName) {
182
+ // Heading doesn't contain Props pattern, use component name
183
+ propsHeading = `## ${componentName} Props`;
184
+ }
185
+ }
186
+ else if (componentName) {
187
+ // No heading found, use component name
188
+ propsHeading = `## ${componentName} Props`;
189
+ }
190
+ this.addContentToSections(propsHeading, sections, addedSections);
191
+ this.addContentToSections('', sections, addedSections);
192
+ this.addContentToSections('| Name | Type | Default |', sections, addedSections);
193
+ this.addContentToSections('|------|------|---------|', sections, addedSections);
194
+ const rows = Array.from(table.querySelectorAll('tr, tbody > div[role="row"]'));
195
+ for (const row of rows) {
196
+ // Skip header rows
197
+ if (row.querySelector('th, [role="columnheader"]'))
198
+ continue;
199
+ const cells = row.querySelectorAll('td, [role="cell"]');
200
+ if (cells.length === 0)
201
+ continue;
202
+ let name = '';
203
+ let type = '';
204
+ let defaultValue = '-';
205
+ let required = false;
206
+ // Cell 0: Name
207
+ if (cells[0]) {
208
+ const nameCell = cells[0];
209
+ name = nameCell.textContent?.trim().split('\n')[0] || '';
210
+ required = name.includes('*') || nameCell.querySelector('[class*="required"]') !== null;
211
+ name = name.replace(/\*$/, '').trim();
212
+ }
213
+ // Cell 1: Type (in Storybook 7+, this is the Description column but contains type)
214
+ if (cells[1]) {
215
+ type = this.extractTypeFromCell(cells[1]);
216
+ }
217
+ // Cell 2: Default value
218
+ if (cells[2]) {
219
+ const defaultCell = cells[2];
220
+ defaultValue = defaultCell.textContent?.trim() || '-';
221
+ }
222
+ // If only 2 cells, second might be default
223
+ if (cells.length === 2 && !type) {
224
+ defaultValue = cells[1]?.textContent?.trim() || '-';
225
+ type = '-';
226
+ }
227
+ // Clean default value
228
+ const formattedDefault = defaultValue
229
+ .replace(/\|/g, '\\|')
230
+ .replace(/^"([^"]+)"$/, '`"$1"`')
231
+ .replace(/^'([^']+)'$/, "`'$1'`");
232
+ const formattedName = required ? `${name}*` : name;
233
+ if (name && name !== 'Name') {
234
+ this.addContentToSections(`| ${formattedName} | ${type || '-'} | ${formattedDefault} |`, sections, addedSections);
235
+ }
236
+ }
237
+ this.addContentToSections('', sections, addedSections);
238
+ }
239
+ /**
240
+ * Find the closest heading element before the given element
241
+ * This helps detect if a Props table already has a component-prefixed heading
242
+ */
243
+ findClosestPrecedingHeading(element) {
244
+ // Check previous siblings
245
+ let sibling = element.previousElementSibling;
246
+ let depth = 0;
247
+ const maxDepth = 5; // Don't look too far back
248
+ while (sibling && depth < maxDepth) {
249
+ // Check if this sibling is a heading
250
+ if (sibling.matches('h1, h2, h3, h4, h5, h6')) {
251
+ return sibling.textContent?.trim() || null;
252
+ }
253
+ // Check if sibling contains a heading
254
+ const heading = sibling.querySelector('h1, h2, h3, h4, h5, h6');
255
+ if (heading) {
256
+ return heading.textContent?.trim() || null;
257
+ }
258
+ sibling = sibling.previousElementSibling;
259
+ depth++;
260
+ }
261
+ // Check parent's previous siblings
262
+ let parent = element.parentElement;
263
+ depth = 0;
264
+ while (parent && depth < 3) {
265
+ sibling = parent.previousElementSibling;
266
+ while (sibling && depth < maxDepth) {
267
+ if (sibling.matches('h1, h2, h3, h4, h5, h6')) {
268
+ return sibling.textContent?.trim() || null;
269
+ }
270
+ const heading = sibling.querySelector('h1, h2, h3, h4, h5, h6');
271
+ if (heading) {
272
+ return heading.textContent?.trim() || null;
273
+ }
274
+ sibling = sibling.previousElementSibling;
275
+ depth++;
276
+ }
277
+ parent = parent.parentElement;
278
+ }
279
+ return null;
280
+ }
281
+ /**
282
+ * Click all "Show more" buttons in a props table to expand type values
283
+ */
284
+ async expandAllTypeValues(table) {
285
+ try {
286
+ // Find all "Show X more" buttons and click them
287
+ const showMoreButtons = table.querySelectorAll('button');
288
+ for (const button of showMoreButtons) {
289
+ const text = button.textContent?.toLowerCase() || '';
290
+ if (text.includes('show') && (text.includes('more') || /\d+/.test(text))) {
291
+ try {
292
+ button.click();
293
+ await new Promise((resolve) => setTimeout(resolve, 100));
294
+ }
295
+ catch {
296
+ // Ignore click errors
297
+ }
298
+ }
299
+ }
300
+ // Wait for expansion to complete
301
+ await new Promise((resolve) => setTimeout(resolve, 200));
302
+ }
303
+ catch (error) {
304
+ console.error('[StorybookExtractor] Error expanding type values:', error);
305
+ }
306
+ }
307
+ /**
308
+ * Extract type values from a table cell, handling Storybook's various DOM structures
309
+ */
310
+ extractTypeFromCell(cell) {
311
+ const typeValues = [];
312
+ const seenValues = new Set();
313
+ // Helper to add a value if it's valid and not seen
314
+ const addValue = (val) => {
315
+ if (!val)
316
+ return;
317
+ const cleaned = val
318
+ .trim()
319
+ .replace(/^["']|["']$/g, '') // Remove surrounding quotes
320
+ .replace(/^\||\|$/g, '') // Remove leading/trailing pipes
321
+ .trim();
322
+ // Skip invalid values
323
+ if (!cleaned ||
324
+ cleaned.length < 1 ||
325
+ cleaned === '-' ||
326
+ /^show/i.test(cleaned) ||
327
+ /more\.\.\.?$/i.test(cleaned) ||
328
+ /^less\.\.\.?$/i.test(cleaned) ||
329
+ /^deprecated/i.test(cleaned) ||
330
+ seenValues.has(cleaned.toLowerCase())) {
331
+ return;
332
+ }
333
+ seenValues.add(cleaned.toLowerCase());
334
+ typeValues.push(cleaned);
335
+ };
336
+ // Strategy 1: Look for specific type value containers (Storybook 7+)
337
+ // These are usually spans/divs with specific classes containing individual values
338
+ const typeContainers = cell.querySelectorAll('[class*="argType"] span, ' +
339
+ '[class*="type-"] span, ' +
340
+ '[class*="union"] > span, ' +
341
+ '.css-in3yi3, ' + // Common Storybook class for type values
342
+ 'span[title]' // Spans with title attributes often contain type info
343
+ );
344
+ if (typeContainers.length > 0) {
345
+ for (const container of typeContainers) {
346
+ // Get the direct text, not nested button text
347
+ const hasButton = container.querySelector('button');
348
+ if (hasButton)
349
+ continue;
350
+ const text = container.textContent?.trim();
351
+ addValue(text);
352
+ }
353
+ }
354
+ // Strategy 2: If we have quoted strings in the cell, extract them
355
+ const cellText = cell.textContent || '';
356
+ const quotedMatches = cellText.match(/"([^"]+)"|'([^']+)'/g);
357
+ if (quotedMatches) {
358
+ for (const match of quotedMatches) {
359
+ addValue(match.replace(/["']/g, ''));
360
+ }
361
+ }
362
+ // Strategy 3: Look for literal type spans (often have specific styling)
363
+ const literalSpans = cell.querySelectorAll('span');
364
+ for (const span of literalSpans) {
365
+ // Only consider leaf spans (no child elements except text)
366
+ if (span.children.length === 0) {
367
+ const text = span.textContent?.trim();
368
+ // Check if it looks like a type value (quoted, or specific format)
369
+ if (text &&
370
+ (text.startsWith('"') ||
371
+ text.startsWith("'") ||
372
+ /^[a-z]+$/i.test(text) || // Simple word like "boolean", "string"
373
+ /^[A-Z][a-zA-Z<>[\]]+$/.test(text))) {
374
+ // Type like "Ref<HTMLElement>"
375
+ addValue(text);
376
+ }
377
+ }
378
+ }
379
+ // Strategy 4: Check for code elements
380
+ const codeElements = cell.querySelectorAll('code');
381
+ for (const code of codeElements) {
382
+ addValue(code.textContent);
383
+ }
384
+ // Strategy 5: Fallback - parse the text content intelligently
385
+ if (typeValues.length === 0) {
386
+ // Clean up the full text
387
+ const fullText = cellText
388
+ .replace(/Show \d+ more\.\.\.?/gi, '')
389
+ .replace(/Show less\.\.\.?/gi, '')
390
+ .replace(/Deprecated:[^|]*/gi, '')
391
+ .trim();
392
+ // Check if it's a simple type (boolean, string, number, any, etc.)
393
+ if (/^(boolean|string|number|any|never|void|null|undefined|object|function)$/i.test(fullText)) {
394
+ return fullText.toLowerCase();
395
+ }
396
+ // Check if it looks like a React type
397
+ if (/^(Ref|React\.|HTMLElement|JSX\.)/i.test(fullText)) {
398
+ return fullText;
399
+ }
400
+ // Try to split by common separators if text has multiple values
401
+ if (fullText.includes('|') || fullText.includes(' or ')) {
402
+ const parts = fullText.split(/\s*\|\s*|\s+or\s+/);
403
+ for (const part of parts) {
404
+ addValue(part);
405
+ }
406
+ }
407
+ else {
408
+ // Last resort: just use the text as-is
409
+ return fullText || '-';
410
+ }
411
+ }
412
+ // Format the output
413
+ if (typeValues.length === 0) {
414
+ return '-';
415
+ }
416
+ // Join with escaped pipe separators (\ |) so they don't break markdown tables
417
+ return typeValues.join(' \\| ');
418
+ }
419
+ async waitForSidebar(document) {
420
+ const maxAttempts = 20;
421
+ const delay = 250;
422
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
423
+ const sidebar = document.querySelector('[class*="sidebar"]');
424
+ if (sidebar) {
425
+ // Wait for sidebar content to load
426
+ await new Promise((resolve) => setTimeout(resolve, 500));
427
+ return;
428
+ }
429
+ await new Promise((resolve) => setTimeout(resolve, delay));
430
+ }
431
+ }
432
+ async expandSidebarSections(document) {
433
+ try {
434
+ // Wait for sidebar to be ready
435
+ await this.waitForSidebar(document);
436
+ // Find all sidebar-subheading-action buttons (the "Show/Hide" buttons)
437
+ const sidebarButtons = document.querySelectorAll('button.sidebar-subheading-action');
438
+ console.error(`[StorybookExtractor] Found ${sidebarButtons.length} sidebar buttons to expand`);
439
+ // First pass: Click all buttons to show all sections
440
+ for (const button of sidebarButtons) {
441
+ if (this.isElementVisible(button)) {
442
+ button.click();
443
+ // Wait for content to update
444
+ await new Promise((resolve) => setTimeout(resolve, 250));
445
+ }
446
+ }
447
+ // Wait for any new buttons that might appear
448
+ await new Promise((resolve) => setTimeout(resolve, 500));
449
+ // Second pass: Click any new buttons that appeared
450
+ const newButtons = document.querySelectorAll('button.sidebar-subheading-action');
451
+ console.error(`[StorybookExtractor] Found ${newButtons.length} total sidebar buttons after expansion`);
452
+ for (const button of newButtons) {
453
+ if (this.isElementVisible(button)) {
454
+ button.click();
455
+ // Wait for content to update
456
+ await new Promise((resolve) => setTimeout(resolve, 250));
457
+ }
458
+ }
459
+ // Final wait to ensure all sections have expanded
460
+ await new Promise((resolve) => setTimeout(resolve, 1000));
461
+ }
462
+ catch (error) {
463
+ console.error('[StorybookExtractor] Error expanding sidebar sections:', error);
464
+ }
465
+ }
466
+ async waitForStorybookContent(document) {
467
+ const maxAttempts = 10;
468
+ const delay = 500;
469
+ // First, expand all sidebar sections
470
+ await this.expandSidebarSections(document);
471
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
472
+ await new Promise((resolve) => setTimeout(resolve, delay));
473
+ const mainArea = document.querySelector('.sbdocs-content, #docs-root');
474
+ if (!mainArea)
475
+ continue;
476
+ const hasContent = mainArea.querySelector('h1') &&
477
+ (mainArea.querySelector('p') ||
478
+ mainArea.querySelector('table') ||
479
+ mainArea.querySelector('ul, ol') ||
480
+ mainArea.querySelector('[class*="docblock"]'));
481
+ if (hasContent) {
482
+ await new Promise((resolve) => setTimeout(resolve, 500));
483
+ return mainArea;
484
+ }
485
+ const loadingIndicator = document.querySelector('[class*="loading"], [class*="pending"]');
486
+ if (!loadingIndicator) {
487
+ console.warn('No loading indicator found but content is missing');
488
+ }
489
+ }
490
+ return null;
491
+ }
492
+ async processTableContent(table, sections, addedSections) {
493
+ try {
494
+ const headers = Array.from(table.querySelectorAll('th')).map((th) => th.textContent?.trim() || '');
495
+ if (headers.length === 0)
496
+ return;
497
+ this.addContentToSections(`| ${headers.join(' | ')} |`, sections, addedSections);
498
+ this.addContentToSections(`| ${headers.map(() => '---').join(' | ')} |`, sections, addedSections);
499
+ const rows = table.querySelectorAll('tr');
500
+ for (const row of rows) {
501
+ const cells = Array.from(row.querySelectorAll('td')).map((td) => {
502
+ const text = this.extractLinks(td);
503
+ return text.replace(/\|/g, '\\|');
504
+ });
505
+ if (cells.length > 0) {
506
+ this.addContentToSections(`| ${cells.join(' | ')} |`, sections, addedSections);
507
+ }
508
+ }
509
+ this.addContentToSections('', sections, addedSections);
510
+ }
511
+ catch (error) {
512
+ console.error('Error processing table:', error);
513
+ }
514
+ }
515
+ async processListContent(list, sections, addedSections) {
516
+ try {
517
+ const items = list.querySelectorAll('li');
518
+ for (const item of items) {
519
+ const text = this.extractLinks(item);
520
+ if (text) {
521
+ const prefix = list.tagName.toLowerCase() === 'ol' ? '1. ' : '- ';
522
+ this.addContentToSections(`${prefix}${text}`, sections, addedSections);
523
+ }
524
+ }
525
+ this.addContentToSections('', sections, addedSections);
526
+ }
527
+ catch (error) {
528
+ console.error('Error processing list:', error);
529
+ }
530
+ }
531
+ /**
532
+ * Extract type annotations from inline code examples and prop documentation
533
+ */
534
+ async extractTypeAnnotations(mainArea, sections, addedSections) {
535
+ try {
536
+ // Look for type definitions in code blocks
537
+ const codeBlocks = mainArea.querySelectorAll('pre code, .prismjs, [class*="highlight"]');
538
+ for (const block of codeBlocks) {
539
+ const code = block.textContent || '';
540
+ // Extract TypeScript interface/type definitions
541
+ const interfaceMatch = code.match(/interface\s+(\w+Props?)\s*\{([^}]+)\}/);
542
+ if (interfaceMatch) {
543
+ const [, name, body] = interfaceMatch;
544
+ if (!addedSections.has(`## ${name} Type`)) {
545
+ this.addContentToSections(`## ${name} Type`, sections, addedSections);
546
+ this.addContentToSections('```typescript', sections, addedSections);
547
+ this.addContentToSections(`interface ${name} {${body}}`, sections, addedSections);
548
+ this.addContentToSections('```', sections, addedSections);
549
+ this.addContentToSections('', sections, addedSections);
550
+ }
551
+ }
552
+ // Extract type alias definitions
553
+ const typeMatch = code.match(/type\s+(\w+)\s*=\s*([^;]+);/);
554
+ if (typeMatch) {
555
+ const [, name, definition] = typeMatch;
556
+ if (!addedSections.has(`Type: ${name}`)) {
557
+ this.addContentToSections(`**Type ${name}:** \`${definition.trim()}\``, sections, addedSections);
558
+ this.addContentToSections('', sections, addedSections);
559
+ }
560
+ }
561
+ }
562
+ // Look for prop annotations in inline code (e.g., buttonStyle="primary")
563
+ const inlineCodes = mainArea.querySelectorAll('code:not(pre code)');
564
+ const propAnnotations = new Map();
565
+ for (const code of inlineCodes) {
566
+ const text = code.textContent || '';
567
+ // Match prop="value" or prop='value' patterns
568
+ const propMatch = text.match(/(\w+)=["']([^"']+)["']/);
569
+ if (propMatch) {
570
+ const [, propName, value] = propMatch;
571
+ if (!propAnnotations.has(propName)) {
572
+ propAnnotations.set(propName, new Set());
573
+ }
574
+ propAnnotations.get(propName)?.add(value);
575
+ }
576
+ }
577
+ // If we found prop annotations, add them as discovered values
578
+ if (propAnnotations.size > 0) {
579
+ let hasAddedHeader = false;
580
+ for (const [propName, values] of propAnnotations) {
581
+ if (values.size > 1) {
582
+ if (!hasAddedHeader) {
583
+ this.addContentToSections('## Discovered Prop Values', sections, addedSections);
584
+ hasAddedHeader = true;
585
+ }
586
+ const valueList = Array.from(values)
587
+ .map((v) => `"${v}"`)
588
+ .join(' | ');
589
+ this.addContentToSections(`- **${propName}**: ${valueList}`, sections, addedSections);
590
+ }
591
+ }
592
+ if (hasAddedHeader) {
593
+ this.addContentToSections('', sections, addedSections);
594
+ }
595
+ }
596
+ }
597
+ catch (error) {
598
+ console.error('[StorybookExtractor] Error extracting type annotations:', error);
599
+ }
600
+ }
601
+ async waitForStorybookAPI() {
602
+ return new Promise((resolve) => {
603
+ const win = window;
604
+ if (typeof win.__STORYBOOK_CLIENT_API__ !== 'undefined') {
605
+ const checkReady = () => {
606
+ const api = win.__STORYBOOK_CLIENT_API__;
607
+ if (api?.storyStore?.ready) {
608
+ resolve();
609
+ }
610
+ else {
611
+ setTimeout(checkReady, 100);
612
+ }
613
+ };
614
+ checkReady();
615
+ return;
616
+ }
617
+ if (document.querySelector('#storybook-root, .sbdocs, [data-nodetype="root"]') !== null ||
618
+ document.querySelector('meta[name="storybook-version"]') !== null ||
619
+ document.baseURI?.includes('path=/docs/') ||
620
+ document.baseURI?.includes('path=/story/')) {
621
+ resolve();
622
+ return;
623
+ }
624
+ resolve();
625
+ });
626
+ }
627
+ async extractContent(document) {
628
+ const emptyResult = {
629
+ content: '',
630
+ metadata: { type: 'overview' },
631
+ };
632
+ try {
633
+ await this.waitForStorybookAPI();
634
+ const mainArea = await this.waitForStorybookContent(document);
635
+ if (!mainArea) {
636
+ console.error('Failed to find Storybook content area');
637
+ return emptyResult;
638
+ }
639
+ const sections = [];
640
+ const addedSections = new Set();
641
+ let mainTitle = '';
642
+ let mainDescription = '';
643
+ const title = mainArea.querySelector('h1')?.textContent?.trim();
644
+ if (title) {
645
+ mainTitle = title;
646
+ this.addContentToSections(`# ${title}`, sections, addedSections);
647
+ this.addContentToSections('', sections, addedSections);
648
+ }
649
+ const description = mainArea.querySelector('h1 + p');
650
+ if (description) {
651
+ mainDescription = this.extractLinks(description);
652
+ this.addContentToSections(mainDescription, sections, addedSections);
653
+ this.addContentToSections('', sections, addedSections);
654
+ }
655
+ const headings = mainArea.querySelectorAll('h1, h2, h3, h4');
656
+ for (const heading of headings) {
657
+ const title = heading.textContent?.trim();
658
+ if (title && !addedSections.has(`## ${title}`)) {
659
+ // Skip "Props" section - it will be handled by processPropsTable
660
+ const isPropsSection = /^props$/i.test(title);
661
+ if (isPropsSection)
662
+ continue;
663
+ const level = heading.tagName === 'H1' ? '#' : '##';
664
+ this.addContentToSections(`${level} ${title}`, sections, addedSections);
665
+ this.addContentToSections('', sections, addedSections);
666
+ let current = heading.nextElementSibling;
667
+ while (current && !current.matches('h1, h2, h3, h4')) {
668
+ if (this.isElementVisible(current)) {
669
+ // Skip props tables - they will be handled separately
670
+ const isPropsTable = current.matches('.docblock-argstable, [class*="ArgTable"], [class*="argtable"]');
671
+ if (current.matches('table') && !isPropsTable) {
672
+ await this.processTableContent(current, sections, addedSections);
673
+ }
674
+ else if (current.matches('ul, ol')) {
675
+ await this.processListContent(current, sections, addedSections);
676
+ }
677
+ else if (!isPropsTable) {
678
+ await this.processSectionContent(current, sections, addedSections);
679
+ }
680
+ }
681
+ current = current.nextElementSibling;
682
+ }
683
+ }
684
+ }
685
+ // Find props/args table with multiple selectors for different Storybook versions
686
+ const propsTableSelectors = [
687
+ '.docblock-argstable', // Storybook 6.x
688
+ '.docblock-argtable', // Alternative naming
689
+ 'table.docblock-table', // Storybook 7.x
690
+ '[class*="ArgTable"]', // React-based ArgTable
691
+ '[class*="argtable"]', // Case variations
692
+ 'table[class*="props"]', // Generic props table
693
+ '.sb-argstable', // Another Storybook variant
694
+ '[data-testid="args-table"]', // Test ID selector
695
+ '.docs-story + table', // Table after story
696
+ 'section:has(h2:contains("Props")) table', // Section with Props heading
697
+ ];
698
+ let propsTable = null;
699
+ for (const selector of propsTableSelectors) {
700
+ try {
701
+ propsTable = mainArea.querySelector(selector);
702
+ if (propsTable)
703
+ break;
704
+ }
705
+ catch {
706
+ // Some selectors may not be valid in all browsers
707
+ continue;
708
+ }
709
+ }
710
+ // Also try to find ArgTypes component which renders the props
711
+ if (!propsTable) {
712
+ const argTypesSection = mainArea.querySelector('[class*="ArgTypes"], [class*="argtypes"]');
713
+ if (argTypesSection) {
714
+ propsTable = argTypesSection.querySelector('table');
715
+ }
716
+ }
717
+ if (propsTable) {
718
+ await this.processPropsTable(propsTable, sections, addedSections, mainTitle);
719
+ }
720
+ // Also extract any inline type annotations from code examples
721
+ await this.extractTypeAnnotations(mainArea, sections, addedSections);
722
+ // Process iframes - Storybook loads docs content in iframes
723
+ const iframes = mainArea.querySelectorAll('iframe');
724
+ for (const iframe of iframes) {
725
+ try {
726
+ const iframeDoc = iframe.contentDocument;
727
+ if (iframeDoc) {
728
+ // Look for props table in iframe
729
+ const iframePropsTable = iframeDoc.querySelector('.docblock-argstable, .docblock-argtable, table.docblock-table, [class*="ArgTable"]');
730
+ if (iframePropsTable && !propsTable) {
731
+ await this.processPropsTable(iframePropsTable, sections, addedSections, mainTitle);
732
+ }
733
+ // Process any other content in the iframe
734
+ const iframeContent = iframeDoc.querySelector('.sbdocs-content, #docs-root, body');
735
+ if (iframeContent) {
736
+ await this.processSection(iframeContent, sections, addedSections);
737
+ // Also try to extract type annotations from iframe
738
+ await this.extractTypeAnnotations(iframeContent, sections, addedSections);
739
+ }
740
+ }
741
+ }
742
+ catch (error) {
743
+ // Cross-origin iframe - expected for external content
744
+ console.error('Error processing iframe (may be cross-origin):', error);
745
+ }
746
+ }
747
+ return {
748
+ content: sections.join('\n\n'),
749
+ metadata: {
750
+ type: 'overview',
751
+ pattern: {
752
+ name: mainTitle,
753
+ type: 'component',
754
+ description: mainDescription,
755
+ usageContexts: [],
756
+ relatedPatterns: [],
757
+ },
758
+ },
759
+ };
760
+ }
761
+ catch (error) {
762
+ console.error('Error extracting Storybook content:', error);
763
+ return emptyResult;
764
+ }
765
+ }
766
+ }
767
+ //# sourceMappingURL=storybook-extractor.js.map