diff --git a/workspaces/arborist/lib/arborist/build-ideal-tree.js b/workspaces/arborist/lib/arborist/build-ideal-tree.js index afcb67903da6d..b79bfe8f459dd 100644 --- a/workspaces/arborist/lib/arborist/build-ideal-tree.js +++ b/workspaces/arborist/lib/arborist/build-ideal-tree.js @@ -28,6 +28,7 @@ const Shrinkwrap = require('../shrinkwrap.js') const { defaultLockfileVersion } = Shrinkwrap const Node = require('../node.js') const Link = require('../link.js') +const Edge = require('../edge.js') const addRmPkgDeps = require('../add-rm-pkg-deps.js') const optionalSet = require('../optional-set.js') const { checkEngine, checkPlatform } = require('npm-install-checks') @@ -1045,14 +1046,18 @@ This is a one-time fix-up, please be patient... // loads a node from an edge, and then loads its peer deps (and their // peer deps, on down the line) into a virtual root parent. async [_nodeFromEdge] (edge, parent_, secondEdge, required) { + let from = edge.from + if (from.installLinks && from.linksIn.size) { + from = from.linksIn.values().next().value + } // create a virtual root node with the same deps as the node that // is requesting this one, so that we can get all the peer deps in // a context where they're likely to be resolvable. // Note that the virtual root will also have virtual copies of the // targets of any child Links, so that they resolve appropriately. - const parent = parent_ || this[_virtualRoot](edge.from) + const parent = parent_ || this[_virtualRoot](from) - const spec = npa.resolve(edge.name, edge.spec, edge.from.path) + const spec = npa.resolve(edge.name, edge.spec, from.path) const first = await this[_nodeFromSpec](edge.name, spec, parent, edge) // we might have a case where the parent has a peer dependency on @@ -1114,7 +1119,7 @@ This is a one-time fix-up, please be patient... // also need to set up any targets from any link deps, so that // they are properly reflected in the virtual environment for (const child of node.children.values()) { - if (child.isLink) { + if (child.isLink && (child.isWorkspace || !child.installLinks)) { new Node({ path: child.realpath, sourceReference: child.target, @@ -1228,7 +1233,7 @@ This is a one-time fix-up, please be patient... const isWorkspace = this.idealTree.workspaces && this.idealTree.workspaces.has(spec.name) // spec is a directory, link it unless installLinks is set or it's a workspace - if (spec.type === 'directory' && (isWorkspace || !installLinks)) { + if (spec.type === 'directory') { return this[_linkFromSpec](name, spec, parent, edge) } @@ -1410,6 +1415,13 @@ This is a one-time fix-up, please be patient... continue } + // if installLinks is set then we want to install deps no matter what + if (link.installLinks) { + this.addTracker('idealTree', link.target.name, link.target.location) + this[_depsQueue].push(link.target) + continue + } + const tree = this.idealTree.target const external = !link.target.isDescendantOf(tree) diff --git a/workspaces/arborist/lib/arborist/reify.js b/workspaces/arborist/lib/arborist/reify.js index c3cbf02b31080..526d84d2c2e4c 100644 --- a/workspaces/arborist/lib/arborist/reify.js +++ b/workspaces/arborist/lib/arborist/reify.js @@ -668,7 +668,7 @@ module.exports = cls => class Reifier extends cls { const nm = resolve(node.parent.path, 'node_modules') await this[_validateNodeModules](nm) - if (node.isLink) { + if (node.isLink && (node.isWorkspace || !node.installLinks)) { await rm(node.path, { recursive: true, force: true }) await this[_symlink](node) } else { @@ -687,6 +687,7 @@ module.exports = cls => class Reifier extends cls { Arborist: this.constructor, resolved: node.resolved, integrity: node.integrity, + where: dirname(node.path), }) } } diff --git a/workspaces/arborist/lib/diff.js b/workspaces/arborist/lib/diff.js index 0387773c29754..8a597f2c9e531 100644 --- a/workspaces/arborist/lib/diff.js +++ b/workspaces/arborist/lib/diff.js @@ -6,7 +6,7 @@ // for a given branch of the tree being mutated. const { depth } = require('treeverse') -const { existsSync } = require('fs') +const { existsSync, lstatSync } = require('fs') const ssri = require('ssri') @@ -114,6 +114,21 @@ const getAction = ({ actual, ideal }) => { return ideal.inDepBundle ? null : 'ADD' } + if (actual.isLink) { + let stat + try { + stat = lstatSync(actual.path) + } catch {} + + if (stat) { + if (ideal.installLinks && stat.isSymbolicLink()) { + return 'CHANGE' + } else if (!ideal.installLinks && !stat.isSymbolicLink()) { + return 'CHANGE' + } + } + } + // always ignore the root node if (ideal.isRoot && actual.isRoot) { return null