@npmcli/arborist
Advanced tools
Comparing version 7.3.1 to 7.4.0
@@ -41,3 +41,2 @@ // mixin implementing the buildIdealTree method | ||
const _flagsSuspect = Symbol.for('flagsSuspect') | ||
const _workspaces = Symbol.for('workspaces') | ||
const _setWorkspaces = Symbol.for('setWorkspaces') | ||
@@ -49,11 +48,5 @@ const _updateNames = Symbol.for('updateNames') | ||
const _stcache = Symbol.for('statCache') | ||
const _includeWorkspaceRoot = Symbol.for('includeWorkspaceRoot') | ||
// exposed symbol for unit testing the placeDep method directly | ||
const _peerSetSource = Symbol.for('peerSetSource') | ||
// used by Reify mixin | ||
const _force = Symbol.for('force') | ||
const _global = Symbol.for('global') | ||
const _idealTreePrune = Symbol.for('idealTreePrune') | ||
const _addNodeToTrashList = Symbol.for('addNodeToTrashList') | ||
@@ -122,2 +115,6 @@ // Push items in, pop them sorted by depth and then path | ||
#mutateTree = false | ||
// a map of each module in a peer set to the thing that depended on | ||
// that set of peers in the first place. Use a WeakMap so that we | ||
// don't hold onto references for nodes that are garbage collected. | ||
#peerSetSource = new WeakMap() | ||
#preferDedupe = false | ||
@@ -137,7 +134,4 @@ #prune | ||
follow = false, | ||
force = false, | ||
global = false, | ||
installStrategy = 'hoisted', | ||
idealTree = null, | ||
includeWorkspaceRoot = false, | ||
installLinks = false, | ||
@@ -147,7 +141,6 @@ legacyPeerDeps = false, | ||
strictPeerDeps = false, | ||
workspaces = [], | ||
workspaces, | ||
global, | ||
} = options | ||
this[_workspaces] = workspaces || [] | ||
this[_force] = !!force | ||
this.#strictPeerDeps = !!strictPeerDeps | ||
@@ -160,7 +153,6 @@ | ||
this[_usePackageLock] = packageLock | ||
this[_global] = !!global | ||
this.#installStrategy = global ? 'shallow' : installStrategy | ||
this.#follow = !!follow | ||
if (this[_workspaces].length && this[_global]) { | ||
if (workspaces?.length && global) { | ||
throw new Error('Cannot operate on workspaces in global mode') | ||
@@ -172,9 +164,2 @@ } | ||
this[_resolvedAdd] = [] | ||
// a map of each module in a peer set to the thing that depended on | ||
// that set of peers in the first place. Use a WeakMap so that we | ||
// don't hold onto references for nodes that are garbage collected. | ||
this[_peerSetSource] = new WeakMap() | ||
this[_includeWorkspaceRoot] = includeWorkspaceRoot | ||
} | ||
@@ -206,3 +191,3 @@ | ||
if (!options.add && !options.rm && !options.update && this[_global]) { | ||
if (!options.add && !options.rm && !options.update && this.options.global) { | ||
throw new Error('global requires add, rm, or update option') | ||
@@ -243,3 +228,3 @@ } | ||
try { | ||
checkEngine(node.package, npmVersion, nodeVersion, this[_force]) | ||
checkEngine(node.package, npmVersion, nodeVersion, this.options.force) | ||
} catch (err) { | ||
@@ -255,3 +240,3 @@ if (engineStrict) { | ||
} | ||
checkPlatform(node.package, this[_force]) | ||
checkPlatform(node.package, this.options.force) | ||
} | ||
@@ -308,3 +293,3 @@ } | ||
let root | ||
if (this[_global]) { | ||
if (this.options.global) { | ||
root = await this.#globalRootNode() | ||
@@ -327,3 +312,3 @@ } else { | ||
// reconstructing it anyway. | ||
.then(root => this[_global] ? root | ||
.then(root => this.options.global ? root | ||
: !this[_usePackageLock] || this[_updateAll] | ||
@@ -344,3 +329,3 @@ ? Shrinkwrap.reset({ | ||
.then(async root => { | ||
if ((!this[_updateAll] && !this[_global] && !root.meta.loadedFromDisk) || (this[_global] && this[_updateNames].length)) { | ||
if ((!this[_updateAll] && !this.options.global && !root.meta.loadedFromDisk) || (this.options.global && this[_updateNames].length)) { | ||
await new this.constructor(this.options).loadActual({ root }) | ||
@@ -424,3 +409,3 @@ const tree = root.target | ||
optional: false, | ||
global: this[_global], | ||
global: this.options.global, | ||
installLinks: this.installLinks, | ||
@@ -440,3 +425,3 @@ legacyPeerDeps: this.legacyPeerDeps, | ||
optional: false, | ||
global: this[_global], | ||
global: this.options.global, | ||
installLinks: this.installLinks, | ||
@@ -456,7 +441,7 @@ legacyPeerDeps: this.legacyPeerDeps, | ||
if (!this[_workspaces].length) { | ||
if (!this.options.workspaces.length) { | ||
await this.#applyUserRequestsToNode(tree, options) | ||
} else { | ||
const nodes = this.workspaceNodes(tree, this[_workspaces]) | ||
if (this[_includeWorkspaceRoot]) { | ||
const nodes = this.workspaceNodes(tree, this.options.workspaces) | ||
if (this.options.includeWorkspaceRoot) { | ||
nodes.push(tree) | ||
@@ -477,3 +462,3 @@ } | ||
// named nodes to the buildIdealTree queue. | ||
if (!this[_global] && this[_updateNames].length) { | ||
if (!this.options.global && this[_updateNames].length) { | ||
this.#queueNamedUpdates() | ||
@@ -485,3 +470,3 @@ } | ||
const globalExplicitUpdateNames = [] | ||
if (this[_global] && (this[_updateAll] || this[_updateNames].length)) { | ||
if (this.options.global && (this[_updateAll] || this[_updateNames].length)) { | ||
const nm = resolve(this.path, 'node_modules') | ||
@@ -531,3 +516,3 @@ const paths = await readdirScoped(nm).catch(() => []) | ||
// resets all edgesOut. | ||
if (add && add.length || rm && rm.length || this[_global]) { | ||
if (add && add.length || rm && rm.length || this.options.global) { | ||
tree.package = tree.package | ||
@@ -638,3 +623,3 @@ } | ||
// just tells the user to cd into that directory and fix it? | ||
if (this[_force] && this.auditReport && this.auditReport.topVulns.size) { | ||
if (this.options.force && this.auditReport && this.auditReport.topVulns.size) { | ||
options.add = options.add || [] | ||
@@ -923,3 +908,3 @@ options.rm = options.rm || [] | ||
const tasks = [] | ||
const peerSource = this[_peerSetSource].get(node) || node | ||
const peerSource = this.#peerSetSource.get(node) || node | ||
for (const edge of this.#problemEdges(node)) { | ||
@@ -982,3 +967,3 @@ if (edge.peerConflicted) { | ||
explicitRequest: this.#explicitRequests.has(edge), | ||
force: this[_force], | ||
force: this.options.force, | ||
installLinks: this.installLinks, | ||
@@ -1124,3 +1109,3 @@ installStrategy: this.#installStrategy, | ||
const src = parent.sourceReference | ||
this[_peerSetSource].set(node, src) | ||
this.#peerSetSource.set(node, src) | ||
@@ -1131,3 +1116,3 @@ // do not load the peers along with the set if this is a global top pkg | ||
// what we want. the missing edges will be picked up on the next pass. | ||
if (this[_global] && edge.from.isProjectRoot) { | ||
if (this.options.global && edge.from.isProjectRoot) { | ||
return node | ||
@@ -1355,3 +1340,3 @@ } | ||
const isMine = isProjectRoot || isWorkspace | ||
const conflictOK = this[_force] || !isMine && !this.#strictPeerDeps | ||
const conflictOK = this.options.force || !isMine && !this.#strictPeerDeps | ||
@@ -1443,3 +1428,3 @@ if (!edge.to) { | ||
strictPeerDeps: this.#strictPeerDeps, | ||
force: this[_force], | ||
force: this.options.force, | ||
} | ||
@@ -1532,3 +1517,3 @@ } | ||
if (this.#prune && needPrune) { | ||
this[_idealTreePrune]() | ||
this.#idealTreePrune() | ||
for (const node of this.idealTree.inventory.values()) { | ||
@@ -1544,3 +1529,3 @@ if (node.extraneous) { | ||
[_idealTreePrune] () { | ||
#idealTreePrune () { | ||
for (const node of this.idealTree.inventory.values()) { | ||
@@ -1565,2 +1550,27 @@ if (node.extraneous) { | ||
} | ||
async prune (options = {}) { | ||
// allow the user to set options on the ctor as well. | ||
// XXX: deprecate separate method options objects. | ||
options = { ...this.options, ...options } | ||
await this.buildIdealTree(options) | ||
this.#idealTreePrune() | ||
if (!this.options.workspacesEnabled) { | ||
const excludeNodes = this.excludeWorkspacesDependencySet(this.idealTree) | ||
for (const node of this.idealTree.inventory.values()) { | ||
if ( | ||
node.parent !== null | ||
&& !node.isProjectRoot | ||
&& !excludeNodes.has(node) | ||
) { | ||
this[_addNodeToTrashList](node) | ||
} | ||
} | ||
} | ||
return this.reify(options) | ||
} | ||
} |
@@ -32,11 +32,12 @@ // The arborist manages three trees: | ||
const { depth } = require('treeverse') | ||
const mapWorkspaces = require('@npmcli/map-workspaces') | ||
const log = require('proc-log') | ||
const { saveTypeMap } = require('../add-rm-pkg-deps.js') | ||
const AuditReport = require('../audit-report.js') | ||
const relpath = require('../relpath.js') | ||
const mixins = [ | ||
require('../tracker.js'), | ||
require('./pruner.js'), | ||
require('./deduper.js'), | ||
require('./audit.js'), | ||
require('./build-ideal-tree.js'), | ||
require('./set-workspaces.js'), | ||
require('./load-actual.js'), | ||
@@ -49,5 +50,4 @@ require('./load-virtual.js'), | ||
const _workspacesEnabled = Symbol.for('workspacesEnabled') | ||
const _setWorkspaces = Symbol.for('setWorkspaces') | ||
const Base = mixins.reduce((a, b) => b(a), require('events')) | ||
const getWorkspaceNodes = require('../get-workspace-nodes.js') | ||
@@ -77,10 +77,18 @@ // if it's 1, 2, or 3, set it explicitly that. | ||
Arborist: this.constructor, | ||
path: options.path || '.', | ||
binLinks: 'binLinks' in options ? !!options.binLinks : true, | ||
cache: options.cache || `${homedir()}/.npm/_cacache`, | ||
force: !!options.force, | ||
global: !!options.global, | ||
ignoreScripts: !!options.ignoreScripts, | ||
installStrategy: options.global ? 'shallow' : (options.installStrategy ? options.installStrategy : 'hoisted'), | ||
lockfileVersion: lockfileVersion(options.lockfileVersion), | ||
packumentCache: options.packumentCache || new Map(), | ||
path: options.path || '.', | ||
rebuildBundle: 'rebuildBundle' in options ? !!options.rebuildBundle : true, | ||
replaceRegistryHost: options.replaceRegistryHost, | ||
scriptShell: options.scriptShell, | ||
workspaces: options.workspaces || [], | ||
workspacesEnabled: options.workspacesEnabled !== false, | ||
replaceRegistryHost: options.replaceRegistryHost, | ||
lockfileVersion: lockfileVersion(options.lockfileVersion), | ||
installStrategy: options.global ? 'shallow' : (options.installStrategy ? options.installStrategy : 'hoisted'), | ||
} | ||
// TODO is this even used? If not is that a bug? | ||
this.replaceRegistryHost = this.options.replaceRegistryHost = | ||
@@ -90,4 +98,2 @@ (!this.options.replaceRegistryHost || this.options.replaceRegistryHost === 'npmjs') ? | ||
this[_workspacesEnabled] = this.options.workspacesEnabled | ||
if (options.saveType && !saveTypeMap.get(options.saveType)) { | ||
@@ -104,8 +110,36 @@ throw new Error(`Invalid saveType ${options.saveType}`) | ||
// returns an array of the actual nodes for all the workspaces | ||
// Get the actual nodes corresponding to a root node's child workspaces, | ||
// given a list of workspace names. | ||
workspaceNodes (tree, workspaces) { | ||
return getWorkspaceNodes(tree, workspaces) | ||
const wsMap = tree.workspaces | ||
if (!wsMap) { | ||
log.warn('workspaces', 'filter set, but no workspaces present') | ||
return [] | ||
} | ||
const nodes = [] | ||
for (const name of workspaces) { | ||
const path = wsMap.get(name) | ||
if (!path) { | ||
log.warn('workspaces', `${name} in filter set, but not in workspaces`) | ||
continue | ||
} | ||
const loc = relpath(tree.realpath, path) | ||
const node = tree.inventory.get(loc) | ||
if (!node) { | ||
log.warn('workspaces', `${name} in filter set, but no workspace folder present`) | ||
continue | ||
} | ||
nodes.push(node) | ||
} | ||
return nodes | ||
} | ||
// returns a set of workspace nodes and all their deps | ||
// TODO why is includeWorkspaceRoot a param? | ||
// TODO why is workspaces a param? | ||
workspaceDependencySet (tree, workspaces, includeWorkspaceRoot) { | ||
@@ -170,4 +204,58 @@ const wsNodes = this.workspaceNodes(tree, workspaces) | ||
} | ||
async [_setWorkspaces] (node) { | ||
const workspaces = await mapWorkspaces({ | ||
cwd: node.path, | ||
pkg: node.package, | ||
}) | ||
if (node && workspaces.size) { | ||
node.workspaces = workspaces | ||
} | ||
return node | ||
} | ||
async audit (options = {}) { | ||
this.addTracker('audit') | ||
if (this.options.global) { | ||
throw Object.assign( | ||
new Error('`npm audit` does not support testing globals'), | ||
{ code: 'EAUDITGLOBAL' } | ||
) | ||
} | ||
// allow the user to set options on the ctor as well. | ||
// XXX: deprecate separate method options objects. | ||
options = { ...this.options, ...options } | ||
process.emit('time', 'audit') | ||
let tree | ||
if (options.packageLock === false) { | ||
// build ideal tree | ||
await this.loadActual(options) | ||
await this.buildIdealTree() | ||
tree = this.idealTree | ||
} else { | ||
tree = await this.loadVirtual() | ||
} | ||
if (this.options.workspaces.length) { | ||
options.filterSet = this.workspaceDependencySet( | ||
tree, | ||
this.options.workspaces, | ||
this.options.includeWorkspaceRoot | ||
) | ||
} | ||
if (!options.workspacesEnabled) { | ||
options.filterSet = | ||
this.excludeWorkspacesDependencySet(tree) | ||
} | ||
this.auditReport = await AuditReport.load(tree, options) | ||
const ret = options.fix ? this.reify(options) : this.auditReport | ||
process.emit('timeEnd', 'audit') | ||
this.finishTracker('audit') | ||
return ret | ||
} | ||
} | ||
module.exports = Arborist |
@@ -19,3 +19,2 @@ // mix-in implementing the loadActual method | ||
const _changePath = Symbol.for('_changePath') | ||
const _global = Symbol.for('global') | ||
const _setWorkspaces = Symbol.for('setWorkspaces') | ||
@@ -49,4 +48,2 @@ const _rpcache = Symbol.for('realpathCache') | ||
this[_global] = !!options.global | ||
// the tree of nodes on disk | ||
@@ -63,2 +60,3 @@ this.actualTree = options.actualTree | ||
// public method | ||
// TODO remove options param in next semver major | ||
async loadActual (options = {}) { | ||
@@ -106,3 +104,3 @@ // In the past this.actualTree was set as a promise that eventually | ||
const { | ||
global = false, | ||
global, | ||
filter = () => true, | ||
@@ -109,0 +107,0 @@ root = null, |
@@ -22,26 +22,4 @@ // Arborist.rebuild({path = this.path}) will do all the binlinks and | ||
const _workspaces = Symbol.for('workspaces') | ||
const _build = Symbol('build') | ||
const _loadDefaultNodes = Symbol('loadDefaultNodes') | ||
const _retrieveNodesByType = Symbol('retrieveNodesByType') | ||
const _resetQueues = Symbol('resetQueues') | ||
const _rebuildBundle = Symbol('rebuildBundle') | ||
const _ignoreScripts = Symbol('ignoreScripts') | ||
const _binLinks = Symbol('binLinks') | ||
const _oldMeta = Symbol('oldMeta') | ||
const _createBinLinks = Symbol('createBinLinks') | ||
const _doHandleOptionalFailure = Symbol('doHandleOptionalFailure') | ||
const _linkAllBins = Symbol('linkAllBins') | ||
const _runScripts = Symbol('runScripts') | ||
const _buildQueues = Symbol('buildQueues') | ||
const _addToBuildSet = Symbol('addToBuildSet') | ||
const _checkBins = Symbol.for('checkBins') | ||
const _queues = Symbol('queues') | ||
const _scriptShell = Symbol('scriptShell') | ||
const _includeWorkspaceRoot = Symbol.for('includeWorkspaceRoot') | ||
const _workspacesEnabled = Symbol.for('workspacesEnabled') | ||
const _force = Symbol.for('force') | ||
const _global = Symbol.for('global') | ||
// defined by reify mixin | ||
@@ -52,19 +30,11 @@ const _handleOptionalFailure = Symbol.for('handleOptionalFailure') | ||
module.exports = cls => class Builder extends cls { | ||
#doHandleOptionalFailure | ||
#oldMeta = null | ||
#queues | ||
constructor (options) { | ||
super(options) | ||
const { | ||
ignoreScripts = false, | ||
scriptShell, | ||
binLinks = true, | ||
rebuildBundle = true, | ||
} = options | ||
this.scriptsRun = new Set() | ||
this[_binLinks] = binLinks | ||
this[_ignoreScripts] = !!ignoreScripts | ||
this[_scriptShell] = scriptShell | ||
this[_rebuildBundle] = !!rebuildBundle | ||
this[_resetQueues]() | ||
this[_oldMeta] = null | ||
this.#resetQueues() | ||
} | ||
@@ -74,3 +44,3 @@ | ||
// nothing to do if we're not building anything! | ||
if (this[_ignoreScripts] && !this[_binLinks]) { | ||
if (this.options.ignoreScripts && !this.options.binLinks) { | ||
return | ||
@@ -82,6 +52,6 @@ } | ||
// running JUST a rebuild, we treat optional failures as real fails | ||
this[_doHandleOptionalFailure] = handleOptionalFailure | ||
this.#doHandleOptionalFailure = handleOptionalFailure | ||
if (!nodes) { | ||
nodes = await this[_loadDefaultNodes]() | ||
nodes = await this.#loadDefaultNodes() | ||
} | ||
@@ -96,11 +66,11 @@ | ||
linkNodes, | ||
} = this[_retrieveNodesByType](nodes) | ||
} = this.#retrieveNodesByType(nodes) | ||
// build regular deps | ||
await this[_build](depNodes, {}) | ||
await this.#build(depNodes, {}) | ||
// build link deps | ||
if (linkNodes.size) { | ||
this[_resetQueues]() | ||
await this[_build](linkNodes, { type: 'links' }) | ||
this.#resetQueues() | ||
await this.#build(linkNodes, { type: 'links' }) | ||
} | ||
@@ -113,7 +83,7 @@ | ||
// the actual tree on disk. | ||
async [_loadDefaultNodes] () { | ||
async #loadDefaultNodes () { | ||
let nodes | ||
const tree = await this.loadActual() | ||
let filterSet | ||
if (!this[_workspacesEnabled]) { | ||
if (!this.options.workspacesEnabled) { | ||
filterSet = this.excludeWorkspacesDependencySet(tree) | ||
@@ -123,7 +93,7 @@ nodes = tree.inventory.filter(node => | ||
) | ||
} else if (this[_workspaces] && this[_workspaces].length) { | ||
} else if (this.options.workspaces.length) { | ||
filterSet = this.workspaceDependencySet( | ||
tree, | ||
this[_workspaces], | ||
this[_includeWorkspaceRoot] | ||
this.options.workspaces, | ||
this.options.includeWorkspaceRoot | ||
) | ||
@@ -137,3 +107,3 @@ nodes = tree.inventory.filter(node => filterSet.has(node)) | ||
[_retrieveNodesByType] (nodes) { | ||
#retrieveNodesByType (nodes) { | ||
const depNodes = new Set() | ||
@@ -165,3 +135,3 @@ const linkNodes = new Set() | ||
// on having the target nodes available in global mode. | ||
if (!this[_global]) { | ||
if (!this.options.global) { | ||
for (const node of linkNodes) { | ||
@@ -178,4 +148,4 @@ depNodes.delete(node.target) | ||
[_resetQueues] () { | ||
this[_queues] = { | ||
#resetQueues () { | ||
this.#queues = { | ||
preinstall: [], | ||
@@ -189,9 +159,9 @@ install: [], | ||
async [_build] (nodes, { type = 'deps' }) { | ||
async #build (nodes, { type = 'deps' }) { | ||
process.emit('time', `build:${type}`) | ||
await this[_buildQueues](nodes) | ||
await this.#buildQueues(nodes) | ||
if (!this[_ignoreScripts]) { | ||
await this[_runScripts]('preinstall') | ||
if (!this.options.ignoreScripts) { | ||
await this.#runScripts('preinstall') | ||
} | ||
@@ -201,11 +171,11 @@ | ||
if (type === 'links') { | ||
await this[_runScripts]('prepare') | ||
await this.#runScripts('prepare') | ||
} | ||
if (this[_binLinks]) { | ||
await this[_linkAllBins]() | ||
if (this.options.binLinks) { | ||
await this.#linkAllBins() | ||
} | ||
if (!this[_ignoreScripts]) { | ||
await this[_runScripts]('install') | ||
await this[_runScripts]('postinstall') | ||
if (!this.options.ignoreScripts) { | ||
await this.#runScripts('install') | ||
await this.#runScripts('postinstall') | ||
} | ||
@@ -216,3 +186,3 @@ | ||
async [_buildQueues] (nodes) { | ||
async #buildQueues (nodes) { | ||
process.emit('time', 'build:queue') | ||
@@ -223,6 +193,6 @@ const set = new Set() | ||
for (const node of nodes) { | ||
promises.push(this[_addToBuildSet](node, set)) | ||
promises.push(this.#addToBuildSet(node, set)) | ||
// if it has bundle deps, add those too, if rebuildBundle | ||
if (this[_rebuildBundle] !== false) { | ||
if (this.options.rebuildBundle !== false) { | ||
const bd = node.package.bundleDependencies | ||
@@ -232,3 +202,3 @@ if (bd && bd.length) { | ||
tree: node, | ||
leave: node => promises.push(this[_addToBuildSet](node, set)), | ||
leave: node => promises.push(this.#addToBuildSet(node, set)), | ||
getChildren: node => [...node.children.values()], | ||
@@ -254,3 +224,3 @@ filter: node => node.inBundle, | ||
if (has) { | ||
this[_queues][key].push(node) | ||
this.#queues[key].push(node) | ||
} | ||
@@ -268,3 +238,3 @@ } | ||
// binaries, which is unsafe and insecure. | ||
if (!node.globalTop || this[_force]) { | ||
if (!node.globalTop || this.options.force) { | ||
return | ||
@@ -276,3 +246,3 @@ } | ||
async [_addToBuildSet] (node, set, refreshed = false) { | ||
async #addToBuildSet (node, set, refreshed = false) { | ||
if (set.has(node)) { | ||
@@ -282,5 +252,5 @@ return | ||
if (this[_oldMeta] === null) { | ||
if (this.#oldMeta === null) { | ||
const { root: { meta } } = node | ||
this[_oldMeta] = meta && meta.loadedFromDisk && | ||
this.#oldMeta = meta && meta.loadedFromDisk && | ||
!(meta.originalLockfileVersion >= 2) | ||
@@ -294,3 +264,3 @@ } | ||
const anyScript = preinstall || install || postinstall || prepare | ||
if (!refreshed && !anyScript && (hasInstallScript || this[_oldMeta])) { | ||
if (!refreshed && !anyScript && (hasInstallScript || this.#oldMeta)) { | ||
// we either have an old metadata (and thus might have scripts) | ||
@@ -309,3 +279,3 @@ // or we have an indication that there's install scripts (but | ||
node.package.scripts = scripts | ||
return this[_addToBuildSet](node, set, true) | ||
return this.#addToBuildSet(node, set, true) | ||
} | ||
@@ -333,4 +303,4 @@ | ||
async [_runScripts] (event) { | ||
const queue = this[_queues][event] | ||
async #runScripts (event) { | ||
const queue = this.#queues[event] | ||
@@ -383,3 +353,3 @@ if (!queue.length) { | ||
env, | ||
scriptShell: this[_scriptShell], | ||
scriptShell: this.options.scriptShell, | ||
} | ||
@@ -408,3 +378,3 @@ const p = runScript(runOpts).catch(er => { | ||
await (this[_doHandleOptionalFailure] | ||
await (this.#doHandleOptionalFailure | ||
? this[_handleOptionalFailure](node, p) | ||
@@ -418,4 +388,4 @@ : p) | ||
async [_linkAllBins] () { | ||
const queue = this[_queues].bin | ||
async #linkAllBins () { | ||
const queue = this.#queues.bin | ||
if (!queue.length) { | ||
@@ -430,3 +400,4 @@ return | ||
for (const node of queue.sort(sortNodes)) { | ||
promises.push(this[_createBinLinks](node)) | ||
// TODO these run before they're awaited | ||
promises.push(this.#createBinLinks(node)) | ||
} | ||
@@ -438,3 +409,3 @@ | ||
async [_createBinLinks] (node) { | ||
async #createBinLinks (node) { | ||
if (this[_trashList].has(node.path)) { | ||
@@ -450,7 +421,7 @@ return | ||
top: !!(node.isTop || node.globalTop), | ||
force: this[_force], | ||
force: this.options.force, | ||
global: !!node.globalTop, | ||
}) | ||
await (this[_doHandleOptionalFailure] | ||
await (this.#doHandleOptionalFailure | ||
? this[_handleOptionalFailure](node, p) | ||
@@ -457,0 +428,0 @@ : p) |
@@ -27,3 +27,2 @@ // mixin implementing the reify method | ||
const { checkEngine, checkPlatform } = require('npm-install-checks') | ||
const _force = Symbol.for('force') | ||
@@ -52,4 +51,2 @@ const treeCheck = require('../tree-check.js') | ||
const _addNodeToTrashList = Symbol.for('addNodeToTrashList') | ||
const _workspaces = Symbol.for('workspaces') | ||
const _workspacesEnabled = Symbol.for('workspacesEnabled') | ||
@@ -96,3 +93,2 @@ // shared by rebuild mixin | ||
const _reifyPackages = Symbol.for('reifyPackages') | ||
const _includeWorkspaceRoot = Symbol.for('includeWorkspaceRoot') | ||
@@ -103,4 +99,2 @@ const _omitDev = Symbol('omitDev') | ||
const _global = Symbol.for('global') | ||
const _pruneBundledMetadeps = Symbol('pruneBundledMetadeps') | ||
@@ -149,3 +143,3 @@ | ||
if (this[_packageLockOnly] && this[_global]) { | ||
if (this[_packageLockOnly] && this.options.global) { | ||
const er = new Error('cannot generate lockfile for global packages') | ||
@@ -295,3 +289,3 @@ er.code = 'ESHRINKWRAPGLOBAL' | ||
const actualOpt = this[_global] ? { | ||
const actualOpt = this.options.global ? { | ||
ignoreMissing: true, | ||
@@ -323,3 +317,3 @@ global: true, | ||
if (!this[_global]) { | ||
if (!this.options.global) { | ||
return Promise.all([ | ||
@@ -351,8 +345,8 @@ this.loadActual(actualOpt), | ||
const includeWorkspaces = this[_workspacesEnabled] | ||
const includeRootDeps = !this[_workspacesEnabled] | ||
|| this[_includeWorkspaceRoot] && this[_workspaces].length > 0 | ||
const includeWorkspaces = this.options.workspacesEnabled | ||
const includeRootDeps = !includeWorkspaces | ||
|| this.options.includeWorkspaceRoot && this.options.workspaces.length > 0 | ||
const filterNodes = [] | ||
if (this[_global] && this.explicitRequests.size) { | ||
if (this.options.global && this.explicitRequests.size) { | ||
const idealTree = this.idealTree.target | ||
@@ -375,3 +369,3 @@ const actualTree = this.actualTree.target | ||
// add all ws nodes to filterNodes | ||
for (const ws of this[_workspaces]) { | ||
for (const ws of this.options.workspaces) { | ||
const ideal = this.idealTree.children.get(ws) | ||
@@ -668,3 +662,3 @@ if (ideal) { | ||
async [_validateNodeModules] (nm) { | ||
if (this[_force] || this[_nmValidated].has(nm)) { | ||
if (this.options.force || this[_nmValidated].has(nm)) { | ||
return | ||
@@ -1005,7 +999,7 @@ } | ||
// if we're operating on a workspace, only audit the workspace deps | ||
if (this[_workspaces] && this[_workspaces].length) { | ||
if (this.options.workspaces.length) { | ||
options.filterSet = this.workspaceDependencySet( | ||
tree, | ||
this[_workspaces], | ||
this[_includeWorkspaceRoot] | ||
this.options.workspaces, | ||
this.options.includeWorkspaceRoot | ||
) | ||
@@ -1234,3 +1228,3 @@ } | ||
(!save && !hasUpdates) | ||
|| this[_global] | ||
|| this.options.global | ||
|| this[_dryRun] | ||
@@ -1581,3 +1575,3 @@ ) | ||
if (!this[_global]) { | ||
if (!this.options.global) { | ||
await this.actualTree.meta.save() | ||
@@ -1609,2 +1603,20 @@ const ignoreScripts = !!this.options.ignoreScripts | ||
} | ||
async dedupe (options = {}) { | ||
// allow the user to set options on the ctor as well. | ||
// XXX: deprecate separate method options objects. | ||
options = { ...this.options, ...options } | ||
const tree = await this.loadVirtual().catch(() => this.loadActual()) | ||
const names = [] | ||
for (const name of tree.inventory.query('name')) { | ||
if (tree.inventory.query('name', name).size > 1) { | ||
names.push(name) | ||
} | ||
} | ||
return this.reify({ | ||
...options, | ||
preferDedupe: true, | ||
update: { names }, | ||
}) | ||
} | ||
} |
@@ -5,10 +5,7 @@ // package children are represented with a Map object, but many file systems | ||
const _keys = Symbol('keys') | ||
const _normKey = Symbol('normKey') | ||
const normalize = s => s.normalize('NFKD').toLowerCase() | ||
const OGMap = Map | ||
module.exports = class Map extends OGMap { | ||
module.exports = class CIMap extends Map { | ||
#keys = new Map() | ||
constructor (items = []) { | ||
super() | ||
this[_keys] = new OGMap() | ||
for (const [key, val] of items) { | ||
@@ -19,9 +16,12 @@ this.set(key, val) | ||
[_normKey] (key) { | ||
return typeof key === 'string' ? normalize(key) : key | ||
#normKey (key) { | ||
if (typeof key !== 'string') { | ||
return key | ||
} | ||
return key.normalize('NFKD').toLowerCase() | ||
} | ||
get (key) { | ||
const normKey = this[_normKey](key) | ||
return this[_keys].has(normKey) ? super.get(this[_keys].get(normKey)) | ||
const normKey = this.#normKey(key) | ||
return this.#keys.has(normKey) ? super.get(this.#keys.get(normKey)) | ||
: undefined | ||
@@ -31,7 +31,7 @@ } | ||
set (key, val) { | ||
const normKey = this[_normKey](key) | ||
if (this[_keys].has(normKey)) { | ||
super.delete(this[_keys].get(normKey)) | ||
const normKey = this.#normKey(key) | ||
if (this.#keys.has(normKey)) { | ||
super.delete(this.#keys.get(normKey)) | ||
} | ||
this[_keys].set(normKey, key) | ||
this.#keys.set(normKey, key) | ||
return super.set(key, val) | ||
@@ -41,6 +41,6 @@ } | ||
delete (key) { | ||
const normKey = this[_normKey](key) | ||
if (this[_keys].has(normKey)) { | ||
const prevKey = this[_keys].get(normKey) | ||
this[_keys].delete(normKey) | ||
const normKey = this.#normKey(key) | ||
if (this.#keys.has(normKey)) { | ||
const prevKey = this.#keys.get(normKey) | ||
this.#keys.delete(normKey) | ||
return super.delete(prevKey) | ||
@@ -51,5 +51,5 @@ } | ||
has (key) { | ||
const normKey = this[_normKey](key) | ||
return this[_keys].has(normKey) && super.has(this[_keys].get(normKey)) | ||
const normKey = this.#normKey(key) | ||
return this.#keys.has(normKey) && super.has(this.#keys.get(normKey)) | ||
} | ||
} |
@@ -7,3 +7,1 @@ module.exports = require('./arborist/index.js') | ||
module.exports.Shrinkwrap = require('./shrinkwrap.js') | ||
// XXX export the other classes, too. shrinkwrap, diff, etc. | ||
// they're handy! |
@@ -11,2 +11,3 @@ 'use strict' | ||
const semver = require('semver') | ||
const fetch = require('npm-registry-fetch') | ||
@@ -22,2 +23,3 @@ // handle results for parsed query asts, results are stored in a map that has a | ||
#outdatedCache = new Map() | ||
#vulnCache | ||
#pendingCombinator | ||
@@ -31,2 +33,3 @@ #results = new Map() | ||
this.#initialItems = opts.initialItems | ||
this.#vulnCache = opts.vulnCache | ||
this.#targetNode = opts.targetNode | ||
@@ -217,2 +220,3 @@ | ||
targetNode: item, | ||
vulnCache: this.#vulnCache, | ||
}) | ||
@@ -246,2 +250,3 @@ if (res.size > 0) { | ||
targetNode: this.currentAstNode, | ||
vulnCache: this.#vulnCache, | ||
}) | ||
@@ -274,2 +279,3 @@ return [...res] | ||
targetNode: this.currentAstNode, | ||
vulnCache: this.#vulnCache, | ||
}) | ||
@@ -441,2 +447,71 @@ const internalSelector = new Set(res) | ||
async vulnPseudo () { | ||
if (!this.initialItems.length) { | ||
return this.initialItems | ||
} | ||
if (!this.#vulnCache) { | ||
const packages = {} | ||
// We have to map the items twice, once to get the request, and a second time to filter out the results of that request | ||
this.initialItems.map((node) => { | ||
if (node.isProjectRoot || node.package.private) { | ||
return | ||
} | ||
if (!packages[node.name]) { | ||
packages[node.name] = [] | ||
} | ||
if (!packages[node.name].includes(node.version)) { | ||
packages[node.name].push(node.version) | ||
} | ||
}) | ||
const res = await fetch('/-/npm/v1/security/advisories/bulk', { | ||
...this.flatOptions, | ||
registry: this.flatOptions.auditRegistry || this.flatOptions.registry, | ||
method: 'POST', | ||
gzip: true, | ||
body: packages, | ||
}) | ||
this.#vulnCache = await res.json() | ||
} | ||
const advisories = this.#vulnCache | ||
const { vulns } = this.currentAstNode | ||
return this.initialItems.filter(item => { | ||
const vulnerable = advisories[item.name]?.filter(advisory => { | ||
// This could be for another version of this package elsewhere in the tree | ||
if (!semver.intersects(advisory.vulnerable_versions, item.version)) { | ||
return false | ||
} | ||
if (!vulns) { | ||
return true | ||
} | ||
// vulns are OR with each other, if any one matches we're done | ||
for (const vuln of vulns) { | ||
if (vuln.severity && !vuln.severity.includes('*')) { | ||
if (!vuln.severity.includes(advisory.severity)) { | ||
continue | ||
} | ||
} | ||
if (vuln?.cwe) { | ||
// * is special, it means "has a cwe" | ||
if (vuln.cwe.includes('*')) { | ||
if (!advisory.cwe.length) { | ||
continue | ||
} | ||
} else if (!vuln.cwe.every(cwe => advisory.cwe.includes(`CWE-${cwe}`))) { | ||
continue | ||
} | ||
} | ||
return true | ||
} | ||
}) | ||
if (vulnerable?.length) { | ||
item.queryContext = { | ||
advisories: vulnerable, | ||
} | ||
return true | ||
} | ||
return false | ||
}) | ||
} | ||
async outdatedPseudo () { | ||
@@ -455,2 +530,7 @@ const { outdatedKind = 'any' } = this.currentAstNode | ||
// private packages can't be published, skip them | ||
if (node.package.private) { | ||
return false | ||
} | ||
// we cache the promise representing the full versions list, this helps reduce the | ||
@@ -850,4 +930,2 @@ // number of requests we send by keeping population of the cache in a single tick | ||
// We are keeping this async in the event that we do add async operators, we | ||
// won't have to have a breaking change on this function signature. | ||
const querySelectorAll = async (targetNode, query, flatOptions) => { | ||
@@ -854,0 +932,0 @@ // This never changes ever we just pass it around. But we can't scope it to |
@@ -1,11 +0,10 @@ | ||
const _progress = Symbol('_progress') | ||
const _onError = Symbol('_onError') | ||
const _setProgress = Symbol('_setProgess') | ||
const npmlog = require('npmlog') | ||
module.exports = cls => class Tracker extends cls { | ||
#progress = new Map() | ||
#setProgress | ||
constructor (options = {}) { | ||
super(options) | ||
this[_setProgress] = !!options.progress | ||
this[_progress] = new Map() | ||
this.#setProgress = !!options.progress | ||
} | ||
@@ -15,3 +14,3 @@ | ||
if (section === null || section === undefined) { | ||
this[_onError](`Tracker can't be null or undefined`) | ||
this.#onError(`Tracker can't be null or undefined`) | ||
} | ||
@@ -23,8 +22,8 @@ | ||
const hasTracker = this[_progress].has(section) | ||
const hasSubtracker = this[_progress].has(`${section}:${key}`) | ||
const hasTracker = this.#progress.has(section) | ||
const hasSubtracker = this.#progress.has(`${section}:${key}`) | ||
if (hasTracker && subsection === null) { | ||
// 0. existing tracker, no subsection | ||
this[_onError](`Tracker "${section}" already exists`) | ||
this.#onError(`Tracker "${section}" already exists`) | ||
} else if (!hasTracker && subsection === null) { | ||
@@ -34,15 +33,15 @@ // 1. no existing tracker, no subsection | ||
// starts progress bar | ||
if (this[_setProgress] && this[_progress].size === 0) { | ||
if (this.#setProgress && this.#progress.size === 0) { | ||
npmlog.enableProgress() | ||
} | ||
this[_progress].set(section, npmlog.newGroup(section)) | ||
this.#progress.set(section, npmlog.newGroup(section)) | ||
} else if (!hasTracker && subsection !== null) { | ||
// 2. no parent tracker and subsection | ||
this[_onError](`Parent tracker "${section}" does not exist`) | ||
this.#onError(`Parent tracker "${section}" does not exist`) | ||
} else if (!hasTracker || !hasSubtracker) { | ||
// 3. existing parent tracker, no subsection tracker | ||
// Create a new subtracker in this[_progress] from parent tracker | ||
this[_progress].set(`${section}:${key}`, | ||
this[_progress].get(section).newGroup(`${section}:${subsection}`) | ||
// Create a new subtracker in this.#progress from parent tracker | ||
this.#progress.set(`${section}:${key}`, | ||
this.#progress.get(section).newGroup(`${section}:${subsection}`) | ||
) | ||
@@ -56,3 +55,3 @@ } | ||
if (section === null || section === undefined) { | ||
this[_onError](`Tracker can't be null or undefined`) | ||
this.#onError(`Tracker can't be null or undefined`) | ||
} | ||
@@ -64,11 +63,11 @@ | ||
const hasTracker = this[_progress].has(section) | ||
const hasSubtracker = this[_progress].has(`${section}:${key}`) | ||
const hasTracker = this.#progress.has(section) | ||
const hasSubtracker = this.#progress.has(`${section}:${key}`) | ||
// 0. parent tracker exists, no subsection | ||
// Finish parent tracker and remove from this[_progress] | ||
// Finish parent tracker and remove from this.#progress | ||
if (hasTracker && subsection === null) { | ||
// check if parent tracker does | ||
// not have any remaining children | ||
const keys = this[_progress].keys() | ||
const keys = this.#progress.keys() | ||
for (const key of keys) { | ||
@@ -81,8 +80,8 @@ if (key.match(new RegExp(section + ':'))) { | ||
// remove parent tracker | ||
this[_progress].get(section).finish() | ||
this[_progress].delete(section) | ||
this.#progress.get(section).finish() | ||
this.#progress.delete(section) | ||
// remove progress bar if all | ||
// trackers are finished | ||
if (this[_setProgress] && this[_progress].size === 0) { | ||
if (this.#setProgress && this.#progress.size === 0) { | ||
npmlog.disableProgress() | ||
@@ -92,8 +91,8 @@ } | ||
// 1. no existing parent tracker, no subsection | ||
this[_onError](`Tracker "${section}" does not exist`) | ||
this.#onError(`Tracker "${section}" does not exist`) | ||
} else if (!hasTracker || hasSubtracker) { | ||
// 2. subtracker exists | ||
// Finish subtracker and remove from this[_progress] | ||
this[_progress].get(`${section}:${key}`).finish() | ||
this[_progress].delete(`${section}:${key}`) | ||
// Finish subtracker and remove from this.#progress | ||
this.#progress.get(`${section}:${key}`).finish() | ||
this.#progress.delete(`${section}:${key}`) | ||
} | ||
@@ -103,4 +102,4 @@ // 3. existing parent tracker, no subsection | ||
[_onError] (msg) { | ||
if (this[_setProgress]) { | ||
#onError (msg) { | ||
if (this.#setProgress) { | ||
npmlog.disableProgress() | ||
@@ -107,0 +106,0 @@ } |
@@ -1,5 +0,4 @@ | ||
/* eslint node/no-deprecated-api: "off" */ | ||
const semver = require('semver') | ||
const { basename } = require('path') | ||
const { parse } = require('url') | ||
const { URL } = require('url') | ||
module.exports = (name, tgz) => { | ||
@@ -11,4 +10,4 @@ const base = basename(tgz) | ||
const u = parse(tgz) | ||
if (/^https?:/.test(u.protocol)) { | ||
if (tgz.startsWith('http:/') || tgz.startsWith('https:/')) { | ||
const u = new URL(tgz) | ||
// registry url? check for most likely pattern. | ||
@@ -20,3 +19,3 @@ // either /@foo/bar/-/bar-1.2.3.tgz or | ||
// is a potential option. | ||
const tfsplit = u.path.slice(1).split('/-/') | ||
const tfsplit = u.pathname.slice(1).split('/-/') | ||
if (tfsplit.length > 1) { | ||
@@ -23,0 +22,0 @@ const afterTF = tfsplit.pop() |
@@ -19,20 +19,19 @@ // An object representing a vulnerability either as the result of an | ||
const npa = require('npm-package-arg') | ||
const _range = Symbol('_range') | ||
const _simpleRange = Symbol('_simpleRange') | ||
const _fixAvailable = Symbol('_fixAvailable') | ||
const severities = new Map([ | ||
['info', 0], | ||
['low', 1], | ||
['moderate', 2], | ||
['high', 3], | ||
['critical', 4], | ||
[null, -1], | ||
['info', 0], [0, 'info'], | ||
['low', 1], [1, 'low'], | ||
['moderate', 2], [2, 'moderate'], | ||
['high', 3], [3, 'high'], | ||
['critical', 4], [4, 'critical'], | ||
[null, -1], [-1, null], | ||
]) | ||
for (const [name, val] of severities.entries()) { | ||
severities.set(val, name) | ||
} | ||
class Vuln { | ||
#range = null | ||
#simpleRange = null | ||
// assume a fix is available unless it hits a top node | ||
// that locks it in place, setting this false or {isSemVerMajor, version}. | ||
#fixAvailable = true | ||
class Vuln { | ||
constructor ({ name, advisory }) { | ||
@@ -45,8 +44,3 @@ this.name = name | ||
this.topNodes = new Set() | ||
this[_range] = null | ||
this[_simpleRange] = null | ||
this.nodes = new Set() | ||
// assume a fix is available unless it hits a top node | ||
// that locks it in place, setting this false or {isSemVerMajor, version}. | ||
this[_fixAvailable] = true | ||
this.addAdvisory(advisory) | ||
@@ -58,7 +52,7 @@ this.packument = advisory.packument | ||
get fixAvailable () { | ||
return this[_fixAvailable] | ||
return this.#fixAvailable | ||
} | ||
set fixAvailable (f) { | ||
this[_fixAvailable] = f | ||
this.#fixAvailable = f | ||
// if there's a fix available for this at the top level, it means that | ||
@@ -137,3 +131,3 @@ // it will also fix the vulns that led to it being there. to get there, | ||
nodes: [...this.nodes].map(n => n.location).sort(localeCompare), | ||
fixAvailable: this[_fixAvailable], | ||
fixAvailable: this.#fixAvailable, | ||
} | ||
@@ -158,4 +152,4 @@ } | ||
this.severity = null | ||
this[_range] = null | ||
this[_simpleRange] = null | ||
this.#range = null | ||
this.#simpleRange = null | ||
// refresh severity | ||
@@ -178,4 +172,4 @@ for (const advisory of this.advisories) { | ||
const sev = severities.get(advisory.severity) | ||
this[_range] = null | ||
this[_simpleRange] = null | ||
this.#range = null | ||
this.#simpleRange = null | ||
if (sev > severities.get(this.severity)) { | ||
@@ -187,9 +181,11 @@ this.severity = advisory.severity | ||
get range () { | ||
return this[_range] || | ||
(this[_range] = [...this.advisories].map(v => v.range).join(' || ')) | ||
if (!this.#range) { | ||
this.#range = [...this.advisories].map(v => v.range).join(' || ') | ||
} | ||
return this.#range | ||
} | ||
get simpleRange () { | ||
if (this[_simpleRange] && this[_simpleRange] === this[_range]) { | ||
return this[_simpleRange] | ||
if (this.#simpleRange && this.#simpleRange === this.#range) { | ||
return this.#simpleRange | ||
} | ||
@@ -199,4 +195,5 @@ | ||
const range = this.range | ||
const simple = simplifyRange(versions, range, semverOpt) | ||
return this[_simpleRange] = this[_range] = simple | ||
this.#simpleRange = simplifyRange(versions, range, semverOpt) | ||
this.#range = this.#simpleRange | ||
return this.#simpleRange | ||
} | ||
@@ -203,0 +200,0 @@ |
{ | ||
"name": "@npmcli/arborist", | ||
"version": "7.3.1", | ||
"version": "7.4.0", | ||
"description": "Manage node_modules trees", | ||
@@ -14,3 +14,3 @@ "dependencies": { | ||
"@npmcli/package-json": "^5.0.0", | ||
"@npmcli/query": "^3.0.1", | ||
"@npmcli/query": "^3.1.0", | ||
"@npmcli/run-script": "^7.0.2", | ||
@@ -17,0 +17,0 @@ "bin-links": "^4.0.1", |
12275
459640
61
3
Updated@npmcli/query@^3.1.0