Socket
Socket
Sign inDemoInstall

@npmcli/arborist

Package Overview
Dependencies
Maintainers
5
Versions
192
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@npmcli/arborist - npm Package Compare versions

Comparing version 7.2.1 to 7.2.2

2

lib/arborist/load-actual.js

@@ -336,3 +336,3 @@ // mix-in implementing the loadActual method

const did = this.#actualTreeLoaded
if (!did.has(node.target.realpath)) {
if (!node.isLink && !did.has(node.target.realpath)) {
did.add(node.target.realpath)

@@ -339,0 +339,0 @@ await this.#loadFSChildren(node.target)

@@ -51,3 +51,3 @@ // a module that manages a shrinkwrap file (npm-shrinkwrap.json or

const npa = require('npm-package-arg')
const rpj = require('read-package-json-fast')
const pkgJson = require('@npmcli/package-json')
const parseJSON = require('parse-conflict-json')

@@ -85,24 +85,2 @@

const maybeReadFile = file => {
return readFile(file, 'utf8').then(d => d, er => {
/* istanbul ignore else - can't test without breaking module itself */
if (er.code === 'ENOENT') {
return ''
} else {
throw er
}
})
}
const maybeStatFile = file => {
return stat(file).then(st => st.isFile(), er => {
/* istanbul ignore else - can't test without breaking module itself */
if (er.code === 'ENOENT') {
return null
} else {
throw er
}
})
}
const pkgMetaKeys = [

@@ -139,42 +117,45 @@ // note: name is included if necessary, for alias packages

const val = pkg[key]
// get the license type, not an object
return (key === 'license' && val && typeof val === 'object' && val.type)
? val.type
if (val) {
// get only the license type, not the full object
if (key === 'license' && typeof val === 'object' && val.type) {
return val.type
}
// skip empty objects and falsey values
: (val && !(typeof val === 'object' && !Object.keys(val).length)) ? val
: null
if (typeof val !== 'object' || Object.keys(val).length) {
return val
}
}
return null
}
// check to make sure that there are no packages newer than the hidden lockfile
const assertNoNewer = async (path, data, lockTime, dir = path, seen = null) => {
// check to make sure that there are no packages newer than or missing from the hidden lockfile
const assertNoNewer = async (path, data, lockTime, dir, seen) => {
const base = basename(dir)
const isNM = dir !== path && base === 'node_modules'
const isScope = dir !== path && !isNM && base.charAt(0) === '@'
const isParent = dir === path || isNM || isScope
const isScope = dir !== path && base.startsWith('@')
const isParent = (dir === path) || isNM || isScope
const parent = isParent ? dir : resolve(dir, 'node_modules')
const rel = relpath(path, dir)
if (dir !== path) {
const dirTime = (await stat(dir)).mtime
seen.add(rel)
let entries
if (dir === path) {
entries = [{ name: 'node_modules', isDirectory: () => true }]
} else {
const { mtime: dirTime } = await stat(dir)
if (dirTime > lockTime) {
throw 'out of date, updated: ' + rel
throw new Error(`out of date, updated: ${rel}`)
}
if (!isScope && !isNM && !data.packages[rel]) {
throw 'missing from lockfile: ' + rel
throw new Error(`missing from lockfile: ${rel}`)
}
seen.add(rel)
} else {
seen = new Set([rel])
entries = await readdir(parent, { withFileTypes: true }).catch(() => [])
}
const parent = isParent ? dir : resolve(dir, 'node_modules')
const children = dir === path
? Promise.resolve([{ name: 'node_modules', isDirectory: () => true }])
: readdir(parent, { withFileTypes: true })
const ents = await children.catch(() => [])
await Promise.all(ents.map(async ent => {
const child = resolve(parent, ent.name)
if (ent.isDirectory() && !/^\./.test(ent.name)) {
// TODO limit concurrency here, this is recursive
await Promise.all(entries.map(async dirent => {
const child = resolve(parent, dirent.name)
if (dirent.isDirectory() && !dirent.name.startsWith('.')) {
await assertNoNewer(path, data, lockTime, child, seen)
} else if (ent.isSymbolicLink()) {
} else if (dirent.isSymbolicLink()) {
const target = resolve(parent, await readlink(child))

@@ -185,3 +166,3 @@ const tstat = await stat(target).catch(

/* istanbul ignore next - windows cannot do this */
if (tstat && tstat.isDirectory() && !seen.has(relpath(path, target))) {
if (tstat?.isDirectory() && !seen.has(relpath(path, target))) {
await assertNoNewer(path, data, lockTime, target, seen)

@@ -191,2 +172,3 @@ }

}))
if (dir !== path) {

@@ -197,5 +179,5 @@ return

// assert that all the entries in the lockfile were seen
for (const loc of new Set(Object.keys(data.packages))) {
for (const loc in data.packages) {
if (!seen.has(loc)) {
throw 'missing from node_modules: ' + loc
throw new Error(`missing from node_modules: ${loc}`)
}

@@ -205,15 +187,2 @@ }

const _awaitingUpdate = Symbol('_awaitingUpdate')
const _updateWaitingNode = Symbol('_updateWaitingNode')
const _lockFromLoc = Symbol('_lockFromLoc')
const _pathToLoc = Symbol('_pathToLoc')
const _loadAll = Symbol('_loadAll')
const _metaFromLock = Symbol('_metaFromLock')
const _resolveMetaNode = Symbol('_resolveMetaNode')
const _fixDependencies = Symbol('_fixDependencies')
const _buildLegacyLockfile = Symbol('_buildLegacyLockfile')
const _filenameSet = Symbol('_filenameSet')
const _maybeRead = Symbol('_maybeRead')
const _maybeStat = Symbol('_maybeStat')
class Shrinkwrap {

@@ -238,9 +207,14 @@ static get defaultLockfileVersion () {

const [sw, lock] = await s[_maybeStat]()
const [sw, lock] = await s.resetFiles
s.filename = resolve(s.path,
(s.hiddenLockfile ? 'node_modules/.package-lock'
: s.shrinkwrapOnly || sw ? 'npm-shrinkwrap'
: 'package-lock') + '.json')
// XXX this is duplicated in this.load(), but using loadFiles instead of resetFiles
if (s.hiddenLockfile) {
s.filename = resolve(s.path, 'node_modules/.package-lock.json')
} else if (s.shrinkwrapOnly || sw) {
s.filename = resolve(s.path, 'npm-shrinkwrap.json')
} else {
s.filename = resolve(s.path, 'package-lock.json')
}
s.loadedFromDisk = !!(sw || lock)
// TODO what uses this?
s.type = basename(s.filename)

@@ -260,3 +234,3 @@

const meta = {}
pkgMetaKeys.forEach(key => {
for (const key of pkgMetaKeys) {
const val = metaFieldFromPkg(node.package, key)

@@ -266,3 +240,3 @@ if (val) {

}
})
}
// we only include name if different from the node path name, and for the

@@ -280,7 +254,7 @@ // root to help prevent churn based on the name of the directory the

nodeMetaKeys.forEach(key => {
for (const key of nodeMetaKeys) {
if (node[key]) {
meta[key] = node[key]
}
})
}

@@ -316,2 +290,4 @@ const resolved = consistentResolve(node.resolved, node.path, path, true)

#awaitingUpdate = new Map()
constructor (options = {}) {

@@ -328,7 +304,10 @@ const {

this.lockfileVersion = hiddenLockfile ? 3
: lockfileVersion ? parseInt(lockfileVersion, 10)
: null
if (hiddenLockfile) {
this.lockfileVersion = 3
} else if (lockfileVersion) {
this.lockfileVersion = parseInt(lockfileVersion, 10)
} else {
this.lockfileVersion = null
}
this[_awaitingUpdate] = new Map()
this.tree = null

@@ -370,5 +349,8 @@ this.path = resolve(path || '.')

const tgz = isReg && versionFromTgz(spec.name, resolved) || {}
const yspec = tgz.name === spec.name && tgz.version === version ? version
: isReg && tgz.name && tgz.version ? `npm:${tgz.name}@${tgz.version}`
: resolved
let yspec = resolved
if (tgz.name === spec.name && tgz.version === version) {
yspec = version
} else if (isReg && tgz.name && tgz.version) {
yspec = `npm:${tgz.name}@${tgz.version}`
}
if (yspec) {

@@ -387,3 +369,3 @@ options.resolved = resolved.replace(yarnRegRe, 'https://registry.npmjs.org/')

this.tree = null
this[_awaitingUpdate] = new Map()
this.#awaitingUpdate = new Map()
const lockfileVersion = this.lockfileVersion || defaultLockfileVersion

@@ -400,30 +382,47 @@ this.originalLockfileVersion = lockfileVersion

[_filenameSet] () {
return this.shrinkwrapOnly ? [
this.path + '/npm-shrinkwrap.json',
] : this.hiddenLockfile ? [
null,
this.path + '/node_modules/.package-lock.json',
] : [
this.path + '/npm-shrinkwrap.json',
this.path + '/package-lock.json',
this.path + '/yarn.lock',
// files to potentially read from and write to, in order of priority
get #filenameSet () {
if (this.shrinkwrapOnly) {
return [`${this.path}/npm-shrinkwrap.json`]
}
if (this.hiddenLockfile) {
return [`${this.path}/node_modules/.package-lock.json`]
}
return [
`${this.path}/npm-shrinkwrap.json`,
`${this.path}/package-lock.json`,
`${this.path}/yarn.lock`,
]
}
[_maybeRead] () {
return Promise.all(this[_filenameSet]().map(fn => fn && maybeReadFile(fn)))
get loadFiles () {
return Promise.all(
this.#filenameSet.map(file => file && readFile(file, 'utf8').then(d => d, er => {
/* istanbul ignore else - can't test without breaking module itself */
if (er.code === 'ENOENT') {
return ''
} else {
throw er
}
}))
)
}
[_maybeStat] () {
// throw away yarn, we only care about lock or shrinkwrap when checking
get resetFiles () {
// slice out yarn, we only care about lock or shrinkwrap when checking
// this way, since we're not actually loading the full lock metadata
return Promise.all(this[_filenameSet]().slice(0, 2)
.map(fn => fn && maybeStatFile(fn)))
return Promise.all(this.#filenameSet.slice(0, 2)
.map(file => file && stat(file).then(st => st.isFile(), er => {
/* istanbul ignore else - can't test without breaking module itself */
if (er.code === 'ENOENT') {
return null
} else {
throw er
}
})
)
)
}
inferFormattingOptions (packageJSONData) {
// don't use detect-indent, just pick the first line.
// if the file starts with {" then we have an indent of '', ie, none
// which will default to 2 at save time.
const {

@@ -433,4 +432,8 @@ [Symbol.for('indent')]: indent,

} = packageJSONData
this.indent = indent !== undefined ? indent : this.indent
this.newline = newline !== undefined ? newline : this.newline
if (indent !== undefined) {
this.indent = indent
}
if (newline !== undefined) {
this.newline = newline
}
}

@@ -441,4 +444,6 @@

// only npm-shrinkwrap.json.
return this[_maybeRead]().then(([sw, lock, yarn]) => {
const data = sw || lock || ''
let data
try {
const [sw, lock, yarn] = await this.loadFiles
data = sw || lock || '{}'

@@ -448,9 +453,11 @@ // use shrinkwrap only for deps, otherwise prefer package-lock

// TODO: emit a warning here or something if both are present.
this.filename = resolve(this.path,
(this.hiddenLockfile ? 'node_modules/.package-lock'
: this.shrinkwrapOnly || sw ? 'npm-shrinkwrap'
: 'package-lock') + '.json')
if (this.hiddenLockfile) {
this.filename = resolve(this.path, 'node_modules/.package-lock.json')
} else if (this.shrinkwrapOnly || sw) {
this.filename = resolve(this.path, 'npm-shrinkwrap.json')
} else {
this.filename = resolve(this.path, 'package-lock.json')
}
this.type = basename(this.filename)
this.loadedFromDisk = !!data
this.loadedFromDisk = Boolean(sw || lock)

@@ -467,23 +474,19 @@ if (yarn) {

return data ? parseJSON(data) : {}
}).then(async data => {
data = parseJSON(data)
this.inferFormattingOptions(data)
if (!this.hiddenLockfile || !data.packages) {
return data
if (this.hiddenLockfile && data.packages) {
// add a few ms just to account for jitter
const lockTime = +(await stat(this.filename)).mtime + 10
await assertNoNewer(this.path, data, lockTime, this.path, new Set())
}
// add a few ms just to account for jitter
const lockTime = +(await stat(this.filename)).mtime + 10
await assertNoNewer(this.path, data, lockTime)
// all good! hidden lockfile is the newest thing in here.
return data
}).catch(er => {
} catch (er) {
/* istanbul ignore else */
if (typeof this.filename === 'string') {
const rel = relpath(this.path, this.filename)
log.verbose('shrinkwrap', `failed to load ${rel}`, er)
log.verbose('shrinkwrap', `failed to load ${rel}`, er.message)
} else {
log.verbose('shrinkwrap', `failed to load ${this.path}`, er)
log.verbose('shrinkwrap', `failed to load ${this.path}`, er.message)
}

@@ -493,48 +496,51 @@ this.loadingError = er

this.ancientLockfile = false
return {}
}).then(lock => {
// auto convert v1 lockfiles to v3
// leave v2 in place unless configured
// v3 by default
const lockfileVersion =
this.lockfileVersion ? this.lockfileVersion
: lock.lockfileVersion === 1 ? defaultLockfileVersion
: lock.lockfileVersion || defaultLockfileVersion
data = {}
}
// auto convert v1 lockfiles to v3
// leave v2 in place unless configured
// v3 by default
let lockfileVersion = defaultLockfileVersion
if (this.lockfileVersion) {
lockfileVersion = this.lockfileVersion
} else if (data.lockfileVersion && data.lockfileVersion !== 1) {
lockfileVersion = data.lockfileVersion
}
this.data = {
...lock,
lockfileVersion: lockfileVersion,
requires: true,
packages: lock.packages || {},
dependencies: lock.dependencies || {},
}
this.data = {
...data,
lockfileVersion,
requires: true,
packages: data.packages || {},
dependencies: data.dependencies || {},
}
this.originalLockfileVersion = lock.lockfileVersion
this.originalLockfileVersion = data.lockfileVersion
// use default if it wasn't explicitly set, and the current file is
// less than our default. otherwise, keep whatever is in the file,
// unless we had an explicit setting already.
if (!this.lockfileVersion) {
this.lockfileVersion = this.data.lockfileVersion = lockfileVersion
}
this.ancientLockfile = this.loadedFromDisk &&
!(lock.lockfileVersion >= 2) && !lock.requires
// use default if it wasn't explicitly set, and the current file is
// less than our default. otherwise, keep whatever is in the file,
// unless we had an explicit setting already.
if (!this.lockfileVersion) {
this.lockfileVersion = this.data.lockfileVersion = lockfileVersion
}
this.ancientLockfile = this.loadedFromDisk &&
!(data.lockfileVersion >= 2) && !data.requires
// load old lockfile deps into the packages listing
// eslint-disable-next-line promise/always-return
if (lock.dependencies && !lock.packages) {
return rpj(this.path + '/package.json').then(pkg => pkg, er => ({}))
// eslint-disable-next-line promise/always-return
.then(pkg => {
this[_loadAll]('', null, this.data)
this[_fixDependencies](pkg)
})
// load old lockfile deps into the packages listing
if (data.dependencies && !data.packages) {
let pkg
try {
pkg = await pkgJson.normalize(this.path)
pkg = pkg.content
} catch {
pkg = {}
}
})
.then(() => this)
this.#loadAll('', null, this.data)
this.#fixDependencies(pkg)
}
return this
}
[_loadAll] (location, name, lock) {
#loadAll (location, name, lock) {
// migrate a v1 package lock to the new format.
const meta = this[_metaFromLock](location, name, lock)
const meta = this.#metaFromLock(location, name, lock)
// dependencies nested under a link are actually under the link target

@@ -545,5 +551,5 @@ if (meta.link) {

if (lock.dependencies) {
for (const [name, dep] of Object.entries(lock.dependencies)) {
for (const name in lock.dependencies) {
const loc = location + (location ? '/' : '') + 'node_modules/' + name
this[_loadAll](loc, name, dep)
this.#loadAll(loc, name, lock.dependencies[name])
}

@@ -556,3 +562,3 @@ }

// to correct that now, or every link will be considered prod
[_fixDependencies] (pkg) {
#fixDependencies (pkg) {
// we need the root package.json because legacy shrinkwraps just

@@ -562,11 +568,11 @@ // have requires:true at the root level, which is even less useful

const root = this.data.packages['']
pkgMetaKeys.forEach(key => {
for (const key of pkgMetaKeys) {
const val = metaFieldFromPkg(pkg, key)
const k = key.replace(/^_/, '')
if (val) {
root[k] = val
root[key.replace(/^_/, '')] = val
}
})
}
for (const [loc, meta] of Object.entries(this.data.packages)) {
for (const loc in this.data.packages) {
const meta = this.data.packages[loc]
if (!meta.requires || !loc) {

@@ -582,15 +588,20 @@ continue

// buildIdealTree process
for (const [name, spec] of Object.entries(meta.requires)) {
const dep = this[_resolveMetaNode](loc, name)
for (const name in meta.requires) {
const dep = this.#resolveMetaNode(loc, name)
// this overwrites the false value set above
const depType = dep && dep.optional && !meta.optional
? 'optionalDependencies'
: /* istanbul ignore next - dev deps are only for the root level */
dep && dep.dev && !meta.dev ? 'devDependencies'
// also land here if the dep just isn't in the tree, which maybe
// should be an error, since it means that the shrinkwrap is
// invalid, but we can't do much better without any info.
: 'dependencies'
meta[depType] = meta[depType] || {}
meta[depType][name] = spec
// default to dependencies if the dep just isn't in the tree, which
// maybe should be an error, since it means that the shrinkwrap is
// invalid, but we can't do much better without any info.
let depType = 'dependencies'
/* istanbul ignore else - dev deps are only for the root level */
if (dep?.optional && !meta.optional) {
depType = 'optionalDependencies'
} else if (dep?.dev && !meta.dev) {
// XXX is this even reachable?
depType = 'devDependencies'
}
if (!meta[depType]) {
meta[depType] = {}
}
meta[depType][name] = meta.requires[name]
}

@@ -601,3 +612,3 @@ delete meta.requires

[_resolveMetaNode] (loc, name) {
#resolveMetaNode (loc, name) {
for (let path = loc; true; path = path.replace(/(^|\/)[^/]*$/, '')) {

@@ -616,3 +627,3 @@ const check = `${path}${path ? '/' : ''}node_modules/${name}`

[_lockFromLoc] (lock, path, i = 0) {
#lockFromLoc (lock, path, i = 0) {
if (!lock) {

@@ -634,3 +645,3 @@ return null

return this[_lockFromLoc](lock.dependencies[path[i]], path, i + 1)
return this.#lockFromLoc(lock.dependencies[path[i]], path, i + 1)
}

@@ -640,3 +651,3 @@

// get back a /-normalized location based on root path.
[_pathToLoc] (path) {
#pathToLoc (path) {
return relpath(this.path, resolve(this.path, path))

@@ -649,4 +660,4 @@ }

}
const location = this[_pathToLoc](nodePath)
this[_awaitingUpdate].delete(location)
const location = this.#pathToLoc(nodePath)
this.#awaitingUpdate.delete(location)

@@ -656,3 +667,3 @@ delete this.data.packages[location]

const name = path.pop()
const pLock = this[_lockFromLoc](this.data, path)
const pLock = this.#lockFromLoc(this.data, path)
if (pLock && pLock.dependencies) {

@@ -668,5 +679,5 @@ delete pLock.dependencies[name]

const location = this[_pathToLoc](nodePath)
if (this[_awaitingUpdate].has(location)) {
this[_updateWaitingNode](location)
const location = this.#pathToLoc(nodePath)
if (this.#awaitingUpdate.has(location)) {
this.#updateWaitingNode(location)
}

@@ -684,8 +695,8 @@

const name = path[path.length - 1]
const lock = this[_lockFromLoc](this.data, path)
const lock = this.#lockFromLoc(this.data, path)
return this[_metaFromLock](location, name, lock)
return this.#metaFromLock(location, name, lock)
}
[_metaFromLock] (location, name, lock) {
#metaFromLock (location, name, lock) {
// This function tries as hard as it can to figure out the metadata

@@ -715,3 +726,3 @@ // from a lockfile which may be outdated or incomplete. Since v1

if (!this.data.packages[target]) {
this[_metaFromLock](target, name, { ...lock, version: null })
this.#metaFromLock(target, name, { ...lock, version: null })
}

@@ -836,6 +847,10 @@ return this.data.packages[location]

const pathFixed = !resolved ? null
: !/^file:/.test(resolved) ? resolved
// resolve onto the metadata path
: `file:${resolve(this.path, resolved.slice(5)).replace(/#/g, '%23')}`
let pathFixed = null
if (resolved) {
if (!/^file:/.test(resolved)) {
pathFixed = resolved
} else {
pathFixed = `file:${resolve(this.path, resolved.slice(5)).replace(/#/g, '%23')}`
}
}

@@ -869,3 +884,3 @@ // if we have one, only set the other if it matches

}
this[_awaitingUpdate].set(loc, node)
this.#awaitingUpdate.set(loc, node)
}

@@ -891,6 +906,11 @@

// we relativize the path here because that's how it shows up in the lock
// XXX how is this different from pathFixed above??
const pathFixed = !node.resolved ? null
: !/file:/.test(node.resolved) ? node.resolved
: consistentResolve(node.resolved, node.path, this.path, true)
// XXX why is this different from pathFixed in this.add??
let pathFixed = null
if (node.resolved) {
if (!/file:/.test(node.resolved)) {
pathFixed = node.resolved
} else {
pathFixed = consistentResolve(node.resolved, node.path, this.path, true)
}
}

@@ -915,8 +935,8 @@ const spec = npa(`${node.name}@${edge.spec}`)

this[_awaitingUpdate].set(relpath(this.path, node.path), node)
this.#awaitingUpdate.set(relpath(this.path, node.path), node)
}
[_updateWaitingNode] (loc) {
const node = this[_awaitingUpdate].get(loc)
this[_awaitingUpdate].delete(loc)
#updateWaitingNode (loc) {
const node = this.#awaitingUpdate.get(loc)
this.#awaitingUpdate.delete(loc)
this.data.packages[loc] = Shrinkwrap.metaFromNode(

@@ -952,5 +972,5 @@ node,

}
} else if (this[_awaitingUpdate].size > 0) {
for (const loc of this[_awaitingUpdate].keys()) {
this[_updateWaitingNode](loc)
} else if (this.#awaitingUpdate.size > 0) {
for (const loc of this.#awaitingUpdate.keys()) {
this.#updateWaitingNode(loc)
}

@@ -970,3 +990,3 @@ }

} else if (this.tree && this.lockfileVersion <= 3) {
this[_buildLegacyLockfile](this.tree, this.data)
this.#buildLegacyLockfile(this.tree, this.data)
}

@@ -988,3 +1008,3 @@

[_buildLegacyLockfile] (node, lock, path = []) {
#buildLegacyLockfile (node, lock, path = []) {
if (node === this.tree) {

@@ -1010,5 +1030,9 @@ // the root node

/* istanbul ignore next - sort calling order is indeterminate */
return aloc.length > bloc.length ? 1
: bloc.length > aloc.length ? -1
: localeCompare(aloc[aloc.length - 1], bloc[bloc.length - 1])
if (aloc.length > bloc.length) {
return 1
}
if (bloc.length > aloc.length) {
return -1
}
return localeCompare(aloc[aloc.length - 1], bloc[bloc.length - 1])
})[0]

@@ -1024,4 +1048,6 @@

// a standard version/range dep, which is a reasonable default.
const spec = !edge ? rSpec
: npa.resolve(node.name, edge.spec, edge.from.realpath)
let spec = rSpec
if (edge) {
spec = npa.resolve(node.name, edge.spec, edge.from.realpath)
}

@@ -1132,3 +1158,3 @@ if (node.isLink) {

}
dependencies[name] = this[_buildLegacyLockfile](kid, {}, kidPath)
dependencies[name] = this.#buildLegacyLockfile(kid, {}, kidPath)
found = true

@@ -1135,0 +1161,0 @@ }

@@ -93,3 +93,3 @@ const debug = require('./debug.js')

if (node.path === tree.root.path && node !== tree.root) {
if (node.path === tree.root.path && node !== tree.root && !tree.root.isLink) {
throw Object.assign(new Error('node with same path as root'), {

@@ -96,0 +96,0 @@ node: node.path,

@@ -344,6 +344,6 @@ // parse a yarn lock file

const _specs = Symbol('_specs')
class YarnLockEntry {
#specs
constructor (specs) {
this[_specs] = new Set(specs)
this.#specs = new Set(specs)
this.resolved = null

@@ -358,3 +358,3 @@ this.version = null

// sort objects to the bottom, then alphabetical
return ([...this[_specs]]
return ([...this.#specs]
.sort(localeCompare)

@@ -375,3 +375,3 @@ .map(quoteIfNeeded).join(', ') +

addSpec (spec) {
this[_specs].add(spec)
this.#specs.add(spec)
}

@@ -378,0 +378,0 @@ }

{
"name": "@npmcli/arborist",
"version": "7.2.1",
"version": "7.2.2",
"description": "Manage node_modules trees",

@@ -42,3 +42,3 @@ "dependencies": {

"@npmcli/eslint-config": "^4.0.0",
"@npmcli/template-oss": "4.19.0",
"@npmcli/template-oss": "4.21.3",
"benchmark": "^2.1.4",

@@ -53,7 +53,7 @@ "minify-registry-metadata": "^3.0.0",

"test": "tap",
"posttest": "node ../.. run lint",
"posttest": "npm run lint",
"snap": "tap",
"test-proxy": "ARBORIST_TEST_PROXY=1 tap --snapshot",
"lint": "eslint \"**/*.js\"",
"lintfix": "node ../.. run lint -- --fix",
"lint": "eslint \"**/*.{js,cjs,ts,mjs,jsx,tsx}\"",
"lintfix": "npm run lint -- --fix",
"benchmark": "node scripts/benchmark.js",

@@ -95,5 +95,5 @@ "benchclean": "rm -rf scripts/benchmark/*/",

"//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
"version": "4.19.0",
"version": "4.21.3",
"content": "../../scripts/template-oss/index.js"
}
}
SocketSocket SOC 2 Logo

Product

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

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc