• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// helper function to output a clearer visualization
2// of the current node and its descendents
3const localeCompare = require('@isaacs/string-locale-compare')('en')
4const util = require('util')
5const relpath = require('./relpath.js')
6
7class ArboristNode {
8  constructor (tree, path) {
9    this.name = tree.name
10    if (tree.packageName && tree.packageName !== this.name) {
11      this.packageName = tree.packageName
12    }
13    if (tree.version) {
14      this.version = tree.version
15    }
16    this.location = tree.location
17    this.path = tree.path
18    if (tree.realpath !== this.path) {
19      this.realpath = tree.realpath
20    }
21    if (tree.resolved !== null) {
22      this.resolved = tree.resolved
23    }
24    if (tree.extraneous) {
25      this.extraneous = true
26    }
27    if (tree.dev) {
28      this.dev = true
29    }
30    if (tree.optional) {
31      this.optional = true
32    }
33    if (tree.devOptional && !tree.dev && !tree.optional) {
34      this.devOptional = true
35    }
36    if (tree.peer) {
37      this.peer = true
38    }
39    if (tree.inBundle) {
40      this.bundled = true
41    }
42    if (tree.inDepBundle) {
43      this.bundler = tree.getBundler().location
44    }
45    if (tree.isProjectRoot) {
46      this.isProjectRoot = true
47    }
48    if (tree.isWorkspace) {
49      this.isWorkspace = true
50    }
51    const bd = tree.package && tree.package.bundleDependencies
52    if (bd && bd.length) {
53      this.bundleDependencies = bd
54    }
55    if (tree.inShrinkwrap) {
56      this.inShrinkwrap = true
57    } else if (tree.hasShrinkwrap) {
58      this.hasShrinkwrap = true
59    }
60    if (tree.error) {
61      this.error = treeError(tree.error)
62    }
63    if (tree.errors && tree.errors.length) {
64      this.errors = tree.errors.map(treeError)
65    }
66
67    if (tree.overrides) {
68      this.overrides = new Map([...tree.overrides.ruleset.values()]
69        .map((override) => [override.key, override.value]))
70    }
71
72    // edgesOut sorted by name
73    if (tree.edgesOut.size) {
74      this.edgesOut = new Map([...tree.edgesOut.entries()]
75        .sort(([a], [b]) => localeCompare(a, b))
76        .map(([name, edge]) => [name, new EdgeOut(edge)]))
77    }
78
79    // edgesIn sorted by location
80    if (tree.edgesIn.size) {
81      this.edgesIn = new Set([...tree.edgesIn]
82        .sort((a, b) => localeCompare(a.from.location, b.from.location))
83        .map(edge => new EdgeIn(edge)))
84    }
85
86    if (tree.workspaces && tree.workspaces.size) {
87      this.workspaces = new Map([...tree.workspaces.entries()]
88        .map(([name, path]) => [name, relpath(tree.root.realpath, path)]))
89    }
90
91    // fsChildren sorted by path
92    if (tree.fsChildren.size) {
93      this.fsChildren = new Set([...tree.fsChildren]
94        .sort(({ path: a }, { path: b }) => localeCompare(a, b))
95        .map(tree => printableTree(tree, path)))
96    }
97
98    // children sorted by name
99    if (tree.children.size) {
100      this.children = new Map([...tree.children.entries()]
101        .sort(([a], [b]) => localeCompare(a, b))
102        .map(([name, tree]) => [name, printableTree(tree, path)]))
103    }
104  }
105}
106
107class ArboristVirtualNode extends ArboristNode {
108  constructor (tree, path) {
109    super(tree, path)
110    this.sourceReference = printableTree(tree.sourceReference, path)
111  }
112}
113
114class ArboristLink extends ArboristNode {
115  constructor (tree, path) {
116    super(tree, path)
117    this.target = printableTree(tree.target, path)
118  }
119}
120
121const treeError = ({ code, path }) => ({
122  code,
123  ...(path ? { path } : {}),
124})
125
126// print out edges without dumping the full node all over again
127// this base class will toJSON as a plain old object, but the
128// util.inspect() output will be a bit cleaner
129class Edge {
130  constructor (edge) {
131    this.type = edge.type
132    this.name = edge.name
133    this.spec = edge.rawSpec || '*'
134    if (edge.rawSpec !== edge.spec) {
135      this.override = edge.spec
136    }
137    if (edge.error) {
138      this.error = edge.error
139    }
140    if (edge.peerConflicted) {
141      this.peerConflicted = edge.peerConflicted
142    }
143  }
144}
145
146// don't care about 'from' for edges out
147class EdgeOut extends Edge {
148  constructor (edge) {
149    super(edge)
150    this.to = edge.to && edge.to.location
151  }
152
153  [util.inspect.custom] () {
154    return `{ ${this.type} ${this.name}@${this.spec}${
155      this.override ? ` overridden:${this.override}` : ''
156    }${
157      this.to ? ' -> ' + this.to : ''
158    }${
159      this.error ? ' ' + this.error : ''
160    }${
161      this.peerConflicted ? ' peerConflicted' : ''
162    } }`
163  }
164}
165
166// don't care about 'to' for edges in
167class EdgeIn extends Edge {
168  constructor (edge) {
169    super(edge)
170    this.from = edge.from && edge.from.location
171  }
172
173  [util.inspect.custom] () {
174    return `{ ${this.from || '""'} ${this.type} ${this.name}@${this.spec}${
175      this.error ? ' ' + this.error : ''
176    }${
177      this.peerConflicted ? ' peerConflicted' : ''
178    } }`
179  }
180}
181
182const printableTree = (tree, path = []) => {
183  if (!tree) {
184    return tree
185  }
186
187  const Cls = tree.isLink ? ArboristLink
188    : tree.sourceReference ? ArboristVirtualNode
189    : ArboristNode
190  if (path.includes(tree)) {
191    const obj = Object.create(Cls.prototype)
192    return Object.assign(obj, { location: tree.location })
193  }
194  path.push(tree)
195  return new Cls(tree, path)
196}
197
198module.exports = printableTree
199