Skip to content
This repository has been archived by the owner on Jan 20, 2022. It is now read-only.

behave properly when root is a symlink in build-ideal-tree #210

Merged
merged 2 commits into from
Jan 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ scripts/benchmark/*/
/test/fixtures/selflink/node_modules/foo/node_modules/selflink
/test/fixtures/symlinked-node-modules/example/node_modules
/test/fixtures/symlinked-node-modules/linked-node-modules/bar
/test/fixtures/testing-peer-deps-link
/test/fixtures/workspace/node_modules/a
/test/fixtures/workspace/node_modules/b
/test/fixtures/workspace/node_modules/c
Expand Down
56 changes: 42 additions & 14 deletions lib/arborist/build-ideal-tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -332,20 +332,28 @@ module.exports = cls => class IdealTreeBuilder extends cls {
})
}

[_globalRootNode] () {
const root = this[_rootNodeFromPackage]({ dependencies: {} })
async [_globalRootNode] () {
const root = await this[_rootNodeFromPackage]({ dependencies: {} })
// this is a gross kludge to handle the fact that we don't save
// metadata on the root node in global installs, because the "root"
// node is something like /usr/local/lib.
const meta = new Shrinkwrap({ path: this.path })
meta.reset()
root.meta = meta
return Promise.resolve(root)
return root
}

[_rootNodeFromPackage] (pkg) {
return new Node({
async [_rootNodeFromPackage] (pkg) {
// if the path doesn't exist, then we explode at this point. Note that
// this is not a problem for reify(), since it creates the root path
// before ever loading trees.
// TODO: make buildIdealTree() and loadActual handle a missing root path,
// or a symlink to a missing target, and let reify() create it as needed.
const real = await realpath(this.path, this[_rpcache], this[_stcache])
const Cls = real === this.path ? Node : Link
const root = new Cls({
path: this.path,
realpath: real,
pkg,
extraneous: false,
dev: false,
Expand All @@ -355,12 +363,29 @@ module.exports = cls => class IdealTreeBuilder extends cls {
global: this[_global],
legacyPeerDeps: this.legacyPeerDeps,
})
if (root.isLink) {
root.target = new Node({
path: real,
realpath: real,
pkg,
extraneous: false,
dev: false,
devOptional: false,
peer: false,
optional: false,
global: this[_global],
legacyPeerDeps: this.legacyPeerDeps,
root,
})
}
return root
}

// process the add/rm requests by modifying the root node, and the
// update.names request by queueing nodes dependent on those named.
async [_applyUserRequests] (options) {
process.emit('time', 'idealTree:userRequests')
const tree = this.idealTree.target || this.idealTree
// 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.
Expand All @@ -373,15 +398,15 @@ module.exports = cls => class IdealTreeBuilder extends cls {
const nm = resolve(this.path, 'node_modules')
for (const name of await readdir(nm)) {
if (this[_updateAll] || this[_updateNames].includes(name))
this.idealTree.package.dependencies[name] = '*'
tree.package.dependencies[name] = '*'
}
}

if (this.auditReport && this.auditReport.size > 0)
this[_queueVulnDependents](options)

if (options.rm && options.rm.length) {
addRmPkgDeps.rm(this.idealTree.package, options.rm)
addRmPkgDeps.rm(tree.package, options.rm)
for (const name of options.rm)
this[_explicitRequests].add(name)
}
Expand All @@ -391,7 +416,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {

// triggers a refresh of all edgesOut
if (options.add && options.add.length || options.rm && options.rm.length)
this.idealTree.package = this.idealTree.package
tree.package = tree.package
process.emit('timeEnd', 'idealTree:userRequests')
}

Expand All @@ -410,8 +435,9 @@ module.exports = cls => class IdealTreeBuilder extends cls {
this[_resolvedAdd] = 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: this.idealTree.package,
pkg: tree.package,
add,
saveBundle,
saveType,
Expand Down Expand Up @@ -514,7 +540,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
fixAvailable,
} = topVuln
for (const node of topNodes) {
if (node !== this.idealTree) {
if (node !== this.idealTree && node !== this.idealTree.target) {
// 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 ' +
Expand Down Expand Up @@ -646,11 +672,12 @@ This is a one-time fix-up, please be patient...
// at this point we have a virtual tree with the actual root node's
// package deps, which may be partly or entirely incomplete, invalid
// or extraneous.
[_buildDeps] (node) {
[_buildDeps] () {
process.emit('time', 'idealTree:buildDeps')
this[_depsQueue].push(this.idealTree)
const tree = this.idealTree.target || this.idealTree
this[_depsQueue].push(tree)
this.log.silly('idealTree', 'buildDeps')
this.addTracker('idealTree', this.idealTree.name, '')
this.addTracker('idealTree', tree.name, '')
return this[_buildDepStep]()
.then(() => process.emit('timeEnd', 'idealTree:buildDeps'))
}
Expand Down Expand Up @@ -1137,7 +1164,8 @@ This is a one-time fix-up, please be patient...

// when installing globally, or just in global style, we never place
// deps above the first level.
if (this[_globalStyle] && check.resolveParent === this.idealTree)
const tree = this.idealTree && this.idealTree.target || this.idealTree
if (this[_globalStyle] && check.resolveParent === tree)
break
}

Expand Down
5 changes: 3 additions & 2 deletions lib/arborist/reify.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,9 @@ module.exports = cls => class Reifier extends cls {
const actualOpt = this[_global] ? {
ignoreMissing: true,
global: true,
filter: (node, kid) => !node.isRoot ? true
: (node.edgesOut.has(kid) || this[_explicitRequests].has(kid)),
filter: (node, kid) => !node.isRoot && node !== node.root.target
? true
: (node.edgesOut.has(kid) || this[_explicitRequests].has(kid)),
} : { ignoreMissing: true }

if (!this[_global]) {
Expand Down
13 changes: 10 additions & 3 deletions lib/printable.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,19 +80,21 @@ const treeError = ({code, path}) => ({
// util.inspect() output will be a bit cleaner
class Edge {
constructor (edge) {
this.from = edge.from.location
this.type = edge.type
this.name = edge.name
this.spec = edge.spec || '*'
if (edge.to)
this.to = edge.to.location
if (edge.error)
this.error = edge.error
}
}

// don't care about 'from' for edges out
class EdgeOut extends Edge {
constructor (edge) {
super(edge)
this.to = edge.to && edge.to.location
}

[util.inspect.custom] () {
return `{ ${this.type} ${this.name}@${this.spec}${
this.to ? ' -> ' + this.to : ''
Expand All @@ -104,6 +106,11 @@ class EdgeOut extends Edge {

// don't care about 'to' for edges in
class EdgeIn extends Edge {
constructor (edge) {
super(edge)
this.from = edge.from && edge.from.location
}

[util.inspect.custom] () {
return `{ ${this.from || '""'} ${this.type} ${this.name}@${this.spec}${
this.error ? ' ' + this.error : ''
Expand Down
Loading