/** * Walk `packages////` and return absolute directory * paths for every workspace package (the level containing a `package.json`). * * Supports the canonical 3-level layout (`packages////`) * and falls back to the legacy 2-level (`packages///`) or flat * (`packages//`) layouts so experimental or transitional packages keep * working. */ import { readdir, stat } from "node:fs/promises"; import { join } from "node:path"; import { CONFIG } from "./config.js"; export interface DiscoverOptions { /** Override the monorepo root. Defaults to `CONFIG.paths.root`. */ root?: string; /** Override the packages base. Defaults to `CONFIG.paths.packagesRoot`. */ packagesRoot?: string; } async function hasPackageJson(dir: string): Promise { try { await stat(join(dir, "package.json")); return true; } catch { return false; } } async function listDirs(parent: string): Promise { const out: string[] = []; const entries = await readdir(parent).catch((): string[] => []); for (const name of entries) { const path = join(parent, name); const s = await stat(path).catch(() => null); if (s?.isDirectory()) out.push(path); } return out; } export async function discoverPackageDirs( options: DiscoverOptions = {} ): Promise { const root = options.root ?? CONFIG.paths.root; const base = join(root, options.packagesRoot ?? CONFIG.paths.packagesRoot); const dirs: string[] = []; const level1 = await listDirs(base); for (const lvl1 of level1) { // Flat: packages//package.json if (await hasPackageJson(lvl1)) { dirs.push(lvl1); continue; } const level2 = await listDirs(lvl1); for (const lvl2 of level2) { // Legacy 2-level: packages///package.json if (await hasPackageJson(lvl2)) { dirs.push(lvl2); continue; } // Canonical 3-level: packages////package.json const level3 = await listDirs(lvl2); for (const lvl3 of level3) { if (await hasPackageJson(lvl3)) { dirs.push(lvl3); } } } } dirs.sort(); return dirs; }