@@ -27,6 +27,8 @@ exports.MAX_DEPENDENCIES = 750;
2727/** @type {string } */
2828exports . payloadPrefix = 'dependencies' ;
2929
30+ const MAX_DEPTH_NODE_MODULES = 2 ;
31+
3032/** @type {Object.<string, string> } */
3133const preliminaryPayload = { } ;
3234
@@ -87,7 +89,13 @@ exports.activate = function activate() {
8789 * @param {string } packageJsonPath
8890 */
8991function addAllDependencies ( dependencyDir , started , packageJsonPath ) {
90- addDependenciesFromDir ( dependencyDir , ( ) => {
92+ addDependenciesFromDir ( dependencyDir , 0 , ( ) => {
93+ // TODO: This check happens AFTER we have already collected the dependencies.
94+ // This is quiet useless for a large dependency tree, because we consume resources to collect
95+ // all the dependencies (fs.stats, fs.readFile etc), but then discard most of them here.
96+ // This is only critical for a very large number of defined dependencies in package.json (vertical).
97+ // NOTE: There is an extra protection in the `addDependenciesFromDir` fn to
98+ // limit the depth of traversing node_modules.
9199 if ( Object . keys ( preliminaryPayload ) . length <= exports . MAX_DEPENDENCIES ) {
92100 // @ts -ignore: Cannot redeclare exported variable 'currentPayload'
93101 exports . currentPayload = preliminaryPayload ;
@@ -114,7 +122,11 @@ function addAllDependencies(dependencyDir, started, packageJsonPath) {
114122 * @param {string } dependencyDir
115123 * @param {() => void } callback
116124 */
117- function addDependenciesFromDir ( dependencyDir , callback ) {
125+ function addDependenciesFromDir ( dependencyDir , currentDepth = 0 , callback ) {
126+ if ( currentDepth >= MAX_DEPTH_NODE_MODULES ) {
127+ return callback ( ) ;
128+ }
129+
118130 fs . readdir ( dependencyDir , ( readDirErr , dependencies ) => {
119131 if ( readDirErr || ! dependencies ) {
120132 logger . warn ( `Cannot analyse dependencies due to ${ readDirErr ?. message } ` ) ;
@@ -140,11 +152,13 @@ function addDependenciesFromDir(dependencyDir, callback) {
140152
141153 filteredDependendencies . forEach ( dependency => {
142154 if ( dependency . indexOf ( '@' ) === 0 ) {
143- addDependenciesFromDir ( path . join ( dependencyDir , dependency ) , ( ) => {
155+ // NOTE: We do not increase currentDepth because scoped packages are just a folder containing more packages.
156+ addDependenciesFromDir ( path . join ( dependencyDir , dependency ) , currentDepth , ( ) => {
144157 countDownLatch . countDown ( ) ;
145158 } ) ;
146159 } else {
147160 const fullDirPath = path . join ( dependencyDir , dependency ) ;
161+
148162 // Only check directories. For example, yarn adds a .yarn-integrity file to /node_modules/ which we need to
149163 // exclude, otherwise we get a confusing "Failed to identify version of .yarn-integrity dependency due to:
150164 // ENOTDIR: not a directory, open '.../node_modules/.yarn-integrity/package.json'." in the logs.
@@ -159,7 +173,7 @@ function addDependenciesFromDir(dependencyDir, callback) {
159173 return ;
160174 }
161175
162- addDependency ( dependency , fullDirPath , countDownLatch ) ;
176+ addDependency ( dependency , fullDirPath , countDownLatch , currentDepth ) ;
163177 } ) ;
164178 }
165179 } ) ;
@@ -173,9 +187,11 @@ function addDependenciesFromDir(dependencyDir, callback) {
173187 * @param {string } dependency
174188 * @param {string } dependencyDirPath
175189 * @param {import('./util/CountDownLatch') } countDownLatch
190+ * @param {number } currentDepth
176191 */
177- function addDependency ( dependency , dependencyDirPath , countDownLatch ) {
192+ function addDependency ( dependency , dependencyDirPath , countDownLatch , currentDepth ) {
178193 const packageJsonPath = path . join ( dependencyDirPath , 'package.json' ) ;
194+
179195 fs . readFile ( packageJsonPath , { encoding : 'utf8' } , ( err , contents ) => {
180196 if ( err && err . code === 'ENOENT' ) {
181197 // This directory does not contain a package json. This happens for example for node_modules/.cache etc.
@@ -198,19 +214,23 @@ function addDependency(dependency, dependencyDirPath, countDownLatch) {
198214 preliminaryPayload [ parsedPackageJson . name ] = parsedPackageJson . version ;
199215 }
200216 } catch ( parseErr ) {
217+ // TODO: countDownLatch.countDown(); needs to be called here too?
218+ // countDownLatch.countDown();
201219 return logger . info (
202220 `Failed to identify version of ${ dependency } dependency due to: ${ parseErr ?. message } .
203221 This means that you will not be able to see details about this dependency within Instana.`
204222 ) ;
205223 }
206224
225+ // NOTE: The dependency metric collector does not respect if the node_modules are dev dependencies or production
226+ // dependencies. It collects all dependencies that are installed in the node_modules folder.
207227 const potentialNestedNodeModulesFolder = path . join ( dependencyDirPath , 'node_modules' ) ;
208228 fs . stat ( potentialNestedNodeModulesFolder , ( statErr , stats ) => {
209229 if ( statErr || ! stats . isDirectory ( ) ) {
210230 countDownLatch . countDown ( ) ;
211231 return ;
212232 }
213- addDependenciesFromDir ( potentialNestedNodeModulesFolder , ( ) => {
233+ addDependenciesFromDir ( potentialNestedNodeModulesFolder , currentDepth + 1 , ( ) => {
214234 countDownLatch . countDown ( ) ;
215235 } ) ;
216236 } ) ;
0 commit comments