Skip to content

Commit 6c55568

Browse files
committed
fix: ls
1 parent d092edf commit 6c55568

File tree

1 file changed

+140
-89
lines changed

1 file changed

+140
-89
lines changed

lib/commands/ls.js

Lines changed: 140 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
const { resolve, relative, sep } = require('node:path')
22
const archy = require('archy')
3-
const { breadth } = require('treeverse')
43
const npa = require('npm-package-arg')
5-
const { output } = require('proc-log')
4+
const { output, log } = require('proc-log')
65
const ArboristWorkspaceCmd = require('../arborist-cmd.js')
76
const localeCompare = require('@isaacs/string-locale-compare')('en')
87

@@ -20,6 +19,72 @@ const _problems = Symbol('problems')
2019
const _required = Symbol('required')
2120
const _type = Symbol('type')
2221

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+
2388
class LS extends ArboristWorkspaceCmd {
2489
static description = 'List installed packages'
2590
static name = 'ls'
@@ -105,94 +170,80 @@ class LS extends ArboristWorkspaceCmd {
105170
return true
106171
}
107172

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 })
111204

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+
}
112227
// defines special handling of printed depth when filtering with args
113228
const filterDefaultDepth = depth === null ? Infinity : depth
114229
const depthToPrint = (all || args.length)
115230
? filterDefaultDepth
116231
: (depth || 0)
117232

118233
// 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])
174234

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)
185236

186237
// handle the special case of a broken package.json in the root folder
187238
const [rootError] = tree.errors.filter(e =>
188239
e.code === 'EJSONPARSE' && e.path === resolve(path, 'package.json'))
189240

190241
if (json) {
191-
output.buffer(jsonOutput({ path, problems, result, rootError, seenItems }))
242+
output.buffer(jsonOutput({ path, problems, result, rootError }))
192243
} else {
193244
output.standard(parseable
194245
? parseableOutput({ seenNodes, global, long })
195-
: humanOutput({ chalk, result, seenItems, unicode })
246+
: humanOutput({ chalk, result, unicode })
196247
)
197248
}
198249

@@ -275,19 +326,19 @@ const getProblems = (node, { global }) => {
275326
// annotates _parent and _include metadata into the resulting
276327
// item obj allowing for filtering out results during output
277328
const augmentItemWithIncludeMetadata = (node, item) => {
278-
item[_parent] = node[_parent]
279-
item[_include] = node[_include]
329+
// item[_parent] = node[_parent]
330+
// item[_include] = node[_include]
280331

281332
// append current item to its parent.nodes which is the
282333
// 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+
// }
291342

292343
return item
293344
}
@@ -351,7 +402,7 @@ const getHumanOutputItem = (node, { args, chalk, global, long }) => {
351402
(node.isLink ? ` -> ${relativePrefix}${targetLocation}` : '') +
352403
(long ? `\n${node.package.description || ''}` : '')
353404

354-
return augmentItemWithIncludeMetadata(node, { label, nodes: [] })
405+
return ({ label, nodes: [], [_include]: node[_include], [_parent]: node[_parent] })
355406
}
356407

357408
const getJsonOutputItem = (node, { global, long }) => {
@@ -520,18 +571,18 @@ const augmentNodesWithMetadata = ({
520571
return node
521572
}
522573

523-
const sortAlphabetically = ({ pkgid: a }, { pkgid: b }) => localeCompare(a, b)
574+
const sortAlphabetically = ({ pkgid: a }, { pkgid: b }) => localeCompare(b, a)
524575

525576
const humanOutput = ({ chalk, result, seenItems, unicode }) => {
526577
// we need to traverse the entire tree in order to determine which items
527578
// should be included (since a nested transitive included dep will make it
528579
// so that all its ancestors should be displayed)
529580
// 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+
// }
535586

536587
if (!result.nodes.length) {
537588
result.nodes = ['(empty)']

0 commit comments

Comments
 (0)