Skip to content

Commit 9a229f5

Browse files
author
Milan Meva
committed
refactor
1 parent 7daaf64 commit 9a229f5

File tree

1 file changed

+127
-117
lines changed

1 file changed

+127
-117
lines changed

lib/commands/ls.js

Lines changed: 127 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -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

228162
module.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+
230243
const 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-
528538
const sortAlphabetically = ({ pkgid: a }, { pkgid: b }) => localeCompare(a, b)
529539

530540
const humanOutput = ({ chalk, result, unicode }) => {

0 commit comments

Comments
 (0)