@@ -58,6 +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
62
62
63
const path = global ? resolve ( this . npm . globalDir , '..' ) : this . npm . prefix
63
64
@@ -84,95 +85,28 @@ class LS extends ArboristWorkspaceCmd {
84
85
if ( this . workspaceNames && this . workspaceNames . length ) {
85
86
wsNodes = arb . workspaceNodes ( tree , this . workspaceNames )
86
87
}
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
- }
112
88
113
89
const seenNodes = new Map ( )
114
90
const problems = new Set ( )
115
91
116
92
const result = exploreDependencyGraph ( {
117
93
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,
173
107
} ,
174
- opts : { json, parseable } ,
175
108
seenNodes,
109
+ problems,
176
110
} )
177
111
178
112
// handle the special case of a broken package.json in the root folder
@@ -227,51 +161,149 @@ class LS extends ArboristWorkspaceCmd {
227
161
228
162
module . exports = LS
229
163
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
+
230
243
const exploreDependencyGraph = ( {
231
244
node,
232
- getChildren,
233
- visit,
234
- opts,
245
+ wsNodes,
246
+ configs,
235
247
seenNodes,
248
+ problems,
236
249
cache = new Map ( ) ,
237
250
traversePathMap = new Map ( ) ,
238
251
} ) => {
239
- const { json, parseable } = opts
252
+ const { json, parseable, depthToPrint , args } = configs
240
253
241
254
// cahce is for already visited nodes results
242
255
// if the node is already seen, we can return it from cache
243
256
if ( cache . has ( node . path ) ) {
244
257
return cache . get ( node . path )
245
258
}
246
259
247
- const currentNodeResult = visit ( node )
260
+ const currentNodeResult = visit ( node , seenNodes , problems , configs )
248
261
249
262
// how the this node is explored
250
263
// so if the explored path contains this node again then it's a cycle
251
264
// 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 )
256
270
257
271
// we want to start using cache after node is identified as a deduped
258
272
if ( node [ _dedupe ] ) {
259
273
cache . set ( node . path , currentNodeResult )
260
274
}
261
275
276
+ const currentDepth = node . isWorkspace ? 0 : node [ _depth ]
277
+ const shouldSkipChildren =
278
+ ( currentDepth > depthToPrint )
279
+
262
280
// Get children of current node
263
- const children = isCircular
281
+ const children = isCircular || shouldSkipChildren || ! currentNodeResult
264
282
? [ ]
265
- : getChildren ( node , currentNodeResult )
283
+ : getChildren ( node , wsNodes , configs )
266
284
267
285
// Recurse on each child node
268
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
+
269
301
const childResult = exploreDependencyGraph ( {
270
302
node : child ,
271
- getChildren,
272
- visit,
273
- opts,
303
+ wsNodes,
304
+ configs,
274
305
seenNodes,
306
+ problems,
275
307
cache,
276
308
traversePathMap,
277
309
} )
@@ -503,28 +535,6 @@ const filterByPositionalArgs = (args, { node }) =>
503
535
( spec ) => ( node . satisfies && node . satisfies ( spec ) )
504
536
) : true
505
537
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
-
528
538
const sortAlphabetically = ( { pkgid : a } , { pkgid : b } ) => localeCompare ( a , b )
529
539
530
540
const humanOutput = ( { chalk, result, unicode } ) => {
0 commit comments