Socket
Socket
Sign inDemoInstall

@npmcli/arborist

Package Overview
Dependencies
140
Maintainers
5
Versions
191
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 7.5.0 to 7.5.1

6

lib/arborist/build-ideal-tree.js

@@ -466,3 +466,3 @@ // mixin implementing the buildIdealTree method

const st = await lstat(dir)
.catch(/* istanbul ignore next */ er => null)
.catch(/* istanbul ignore next */ () => null)
if (st && st.isSymbolicLink()) {

@@ -1028,3 +1028,3 @@ const target = await readlink(dir)

this.#fetchManifest(npa.resolve(e.name, e.spec, fromPath(placed, e)))
.catch(er => null)
.catch(() => null)
)

@@ -1278,3 +1278,3 @@ }

#linkFromSpec (name, spec, parent, edge) {
#linkFromSpec (name, spec, parent) {
const realpath = spec.fetchSpec

@@ -1281,0 +1281,0 @@ const { installLinks, legacyPeerDeps } = this

@@ -77,2 +77,4 @@ // The arborist manages three trees:

cache: options.cache || `${homedir()}/.npm/_cacache`,
dryRun: !!options.dryRun,
formatPackageLock: 'formatPackageLock' in options ? !!options.formatPackageLock : true,
force: !!options.force,

@@ -83,2 +85,3 @@ global: !!options.global,

lockfileVersion: lockfileVersion(options.lockfileVersion),
packageLockOnly: !!options.packageLockOnly,
packumentCache: options.packumentCache || new Map(),

@@ -88,2 +91,3 @@ path: options.path || '.',

replaceRegistryHost: options.replaceRegistryHost,
savePrefix: 'savePrefix' in options ? options.savePrefix : '^',
scriptShell: options.scriptShell,

@@ -93,3 +97,5 @@ workspaces: options.workspaces || [],

}
// TODO is this even used? If not is that a bug?
// TODO we only ever look at this.options.replaceRegistryHost, not
// this.replaceRegistryHost. Defaulting needs to be written back to
// this.options to work properly
this.replaceRegistryHost = this.options.replaceRegistryHost =

@@ -103,2 +109,3 @@ (!this.options.replaceRegistryHost || this.options.replaceRegistryHost === 'npmjs') ?

this.cache = resolve(this.options.cache)
this.diff = null
this.path = resolve(this.options.path)

@@ -258,4 +265,22 @@ timeEnd()

}
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 },
})
}
}
module.exports = Arborist

@@ -215,3 +215,3 @@ const _makeIdealGraph = Symbol('makeIdealGraph')

async [_createIsolatedTree] (idealTree) {
async [_createIsolatedTree] () {
await this[_makeIdealGraph](this.options)

@@ -218,0 +218,0 @@

@@ -339,4 +339,4 @@ // mix-in implementing the loadActual method

[...node.target.children.entries()]
.filter(([name, kid]) => !did.has(kid.realpath))
.map(([name, kid]) => this.#loadFSTree(kid))
.filter(([, kid]) => !did.has(kid.realpath))
.map(([, kid]) => this.#loadFSTree(kid))
)

@@ -343,0 +343,0 @@ }

@@ -286,3 +286,3 @@ // mixin providing the loadVirtual method

#loadLink (location, targetLoc, target, meta) {
#loadLink (location, targetLoc, target) {
const path = resolve(this.path, location)

@@ -289,0 +289,0 @@ const link = new Link({

@@ -41,11 +41,9 @@ // mixin implementing the reify method

const _retiredPaths = Symbol('retiredPaths')
const _retiredUnchanged = Symbol('retiredUnchanged')
const _sparseTreeDirs = Symbol('sparseTreeDirs')
const _sparseTreeRoots = Symbol('sparseTreeRoots')
const _savePrefix = Symbol('savePrefix')
// Part of steps (steps need refactoring before we can do anything about these)
const _retireShallowNodes = Symbol.for('retireShallowNodes')
const _getBundlesByDepth = Symbol('getBundlesByDepth')
const _registryResolved = Symbol('registryResolved')
const _addNodeToTrashList = Symbol.for('addNodeToTrashList')
const _loadBundlesAndUpdateTrees = Symbol.for('loadBundlesAndUpdateTrees')
const _submitQuickAudit = Symbol('submitQuickAudit')
const _addOmitsToTrashList = Symbol('addOmitsToTrashList')
const _unpackNewModules = Symbol.for('unpackNewModules')
const _build = Symbol.for('build')

@@ -56,24 +54,15 @@ // shared by rebuild mixin

const _loadTrees = Symbol.for('loadTrees')
// defined by rebuild mixin
const _checkBins = Symbol.for('checkBins')
// shared symbols for swapping out when testing
// TODO tests should not be this deep into internals
const _diffTrees = Symbol.for('diffTrees')
const _createSparseTree = Symbol.for('createSparseTree')
const _loadShrinkwrapsAndUpdateTrees = Symbol.for('loadShrinkwrapsAndUpdateTrees')
const _shrinkwrapInflated = Symbol('shrinkwrapInflated')
const _bundleUnpacked = Symbol('bundleUnpacked')
const _bundleMissing = Symbol('bundleMissing')
const _reifyNode = Symbol.for('reifyNode')
const _extractOrLink = Symbol('extractOrLink')
const _updateAll = Symbol.for('updateAll')
const _updateNames = Symbol.for('updateNames')
// defined by rebuild mixin
const _checkBins = Symbol.for('checkBins')
const _symlink = Symbol('symlink')
const _warnDeprecated = Symbol('warnDeprecated')
const _loadBundlesAndUpdateTrees = Symbol.for('loadBundlesAndUpdateTrees')
const _submitQuickAudit = Symbol('submitQuickAudit')
const _unpackNewModules = Symbol.for('unpackNewModules')
const _moveContents = Symbol.for('moveContents')
const _moveBackRetiredUnchanged = Symbol.for('moveBackRetiredUnchanged')
const _build = Symbol.for('build')
const _removeTrash = Symbol.for('removeTrash')

@@ -85,21 +74,9 @@ const _renamePath = Symbol.for('renamePath')

const _saveIdealTree = Symbol.for('saveIdealTree')
const _copyIdealToActual = Symbol('copyIdealToActual')
const _addOmitsToTrashList = Symbol('addOmitsToTrashList')
const _packageLockOnly = Symbol('packageLockOnly')
const _dryRun = Symbol('dryRun')
const _validateNodeModules = Symbol('validateNodeModules')
const _nmValidated = Symbol('nmValidated')
const _validatePath = Symbol('validatePath')
const _reifyPackages = Symbol.for('reifyPackages')
const _omitDev = Symbol('omitDev')
const _omitOptional = Symbol('omitOptional')
const _omitPeer = Symbol('omitPeer')
const _pruneBundledMetadeps = Symbol('pruneBundledMetadeps')
// defined by Ideal mixin
// defined by build-ideal-tree mixin
const _resolvedAdd = Symbol.for('resolvedAdd')
const _usePackageLock = Symbol.for('usePackageLock')
const _formatPackageLock = Symbol.for('formatPackageLock')
// used by build-ideal-tree mixin
const _addNodeToTrashList = Symbol.for('addNodeToTrashList')

@@ -109,29 +86,20 @@ const _createIsolatedTree = Symbol.for('createIsolatedTree')

module.exports = cls => class Reifier extends cls {
#bundleMissing = new Set() // child nodes we'd EXPECT to be included in a bundle, but aren't
#bundleUnpacked = new Set() // the nodes we unpack to read their bundles
#dryRun
#nmValidated = new Set()
#omitDev
#omitPeer
#omitOptional
#retiredPaths = {}
#retiredUnchanged = {}
#savePrefix
#shrinkwrapInflated = new Set()
#sparseTreeDirs = new Set()
#sparseTreeRoots = new Set()
constructor (options) {
super(options)
const {
savePrefix = '^',
packageLockOnly = false,
dryRun = false,
formatPackageLock = true,
} = options
this[_dryRun] = !!dryRun
this[_packageLockOnly] = !!packageLockOnly
this[_savePrefix] = savePrefix
this[_formatPackageLock] = !!formatPackageLock
this.diff = null
this[_retiredPaths] = {}
this[_shrinkwrapInflated] = new Set()
this[_retiredUnchanged] = {}
this[_sparseTreeDirs] = new Set()
this[_sparseTreeRoots] = new Set()
this[_trashList] = new Set()
// the nodes we unpack to read their bundles
this[_bundleUnpacked] = new Set()
// child nodes we'd EXPECT to be included in a bundle, but aren't
this[_bundleMissing] = new Set()
this[_nmValidated] = new Set()
}

@@ -143,3 +111,3 @@

if (this[_packageLockOnly] && this.options.global) {
if (this.options.packageLockOnly && this.options.global) {
const er = new Error('cannot generate lockfile for global packages')

@@ -151,5 +119,5 @@ er.code = 'ESHRINKWRAPGLOBAL'

const omit = new Set(options.omit || [])
this[_omitDev] = omit.has('dev')
this[_omitOptional] = omit.has('optional')
this[_omitPeer] = omit.has('peer')
this.#omitDev = omit.has('dev')
this.#omitOptional = omit.has('optional')
this.#omitPeer = omit.has('peer')

@@ -159,3 +127,12 @@ // start tracker block

const timeEnd = time.start('reify')
await this[_validatePath]()
// don't create missing dirs on dry runs
if (!this.options.packageLockOnly && !this.options.dryRun) {
// we do NOT want to set ownership on this folder, especially
// recursively, because it can have other side effects to do that
// in a project directory. We just want to make it if it's missing.
await mkdir(resolve(this.path), { recursive: true })
// do not allow the top-level node_modules to be a symlink
await this.#validateNodeModules(resolve(this.path, 'node_modules'))
}
await this[_loadTrees](options)

@@ -169,3 +146,3 @@

log.warn('reify', 'The "linked" install strategy is EXPERIMENTAL and may contain bugs.')
this.idealTree = await this[_createIsolatedTree](this.idealTree)
this.idealTree = await this[_createIsolatedTree]()
}

@@ -180,3 +157,120 @@ await this[_diffTrees]()

await this[_saveIdealTree](options)
await this[_copyIdealToActual]()
// clean up any trash that is still in the tree
for (const path of this[_trashList]) {
const loc = relpath(this.idealTree.realpath, path)
const node = this.idealTree.inventory.get(loc)
if (node && node.root === this.idealTree) {
node.parent = null
}
}
// if we filtered to only certain nodes, then anything ELSE needs
// to be untouched in the resulting actual tree, even if it differs
// in the idealTree. Copy over anything that was in the actual and
// was not changed, delete anything in the ideal and not actual.
// Then we move the entire idealTree over to this.actualTree, and
// save the hidden lockfile.
if (this.diff && this.diff.filterSet.size) {
const reroot = new Set()
const { filterSet } = this.diff
const seen = new Set()
for (const [loc, ideal] of this.idealTree.inventory.entries()) {
seen.add(loc)
// if it's an ideal node from the filter set, then skip it
// because we already made whatever changes were necessary
if (filterSet.has(ideal)) {
continue
}
// otherwise, if it's not in the actualTree, then it's not a thing
// that we actually added. And if it IS in the actualTree, then
// it's something that we left untouched, so we need to record
// that.
const actual = this.actualTree.inventory.get(loc)
if (!actual) {
ideal.root = null
} else {
if ([...actual.linksIn].some(link => filterSet.has(link))) {
seen.add(actual.location)
continue
}
const { realpath, isLink } = actual
if (isLink && ideal.isLink && ideal.realpath === realpath) {
continue
} else {
reroot.add(actual)
}
}
}
// now find any actual nodes that may not be present in the ideal
// tree, but were left behind by virtue of not being in the filter
for (const [loc, actual] of this.actualTree.inventory.entries()) {
if (seen.has(loc)) {
continue
}
seen.add(loc)
// we know that this is something that ISN'T in the idealTree,
// or else we will have addressed it in the previous loop.
// If it's in the filterSet, that means we intentionally removed
// it, so nothing to do here.
if (filterSet.has(actual)) {
continue
}
reroot.add(actual)
}
// go through the rerooted actual nodes, and move them over.
for (const actual of reroot) {
actual.root = this.idealTree
}
// prune out any tops that lack a linkIn, they are no longer relevant.
for (const top of this.idealTree.tops) {
if (top.linksIn.size === 0) {
top.root = null
}
}
// need to calculate dep flags, since nodes may have been marked
// as extraneous or otherwise incorrect during transit.
calcDepFlags(this.idealTree)
}
// save the ideal's meta as a hidden lockfile after we actualize it
this.idealTree.meta.filename =
this.idealTree.realpath + '/node_modules/.package-lock.json'
this.idealTree.meta.hiddenLockfile = true
this.idealTree.meta.lockfileVersion = defaultLockfileVersion
this.actualTree = this.idealTree
this.idealTree = null
if (!this.options.global) {
await this.actualTree.meta.save()
const ignoreScripts = !!this.options.ignoreScripts
// if we aren't doing a dry run or ignoring scripts and we actually made changes to the dep
// tree, then run the dependencies scripts
if (!this.options.dryRun && !ignoreScripts && this.diff && this.diff.children.length) {
const { path, package: pkg } = this.actualTree.target
const stdio = this.options.foregroundScripts ? 'inherit' : 'pipe'
const { scripts = {} } = pkg
for (const event of ['predependencies', 'dependencies', 'postdependencies']) {
if (Object.prototype.hasOwnProperty.call(scripts, event)) {
log.info('run', pkg._id, event, scripts[event])
await time.start(`reify:run:${event}`, () => runScript({
event,
path,
pkg,
stdio,
scriptShell: this.options.scriptShell,
}))
}
}
}
}
// This is a very bad pattern and I can't wait to stop doing it

@@ -190,24 +284,9 @@ this.auditReport = await this.auditReport

async [_validatePath] () {
// don't create missing dirs on dry runs
if (this[_packageLockOnly] || this[_dryRun]) {
return
}
// we do NOT want to set ownership on this folder, especially
// recursively, because it can have other side effects to do that
// in a project directory. We just want to make it if it's missing.
await mkdir(resolve(this.path), { recursive: true })
// do not allow the top-level node_modules to be a symlink
await this[_validateNodeModules](resolve(this.path, 'node_modules'))
}
async [_reifyPackages] () {
// we don't submit the audit report or write to disk on dry runs
if (this[_dryRun]) {
if (this.options.dryRun) {
return
}
if (this[_packageLockOnly]) {
if (this.options.packageLockOnly) {
// we already have the complete tree, so just audit it now,

@@ -261,2 +340,3 @@ // and that's all we have to do here.

} catch (er) {
// TODO rollbacks shouldn't be relied on to throw err
await this[rollback](er)

@@ -286,7 +366,7 @@ /* istanbul ignore next - rollback throws, should never hit this */

...options,
complete: this[_packageLockOnly] || this[_dryRun],
complete: this.options.packageLockOnly || this.options.dryRun,
}
// if we're only writing a package lock, then it doesn't matter what's here
if (this[_packageLockOnly]) {
if (this.options.packageLockOnly) {
return this.buildIdealTree(bitOpt).then(timeEnd)

@@ -340,3 +420,3 @@ }

[_diffTrees] () {
if (this[_packageLockOnly]) {
if (this.options.packageLockOnly) {
return

@@ -399,3 +479,3 @@ }

this.diff = Diff.calculate({
shrinkwrapInflated: this[_shrinkwrapInflated],
shrinkwrapInflated: this.#shrinkwrapInflated,
filterNodes,

@@ -422,3 +502,3 @@ actual: this.actualTree,

const paths = [node.path, ...node.binPaths]
const moves = this[_retiredPaths]
const moves = this.#retiredPaths
log.silly('reify', 'mark', retire ? 'retired' : 'deleted', paths)

@@ -440,3 +520,3 @@ for (const path of paths) {

const timeEnd = time.start('reify:retireShallow')
const moves = this[_retiredPaths] = {}
const moves = this.#retiredPaths = {}
for (const diff of this.diff.children) {

@@ -474,3 +554,3 @@ if (diff.action === 'CHANGE' || diff.action === 'REMOVE') {

const timeEnd = time.start('reify:rollback:retireShallow')
const moves = this[_retiredPaths]
const moves = this.#retiredPaths
const movePromises = Object.entries(moves)

@@ -480,3 +560,3 @@ .map(([from, to]) => this[_renamePath](to, from))

// ignore subsequent rollback errors
.catch(er => {})
.catch(() => {})
.then(timeEnd)

@@ -491,3 +571,3 @@ .then(() => {

[_addOmitsToTrashList] () {
if (!this[_omitDev] && !this[_omitOptional] && !this[_omitPeer]) {
if (!this.#omitDev && !this.#omitOptional && !this.#omitPeer) {
return

@@ -514,6 +594,6 @@ }

if (
node.peer && this[_omitPeer] ||
node.dev && this[_omitDev] ||
node.optional && this[_omitOptional] ||
node.devOptional && this[_omitOptional] && this[_omitDev]
node.peer && this.#omitPeer ||
node.dev && this.#omitDev ||
node.optional && this.#omitOptional ||
node.devOptional && this.#omitOptional && this.#omitDev
) {

@@ -534,3 +614,3 @@ this[_addNodeToTrashList](node)

return (diff.action === 'ADD' || diff.action === 'CHANGE') &&
!this[_sparseTreeDirs].has(diff.ideal.path) &&
!this.#sparseTreeDirs.has(diff.ideal.path) &&
!diff.ideal.isLink

@@ -552,3 +632,3 @@ })

dirsChecked.add(d)
const st = await lstat(d).catch(er => null)
const st = await lstat(d).catch(() => null)
// this can happen if we have a link to a package with a name

@@ -560,3 +640,3 @@ // that the filesystem treats as if it is the same thing.

const retired = retirePath(d)
this[_retiredPaths][d] = retired
this.#retiredPaths[d] = retired
this[_trashList].add(retired)

@@ -566,9 +646,9 @@ await this[_renamePath](d, retired)

}
this[_sparseTreeDirs].add(node.path)
this.#sparseTreeDirs.add(node.path)
const made = await mkdir(node.path, { recursive: true })
// if the directory already exists, made will be undefined. if that's the case
// we don't want to remove it because we aren't the ones who created it so we
// omit it from the _sparseTreeRoots
// omit it from the #sparseTreeRoots
if (made) {
this[_sparseTreeRoots].add(made)
this.#sparseTreeRoots.add(made)
}

@@ -581,6 +661,6 @@ })).then(timeEnd)

// cut the roots of the sparse tree that were created, not the leaves
const roots = this[_sparseTreeRoots]
const roots = this.#sparseTreeRoots
// also delete the moves that we retired, so that we can move them back
const failures = []
const targets = [...roots, ...Object.keys(this[_retiredPaths])]
const targets = [...roots, ...Object.keys(this.#retiredPaths)]
const unlinks = targets

@@ -602,3 +682,3 @@ .map(path => rm(path, { recursive: true, force: true }).catch(er => failures.push([path, er])))

[_loadShrinkwrapsAndUpdateTrees] () {
const seen = this[_shrinkwrapInflated]
const seen = this.#shrinkwrapInflated
const shrinkwraps = this.diff.leaves

@@ -661,4 +741,9 @@ .filter(d => (d.action === 'CHANGE' || d.action === 'ADD' || !d.action) &&

await this[_checkBins](node)
await this[_extractOrLink](node)
await this[_warnDeprecated](node)
await this.#extractOrLink(node)
const { _id, deprecated } = node.package
// The .catch is in _handleOptionalFailure. Not ideal, this should be cleaned up.
// eslint-disable-next-line promise/always-return
if (deprecated) {
log.warn('deprecated', `${_id}: ${deprecated}`)
}
})

@@ -675,4 +760,4 @@

// do not allow node_modules to be a symlink
async [_validateNodeModules] (nm) {
if (this.options.force || this[_nmValidated].has(nm)) {
async #validateNodeModules (nm) {
if (this.options.force || this.#nmValidated.has(nm)) {
return

@@ -682,3 +767,3 @@ }

if (!st || st.isDirectory()) {
this[_nmValidated].add(nm)
this.#nmValidated.add(nm)
return

@@ -690,5 +775,5 @@ }

async [_extractOrLink] (node) {
async #extractOrLink (node) {
const nm = resolve(node.parent.path, 'node_modules')
await this[_validateNodeModules](nm)
await this.#validateNodeModules(nm)

@@ -705,3 +790,3 @@ if (!node.isLink) {

if (node.resolved) {
const registryResolved = this[_registryResolved](node.resolved)
const registryResolved = this.#registryResolved(node.resolved)
if (registryResolved) {

@@ -728,3 +813,3 @@ res = `${node.name}@${registryResolved}`

await debug(async () => {
const st = await lstat(node.path).catch(e => null)
const st = await lstat(node.path).catch(() => null)
if (st && !st.isDirectory()) {

@@ -753,6 +838,4 @@ debug.log('unpacking into a non-directory', node)

await rm(node.path, { recursive: true, force: true })
await this[_symlink](node)
}
async [_symlink] (node) {
// symlink
const dir = dirname(node.path)

@@ -765,13 +848,6 @@ const target = node.realpath

[_warnDeprecated] (node) {
const { _id, deprecated } = node.package
if (deprecated) {
log.warn('deprecated', `${_id}: ${deprecated}`)
}
}
// if the node is optional, then the failure of the promise is nonfatal
// just add it and its optional set to the trash list.
[_handleOptionalFailure] (node, p) {
return (node.optional ? p.catch(er => {
return (node.optional ? p.catch(() => {
const set = optionalSet(node)

@@ -785,3 +861,3 @@ for (node of set) {

[_registryResolved] (resolved) {
#registryResolved (resolved) {
// the default registry url is a magic value meaning "the currently

@@ -816,5 +892,36 @@ // configured registry".

// reified actual tree that must be unpacked and not modified.
[_loadBundlesAndUpdateTrees] (
depth = 0, bundlesByDepth = this[_getBundlesByDepth]()
) {
[_loadBundlesAndUpdateTrees] (depth = 0, bundlesByDepth) {
let maxBundleDepth
if (!bundlesByDepth) {
bundlesByDepth = new Map()
maxBundleDepth = -1
dfwalk({
tree: this.diff,
visit: diff => {
const node = diff.ideal
if (!node) {
return
}
if (node.isProjectRoot) {
return
}
const { bundleDependencies } = node.package
if (bundleDependencies && bundleDependencies.length) {
maxBundleDepth = Math.max(maxBundleDepth, node.depth)
if (!bundlesByDepth.has(node.depth)) {
bundlesByDepth.set(node.depth, [node])
} else {
bundlesByDepth.get(node.depth).push(node)
}
}
},
getChildren: diff => diff.children,
})
bundlesByDepth.set('maxBundleDepth', maxBundleDepth)
} else {
maxBundleDepth = bundlesByDepth.get('maxBundleDepth')
}
if (depth === 0) {

@@ -824,7 +931,6 @@ time.start('reify:loadBundles')

const maxBundleDepth = bundlesByDepth.get('maxBundleDepth')
if (depth > maxBundleDepth) {
// if we did something, then prune the tree and update the diffs
if (maxBundleDepth !== -1) {
this[_pruneBundledMetadeps](bundlesByDepth)
this.#pruneBundledMetadeps(bundlesByDepth)
this[_diffTrees]()

@@ -850,3 +956,3 @@ }

return () => {
this[_bundleUnpacked].add(node)
this.#bundleUnpacked.add(node)
return this[_reifyNode](node)

@@ -880,3 +986,3 @@ }

for (const name of notTransplanted) {
this[_bundleMissing].add(node.children.get(name))
this.#bundleMissing.add(node.children.get(name))
}

@@ -888,35 +994,4 @@ })))

[_getBundlesByDepth] () {
const bundlesByDepth = new Map()
let maxBundleDepth = -1
dfwalk({
tree: this.diff,
visit: diff => {
const node = diff.ideal
if (!node) {
return
}
if (node.isProjectRoot) {
return
}
const { bundleDependencies } = node.package
if (bundleDependencies && bundleDependencies.length) {
maxBundleDepth = Math.max(maxBundleDepth, node.depth)
if (!bundlesByDepth.has(node.depth)) {
bundlesByDepth.set(node.depth, [node])
} else {
bundlesByDepth.get(node.depth).push(node)
}
}
},
getChildren: diff => diff.children,
})
bundlesByDepth.set('maxBundleDepth', maxBundleDepth)
return bundlesByDepth
}
// https://github.com/npm/cli/issues/1597#issuecomment-667639545
[_pruneBundledMetadeps] (bundlesByDepth) {
#pruneBundledMetadeps (bundlesByDepth) {
const bundleShadowed = new Set()

@@ -1055,5 +1130,5 @@

const node = diff.ideal
const bd = this[_bundleUnpacked].has(node)
const sw = this[_shrinkwrapInflated].has(node)
const bundleMissing = this[_bundleMissing].has(node)
const bd = this.#bundleUnpacked.has(node)
const sw = this.#shrinkwrapInflated.has(node)
const bundleMissing = this.#bundleMissing.has(node)

@@ -1094,4 +1169,4 @@ // check whether we still need to unpack this one.

const timeEnd = time.start('reify:unretire')
const moves = this[_retiredPaths]
this[_retiredUnchanged] = {}
const moves = this.#retiredPaths
this.#retiredUnchanged = {}
return promiseAllRejectLate(this.diff.children.map(diff => {

@@ -1119,3 +1194,3 @@ // skip if nothing was retired

this[_retiredUnchanged][retireFolder] = []
this.#retiredUnchanged[retireFolder] = []
return promiseAllRejectLate(diff.unchanged.map(node => {

@@ -1129,7 +1204,7 @@ // no need to roll back links, since we'll just delete them anyway

// will have been moved/unpacked along with bundler
if (node.inDepBundle && !this[_bundleMissing].has(node)) {
if (node.inDepBundle && !this.#bundleMissing.has(node)) {
return
}
this[_retiredUnchanged][retireFolder].push(node)
this.#retiredUnchanged[retireFolder].push(node)

@@ -1161,6 +1236,6 @@ const rel = relative(realFolder, node.path)

[_rollbackMoveBackRetiredUnchanged] (er) {
const moves = this[_retiredPaths]
const moves = this.#retiredPaths
// flip the mapping around to go back
const realFolders = new Map(Object.entries(moves).map(([k, v]) => [v, k]))
const promises = Object.entries(this[_retiredUnchanged])
const promises = Object.entries(this.#retiredUnchanged)
.map(([retireFolder, nodes]) => promiseAllRejectLate(nodes.map(node => {

@@ -1257,3 +1332,3 @@ const realFolder = realFolders.get(retireFolder)

|| this.options.global
|| this[_dryRun]
|| this.options.dryRun
)

@@ -1294,3 +1369,3 @@

const version = child.version
const prefixRange = version ? this[_savePrefix] + version : '*'
const prefixRange = version ? this.options.savePrefix + version : '*'
// if we installed a range, then we save the range specified

@@ -1330,3 +1405,3 @@ // if it is not a subset of the ^x.y.z. eg, installing a range

const { version } = edge.to.target
const prefixRange = version ? this[_savePrefix] + version : '*'
const prefixRange = version ? this.options.savePrefix + version : '*'
newSpec = prefixRange

@@ -1500,4 +1575,4 @@ } else {

await this.idealTree.meta.save({
format: (this[_formatPackageLock] && format) ? format
: this[_formatPackageLock],
format: (this.options.formatPackageLock && format) ? format
: this.options.formatPackageLock,
})

@@ -1509,141 +1584,2 @@ }

}
async [_copyIdealToActual] () {
// clean up any trash that is still in the tree
for (const path of this[_trashList]) {
const loc = relpath(this.idealTree.realpath, path)
const node = this.idealTree.inventory.get(loc)
if (node && node.root === this.idealTree) {
node.parent = null
}
}
// if we filtered to only certain nodes, then anything ELSE needs
// to be untouched in the resulting actual tree, even if it differs
// in the idealTree. Copy over anything that was in the actual and
// was not changed, delete anything in the ideal and not actual.
// Then we move the entire idealTree over to this.actualTree, and
// save the hidden lockfile.
if (this.diff && this.diff.filterSet.size) {
const reroot = new Set()
const { filterSet } = this.diff
const seen = new Set()
for (const [loc, ideal] of this.idealTree.inventory.entries()) {
seen.add(loc)
// if it's an ideal node from the filter set, then skip it
// because we already made whatever changes were necessary
if (filterSet.has(ideal)) {
continue
}
// otherwise, if it's not in the actualTree, then it's not a thing
// that we actually added. And if it IS in the actualTree, then
// it's something that we left untouched, so we need to record
// that.
const actual = this.actualTree.inventory.get(loc)
if (!actual) {
ideal.root = null
} else {
if ([...actual.linksIn].some(link => filterSet.has(link))) {
seen.add(actual.location)
continue
}
const { realpath, isLink } = actual
if (isLink && ideal.isLink && ideal.realpath === realpath) {
continue
} else {
reroot.add(actual)
}
}
}
// now find any actual nodes that may not be present in the ideal
// tree, but were left behind by virtue of not being in the filter
for (const [loc, actual] of this.actualTree.inventory.entries()) {
if (seen.has(loc)) {
continue
}
seen.add(loc)
// we know that this is something that ISN'T in the idealTree,
// or else we will have addressed it in the previous loop.
// If it's in the filterSet, that means we intentionally removed
// it, so nothing to do here.
if (filterSet.has(actual)) {
continue
}
reroot.add(actual)
}
// go through the rerooted actual nodes, and move them over.
for (const actual of reroot) {
actual.root = this.idealTree
}
// prune out any tops that lack a linkIn, they are no longer relevant.
for (const top of this.idealTree.tops) {
if (top.linksIn.size === 0) {
top.root = null
}
}
// need to calculate dep flags, since nodes may have been marked
// as extraneous or otherwise incorrect during transit.
calcDepFlags(this.idealTree)
}
// save the ideal's meta as a hidden lockfile after we actualize it
this.idealTree.meta.filename =
this.idealTree.realpath + '/node_modules/.package-lock.json'
this.idealTree.meta.hiddenLockfile = true
this.idealTree.meta.lockfileVersion = defaultLockfileVersion
this.actualTree = this.idealTree
this.idealTree = null
if (!this.options.global) {
await this.actualTree.meta.save()
const ignoreScripts = !!this.options.ignoreScripts
// if we aren't doing a dry run or ignoring scripts and we actually made changes to the dep
// tree, then run the dependencies scripts
if (!this[_dryRun] && !ignoreScripts && this.diff && this.diff.children.length) {
const { path, package: pkg } = this.actualTree.target
const stdio = this.options.foregroundScripts ? 'inherit' : 'pipe'
const { scripts = {} } = pkg
for (const event of ['predependencies', 'dependencies', 'postdependencies']) {
if (Object.prototype.hasOwnProperty.call(scripts, event)) {
log.info('run', pkg._id, event, scripts[event])
await time.start(`reify:run:${event}`, () => runScript({
event,
path,
pkg,
stdio,
scriptShell: this.options.scriptShell,
}))
}
}
}
}
}
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 },
})
}
}

@@ -127,3 +127,3 @@ // Do not rely on package._fields, so that we don't throw

const tarballValid = (child, requested, requestor) => {
const tarballValid = (child, requested) => {
if (child.isLink) {

@@ -130,0 +130,0 @@ return false

@@ -133,3 +133,3 @@ // a class to manage an inventory and set of indexes of a set of objects based

set (k, v) {
set () {
throw new Error('direct set() not supported, use inventory.add(node)')

@@ -136,0 +136,0 @@ }

@@ -653,23 +653,23 @@ 'use strict'

// attribute value is equivalent
'=' ({ attr, value, insensitive }) {
'=' ({ attr, value }) {
return attr === value
},
// attribute value contains word
'~=' ({ attr, value, insensitive }) {
'~=' ({ attr, value }) {
return (attr.match(/\w+/g) || []).includes(value)
},
// attribute value contains string
'*=' ({ attr, value, insensitive }) {
'*=' ({ attr, value }) {
return attr.includes(value)
},
// attribute value is equal or starts with
'|=' ({ attr, value, insensitive }) {
'|=' ({ attr, value }) {
return attr.startsWith(`${value}-`)
},
// attribute value starts with
'^=' ({ attr, value, insensitive }) {
'^=' ({ attr, value }) {
return attr.startsWith(value)
},
// attribute value ends with
'$=' ({ attr, value, insensitive }) {
'$=' ({ attr, value }) {
return attr.endsWith(value)

@@ -676,0 +676,0 @@ },

{
"name": "@npmcli/arborist",
"version": "7.5.0",
"version": "7.5.1",
"description": "Manage node_modules trees",

@@ -15,4 +15,4 @@ "dependencies": {

"@npmcli/query": "^3.1.0",
"@npmcli/redact": "^1.1.0",
"@npmcli/run-script": "^8.0.0",
"@npmcli/redact": "^2.0.0",
"@npmcli/run-script": "^8.1.0",
"bin-links": "^4.0.1",

@@ -29,3 +29,3 @@ "cacache": "^18.0.0",

"npm-pick-manifest": "^9.0.0",
"npm-registry-fetch": "^16.2.1",
"npm-registry-fetch": "^17.0.0",
"pacote": "^18.0.1",

@@ -67,3 +67,3 @@ "parse-conflict-json": "^3.0.0",

"type": "git",
"url": "https://github.com/npm/cli.git",
"url": "git+https://github.com/npm/cli.git",
"directory": "workspaces/arborist"

@@ -70,0 +70,0 @@ },

SocketSocket SOC 2 Logo

Product

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

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc