Skip to content
This repository was archived by the owner on Jan 20, 2022. It is now read-only.
Closed
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
8 changes: 7 additions & 1 deletion bin/lib/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 9 additions & 3 deletions lib/arborist/build-ideal-tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -1480,9 +1480,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
Expand Down
114 changes: 105 additions & 9 deletions lib/arborist/reify.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,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')
Expand All @@ -37,6 +38,8 @@ const _retireShallowNodes = Symbol.for('retireShallowNodes')
const _getBundlesByDepth = Symbol('getBundlesByDepth')
const _registryResolved = Symbol('registryResolved')
const _addNodeToTrashList = Symbol('addNodeToTrashList')
const _workspaces = Symbol('workspaces')

// shared by rebuild mixin
const _trashList = Symbol.for('trashList')
const _handleOptionalFailure = Symbol.for('handleOptionalFailure')
Expand Down Expand Up @@ -97,8 +100,10 @@ module.exports = cls => class Reifier extends cls {
packageLockOnly = false,
dryRun = false,
formatPackageLock = true,
workspaces = [],
} = options

this[_workspaces] = workspaces
this[_dryRun] = !!dryRun
this[_packageLockOnly] = !!packageLockOnly
this[_savePrefix] = savePrefix
Expand Down Expand Up @@ -270,9 +275,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,
})
Expand Down Expand Up @@ -970,20 +1001,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()
}
}
69 changes: 59 additions & 10 deletions lib/diff.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
})
Expand Down Expand Up @@ -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)
Expand All @@ -111,15 +157,18 @@ 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
// otherwise, this is the child diff node
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 !*!
Expand Down Expand Up @@ -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}))
}
}

Expand Down
7 changes: 5 additions & 2 deletions lib/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]()
Expand All @@ -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 () {
Expand Down
3 changes: 3 additions & 0 deletions lib/printable.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ class EdgeIn extends Edge {
}

const printableTree = (tree, path = []) => {
if (!tree)
return tree

const Cls = tree.isLink ? ArboristLink
: tree.sourceReference ? ArboristVirtualNode
: ArboristNode
Expand Down
Loading