| 69 | } |
| 70 | |
| 71 | async function walkSource(root: string, maxFiles: number): Promise<FileInfo[]> { |
| 72 | const out: FileInfo[] = []; |
| 73 | const stack = [root]; |
| 74 | while (stack.length > 0 && out.length < maxFiles) { |
| 75 | const dir = stack.pop()!; |
| 76 | let entries; |
| 77 | try { entries = await fs.readdir(dir, { withFileTypes: true }); } catch { continue; } |
| 78 | for (const e of entries) { |
| 79 | if (out.length >= maxFiles) break; |
| 80 | if (e.isDirectory()) { |
| 81 | if (SKIP_DIRS.has(e.name) || e.name.startsWith('.')) continue; |
| 82 | stack.push(path.join(dir, e.name)); |
| 83 | } else if (e.isFile()) { |
| 84 | const ext = path.extname(e.name).toLowerCase(); |
| 85 | if (!SOURCE_EXTS.has(ext)) continue; |
| 86 | const abs = path.join(dir, e.name); |
| 87 | try { |
| 88 | const stat = await fs.stat(abs); |
| 89 | if (stat.size > 2_000_000) continue; |
| 90 | const content = await fs.readFile(abs, 'utf-8'); |
| 91 | const rel = path.relative(root, abs); |
| 92 | out.push({ abs, rel, content, ext, isTest: TEST_EXT_RE.test(rel) }); |
| 93 | } catch { /* skip */ } |
| 94 | } |
| 95 | } |
| 96 | } |
| 97 | return out; |
| 98 | } |
| 99 | |
| 100 | function isLikelyEntryPoint(rel: string, packageJson: any): boolean { |
| 101 | if (ENTRY_HINT_PATTERNS.some(p => p.test(rel))) return true; |