Socket
Socket
Sign inDemoInstall

@npmcli/arborist

Package Overview
Dependencies
148
Maintainers
5
Versions
189
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 7.3.1 to 7.4.0

102

lib/arborist/build-ideal-tree.js

@@ -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",

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc