@@ -58,6 +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
6162
6263 const path = global ? resolve ( this . npm . globalDir , '..' ) : this . npm . prefix
6364
@@ -84,95 +85,28 @@ class LS extends ArboristWorkspaceCmd {
8485 if ( this . workspaceNames && this . workspaceNames . length ) {
8586 wsNodes = arb . workspaceNodes ( tree , this . workspaceNames )
8687 }
87- const filterBySelectedWorkspaces = edge => {
88- if ( ! workspacesEnabled
89- && edge . from . isProjectRoot
90- && edge . to . isWorkspace
91- ) {
92- return false
93- }
94-
95- if ( ! wsNodes || ! wsNodes . length ) {
96- return true
97- }
98-
99- if ( this . npm . flatOptions . includeWorkspaceRoot
100- && edge . to && ! edge . to . isWorkspace ) {
101- return true
102- }
103-
104- if ( edge . from . isProjectRoot ) {
105- return ( edge . to
106- && edge . to . isWorkspace
107- && wsNodes . includes ( edge . to . target ) )
108- }
109-
110- return true
111- }
11288
11389 const seenNodes = new Map ( )
11490 const problems = new Set ( )
11591
11692 const result = exploreDependencyGraph ( {
11793 node : tree ,
118- getChildren ( node , nodeResult ) {
119- const seenPaths = new Set ( )
120- const workspace = node . isWorkspace
121- const currentDepth = workspace ? 0 : node [ _depth ]
122- const target = ( node . target ) ?. edgesOut
123-
124- const shouldSkipChildren =
125- ( currentDepth > depthToPrint ) || ! nodeResult
126-
127- return ( shouldSkipChildren || ! target )
128- ? [ ]
129- : [ ...target . values ( ) ]
130- . filter ( filterBySelectedWorkspaces )
131- . filter ( currentDepth === 0 ? filterByEdgesTypes ( {
132- link,
133- omit,
134- } ) : ( ) => true )
135- . map ( mapEdgesToNodes ( { seenPaths } ) )
136- . concat ( appendExtraneousChildren ( { node, seenPaths } ) )
137- . sort ( sortAlphabetically )
138- . map ( augmentNodesWithMetadata ( {
139- args,
140- currentDepth,
141- nodeResult,
142- seenNodes,
143- } ) )
144- } ,
145- visit ( node ) {
146- // add to seenNodes as soon as we visit and not when the children are calculated in previous call
147- if ( seenNodes . has ( node . path ) ) {
148- node [ _dedupe ] = ! node [ _missing ]
149- } else {
150- seenNodes . set ( node . path , node )
151- }
152-
153- node [ _problems ] = getProblems ( node , { global } )
154-
155- const item = json
156- ? getJsonOutputItem ( node , { global, long } )
157- : parseable
158- ? {
159- pkgid : node . pkgid ,
160- path : node . path ,
161- [ _dedupe ] : node [ _dedupe ] ,
162- [ _parent ] : node [ _parent ] ,
163- }
164- : getHumanOutputItem ( node , { args, chalk, global, long } )
165-
166- // loop through list of node problems to add them to global list
167- if ( node [ _include ] ) {
168- for ( const problem of node [ _problems ] ) {
169- problems . add ( problem )
170- }
171- }
172- return item
94+ wsNodes,
95+ configs : {
96+ json,
97+ parseable,
98+ depthToPrint,
99+ workspacesEnabled,
100+ link,
101+ omit,
102+ includeWorkspaceRoot,
103+ args,
104+ chalk,
105+ global,
106+ long,
173107 } ,
174- opts : { json, parseable } ,
175108 seenNodes,
109+ problems,
176110 } )
177111
178112 // handle the special case of a broken package.json in the root folder
@@ -227,51 +161,149 @@ class LS extends ArboristWorkspaceCmd {
227161
228162module . exports = LS
229163
164+ const createWsFilter = ( wsNodes , options ) => edge => {
165+ const { workspacesEnabled, includeWorkspaceRoot } = options
166+
167+ if ( ! workspacesEnabled
168+ && edge . from . isProjectRoot
169+ && edge . to . isWorkspace
170+ ) {
171+ return false
172+ }
173+
174+ if ( ! wsNodes || ! wsNodes . length ) {
175+ return true
176+ }
177+
178+ if ( includeWorkspaceRoot
179+ && edge . to && ! edge . to . isWorkspace ) {
180+ return true
181+ }
182+
183+ if ( edge . from . isProjectRoot ) {
184+ return ( edge . to
185+ && edge . to . isWorkspace
186+ && wsNodes . includes ( edge . to . target ) )
187+ }
188+
189+ return true
190+ }
191+
192+ const visit = ( node , seenNodes , problems , opts ) => {
193+ const { json, parseable, args, chalk, global, long } = opts
194+ // add to seenNodes as soon as we visit and not when the children are calculated in previous call
195+ if ( seenNodes . has ( node . path ) ) {
196+ node [ _dedupe ] = ! node [ _missing ]
197+ } else {
198+ seenNodes . set ( node . path , node )
199+ }
200+
201+ node [ _problems ] = getProblems ( node , { global } )
202+
203+ const item = json
204+ ? getJsonOutputItem ( node , { global, long } )
205+ : parseable
206+ ? {
207+ pkgid : node . pkgid ,
208+ path : node . path ,
209+ [ _dedupe ] : node [ _dedupe ] ,
210+ [ _parent ] : node [ _parent ] ,
211+ }
212+ : getHumanOutputItem ( node , { args, chalk, global, long } )
213+
214+ // loop through list of node problems to add them to global list
215+ if ( node [ _include ] ) {
216+ for ( const problem of node [ _problems ] ) {
217+ problems . add ( problem )
218+ }
219+ }
220+ return item
221+ }
222+
223+ const getChildren = ( node , wsNodes , options ) => {
224+ const { link, omit } = options
225+ const seenPaths = new Set ( )
226+ const workspace = node . isWorkspace
227+ const currentDepth = workspace ? 0 : node [ _depth ]
228+ const target = ( node . target ) ?. edgesOut
229+ if ( ! target ) {
230+ return [ ]
231+ }
232+ return [ ...target . values ( ) ]
233+ . filter ( createWsFilter ( wsNodes , options ) )
234+ . filter ( currentDepth === 0 ? filterByEdgesTypes ( {
235+ link,
236+ omit,
237+ } ) : ( ) => true )
238+ . map ( mapEdgesToNodes ( { seenPaths } ) )
239+ . concat ( appendExtraneousChildren ( { node, seenPaths } ) )
240+ . sort ( sortAlphabetically )
241+ }
242+
230243const exploreDependencyGraph = ( {
231244 node,
232- getChildren,
233- visit,
234- opts,
245+ wsNodes,
246+ configs,
235247 seenNodes,
248+ problems,
236249 cache = new Map ( ) ,
237250 traversePathMap = new Map ( ) ,
238251} ) => {
239- const { json, parseable } = opts
252+ const { json, parseable, depthToPrint , args } = configs
240253
241254 // cahce is for already visited nodes results
242255 // if the node is already seen, we can return it from cache
243256 if ( cache . has ( node . path ) ) {
244257 return cache . get ( node . path )
245258 }
246259
247- const currentNodeResult = visit ( node )
260+ const currentNodeResult = visit ( node , seenNodes , problems , configs )
248261
249262 // how the this node is explored
250263 // so if the explored path contains this node again then it's a cycle
251264 // and we don't want to explore it again
252- const traversePath = [ ...( traversePathMap . get ( currentNodeResult [ _parent ] ) || [ ] ) ]
253- const isCircular = traversePath ?. includes ( node . pkgid )
254- traversePath . push ( node . pkgid )
255- traversePathMap . set ( currentNodeResult , traversePath )
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 )
256270
257271 // we want to start using cache after node is identified as a deduped
258272 if ( node [ _dedupe ] ) {
259273 cache . set ( node . path , currentNodeResult )
260274 }
261275
276+ const currentDepth = node . isWorkspace ? 0 : node [ _depth ]
277+ const shouldSkipChildren =
278+ ( currentDepth > depthToPrint )
279+
262280 // Get children of current node
263- const children = isCircular
281+ const children = isCircular || shouldSkipChildren || ! currentNodeResult
264282 ? [ ]
265- : getChildren ( node , currentNodeResult )
283+ : getChildren ( node , wsNodes , configs )
266284
267285 // Recurse on each child node
268286 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+
269301 const childResult = exploreDependencyGraph ( {
270302 node : child ,
271- getChildren,
272- visit,
273- opts,
303+ wsNodes,
304+ configs,
274305 seenNodes,
306+ problems,
275307 cache,
276308 traversePathMap,
277309 } )
@@ -503,28 +535,6 @@ const filterByPositionalArgs = (args, { node }) =>
503535 ( spec ) => ( node . satisfies && node . satisfies ( spec ) )
504536 ) : true
505537
506- const augmentNodesWithMetadata = ( {
507- args,
508- currentDepth,
509- nodeResult,
510- } ) => ( node ) => {
511- // _parent is going to be a ref to a traversed node (returned from
512- // getHumanOutputItem, getJsonOutputItem, etc) so that we have an easy
513- // shortcut to place new nodes in their right place during tree traversal
514- node [ _parent ] = nodeResult
515- // _include is the property that allow us to filter based on position args
516- // e.g: `npm ls foo`, `npm ls simple-output@2`
517- // _filteredBy is used to apply extra color info to the item that
518- // was used in args in order to filter
519- node [ _filteredBy ] = node [ _include ] =
520- filterByPositionalArgs ( args , { node } )
521- // _depth keeps track of how many levels deep tree traversal currently is
522- // so that we can `npm ls --depth=1`
523- node [ _depth ] = currentDepth + 1
524-
525- return node
526- }
527-
528538const sortAlphabetically = ( { pkgid : a } , { pkgid : b } ) => localeCompare ( a , b )
529539
530540const humanOutput = ( { chalk, result, unicode } ) => {
0 commit comments