Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(arborist): check if a spec is a workspace before fetching a manifest, closes #3637 #4371

Merged
merged 1 commit into from
Feb 7, 2022
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
50 changes: 33 additions & 17 deletions workspaces/arborist/lib/arborist/build-ideal-tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -1250,24 +1250,40 @@ This is a one-time fix-up, please be patient...
// Don't bother to load the manifest for link deps, because the target
// might be within another package that doesn't exist yet.
const { legacyPeerDeps } = this
return spec.type === 'directory'
? this[_linkFromSpec](name, spec, parent, edge)
: this[_fetchManifest](spec)
.then(pkg => new Node({ name, pkg, parent, legacyPeerDeps }), error => {
error.requiredBy = edge.from.location || '.'

// failed to load the spec, either because of enotarget or
// fetch failure of some other sort. save it so we can verify
// later that it's optional, otherwise the error is fatal.
const n = new Node({
name,
parent,
error,
legacyPeerDeps,
})
this[_loadFailures].add(n)
return n

// spec is a directory, link it
if (spec.type === 'directory') {
return this[_linkFromSpec](name, spec, parent, edge)
}

// if the spec matches a workspace name, then see if the workspace node will
// satisfy the edge. if it does, we return the workspace node to make sure it
// takes priority.
if (this.idealTree.workspaces && this.idealTree.workspaces.has(spec.name)) {
const existingNode = this.idealTree.edgesOut.get(spec.name).to
if (existingNode && existingNode.isWorkspace && existingNode.satisfies(edge)) {
return edge.to
}
}

// spec isn't a directory, and either isn't a workspace or the workspace we have
// doesn't satisfy the edge. try to fetch a manifest and build a node from that.
return this[_fetchManifest](spec)
.then(pkg => new Node({ name, pkg, parent, legacyPeerDeps }), error => {
error.requiredBy = edge.from.location || '.'

// failed to load the spec, either because of enotarget or
// fetch failure of some other sort. save it so we can verify
// later that it's optional, otherwise the error is fatal.
const n = new Node({
name,
parent,
error,
legacyPeerDeps,
})
this[_loadFailures].add(n)
return n
})
}

[_linkFromSpec] (name, spec, parent, edge) {
Expand Down
99 changes: 99 additions & 0 deletions workspaces/arborist/test/arborist/build-ideal-tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -877,6 +877,105 @@ t.test('workspaces', t => {
t.matchSnapshot(printTree(await tree))
})

t.test('workspace nodes are used instead of fetching manifests when they are valid', async t => {
// turn off networking, this should never make a registry request
nock.disableNetConnect()
t.teardown(() => nock.enableNetConnect())

const path = t.testdir({
'package.json': JSON.stringify({
name: 'root',
workspaces: ['workspace-a', 'workspace-b'],
}),
// the package-lock.json references version 1.0.0 of the workspace deps
// as it would if a user hand edited their workspace's package.json and
// now are attempting to reify with a stale package-lock
'package-lock.json': JSON.stringify({
name: 'root',
lockfileVersion: 2,
requires: true,
packages: {
'': {
name: 'root',
workspaces: ['workspace-a', 'workspace-b'],
},
'node_modules/workspace-a': {
resolved: 'workspace-a',
link: true,
},
'node_modules/workspace-b': {
resolved: 'workspace-b',
link: true,
},
'workspace-a': {
name: 'workspace-a',
version: '1.0.0',
dependencies: {
'workspace-b': '1.0.0',
},
},
'workspace-b': {
name: 'workspace-b',
version: '1.0.0',
},
},
dependencies: {
'workspace-a': {
version: 'file:workspace-a',
requires: {
'workspace-b': '1.0.0',
},
},
'workspace-b': {
version: 'file:workspace-b',
},
},
}),
node_modules: {
'workspace-a': t.fixture('symlink', '../workspace-a'),
'workspace-b': t.fixture('symlink', '../workspace-b'),
},
// the workspaces themselves are at 2.0.0 because they're what was edited
'workspace-a': {
'package.json': JSON.stringify({
name: 'workspace-a',
version: '2.0.0',
dependencies: {
'workspace-b': '2.0.0',
},
}),
},
'workspace-b': {
'package.json': JSON.stringify({
name: 'workspace-b',
version: '2.0.0',
}),
},
})

const arb = new Arborist({
...OPT,
path,
workspaces: ['workspace-a', 'workspace-b'],
})

// this will reject if we try to fetch a manifest for some reason
const tree = await arb.buildIdealTree({
path,
})

const edgeA = tree.edgesOut.get('workspace-a')
t.ok(edgeA.valid, 'workspace-a should be valid')
const edgeB = tree.edgesOut.get('workspace-b')
t.ok(edgeB.valid, 'workspace-b should be valid')
const nodeA = edgeA.to.target
t.ok(nodeA.isWorkspace, 'workspace-a is definitely a workspace')
const nodeB = edgeB.to.target
t.ok(nodeB.isWorkspace, 'workspace-b is definitely a workspace')
const nodeBfromA = nodeA.edgesOut.get('workspace-b').to.target
t.equal(nodeBfromA, nodeB, 'workspace-b edgeOut from workspace-a is the workspace')
})

t.end()
})

Expand Down