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