Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/npm/cli.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorisaacs <i@izs.me>2021-04-01 21:42:32 +0300
committerisaacs <i@izs.me>2021-04-01 21:42:34 +0300
commitfb095a708a1f930bbd0195446ac611b82bfeff14 (patch)
tree500d004ec2ee414cbd07a08c2bdcd1d0678f770b /node_modules/@npmcli
parent7b177e43f3bfb558bcd8723cdb2166a3df19647a (diff)
@npmcli/arborist@2.3.0
* [#2896](https://github.com/npm/cli/issues/2896) Provide currentEdge in ERESOLVE if known, and address self-linking edge case. * Add/remove dependencies to/from workspaces when set, not root project * Only reify the portions of the dependency graph identified by the `workspace` configuration value. * Do not recursively `chown` the project root path.
Diffstat (limited to 'node_modules/@npmcli')
-rw-r--r--node_modules/@npmcli/arborist/bin/lib/options.js8
-rw-r--r--node_modules/@npmcli/arborist/lib/arborist/build-ideal-tree.js123
-rw-r--r--node_modules/@npmcli/arborist/lib/arborist/load-actual.js30
-rw-r--r--node_modules/@npmcli/arborist/lib/arborist/reify.js143
-rw-r--r--node_modules/@npmcli/arborist/lib/diff.js69
-rw-r--r--node_modules/@npmcli/arborist/lib/link.js20
-rw-r--r--node_modules/@npmcli/arborist/lib/node.js7
-rw-r--r--node_modules/@npmcli/arborist/lib/printable.js9
-rw-r--r--node_modules/@npmcli/arborist/lib/tree-check.js47
-rw-r--r--node_modules/@npmcli/arborist/package.json4
10 files changed, 390 insertions, 70 deletions
diff --git a/node_modules/@npmcli/arborist/bin/lib/options.js b/node_modules/@npmcli/arborist/bin/lib/options.js
index bf8e08ec2..a1b671962 100644
--- a/node_modules/@npmcli/arborist/bin/lib/options.js
+++ b/node_modules/@npmcli/arborist/bin/lib/options.js
@@ -33,7 +33,13 @@ for (const arg of process.argv.slice(2)) {
options.omit.push(arg.substr('--omit='.length))
} else if (/^--before=/.test(arg))
options.before = new Date(arg.substr('--before='.length))
- else if (/^--[^=]+=/.test(arg)) {
+ else if (/^-w.+/.test(arg)) {
+ options.workspaces = options.workspaces || []
+ options.workspaces.push(arg.replace(/^-w/, ''))
+ } else if (/^--workspace=/.test(arg)) {
+ options.workspaces = options.workspaces || []
+ options.workspaces.push(arg.replace(/^--workspace=/, ''))
+ } else if (/^--[^=]+=/.test(arg)) {
const [key, ...v] = arg.replace(/^--/, '').split('=')
const val = v.join('=')
options[key] = val === 'false' ? false : val === 'true' ? true : val
diff --git a/node_modules/@npmcli/arborist/lib/arborist/build-ideal-tree.js b/node_modules/@npmcli/arborist/lib/arborist/build-ideal-tree.js
index f7e5b7e32..f836fc04d 100644
--- a/node_modules/@npmcli/arborist/lib/arborist/build-ideal-tree.js
+++ b/node_modules/@npmcli/arborist/lib/arborist/build-ideal-tree.js
@@ -44,12 +44,14 @@ const _currentDep = Symbol('currentDep')
const _updateAll = Symbol('updateAll')
const _mutateTree = Symbol('mutateTree')
const _flagsSuspect = Symbol.for('flagsSuspect')
+const _workspaces = Symbol.for('workspaces')
const _prune = Symbol('prune')
const _preferDedupe = Symbol('preferDedupe')
const _legacyBundling = Symbol('legacyBundling')
const _parseSettings = Symbol('parseSettings')
const _initTree = Symbol('initTree')
const _applyUserRequests = Symbol('applyUserRequests')
+const _applyUserRequestsToNode = Symbol('applyUserRequestsToNode')
const _inflateAncientLockfile = Symbol('inflateAncientLockfile')
const _buildDeps = Symbol('buildDeps')
const _buildDepStep = Symbol('buildDepStep')
@@ -109,7 +111,7 @@ const _peerSetSource = Symbol.for('peerSetSource')
// used by Reify mixin
const _force = Symbol.for('force')
-const _explicitRequests = Symbol.for('explicitRequests')
+const _explicitRequests = Symbol('explicitRequests')
const _global = Symbol.for('global')
const _idealTreePrune = Symbol.for('idealTreePrune')
@@ -130,8 +132,10 @@ module.exports = cls => class IdealTreeBuilder extends cls {
force = false,
packageLock = true,
strictPeerDeps = false,
+ workspaces = [],
} = options
+ this[_workspaces] = workspaces || []
this[_force] = !!force
this[_strictPeerDeps] = !!strictPeerDeps
@@ -143,6 +147,9 @@ module.exports = cls => class IdealTreeBuilder extends cls {
this[_globalStyle] = this[_global] || globalStyle
this[_follow] = !!follow
+ if (this[_workspaces].length && this[_global])
+ throw new Error('Cannot operate on workspaces in global mode')
+
this[_explicitRequests] = new Set()
this[_preferDedupe] = false
this[_legacyBundling] = false
@@ -157,6 +164,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
this[_manifests] = new Map()
this[_peerConflict] = null
this[_edgesOverridden] = new Set()
+ this[_resolvedAdd] = []
// a map of each module in a peer set to the thing that depended on
// that set of peers in the first place. Use a WeakMap so that we
@@ -204,8 +212,8 @@ module.exports = cls => class IdealTreeBuilder extends cls {
try {
await this[_initTree]()
- await this[_applyUserRequests](options)
await this[_inflateAncientLockfile]()
+ await this[_applyUserRequests](options)
await this[_buildDeps]()
await this[_fixDepFlags]()
await this[_pruneFailedOptional]()
@@ -266,6 +274,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
this[_preferDedupe] = !!options.preferDedupe
this[_legacyBundling] = !!options.legacyBundling
this[_updateNames] = update.names
+
this[_updateAll] = update.all
// we prune by default unless explicitly set to boolean false
this[_prune] = options.prune !== false
@@ -387,6 +396,42 @@ module.exports = cls => class IdealTreeBuilder extends cls {
async [_applyUserRequests] (options) {
process.emit('time', 'idealTree:userRequests')
const tree = this.idealTree.target || this.idealTree
+
+ if (!this[_workspaces].length) {
+ return this[_applyUserRequestsToNode](tree, options).then(() =>
+ process.emit('timeEnd', 'idealTree:userRequests'))
+ }
+
+ const wsMap = tree.workspaces
+ if (!wsMap) {
+ this.log.warn('idealTree', 'Workspace filter set, but no workspaces present')
+ return
+ }
+
+ const promises = []
+ for (const name of this[_workspaces]) {
+ const path = wsMap.get(name)
+ if (!path) {
+ this.log.warn('idealTree', `Workspace ${name} in filter set, but not in workspaces`)
+ continue
+ }
+ const loc = relpath(tree.realpath, path)
+ const node = tree.inventory.get(loc)
+
+ /* istanbul ignore if - should be impossible */
+ if (!node) {
+ this.log.warn('idealTree', `Workspace ${name} in filter set, but no workspace folder present`)
+ continue
+ }
+
+ promises.push(this[_applyUserRequestsToNode](node, options))
+ }
+
+ return Promise.all(promises).then(() =>
+ process.emit('timeEnd', 'idealTree:userRequests'))
+ }
+
+ async [_applyUserRequestsToNode] (tree, options) {
// If we have a list of package names to update, and we know it's
// going to update them wherever they are, add any paths into those
// named nodes to the buildIdealTree queue.
@@ -395,38 +440,49 @@ module.exports = cls => class IdealTreeBuilder extends cls {
// global updates only update the globalTop nodes, but we need to know
// that they're there, and not reinstall the world unnecessarily.
+ const globalExplicitUpdateNames = []
if (this[_global] && (this[_updateAll] || this[_updateNames].length)) {
const nm = resolve(this.path, 'node_modules')
for (const name of await readdir(nm).catch(() => [])) {
- if (this[_updateNames].includes(name))
- this[_explicitRequests].add(name)
tree.package.dependencies = tree.package.dependencies || {}
- if (this[_updateAll] || this[_updateNames].includes(name))
+ const updateName = this[_updateNames].includes(name)
+ if (this[_updateAll] || updateName) {
+ if (updateName)
+ globalExplicitUpdateNames.push(name)
tree.package.dependencies[name] = '*'
+ }
}
}
if (this.auditReport && this.auditReport.size > 0)
this[_queueVulnDependents](options)
- if (options.rm && options.rm.length) {
- addRmPkgDeps.rm(tree.package, options.rm)
- for (const name of options.rm)
- this[_explicitRequests].add(name)
+ const { add, rm } = options
+
+ if (rm && rm.length) {
+ addRmPkgDeps.rm(tree.package, rm)
+ for (const name of rm)
+ this[_explicitRequests].add({ from: tree, name, action: 'DELETE' })
}
- if (options.add)
- await this[_add](options)
+ if (add && add.length)
+ await this[_add](tree, options)
- // triggers a refresh of all edgesOut
- if (options.add && options.add.length || options.rm && options.rm.length || this[_global])
+ // triggers a refresh of all edgesOut. this has to be done BEFORE
+ // adding the edges to explicitRequests, because the package setter
+ // resets all edgesOut.
+ if (add && add.length || rm && rm.length || this[_global])
tree.package = tree.package
- process.emit('timeEnd', 'idealTree:userRequests')
+
+ for (const spec of this[_resolvedAdd])
+ this[_explicitRequests].add(tree.edgesOut.get(spec.name))
+ for (const name of globalExplicitUpdateNames)
+ this[_explicitRequests].add(tree.edgesOut.get(name))
}
// This returns a promise because we might not have the name yet,
// and need to call pacote.manifest to find the name.
- [_add] ({add, saveType = null, saveBundle = false}) {
+ [_add] (tree, {add, saveType = null, saveBundle = false}) {
// get the name for each of the specs in the list.
// ie, doing `foo@bar` we just return foo
// but if it's a url or git, we don't know the name until we
@@ -438,10 +494,9 @@ module.exports = cls => class IdealTreeBuilder extends cls {
.then(add => this[_updateFilePath](add))
.then(add => this[_followSymlinkPath](add))
})).then(add => {
- this[_resolvedAdd] = add
+ this[_resolvedAdd].push(...add)
// now add is a list of spec objects with names.
// find a home for each of them!
- const tree = this.idealTree.target || this.idealTree
addRmPkgDeps.add({
pkg: tree.package,
add,
@@ -449,8 +504,6 @@ module.exports = cls => class IdealTreeBuilder extends cls {
saveType,
path: this.path,
})
- for (const spec of add)
- this[_explicitRequests].add(spec.name)
})
}
@@ -991,7 +1044,7 @@ This is a one-time fix-up, please be patient...
// if it's peerOptional and not explicitly requested.
if (!edge.to) {
return edge.type !== 'peerOptional' ||
- this[_explicitRequests].has(edge.name)
+ this[_explicitRequests].has(edge)
}
// If the edge has an error, there's a problem.
@@ -1007,7 +1060,7 @@ This is a one-time fix-up, please be patient...
return true
// If the user has explicitly asked to install this package, it's a problem.
- if (node.isProjectRoot && this[_explicitRequests].has(edge.name))
+ if (node.isProjectRoot && this[_explicitRequests].has(edge))
return true
// No problems!
@@ -1131,7 +1184,7 @@ This is a one-time fix-up, please be patient...
continue
// problem
- this[_failPeerConflict](edge)
+ this[_failPeerConflict](edge, parentEdge)
}
}
@@ -1147,17 +1200,17 @@ This is a one-time fix-up, please be patient...
continue
// ok, it's the root, or we're in unforced strict mode, so this is bad
- this[_failPeerConflict](edge)
+ this[_failPeerConflict](edge, parentEdge)
}
return node
}
- [_failPeerConflict] (edge) {
- const expl = this[_explainPeerConflict](edge)
+ [_failPeerConflict] (edge, currentEdge) {
+ const expl = this[_explainPeerConflict](edge, currentEdge)
throw Object.assign(new Error('unable to resolve dependency tree'), expl)
}
- [_explainPeerConflict] (edge) {
+ [_explainPeerConflict] (edge, currentEdge) {
const node = edge.from
const curNode = node.resolve(edge.name)
const pc = this[_peerConflict] || { peer: null, current: null }
@@ -1166,6 +1219,10 @@ This is a one-time fix-up, please be patient...
return {
code: 'ERESOLVE',
current,
+ // it SHOULD be impossible to get here without a current node in place,
+ // but this at least gives us something report on when bugs creep into
+ // the tree handling logic.
+ currentEdge: currentEdge ? currentEdge.explain() : null,
edge: edge.explain(),
peerConflict,
strictPeerDeps: this[_strictPeerDeps],
@@ -1190,7 +1247,7 @@ This is a one-time fix-up, please be patient...
[_placeDep] (dep, node, edge, peerEntryEdge = null, peerPath = []) {
if (edge.to &&
!edge.error &&
- !this[_explicitRequests].has(edge.name) &&
+ !this[_explicitRequests].has(edge) &&
!this[_updateNames].includes(edge.name) &&
!this[_isVulnerable](edge.to))
return []
@@ -1480,9 +1537,15 @@ This is a one-time fix-up, please be patient...
if (target.children.has(edge.name)) {
const current = target.children.get(edge.name)
- // same thing = keep
- if (dep.matches(current))
- return KEEP
+ // same thing = keep, UNLESS the current doesn't satisfy and new
+ // one does satisfy. This can happen if it's a link to a matching target
+ // at a different location, which satisfies a version dep, but not a
+ // file: dep. If neither of them satisfy, then we can replace it,
+ // because presumably it's better for a peer or something.
+ if (dep.matches(current)) {
+ if (current.satisfies(edge) || !dep.satisfies(edge))
+ return KEEP
+ }
const { version: curVer } = current
const { version: newVer } = dep
diff --git a/node_modules/@npmcli/arborist/lib/arborist/load-actual.js b/node_modules/@npmcli/arborist/lib/arborist/load-actual.js
index 49e76e265..d9e7fb46d 100644
--- a/node_modules/@npmcli/arborist/lib/arborist/load-actual.js
+++ b/node_modules/@npmcli/arborist/lib/arborist/load-actual.js
@@ -32,6 +32,7 @@ const _loadActual = Symbol('loadActual')
const _loadActualVirtually = Symbol('loadActualVirtually')
const _loadActualActually = Symbol('loadActualActually')
const _loadWorkspaces = Symbol.for('loadWorkspaces')
+const _loadWorkspaceTargets = Symbol('loadWorkspaceTargets')
const _actualTreePromise = Symbol('actualTreePromise')
const _actualTree = Symbol('actualTree')
const _transplant = Symbol('transplant')
@@ -150,18 +151,22 @@ module.exports = cls => class ActualLoader extends cls {
await new this.constructor({...this.options}).loadVirtual({
root: this[_actualTree],
})
+ await this[_loadWorkspaces](this[_actualTree])
+ if (this[_actualTree].workspaces && this[_actualTree].workspaces.size)
+ calcDepFlags(this[_actualTree], !root)
this[_transplant](root)
return this[_actualTree]
}
async [_loadActualActually] ({ root, ignoreMissing, global }) {
await this[_loadFSTree](this[_actualTree])
+ await this[_loadWorkspaces](this[_actualTree])
+ await this[_loadWorkspaceTargets](this[_actualTree])
if (!ignoreMissing)
await this[_findMissingEdges]()
this[_findFSParents]()
this[_transplant](root)
- await this[_loadWorkspaces](this[_actualTree])
if (global) {
// need to depend on the children, or else all of them
// will end up being flagged as extraneous, since the
@@ -178,16 +183,37 @@ module.exports = cls => class ActualLoader extends cls {
return this[_actualTree]
}
+ // if there are workspace targets without Link nodes created, load
+ // the targets, so that we know what they are.
+ async [_loadWorkspaceTargets] (tree) {
+ if (!tree.workspaces || !tree.workspaces.size)
+ return
+
+ const promises = []
+ for (const path of tree.workspaces.values()) {
+ if (!this[_cache].has(path)) {
+ const p = this[_loadFSNode]({ path, root: this[_actualTree] })
+ .then(node => this[_loadFSTree](node))
+ promises.push(p)
+ }
+ }
+ await Promise.all(promises)
+ }
+
[_transplant] (root) {
if (!root || root === this[_actualTree])
return
+
this[_actualTree][_changePath](root.path)
for (const node of this[_actualTree].children.values()) {
if (!this[_transplantFilter](node))
- node.parent = null
+ node.root = null
}
root.replace(this[_actualTree])
+ for (const node of this[_actualTree].fsChildren)
+ node.root = this[_transplantFilter](node) ? root : null
+
this[_actualTree] = root
}
diff --git a/node_modules/@npmcli/arborist/lib/arborist/reify.js b/node_modules/@npmcli/arborist/lib/arborist/reify.js
index 000804552..aaaa3d61c 100644
--- a/node_modules/@npmcli/arborist/lib/arborist/reify.js
+++ b/node_modules/@npmcli/arborist/lib/arborist/reify.js
@@ -14,6 +14,7 @@ const fs = require('fs')
const {promisify} = require('util')
const symlink = promisify(fs.symlink)
const mkdirp = require('mkdirp-infer-owner')
+const justMkdirp = require('mkdirp')
const moveFile = require('@npmcli/move-file')
const rimraf = promisify(require('rimraf'))
const packageContents = require('@npmcli/installed-package-contents')
@@ -26,6 +27,7 @@ const retirePath = require('../retire-path.js')
const promiseAllRejectLate = require('promise-all-reject-late')
const optionalSet = require('../optional-set.js')
const updateRootPackageJson = require('../update-root-package-json.js')
+const calcDepFlags = require('../calc-dep-flags.js')
const _retiredPaths = Symbol('retiredPaths')
const _retiredUnchanged = Symbol('retiredUnchanged')
@@ -36,6 +38,8 @@ const _retireShallowNodes = Symbol.for('retireShallowNodes')
const _getBundlesByDepth = Symbol('getBundlesByDepth')
const _registryResolved = Symbol('registryResolved')
const _addNodeToTrashList = Symbol('addNodeToTrashList')
+const _workspaces = Symbol.for('workspaces')
+
// shared by rebuild mixin
const _trashList = Symbol.for('trashList')
const _handleOptionalFailure = Symbol.for('handleOptionalFailure')
@@ -82,7 +86,6 @@ const _global = Symbol.for('global')
// defined by Ideal mixin
const _pruneBundledMetadeps = Symbol.for('pruneBundledMetadeps')
-const _explicitRequests = Symbol.for('explicitRequests')
const _resolvedAdd = Symbol.for('resolvedAdd')
const _usePackageLock = Symbol.for('usePackageLock')
const _formatPackageLock = Symbol.for('formatPackageLock')
@@ -146,7 +149,10 @@ module.exports = cls => class Reifier extends cls {
if (this[_packageLockOnly] || this[_dryRun])
return
- await mkdirp(resolve(this.path))
+ // 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 justMkdirp(resolve(this.path))
}
async [_reifyPackages] () {
@@ -237,9 +243,25 @@ module.exports = cls => class Reifier extends cls {
const actualOpt = this[_global] ? {
ignoreMissing: true,
global: true,
- filter: (node, kid) =>
- this[_explicitRequests].size === 0 || !node.isProjectRoot ? true
- : (this.idealTree.edgesOut.has(kid) || this[_explicitRequests].has(kid)),
+ filter: (node, kid) => {
+ // if it's not the project root, and we have no explicit requests,
+ // then we're already into a nested dep, so we keep it
+ if (this.explicitRequests.size === 0 || !node.isProjectRoot)
+ return true
+
+ // if we added it as an edgeOut, then we want it
+ if (this.idealTree.edgesOut.has(kid))
+ return true
+
+ // if it's an explicit request, then we want it
+ const hasExplicit = [...this.explicitRequests]
+ .some(edge => edge.name === kid)
+ if (hasExplicit)
+ return true
+
+ // ignore the rest of the global install folder
+ return false
+ },
} : { ignoreMissing: true }
if (!this[_global]) {
@@ -266,9 +288,35 @@ module.exports = cls => class Reifier extends cls {
// to just invalidate the parts that changed, but avoid walking the
// whole tree again.
+ const filterNodes = []
+ if (this[_global] && this.explicitRequests.size) {
+ const idealTree = this.idealTree.target || this.idealTree
+ const actualTree = this.actualTree.target || this.actualTree
+ // we ONLY are allowed to make changes in the global top-level
+ // children where there's an explicit request.
+ for (const { name } of this.explicitRequests) {
+ const ideal = idealTree.children.get(name)
+ if (ideal)
+ filterNodes.push(ideal)
+ const actual = actualTree.children.get(name)
+ if (actual)
+ filterNodes.push(actual)
+ }
+ } else {
+ for (const ws of this[_workspaces]) {
+ const ideal = this.idealTree.children.get(ws)
+ if (ideal)
+ filterNodes.push(ideal)
+ const actual = this.actualTree.children.get(ws)
+ if (actual)
+ filterNodes.push(actual)
+ }
+ }
+
// find all the nodes that need to change between the actual
// and ideal trees.
this.diff = Diff.calculate({
+ filterNodes,
actual: this.actualTree,
ideal: this.idealTree,
})
@@ -886,7 +934,7 @@ module.exports = cls => class Reifier extends cls {
// to things like git repos and tarball file/urls. However, if the
// user requested 'foo@', and we have a foo@file:../foo, then we should
// end up saving the spec we actually used, not whatever they gave us.
- if (this[_resolvedAdd]) {
+ if (this[_resolvedAdd].length) {
const root = this.idealTree
const pkg = root.package
for (const { name } of this[_resolvedAdd]) {
@@ -966,20 +1014,85 @@ module.exports = cls => class Reifier extends cls {
return meta.save(saveOpt)
}
- [_copyIdealToActual] () {
+ 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 { filterSet } = this.diff
+ const seen = new Set()
+ for (const [loc, ideal] of this.idealTree.inventory.entries()) {
+ if (seen.has(loc))
+ continue
+ 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
+ actual.root = this.idealTree
+ }
+ }
+
+ // 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)
+ if (filterSet.has(actual))
+ continue
+ actual.root = this.idealTree
+ }
+
+ // prune out any tops that lack a linkIn
+ 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.path + '/node_modules/.package-lock.json'
+ this.idealTree.realpath + '/node_modules/.package-lock.json'
this.idealTree.meta.hiddenLockfile = true
+
this.actualTree = this.idealTree
this.idealTree = null
- for (const path of this[_trashList]) {
- const loc = relpath(this.path, path)
- const node = this.actualTree.inventory.get(loc)
- if (node && node.root === this.actualTree)
- node.parent = null
- }
- return !this[_global] && this.actualTree.meta.save()
+ if (!this[_global])
+ await this.actualTree.meta.save()
}
}
diff --git a/node_modules/@npmcli/arborist/lib/diff.js b/node_modules/@npmcli/arborist/lib/diff.js
index ada67f816..84a8bae41 100644
--- a/node_modules/@npmcli/arborist/lib/diff.js
+++ b/node_modules/@npmcli/arborist/lib/diff.js
@@ -11,7 +11,8 @@ const {existsSync} = require('fs')
const ssri = require('ssri')
class Diff {
- constructor ({actual, ideal}) {
+ constructor ({actual, ideal, filterSet}) {
+ this.filterSet = filterSet
this.children = []
this.actual = actual
this.ideal = ideal
@@ -29,9 +30,54 @@ class Diff {
this.removed = []
}
- static calculate ({actual, ideal}) {
+ static calculate ({actual, ideal, filterNodes = []}) {
+ // if there's a filterNode, then:
+ // - get the path from the root to the filterNode. The root or
+ // root.target should have an edge either to the filterNode or
+ // a link to the filterNode. If not, abort. Add the path to the
+ // filterSet.
+ // - Add set of Nodes depended on by the filterNode to filterSet.
+ // - Anything outside of that set should be ignored by getChildren
+ const filterSet = new Set()
+ for (const filterNode of filterNodes) {
+ const { root } = filterNode
+ if (root !== ideal && root !== actual)
+ throw new Error('invalid filterNode: outside idealTree/actualTree')
+ const { target } = root
+ const rootTarget = target || root
+ const edge = [...rootTarget.edgesOut.values()].filter(e => {
+ return e.to && (e.to === filterNode || e.to.target === filterNode)
+ })[0]
+ filterSet.add(root)
+ filterSet.add(rootTarget)
+ filterSet.add(ideal)
+ filterSet.add(actual)
+ if (edge && edge.to) {
+ filterSet.add(edge.to)
+ if (edge.to.target)
+ filterSet.add(edge.to.target)
+ }
+ filterSet.add(filterNode)
+
+ depth({
+ tree: filterNode,
+ visit: node => filterSet.add(node),
+ getChildren: node => {
+ node = node.target || node
+ const loc = node.location
+ const idealNode = ideal.inventory.get(loc)
+ const ideals = !idealNode ? []
+ : [...idealNode.edgesOut.values()].filter(e => e.to).map(e => e.to)
+ const actualNode = actual.inventory.get(loc)
+ const actuals = !actualNode ? []
+ : [...actualNode.edgesOut.values()].filter(e => e.to).map(e => e.to)
+ return ideals.concat(actuals)
+ },
+ })
+ }
+
return depth({
- tree: new Diff({actual, ideal}),
+ tree: new Diff({actual, ideal, filterSet}),
getChildren,
leave,
})
@@ -89,20 +135,20 @@ const allChildren = node => {
// to create the diff tree
const getChildren = diff => {
const children = []
- const {unchanged, removed} = diff
+ const {actual, ideal, unchanged, removed, filterSet} = diff
// Note: we DON'T diff fsChildren themselves, because they are either
// included in the package contents, or part of some other project, and
// will never appear in legacy shrinkwraps anyway. but we _do_ include the
// child nodes of fsChildren, because those are nodes that we are typically
// responsible for installing.
- const actualKids = allChildren(diff.actual)
- const idealKids = allChildren(diff.ideal)
+ const actualKids = allChildren(actual)
+ const idealKids = allChildren(ideal)
const paths = new Set([...actualKids.keys(), ...idealKids.keys()])
for (const path of paths) {
const actual = actualKids.get(path)
const ideal = idealKids.get(path)
- diffNode(actual, ideal, children, unchanged, removed)
+ diffNode(actual, ideal, children, unchanged, removed, filterSet)
}
if (diff.leaves && !children.length)
@@ -111,7 +157,10 @@ const getChildren = diff => {
return children
}
-const diffNode = (actual, ideal, children, unchanged, removed) => {
+const diffNode = (actual, ideal, children, unchanged, removed, filterSet) => {
+ if (filterSet.size && !(filterSet.has(ideal) || filterSet.has(actual)))
+ return
+
const action = getAction({actual, ideal})
// if it's a match, then get its children
@@ -119,7 +168,7 @@ const diffNode = (actual, ideal, children, unchanged, removed) => {
if (action) {
if (action === 'REMOVE')
removed.push(actual)
- children.push(new Diff({actual, ideal}))
+ children.push(new Diff({actual, ideal, filterSet}))
} else {
unchanged.push(ideal)
// !*! Weird dirty hack warning !*!
@@ -150,7 +199,7 @@ const diffNode = (actual, ideal, children, unchanged, removed) => {
for (const node of bundledChildren)
node.parent = ideal
}
- children.push(...getChildren({actual, ideal, unchanged, removed}))
+ children.push(...getChildren({actual, ideal, unchanged, removed, filterSet}))
}
}
diff --git a/node_modules/@npmcli/arborist/lib/link.js b/node_modules/@npmcli/arborist/lib/link.js
index 2394c6e41..4d15428d8 100644
--- a/node_modules/@npmcli/arborist/lib/link.js
+++ b/node_modules/@npmcli/arborist/lib/link.js
@@ -23,13 +23,19 @@ class Link extends Node {
: null),
})
- this.target = target || new Node({
- ...options,
- path: realpath,
- parent: null,
- fsParent: null,
- root: this.root,
- })
+ if (target)
+ this.target = target
+ else if (this.realpath === this.root.path)
+ this.target = this.root
+ else {
+ this.target = new Node({
+ ...options,
+ path: realpath,
+ parent: null,
+ fsParent: null,
+ root: this.root,
+ })
+ }
}
get version () {
diff --git a/node_modules/@npmcli/arborist/lib/node.js b/node_modules/@npmcli/arborist/lib/node.js
index fa39bed5e..197804e0c 100644
--- a/node_modules/@npmcli/arborist/lib/node.js
+++ b/node_modules/@npmcli/arborist/lib/node.js
@@ -685,6 +685,7 @@ class Node {
...this.children.values(),
...this.inventory.values(),
].filter(n => n !== this))
+
for (const child of family) {
if (child.root !== root) {
child[_delistFromMeta]()
@@ -704,12 +705,14 @@ class Node {
}
// if we had a target, and didn't find one in the new root, then bring
- // it over as well.
- if (this.isLink && target && !this.target)
+ // it over as well, but only if we're setting the link into a new root,
+ // as we don't want to lose the target any time we remove a link.
+ if (this.isLink && target && !this.target && root !== this)
target.root = root
// tree should always be valid upon root setter completion.
treeCheck(this)
+ treeCheck(root)
}
get root () {
diff --git a/node_modules/@npmcli/arborist/lib/printable.js b/node_modules/@npmcli/arborist/lib/printable.js
index 588121dbc..169984fcf 100644
--- a/node_modules/@npmcli/arborist/lib/printable.js
+++ b/node_modules/@npmcli/arborist/lib/printable.js
@@ -2,6 +2,7 @@
// of the current node and its descendents
const util = require('util')
+const relpath = require('./relpath.js')
class ArboristNode {
constructor (tree, path) {
@@ -47,6 +48,11 @@ class ArboristNode {
.map(edge => new EdgeIn(edge)))
}
+ if (tree.workspaces && tree.workspaces.size) {
+ this.workspaces = new Map([...tree.workspaces.entries()]
+ .map(([name, path]) => [name, relpath(tree.root.realpath, path)]))
+ }
+
// fsChildren sorted by path
if (tree.fsChildren.size) {
this.fsChildren = new Set([...tree.fsChildren]
@@ -126,6 +132,9 @@ class EdgeIn extends Edge {
}
const printableTree = (tree, path = []) => {
+ if (!tree)
+ return tree
+
const Cls = tree.isLink ? ArboristLink
: tree.sourceReference ? ArboristVirtualNode
: ArboristNode
diff --git a/node_modules/@npmcli/arborist/lib/tree-check.js b/node_modules/@npmcli/arborist/lib/tree-check.js
index 00b43296f..a7e8d9c01 100644
--- a/node_modules/@npmcli/arborist/lib/tree-check.js
+++ b/node_modules/@npmcli/arborist/lib/tree-check.js
@@ -1,6 +1,8 @@
const debug = require('./debug.js')
const checkTree = (tree, checkUnreachable = true) => {
+ const log = [['START TREE CHECK', tree.path]]
+
// this can only happen in tests where we have a "tree" object
// that isn't actually a tree.
if (!tree.root || !tree.root.inventory)
@@ -9,8 +11,21 @@ const checkTree = (tree, checkUnreachable = true) => {
const { inventory } = tree.root
const seen = new Set()
const check = (node, via = tree, viaType = 'self') => {
+ log.push([
+ 'CHECK',
+ node && node.location,
+ via && via.location,
+ viaType,
+ 'seen=' + seen.has(node),
+ 'promise=' + !!(node && node.then),
+ 'root=' + !!(node && node.isRoot),
+ ])
+
if (!node || seen.has(node) || node.then)
return
+
+ seen.add(node)
+
if (node.isRoot && node !== tree.root) {
throw Object.assign(new Error('double root'), {
node: node.path,
@@ -19,6 +34,7 @@ const checkTree = (tree, checkUnreachable = true) => {
root: tree.root.path,
via: via.path,
viaType,
+ log,
})
}
@@ -31,6 +47,7 @@ const checkTree = (tree, checkUnreachable = true) => {
via: via.path,
viaType,
otherRoot: node.root && node.root.path,
+ log,
})
}
@@ -43,6 +60,7 @@ const checkTree = (tree, checkUnreachable = true) => {
viaType,
inventory: [...node.inventory.values()].map(node =>
[node.path, node.location]),
+ log,
})
}
@@ -53,6 +71,7 @@ const checkTree = (tree, checkUnreachable = true) => {
root: tree.root.path,
via: via.path,
viaType,
+ log,
})
}
@@ -65,14 +84,38 @@ const checkTree = (tree, checkUnreachable = true) => {
via: via.path,
viaType,
devEdges: devEdges.map(e => [e.type, e.name, e.spec, e.error]),
+ log,
+ })
+ }
+
+ if (node.path === tree.root.path && node !== tree.root) {
+ throw Object.assign(new Error('node with same path as root'), {
+ node: node.path,
+ tree: tree.path,
+ root: tree.root.path,
+ via: via.path,
+ viaType,
+ log,
+ })
+ }
+
+ if (!node.isLink && node.path !== node.realpath) {
+ throw Object.assign(new Error('non-link with mismatched path/realpath'), {
+ node: node.path,
+ tree: tree.path,
+ realpath: node.realpath,
+ root: tree.root.path,
+ via: via.path,
+ viaType,
+ log,
})
}
const { parent, fsParent, target } = node
- seen.add(node)
check(parent, node, 'parent')
check(fsParent, node, 'fsParent')
check(target, node, 'target')
+ log.push(['CHILDREN', node.location, ...node.children.keys()])
for (const kid of node.children.values())
check(kid, node, 'children')
for (const kid of node.fsChildren)
@@ -81,6 +124,7 @@ const checkTree = (tree, checkUnreachable = true) => {
check(link, node, 'linksIn')
for (const top of node.tops)
check(top, node, 'tops')
+ log.push(['DONE', node.location])
}
check(tree)
if (checkUnreachable) {
@@ -92,6 +136,7 @@ const checkTree = (tree, checkUnreachable = true) => {
location: node.location,
root: tree.root.path,
tree: tree.path,
+ log,
})
}
}
diff --git a/node_modules/@npmcli/arborist/package.json b/node_modules/@npmcli/arborist/package.json
index e745be2c7..d08102ce0 100644
--- a/node_modules/@npmcli/arborist/package.json
+++ b/node_modules/@npmcli/arborist/package.json
@@ -1,6 +1,6 @@
{
"name": "@npmcli/arborist",
- "version": "2.2.9",
+ "version": "2.3.0",
"description": "Manage node_modules trees",
"dependencies": {
"@npmcli/installed-package-contents": "^1.0.7",
@@ -14,7 +14,7 @@
"cacache": "^15.0.3",
"common-ancestor-path": "^1.0.1",
"json-parse-even-better-errors": "^2.3.1",
- "json-stringify-nice": "^1.1.1",
+ "json-stringify-nice": "^1.1.2",
"mkdirp-infer-owner": "^2.0.0",
"npm-install-checks": "^4.0.0",
"npm-package-arg": "^8.1.0",