* Search for .mdx/.md files in the project that contain the given text. * Used as a fallback when a text edit targets a JSX wrapper but the actual * content lives in a compiled MDX file.
(projectRoot: string, text: string)
| 24 | * content lives in a compiled MDX file. |
| 25 | */ |
| 26 | function findMdxFileContainingText(projectRoot: string, text: string): string | null { |
| 27 | const normalizedText = text.replace(/\s+/g, " ").trim(); |
| 28 | if (!normalizedText) return null; |
| 29 | |
| 30 | const candidates: string[] = []; |
| 31 | const searchDirs = [projectRoot]; |
| 32 | const visited = new Set<string>(); |
| 33 | |
| 34 | while (searchDirs.length > 0) { |
| 35 | const dir = searchDirs.pop()!; |
| 36 | if (visited.has(dir)) continue; |
| 37 | visited.add(dir); |
| 38 | |
| 39 | let entries: fs.Dirent[]; |
| 40 | try { |
| 41 | entries = fs.readdirSync(dir, { withFileTypes: true }); |
| 42 | } catch { |
| 43 | continue; |
| 44 | } |
| 45 | |
| 46 | for (const entry of entries) { |
| 47 | if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build") continue; |
| 48 | const fullPath = path.join(dir, entry.name); |
| 49 | if (entry.isDirectory()) { |
| 50 | searchDirs.push(fullPath); |
| 51 | } else if (entry.name.endsWith(".mdx") || entry.name.endsWith(".md")) { |
| 52 | candidates.push(fullPath); |
| 53 | } |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | for (const filePath of candidates) { |
| 58 | try { |
| 59 | const content = fs.readFileSync(filePath, "utf-8"); |
| 60 | const normalizedContent = content.replace(/\s+/g, " "); |
| 61 | if (normalizedContent.includes(normalizedText)) { |
| 62 | return filePath; |
| 63 | } |
| 64 | } catch { |
| 65 | continue; |
| 66 | } |
| 67 | } |
| 68 | |
| 69 | return null; |
| 70 | } |
| 71 | |
| 72 | /** Get the primary line number from any BatchOperation variant. */ |
| 73 | function getOpLine(op: BatchOperation): number { |