@npmcli/arborist
Advanced tools
Comparing version 0.0.0-pre.14 to 0.0.0-pre.15
@@ -7,2 +7,3 @@ // mixin implementing the buildIdealTree method | ||
const semver = require('semver') | ||
const pickManifest = require('npm-pick-manifest') | ||
@@ -63,2 +64,4 @@ const calcDepFlags = require('../calc-dep-flags.js') | ||
const _queueNamedUpdates = Symbol('queueNamedUpdates') | ||
const _queueVulnDependents = Symbol('queueVulnDependents') | ||
const _avoidRange = Symbol('avoidRange') | ||
const _shouldUpdateNode = Symbol('shouldUpdateNode') | ||
@@ -72,4 +75,6 @@ const _resetDepFlags = Symbol('resetDepFlags') | ||
const _globalRootNode = Symbol('globalRootNode') | ||
const _isVulnerable = Symbol.for('isVulnerable') | ||
// used by Reify mixin | ||
const _force = Symbol.for('force') | ||
const _explicitRequests = Symbol.for('explicitRequests') | ||
@@ -95,5 +100,10 @@ const _global = Symbol.for('global') | ||
globalStyle = false, | ||
legacyPeerDeps = false, | ||
force = false, | ||
} = options | ||
this[_force] = !!force | ||
this.idealTree = options.idealTree | ||
this.legacyPeerDeps = legacyPeerDeps | ||
@@ -126,2 +136,4 @@ this[_globalStyle] = this[_global] || globalStyle | ||
process.emit('time', 'idealTree') | ||
if (!options.add && !options.rm && this[_global]) | ||
@@ -147,2 +159,3 @@ return Promise.reject(new Error('global requires an add or rm option')) | ||
.then(() => { | ||
process.emit('timeEnd', 'idealTree') | ||
this.finishTracker('idealTree') | ||
@@ -182,2 +195,3 @@ return this.idealTree | ||
[_initTree] () { | ||
process.emit('time', 'idealTree:init') | ||
return ( | ||
@@ -204,2 +218,3 @@ this[_global] ? this[_globalRootNode]() | ||
this.virtualTree = null | ||
process.emit('timeEnd', 'idealTree:init') | ||
}) | ||
@@ -229,2 +244,3 @@ } | ||
global: this[_global], | ||
legacyPeerDeps: this.legacyPeerDeps, | ||
}) | ||
@@ -236,2 +252,3 @@ } | ||
[_applyUserRequests] (options) { | ||
process.emit('time', 'idealTree:userRequests') | ||
// If we have a list of package names to update, and we know it's | ||
@@ -243,2 +260,5 @@ // going to update them wherever they are, add any paths into those | ||
if (this.auditReport && this.auditReport.size > 0) | ||
this[_queueVulnDependents](options) | ||
if (options.rm && options.rm.length) { | ||
@@ -251,3 +271,6 @@ addRmPkgDeps.rm(this.idealTree.package, options.rm) | ||
// triggers a refresh of all edgesOut | ||
const after = () => this.idealTree.package = this.idealTree.package | ||
const after = () => { | ||
this.idealTree.package = this.idealTree.package | ||
process.emit('timeEnd', 'idealTree:userRequests') | ||
} | ||
@@ -260,3 +283,2 @@ // these just add and remove to/from the root node | ||
// This returns a promise because we might not have the name yet, | ||
@@ -293,2 +315,69 @@ // and need to call pacote.manifest to find the name. | ||
[_queueVulnDependents] (options) { | ||
for (const [name, {nodes}] of this.auditReport.entries()) { | ||
for (const node of nodes) { | ||
for (const edge of node.edgesIn) { | ||
this.addTracker('buildIdealTree', edge.from.name, edge.from.location) | ||
this[_depsQueue].push(edge.from) | ||
} | ||
} | ||
} | ||
// note any that can't be fixed at the root level without --force | ||
// if there's a fix, we use that. otherwise, the user has to remove it, | ||
// find a different thing, fix the upstream, etc. | ||
// | ||
// XXX: how to handle top nodes that aren't the root? Maybe the report | ||
// just tells the user to cd into that directory and fix it? | ||
if (this[_force] && this.auditReport && this.auditReport.topVulns.size) { | ||
options.add = options.add || [] | ||
options.rm = options.rm || [] | ||
for (const [name, topVuln] of this.auditReport.topVulns.entries()) { | ||
const { | ||
packument, | ||
simpleRange, | ||
range: avoid, | ||
topNodes, | ||
fixAvailable, | ||
} = topVuln | ||
for (const node of topNodes) { | ||
if (node !== this.idealTree) { | ||
// not something we're going to fix, sorry. have to cd into | ||
// that directory and fix it yourself. | ||
this.log.warn('audit', 'Manual fix required in linked project ' + | ||
`at ./${node.location} for ${name}@${simpleRange}.\n` + | ||
`'cd ./${node.location}' and run 'npm audit' for details.`) | ||
continue | ||
} | ||
if (!fixAvailable) { | ||
this.log.warn('audit', `No fix available for ${name}@${simpleRange}`) | ||
continue | ||
} | ||
const { isSemVerMajor, version } = fixAvailable | ||
const breakingMessage = isSemVerMajor | ||
? 'a SemVer major change' | ||
: 'outside your stated dependency range' | ||
this.log.warn('audit', `Updating ${name} to ${version},` + | ||
`which is ${breakingMessage}.`) | ||
options.add.push(`${name}@${version}`) | ||
} | ||
} | ||
} | ||
} | ||
[_isVulnerable] (node) { | ||
return this.auditReport && this.auditReport.isVulnerable(node) | ||
} | ||
[_avoidRange] (name) { | ||
if (!this.auditReport) | ||
return null | ||
const vuln = this.auditReport.get(name) | ||
if (!vuln) | ||
return null | ||
return vuln.range | ||
} | ||
[_queueNamedUpdates] () { | ||
@@ -304,2 +393,4 @@ const names = this[_updateNames] | ||
// XXX this could be faster by doing a series of inventory.query('name') | ||
// calls rather than walking over everything in the tree. | ||
const set = this.idealTree.inventory | ||
@@ -327,2 +418,3 @@ .filter(n => this[_shouldUpdateNode](n)) | ||
[_buildDeps] (node) { | ||
process.emit('time', 'idealTree:buildDeps') | ||
this[_depsQueue].push(this.idealTree) | ||
@@ -332,2 +424,3 @@ this.log.silly('idealTree', 'buildDeps') | ||
return this[_buildDepStep]() | ||
.then(() => process.emit('timeEnd', 'idealTree:buildDeps')) | ||
} | ||
@@ -338,3 +431,5 @@ | ||
if (this[_currentDep]) { | ||
this.finishTracker('idealTree', this[_currentDep].name, this[_currentDep].location) | ||
const { location, name } = this[_currentDep] | ||
process.emit('timeEnd', `idealTree:${location || '#root'}`) | ||
this.finishTracker('idealTree', name, location) | ||
this[_currentDep] = null | ||
@@ -364,2 +459,3 @@ } | ||
this[_currentDep] = node | ||
process.emit('time', `idealTree:${node.location || '#root'}`) | ||
@@ -450,5 +546,7 @@ // if any deps are missing or invalid, then we fetch the manifest for | ||
// a context where they're likely to be resolvable. | ||
const { legacyPeerDeps } = this | ||
parent = parent || new Node({ | ||
path: '/virtual-root', | ||
pkg: edge.from.package, | ||
legacyPeerDeps, | ||
}) | ||
@@ -475,2 +573,3 @@ const spec = npa.resolve(edge.name, edge.spec, edge.from.path) | ||
(!edge.valid || !edge.to || this[_updateNames].includes(edge.name) || | ||
this[_isVulnerable](edge.to) || | ||
node.isRoot && this[_explicitRequests].has(edge.name))) | ||
@@ -483,3 +582,6 @@ } | ||
else { | ||
const options = Object.create(this.options) | ||
const options = { | ||
...this.options, | ||
avoid: this[_avoidRange](spec.name), | ||
} | ||
const p = pacote.manifest(spec, options) | ||
@@ -496,6 +598,7 @@ this[_manifests].set(spec.raw, p) | ||
// might be within another package that doesn't exist yet. | ||
const { legacyPeerDeps } = this | ||
return spec.type === 'directory' | ||
? this[_linkFromSpec](name, spec, parent, edge) | ||
: this[_fetchManifest](spec) | ||
.then(pkg => new Node({ name, pkg, parent }), error => { | ||
.then(pkg => new Node({ name, pkg, parent, legacyPeerDeps }), error => { | ||
error.requiredBy = edge.from.location || '.' | ||
@@ -509,2 +612,3 @@ // failed to load the spec, either because of enotarget or | ||
error, | ||
legacyPeerDeps, | ||
}) | ||
@@ -518,4 +622,5 @@ this[_loadFailures].add(n) | ||
const realpath = spec.fetchSpec | ||
const { legacyPeerDeps } = this | ||
return rpj(realpath + '/package.json').catch(() => ({})).then(pkg => { | ||
const link = new Link({ name, parent, realpath, pkg }) | ||
const link = new Link({ name, parent, realpath, pkg, legacyPeerDeps }) | ||
this[_linkNodes].add(link) | ||
@@ -546,3 +651,4 @@ return link | ||
[_placeDep] (dep, node, edge, peerEntryEdge = null) { | ||
if (edge.to && !edge.error && !this[_updateNames].includes(edge.name)) | ||
if (edge.to && !edge.error && !this[_updateNames].includes(edge.name) && | ||
!this[_isVulnerable](edge.to)) | ||
return [] | ||
@@ -894,2 +1000,3 @@ | ||
[_fixDepFlags] () { | ||
process.emit('time', 'idealTree:fixDepFlags') | ||
const metaFromDisk = this.idealTree.meta.loadedFromDisk | ||
@@ -931,2 +1038,3 @@ // if the options set prune:false, then we don't prune, but we still | ||
} | ||
process.emit('timeEnd', 'idealTree:fixDepFlags') | ||
} | ||
@@ -933,0 +1041,0 @@ |
@@ -27,6 +27,7 @@ // The arborist manages three trees: | ||
// and path, and call out to the others. | ||
const Reify = require('./reify.js') | ||
const Auditor = require('./audit.js') | ||
const {resolve} = require('path') | ||
class Arborist extends Reify(require('events')) { | ||
class Arborist extends Auditor(require('events')) { | ||
constructor (options = {}) { | ||
process.emit('time', 'arborist:ctor') | ||
super(options) | ||
@@ -39,2 +40,3 @@ this.options = { | ||
this.path = resolve(this.options.path) | ||
process.emit('timeEnd', 'arborist:ctor') | ||
} | ||
@@ -41,0 +43,0 @@ } |
@@ -83,2 +83,4 @@ // mix-in implementing the loadActual method | ||
return this[_loadActualActually]() | ||
}).then(tree => { | ||
return tree | ||
}) | ||
@@ -165,2 +167,3 @@ } | ||
return this[path === real ? _newNode : _newLink]({ | ||
legacyPeerDeps: this.legacyPeerDeps, | ||
path, | ||
@@ -197,3 +200,3 @@ realpath: real, | ||
return process.env._TEST_ARBORIST_SLOW_LINK_TARGET_ === '1' | ||
? new Promise(res => setTimeout(() => res(new Node(options)), 10)) | ||
? new Promise(res => setTimeout(() => res(new Node(options)), 100)) | ||
: new Node(options) | ||
@@ -200,0 +203,0 @@ } |
@@ -10,2 +10,3 @@ // mixin providing the loadVirtual method | ||
const relpath = require('../relpath.js') | ||
const rpj = require('read-package-json-fast') | ||
@@ -42,3 +43,3 @@ const loadFromShrinkwrap = Symbol('loadFromShrinkwrap') | ||
const { | ||
root = this[loadNode]('', s.data.packages['']) | ||
root = this[loadNode]('', s.data.packages[''] || {}) | ||
} = options | ||
@@ -50,3 +51,3 @@ | ||
[loadFromShrinkwrap] (s, root) { | ||
async [loadFromShrinkwrap] (s, root) { | ||
root.meta = s | ||
@@ -56,5 +57,5 @@ s.add(root) | ||
const {links, nodes} = this[resolveNodes](s, root) | ||
this[resolveLinks](links, nodes) | ||
await this[resolveLinks](links, nodes) | ||
this[assignParentage](nodes) | ||
return Promise.resolve(root) | ||
return root | ||
} | ||
@@ -81,5 +82,9 @@ | ||
// Set the targets to nodes in the set, if we have them (we might not) | ||
[resolveLinks] (links, nodes) { | ||
async [resolveLinks] (links, nodes) { | ||
// now we've loaded the root, and all real nodes | ||
// link up the links | ||
const {meta} = this.virtualTree | ||
const {loadedFromDisk, originalLockfileVersion} = meta | ||
const oldLockfile = loadedFromDisk && !(originalLockfileVersion >= 2) | ||
for (const [location, meta] of links.entries()) { | ||
@@ -91,2 +96,13 @@ const targetPath = resolve(this.path, meta.resolved) | ||
nodes.set(location, link) | ||
nodes.set(targetLoc, link.target) | ||
// legacy shrinkwraps do not store all the info we need for the target. | ||
// if we're loading from disk, and have a link in place, we need to | ||
// look in that actual folder (or at least try to) in order to get | ||
// the dependencies of the link target and load it properly. | ||
if (oldLockfile) { | ||
const pj = link.realpath + '/package.json' | ||
const pkg = await rpj(pj).catch(() => null) | ||
if (pkg) | ||
link.target.package = pkg | ||
} | ||
} | ||
@@ -125,3 +141,3 @@ } | ||
ppkg.bundleDependencies = [name] | ||
else | ||
else if (!ppkg.bundleDependencies.includes(name)) | ||
ppkg.bundleDependencies.push(name) | ||
@@ -136,2 +152,3 @@ } | ||
const node = new Node({ | ||
legacyPeerDeps: this.legacyPeerDeps, | ||
root: this.virtualTree, | ||
@@ -157,2 +174,3 @@ path, | ||
const link = new Link({ | ||
legacyPeerDeps: this.legacyPeerDeps, | ||
path, | ||
@@ -159,0 +177,0 @@ realpath: resolve(this.path, targetLoc), |
// mixin implementing the reify method | ||
// XXX unsupported platforms should be failures if the node is optional | ||
// otherwise we try anyway. | ||
// XXX this needs to clone rather than copy, so that we can leave failed | ||
// optional deps in the ideal tree, but remove them from the actual. | ||
// But to do that, we need a way to clone a tree efficiently. | ||
const npa = require('npm-package-arg') | ||
@@ -16,2 +10,3 @@ const pacote = require('pacote') | ||
const updateDepSpec = require('../update-dep-spec.js') | ||
const AuditReport = require('../audit-report.js') | ||
@@ -49,3 +44,3 @@ const boolEnv = b => b ? '1' : '' | ||
const _handleOptionalFailure = Symbol('handleOptionalFailure') | ||
const _loadTrees = Symbol('loadTrees') | ||
const _loadTrees = Symbol.for('loadTrees') | ||
@@ -66,2 +61,4 @@ // shared symbols for swapping out when testing | ||
const _loadBundlesAndUpdateTrees = Symbol.for('loadBundlesAndUpdateTrees') | ||
const _submitQuickAudit = Symbol('submitQuickAudit') | ||
const _awaitQuickAudit = Symbol('awaitQuickAudit') | ||
const _unpackNewModules = Symbol.for('unpackNewModules') | ||
@@ -88,5 +85,5 @@ const _moveContents = Symbol.for('moveContents') | ||
const _scriptShell = Symbol('scriptShell') | ||
const _force = Symbol('force') | ||
// defined by Ideal mixin | ||
const _force = Symbol.for('force') | ||
const _idealTreePrune = Symbol.for('idealTreePrune') | ||
@@ -102,3 +99,2 @@ const _explicitRequests = Symbol.for('explicitRequests') | ||
ignoreScripts = false, | ||
force = false, | ||
scriptShell, | ||
@@ -113,3 +109,2 @@ savePrefix = '^', | ||
this[_ignoreScripts] = !!ignoreScripts | ||
this[_force] = !!force | ||
this[_scriptShell] = scriptShell | ||
@@ -134,2 +129,3 @@ this[_savePrefix] = savePrefix | ||
this.addTracker('reify') | ||
process.emit('time', 'reify') | ||
return this[_loadTrees](options) | ||
@@ -142,2 +138,3 @@ .then(() => this[_diffTrees]()) | ||
.then(() => this[_loadBundlesAndUpdateTrees]()) | ||
.then(() => this[_submitQuickAudit]()) | ||
.then(() => this[_unpackNewModules]()) | ||
@@ -149,4 +146,6 @@ .then(() => this[_moveBackRetiredUnchanged]()) | ||
.then(() => this[_copyIdealToActual]()) | ||
.then(() => this[_awaitQuickAudit]()) | ||
.then(() => { | ||
this.finishTracker('reify') | ||
process.emit('timeEnd', 'reify') | ||
return this.actualTree | ||
@@ -159,4 +158,6 @@ }) | ||
[_loadTrees] (options) { | ||
process.emit('time', 'reify:loadTrees') | ||
if (!this[_global]) | ||
return Promise.all([this.loadActual(), this.buildIdealTree(options)]) | ||
.then(() => process.emit('timeEnd', 'reify:loadTrees')) | ||
@@ -173,5 +174,7 @@ // the global install space tends to have a lot of stuff in it. don't | ||
.then(() => this.loadActual(actualOpts)) | ||
.then(() => process.emit('timeEnd', 'reify:loadTrees')) | ||
} | ||
[_diffTrees] () { | ||
process.emit('time', 'reify:diffTrees') | ||
// XXX if we have an existing diff already, there should be a way | ||
@@ -190,2 +193,3 @@ // to just invalidate the parts that changed, but avoid walking the | ||
} | ||
process.emit('timeEnd', 'reify:diffTrees') | ||
} | ||
@@ -213,2 +217,3 @@ | ||
[_retireShallowNodes] () { | ||
process.emit('time', 'reify:retireShallow') | ||
const moves = this[_retiredPaths] = {} | ||
@@ -226,2 +231,3 @@ for (const diff of this.diff.children) { | ||
.catch(er => this[_rollbackRetireShallowNodes](er)) | ||
.then(() => process.emit('timeEnd', 'reify:retireShallow')) | ||
} | ||
@@ -247,2 +253,3 @@ | ||
[_rollbackRetireShallowNodes] (er) { | ||
process.emit('time', 'reify:rollback:retireShallow') | ||
const moves = this[_retiredPaths] | ||
@@ -254,2 +261,3 @@ const movePromises = Object.entries(moves) | ||
.catch(er => {}) | ||
.then(() => process.emit('timeEnd', 'reify:rollback:retireShallow')) | ||
.then(() => { throw er }) | ||
@@ -264,2 +272,3 @@ } | ||
process.emit('time', 'reify:trashOmits') | ||
const filter = node => | ||
@@ -274,5 +283,7 @@ node.peer && this[_omitPeer] || | ||
} | ||
process.emit('timeEnd', 'reify:trashOmits') | ||
} | ||
[_createSparseTree] () { | ||
process.emit('time', 'reify:createSparse') | ||
// if we call this fn again, we look for the previous list | ||
@@ -289,2 +300,3 @@ // so that we can avoid making the same directory multiple times | ||
.then(() => dirs.forEach(dir => this[_sparseTreeDirs].add(dir))) | ||
.then(() => process.emit('timeEnd', 'reify:createSparse')) | ||
.catch(er => this[_rollbackCreateSparseTree](er)) | ||
@@ -294,2 +306,3 @@ } | ||
[_rollbackCreateSparseTree] (er) { | ||
process.emit('time', 'reify:rollback:createSparse') | ||
// cut the roots of the sparse tree, not the leaves | ||
@@ -305,2 +318,3 @@ const moves = this[_retiredPaths] | ||
}) | ||
.then(() => process.emit('timeEnd', 'reify:rollback:createSparse')) | ||
.then(() => this[_rollbackRetireShallowNodes](er)) | ||
@@ -321,2 +335,4 @@ } | ||
process.emit('time', 'reify:loadShrinkwraps') | ||
const Arborist = this.constructor | ||
@@ -335,2 +351,3 @@ return promiseAllRejectLate(shrinkwraps.map(diff => { | ||
.then(() => this[_createSparseTree]()) | ||
.then(() => process.emit('timeEnd', 'reify:loadShrinkwraps')) | ||
.catch(er => this[_rollbackCreateSparseTree](er)) | ||
@@ -350,2 +367,3 @@ } | ||
process.emit('time', `reifyNode:${node.location}`) | ||
this.addTracker('reify', node.name, node.location) | ||
@@ -362,2 +380,3 @@ | ||
this.finishTracker('reify', node.name, node.location) | ||
process.emit('timeEnd', `reifyNode:${node.location}`) | ||
return node | ||
@@ -410,11 +429,15 @@ }) | ||
// If we're loading from a v1 lockfile, then need to do this again later | ||
// after reading from the disk. | ||
// after reading from the disk. Also grab the bin, because old lockfiles | ||
// did not track that useful bit of info. | ||
const {meta} = this.idealTree | ||
return meta.loadedFromDisk && meta.originalLockfileVersion < 2 && | ||
rpj(node.path + '/package.json').then(pkg => { | ||
if (meta.loadedFromDisk && !(meta.originalLockfileVersion >= 2)) { | ||
return rpj(node.path + '/package.json').then(pkg => { | ||
node.package.bin = pkg.bin | ||
node.package.os = pkg.os | ||
node.package.cpu = pkg.cpu | ||
node.package.engines = pkg.engines | ||
meta.add(node) | ||
return this[_checkEngineAndPlatform](node) | ||
}) | ||
} | ||
} | ||
@@ -489,2 +512,4 @@ | ||
) { | ||
if (depth === 0) | ||
process.emit('time', 'reify:loadBundles') | ||
const maxBundleDepth = bundlesByDepth.get('maxBundleDepth') | ||
@@ -498,2 +523,3 @@ if (depth > maxBundleDepth) { | ||
} | ||
process.emit('timeEnd', 'reify:loadBundles') | ||
return | ||
@@ -514,3 +540,2 @@ } | ||
// extract all the nodes with bundles | ||
this.log.silly('reify', 'reifyNode') | ||
return promiseAllRejectLate(set.map(node => this[_reifyNode](node))) | ||
@@ -574,2 +599,26 @@ // then load their unpacked children and move into the ideal tree | ||
[_submitQuickAudit] () { | ||
if (this.options.audit === false) | ||
return this.auditReport = null | ||
// we submit the quick audit at this point in the process, as soon as | ||
// we have all the deps resolved, so that it can overlap with the other | ||
// actions as much as possible. Stash the promise, which we resolve | ||
// before finishing the reify() and returning the tree. Thus, we do | ||
// NOT return the promise, as the intent is for this to run in parallel | ||
// with the reification, and be resolved at a later time. | ||
process.emit('time', 'reify:audit') | ||
this.auditReport = AuditReport.load(this.idealTree, this.options) | ||
.then(res => { | ||
process.emit('timeEnd', 'reify:audit') | ||
this.auditReport = res | ||
}) | ||
} | ||
// return the promise if we're waiting for it, or the replaced result | ||
[_awaitQuickAudit] () { | ||
return this.auditReport | ||
} | ||
// ok! actually unpack stuff into their target locations! | ||
@@ -580,2 +629,3 @@ // The sparse tree has already been created, so we walk the diff | ||
[_unpackNewModules] () { | ||
process.emit('time', 'reify:unpack') | ||
const unpacks = [] | ||
@@ -597,2 +647,3 @@ dfwalk({ | ||
return promiseAllRejectLate(unpacks) | ||
.then(() => process.emit('timeEnd', 'reify:unpack')) | ||
.catch(er => this[_rollbackCreateSparseTree](er)) | ||
@@ -613,2 +664,3 @@ } | ||
// shallowest nodes that we moved aside in the first place. | ||
process.emit('time', 'reify:unretire') | ||
const moves = this[_retiredPaths] | ||
@@ -641,2 +693,3 @@ this[_retiredUnchanged] = {} | ||
})) | ||
.then(() => process.emit('timeEnd', 'reify:unretire')) | ||
.catch(er => this[_rollbackMoveBackRetiredUnchanged](er)) | ||
@@ -677,2 +730,4 @@ } | ||
process.emit('time', 'reify:runScripts') | ||
// for all the things being installed, run their appropriate scripts | ||
@@ -729,2 +784,3 @@ // run in tip->root order, so as to be more likely to build a node's | ||
.then(() => this[_runScriptQueue]('postinstall', postinstall)) | ||
.then(() => process.emit('timeEnd', 'reify:runScripts')) | ||
.catch(er => this[_rollbackMoveBackRetiredUnchanged](er)) | ||
@@ -737,2 +793,3 @@ } | ||
process.emit('time', `reify:runScripts:${event}`) | ||
return promiseCallLimit(queue.map(([node, pkg]) => () => { | ||
@@ -762,2 +819,3 @@ const {path} = node | ||
})) | ||
.then(() => process.emit('timeEnd', `reify:runScripts:${event}`)) | ||
} | ||
@@ -770,2 +828,3 @@ | ||
[_removeTrash] () { | ||
process.emit('time', 'reify:trash') | ||
const promises = [] | ||
@@ -783,2 +842,3 @@ const failures = [] | ||
}) | ||
.then(() => process.emit('timeEnd', 'reify:trash')) | ||
} | ||
@@ -798,2 +858,4 @@ | ||
process.emit('time', 'reify:save') | ||
if (this[_resolvedAdd]) { | ||
@@ -832,3 +894,3 @@ const root = this.idealTree | ||
}, null, 2) + '\n'), | ||
]) | ||
]).then(() => process.emit('timeEnd', 'reify:save')) | ||
} | ||
@@ -835,0 +897,0 @@ |
@@ -8,3 +8,3 @@ const { depth } = require('treeverse') | ||
tree.peer = false | ||
return depth({ | ||
const ret = depth({ | ||
tree, | ||
@@ -15,2 +15,3 @@ visit: node => calcDepFlagsStep(node), | ||
}) | ||
return ret | ||
} | ||
@@ -17,0 +18,0 @@ |
@@ -102,2 +102,3 @@ // a tree representing the difference between two trees | ||
const action = getAction({actual, ideal}) | ||
// if it's a match, then get its children | ||
@@ -121,8 +122,20 @@ // otherwise, this is the child diff node | ||
// efficient to just fix it while we're passing through already. | ||
// | ||
// Note that moving over a bundled dep will break the links to other | ||
// deps under this parent, which may have been transitively bundled. | ||
// Breaking those links means that we'll no longer see the transitive | ||
// dependency, meaning that it won't appear as bundled any longer! | ||
// In order to not end up dropping transitively bundled deps, we have | ||
// to get the list of nodes to move, then move them all at once, rather | ||
// than moving them one at a time in the first loop. | ||
const bd = ideal.package.bundleDependencies | ||
if (actual && bd && bd.length) { | ||
const bundledChildren = [] | ||
for (const [name, node] of actual.children.entries()) { | ||
if (node.inBundle) | ||
node.parent = ideal | ||
bundledChildren.push(node) | ||
} | ||
for (const node of bundledChildren) { | ||
node.parent = ideal | ||
} | ||
} | ||
@@ -129,0 +142,0 @@ children.push(...getChildren({actual, ideal, unchanged, removed})) |
@@ -82,2 +82,3 @@ // inventory, path, realpath, root, and parent | ||
fsChildren, | ||
legacyPeerDeps = false, | ||
linksIn, | ||
@@ -127,2 +128,3 @@ hasShrinkwrap, | ||
this.hasShrinkwrap = hasShrinkwrap || pkg._hasShrinkwrap || false | ||
this.legacyPeerDeps = legacyPeerDeps | ||
@@ -347,3 +349,3 @@ this.children = new Map() | ||
const pd = this.package.peerDependencies | ||
if (pd && typeof pd === 'object') { | ||
if (pd && typeof pd === 'object' && !this.legacyPeerDeps) { | ||
const pm = this.package.peerDependenciesMeta || {} | ||
@@ -350,0 +352,0 @@ const peerDependencies = {} |
@@ -259,2 +259,3 @@ // a module that manages a shrinkwrap file (npm-shrinkwrap.json or | ||
load () { | ||
const timer = `shrinkwrap:${this.path}${this.hiddenLockfile ? ':hidden' : ''}` | ||
// we don't need to load package-lock.json except for top of tree nodes, | ||
@@ -303,8 +304,5 @@ // only npm-shrinkwrap.json. | ||
this[_fixDependencies](pkg) | ||
return this | ||
}) | ||
} | ||
return this | ||
}) | ||
}).then(() => this) | ||
} | ||
@@ -311,0 +309,0 @@ |
{ | ||
"name": "@npmcli/arborist", | ||
"version": "0.0.0-pre.14", | ||
"version": "0.0.0-pre.15", | ||
"description": "Manage node_modules trees", | ||
@@ -8,3 +8,3 @@ "dependencies": { | ||
"@npmcli/name-from-folder": "^1.0.1", | ||
"@npmcli/run-script": "^1.2.1", | ||
"@npmcli/run-script": "^1.3.1", | ||
"bin-links": "^2.1.2", | ||
@@ -15,3 +15,4 @@ "json-stringify-nice": "^1.1.1", | ||
"npm-package-arg": "^8.0.0", | ||
"pacote": "^11.1.0", | ||
"npm-pick-manifest": "^6.1.0", | ||
"pacote": "^11.1.6", | ||
"parse-conflict-json": "^1.0.0", | ||
@@ -29,3 +30,3 @@ "promise-all-reject-late": "^1.0.0", | ||
"require-inject": "^1.4.4", | ||
"tap": "^14.10.6", | ||
"tap": "^14.10.7", | ||
"tcompare": "^3.0.4" | ||
@@ -32,0 +33,0 @@ }, |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
220949
33
5306
17
6
1
+ Addednpm-pick-manifest@^6.1.0
Updated@npmcli/run-script@^1.3.1
Updatedpacote@^11.1.6