(projectRoot: string)
| 130 | ]); |
| 131 | |
| 132 | function collectOhpmFileDeps(projectRoot: string): Map<string, string> { |
| 133 | const byName = new Map<string, string>(); |
| 134 | const ambiguous = new Set<string>(); |
| 135 | |
| 136 | const queue: Array<{ rel: string; depth: number }> = [{ rel: '', depth: 0 }]; |
| 137 | let visited = 0; |
| 138 | while (queue.length > 0) { |
| 139 | const { rel, depth } = queue.shift()!; |
| 140 | if (++visited > OHPM_WALK_DIR_BUDGET) break; |
| 141 | const abs = path.join(projectRoot, rel); |
| 142 | |
| 143 | let entries: fs.Dirent[]; |
| 144 | try { |
| 145 | entries = fs.readdirSync(abs, { withFileTypes: true }); |
| 146 | } catch { |
| 147 | continue; |
| 148 | } |
| 149 | |
| 150 | for (const e of entries) { |
| 151 | if (e.isDirectory()) { |
| 152 | if (depth >= OHPM_WALK_MAX_DEPTH) continue; |
| 153 | if (e.name.startsWith('.') || OHPM_SKIP_DIRS.has(e.name)) continue; |
| 154 | queue.push({ rel: rel ? `${rel}/${e.name}` : e.name, depth: depth + 1 }); |
| 155 | continue; |
| 156 | } |
| 157 | if (e.name !== OHPM_MANIFEST) continue; |
| 158 | |
| 159 | const deps = readOhpmFileDeps(path.join(abs, e.name)); |
| 160 | for (const [name, target] of deps) { |
| 161 | const targetAbs = path.resolve(abs, target); |
| 162 | const targetRel = path.relative(projectRoot, targetAbs).replace(/\\/g, '/'); |
| 163 | if (targetRel.startsWith('..')) continue; // escapes the project |
| 164 | const existing = byName.get(name); |
| 165 | if (existing === undefined) { |
| 166 | if (!ambiguous.has(name)) byName.set(name, targetRel); |
| 167 | } else if (existing !== targetRel) { |
| 168 | byName.delete(name); |
| 169 | ambiguous.add(name); |
| 170 | } |
| 171 | } |
| 172 | } |
| 173 | } |
| 174 | |
| 175 | return byName; |
| 176 | } |
| 177 | |
| 178 | /** Parse one oh-package.json5's dependencies → [name, file-target] pairs. */ |
| 179 | function readOhpmFileDeps(manifestAbs: string): Array<[string, string]> { |
no test coverage detected