diff options
Diffstat (limited to 'deps/npm/node_modules/@npmcli/arborist/lib/arborist/load-actual.js')
-rw-r--r-- | deps/npm/node_modules/@npmcli/arborist/lib/arborist/load-actual.js | 213 |
1 files changed, 105 insertions, 108 deletions
diff --git a/deps/npm/node_modules/@npmcli/arborist/lib/arborist/load-actual.js b/deps/npm/node_modules/@npmcli/arborist/lib/arborist/load-actual.js index 6c3f917c6a..8c4e148464 100644 --- a/deps/npm/node_modules/@npmcli/arborist/lib/arborist/load-actual.js +++ b/deps/npm/node_modules/@npmcli/arborist/lib/arborist/load-actual.js @@ -4,7 +4,7 @@ const { relative, dirname, resolve, join, normalize } = require('path') const rpj = require('read-package-json-fast') const { readdirScoped } = require('@npmcli/fs') -const walkUp = require('walk-up-path') +const { walkUp } = require('walk-up-path') const ancestorPath = require('common-ancestor-path') const treeCheck = require('../tree-check.js') @@ -17,28 +17,31 @@ const realpath = require('../realpath.js') // public symbols const _changePath = Symbol.for('_changePath') const _global = Symbol.for('global') -const _loadWorkspaces = Symbol.for('loadWorkspaces') +const _setWorkspaces = Symbol.for('setWorkspaces') const _rpcache = Symbol.for('realpathCache') const _stcache = Symbol.for('statCache') -// private symbols -const _actualTree = Symbol('actualTree') -const _actualTreeLoaded = Symbol('actualTreeLoaded') -const _actualTreePromise = Symbol('actualTreePromise') -const _cache = Symbol('nodeLoadingCache') -const _filter = Symbol('filter') -const _findMissingEdges = Symbol('findMissingEdges') -const _loadActual = Symbol('loadActual') -const _loadFSChildren = Symbol('loadFSChildren') -const _loadFSNode = Symbol('loadFSNode') -const _loadFSTree = Symbol('loadFSTree') -const _newLink = Symbol('newLink') -const _newNode = Symbol('newNode') -const _topNodes = Symbol('linkTargets') -const _transplant = Symbol('transplant') -const _transplantFilter = Symbol('transplantFilter') - module.exports = cls => class ActualLoader extends cls { + #actualTree + // ensure when walking the tree that we don't call loadTree on the same + // actual node more than one time. + #actualTreeLoaded = new Set() + #actualTreePromise + + // cache of nodes when loading the actualTree, so that we avoid loaded the + // same node multiple times when symlinks attack. + #cache = new Map() + #filter + + // cache of link targets for setting fsParent links + // We don't do fsParent as a magic getter/setter, because it'd be too costly + // to keep up to date along the walk. + // And, we know that it can ONLY be relevant when the node is a target of a + // link, otherwise it'd be in a node_modules folder, so take advantage of + // that to limit the scans later. + #topNodes = new Set() + #transplantFilter + constructor (options) { super(options) @@ -47,27 +50,11 @@ module.exports = cls => class ActualLoader extends cls { // the tree of nodes on disk this.actualTree = options.actualTree - // ensure when walking the tree that we don't call loadTree on the - // same actual node more than one time. - this[_actualTreeLoaded] = new Set() - // caches for cached realpath calls const cwd = process.cwd() // assume that the cwd is real enough for our purposes this[_rpcache] = new Map([[cwd, cwd]]) this[_stcache] = new Map() - - // cache of nodes when loading the actualTree, so that we avoid - // loaded the same node multiple times when symlinks attack. - this[_cache] = new Map() - - // cache of link targets for setting fsParent links - // We don't do fsParent as a magic getter/setter, because - // it'd be too costly to keep up to date along the walk. - // And, we know that it can ONLY be relevant when the node - // is a target of a link, otherwise it'd be in a node_modules - // folder, so take advantage of that to limit the scans later. - this[_topNodes] = new Set() } // public method @@ -81,12 +68,12 @@ module.exports = cls => class ActualLoader extends cls { if (this.actualTree) { return this.actualTree } - if (!this[_actualTreePromise]) { + if (!this.#actualTreePromise) { // allow the user to set options on the ctor as well. // XXX: deprecate separate method options objects. options = { ...this.options, ...options } - this[_actualTreePromise] = this[_loadActual](options) + this.#actualTreePromise = this.#loadActual(options) .then(tree => { // reset all deps to extraneous prior to recalc if (!options.root) { @@ -102,14 +89,15 @@ module.exports = cls => class ActualLoader extends cls { return this.actualTree }) } - return this[_actualTreePromise] + return this.#actualTreePromise } + // return the promise so that we don't ever have more than one going at the // same time. This is so that buildIdealTree can default to the actualTree // if no shrinkwrap present, but reify() can still call buildIdealTree and // loadActual in parallel safely. - async [_loadActual] (options) { + async #loadActual (options) { // mostly realpath to throw if the root doesn't exist const { global = false, @@ -119,8 +107,8 @@ module.exports = cls => class ActualLoader extends cls { ignoreMissing = false, forceActual = false, } = options - this[_filter] = filter - this[_transplantFilter] = transplantFilter + this.#filter = filter + this.#transplantFilter = transplantFilter if (global) { const real = await realpath(this.path, this[_rpcache], this[_stcache]) @@ -132,19 +120,19 @@ module.exports = cls => class ActualLoader extends cls { loadOverrides: true, } if (this.path === real) { - this[_actualTree] = this[_newNode](params) + this.#actualTree = this.#newNode(params) } else { - this[_actualTree] = await this[_newLink](params) + this.#actualTree = await this.#newLink(params) } } else { // not in global mode, hidden lockfile is allowed, load root pkg too - this[_actualTree] = await this[_loadFSNode]({ + this.#actualTree = await this.#loadFSNode({ path: this.path, real: await realpath(this.path, this[_rpcache], this[_stcache]), loadOverrides: true, }) - this[_actualTree].assertRootOverrides() + this.#actualTree.assertRootOverrides() // if forceActual is set, don't even try the hidden lockfile if (!forceActual) { @@ -152,48 +140,48 @@ module.exports = cls => class ActualLoader extends cls { // in the folder, or if any of the entries in the hidden lockfile are // missing. const meta = await Shrinkwrap.load({ - path: this[_actualTree].path, + path: this.#actualTree.path, hiddenLockfile: true, resolveOptions: this.options, }) if (meta.loadedFromDisk) { - this[_actualTree].meta = meta + this.#actualTree.meta = meta // have to load on a new Arborist object, so we don't assign // the virtualTree on this one! Also, the weird reference is because // we can't easily get a ref to Arborist in this module, without // creating a circular reference, since this class is a mixin used // to build up the Arborist class itself. await new this.constructor({ ...this.options }).loadVirtual({ - root: this[_actualTree], + root: this.#actualTree, }) - await this[_loadWorkspaces](this[_actualTree]) + await this[_setWorkspaces](this.#actualTree) - this[_transplant](root) - return this[_actualTree] + this.#transplant(root) + return this.#actualTree } } const meta = await Shrinkwrap.load({ - path: this[_actualTree].path, + path: this.#actualTree.path, lockfileVersion: this.options.lockfileVersion, resolveOptions: this.options, }) - this[_actualTree].meta = meta + this.#actualTree.meta = meta } - await this[_loadFSTree](this[_actualTree]) - await this[_loadWorkspaces](this[_actualTree]) + await this.#loadFSTree(this.#actualTree) + await this[_setWorkspaces](this.#actualTree) // if there are workspace targets without Link nodes created, load // the targets, so that we know what they are. - if (this[_actualTree].workspaces && this[_actualTree].workspaces.size) { + if (this.#actualTree.workspaces && this.#actualTree.workspaces.size) { const promises = [] - for (const path of this[_actualTree].workspaces.values()) { - if (!this[_cache].has(path)) { + for (const path of this.#actualTree.workspaces.values()) { + if (!this.#cache.has(path)) { // workspace overrides use the root overrides - const p = this[_loadFSNode]({ path, root: this[_actualTree], useRootOverrides: true }) - .then(node => this[_loadFSTree](node)) + const p = this.#loadFSNode({ path, root: this.#actualTree, useRootOverrides: true }) + .then(node => this.#loadFSTree(node)) promises.push(p) } } @@ -201,32 +189,32 @@ module.exports = cls => class ActualLoader extends cls { } if (!ignoreMissing) { - await this[_findMissingEdges]() + await this.#findMissingEdges() } // try to find a node that is the parent in a fs tree sense, but not a // node_modules tree sense, of any link targets. this allows us to // resolve deps that node will find, but a legacy npm view of the // world would not have noticed. - for (const path of this[_topNodes]) { - const node = this[_cache].get(path) + for (const path of this.#topNodes) { + const node = this.#cache.get(path) if (node && !node.parent && !node.fsParent) { for (const p of walkUp(dirname(path))) { - if (this[_cache].has(p)) { - node.fsParent = this[_cache].get(p) + if (this.#cache.has(p)) { + node.fsParent = this.#cache.get(p) break } } } } - this[_transplant](root) + this.#transplant(root) if (global) { // need to depend on the children, or else all of them // will end up being flagged as extraneous, since the // global root isn't a "real" project - const tree = this[_actualTree] + const tree = this.#actualTree const actualRoot = tree.isLink ? tree.target : tree const { dependencies = {} } = actualRoot.package for (const [name, kid] of actualRoot.children.entries()) { @@ -235,30 +223,30 @@ module.exports = cls => class ActualLoader extends cls { } actualRoot.package = { ...actualRoot.package, dependencies } } - return this[_actualTree] + return this.#actualTree } - [_transplant] (root) { - if (!root || root === this[_actualTree]) { + #transplant (root) { + if (!root || root === this.#actualTree) { return } - this[_actualTree][_changePath](root.path) - for (const node of this[_actualTree].children.values()) { - if (!this[_transplantFilter](node)) { + this.#actualTree[_changePath](root.path) + for (const node of this.#actualTree.children.values()) { + if (!this.#transplantFilter(node)) { node.root = null } } - root.replace(this[_actualTree]) - for (const node of this[_actualTree].fsChildren) { - node.root = this[_transplantFilter](node) ? root : null + root.replace(this.#actualTree) + for (const node of this.#actualTree.fsChildren) { + node.root = this.#transplantFilter(node) ? root : null } - this[_actualTree] = root + this.#actualTree = root } - async [_loadFSNode] ({ path, parent, real, root, loadOverrides, useRootOverrides }) { + async #loadFSNode ({ path, parent, real, root, loadOverrides, useRootOverrides }) { if (!real) { try { real = await realpath(path, this[_rpcache], this[_stcache]) @@ -275,7 +263,7 @@ module.exports = cls => class ActualLoader extends cls { } } - const cached = this[_cache].get(path) + const cached = this.#cache.get(path) let node // missing edges get a dummy node, assign the parent and return it if (cached && !cached.dummy) { @@ -306,67 +294,67 @@ module.exports = cls => class ActualLoader extends cls { // Node which will attach it to its errors array (Link passes it along to // its target node) if (normalize(path) === real) { - node = this[_newNode](params) + node = this.#newNode(params) } else { - node = await this[_newLink](params) + node = await this.#newLink(params) } } - this[_cache].set(path, node) + this.#cache.set(path, node) return node } - [_newNode] (options) { + #newNode (options) { // check it for an fsParent if it's a tree top. there's a decent chance // it'll get parented later, making the fsParent scan a no-op, but better // safe than sorry, since it's cheap. const { parent, realpath } = options if (!parent) { - this[_topNodes].add(realpath) + this.#topNodes.add(realpath) } return new Node(options) } - async [_newLink] (options) { + async #newLink (options) { const { realpath } = options - this[_topNodes].add(realpath) - const target = this[_cache].get(realpath) + this.#topNodes.add(realpath) + const target = this.#cache.get(realpath) const link = new Link({ ...options, target }) if (!target) { // Link set its target itself in this case - this[_cache].set(realpath, link.target) + this.#cache.set(realpath, link.target) // if a link target points at a node outside of the root tree's // node_modules hierarchy, then load that node as well. - await this[_loadFSTree](link.target) + await this.#loadFSTree(link.target) } return link } - async [_loadFSTree] (node) { - const did = this[_actualTreeLoaded] + async #loadFSTree (node) { + const did = this.#actualTreeLoaded if (!did.has(node.target.realpath)) { did.add(node.target.realpath) - await this[_loadFSChildren](node.target) + await this.#loadFSChildren(node.target) return Promise.all( [...node.target.children.entries()] .filter(([name, kid]) => !did.has(kid.realpath)) - .map(([name, kid]) => this[_loadFSTree](kid)) + .map(([name, kid]) => this.#loadFSTree(kid)) ) } } // create child nodes for all the entries in node_modules // and attach them to the node as a parent - async [_loadFSChildren] (node) { + async #loadFSChildren (node) { const nm = resolve(node.realpath, 'node_modules') try { const kids = await readdirScoped(nm).then(paths => paths.map(p => p.replace(/\\/g, '/'))) return Promise.all( // ignore . dirs and retired scoped package folders kids.filter(kid => !/^(@[^/]+\/)?\./.test(kid)) - .filter(kid => this[_filter](node, kid)) - .map(kid => this[_loadFSNode]({ + .filter(kid => this.#filter(node, kid)) + .map(kid => this.#loadFSNode({ parent: node, path: resolve(nm, kid), }))) @@ -375,7 +363,7 @@ module.exports = cls => class ActualLoader extends cls { } } - async [_findMissingEdges] () { + async #findMissingEdges () { // try to resolve any missing edges by walking up the directory tree, // checking for the package in each node_modules folder. stop at the // root directory. @@ -385,7 +373,7 @@ module.exports = cls => class ActualLoader extends cls { // because people sometimes develop in ~/projects/node_modules/... // so we'd end up loading a massive tree with lots of unrelated junk. const nmContents = new Map() - const tree = this[_actualTree] + const tree = this.#actualTree for (const node of tree.inventory.values()) { const ancestor = ancestorPath(node.realpath, this.path) @@ -410,28 +398,37 @@ module.exports = cls => class ActualLoader extends cls { break } - const entries = nmContents.get(p) || await readdirScoped(p + '/node_modules') - .catch(() => []).then(paths => paths.map(p => p.replace(/\\/g, '/'))) - nmContents.set(p, entries) + let entries + if (!nmContents.has(p)) { + entries = await readdirScoped(p + '/node_modules') + .catch(() => []).then(paths => paths.map(p => p.replace(/\\/g, '/'))) + nmContents.set(p, entries) + } else { + entries = nmContents.get(p) + } + if (!entries.includes(name)) { continue } - const d = this[_cache].has(p) ? await this[_cache].get(p) - : new Node({ path: p, root: node.root, dummy: true }) - // not a promise - this[_cache].set(p, d) + let d + if (!this.#cache.has(p)) { + d = new Node({ path: p, root: node.root, dummy: true }) + this.#cache.set(p, d) + } else { + d = this.#cache.get(p) + } if (d.dummy) { // it's a placeholder, so likely would not have loaded this dep, // unless another dep in the tree also needs it. const depPath = normalize(`${p}/node_modules/${name}`) - const cached = this[_cache].get(depPath) + const cached = this.#cache.get(depPath) if (!cached || cached.dummy) { - depPromises.push(this[_loadFSNode]({ + depPromises.push(this.#loadFSNode({ path: depPath, root: node.root, parent: d, - }).then(node => this[_loadFSTree](node))) + }).then(node => this.#loadFSTree(node))) } } break |