codekingpro/portable-devtools
114k
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('node: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 