@@ -58,7 +58,7 @@ class LS extends ArboristWorkspaceCmd {
58
58
const unicode = this . npm . config . get ( 'unicode' )
59
59
const packageLockOnly = this . npm . config . get ( 'package-lock-only' )
60
60
const workspacesEnabled = this . npm . flatOptions . workspacesEnabled
61
- const includeWorkspaceRoot = this . npm . flatOptions . includeWorkspaceRoot
61
+ const includeWorkspaceRoot = this . npm . config . get ( ' includeWorkspaceRoot' )
62
62
63
63
const path = global ? resolve ( this . npm . globalDir , '..' ) : this . npm . prefix
64
64
@@ -246,81 +246,105 @@ const exploreDependencyGraph = ({
246
246
configs,
247
247
seenNodes,
248
248
problems,
249
- cache = new Map ( ) ,
250
- traversePathMap = new Map ( ) ,
251
249
} ) => {
252
250
const { json, parseable, depthToPrint, args } = configs
253
251
254
- // cahce is for already visited nodes results
255
- // if the node is already seen, we can return it from cache
256
- if ( cache . has ( node . path ) ) {
257
- return cache . get ( node . path )
258
- }
259
-
260
- const currentNodeResult = visit ( node , seenNodes , problems , configs )
261
-
262
- // how the this node is explored
263
- // so if the explored path contains this node again then it's a cycle
264
- // and we don't want to explore it again
265
- // Track the path of pkgids to detect cycles efficiently
266
- const parentTraversePath = traversePathMap . get ( currentNodeResult [ _parent ] ) || [ ]
267
- const isCircular = parentTraversePath . includes ( node . pkgid )
268
- const currentPath = [ ...parentTraversePath , node . pkgid ]
269
- traversePathMap . set ( currentNodeResult , currentPath )
270
-
271
- // we want to start using cache after node is identified as a deduped
272
- if ( node [ _dedupe ] ) {
273
- cache . set ( node . path , currentNodeResult )
274
- }
275
-
276
- const currentDepth = node . isWorkspace ? 0 : node [ _depth ]
277
- const shouldSkipChildren =
278
- ( currentDepth > depthToPrint )
279
-
280
- // Get children of current node
281
- const children = isCircular || shouldSkipChildren || ! currentNodeResult
282
- ? [ ]
283
- : getChildren ( node , wsNodes , configs )
284
-
285
- // Recurse on each child node
286
- for ( const child of children ) {
287
- // _parent is going to be a ref to a traversed node (returned from
288
- // getHumanOutputItem, getJsonOutputItem, etc) so that we have an easy
289
- // shortcut to place new nodes in their right place during tree traversal
290
- child [ _parent ] = currentNodeResult
291
- // _include is the property that allow us to filter based on position args
292
- // e.g: `npm ls foo`, `npm ls simple-output@2`
293
- // _filteredBy is used to apply extra color info to the item that
294
- // was used in args in order to filter
295
- child [ _filteredBy ] = child [ _include ] =
296
- filterByPositionalArgs ( args , { node : child } )
297
- // _depth keeps track of how many levels deep tree traversal currently is
298
- // so that we can `npm ls --depth=1`
299
- child [ _depth ] = currentDepth + 1
300
-
301
- const childResult = exploreDependencyGraph ( {
302
- node : child ,
303
- wsNodes,
304
- configs,
305
- seenNodes,
306
- problems,
307
- cache,
308
- traversePathMap,
309
- } )
310
- // include current node if any of its children are included
311
- currentNodeResult [ _include ] = currentNodeResult [ _include ] || childResult [ _include ]
312
-
313
- if ( childResult [ _include ] && ! parseable ) {
314
- if ( json ) {
315
- currentNodeResult . dependencies = currentNodeResult . dependencies || { }
316
- currentNodeResult . dependencies [ childResult [ _name ] ] = childResult
317
- } else {
318
- currentNodeResult . nodes . push ( childResult )
252
+ // Stack for iterative traversal
253
+ const stack = [ ]
254
+ // Map to store results for each node
255
+ const results = new WeakMap ( )
256
+
257
+ // Cache for deduped nodes
258
+ const cache = new Map ( )
259
+
260
+ // Initialize stack with the root node
261
+ stack . push ( {
262
+ node,
263
+ parent : null ,
264
+ parentTraversePath : [ ] ,
265
+ state : 'not-processed' , // 'enter' or 'exit'
266
+ } )
267
+
268
+ while ( stack . length > 0 ) {
269
+ const current = stack [ stack . length - 1 ]
270
+ const { node : currentNode , parentTraversePath, state, parent } = current
271
+
272
+ const currentDepth = currentNode . isWorkspace ? 0 : currentNode [ _depth ]
273
+ const shouldSkipChildren = currentDepth > depthToPrint
274
+
275
+ currentNode [ _parent ] = parent
276
+ currentNode [ _filteredBy ] = currentNode [ _include ] =
277
+ filterByPositionalArgs ( args , { node : currentNode } )
278
+
279
+ // Cycle detection
280
+ const isCircular = parentTraversePath . includes ( currentNode . path )
281
+ const currentPath = [ ...parentTraversePath , currentNode . path ]
282
+
283
+ if ( state === 'not-processed' ) {
284
+ // Check cache
285
+ if ( cache . has ( currentNode . path ) ) {
286
+ results . set ( currentNode , cache . get ( currentNode . path ) )
287
+ stack . pop ( )
288
+ continue
289
+ }
290
+
291
+ // Visit node
292
+ const currentNodeResult = visit ( currentNode , seenNodes , problems , configs )
293
+
294
+ // Cache deduped nodes
295
+ // if (currentNode[_dedupe]) {
296
+ // cache.set(currentNode.path, currentNodeResult)
297
+ // }
298
+
299
+ // Get children if not circular/shouldn't skip
300
+ let children = [ ]
301
+ if ( ! isCircular && ! shouldSkipChildren ) {
302
+ children = getChildren ( currentNode , wsNodes , configs )
303
+ }
304
+
305
+ // Push children onto stack
306
+ for ( let i = children . length - 1 ; i >= 0 ; i -- ) {
307
+ const child = children [ i ]
308
+ child [ _depth ] = currentDepth + 1
309
+ stack . push ( {
310
+ node : child ,
311
+ parent : currentNodeResult ,
312
+ parentTraversePath : currentPath ,
313
+ state : 'not-processed' ,
314
+ } )
315
+ }
316
+ // since this node is processed
317
+ current . state = 'processed' // next state will be 'processed'
318
+ current . currentNodeResult = currentNodeResult
319
+ current . children = children
320
+ }
321
+ if ( state === 'processed' ) {
322
+ // Collect child results
323
+ const { currentNodeResult, children } = current
324
+
325
+ for ( const child of children ) {
326
+ const childResult = results . get ( child )
327
+ currentNodeResult [ _include ] = currentNodeResult [ _include ] || childResult [ _include ]
328
+ if ( childResult [ _include ] && ! parseable ) {
329
+ if ( json ) {
330
+ currentNodeResult . dependencies = currentNodeResult . dependencies || { }
331
+ currentNodeResult . dependencies [ childResult [ _name ] ] = childResult
332
+ } else {
333
+ currentNodeResult . nodes . push ( childResult )
334
+ }
335
+ }
336
+ }
337
+
338
+ results . set ( currentNode , currentNodeResult )
339
+ if ( currentNode [ _dedupe ] ) {
340
+ cache . set ( currentNode . path , currentNodeResult )
319
341
}
342
+ stack . pop ( )
320
343
}
321
344
}
322
345
323
- return currentNodeResult
346
+ // Return the result for the root node
347
+ return results . get ( node )
324
348
}
325
349
326
350
const isGitNode = ( node ) => {
0 commit comments