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

deep reify local links #44

Merged
merged 4 commits into from
Feb 7, 2020
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
65 changes: 63 additions & 2 deletions lib/arborist/build-ideal-tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ const KEEP = Symbol('KEEP')
// Yes, clobber the package that is already here
const REPLACE = Symbol('REPLACE')

const {resolve} = require('path')
const relpath = require('../relpath.js')

const _depsSeen = Symbol('depsSeen')
const _depsQueue = Symbol('depsQueue')
const _currentDep = Symbol('currentDep')
Expand All @@ -39,6 +42,7 @@ const _buildDeps = Symbol('buildDeps')
const _buildDepStep = Symbol('buildDepStep')
const _nodeFromEdge = Symbol('nodeFromEdge')
const _nodeFromSpec = Symbol('nodeFromSpec')
const _linkFromSpec = Symbol('linkFromSpec')
const _loadPeerSet = Symbol('loadPeerSet')
// shared symbols so we can hit them with unit tests
const _updateNames = Symbol.for('updateNames')
Expand All @@ -47,6 +51,7 @@ const _canPlaceDep = Symbol.for('canPlaceDep')
const _canPlacePeers = Symbol('canPlacePeers')
const _pruneForReplacement = Symbol('pruneForReplacement')
const _fixDepFlags = Symbol('fixDepFlags')
const _resolveLinks = Symbol('resolveLinks')
const _rootNodeFromPackage = Symbol('rootNodeFromPackage')
const _addRm = Symbol('addRm')
const _explicitRequests = Symbol('explicitRequests')
Expand All @@ -55,6 +60,7 @@ const _shouldUpdateNode = Symbol('shouldUpdateNode')
const _resetDepFlags = Symbol('resetDepFlags')
const _loadFailures = Symbol('loadFailures')
const _pruneFailedOptional = Symbol('pruneFailedOptional')
const _linkNodes = Symbol('linkNodes')

// used by Reify mixin
const _idealTreePrune = Symbol.for('idealTreePrune')
Expand Down Expand Up @@ -82,6 +88,7 @@ module.exports = cls => class IdealTreeBuilder extends Tracker(Virtual(Actual(cl
this[_updateAll] = false
this[_mutateTree] = false
this[_loadFailures] = new Set()
this[_linkNodes] = new Set()
}

// public method
Expand Down Expand Up @@ -258,7 +265,7 @@ module.exports = cls => class IdealTreeBuilder extends Tracker(Virtual(Actual(cl
}

if (!this[_depsQueue].length)
return
return this[_resolveLinks]()

// sort physically shallower deps up to the front of the queue,
// because they'll affect things deeper in, then alphabetical
Expand Down Expand Up @@ -384,7 +391,7 @@ module.exports = cls => class IdealTreeBuilder extends Tracker(Virtual(Actual(cl
// Don't bother to load the manifest for link deps, because the target
// might be within another package that doesn't exist yet.
return spec.type === 'directory'
? Promise.resolve(new Link({ name, parent, realpath: spec.fetchSpec }))
? this[_linkFromSpec](name, spec, parent, edge)
: pacote.manifest(spec, Object.create(this.options))
.then(pkg => new Node({ name, pkg, parent }), error => {
error.requiredBy = edge.from.location || '.'
Expand All @@ -401,6 +408,15 @@ module.exports = cls => class IdealTreeBuilder extends Tracker(Virtual(Actual(cl
})
}

[_linkFromSpec] (name, spec, parent, edge) {
const realpath = spec.fetchSpec
return rpj(realpath + '/package.json').catch(() => ({})).then(pkg => {
const link = new Link({ name, parent, realpath, pkg })
this[_linkNodes].add(link)
return link
})
}

// load all peer deps and meta-peer deps into the node's parent
// At the end of this, the node's peer-type outward edges are all
// resolved, and so are all of theirs, but other dep types are not.
Expand Down Expand Up @@ -708,6 +724,51 @@ module.exports = cls => class IdealTreeBuilder extends Tracker(Virtual(Actual(cl
return ret
}

// go through all the links in the this[_linkNodes] set
// for each one:
// - if outside the root, ignore it, assume it's fine, it's not our problem
// - if a node in the tree already, assign the target to that node.
// - if a path under an existing node, then assign that as the fsParent,
// and add it to the _depsQueue
//
// call buildDepStep if anything was added to the queue, otherwise we're done
[_resolveLinks] () {
for (const link of this[_linkNodes]) {
this[_linkNodes].delete(link)
const realpath = link.realpath
const loc = relpath(this.path, realpath)
if (/^\.\.[\\\/]/.test(loc)) {
// outside the root, somebody else's problem, ignore it
// TODO: deep updates somehow, with a flag
continue
}

const fromInv = this.idealTree.inventory.get(loc)
if (fromInv && fromInv !== link.target)
link.target = fromInv

if (!link.target.parent && !link.target.fsParent) {
// the fsParent MUST be some node in the tree, possibly the root.
// find it by walking up. Note that this is where its deps may
// end up being installed, if possible.
const parts = loc.split('/')
let p = parts.length - 1
for (let p = parts.length - 1; p > -1; p--) {
const path = parts.slice(0, p).join('/')
const node = !path ? this.idealTree : this.idealTree.inventory.get(path)
if (node) {
link.target.fsParent = node
this[_depsQueue].push(link.target)
p = -1
}
}
}
}

if (this[_depsQueue].length)
return this[_buildDepStep]()
}

[_fixDepFlags] () {
const metaFromDisk = this.idealTree.meta.loadedFromDisk
// if the options set prune:false, then we don't prune, but we still
Expand Down
10 changes: 9 additions & 1 deletion lib/arborist/load-virtual.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,15 @@ module.exports = cls => class VirtualLoader extends cls {
const parent = nodes.get(ploc)
/* istanbul ignore else - impossible unless lockfile damaged/invalid */
if (parent) {
node[ name === node.name ? 'parent' : 'fsParent' ] = parent
// if the node location doesn't actually start with node_modules, but
// the node name DOES match the folder it's in, like if you have a
// link from `node_modules/app` to `./app`, then split won't contain
// anything, but the name will still match. In that case, it is an
// fsParent, though, not a parent.
const parentType = name === node.name && split.length
? 'parent'
: 'fsParent'
node[ parentType ] = parent
// read inBundle from package because 'package' here is
// actually a v2 lockfile metadata entry.
if (node.package.inBundle && parent.edgesOut.has(name)) {
Expand Down
14 changes: 11 additions & 3 deletions lib/arborist/reify.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const _loadShrinkwrapsAndUpdateTrees = Symbol.for('loadShrinkwrapsAndUpdateTrees
const _reifyNode = Symbol.for('reifyNode')
const _checkEngineAndPlatform = Symbol('checkEngineAndPlatform')
const _extractOrLink = Symbol('extractOrLink')
const _symlink = Symbol('symlink')
const _recheckEngineAndPlatform = Symbol('recheckEngineAndPlatform')
const _checkEngine = Symbol('checkEngine')
const _checkPlatform = Symbol('checkPlatform')
Expand Down Expand Up @@ -266,14 +267,21 @@ module.exports = cls => class Reifier extends Ideal(cls) {

[_extractOrLink] (node) {
return node.isLink
? rimraf(node.path).then(() => symlink(node.realpath, node.path, 'dir'))
? rimraf(node.path).then(() => this[_symlink](node))
: pacote.extract(this[_registryResolved](node.resolved), node.path, {
...this.options,
resolved: node.resolved,
integrity: node.integrity,
})
}

[_symlink] (node) {
const dir = dirname(node.path)
const target = node.realpath
const rel = relative(dir, target)
return symlink(rel, node.path, 'dir')
}

[_recheckEngineAndPlatform] (node) {
// If we're loading from a v1 lockfile, then need to do this again later
// after reading from the disk.
Expand All @@ -288,8 +296,8 @@ module.exports = cls => class Reifier extends Ideal(cls) {
[_checkEngineAndPlatform] (node) {
// engine/platform checks throw, so start the promise chain off first
return Promise.resolve()
.then(() => this[_checkEngine](node))
.then(() => this[_checkPlatform](node))
.then(() => this[_checkEngine](node))
.then(() => this[_checkPlatform](node))
}


Expand Down
3 changes: 2 additions & 1 deletion lib/link.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const relpath = require('./relpath.js')
const Node = require('./node.js')
const _loadDeps = Symbol.for('Arborist.Node._loadDeps')
const _target = Symbol('_target')
const {dirname} = require('path')
class Link extends Node {
constructor (options) {
const { realpath, target } = options
Expand Down Expand Up @@ -60,7 +61,7 @@ class Link extends Node {
// a link always resolves to the relative path to its target
get resolved () {
return this.path && this.realpath
? `file:${relpath(this.path, this.realpath)}`
? `file:${relpath(dirname(this.path), this.realpath)}`
: null
}
set resolved (r) {}
Expand Down
8 changes: 6 additions & 2 deletions lib/yarn-lock.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

const npa = require('npm-package-arg')
const consistentResolve = require('./consistent-resolve.js')
const {dirname} = require('path')

const prefix =
`# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
Expand Down Expand Up @@ -168,8 +169,11 @@ class YarnLock {
if (node.package.version)
this.current.version = node.package.version
if (node.resolved)
this.current.resolved =
consistentResolve(node.resolved, node.path, node.root.path)
this.current.resolved = consistentResolve(
node.resolved,
node.isLink ? dirname(node.path) : node.path,
node.root.path
)
if (node.integrity)
this.current.integrity = node.integrity
specs.forEach(spec => this.entries.set(spec, this.current))
Expand Down