// helper function to output a clearer visualization // of the current node and its descendents const localeCompare = require('@isaacs/string-locale-compare')('en') const util = require('util') const relpath = require('./relpath.js') class ArboristNode { constructor (tree, path) { this.name = tree.name if (tree.packageName && tree.packageName !== this.name) { this.packageName = tree.packageName } if (tree.version) { this.version = tree.version } this.location = tree.location this.path = tree.path if (tree.realpath !== this.path) { this.realpath = tree.realpath } if (tree.resolved !== null) { this.resolved = tree.resolved } if (tree.extraneous) { this.extraneous = true } if (tree.dev) { this.dev = true } if (tree.optional) { this.optional = true } if (tree.devOptional && !tree.dev && !tree.optional) { this.devOptional = true } if (tree.peer) { this.peer = true } if (tree.inBundle) { this.bundled = true } if (tree.inDepBundle) { this.bundler = tree.getBundler().location } if (tree.isProjectRoot) { this.isProjectRoot = true } if (tree.isWorkspace) { this.isWorkspace = true } const bd = tree.package && tree.package.bundleDependencies if (bd && bd.length) { this.bundleDependencies = bd } if (tree.inShrinkwrap) { this.inShrinkwrap = true } else if (tree.hasShrinkwrap) { this.hasShrinkwrap = true } if (tree.error) { this.error = treeError(tree.error) } if (tree.errors && tree.errors.length) { this.errors = tree.errors.map(treeError) } if (tree.overrides) { this.overrides = new Map([...tree.overrides.ruleset.values()] .map((override) => [override.key, override.value])) } // edgesOut sorted by name if (tree.edgesOut.size) { this.edgesOut = new Map([...tree.edgesOut.entries()] .sort(([a], [b]) => localeCompare(a, b)) .map(([name, edge]) => [name, new EdgeOut(edge)])) } // edgesIn sorted by location if (tree.edgesIn.size) { this.edgesIn = new Set([...tree.edgesIn] .sort((a, b) => localeCompare(a.from.location, b.from.location)) .map(edge => new EdgeIn(edge))) } if (tree.workspaces && tree.workspaces.size) { this.workspaces = new Map([...tree.workspaces.entries()] .map(([name, path]) => [name, relpath(tree.root.realpath, path)])) } // fsChildren sorted by path if (tree.fsChildren.size) { this.fsChildren = new Set([...tree.fsChildren] .sort(({ path: a }, { path: b }) => localeCompare(a, b)) .map(tree => printableTree(tree, path))) } // children sorted by name if (tree.children.size) { this.children = new Map([...tree.children.entries()] .sort(([a], [b]) => localeCompare(a, b)) .map(([name, tree]) => [name, printableTree(tree, path)])) } } } class ArboristVirtualNode extends ArboristNode { constructor (tree, path) { super(tree, path) this.sourceReference = printableTree(tree.sourceReference, path) } } class ArboristLink extends ArboristNode { constructor (tree, path) { super(tree, path) this.target = printableTree(tree.target, path) } } const treeError = ({ code, path }) => ({ code, ...(path ? { path } : {}), }) // print out edges without dumping the full node all over again // this base class will toJSON as a plain old object, but the // util.inspect() output will be a bit cleaner class Edge { constructor (edge) { this.type = edge.type this.name = edge.name this.spec = edge.rawSpec || '*' if (edge.rawSpec !== edge.spec) { this.override = edge.spec } if (edge.error) { this.error = edge.error } if (edge.peerConflicted) { this.peerConflicted = edge.peerConflicted } } } // don't care about 'from' for edges out class EdgeOut extends Edge { constructor (edge) { super(edge) this.to = edge.to && edge.to.location } [util.inspect.custom] () { return `{ ${this.type} ${this.name}@${this.spec}${ this.override ? ` overridden:${this.override}` : '' }${ this.to ? ' -> ' + this.to : '' }${ this.error ? ' ' + this.error : '' }${ this.peerConflicted ? ' peerConflicted' : '' } }` } } // don't care about 'to' for edges in class EdgeIn extends Edge { constructor (edge) { super(edge) this.from = edge.from && edge.from.location } [util.inspect.custom] () { return `{ ${this.from || '""'} ${this.type} ${this.name}@${this.spec}${ this.error ? ' ' + this.error : '' }${ this.peerConflicted ? ' peerConflicted' : '' } }` } } const printableTree = (tree, path = []) => { if (!tree) { return tree } const Cls = tree.isLink ? ArboristLink : tree.sourceReference ? ArboristVirtualNode : ArboristNode if (path.includes(tree)) { const obj = Object.create(Cls.prototype) return Object.assign(obj, { location: tree.location }) } path.push(tree) return new Cls(tree, path) } module.exports = printableTree