import { existsSync } from "node:fs"; import { resolve, isAbsolute } from "node:path"; // IMPORTANT: this module imports `node:fs` / `node:path` and must NEVER be // re-exported from the main `@pipeline/shared` barrel (which client bundles // import). Reach it only via the `@pipeline/shared/node` subpath. // Files that mark the monorepo root. Used so CLI (cwd = repo root) and the web // app (cwd = apps/web) — and the Docker container (cwd = /app/apps/web) — all // resolve the same unified output directory. const ROOT_MARKERS = ["pnpm-workspace.yaml", "turbo.json"]; /** * Walk up from `from` (default: cwd) until a monorepo marker file is found. * Falls back to `from` when no marker is discovered (e.g. running outside the repo). */ export function findMonorepoRoot(from: string = process.cwd()): string { let dir = resolve(from); for (let i = 0; i < 8; i++) { if (ROOT_MARKERS.some((m) => existsSync(resolve(dir, m)))) { return dir; } const parent = resolve(dir, ".."); if (parent === dir) break; dir = parent; } return resolve(from); } /** * Resolve the unified output base directory. * * Resolution order: `OUTPUT_DIR` env var > `configured` (config/default.yaml * `output.dir`) > `./output`. Relative paths are resolved against the monorepo * root (not cwd), so CLI / HTTP / web all land in the same physical directory. */ export function resolveOutputDir(configured?: string): string { const raw = process.env.OUTPUT_DIR || configured || "./output"; return isAbsolute(raw) ? raw : resolve(findMonorepoRoot(), raw); }