@@ -58,7 +58,7 @@ class LS extends ArboristWorkspaceCmd {
5858 const unicode = this . npm . config . get ( 'unicode' )
5959 const packageLockOnly = this . npm . config . get ( 'package-lock-only' )
6060 const workspacesEnabled = this . npm . flatOptions . workspacesEnabled
61- const includeWorkspaceRoot = this . npm . flatOptions . includeWorkspaceRoot
61+ const includeWorkspaceRoot = this . npm . config . get ( 'include-workspace-root' )
6262
6363 const path = global ? resolve ( this . npm . globalDir , '..' ) : this . npm . prefix
6464
@@ -246,81 +246,100 @@ const exploreDependencyGraph = ({
246246 configs,
247247 seenNodes,
248248 problems,
249- cache = new Map ( ) ,
250- traversePathMap = new Map ( ) ,
251249} ) => {
252250 const { json, parseable, depthToPrint, args } = configs
253251
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+ // Get children if not circular/shouldn't skip
295+ let children = [ ]
296+ if ( ! isCircular && ! shouldSkipChildren ) {
297+ children = getChildren ( currentNode , wsNodes , configs )
298+ }
299+
300+ // Push children onto stack
301+ for ( let i = children . length - 1 ; i >= 0 ; i -- ) {
302+ const child = children [ i ]
303+ child [ _depth ] = currentDepth + 1
304+ stack . push ( {
305+ node : child ,
306+ parent : currentNodeResult ,
307+ parentTraversePath : currentPath ,
308+ state : 'not-processed' ,
309+ } )
310+ }
311+ // since this node is processed
312+ current . state = 'processed' // next state will be 'processed'
313+ current . currentNodeResult = currentNodeResult
314+ current . children = children
315+ }
316+ if ( state === 'processed' ) {
317+ // Collect child results
318+ const { currentNodeResult, children } = current
319+
320+ for ( const child of children ) {
321+ const childResult = results . get ( child )
322+ currentNodeResult [ _include ] = currentNodeResult [ _include ] || childResult [ _include ]
323+ if ( childResult [ _include ] && ! parseable ) {
324+ if ( json ) {
325+ currentNodeResult . dependencies = currentNodeResult . dependencies || { }
326+ currentNodeResult . dependencies [ childResult [ _name ] ] = childResult
327+ } else {
328+ currentNodeResult . nodes . push ( childResult )
329+ }
330+ }
331+ }
332+
333+ results . set ( currentNode , currentNodeResult )
334+ if ( currentNode [ _dedupe ] ) {
335+ cache . set ( currentNode . path , currentNodeResult )
319336 }
337+ stack . pop ( )
320338 }
321339 }
322340
323- return currentNodeResult
341+ // Return the result for the root node
342+ return results . get ( node )
324343}
325344
326345const isGitNode = ( node ) => {
0 commit comments