import { compareUnicodeCodePoints } from "../domain/unicodeCodePointOrder.js"; import { err, ok } from "../domain/result.js"; import { traverseDirectory } from "./ReferenceSourceReaderEntries.js"; import { type ReferenceSourceReaderOptions, type ReferenceSourceRead, type ReferenceSourceResult, type TraversalState, } from "./ReferenceSourceReaderTypes.js"; import { isAborted, noFollowOpenSupported, prepareRoot, } from "./ReferenceSourceReaderValidate.js"; export type { ReferenceSourceEntry, ReferenceSourceRead, ReferenceSourceReaderOptions, } from "./ReferenceSourceReaderTypes.js"; const PATH_RACE_LIMITATION = "Path identity is revalidated around operations; Node lacks portable descriptor-relative openat traversal, so a syscall-boundary pathname race remains."; /** Read a source tree without intentionally following symbolic links. */ export const readReferenceSource = async ( root: string, options: ReferenceSourceReaderOptions = {}, ): Promise> => { if (!noFollowOpenSupported()) return err({ tag: "reference-source-reader", code: "unsupported", message: "Safe no-follow file opens are unavailable", }); const prepared = await prepareRoot(root, options.signal); if (!prepared.ok) return prepared; const { canonicalRoot, rootIdentity } = prepared.value; const state: TraversalState = { root: canonicalRoot, rootIdentity, ...(options.signal === undefined ? {} : { signal: options.signal }), ...(options.shouldExclude === undefined ? {} : { shouldExclude: options.shouldExclude }), entries: [], pending: [{ path: canonicalRoot }], bytesRead: 0, }; const traversal = await traverse(state); if (!traversal.ok) return traversal; state.entries.sort((left, right) => compareUnicodeCodePoints(left.path, right.path), ); return ok({ root: canonicalRoot, entries: state.entries, bytesRead: state.bytesRead, limitations: [PATH_RACE_LIMITATION], }); }; const traverse = async ( state: TraversalState, ): Promise> => { while (state.pending.length > 0) { if (isAborted(state.signal)) return err({ tag: "reference-source-reader", code: "cancelled", message: "Reference source traversal cancelled", }); const current = state.pending.pop(); if (current === undefined) break; const result = await traverseDirectory(state, current); if (!result.ok) return result; } return ok(undefined); };