@@ -80,19 +80,50 @@ export class NodeModulesAnalyzer {
8080 const entries = await fs . readdir ( nodeModulesPath , { withFileTypes : true } ) ;
8181
8282 for ( const entry of entries ) {
83- if ( entry . isDirectory ( ) ) {
83+ if ( entry . isDirectory ( ) || entry . isSymbolicLink ( ) ) {
8484 const entryPath = path . join ( nodeModulesPath , entry . name ) ;
8585
86+ // For symlinks, check if they point to directories
87+ if ( entry . isSymbolicLink ( ) ) {
88+ try {
89+ const stats = await fs . stat ( entryPath ) ;
90+ if ( ! stats . isDirectory ( ) ) {
91+ debug ( 'Symlink %s does not point to a directory, skipping' , entry . name ) ;
92+ continue ;
93+ }
94+ debug ( 'Found symlinked directory: %s' , entry . name ) ;
95+ } catch ( error ) {
96+ debug ( 'Failed to resolve symlink %s: %O' , entry . name , error ) ;
97+ continue ;
98+ }
99+ }
100+
86101 if ( entry . name . startsWith ( '@' ) ) {
87102 // Scoped packages
88103 debug ( 'Processing scoped package directory: %s' , entry . name ) ;
89104 try {
90105 const scopedEntries = await fs . readdir ( entryPath , { withFileTypes : true } ) ;
91106 for ( const scopedEntry of scopedEntries ) {
92- if ( scopedEntry . isDirectory ( ) ) {
107+ if ( scopedEntry . isDirectory ( ) || scopedEntry . isSymbolicLink ( ) ) {
93108 const scopedPath = path . join ( entryPath , scopedEntry . name ) ;
109+
110+ // For symlinks in scoped packages, check if they point to directories
111+ if ( scopedEntry . isSymbolicLink ( ) ) {
112+ try {
113+ const stats = await fs . stat ( scopedPath ) ;
114+ if ( ! stats . isDirectory ( ) ) {
115+ debug ( 'Symlink %s/%s does not point to a directory, skipping' , entry . name , scopedEntry . name ) ;
116+ continue ;
117+ }
118+ debug ( 'Found symlinked scoped package: %s/%s' , entry . name , scopedEntry . name ) ;
119+ } catch ( error ) {
120+ debug ( 'Failed to resolve symlink %s/%s: %O' , entry . name , scopedEntry . name , error ) ;
121+ continue ;
122+ }
123+ }
124+
94125 const packageName = `${ entry . name } /${ scopedEntry . name } ` ;
95-
126+
96127 // Verify it's a valid package
97128 if ( await this . isValidPackage ( scopedPath ) ) {
98129 debug ( 'Found scoped package: %s' , packageName ) ;
@@ -175,6 +206,25 @@ export class NodeModulesAnalyzer {
175206 continue ;
176207 }
177208 size += await this . getDirectorySize ( entryPath ) ;
209+ } else if ( entry . isSymbolicLink ( ) ) {
210+ try {
211+ const stats = await fs . stat ( entryPath ) ;
212+ if ( stats . isDirectory ( ) ) {
213+ // Skip nested node_modules to avoid double counting
214+ if ( entry . name === 'node_modules' ) {
215+ debug ( 'Skipping symlinked nested node_modules in: %s' , dirPath ) ;
216+ continue ;
217+ }
218+ debug ( 'Following symlinked directory: %s' , entryPath ) ;
219+ size += await this . getDirectorySize ( entryPath ) ;
220+ } else if ( stats . isFile ( ) ) {
221+ debug ( 'Following symlinked file: %s' , entryPath ) ;
222+ size += stats . size ;
223+ }
224+ } catch ( error ) {
225+ // Skip symlinks we can't resolve
226+ debug ( 'Failed to resolve symlink %s: %O' , entryPath , error ) ;
227+ }
178228 } else if ( entry . isFile ( ) ) {
179229 try {
180230 const stats = await fs . stat ( entryPath ) ;
0 commit comments