From bf09e719c7f563a255b1e9af6b1237ebc5598db6 Mon Sep 17 00:00:00 2001 From: isaacs Date: Tue, 8 Dec 2020 15:25:15 -0800 Subject: [PATCH] @npmcli/arborist@2.0.0 Bumps libnpmfund so we don't have a dupe --- node_modules/@npmcli/arborist/CHANGELOG.md | 19 + .../arborist/lib/arborist/build-ideal-tree.js | 79 +-- .../arborist/lib/arborist/load-actual.js | 22 +- .../arborist/lib/arborist/load-virtual.js | 81 +-- .../@npmcli/arborist/lib/arborist/reify.js | 3 +- .../@npmcli/arborist/lib/audit-report.js | 9 +- node_modules/@npmcli/arborist/lib/debug.js | 10 +- .../@npmcli/arborist/lib/dep-valid.js | 2 +- .../@npmcli/arborist/lib/inventory.js | 23 +- node_modules/@npmcli/arborist/lib/link.js | 80 ++- node_modules/@npmcli/arborist/lib/node.js | 648 +++++++++++------- .../@npmcli/arborist/lib/shrinkwrap.js | 20 +- .../@npmcli/arborist/lib/tree-check.js | 104 +++ node_modules/@npmcli/arborist/package.json | 18 +- node_modules/libnpmfund/package.json | 4 +- package-lock.json | 318 ++++++++- package.json | 4 +- 17 files changed, 1011 insertions(+), 433 deletions(-) create mode 100644 node_modules/@npmcli/arborist/CHANGELOG.md create mode 100644 node_modules/@npmcli/arborist/lib/tree-check.js diff --git a/node_modules/@npmcli/arborist/CHANGELOG.md b/node_modules/@npmcli/arborist/CHANGELOG.md new file mode 100644 index 0000000000000..3cd36d027b631 --- /dev/null +++ b/node_modules/@npmcli/arborist/CHANGELOG.md @@ -0,0 +1,19 @@ +# CHANGELOG + +## 2.0 + +* BREAKING CHANGE: root node is now included in inventory +* All parent/target/fsParent/etc. references set in `root` setter, rather + than the hodgepodge of setters that existed before. +* `treeCheck` function added, to enforce strict correctness guarantees when + `ARBORIST_DEBUG=1` in the environment (on by default in Arborist tests). + +## 1.0 + +* Release for npm v7 beta +* Fully functional + +## 0.0 + +* Proof of concept +* Before this, it was [`read-package-tree`](http://npm.im/read-package-tree) diff --git a/node_modules/@npmcli/arborist/lib/arborist/build-ideal-tree.js b/node_modules/@npmcli/arborist/lib/arborist/build-ideal-tree.js index 579d5740da4f7..731b78518775c 100644 --- a/node_modules/@npmcli/arborist/lib/arborist/build-ideal-tree.js +++ b/node_modules/@npmcli/arborist/lib/arborist/build-ideal-tree.js @@ -7,9 +7,9 @@ const semver = require('semver') const promiseCallLimit = require('promise-call-limit') const getPeerSet = require('../peer-set.js') const realpath = require('../../lib/realpath.js') -const walkUpPath = require('walk-up-path') -const { dirname, resolve } = require('path') +const { resolve } = require('path') const { promisify } = require('util') +const treeCheck = require('../tree-check.js') const readdir = promisify(require('readdir-scoped-modules')) const debug = require('../debug.js') @@ -215,7 +215,7 @@ module.exports = cls => class IdealTreeBuilder extends cls { this.finishTracker('idealTree') } - return this.idealTree + return treeCheck(this.idealTree) } [_checkEngineAndPlatform] () { @@ -384,7 +384,8 @@ module.exports = cls => class IdealTreeBuilder extends cls { await this[_add](options) // triggers a refresh of all edgesOut - this.idealTree.package = this.idealTree.package + if (options.add && options.add.length || options.rm && options.rm.length) + this.idealTree.package = this.idealTree.package process.emit('timeEnd', 'idealTree:userRequests') } @@ -599,21 +600,29 @@ This is a one-time fix-up, please be patient... this.addTracker('idealTree:inflate') const queue = [] for (const node of inventory.values()) { + if (node.isRoot) + continue + queue.push(async () => { this.log.silly('inflate', node.location) - const id = `${node.name}@${node.version}` - const sloc = node.location.substr('node_modules/'.length) + const { resolved, version, path, name, location, integrity } = node + // don't try to hit the registry for linked deps + const useResolved = !version || + resolved && resolved.startsWith('file:') + const id = useResolved ? resolved : version + const spec = npa.resolve(name, id, path) + const sloc = location.substr('node_modules/'.length) const t = `idealTree:inflate:${sloc}` this.addTracker(t) - await pacote.manifest(id, { + await pacote.manifest(spec, { ...this.options, - resolved: node.resolved, - integrity: node.integrity, + resolved: resolved, + integrity: integrity, fullMetadata: false, }).then(mani => { node.package = { ...mani, _id: `${mani.name}@${mani.version}` } }).catch((er) => { - const warning = `Could not fetch metadata for ${id}` + const warning = `Could not fetch metadata for ${name}@${id}` this.log.warn(heading, warning, er) }) this.finishTracker(t) @@ -825,6 +834,7 @@ This is a one-time fix-up, please be patient... // is requesting this one, so that we can get all the peer deps in // a context where they're likely to be resolvable. const parent = parent_ || this[_virtualRoot](edge.from) + const realParent = edge.peer ? edge.from.resolveParent : edge.from const spec = npa.resolve(edge.name, edge.spec, edge.from.path) return this[_nodeFromSpec](edge.name, spec, parent, edge) @@ -836,7 +846,7 @@ This is a one-time fix-up, please be patient... // a symbolic link to the earlier instance for (let p = edge.from.resolveParent; p; p = p.resolveParent) { if (p.matches(node) && !p.isRoot) - return new Link({ parent, target: p }) + return new Link({ parent: realParent, target: p }) } // keep track of the thing that caused this node to be included. const src = parent.sourceReference @@ -1160,7 +1170,7 @@ This is a one-time fix-up, please be patient... integrity: dep.integrity, legacyPeerDeps: this.legacyPeerDeps, error: dep.errors[0], - ...(dep.target ? { target: dep.target } : {}), + ...(dep.target ? { target: dep.target, realpath: dep.target.path } : {}), }) if (this[_loadFailures].has(dep)) this[_loadFailures].add(newDep) @@ -1235,6 +1245,8 @@ This is a one-time fix-up, please be patient... // +-- c2 <-- pruning this would be bad const mask = node.parent !== target && + node.parent && + node.parent.parent && node.parent.parent !== target && node.parent.parent.resolve(newDep.name) @@ -1550,30 +1562,12 @@ This is a one-time fix-up, please be patient... [_resolveLinks] () { for (const link of this[_linkNodes]) { this[_linkNodes].delete(link) - const realpath = link.realpath - const loc = relpath(this.path, realpath) - const fromInv = this.idealTree.inventory.get(loc) - if (fromInv && fromInv !== link.target) - link.target = fromInv - - const external = /^\.\.(\/|$)/.test(loc) - - if (!link.target.parent && !link.target.fsParent) { - // the fsParent likely some node in the tree, possibly the root, - // unless it is external. find it by walking up. Note that this - // is where its deps may end up being installed, if possible. - for (const p of walkUpPath(dirname(realpath))) { - const path = relpath(this.path, p) - const node = !path ? this.idealTree - : this.idealTree.inventory.get(path) - if (node) { - link.target.fsParent = node - this.addTracker('idealTree', link.target.name, link.target.location) - this[_depsQueue].push(link.target) - break - } - } - } + + // link we never ended up placing, skip it + if (link.root !== this.idealTree) + continue + + const external = /^\.\.(\/|$)/.test(relpath(this.path, link.realpath)) // outside the root, somebody else's problem, ignore it if (external && !this[_follow]) @@ -1581,12 +1575,13 @@ This is a one-time fix-up, please be patient... // didn't find a parent for it or it has not been seen yet // so go ahead and process it. - const unseenLink = (link.target.parent || link.target.fsParent) - && !this[_depsSeen].has(link.target) - if (this[_follow] - && !link.target.parent - && !link.target.fsParent - || unseenLink) { + const unseenLink = (link.target.parent || link.target.fsParent) && + !this[_depsSeen].has(link.target) + + if (this[_follow] && + !link.target.parent && + !link.target.fsParent || + unseenLink) { this.addTracker('idealTree', link.target.name, link.target.location) this[_depsQueue].push(link.target) } diff --git a/node_modules/@npmcli/arborist/lib/arborist/load-actual.js b/node_modules/@npmcli/arborist/lib/arborist/load-actual.js index 22ce9cc8fc1b4..abf39e5dc1757 100644 --- a/node_modules/@npmcli/arborist/lib/arborist/load-actual.js +++ b/node_modules/@npmcli/arborist/lib/arborist/load-actual.js @@ -7,6 +7,7 @@ const {promisify} = require('util') const readdir = promisify(require('readdir-scoped-modules')) const walkUp = require('walk-up-path') const ancestorPath = require('common-ancestor-path') +const treeCheck = require('../tree-check.js') const Shrinkwrap = require('../shrinkwrap.js') const calcDepFlags = require('../calc-dep-flags.js') @@ -38,6 +39,7 @@ const _transplantFilter = Symbol('transplantFilter') const _filter = Symbol('filter') const _global = Symbol.for('global') +const _changePath = Symbol.for('_changePath') module.exports = cls => class ActualLoader extends cls { constructor (options) { @@ -85,7 +87,7 @@ module.exports = cls => class ActualLoader extends cls { return this.actualTree ? this.actualTree : this[_actualTreePromise] ? this[_actualTreePromise] : this[_actualTreePromise] = this[_loadActual](options) - .then(tree => this.actualTree = tree) + .then(tree => this.actualTree = treeCheck(tree)) } async [_loadActual] (options) { @@ -166,19 +168,15 @@ module.exports = cls => class ActualLoader extends cls { } [_transplant] (root) { - if (!root) + if (!root || root === this[_actualTree]) return - // have to set the fsChildren first, because re-rooting a Link - // re-roots the target, but without updating its realpath, so - // we have to re-root the targets first so their location is - // updated appropriately. - for (const node of this[_actualTree].fsChildren) - node.fsParent = root - + this[_actualTree][_changePath](root.path) for (const node of this[_actualTree].children.values()) { - if (this[_transplantFilter](node)) - node.parent = root + if (!this[_transplantFilter](node)) + node.parent = null } + + root.replace(this[_actualTree]) this[_actualTree] = root } @@ -322,7 +320,7 @@ module.exports = cls => class ActualLoader extends cls { const depPromises = [] for (const [name, edge] of node.edgesOut.entries()) { - if (!edge.missing && !(edge.to && edge.to.dummy)) + if (!edge.missing && !(edge.to && (edge.to.dummy || edge.to.parent !== node))) continue // start the walk from the dirname, because we would have found diff --git a/node_modules/@npmcli/arborist/lib/arborist/load-virtual.js b/node_modules/@npmcli/arborist/lib/arborist/load-virtual.js index e335bdadd4541..f03bd80c460de 100644 --- a/node_modules/@npmcli/arborist/lib/arborist/load-virtual.js +++ b/node_modules/@npmcli/arborist/lib/arborist/load-virtual.js @@ -1,7 +1,6 @@ // mixin providing the loadVirtual method -const {dirname, resolve} = require('path') -const walkUp = require('walk-up-path') +const {resolve} = require('path') const nameFromFolder = require('@npmcli/name-from-folder') const consistentResolve = require('../consistent-resolve.js') @@ -11,11 +10,12 @@ const Link = require('../link.js') const relpath = require('../relpath.js') const calcDepFlags = require('../calc-dep-flags.js') const rpj = require('read-package-json-fast') +const treeCheck = require('../tree-check.js') const loadFromShrinkwrap = Symbol('loadFromShrinkwrap') const resolveNodes = Symbol('resolveNodes') const resolveLinks = Symbol('resolveLinks') -const assignParentage = Symbol('assignParentage') +const assignBundles = Symbol('assignBundles') const loadRoot = Symbol('loadRoot') const loadNode = Symbol('loadVirtualNode') const loadLink = Symbol('loadVirtualLink') @@ -40,14 +40,16 @@ module.exports = cls => class VirtualLoader extends cls { // public method async loadVirtual (options = {}) { if (this.virtualTree) - return Promise.resolve(this.virtualTree) + return this.virtualTree // allow the user to set reify options on the ctor as well. // XXX: deprecate separate reify() options object. options = { ...this.options, ...options } - if (options.root && options.root.meta) - return this[loadFromShrinkwrap](options.root.meta, options.root) + if (options.root && options.root.meta) { + await this[loadFromShrinkwrap](options.root.meta, options.root) + return treeCheck(this.virtualTree) + } const s = await Shrinkwrap.load({ path: this.path }) if (!s.loadedFromDisk && !options.root) { @@ -61,7 +63,8 @@ module.exports = cls => class VirtualLoader extends cls { root = await this[loadRoot](s), } = options - return this[loadFromShrinkwrap](s, root) + await this[loadFromShrinkwrap](s, root) + return treeCheck(this.virtualTree) } async [loadRoot] (s) { @@ -83,7 +86,7 @@ module.exports = cls => class VirtualLoader extends cls { this.virtualTree = root const {links, nodes} = this[resolveNodes](s, root) await this[resolveLinks](links, nodes) - this[assignParentage](nodes) + this[assignBundles](nodes) if (this[flagsSuspect]) this[reCalcDepFlags]() return root @@ -194,57 +197,43 @@ module.exports = cls => class VirtualLoader extends cls { nodes.set(targetLoc, link.target) // we always need to read the package.json for link targets - // because they can be changed by the local user - const pj = link.realpath + '/package.json' - const pkg = await rpj(pj).catch(() => null) - if (pkg) - link.target.package = pkg + // outside node_modules because they can be changed by the local user + if (!link.target.parent) { + const pj = link.realpath + '/package.json' + const pkg = await rpj(pj).catch(() => null) + if (pkg) + link.target.package = pkg + } } } - [assignParentage] (nodes) { + [assignBundles] (nodes) { for (const [location, node] of nodes) { // Skip assignment of parentage for the root package if (!location) continue - const { path, name } = node - for (const p of walkUp(dirname(path))) { - const ploc = relpath(this.path, p) - const parent = nodes.get(ploc) - if (!parent) - continue - // Safety check: avoid self-assigning nodes as their own parents - /* istanbul ignore if - should be obviated by parentage skip check */ - if (parent === node) - continue - - const locTest = `${ploc}/node_modules/${name}`.replace(/^\//, '') - const ptype = location === locTest - ? 'parent' - : 'fsParent' - node[ptype] = parent - // read inBundle from package because 'package' here is - // actually a v2 lockfile metadata entry. - // If the *parent* is also bundled, though, then we assume - // that it's being pulled in just by virtue of that. - const {inBundle} = node.package - const ppkg = parent.package - const {inBundle: parentBundled} = ppkg - const hasEdge = parent.edgesOut.has(name) - if (ptype === 'parent' && inBundle && hasEdge && !parentBundled) { - if (!ppkg.bundleDependencies) - ppkg.bundleDependencies = [name] - else if (!ppkg.bundleDependencies.includes(name)) - ppkg.bundleDependencies.push(name) - } + const { name, parent, package: { inBundle }} = node + if (!parent) + continue - break + // read inBundle from package because 'package' here is + // actually a v2 lockfile metadata entry. + // If the *parent* is also bundled, though, then we assume + // that it's being pulled in just by virtue of that. + const { package: ppkg } = parent + const { inBundle: parentBundled } = ppkg + if (inBundle && !parentBundled) { + if (!ppkg.bundleDependencies) + ppkg.bundleDependencies = [name] + else if (!ppkg.bundleDependencies.includes(name)) + ppkg.bundleDependencies.push(name) } } } [loadNode] (location, sw) { - const path = resolve(this.path, location) + const p = this.virtualTree ? this.virtualTree.realpath : this.path + const path = resolve(p, location) // shrinkwrap doesn't include package name unless necessary if (!sw.name) sw.name = nameFromFolder(path) diff --git a/node_modules/@npmcli/arborist/lib/arborist/reify.js b/node_modules/@npmcli/arborist/lib/arborist/reify.js index 6db1b7391c4d4..b16f2085566b2 100644 --- a/node_modules/@npmcli/arborist/lib/arborist/reify.js +++ b/node_modules/@npmcli/arborist/lib/arborist/reify.js @@ -16,6 +16,7 @@ const moveFile = require('@npmcli/move-file') const rimraf = promisify(require('rimraf')) const packageContents = require('@npmcli/installed-package-contents') +const treeCheck = require('../tree-check.js') const relpath = require('../relpath.js') const Diff = require('../diff.js') const retirePath = require('../retire-path.js') @@ -128,7 +129,7 @@ module.exports = cls => class Reifier extends cls { this.finishTracker('reify') process.emit('timeEnd', 'reify') - return this.actualTree + return treeCheck(this.actualTree) } async [_reifyPackages] () { diff --git a/node_modules/@npmcli/arborist/lib/audit-report.js b/node_modules/@npmcli/arborist/lib/audit-report.js index 9407e0ee6f097..15e17330addc0 100644 --- a/node_modules/@npmcli/arborist/lib/audit-report.js +++ b/node_modules/@npmcli/arborist/lib/audit-report.js @@ -44,7 +44,7 @@ class AuditReport extends Map { optional: 0, peer: 0, peerOptional: 0, - total: this.tree.inventory.size, + total: this.tree.inventory.size - 1, }, }, } @@ -281,7 +281,7 @@ class AuditReport extends Map { async [_getReport] () { // if we're not auditing, just return false - if (this.options.audit === false || this.tree.inventory.size === 0) + if (this.options.audit === false || this.tree.inventory.size === 1) return null process.emit('time', 'auditReport:getReport') @@ -290,9 +290,10 @@ class AuditReport extends Map { // first try the super fast bulk advisory listing const body = prepareBulkData(this.tree, this[_omit]) - // no sense asking if we don't have anything to audit + // no sense asking if we don't have anything to audit, + // we know it'll be empty if (!Object.keys(body).length) - return {} + return null const res = await fetch('/-/npm/v1/security/advisories/bulk', { ...this.options, diff --git a/node_modules/@npmcli/arborist/lib/debug.js b/node_modules/@npmcli/arborist/lib/debug.js index 9a8c0cf5f0ca8..5acacee69e223 100644 --- a/node_modules/@npmcli/arborist/lib/debug.js +++ b/node_modules/@npmcli/arborist/lib/debug.js @@ -10,9 +10,15 @@ // throw new Error('expensive check should have returned false') // }) -const debug = process.env.ARBORIST_DEBUG === '1' || +// run in debug mode if explicitly requested, running arborist tests, +// or working in the arborist project directory. +const debug = process.env.ARBORIST_DEBUG !== '0' && ( + process.env.ARBORIST_DEBUG === '1' || /\barborist\b/.test(process.env.NODE_DEBUG || '') || process.env.npm_package_name === '@npmcli/arborist' && - ['test', 'snap'].includes(process.env.npm_lifecycle_event) + ['test', 'snap'].includes(process.env.npm_lifecycle_event) || + process.cwd() === require('path').resolve(__dirname, '..') +) module.exports = debug ? fn => fn() : () => {} +module.exports.log = (...msg) => module.exports(() => console.error(...msg)) diff --git a/node_modules/@npmcli/arborist/lib/dep-valid.js b/node_modules/@npmcli/arborist/lib/dep-valid.js index 78661fea12b09..01e5e21e94ce5 100644 --- a/node_modules/@npmcli/arborist/lib/dep-valid.js +++ b/node_modules/@npmcli/arborist/lib/dep-valid.js @@ -49,7 +49,7 @@ const depValid = (child, requested, requestor) => { // fallthrough case 'version': // if it's a version or a range other than '*', semver it - return semver.satisfies(child.package.version, requested.fetchSpec, true) + return semver.satisfies(child.version, requested.fetchSpec, true) case 'directory': // directory must be a link to the specified folder diff --git a/node_modules/@npmcli/arborist/lib/inventory.js b/node_modules/@npmcli/arborist/lib/inventory.js index 696ad25e437f4..cef0c4e265899 100644 --- a/node_modules/@npmcli/arborist/lib/inventory.js +++ b/node_modules/@npmcli/arborist/lib/inventory.js @@ -4,7 +4,9 @@ // keys is the set of fields to be able to query. const _primaryKey = Symbol('_primaryKey') const _index = Symbol('_index') -const defaultKeys = ['name', 'license', 'funding'] +const defaultKeys = ['name', 'license', 'funding', 'realpath'] +const { hasOwnProperty } = Object.prototype +const debug = require('./debug.js') class Inventory extends Map { constructor (opt = {}) { const { primary, keys } = opt @@ -32,6 +34,18 @@ class Inventory extends Map { } add (node) { + const root = super.get('') + if (root && node.root !== root && node.root !== root.root) { + debug(() => { + throw Object.assign(new Error('adding external node to inventory'), { + root: root.path, + node: node.path, + nodeRoot: node.root.path, + }) + }) + return + } + const current = super.get(node[this.primaryKey]) if (current) { if (current === node) @@ -40,7 +54,9 @@ class Inventory extends Map { } super.set(node[this.primaryKey], node) for (const [key, map] of this[_index].entries()) { - const val_ = node[key] || (node.package && node.package[key]) + // if the node has the value, but it's false, then use that + const val_ = hasOwnProperty.call(node, key) ? node[key] + : node[key] || (node.package && node.package[key]) const val = typeof val_ === 'string' ? val_ : !val_ || typeof val_ !== 'object' ? val_ : key === 'license' ? val_.type @@ -58,7 +74,8 @@ class Inventory extends Map { super.delete(node[this.primaryKey]) for (const [key, map] of this[_index].entries()) { - const val = node[key] || (node.package && node.package[key]) + const val = node[key] !== undefined ? node[key] + : (node[key] || (node.package && node.package[key])) const set = map.get(val) if (set) { set.delete(node) diff --git a/node_modules/@npmcli/arborist/lib/link.js b/node_modules/@npmcli/arborist/lib/link.js index af4fac158ff0f..2394c6e41173c 100644 --- a/node_modules/@npmcli/arborist/lib/link.js +++ b/node_modules/@npmcli/arborist/lib/link.js @@ -1,11 +1,15 @@ +const debug = require('./debug.js') const relpath = require('./relpath.js') const Node = require('./node.js') const _loadDeps = Symbol.for('Arborist.Node._loadDeps') -const _target = Symbol('_target') +const _target = Symbol.for('_target') const {dirname} = require('path') +// defined by Node class +const _delistFromMeta = Symbol.for('_delistFromMeta') +const _refreshLocation = Symbol.for('_refreshLocation') class Link extends Node { constructor (options) { - const { realpath, target } = options + const { root, realpath, target, parent, fsParent } = options if (!realpath && !(target && target.path)) throw new TypeError('must provide realpath for Link node') @@ -13,18 +17,23 @@ class Link extends Node { super({ ...options, realpath: realpath || target.path, + root: root || (parent ? parent.root + : fsParent ? fsParent.root + : target ? target.root + : null), }) this.target = target || new Node({ ...options, path: realpath, parent: null, + fsParent: null, root: this.root, - linksIn: [this], }) + } - if (this.root.meta) - this.root.meta.add(this) + get version () { + return this.target ? this.target.version : this.package.version || '' } get target () { @@ -33,33 +42,70 @@ class Link extends Node { set target (target) { const current = this[_target] - if (current && current.linksIn) - current.linksIn.delete(this) + if (target === current) + return + + if (current && current.then) { + debug(() => { + throw Object.assign(new Error('cannot set target while awaiting'), { + path: this.path, + realpath: this.realpath, + }) + }) + } - this[_target] = target + if (target && target.then) { + // can set to a promise during an async tree build operation + // wait until then to assign it. + this[_target] = target + target.then(node => { + this[_target] = null + this.target = node + }) + return + } if (!target) { - this.package = {} + if (current && current.linksIn) + current.linksIn.delete(this) + if (this.path) { + this[_delistFromMeta]() + this[_target] = null + this.package = {} + this[_refreshLocation]() + } else + this[_target] = null return } - if (target.then) { - // can set to a promise during an async tree build operation - // wait until then to assign it. - target.then(node => this.target = node) + if (!this.path) { + // temp node pending assignment to a tree + // we know it's not in the inventory yet, because no path. + if (target.path) + this.realpath = target.path + else + target.path = target.realpath = this.realpath + target.root = this.root + this[_target] = target + target.linksIn.add(this) + this.package = target.package return } + // have to refresh metadata, because either realpath or package + // is very likely changing. + this[_delistFromMeta]() this.package = target.package - this.realpath = target.path - if (target.root === target) - target.root = this.root - target.linksIn.add(this) + this[_refreshLocation]() + + target.root = this.root } // a link always resolves to the relative path to its target get resolved () { + // the path/realpath guard is there for the benefit of setting + // these things in the "wrong" order return this.path && this.realpath ? `file:${relpath(dirname(this.path), this.realpath)}` : null diff --git a/node_modules/@npmcli/arborist/lib/node.js b/node_modules/@npmcli/arborist/lib/node.js index 6a274bf92b781..6e243c049d273 100644 --- a/node_modules/@npmcli/arborist/lib/node.js +++ b/node_modules/@npmcli/arborist/lib/node.js @@ -36,22 +36,24 @@ const {getPaths: getBinPaths} = require('bin-links') const npa = require('npm-package-arg') const debug = require('./debug.js') const gatherDepSet = require('./gather-dep-set.js') +const treeCheck = require('./tree-check.js') +const walkUp = require('walk-up-path') const {resolve, relative, dirname, basename} = require('path') const _package = Symbol('_package') const _parent = Symbol('_parent') +const _target = Symbol.for('_target') const _fsParent = Symbol('_fsParent') -const _reloadEdges = Symbol('_reloadEdges') const _loadDepType = Symbol('_loadDepType') const _loadWorkspaces = Symbol('_loadWorkspaces') const _reloadNamedEdges = Symbol('_reloadNamedEdges') // overridden by Link class const _loadDeps = Symbol.for('Arborist.Node._loadDeps') const _root = Symbol('_root') -const _refreshLocation = Symbol('_refreshLocation') -const _refreshTopMeta = Symbol('_refreshTopMeta') -const _refreshPath = Symbol('_refreshPath') -const _delistFromMeta = Symbol('_delistFromMeta') +const _refreshLocation = Symbol.for('_refreshLocation') +const _changePath = Symbol.for('_changePath') +// used by Link class as well +const _delistFromMeta = Symbol.for('_delistFromMeta') const _global = Symbol.for('global') const _workspaces = Symbol('_workspaces') const _explain = Symbol('_explain') @@ -111,7 +113,7 @@ class Node { null // should be equal if not a link - this.path = path && resolve(path) + this.path = path ? resolve(path) : null if (!this.name && (!this.path || this.path !== dirname(this.path))) throw new TypeError('could not detect node name from path or package') @@ -145,6 +147,7 @@ class Node { this.children = new Map() this.fsChildren = new Set() this.inventory = new Inventory({}) + this.tops = new Set() this.linksIn = new Set(linksIn || []) // these three are set by an Arborist taking a catalog @@ -198,7 +201,8 @@ class Node { // Must be set prior to calling _loadDeps, because top-ness is relevant // will also assign root if present on the parent - this.parent = parent + this[_parent] = null + this.parent = parent || null this[_fsParent] = null this.fsParent = fsParent || null @@ -209,9 +213,6 @@ class Node { if (!parent && !fsParent) this.root = root || null - if (this.isRoot) - this.location = '' - // mostly a convenience for testing, but also a way to create // trees in a more declarative way than setting parent on each if (children) { @@ -461,35 +462,244 @@ class Node { } set root (root) { - const nullRoot = root === null - if (nullRoot) - root = this - else { - // should only ever be 1 step - while (root.root !== root) - root = root.root - } + // setting to null means this is the new root + // should only ever be one step + while (root && root.root !== root) + root = root.root - if (root === this.root) - return + root = root || this + // delete from current root inventory this[_delistFromMeta]() - this[_root] = root - this[_refreshLocation]() - if (this.top.meta) - this[_refreshTopMeta]() + // can't set the root (yet) if there's no way to determine location + // this allows us to do new Node({...}) and then set the root later. + // just make the assignment so we don't lose it, and move on. + if (!this.path || !root.realpath || !root.path) + return this[_root] = root - if (this.target && !nullRoot) - this.target.root = root + // temporarily become a root node + this[_root] = this - this.fsChildren.forEach(c => c.root = root) - this.children.forEach(c => c.root = root) - /* istanbul ignore next - debug check */ - debug(() => { - if (this !== root && this.inventory.size !== 0) - throw new Error('non-root has non-zero inventory') - }) + // break all linksIn, we're going to re-set them if needed later + for (const link of this.linksIn) { + link[_target] = null + this.linksIn.delete(link) + } + + // temporarily break this link as well, we'll re-set if possible later + const { target } = this + if (this.isLink) { + if (target) { + target.linksIn.delete(this) + if (target.root === this) + target[_delistFromMeta]() + } + this[_target] = null + } + + // if this is part of a cascading root set, then don't do this bit + // but if the parent/fsParent is in a different set, we have to break + // that reference before proceeding + if (this.parent && this.parent.root !== root) { + this.parent.children.delete(this.name) + this[_parent] = null + } + if (this.fsParent && this.fsParent.root !== root) { + this.fsParent.fsChildren.delete(this) + this[_fsParent] = null + } + + if (root === this) + this[_refreshLocation]() + else { + // setting to some different node. + const loc = relpath(root.realpath, this.path) + const current = root.inventory.get(loc) + + // clobber whatever is there now + if (current) + current.root = null + + this[_root] = root + // set this.location and add to inventory + this[_refreshLocation]() + + // try to find our parent/fsParent in the new root inventory + for (const p of walkUp(dirname(this.path))) { + const ploc = relpath(root.realpath, p) + const parent = root.inventory.get(ploc) + if (parent) { + /* istanbul ignore next - impossible */ + if (parent.isLink) { + debug(() => { + throw Object.assign(new Error('assigning parentage to link'), { + path: this.path, + parent: parent.path, + parentReal: parent.realpath, + }) + }) + continue + } + const childLoc = `${ploc}${ploc ? '/' : ''}node_modules/${this.name}` + const isParent = this.location === childLoc + if (isParent) { + const oldChild = parent.children.get(this.name) + if (oldChild && oldChild !== this) + oldChild.root = null + if (this.parent) { + this.parent.children.delete(this.name) + this.parent[_reloadNamedEdges](this.name) + } + parent.children.set(this.name, this) + this[_parent] = parent + // don't do it for links, because they don't have a target yet + // we'll hit them up a bit later on. + if (!this.isLink) + parent[_reloadNamedEdges](this.name) + } else { + /* istanbul ignore if - should be impossible, since we break + * all fsParent/child relationships when moving? */ + if (this.fsParent) + this.fsParent.fsChildren.delete(this) + parent.fsChildren.add(this) + this[_fsParent] = parent + } + break + } + } + + // if it doesn't have a parent, it's a top node + if (!this.parent) + root.tops.add(this) + else + root.tops.delete(this) + + // assign parentage for any nodes that need to have this as a parent + // this can happen when we have a node at nm/a/nm/b added *before* + // the node at nm/a, which might have the root node as a fsParent. + // we can't rely on the public setter here, because it calls into + // this function to set up these references! + const nmloc = `${this.location}${this.location ? '/' : ''}node_modules/` + const isChild = n => n.location === nmloc + n.name + // check dirname so that /foo isn't treated as the fsparent of /foo-bar + const isFsChild = n => dirname(n.path).startsWith(this.path) && + n !== this && + !n.parent && + (!n.fsParent || n.fsParent === this || dirname(this.path).startsWith(n.fsParent.path)) + const isKid = n => isChild(n) || isFsChild(n) + + // only walk top nodes, since anything else already has a parent. + for (const child of root.tops) { + if (!isKid(child)) + continue + + // set up the internal parentage links + if (this.isLink) + child.root = null + else { + // can't possibly have a parent, because it's in tops + if (child.fsParent) + child.fsParent.fsChildren.delete(child) + child[_fsParent] = null + if (isChild(child)) { + this.children.set(child.name, child) + child[_parent] = this + root.tops.delete(child) + } else { + this.fsChildren.add(child) + child[_fsParent] = this + } + } + } + + // look for any nodes with the same realpath. either they're links + // to that realpath, or a thing at that realpath if we're adding a link + // (if we're adding a regular node, we already deleted the old one) + for (const node of root.inventory.query('realpath', this.realpath)) { + if (node === this) + continue + + /* istanbul ignore next - should be impossible */ + debug(() => { + if (node.root !== root) + throw new Error('inventory contains node from other root') + }) + + if (this.isLink) { + const target = node.target || node + this[_target] = target + this[_package] = target.package + target.linksIn.add(this) + // reload edges here, because now we have a target + if (this.parent) + this.parent[_reloadNamedEdges](this.name) + break + } else { + /* istanbul ignore else - should be impossible */ + if (node.isLink) { + node[_target] = this + node[_package] = this.package + this.linksIn.add(node) + if (node.parent) + node.parent[_reloadNamedEdges](node.name) + } else { + debug(() => { + throw Object.assign(new Error('duplicate node in root setter'), { + path: this.path, + realpath: this.realpath, + root: root.realpath, + }) + }) + } + } + } + } + + // reload all edgesIn where the root doesn't match, so we don't have + // cross-tree dependency graphs + for (const edge of this.edgesIn) { + if (edge.from.root !== root) + edge.reload() + } + // reload all edgesOut where root doens't match, or is missing, since + // it might not be missing in the new tree + for (const edge of this.edgesOut.values()) { + if (!edge.to || edge.to.root !== root) + edge.reload() + } + + // now make sure our family comes along for the ride! + const family = new Set([ + ...this.fsChildren, + ...this.children.values(), + ...this.inventory.values(), + ].filter(n => n !== this)) + for (const child of family) { + if (child.root !== root) { + child[_delistFromMeta]() + child[_parent] = null + this.children.delete(child.name) + child[_fsParent] = null + this.fsChildren.delete(child) + for (const l of child.linksIn) { + l[_target] = null + child.linksIn.delete(l) + } + } + } + for (const child of family) { + if (child.root !== root) + child.root = root + } + + // if we had a target, and didn't find one in the new root, then bring + // it over as well. + if (this.isLink && target && !this.target) + target.root = root + + // tree should always be valid upon root setter completion. + treeCheck(this) } get root () { @@ -516,7 +726,7 @@ class Node { // Linked targets that are disconnected from the tree are tops, // but don't have a 'path' field, only a 'realpath', because we // don't know their canonical location. We don't need their devDeps. - if (this.isTop && this.path) + if (this.isTop && this.path && !this.sourceReference) this[_loadDepType](this.package.devDependencies, 'dev') const pd = this.package.peerDependencies @@ -552,19 +762,9 @@ class Node { } set fsParent (fsParent) { - fsParent = fsParent || null - - if (this[_fsParent] === fsParent) - return - - const current = this[_fsParent] - if (current) - current.fsChildren.delete(this) - if (!fsParent) { - this[_fsParent] = null - // reload ALL edges, since they're now all suspect and likely invalid - this[_reloadEdges](e => true) + if (this[_fsParent]) + this.root = null return } @@ -587,47 +787,53 @@ class Node { }, }) } - - if (fsParent.isLink) - throw new Error('setting fsParent to link node') }) + if (fsParent.isLink) + fsParent = fsParent.target + + // setting a thing to its own fsParent is not normal, but no-op for safety if (this === fsParent || fsParent.realpath === this.realpath) return - // prune off the original location, so we don't leave edges lying around - if (current) - this.fsParent = null + // nothing to do + if (this[_fsParent] === fsParent) + return - const fspp = fsParent.realpath - const nmPath = resolve(fspp, 'node_modules', this.name) - // actually in the node_modules folder! this can happen when a link - // points deep within a node_modules folder, so that the target node - // is loaded before its parent. - if (nmPath === this.path) { - this[_fsParent] = null + const oldFsParent = this[_fsParent] + const newPath = !oldFsParent ? this.path + : resolve(fsParent.path, relative(oldFsParent.path, this.path)) + const nmPath = resolve(fsParent.path, 'node_modules', this.name) + + // this is actually the parent, set that instead + if (newPath === nmPath) { this.parent = fsParent return } - // ok! have a pseudo-parent, meaning that we're contained in - // the parent node's fs tree, but NOT in its node_modules folder. - // Almost certainly due to being a linked workspace-style package. - this[_fsParent] = fsParent - fsParent.fsChildren.add(this) - // refresh the path BEFORE setting root, so meta gets updated properly - this[_refreshPath](fsParent, current && current.path) - this.root = fsParent.root - this[_reloadEdges](e => !e.to) - } + const pathChange = newPath !== this.path - // called when we find that we have an fsParent which could account - // for some missing edges which are actually fine and not missing at all. - [_reloadEdges] (filter) { - this[_explanation] = null - this.edgesOut.forEach(edge => filter(edge) && edge.reload()) - this.fsChildren.forEach(c => c[_reloadEdges](filter)) - this.children.forEach(c => c[_reloadEdges](filter)) + // remove from old parent/fsParent + const oldParent = this.parent + const oldName = this.name + if (this.parent) { + this.parent.children.delete(this.name) + this[_parent] = null + } + if (this.fsParent) { + this.fsParent.fsChildren.delete(this) + this[_fsParent] = null + } + + // update this.path/realpath for this and all children/fsChildren + if (pathChange) + this[_changePath](newPath) + + if (oldParent) + oldParent[_reloadNamedEdges](oldName) + + // clobbers anything at that path, resets all appropriate references + this.root = fsParent.root } // is it safe to replace one node with another? check the edges to @@ -668,7 +874,7 @@ class Node { const parsed = npa(requested) const { name = this.name, rawSpec: spec } = parsed return this.name === name && this.satisfies(new Edge({ - from: new Node({ path: this.root.path }), + from: new Node({ path: this.root.realpath }), type: 'prod', name, spec, @@ -713,29 +919,27 @@ class Node { // Useful when mutating an ideal tree, so we can avoid having to call // the parent/root setters more than necessary. replaceWith (node) { - node.path = this.path - node.name = this.name - if (!node.isLink) - node.realpath = this.path - node.root = this.isRoot ? node : this.root - // pretend to be in the tree, so top/etc refs are not changing for kids. - node.parent = null - node[_parent] = this[_parent] - - // if we're replacing a non-link node with a link, then all the children - // and fsChildren just go along with it, because links don't have those. - if (!node.isLink) { - this.fsChildren.forEach(c => c.fsParent = node) - this.children.forEach(c => c.parent = node) - } - - // now remove the hidden reference, and call parent setter to finalize. - node[_parent] = null - node.parent = this.parent + node.replace(this) } replace (node) { - node.replaceWith(this) + this[_delistFromMeta]() + this.path = node.path + this.name = node.name + if (!this.isLink) + this.realpath = this.path + this[_refreshLocation]() + + // keep children when a node replaces another + if (!this.isLink) { + for (const kid of node.children.values()) + kid.parent = this + } + + if (!node.isRoot) + this.root = node.root + + treeCheck(this) } get inShrinkwrap () { @@ -757,176 +961,94 @@ class Node { // The only walk that starts from the parent rather than this node is // limited by edge name. set parent (parent) { - const oldParent = this[_parent] + // when setting to null, just remove it from the tree entirely + if (!parent) { + // but only delete it if we actually had a parent in the first place + // otherwise it's just setting to null when it's already null + if (this[_parent]) + this.root = null + return + } + if (parent.isLink) + parent = parent.target + + // setting a thing to its own parent is not normal, but no-op for safety if (this === parent) return - // link nodes can't contain children directly. - // children go under the link target. - if (parent) { - if (parent.isLink) - parent = parent.target + const oldParent = this[_parent] - if (oldParent === parent) - return - } + // nothing to do + if (oldParent === parent) + return // ok now we know something is actually changing, and parent is not a link - - // check to see if the location is going to change. - // we can skip some of the inventory/meta stuff if not. - const newPath = parent ? resolve(parent.path, 'node_modules', this.name) - : this.path + const newPath = resolve(parent.path, 'node_modules', this.name) const pathChange = newPath !== this.path - const newTop = parent ? parent.top : this - const topChange = newTop !== this.top - const newRoot = parent ? parent.root : null - const rootChange = newRoot !== this.root - - // if the path, top, or root are changing, then we need to delist - // from metadata and inventory where this module (and its children) - // are currently tracked. Need to do this BEFORE updating the - // path and setting node.root. We don't have to do this for node.target, - // because its path isn't changing, so everything we need will happen - // safely when we set this.root = parent.root. - if (this.path && (pathChange || topChange || rootChange)) { - this[_delistFromMeta]() - // delisting method doesn't walk children by default, since it would - // be excessive to do so when changing the root reference, as a - // root change walks children changing root as well. But in this case, - // we are about to change the parent, and thus the top, so we have - // to delist from the metadata now to ensure we remove it from the - // proper top node metadata if it isn't the root. - this.fsChildren.forEach(c => c[_delistFromMeta]()) - this.children.forEach(c => c[_delistFromMeta]()) - } - // remove from former parent. - if (oldParent) + // remove from old parent/fsParent + if (oldParent) { oldParent.children.delete(this.name) - - // update internal link. at this point, the node is actually in - // the new location in the tree, but the paths are not updated yet. - this[_parent] = parent - - // remove former child. calls back into this setter to unlist - if (parent) { - const oldChild = parent.children.get(this.name) - if (oldChild) - oldChild.parent = null - - parent.children.set(this.name, this) + this[_parent] = null } - - // this is the point of no return. this.location is no longer valid, - // and this.path is no longer going to reference this node in the - // inventory or shrinkwrap metadata. - if (parent) - this[_refreshPath](parent, oldParent && oldParent.path) - - // call the root setter. this updates this.location, and sets the - // root on all children, and this.target if this is a link. - // if the root isn't changing, then this is a no-op. - // the root setter is a no-op if the root didn't change, so we have - // to manually call the method to update location and metadata - if (!rootChange) - this[_refreshLocation]() - else - this.root = newRoot - - // if the new top is not the root, and it has meta, then we're updating - // nodes within a link target's folder. update it now. - if (newTop !== newRoot && newTop.meta) - this[_refreshTopMeta]() - - // refresh dep links - // note that this is _also_ done when a node is removed from the - // tree by setting parent=null, so deduplication is covered. - this.edgesIn.forEach(edge => edge.reload()) - this.edgesOut.forEach(edge => edge.reload()) - - // in case any of the parent's other descendants were resolving to - // a different instance of this package, walk the tree from that point - // reloading edges by this name. This only walks until it stops finding - // changes, so if there's a portion of the tree blocked by a different - // instance, or already updated by the previous in/out reloading, it won't - // needlessly re-resolve deps that won't need to be changed. - if (parent) - parent[_reloadNamedEdges](this.name, true) - - // since loading a parent can add *or change* resolutions, we also - // walk the tree from this point reloading all edges. - this[_reloadEdges](e => true) - - // have to refresh the location of children and fsChildren at this point, - // because their paths have likely changed, and root may have been set. - if (!rootChange) { - this.children.forEach(c => c[_refreshLocation]()) - this.fsChildren.forEach(c => c[_refreshLocation]()) + if (this.fsParent) { + this.fsParent.fsChildren.delete(this) + this[_fsParent] = null } - } - // called after changing the parent (and thus the top), and after changing - // the path, if the top is tracking metadata, so that we update the top's - // metadata with the new node. Note that we DON'T walk fsChildren here, - // because they do not share our top node. - [_refreshTopMeta] () { - this.top.meta.add(this) - this.children.forEach(c => c[_refreshTopMeta]()) + // update this.path/realpath for this and all children/fsChildren + if (pathChange) + this[_changePath](newPath) + + // clobbers anything at that path, resets all appropriate references + this.root = parent.root } // Call this before changing path or updating the _root reference. - // Removes the node from all the metadata trackers where it might live. + // Removes the node from its root the metadata and inventory. [_delistFromMeta] () { - const top = this.top const root = this.root - + if (!root.realpath || !this.path) + return root.inventory.delete(this) + root.tops.delete(this) if (root.meta) root.meta.delete(this.path) - - // need to also remove from the top meta if that's set. but, we only do - // that if the top is not the same as the root, or else we'll remove it - // twice unnecessarily. If the top and this have different roots, then - // that means we're in the process of changing this.parent, which sets the - // internal _parent reference BEFORE setting the root node, because paths - // need to be set up before assigning root. In that case, don't delist, - // or else we'll delete the metadata before we have a chance to apply it. - if (top.meta && top !== root && top.root === this.root) - top.meta.delete(this.path) + /* istanbul ignore next - should be impossible */ + debug(() => { + if ([...root.inventory.values()].includes(this)) + throw new Error('failed to delist') + }) } - // recurse through the tree updating path when it changes. - // called by the parent and fsParent setters. - [_refreshPath] (parent, fromPath = null) { - const ppath = parent.path - const relPath = typeof fromPath === 'string' - ? relative(fromPath, this.path) - : null - const oldPath = this.path - const newPath = relPath !== null ? resolve(ppath, relPath) - : parent === this[_parent] ? resolve(ppath, 'node_modules', this.name) - // fsparent initial assignment, nothing to update here - : oldPath - - // if no change, nothing to do! - if (newPath === oldPath) - return - + // update this.path/realpath and the paths of all children/fsChildren + [_changePath] (newPath) { + // have to de-list before changing paths this[_delistFromMeta]() + const oldPath = this.path this.path = newPath + const namePattern = /(?:^|\/|\\)node_modules[\\/](@[^/\\]+[\\/][^\\/]+|[^\\/]+)$/ + const nameChange = newPath.match(namePattern) + if (nameChange && this.name !== nameChange[1]) + this.name = nameChange[1].replace(/\\/g, '/') + + // if we move a link target, update link realpaths if (!this.isLink) { - this.realpath = this.path - if (this.linksIn.size) { - for (const link of this.linksIn) - link.realpath = newPath + this.realpath = newPath + for (const link of this.linksIn) { + link[_delistFromMeta]() + link.realpath = newPath + link[_refreshLocation]() } } + // if we move /x to /y, then a module at /x/a/b becomes /y/a/b + for (const child of this.fsChildren) + child[_changePath](resolve(newPath, relative(oldPath, child.path))) + for (const [name, child] of this.children.entries()) + child[_changePath](resolve(newPath, 'node_modules', name)) this[_refreshLocation]() - this.fsChildren.forEach(c => c[_refreshPath](this, oldPath)) - this.children.forEach(c => c[_refreshPath](this, oldPath)) } // Called whenever the root/parent is changed. @@ -934,7 +1056,9 @@ class Node { // this.path BEFORE calling this method! [_refreshLocation] () { const root = this.root - this.location = relpath(root.realpath, this.path) + const loc = relpath(root.realpath, this.path) + + this.location = loc root.inventory.add(this) if (root.meta) @@ -953,44 +1077,38 @@ class Node { this.root.meta.addEdge(edge) } - [_reloadNamedEdges] (name, root) { - // either it's the node in question, or it's going to block it anyway - if (this.name === name && !this.isTop) { - // reload the edges in so that anything that SHOULD be blocked - // by this node actually will be. - this.edgesIn.forEach(e => e.reload()) - return - } - + [_reloadNamedEdges] (name, rootLoc = this.location) { const edge = this.edgesOut.get(name) // if we don't have an edge, do nothing, but keep descending - if (edge) { - const toBefore = edge.to - edge.reload() - const toAfter = edge.to - if (toBefore === toAfter && !root) { - // nothing changed, we're done here. either it was already - // referring to this node (due to its edgesIn reloads), or - // it is blocked by another node in the tree. So either its children - // have already been updated, or don't need to be. - // - // but: always descend past the _first_ node, because it's likely - // that this is being triggered by this node getting a new child, - // so the whole point is to update the rest of the family. - return - } - } + const rootLocResolved = edge && edge.to && + edge.to.location === `${rootLoc}/node_modules/${edge.name}` + const sameResolved = edge && this.resolve(name) === edge.to + const recheck = rootLocResolved || !sameResolved + if (edge && recheck) + edge.reload(true) for (const c of this.children.values()) - c[_reloadNamedEdges](name) + c[_reloadNamedEdges](name, rootLoc) for (const c of this.fsChildren) - c[_reloadNamedEdges](name) + c[_reloadNamedEdges](name, rootLoc) } get isLink () { return false } + get target () { + return null + } + + set target (n) { + debug(() => { + throw Object.assign(new Error('cannot set target on non-Link Nodes'), { + path: this.path, + }) + }) + } + get depth () { return this.isTop ? 0 : this.parent.depth + 1 } diff --git a/node_modules/@npmcli/arborist/lib/shrinkwrap.js b/node_modules/@npmcli/arborist/lib/shrinkwrap.js index 74d14a8e735c1..a454320a318e6 100644 --- a/node_modules/@npmcli/arborist/lib/shrinkwrap.js +++ b/node_modules/@npmcli/arborist/lib/shrinkwrap.js @@ -758,11 +758,14 @@ class Shrinkwrap { if (this.tree) { if (this.yarnLock) this.yarnLock.fromTree(this.tree) - const root = Shrinkwrap.metaFromNode(this.tree, this.path) + const root = Shrinkwrap.metaFromNode(this.tree.target || this.tree, this.path) this.data.packages = {} if (Object.keys(root).length) this.data.packages[''] = root - for (const node of this.tree.inventory.values()) { + for (const node of this.tree.root.inventory.values()) { + // only way this.tree is not root is if the root is a link to it + if (node === this.tree || node.isRoot || node.location === '') + continue const loc = relpath(this.path, node.path) this.data.packages[loc] = Shrinkwrap.metaFromNode(node, this.path) } @@ -877,8 +880,17 @@ class Shrinkwrap { // omit peer deps from legacy lockfile requires field, because // npm v6 doesn't handle peer deps, and this triggers some bad // behavior if the dep can't be found in the dependencies list. - if (!v.peer) - set[k] = v.spec + const { spec, peer } = v + if (peer) + return set + if (spec.startsWith('file:')) { + // turn absolute file: paths into relative paths from the node + // this especially shows up with workspace edges when the root + // node is also a workspace in the set. + const p = resolve(node.realpath, spec.substr('file:'.length)) + set[k] = `file:${relpath(node.realpath, p)}` + } else + set[k] = spec return set }, {}) } else diff --git a/node_modules/@npmcli/arborist/lib/tree-check.js b/node_modules/@npmcli/arborist/lib/tree-check.js new file mode 100644 index 0000000000000..00b43296fbdf5 --- /dev/null +++ b/node_modules/@npmcli/arborist/lib/tree-check.js @@ -0,0 +1,104 @@ +const debug = require('./debug.js') + +const checkTree = (tree, checkUnreachable = true) => { + // this can only happen in tests where we have a "tree" object + // that isn't actually a tree. + if (!tree.root || !tree.root.inventory) + return tree + + const { inventory } = tree.root + const seen = new Set() + const check = (node, via = tree, viaType = 'self') => { + if (!node || seen.has(node) || node.then) + return + if (node.isRoot && node !== tree.root) { + throw Object.assign(new Error('double root'), { + node: node.path, + realpath: node.realpath, + tree: tree.path, + root: tree.root.path, + via: via.path, + viaType, + }) + } + + if (node.root !== tree.root) { + throw Object.assign(new Error('node from other root in tree'), { + node: node.path, + realpath: node.realpath, + tree: tree.path, + root: tree.root.path, + via: via.path, + viaType, + otherRoot: node.root && node.root.path, + }) + } + + if (!node.isRoot && node.inventory.size !== 0) { + throw Object.assign(new Error('non-root has non-zero inventory'), { + node: node.path, + tree: tree.path, + root: tree.root.path, + via: via.path, + viaType, + inventory: [...node.inventory.values()].map(node => + [node.path, node.location]), + }) + } + + if (!node.isRoot && !inventory.has(node) && !node.dummy) { + throw Object.assign(new Error('not in inventory'), { + node: node.path, + tree: tree.path, + root: tree.root.path, + via: via.path, + viaType, + }) + } + + const devEdges = [...node.edgesOut.values()].filter(e => e.dev) + if (!node.isTop && devEdges.length) { + throw Object.assign(new Error('dev edges on non-top node'), { + node: node.path, + tree: tree.path, + root: tree.root.path, + via: via.path, + viaType, + devEdges: devEdges.map(e => [e.type, e.name, e.spec, e.error]), + }) + } + + const { parent, fsParent, target } = node + seen.add(node) + check(parent, node, 'parent') + check(fsParent, node, 'fsParent') + check(target, node, 'target') + for (const kid of node.children.values()) + check(kid, node, 'children') + for (const kid of node.fsChildren) + check(kid, node, 'fsChildren') + for (const link of node.linksIn) + check(link, node, 'linksIn') + for (const top of node.tops) + check(top, node, 'tops') + } + check(tree) + if (checkUnreachable) { + for (const node of inventory.values()) { + if (!seen.has(node) && node !== tree.root) { + throw Object.assign(new Error('unreachable in inventory'), { + node: node.path, + realpath: node.realpath, + location: node.location, + root: tree.root.path, + tree: tree.path, + }) + } + } + } + return tree +} + +// should only ever run this check in debug mode +module.exports = tree => tree +debug(() => module.exports = checkTree) diff --git a/node_modules/@npmcli/arborist/package.json b/node_modules/@npmcli/arborist/package.json index 80d24c62c7cfa..c8dce9a2b0684 100644 --- a/node_modules/@npmcli/arborist/package.json +++ b/node_modules/@npmcli/arborist/package.json @@ -1,6 +1,6 @@ { "name": "@npmcli/arborist", - "version": "1.0.14", + "version": "2.0.0", "description": "Manage node_modules trees", "dependencies": { "@npmcli/installed-package-contents": "^1.0.5", @@ -8,8 +8,8 @@ "@npmcli/metavuln-calculator": "^1.0.0", "@npmcli/move-file": "^1.0.1", "@npmcli/name-from-folder": "^1.0.1", - "@npmcli/node-gyp": "^1.0.0", - "@npmcli/run-script": "^1.8.0", + "@npmcli/node-gyp": "^1.0.1", + "@npmcli/run-script": "^1.8.1", "bin-links": "^2.2.1", "cacache": "^15.0.3", "common-ancestor-path": "^1.0.1", @@ -17,19 +17,21 @@ "json-stringify-nice": "^1.1.1", "mkdirp-infer-owner": "^2.0.0", "npm-install-checks": "^4.0.0", - "npm-package-arg": "^8.0.0", + "npm-package-arg": "^8.1.0", "npm-pick-manifest": "^6.1.0", - "pacote": "^11.1.10", + "pacote": "^11.1.13", "parse-conflict-json": "^1.1.1", "promise-all-reject-late": "^1.0.0", "promise-call-limit": "^1.0.1", "read-package-json-fast": "^1.2.1", "readdir-scoped-modules": "^1.1.0", - "semver": "^7.1.2", + "semver": "^7.3.4", "treeverse": "^1.0.4", "walk-up-path": "^1.0.0" }, "devDependencies": { + "benchmark": "^2.1.4", + "chalk": "^4.1.0", "eslint": "^7.9.0", "eslint-plugin-import": "^2.22.0", "eslint-plugin-node": "^11.1.0", @@ -52,7 +54,9 @@ "prepublishOnly": "git push origin --follow-tags", "eslint": "eslint", "lint": "npm run eslint -- \"lib/**/*.js\"", - "lintfix": "npm run lint -- --fix" + "lintfix": "npm run lint -- --fix", + "benchmark": "node scripts/benchmark.js", + "benchclean": "rm -rf scripts/benchmark/*/" }, "repository": { "type": "git", diff --git a/node_modules/libnpmfund/package.json b/node_modules/libnpmfund/package.json index f337fffd15fd7..b25d3aa6b520e 100644 --- a/node_modules/libnpmfund/package.json +++ b/node_modules/libnpmfund/package.json @@ -1,6 +1,6 @@ { "name": "libnpmfund", - "version": "1.0.1", + "version": "1.0.2", "files": [ "index.js" ], @@ -47,6 +47,6 @@ "tap": "^14.10.7" }, "dependencies": { - "@npmcli/arborist": "^0.0.33 || ^1.x" + "@npmcli/arborist": "^2.0.0" } } diff --git a/package-lock.json b/package-lock.json index c970ab12665e4..55105058232b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -74,11 +74,181 @@ "uuid", "validate-npm-package-name", "which", - "write-file-atomic" + "write-file-atomic", + "@npmcli/git", + "@npmcli/installed-package-contents", + "@npmcli/map-workspaces", + "@npmcli/metavuln-calculator", + "@npmcli/move-file", + "@npmcli/name-from-folder", + "@npmcli/node-gyp", + "@npmcli/promise-spawn", + "@tootallnate/once", + "agent-base", + "agentkeepalive", + "aggregate-error", + "ajv", + "ansi-regex", + "ansi-styles", + "are-we-there-yet", + "asap", + "asn1", + "assert-plus", + "asynckit", + "aws-sign2", + "aws4", + "balanced-match", + "bcrypt-pbkdf", + "brace-expansion", + "builtins", + "caseless", + "cidr-regex", + "clean-stack", + "clone", + "cmd-shim", + "code-point-at", + "color-convert", + "color-name", + "colors", + "combined-stream", + "common-ancestor-path", + "concat-map", + "console-control-strings", + "core-util-is", + "dashdash", + "debug", + "debuglog", + "defaults", + "delayed-stream", + "delegates", + "depd", + "dezalgo", + "ecc-jsbn", + "emoji-regex", + "encoding", + "env-paths", + "err-code", + "extend", + "extsprintf", + "fast-deep-equal", + "fast-json-stable-stringify", + "forever-agent", + "form-data", + "fs-minipass", + "fs.realpath", + "gauge", + "getpass", + "har-schema", + "har-validator", + "has-flag", + "has-unicode", + "http-cache-semantics", + "http-proxy-agent", + "http-signature", + "https-proxy-agent", + "humanize-ms", + "iconv-lite", + "ignore-walk", + "imurmurhash", + "indent-string", + "infer-owner", + "inflight", + "ip", + "ip-regex", + "is-fullwidth-code-point", + "is-lambda", + "is-typedarray", + "isarray", + "isexe", + "isstream", + "jsbn", + "json-parse-even-better-errors", + "json-schema", + "json-schema-traverse", + "json-stringify-nice", + "json-stringify-safe", + "jsonparse", + "jsprim", + "just-diff", + "just-diff-apply", + "lru-cache", + "mime-db", + "mime-types", + "minimatch", + "minipass", + "minipass-collect", + "minipass-fetch", + "minipass-flush", + "minipass-json-stream", + "minipass-pipeline", + "minipass-sized", + "minizlib", + "mute-stream", + "npm-bundled", + "npm-install-checks", + "npm-normalize-package-bin", + "npm-packlist", + "number-is-nan", + "oauth-sign", + "object-assign", + "once", + "p-map", + "path-is-absolute", + "path-parse", + "performance-now", + "process-nextick-args", + "promise-all-reject-late", + "promise-call-limit", + "promise-inflight", + "promise-retry", + "promzard", + "psl", + "puka", + "punycode", + "qs", + "read-cmd-shim", + "readable-stream", + "readdir-scoped-modules", + "request", + "resolve", + "retry", + "safe-buffer", + "safer-buffer", + "set-blocking", + "signal-exit", + "smart-buffer", + "socks", + "socks-proxy-agent", + "spdx-correct", + "spdx-exceptions", + "spdx-expression-parse", + "spdx-license-ids", + "sshpk", + "string_decoder", + "string-width", + "stringify-package", + "strip-ansi", + "supports-color", + "tough-cookie", + "treeverse", + "tunnel-agent", + "tweetnacl", + "typedarray-to-buffer", + "unique-filename", + "unique-slug", + "uri-js", + "util-deprecate", + "validate-npm-package-license", + "verror", + "walk-up-path", + "wcwidth", + "wide-align", + "wrappy", + "yallist" ], "license": "Artistic-2.0", "dependencies": { - "@npmcli/arborist": "^1.0.14", + "@npmcli/arborist": "^2.0.0", "@npmcli/ci-detect": "^1.2.0", "@npmcli/config": "^1.2.3", "@npmcli/run-script": "^1.8.1", @@ -105,7 +275,7 @@ "is-cidr": "^4.0.2", "leven": "^3.1.0", "libnpmaccess": "^4.0.1", - "libnpmfund": "^1.0.1", + "libnpmfund": "^1.0.2", "libnpmhook": "^6.0.1", "libnpmorg": "^2.0.1", "libnpmpack": "^2.0.0", @@ -386,9 +556,9 @@ } }, "node_modules/@npmcli/arborist": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-1.0.14.tgz", - "integrity": "sha512-caa8hv5rW9VpQKk6tyNRvSaVDySVjo9GkI7Wj/wcsFyxPm3tYrEsFyTjSnJH8HCIfEGVQNjqqKXaXLFVp7UBag==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-2.0.0.tgz", + "integrity": "sha512-0h32rv2ZU6j5NR6V3WWx+8u10rwoF2SH1uTRzuNeTxfkHWbiuMd4xeUqJDMYzzlVAi9Jdk9L3pgtiDyXZP+8Lw==", "inBundle": true, "dependencies": { "@npmcli/installed-package-contents": "^1.0.5", @@ -396,8 +566,8 @@ "@npmcli/metavuln-calculator": "^1.0.0", "@npmcli/move-file": "^1.0.1", "@npmcli/name-from-folder": "^1.0.1", - "@npmcli/node-gyp": "^1.0.0", - "@npmcli/run-script": "^1.8.0", + "@npmcli/node-gyp": "^1.0.1", + "@npmcli/run-script": "^1.8.1", "bin-links": "^2.2.1", "cacache": "^15.0.3", "common-ancestor-path": "^1.0.1", @@ -405,15 +575,15 @@ "json-stringify-nice": "^1.1.1", "mkdirp-infer-owner": "^2.0.0", "npm-install-checks": "^4.0.0", - "npm-package-arg": "^8.0.0", + "npm-package-arg": "^8.1.0", "npm-pick-manifest": "^6.1.0", - "pacote": "^11.1.10", + "pacote": "^11.1.13", "parse-conflict-json": "^1.1.1", "promise-all-reject-late": "^1.0.0", "promise-call-limit": "^1.0.1", "read-package-json-fast": "^1.2.1", "readdir-scoped-modules": "^1.1.0", - "semver": "^7.1.2", + "semver": "^7.3.4", "treeverse": "^1.0.4", "walk-up-path": "^1.0.0" } @@ -3739,11 +3909,12 @@ } }, "node_modules/libnpmfund": { - "version": "1.0.1", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/libnpmfund/-/libnpmfund-1.0.2.tgz", + "integrity": "sha512-Scw2JiLxfT7wqW/VbxIXV8u3FaFT/ZlR8YLFgTdCPsL1Hhli0554ZXyP8JTu1sLeDpHsoqtgLb4mgYVQnqigjA==", "inBundle": true, - "license": "ISC", "dependencies": { - "@npmcli/arborist": "^0.0.33 || ^1.x" + "@npmcli/arborist": "^2.0.0" } }, "node_modules/libnpmhook": { @@ -5987,7 +6158,102 @@ "signal-exit", "tap-parser", "tap-yaml", - "yaml" + "yaml", + "@babel/code-frame", + "@babel/core", + "@babel/generator", + "@babel/helper-annotate-as-pure", + "@babel/helper-builder-react-jsx", + "@babel/helper-builder-react-jsx-experimental", + "@babel/helper-function-name", + "@babel/helper-get-function-arity", + "@babel/helper-member-expression-to-functions", + "@babel/helper-module-imports", + "@babel/helper-module-transforms", + "@babel/helper-optimise-call-expression", + "@babel/helper-plugin-utils", + "@babel/helper-replace-supers", + "@babel/helper-simple-access", + "@babel/helper-split-export-declaration", + "@babel/helper-validator-identifier", + "@babel/helpers", + "@babel/highlight", + "@babel/parser", + "@babel/plugin-proposal-object-rest-spread", + "@babel/plugin-syntax-jsx", + "@babel/plugin-syntax-object-rest-spread", + "@babel/plugin-transform-destructuring", + "@babel/plugin-transform-parameters", + "@babel/plugin-transform-react-jsx", + "@babel/template", + "@babel/traverse", + "@babel/types", + "@types/color-name", + "@types/prop-types", + "@types/yoga-layout", + "ansi-escapes", + "ansi-regex", + "ansi-styles", + "ansicolors", + "arrify", + "astral-regex", + "auto-bind", + "caller-callsite", + "caller-path", + "callsites", + "cardinal", + "chalk", + "ci-info", + "cli-cursor", + "cli-truncate", + "color-convert", + "color-name", + "convert-source-map", + "csstype", + "debug", + "emoji-regex", + "escape-string-regexp", + "esprima", + "events-to-array", + "gensync", + "globals", + "has-flag", + "is-ci", + "is-fullwidth-code-point", + "js-tokens", + "jsesc", + "json5", + "lodash", + "lodash.throttle", + "log-update", + "loose-envify", + "mimic-fn", + "minimist", + "ms", + "object-assign", + "onetime", + "path-parse", + "prop-types", + "punycode", + "react-is", + "react-reconciler", + "redeyed", + "resolve", + "resolve-from", + "restore-cursor", + "scheduler", + "semver", + "slice-ansi", + "string-length", + "string-width", + "strip-ansi", + "supports-color", + "to-fast-properties", + "type-fest", + "unicode-length", + "widest-line", + "wrap-ansi", + "yoga-layout-prebuilt" ], "dev": true, "dependencies": { @@ -9114,17 +9380,17 @@ } }, "@npmcli/arborist": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-1.0.14.tgz", - "integrity": "sha512-caa8hv5rW9VpQKk6tyNRvSaVDySVjo9GkI7Wj/wcsFyxPm3tYrEsFyTjSnJH8HCIfEGVQNjqqKXaXLFVp7UBag==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-2.0.0.tgz", + "integrity": "sha512-0h32rv2ZU6j5NR6V3WWx+8u10rwoF2SH1uTRzuNeTxfkHWbiuMd4xeUqJDMYzzlVAi9Jdk9L3pgtiDyXZP+8Lw==", "requires": { "@npmcli/installed-package-contents": "^1.0.5", "@npmcli/map-workspaces": "^1.0.1", "@npmcli/metavuln-calculator": "^1.0.0", "@npmcli/move-file": "^1.0.1", "@npmcli/name-from-folder": "^1.0.1", - "@npmcli/node-gyp": "^1.0.0", - "@npmcli/run-script": "^1.8.0", + "@npmcli/node-gyp": "^1.0.1", + "@npmcli/run-script": "^1.8.1", "bin-links": "^2.2.1", "cacache": "^15.0.3", "common-ancestor-path": "^1.0.1", @@ -9132,15 +9398,15 @@ "json-stringify-nice": "^1.1.1", "mkdirp-infer-owner": "^2.0.0", "npm-install-checks": "^4.0.0", - "npm-package-arg": "^8.0.0", + "npm-package-arg": "^8.1.0", "npm-pick-manifest": "^6.1.0", - "pacote": "^11.1.10", + "pacote": "^11.1.13", "parse-conflict-json": "^1.1.1", "promise-all-reject-late": "^1.0.0", "promise-call-limit": "^1.0.1", "read-package-json-fast": "^1.2.1", "readdir-scoped-modules": "^1.1.0", - "semver": "^7.1.2", + "semver": "^7.3.4", "treeverse": "^1.0.4", "walk-up-path": "^1.0.0" } @@ -11492,9 +11758,11 @@ } }, "libnpmfund": { - "version": "1.0.1", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/libnpmfund/-/libnpmfund-1.0.2.tgz", + "integrity": "sha512-Scw2JiLxfT7wqW/VbxIXV8u3FaFT/ZlR8YLFgTdCPsL1Hhli0554ZXyP8JTu1sLeDpHsoqtgLb4mgYVQnqigjA==", "requires": { - "@npmcli/arborist": "^0.0.33 || ^1.x" + "@npmcli/arborist": "^2.0.0" } }, "libnpmhook": { diff --git a/package.json b/package.json index 393993f79e206..dab2d07a02f67 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "./package.json": "./package.json" }, "dependencies": { - "@npmcli/arborist": "^1.0.14", + "@npmcli/arborist": "^2.0.0", "@npmcli/ci-detect": "^1.2.0", "@npmcli/config": "^1.2.3", "@npmcli/run-script": "^1.8.1", @@ -69,7 +69,7 @@ "is-cidr": "^4.0.2", "leven": "^3.1.0", "libnpmaccess": "^4.0.1", - "libnpmfund": "^1.0.1", + "libnpmfund": "^1.0.2", "libnpmhook": "^6.0.1", "libnpmorg": "^2.0.1", "libnpmpack": "^2.0.0",