Skip to content
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
5 changes: 5 additions & 0 deletions workspaces/arborist/lib/arborist/isolated-reifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ module.exports = cls => class IsolatedReifier extends cls {
isInStore,
inBundle,
isRegistryDependency: node.isRegistryDependency,
isRootDependency: node.isRootDependency,
location,
name: node.packageName || node.name,
optional: node.optional,
Expand Down Expand Up @@ -157,6 +158,10 @@ module.exports = cls => class IsolatedReifier extends cls {
// Carry the source node's registry-dependency flag so the store node retains it.
// IsolatedNode has no edges to recompute it from, and reify's registry-tarball allow-remote exemption depends on it.
result.isRegistryDependency = node.isRegistryDependency
// Same reasoning for allow-remote=root: the store node has no edgesIn, so capture from the source node whether it satisfies a valid edge from the project root or a workspace.
result.isRootDependency = [...node.edgesIn].some(e =>
e.valid && (e.from?.isProjectRoot || e.from?.isWorkspace)
)
return result
}

Expand Down
3 changes: 2 additions & 1 deletion workspaces/arborist/lib/arborist/reify.js
Original file line number Diff line number Diff line change
Expand Up @@ -707,7 +707,8 @@ module.exports = cls => class Reifier extends cls {
integrity: node.integrity,
// A node counts as "root" for allow-* enforcement if it satisfies at least one valid dependency edge declared by the project root or a workspace.
// node.parent is unsafe here: after hoisting, transitive packages can have the project root as their tree parent.
_isRoot: [...node.edgesIn].some(e =>
// In the linked strategy the store node has no edgesIn, so isolated-reifier precomputes isRootDependency from the source node's edges.
_isRoot: node.isRootDependency || [...node.edgesIn].some(e =>
e.valid && (e.from?.isProjectRoot || e.from?.isWorkspace)
),
// pacote's npa re-parses our `name@URL` spec as type=remote, so allowRemote would mis-fire on registry tarballs.
Expand Down
4 changes: 4 additions & 0 deletions workspaces/arborist/lib/isolated-classes.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class IsolatedNode {
isInStore = false
inBundle = false
isRegistryDependency = false
isRootDependency = false
linksIn = new Set()
meta = { loadedFromDisk: false }
optional = false
Expand Down Expand Up @@ -54,6 +55,9 @@ class IsolatedNode {
if (options.isRegistryDependency) {
this.isRegistryDependency = true
}
if (options.isRootDependency) {
this.isRootDependency = true
}
if (options.optional) {
this.optional = true
}
Expand Down
30 changes: 30 additions & 0 deletions workspaces/arborist/test/arborist/reify.js
Original file line number Diff line number Diff line change
Expand Up @@ -3904,6 +3904,36 @@ t.test('should preserve exact ranges, missing actual tree', async (t) => {
await t.resolves(arb.reify(), 'registry tarball is allowed under linked strategy')
})

t.test('allowRemote=root allows root-direct remote tarball under linked install strategy', async t => {
// The linked strategy extracts store nodes as IsolatedNode, which has no edgesIn to recompute root-ness from.
// isRootDependency must be carried from the source tree node, otherwise allow-remote=root mis-fires on a genuine remote tarball that is a direct dep of the project root.
const testdir = t.testdir({
project: {
'package.json': JSON.stringify({
name: 'myproject',
version: '1.0.0',
dependencies: {
abbrev: 'https://remote.example.com/abbrev-1.1.1.tgz',
},
}),
},
})

tnock(t, 'https://remote.example.com')
.get('/abbrev-1.1.1.tgz')
.reply(200, abbrevTGZ)

const arb = new Arborist({
path: resolve(testdir, 'project'),
registry: 'https://registry.example.com',
cache: resolve(testdir, 'cache'),
allowRemote: 'root',
installStrategy: 'linked',
})

await t.resolves(arb.reify(), 'root-direct remote tarball is allowed under linked strategy with allow-remote=root')
})

t.test('registry with different protocol should swap protocol', async (t) => {
const abbrevPackument4 = JSON.stringify({
_id: 'abbrev',
Expand Down
Loading