@npmcli/arborist
Advanced tools
Comparing version 7.5.0 to 7.5.1
@@ -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 @@ }, |
457505
12224
+ Addedhttps-proxy-agent@7.0.5(transitive)
+ Addedlru-cache@10.3.0(transitive)
+ Addedsocks-proxy-agent@8.0.4(transitive)
- Removed@npmcli/redact@1.1.0(transitive)
- Removedhttps-proxy-agent@7.0.4(transitive)
- Removedlru-cache@10.2.2(transitive)
- Removedminipass-json-stream@1.0.1(transitive)
- Removednpm-registry-fetch@16.2.1(transitive)
- Removedsocks-proxy-agent@8.0.3(transitive)
Updated@npmcli/redact@^2.0.0
Updated@npmcli/run-script@^8.1.0
Updatednpm-registry-fetch@^17.0.0