* Native implementation to find markdown files using Node.js fs APIs * * This implementation exists alongside ripgrep for the following reasons: * 1. Ripgrep has poor startup performance in native builds (noticeable on app startup) * 2. Provides a fallback when ripgrep is unavailable * 3. Can b
( dir: string, signal: AbortSignal, )
| 516 | * @returns Array of file paths |
| 517 | */ |
| 518 | async function findMarkdownFilesNative( |
| 519 | dir: string, |
| 520 | signal: AbortSignal, |
| 521 | ): Promise<string[]> { |
| 522 | const files: string[] = [] |
| 523 | const visitedDirs = new Set<string>() |
| 524 | |
| 525 | async function walk(currentDir: string): Promise<void> { |
| 526 | if (signal.aborted) { |
| 527 | return |
| 528 | } |
| 529 | |
| 530 | // Cycle detection: track visited directories by device+inode |
| 531 | // Uses bigint: true to handle filesystems with large inodes (e.g., ExFAT) |
| 532 | // that exceed JavaScript's Number precision (53 bits). |
| 533 | // See: upstream issue context in source history |
| 534 | try { |
| 535 | const stats = await stat(currentDir, { bigint: true }) |
| 536 | if (stats.isDirectory()) { |
| 537 | const dirKey = |
| 538 | stats.dev !== undefined && stats.ino !== undefined |
| 539 | ? `${stats.dev}:${stats.ino}` // Unix/Linux: device + inode |
| 540 | : await realpath(currentDir) // Windows: canonical path |
| 541 | |
| 542 | if (visitedDirs.has(dirKey)) { |
| 543 | logForDebugging( |
| 544 | `Skipping already visited directory (circular symlink): ${currentDir}`, |
| 545 | ) |
| 546 | return |
| 547 | } |
| 548 | visitedDirs.add(dirKey) |
| 549 | } |
| 550 | } catch (error) { |
| 551 | const errorMessage = |
| 552 | error instanceof Error ? error.message : String(error) |
| 553 | logForDebugging(`Failed to stat directory ${currentDir}: ${errorMessage}`) |
| 554 | return |
| 555 | } |
| 556 | |
| 557 | try { |
| 558 | const entries = await readdir(currentDir, { withFileTypes: true }) |
| 559 | |
| 560 | for (const entry of entries) { |
| 561 | if (signal.aborted) { |
| 562 | break |
| 563 | } |
| 564 | |
| 565 | const fullPath = join(currentDir, entry.name) |
| 566 | |
| 567 | try { |
| 568 | // Handle symlinks: isFile() and isDirectory() return false for symlinks |
| 569 | if (entry.isSymbolicLink()) { |
| 570 | try { |
| 571 | const stats = await stat(fullPath) // stat() follows symlinks |
| 572 | if (stats.isDirectory()) { |
| 573 | await walk(fullPath) |
| 574 | } else if (stats.isFile() && entry.name.endsWith('.md')) { |
| 575 | files.push(fullPath) |
no test coverage detected