@npmcli/arborist
Advanced tools
Comparing version 0.0.0-pre.9 to 0.0.0-pre.10
@@ -60,3 +60,2 @@ // mixin implementing the buildIdealTree method | ||
const _add = Symbol('add') | ||
const _explicitRequests = Symbol('explicitRequests') | ||
const _queueNamedUpdates = Symbol('queueNamedUpdates') | ||
@@ -69,4 +68,8 @@ const _shouldUpdateNode = Symbol('shouldUpdateNode') | ||
const _follow = Symbol('follow') | ||
const _globalStyle = Symbol('globalStyle') | ||
const _globalRootNode = Symbol('globalRootNode') | ||
// used by Reify mixin | ||
const _explicitRequests = Symbol.for('explicitRequests') | ||
const _global = Symbol.for('global') | ||
const _idealTreePrune = Symbol.for('idealTreePrune') | ||
@@ -86,5 +89,13 @@ const _resolvedAdd = Symbol.for('resolvedAdd') | ||
const { | ||
idealTree = null, | ||
global = false, | ||
follow = false, | ||
globalStyle = false, | ||
} = options | ||
this.idealTree = options.idealTree | ||
this[_follow] = !!options.follow | ||
this[_globalStyle] = this[_global] || globalStyle | ||
this[_follow] = !!follow | ||
@@ -110,2 +121,5 @@ this[_explicitRequests] = new Set() | ||
if (!options.add && !options.rm && this[_global]) | ||
return Promise.reject(new Error('global requires an add or rm option')) | ||
// first get the virtual tree, if possible. If there's a lockfile, then | ||
@@ -162,4 +176,9 @@ // that defines the ideal tree, unless the root package.json is not | ||
[_initTree] () { | ||
return rpj(this.path + '/package.json') | ||
.then(pkg => this[_rootNodeFromPackage](pkg)) | ||
return ( | ||
this[_global] ? this[_globalRootNode]() | ||
: rpj(this.path + '/package.json') | ||
.then( | ||
pkg => this[_rootNodeFromPackage](pkg), | ||
er => this[_rootNodeFromPackage]({}) | ||
)) | ||
// ok to not have a virtual tree. probably initial install. | ||
@@ -169,7 +188,7 @@ // When updating all, we load the shrinkwrap, but don't bother | ||
// reconstructing it anyway. | ||
.then(root => this[_updateAll] | ||
? Shrinkwrap.load({ path: this.path }).then(meta => { | ||
meta.reset() | ||
root.meta = meta | ||
return root | ||
.then(root => this[_global] ? root | ||
: this[_updateAll] ? Shrinkwrap.load({ path: this.path }).then(meta => { | ||
meta.reset() | ||
root.meta = meta | ||
return root | ||
}) | ||
@@ -186,2 +205,13 @@ : this.loadVirtual({ root })) | ||
[_globalRootNode] () { | ||
const root = this[_rootNodeFromPackage]({}) | ||
// this is a gross kludge to handle the fact that we don't save | ||
// metadata on the root node in global installs, because the "root" | ||
// node is something like /usr/local/lib/node_modules. | ||
const meta = new Shrinkwrap({ path: this.path }) | ||
meta.reset() | ||
root.meta = meta | ||
return Promise.resolve(root) | ||
} | ||
[_rootNodeFromPackage] (pkg) { | ||
@@ -196,2 +226,3 @@ return new Node({ | ||
optional: false, | ||
global: this[_global], | ||
}) | ||
@@ -209,4 +240,7 @@ } | ||
if (options.rm && options.rm.length) | ||
if (options.rm && options.rm.length) { | ||
addRmPkgDeps.rm(this.idealTree.package, options.rm) | ||
for (const name of options.rm) | ||
this[_explicitRequests].add(name) | ||
} | ||
@@ -223,4 +257,4 @@ // triggers a refresh of all edgesOut | ||
// add => might return promise | ||
// might not have name, call pacote.manifest (find name) | ||
// This returns a promise because we might not have the name yet, | ||
// and need to call pacote.manifest to find the name. | ||
[_add] (add) { | ||
@@ -234,2 +268,6 @@ const promises = [] | ||
for (const [type, specs] of Object.entries(add)) { | ||
// get the name for each of the specs in the list. | ||
// ie, doing `foo@bar` we just return foo | ||
// but if it's a url or git, we don't know the name until we | ||
// fetch it and look in its manifest. | ||
const p = Promise.all(specs.map(s => { | ||
@@ -251,2 +289,8 @@ const spec = npa(s, this.path) | ||
} else { | ||
// XXX this is a bit inefficient. we parse the spec, and then | ||
// immediately throw it away and keep just the rawSpec. Later, | ||
// when creating the Node, we parse them AGAIN for the Edge objs | ||
// and multiple other times when checking if a dep is valid. | ||
// It'd be eleganter to parse it once, and keep the spec around as | ||
// an object for all future uses, even though npa is pretty fast. | ||
this[_resolvedAdd][type] = this[_resolvedAdd][type] || {} | ||
@@ -259,2 +303,3 @@ this[_resolvedAdd][type][spec.name] = spec.rawSpec | ||
} | ||
return Promise.all(promises).then(() => { | ||
@@ -542,2 +587,7 @@ // get the list of deps that we're explicitly requesting, so that | ||
break | ||
// when installing globally, or just in global style, we never place | ||
// deps above the first level. | ||
if (this[_globalStyle] && check.resolveParent === this.idealTree) | ||
break | ||
} | ||
@@ -544,0 +594,0 @@ |
@@ -15,8 +15,8 @@ // mix-in implementing the loadActual method | ||
const loadFSNode = Symbol('loadFSNode') | ||
const newNode = Symbol('newNode') | ||
const newLink = Symbol('newLink') | ||
const loadFSTree = Symbol('loadFSTree') | ||
const loadFSChildren = Symbol('loadFSChildren') | ||
const findFSParents = Symbol('findFSParents') | ||
const _loadFSNode = Symbol('loadFSNode') | ||
const _newNode = Symbol('newNode') | ||
const _newLink = Symbol('newLink') | ||
const _loadFSTree = Symbol('loadFSTree') | ||
const _loadFSChildren = Symbol('loadFSChildren') | ||
const _findFSParents = Symbol('findFSParents') | ||
@@ -31,2 +31,5 @@ const _actualTreeLoaded = Symbol('actualTreeLoaded') | ||
const _filter = Symbol('filter') | ||
const _global = Symbol.for('global') | ||
module.exports = cls => class ActualLoader extends cls { | ||
@@ -36,2 +39,4 @@ constructor (options) { | ||
this[_global] = !!options.global | ||
// the tree of nodes on disk | ||
@@ -64,3 +69,3 @@ this.actualTree = options.actualTree | ||
// public method | ||
loadActual () { | ||
loadActual (options = {}) { | ||
// mostly realpath to throw if the root doesn't exist | ||
@@ -70,4 +75,19 @@ if (this.actualTree) | ||
const { global = false, filter = () => true } = options | ||
this[_filter] = filter | ||
if (global) { | ||
return realpath(this.path, this[_rpcache], this[_stcache]) | ||
.then(real => this[this.path === real ? _newNode : _newLink]({ | ||
path: this.path, | ||
realpath: real, | ||
pkg: {}, | ||
global, | ||
})).then(node => { | ||
this.actualTree = node | ||
return this[_loadActualActually]() | ||
}) | ||
} | ||
return realpath(this.path, this[_rpcache], this[_stcache]) | ||
.then(real => this[loadFSNode]({ path: this.path, real })) | ||
.then(real => this[_loadFSNode]({ path: this.path, real })) | ||
.then(node => { | ||
@@ -80,2 +100,3 @@ // XXX only rely on this if the hidden lockfile is the newest thing? | ||
this.actualTree = node | ||
return Shrinkwrap.load({ | ||
@@ -116,4 +137,4 @@ path: node.realpath, | ||
// important when a link points at a node we end up visiting later. | ||
return this[loadFSTree](this.actualTree) | ||
.then(() => this[findFSParents]()) | ||
return this[_loadFSTree](this.actualTree) | ||
.then(() => this[_findFSParents]()) | ||
.then(() => calcDepFlags(this.actualTree)) | ||
@@ -123,7 +144,7 @@ .then(() => this.actualTree) | ||
[loadFSNode] ({ path, parent, real, root }) { | ||
[_loadFSNode] ({ path, parent, real, root }) { | ||
if (!real) | ||
return realpath(path, this[_rpcache], this[_stcache]) | ||
.then( | ||
real => this[loadFSNode]({ path, parent, real, root }), | ||
real => this[_loadFSNode]({ path, parent, real, root }), | ||
// if realpath fails, just provide a dummy error node | ||
@@ -150,3 +171,3 @@ error => new Node({ error, path, realpath: path, parent, root }) | ||
.then(([pkg, error]) => { | ||
return this[path === real ? newNode : newLink]({ | ||
return this[path === real ? _newNode : _newLink]({ | ||
path, | ||
@@ -175,3 +196,3 @@ realpath: real, | ||
// Hence this kludge. | ||
[newNode] (options) { | ||
[_newNode] (options) { | ||
// check it for an fsParent if it's a tree top. there's a decent chance | ||
@@ -188,3 +209,3 @@ // it'll get parented later, making the fsParent scan a no-op, but better | ||
[newLink] (options) { | ||
[_newLink] (options) { | ||
const { realpath } = options | ||
@@ -203,4 +224,4 @@ this[_linkTargets].add(realpath) | ||
if (nmParent) { | ||
return this[loadFSNode]({ path: nmParent, root: link.root }) | ||
.then(node => this[loadFSTree](node)) | ||
return this[_loadFSNode]({ path: nmParent, root: link.root }) | ||
.then(node => this[_loadFSTree](node)) | ||
.then(() => link) | ||
@@ -214,3 +235,3 @@ } | ||
[loadFSTree] (node) { | ||
[_loadFSTree] (node) { | ||
const did = this[_actualTreeLoaded] | ||
@@ -222,3 +243,3 @@ node = node.target || node | ||
if (node.then) | ||
return node.then(node => this[loadFSTree](node)) | ||
return node.then(node => this[_loadFSTree](node)) | ||
@@ -231,7 +252,7 @@ // impossible except in pathological ELOOP cases | ||
did.add(node.realpath) | ||
return this[loadFSChildren](node) | ||
return this[_loadFSChildren](node) | ||
.then(() => Promise.all( | ||
[...node.children.entries()] | ||
.filter(([name, kid]) => !did.has(kid.realpath)) | ||
.map(([name, kid]) => this[loadFSTree](kid)))) | ||
.map(([name, kid]) => this[_loadFSTree](kid)))) | ||
} | ||
@@ -241,3 +262,3 @@ | ||
// and attach them to the node as a parent | ||
[loadFSChildren] (node) { | ||
[_loadFSChildren] (node) { | ||
const nm = resolve(node.realpath, 'node_modules') | ||
@@ -248,6 +269,7 @@ return readdir(nm).then(kids => { | ||
kids.filter(kid => !/^(@[^/]+\/)?\./.test(kid)) | ||
.map(kid => this[loadFSNode]({ | ||
parent: node, | ||
path: resolve(nm, kid), | ||
}))) | ||
.filter(kid => this[_filter](node, kid)) | ||
.map(kid => this[_loadFSNode]({ | ||
parent: node, | ||
path: resolve(nm, kid), | ||
}))) | ||
}, | ||
@@ -262,3 +284,3 @@ // error in the readdir is not fatal, just means no kids | ||
// world would not have noticed. | ||
[findFSParents] () { | ||
[_findFSParents] () { | ||
for (const path of this[_linkTargets]) { | ||
@@ -265,0 +287,0 @@ const node = this[_cache].get(path) |
@@ -48,2 +48,3 @@ // mixin implementing the reify method | ||
const _handleOptionalFailure = Symbol('handleOptionalFailure') | ||
const _loadTrees = Symbol('loadTrees') | ||
@@ -81,3 +82,3 @@ // shared symbols for swapping out when testing | ||
const _global = Symbol('global') | ||
const _global = Symbol.for('global') | ||
const _ignoreScripts = Symbol('ignoreScripts') | ||
@@ -89,3 +90,3 @@ const _scriptShell = Symbol('scriptShell') | ||
const _idealTreePrune = Symbol.for('idealTreePrune') | ||
const _explicitRootInstalls = Symbol.for('explicitRootInstalls') | ||
const _explicitRequests = Symbol.for('explicitRequests') | ||
const _resolvedAdd = Symbol.for('resolvedAdd') | ||
@@ -99,3 +100,2 @@ | ||
ignoreScripts = false, | ||
global = false, | ||
force = false, | ||
@@ -107,3 +107,2 @@ scriptShell, | ||
this[_ignoreScripts] = !!ignoreScripts | ||
this[_global] = !!global | ||
this[_force] = !!force | ||
@@ -129,3 +128,3 @@ this[_scriptShell] = scriptShell | ||
this.addTracker('reify') | ||
return Promise.all([this.loadActual(), this.buildIdealTree(options)]) | ||
return this[_loadTrees](options) | ||
.then(() => this[_diffTrees]()) | ||
@@ -149,2 +148,20 @@ .then(() => this[_retireShallowNodes]()) | ||
// when doing a local install, we load everything and figure it all out. | ||
// when doing a global install, we *only* care about the explicit requests. | ||
[_loadTrees] (options) { | ||
if (!this[_global]) | ||
return Promise.all([this.loadActual(), this.buildIdealTree(options)]) | ||
// the global install space tends to have a lot of stuff in it. don't | ||
// load all of it, just what we care about. we won't be saving a | ||
// hidden lockfile in there anyway. | ||
const actualOpts = { | ||
global: true, | ||
filter: (node, kid) => !node.isRoot ? true | ||
: this[_explicitRequests].has(kid), | ||
} | ||
return this.buildIdealTree(options) | ||
.then(() => this.loadActual(actualOpts)) | ||
} | ||
[_diffTrees] () { | ||
@@ -203,12 +220,12 @@ // XXX if we have an existing diff already, there should be a way | ||
.catch(er => { | ||
// ignore missing shims, since only windows has them anyway | ||
if (er.code === 'ENOENT' && from.match(/\.(cmd|ps1)$/)) | ||
return | ||
// Occasionally an expected bin file might not exist in the package, | ||
// or a shim/symlink might have been moved aside. If we've already | ||
// handled the most common cause of ENOENT (dir doesn't exist yet), | ||
// then just ignore any ENOENT. | ||
if (er.code === 'ENOENT') | ||
return didMkdirp ? null : mkdirp(dirname(to)).then(() => | ||
this[_renamePath](from, to, true)) | ||
else if (er.code === 'EEXIST') | ||
return rimraf(to).then(() => rename(from, to)) | ||
else if (er.code === 'ENOENT' && !didMkdirp) { | ||
// often this is a matter of the target dir not existing | ||
return mkdirp(dirname(to)).then(() => | ||
this[_renamePath](from, to, true)) | ||
} else | ||
else | ||
throw er | ||
@@ -397,5 +414,5 @@ }) | ||
path: node.path, | ||
top: node.isTop, | ||
top: node.isTop || node.globalTop, | ||
force: this[_force], | ||
global: this[_global], | ||
global: node.globalTop, | ||
}) | ||
@@ -475,5 +492,5 @@ } | ||
path: node.path, | ||
top: node.isTop, | ||
top: node.isTop || node.globalTop, | ||
force: this[_force], | ||
global: this[_global], | ||
global: node.globalTop, | ||
})) | ||
@@ -722,3 +739,3 @@ } | ||
// support save=false option | ||
if (options.save === false) | ||
if (options.save === false || this[_global]) | ||
return | ||
@@ -780,4 +797,4 @@ | ||
return this.actualTree.meta.save() | ||
return !this[_global] && this.actualTree.meta.save() | ||
} | ||
} |
@@ -36,2 +36,3 @@ // inventory, path, realpath, root, and parent | ||
const {normalize} = require('read-package-json-fast') | ||
const {getPaths: getBinPaths} = require('bin-links') | ||
@@ -57,2 +58,3 @@ /* istanbul ignore next */ | ||
const _delistFromMeta = Symbol('_delistFromMeta') | ||
const _global = Symbol.for('global') | ||
@@ -89,4 +91,8 @@ const relpath = require('./relpath.js') | ||
peer = true, | ||
global = false, | ||
} = options | ||
// true if part of a global install | ||
this[_global] = global | ||
this.errors = error ? [error] : [] | ||
@@ -198,2 +204,11 @@ const pkg = normalize(options.pkg || {}) | ||
get global () { | ||
return this.root[_global] | ||
} | ||
// true for packages installed directly in the global node_modules folder | ||
get globalTop () { | ||
return this.global && this.parent.isRoot | ||
} | ||
get binPaths () { | ||
@@ -203,14 +218,8 @@ if (!this.parent) | ||
// just use a string resolution rather than path functions because | ||
// we already know that the path is .../node_modules/node.name | ||
const base = this.path.slice(0, -1 * this.name.length) | ||
const nmbin = resolve(base, '.bin') | ||
const paths = [] | ||
for (const bin of Object.keys(this[_package].bin || {})) { | ||
paths.push(resolve(nmbin, bin)) | ||
paths.push(resolve(nmbin, bin + '.cmd')) | ||
paths.push(resolve(nmbin, bin + '.ps1')) | ||
} | ||
return paths | ||
return getBinPaths({ | ||
pkg: this[_package], | ||
path: this.path, | ||
global: this.global, | ||
top: this.globalTop, | ||
}) | ||
} | ||
@@ -217,0 +226,0 @@ |
{ | ||
"name": "@npmcli/arborist", | ||
"version": "0.0.0-pre.9", | ||
"version": "0.0.0-pre.10", | ||
"description": "Manage node_modules trees", | ||
@@ -8,3 +8,3 @@ "dependencies": { | ||
"@npmcli/run-script": "^1.2.1", | ||
"bin-links": "^2.0.0", | ||
"bin-links": "^2.1.2", | ||
"json-stringify-nice": "^1.1.1", | ||
@@ -11,0 +11,0 @@ "mkdirp-infer-owner": "^1.0.2", |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
193138
4553
Updatedbin-links@^2.1.2