@@ -346,98 +346,61 @@ export async function handleLocalOp(
346346 }
347347}
348348
349- /** Directory names that are listed but never recursed into. */
350- const SKIP_DIRS = new Set ( [ "node_modules" ] ) ;
351-
352- /** Return true if a symlink escapes the project directory. */
353- function isEscapingSymlink ( cwd : string , relPath : string ) : boolean {
354- try {
355- safePath ( cwd , relPath ) ;
356- return false ;
357- } catch {
358- return true ;
359- }
360- }
361-
362- /** Whether a directory entry should be recursed into. */
363- function shouldRecurse ( entry : fs . Dirent ) : boolean {
364- if ( ! entry . isDirectory ( ) || entry . isSymbolicLink ( ) ) {
365- return false ;
366- }
367- return ! ( entry . name . startsWith ( "." ) || SKIP_DIRS . has ( entry . name ) ) ;
368- }
349+ async function listDir ( payload : ListDirPayload ) : Promise < LocalOpResult > {
350+ const { cwd, params } = payload ;
351+ const targetPath = safePath ( cwd , params . path ) ;
352+ const maxDepth = params . maxDepth ?? 3 ;
353+ const maxEntries = params . maxEntries ?? 500 ;
354+ const recursive = params . recursive ?? false ;
369355
370- type WalkContext = {
371- cwd : string ;
372- recursive : boolean ;
373- maxDepth : number ;
374- maxEntries : number ;
375- entries : DirEntry [ ] ;
376- } ;
356+ const entries : DirEntry [ ] = [ ] ;
377357
378- /** Process a single dirent during directory walking. */
379- async function processDirEntry (
380- ctx : WalkContext ,
381- dir : string ,
382- entry : fs . Dirent ,
383- depth : number
384- ) : Promise < void > {
385- const relPath = path . relative ( ctx . cwd , path . join ( dir , entry . name ) ) ;
386- if ( entry . isSymbolicLink ( ) && isEscapingSymlink ( ctx . cwd , relPath ) ) {
387- return ;
388- }
358+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: recursive directory walk is inherently complex but straightforward
359+ async function walk ( dir : string , depth : number ) : Promise < void > {
360+ if ( entries . length >= maxEntries || depth > maxDepth ) {
361+ return ;
362+ }
389363
390- const type = entry . isDirectory ( ) ? "directory" : "file" ;
391- ctx . entries . push ( { name : entry . name , path : relPath , type } ) ;
364+ let dirEntries : fs . Dirent [ ] ;
365+ try {
366+ dirEntries = await fs . promises . readdir ( dir , { withFileTypes : true } ) ;
367+ } catch {
368+ return ;
369+ }
392370
393- if ( ctx . recursive && shouldRecurse ( entry ) ) {
394- await walkDir ( ctx , path . join ( dir , entry . name ) , depth + 1 ) ;
395- }
396- }
371+ for ( const entry of dirEntries ) {
372+ if ( entries . length >= maxEntries ) {
373+ return ;
374+ }
397375
398- async function walkDir (
399- ctx : WalkContext ,
400- dir : string ,
401- depth : number
402- ) : Promise < void > {
403- if ( ctx . entries . length >= ctx . maxEntries || depth > ctx . maxDepth ) {
404- return ;
405- }
376+ const relPath = path . relative ( cwd , path . join ( dir , entry . name ) ) ;
406377
407- let handle : fs . Dir ;
408- try {
409- handle = await fs . promises . opendir ( dir , { bufferSize : 1024 } ) ;
410- } catch {
411- return ;
412- }
378+ // Skip symlinks that escape the project directory
379+ if ( entry . isSymbolicLink ( ) ) {
380+ try {
381+ safePath ( cwd , relPath ) ;
382+ } catch {
383+ continue ;
384+ }
385+ }
413386
414- // No explicit handle.close() needed: for-await-of auto-closes the Dir
415- try {
416- for await ( const entry of handle ) {
417- if ( ctx . entries . length >= ctx . maxEntries ) {
418- break ;
387+ const type = entry . isDirectory ( ) ? "directory" : "file" ;
388+ entries . push ( { name : entry . name , path : relPath , type } ) ;
389+
390+ if (
391+ recursive &&
392+ entry . isDirectory ( ) &&
393+ ! entry . isSymbolicLink ( ) &&
394+ ! entry . name . startsWith ( "." ) &&
395+ entry . name !== "node_modules"
396+ ) {
397+ await walk ( path . join ( dir , entry . name ) , depth + 1 ) ;
419398 }
420- await processDirEntry ( ctx , dir , entry , depth ) ;
421399 }
422- } catch {
423- // Directory unreadable (ENOENT, EACCES, etc.) — skip gracefully
424400 }
425- }
426-
427- async function listDir ( payload : ListDirPayload ) : Promise < LocalOpResult > {
428- const { cwd, params } = payload ;
429- const targetPath = safePath ( cwd , params . path ) ;
430-
431- const ctx : WalkContext = {
432- cwd,
433- recursive : params . recursive ?? false ,
434- maxDepth : params . maxDepth ?? 3 ,
435- maxEntries : params . maxEntries ?? 500 ,
436- entries : [ ] ,
437- } ;
438401
439- await walkDir ( ctx , targetPath , 0 ) ;
440- return { ok : true , data : { entries : ctx . entries } } ;
402+ await walk ( targetPath , 0 ) ;
403+ return { ok : true , data : { entries } } ;
441404}
442405
443406async function readSingleFile (
0 commit comments