@@ -188,13 +188,14 @@ class GitIgnoreFiles {
188188 }
189189
190190 // Ignore files may disappear or become unreadable during traversal.
191- const loading = readFile ( path . join ( directoryPath , '.gitignore' ) , 'utf8' )
192- . then ( ( content ) => {
191+ const loading = readFile ( path . join ( directoryPath , '.gitignore' ) , 'utf8' ) . then (
192+ ( content ) => {
193193 const relativePath = toPosixPath ( this . #resolveRelativePath( directoryPath ) ) ;
194194 this . #matcher ??= new ( loadNativeBinding ( ) . GitIgnoreMatcher ) ( ) ;
195195 this . #hasRules = this . #matcher. addSource ( relativePath , content ) ;
196- } )
197- . catch ( ( ) => undefined ) ;
196+ } ,
197+ ( ) => undefined ,
198+ ) ;
198199
199200 this . #loads. set ( directoryPath , loading ) ;
200201 return loading ;
@@ -204,11 +205,14 @@ class GitIgnoreFiles {
204205const createTraversalOptions = (
205206 gitIgnore : GitIgnoreFiles ,
206207 ignoredDirNames : ReadonlySet < string > ,
208+ signal : { aborted : boolean } ,
209+ onError : ( error : unknown ) => void ,
207210 isIncluded ?: ( filePath : string ) => boolean ,
208211 isIgnored ?: ( filePath : string , isDirectory : boolean ) => boolean ,
209212) => {
210213 return {
211214 followSymlinks : false ,
215+ signal,
212216 ignore : ( targetPath : string , targetContext : DirentLike ) => {
213217 // With symlink following disabled, tiny-readdir always provides a Dirent here.
214218 const dirent = targetContext as Dirent ;
@@ -233,43 +237,78 @@ const createTraversalOptions = (
233237 ) ;
234238 } ,
235239 onDirents : async ( dirents : Dirent [ ] ) => {
236- const parentPath = getDirentParentPath ( dirents [ 0 ] ) ;
237- let hasGitIgnore = false ;
240+ try {
241+ const parentPath = getDirentParentPath ( dirents [ 0 ] ) ;
242+ let hasGitIgnore = false ;
238243
239- for ( const dirent of dirents ) {
240- if ( dirent . name === '.gitignore' ) {
241- hasGitIgnore = true ;
244+ for ( const dirent of dirents ) {
245+ if ( dirent . name === '.gitignore' ) {
246+ hasGitIgnore = true ;
247+ }
242248 }
243- }
244249
245- if ( hasGitIgnore ) {
246- await gitIgnore . load ( parentPath ) ;
247- }
248-
249- const ignored = gitIgnore . matchDirents ( parentPath , dirents ) ;
250- if ( typeof ignored === 'boolean' ) {
251- if ( ignored ) {
252- ( dirents [ 0 ] as GitIgnoreDirent ) [ gitIgnored ] = true ;
250+ if ( hasGitIgnore ) {
251+ await gitIgnore . load ( parentPath ) ;
253252 }
254- } else if ( typeof ignored === 'number' ) {
255- for ( let index = 0 ; index < dirents . length ; index ++ ) {
256- if ( ignored & ( 1 << index ) ) {
257- ( dirents [ index ] as GitIgnoreDirent ) [ gitIgnored ] = true ;
253+
254+ const ignored = gitIgnore . matchDirents ( parentPath , dirents ) ;
255+ if ( typeof ignored === 'boolean' ) {
256+ if ( ignored ) {
257+ ( dirents [ 0 ] as GitIgnoreDirent ) [ gitIgnored ] = true ;
258258 }
259- }
260- } else if ( ignored ) {
261- for ( let index = 0 ; index < ignored . length ; index ++ ) {
262- if ( ignored [ index ] === 1 ) {
263- ( dirents [ index ] as GitIgnoreDirent ) [ gitIgnored ] = true ;
259+ } else if ( typeof ignored === 'number' ) {
260+ for ( let index = 0 ; index < dirents . length ; index ++ ) {
261+ if ( ignored & ( 1 << index ) ) {
262+ ( dirents [ index ] as GitIgnoreDirent ) [ gitIgnored ] = true ;
263+ }
264+ }
265+ } else if ( ignored ) {
266+ for ( let index = 0 ; index < ignored . length ; index ++ ) {
267+ if ( ignored [ index ] === 1 ) {
268+ ( dirents [ index ] as GitIgnoreDirent ) [ gitIgnored ] = true ;
269+ }
264270 }
265271 }
272+ } catch ( error ) {
273+ onError ( error ) ;
266274 }
267275
268276 return undefined ;
269277 } ,
270278 } ;
271279} ;
272280
281+ const discoverDirectoryFiles = async (
282+ rootPath : string ,
283+ gitIgnore : GitIgnoreFiles ,
284+ ignoredDirNames : ReadonlySet < string > ,
285+ isIncluded ?: ( filePath : string ) => boolean ,
286+ isIgnored ?: ( filePath : string , isDirectory : boolean ) => boolean ,
287+ ) : Promise < string [ ] > => {
288+ let failed = false ;
289+ let failure : unknown ;
290+ const signal = { aborted : false } ;
291+ const onError = ( error : unknown ) : void => {
292+ if ( ! failed ) {
293+ failed = true ;
294+ failure = error ;
295+ }
296+ signal . aborted = true ;
297+ } ;
298+
299+ const result = await readdir (
300+ rootPath ,
301+ createTraversalOptions ( gitIgnore , ignoredDirNames , signal , onError , isIncluded , isIgnored ) ,
302+ ) ;
303+
304+ // tiny-readdir only handles fulfilled onDirents promises, so rethrow after its counter settles.
305+ if ( failed ) {
306+ throw failure ;
307+ }
308+
309+ return result . files ;
310+ } ;
311+
273312const normalizeGlob = ( cwd : string , pattern : string ) : string => {
274313 const relativePattern = path . isAbsolute ( pattern ) ? path . relative ( cwd , pattern ) : pattern ;
275314 return toPosixPath ( relativePattern ) ;
@@ -426,12 +465,7 @@ const discoverFmtPaths = async ({
426465 return globMatchers . some ( ( matches ) => matches ( relativePath ) ) ;
427466 } ;
428467
429- return (
430- await readdir (
431- rootPath ,
432- createTraversalOptions ( gitIgnore , ignoredDirNames , isIncluded , isIgnored ) ,
433- )
434- ) . files ;
468+ return discoverDirectoryFiles ( rootPath , gitIgnore , ignoredDirNames , isIncluded , isIgnored ) ;
435469 } ) ,
436470 ) ;
437471
0 commit comments