1
1
const { resolve, relative, sep } = require ( 'node:path' )
2
2
const archy = require ( 'archy' )
3
- const { breadth } = require ( 'treeverse' )
4
3
const npa = require ( 'npm-package-arg' )
5
- const { output } = require ( 'proc-log' )
4
+ const { output, log } = require ( 'proc-log' )
6
5
const ArboristWorkspaceCmd = require ( '../arborist-cmd.js' )
7
6
const localeCompare = require ( '@isaacs/string-locale-compare' ) ( 'en' )
8
7
@@ -20,6 +19,72 @@ const _problems = Symbol('problems')
20
19
const _required = Symbol ( 'required' )
21
20
const _type = Symbol ( 'type' )
22
21
22
+ function bfs ( tree , visit , getChildren ) {
23
+ const queue = [ tree ]
24
+ let finalResult = null
25
+ const seenNodes = new Map ( )
26
+ const problems = new Set ( )
27
+
28
+ seenNodes . set ( tree . path , tree )
29
+ while ( queue . length > 0 ) {
30
+ const node = queue . shift ( )
31
+
32
+ const result = visit ( node , problems )
33
+ if ( ! finalResult ) {
34
+ finalResult = result
35
+ }
36
+ queue . push ( ...getChildren ( node , result , seenNodes ) )
37
+ }
38
+ return [ finalResult , seenNodes , problems ]
39
+ }
40
+
41
+ const deepCopyNodes = ( nodes ) => {
42
+ return nodes . map ( child => {
43
+ const copiedChild = { ...child }
44
+ if ( child . nodes && Array . isArray ( child . nodes ) ) {
45
+ copiedChild . nodes = deepCopyNodes ( child . nodes )
46
+ }
47
+ return copiedChild
48
+ } )
49
+ }
50
+
51
+ function dfs (
52
+ node ,
53
+ getChildren ,
54
+ visit ,
55
+ seenNodes = new Map ( ) ,
56
+ problems = new Set ( ) ,
57
+ cache = new Map ( ) ,
58
+ encounterCount = new Map ( )
59
+ ) {
60
+ // Track the number of encounters for the current node
61
+ // Why because we want to start storing after the node is identified as a deduped edge
62
+ const count = ( encounterCount . get ( node . path ) || 0 ) + 1
63
+ encounterCount . set ( node . path , count )
64
+
65
+ // Start caching only after the third encounter
66
+ if ( cache . has ( node . path ) ) {
67
+ return [ cache . get ( node . path ) , seenNodes , problems ]
68
+ }
69
+
70
+ const result = visit ( node , problems )
71
+ if ( count > 1 ) {
72
+ cache . set ( node . path , result )
73
+ }
74
+
75
+ // Get children of current node
76
+ const children = getChildren ( node , result , seenNodes )
77
+
78
+ // Recurse on each child
79
+ for ( const child of children ) {
80
+ const [ cResult ] = dfs ( child , getChildren , visit , seenNodes , problems , cache , encounterCount )
81
+ result [ _include ] = result [ _include ] || cResult [ _include ]
82
+ cResult [ _include ] && result . nodes . push ( cResult )
83
+ }
84
+
85
+ return [ result , seenNodes , problems ]
86
+ }
87
+
23
88
class LS extends ArboristWorkspaceCmd {
24
89
static description = 'List installed packages'
25
90
static name = 'ls'
@@ -105,94 +170,80 @@ class LS extends ArboristWorkspaceCmd {
105
170
return true
106
171
}
107
172
108
- const seenItems = new Set ( )
109
- const seenNodes = new Map ( )
110
- const problems = new Set ( )
173
+ const getChildren = ( node , nodeResult , seenNodes ) => {
174
+ const seenPaths = new Set ( )
175
+ const workspace = node . isWorkspace
176
+ const currentDepth = workspace ? 0 : node [ _depth ]
177
+ const target = ( node . target ) ?. edgesOut
178
+
179
+ const shouldSkipChildren =
180
+ ( currentDepth > depthToPrint )
181
+ || nodeResult ?. isCircular
182
+
183
+ return ( shouldSkipChildren || ! target )
184
+ ? [ ]
185
+ : [ ...target . values ( ) ]
186
+ . filter ( filterBySelectedWorkspaces )
187
+ . filter ( currentDepth === 0 ? filterByEdgesTypes ( {
188
+ link,
189
+ omit,
190
+ } ) : ( ) => true )
191
+ . map ( mapEdgesToNodes ( { seenPaths } ) )
192
+ . concat ( appendExtraneousChildren ( { node, seenPaths } ) )
193
+ . sort ( sortAlphabetically )
194
+ . map ( augmentNodesWithMetadata ( {
195
+ args,
196
+ currentDepth,
197
+ nodeResult,
198
+ seenNodes,
199
+ } ) )
200
+ }
201
+
202
+ const visit = ( node , problems ) => {
203
+ node [ _problems ] = getProblems ( node , { global } )
111
204
205
+ const item = json
206
+ ? getJsonOutputItem ( node , { global, long } )
207
+ : parseable
208
+ ? { }
209
+ : getHumanOutputItem ( node , { args, chalk, global, long } )
210
+
211
+ // loop through list of node problems to add them to global list
212
+ if ( node [ _include ] ) {
213
+ for ( const problem of node [ _problems ] ) {
214
+ problems . add ( problem )
215
+ }
216
+ }
217
+
218
+ item . traversePath = ( node [ _parent ] ?. traversePath || [ ] ) . concat ( [ node . realpath + '-' + node . pkgid ] )
219
+
220
+ if ( node [ _parent ] ?. traversePath ?. includes ( node . realpath + '-' + node . pkgid ) ) {
221
+ item . isCircular = true
222
+ }
223
+
224
+ // return a promise so we don't blow the stack
225
+ return item
226
+ }
112
227
// defines special handling of printed depth when filtering with args
113
228
const filterDefaultDepth = depth === null ? Infinity : depth
114
229
const depthToPrint = ( all || args . length )
115
230
? filterDefaultDepth
116
231
: ( depth || 0 )
117
232
118
233
// add root node of tree to list of seenNodes
119
- seenNodes . set ( tree . path , tree )
120
-
121
- // tree traversal happens here, using treeverse.breadth
122
- const result = await breadth ( {
123
- tree,
124
- // recursive method, `node` is going to be the current elem (starting from
125
- // the `tree` obj) that was just visited in the `visit` method below
126
- // `nodeResult` is going to be the returned `item` from `visit`
127
- getChildren ( node , nodeResult ) {
128
- const seenPaths = new Set ( )
129
- const workspace = node . isWorkspace
130
- const currentDepth = workspace ? 0 : node [ _depth ]
131
- const target = ( node . target ) ?. edgesOut
132
-
133
- const shouldSkipChildren =
134
- ( currentDepth > depthToPrint )
135
- || nodeResult ?. isCircular
136
-
137
- return ( shouldSkipChildren || ! target )
138
- ? [ ]
139
- : [ ...target . values ( ) ]
140
- . filter ( filterBySelectedWorkspaces )
141
- . filter ( currentDepth === 0 ? filterByEdgesTypes ( {
142
- link,
143
- omit,
144
- } ) : ( ) => true )
145
- . map ( mapEdgesToNodes ( { seenPaths } ) )
146
- . concat ( appendExtraneousChildren ( { node, seenPaths } ) )
147
- . sort ( sortAlphabetically )
148
- . map ( augmentNodesWithMetadata ( {
149
- args,
150
- currentDepth,
151
- nodeResult,
152
- seenNodes,
153
- } ) )
154
- } ,
155
- // visit each `node` of the `tree`, returning an `item` - these are
156
- // the elements that will be used to build the final output
157
- visit ( node ) {
158
- node [ _problems ] = getProblems ( node , { global } )
159
-
160
- const item = json
161
- ? getJsonOutputItem ( node , { global, long } )
162
- : parseable
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
-
173
- item . traversePath = ( node [ _parent ] ?. traversePath || [ ] ) . concat ( [ node . realpath + '-' + node . pkgid ] )
174
234
175
- if ( node [ _parent ] ?. traversePath ?. includes ( node . realpath + '-' + node . pkgid ) ) {
176
- item . isCircular = true
177
- }
178
-
179
- seenItems . add ( item )
180
-
181
- // return a promise so we don't blow the stack
182
- return Promise . resolve ( item )
183
- } ,
184
- } )
235
+ const [ result , seenNodes , problems ] = dfs ( tree , getChildren , visit )
185
236
186
237
// handle the special case of a broken package.json in the root folder
187
238
const [ rootError ] = tree . errors . filter ( e =>
188
239
e . code === 'EJSONPARSE' && e . path === resolve ( path , 'package.json' ) )
189
240
190
241
if ( json ) {
191
- output . buffer ( jsonOutput ( { path, problems, result, rootError, seenItems } ) )
242
+ output . buffer ( jsonOutput ( { path, problems, result, rootError } ) )
192
243
} else {
193
244
output . standard ( parseable
194
245
? parseableOutput ( { seenNodes, global, long } )
195
- : humanOutput ( { chalk, result, seenItems , unicode } )
246
+ : humanOutput ( { chalk, result, unicode } )
196
247
)
197
248
}
198
249
@@ -275,19 +326,19 @@ const getProblems = (node, { global }) => {
275
326
// annotates _parent and _include metadata into the resulting
276
327
// item obj allowing for filtering out results during output
277
328
const augmentItemWithIncludeMetadata = ( node , item ) => {
278
- item [ _parent ] = node [ _parent ]
279
- item [ _include ] = node [ _include ]
329
+ // item[_parent] = node[_parent]
330
+ // item[_include] = node[_include]
280
331
281
332
// append current item to its parent.nodes which is the
282
333
// structure expected by archy in order to print tree
283
- if ( node [ _include ] ) {
284
- // includes all ancestors of included node
285
- let p = node [ _parent ]
286
- while ( p ) {
287
- p [ _include ] = true
288
- p = p [ _parent ]
289
- }
290
- }
334
+ // if (node[_include]) {
335
+ // // includes all ancestors of included node
336
+ // let p = node[_parent]
337
+ // while (p) {
338
+ // p[_include] = true
339
+ // p = p[_parent]
340
+ // }
341
+ // }
291
342
292
343
return item
293
344
}
@@ -351,7 +402,7 @@ const getHumanOutputItem = (node, { args, chalk, global, long }) => {
351
402
( node . isLink ? ` -> ${ relativePrefix } ${ targetLocation } ` : '' ) +
352
403
( long ? `\n${ node . package . description || '' } ` : '' )
353
404
354
- return augmentItemWithIncludeMetadata ( node , { label, nodes : [ ] } )
405
+ return ( { label, nodes : [ ] , [ _include ] : node [ _include ] , [ _parent ] : node [ _parent ] } )
355
406
}
356
407
357
408
const getJsonOutputItem = ( node , { global, long } ) => {
@@ -520,18 +571,18 @@ const augmentNodesWithMetadata = ({
520
571
return node
521
572
}
522
573
523
- const sortAlphabetically = ( { pkgid : a } , { pkgid : b } ) => localeCompare ( a , b )
574
+ const sortAlphabetically = ( { pkgid : a } , { pkgid : b } ) => localeCompare ( b , a )
524
575
525
576
const humanOutput = ( { chalk, result, seenItems, unicode } ) => {
526
577
// we need to traverse the entire tree in order to determine which items
527
578
// should be included (since a nested transitive included dep will make it
528
579
// so that all its ancestors should be displayed)
529
580
// here is where we put items in their expected place for archy output
530
- for ( const item of seenItems ) {
531
- if ( item [ _include ] && item [ _parent ] ) {
532
- item [ _parent ] . nodes . push ( item )
533
- }
534
- }
581
+ // for (const item of seenItems) {
582
+ // if (item[_include] && item[_parent]) {
583
+ // item[_parent].nodes.push(item)
584
+ // }
585
+ // }
535
586
536
587
if ( ! result . nodes . length ) {
537
588
result . nodes = [ '(empty)' ]
0 commit comments