2026-09-08 10:59:13 +08:00
import { compareUnicodeCodePoints } from "../domain/unicodeCodePointOrder.js" ;
2026-07-12 21:12:18 +00:00
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." ;
2026-09-29 14:53:20 +08:00
/** Read a source tree without intentionally following symbolic links. */
2026-07-12 21:12:18 +00:00
export const readReferenceSource = async (
root : string ,
options : ReferenceSourceReaderOptions = {},
) : Promise < ReferenceSourceResult < ReferenceSourceRead >> => {
if ( ! noFollowOpenSupported ())
return err ({
tag : "reference-source-reader" ,
code : "unsupported" ,
message : "Safe no-follow file opens are unavailable" ,
});
2026-09-29 14:53:20 +08:00
const prepared = await prepareRoot ( root , options . signal );
2026-07-12 21:12:18 +00:00
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 : [],
2026-09-29 14:53:20 +08:00
pending : [{ path : canonicalRoot }],
2026-07-12 21:12:18 +00:00
bytesRead : 0 ,
};
const traversal = await traverse ( state );
if ( ! traversal . ok ) return traversal ;
2026-09-08 10:59:13 +08:00
state . entries . sort (( left , right ) =>
compareUnicodeCodePoints ( left . path , right . path ),
);
2026-07-12 21:12:18 +00:00
return ok ({
root : canonicalRoot ,
entries : state.entries ,
bytesRead : state.bytesRead ,
2026-09-29 14:53:20 +08:00
limitations : [ PATH_RACE_LIMITATION ],
2026-07-12 21:12:18 +00:00
});
};
const traverse = async (
state : TraversalState ,
) : Promise < ReferenceSourceResult < undefined >> => {
2026-09-29 14:53:20 +08:00
while ( state . pending . length > 0 ) {
2026-07-12 21:12:18 +00:00
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 );
};