(dirRel: string)
| 366 | export async function listDatabases(root: string): Promise<DatabaseSummary[]> { |
| 367 | const out: DatabaseSummary[] = [] |
| 368 | const walk = async (dirRel: string): Promise<void> => { |
| 369 | const dirAbs = dirRel ? path.join(root, dirRel) : root |
| 370 | let entries: import('node:fs').Dirent[] |
| 371 | try { |
| 372 | entries = await fs.readdir(dirAbs, { withFileTypes: true }) |
| 373 | } catch { |
| 374 | return |
| 375 | } |
| 376 | for (const entry of entries) { |
| 377 | const name = entry.name |
| 378 | if (name.startsWith('.') || name === 'trash' || name === 'node_modules') continue |
| 379 | const childRel = dirRel ? `${dirRel}/${name}` : name |
| 380 | if (entry.isDirectory()) { |
| 381 | if (isFormDirName(name)) { |
| 382 | // A database folder — surface it, don't descend into its internals. |
| 383 | const rel = csvPathForFormDir(childRel) |
| 384 | out.push({ path: toPosix(rel), title: titleFromPath(rel) }) |
| 385 | continue |
| 386 | } |
| 387 | await walk(childRel) |
| 388 | } else if ( |
| 389 | name.toLowerCase().endsWith('.csv') && |
| 390 | !name.toLowerCase().endsWith(`.csv${DATABASE_SIDECAR_SUFFIX}`) |
| 391 | ) { |
| 392 | // Legacy loose `.csv` (pre-migration). |
| 393 | out.push({ path: toPosix(childRel), title: titleFromPath(childRel) }) |
| 394 | } |
| 395 | } |
| 396 | } |
| 397 | await walk('') |
| 398 | return out.sort((a, b) => a.title.localeCompare(b.title)) |
| 399 | } |
no test coverage detected