import { mkdirSync } from "node:fs"; import { realpath } from "node:fs/promises"; import { resolve, sep } from "node:path"; import type { Deps } from "./context"; export const OUTSIDE = "path is outside the allowed import folders"; /** AppConfig.importRoots: STC_IMPORT_ROOTS (":"-separated), default /import-inbox. */ export function importRoots(d: Pick): string[] { return d.importRoots.map((r) => resolve(r)); } export function ensureRoots(d: Pick): void { for (const r of importRoots(d)) { try { mkdirSync(r, { recursive: true }); } catch { /* reported when used */ } } } /** Real path (symlinks followed) must lie inside the real path of one root. Throws OUTSIDE otherwise. */ export async function assertAllowedPath(d: Pick, path: string): Promise { let real: string; try { real = await realpath(resolve(path)); } catch { throw new Error(OUTSIDE); } for (const root of importRoots(d)) { const rr = await realpath(root).catch(() => null); if (rr && (real === rr || real.startsWith(rr + sep))) return real; } throw new Error(OUTSIDE); } export const isGitUrl = (p: string) => /^(https?:\/\/|ssh:\/\/|git@)\S+$/.test(p);