@@ -504,31 +504,54 @@ function findMarkdownFiles(dir: string): string[] {
504504 return files ;
505505}
506506
507+ /**
508+ * Directory names that are never descended into when looking for package sources.
509+ *
510+ * `node_modules` is the critical one: pnpm fills it with symlinks pointing back
511+ * into the store, whose packages link onward in turn. Entering it means walking
512+ * that graph along every distinct link path, re-visiting the same directories
513+ * over and over.
514+ *
515+ * `batch-test` holds the codemod's cloned fixture repositories — whole external
516+ * monorepos that `pnpm-workspace.yaml` already excludes from the workspace, and
517+ * whose sources this script must never rewrite.
518+ */
519+ const SKIPPED_DIRS = new Set ( [ 'node_modules' , 'dist' , 'batch-test' ] ) ;
520+
507521/**
508522 * Find all package src directories under the packages directory.
523+ *
524+ * Descends explicitly rather than using `readdirSync`'s `recursive` option. That
525+ * option follows symlinks and collects every entry it visits into a single array
526+ * before returning, so filtering unwanted directories out of the result is too
527+ * late to keep the walk bounded — the traversal has already happened.
528+ *
509529 * @param packagesDir The packages directory
510530 * @returns Array of absolute paths to src directories
511531 */
512532function findPackageSrcDirs ( packagesDir : string ) : string [ ] {
513533 const srcDirs : string [ ] = [ ] ;
514- const entries = readdirSync ( packagesDir , {
515- withFileTypes : true ,
516- recursive : true ,
517- } ) ;
518534
519- for ( const entry of entries ) {
520- if ( ! entry . isDirectory ( ) ) continue ;
521- if ( entry . name !== 'src' ) continue ;
535+ const descend = ( dir : string ) : void => {
536+ for ( const entry of readdirSync ( dir , { withFileTypes : true } ) ) {
537+ // isDirectory() is false for a symlink, so pnpm's links are never entered.
538+ if ( ! entry . isDirectory ( ) ) continue ;
539+ if ( SKIPPED_DIRS . has ( entry . name ) ) continue ;
522540
523- const fullPath = join ( entry . parentPath , entry . name ) ;
541+ const fullPath = join ( dir , entry . name ) ;
524542
525- // Only include src dirs that are direct children of a package
526- // (e.g., packages/core-internal/src, packages/middleware/express/src)
527- // Skip nested src dirs like node_modules/*/src
528- if ( fullPath . includes ( 'node_modules' ) ) continue ;
543+ // A package owns a single src dir; everything below it is that package's
544+ // own tree, which findSourceFiles walks.
545+ if ( entry . name === 'src' ) {
546+ srcDirs . push ( fullPath ) ;
547+ continue ;
548+ }
529549
530- srcDirs . push ( fullPath ) ;
531- }
550+ descend ( fullPath ) ;
551+ }
552+ } ;
553+
554+ descend ( packagesDir ) ;
532555
533556 return srcDirs ;
534557}
0 commit comments