diff --git a/__tests__/commands/install/integration.js b/__tests__/commands/install/integration.js index 8c5766a124..7fd8051da3 100644 --- a/__tests__/commands/install/integration.js +++ b/__tests__/commands/install/integration.js @@ -55,6 +55,16 @@ test.concurrent('install should not hoist packages above their peer dependencies }); }); +test.concurrent('install should resolve peer dependencies from same subtrees', async () => { + await runInstall({}, 'peer-dep-same-subtree', async (config): Promise => { + expect(JSON.parse(await fs.readFile(`${config.cwd}/node_modules/d/node_modules/a/package.json`)).version).toEqual( + '1.0.0', + ); + expect(JSON.parse(await fs.readFile(`${config.cwd}/node_modules//a/package.json`)).version).toEqual('1.1.0'); + expect(await fs.exists(`${config.cwd}/node_modules/c/node_modules/a`)).toEqual(false); + }); +}); + test.concurrent('install optional subdependencies by default', async () => { await runInstall({}, 'install-optional-dependencies', async (config): Promise => { expect(await fs.exists(`${config.cwd}/node_modules/dep-b`)).toEqual(true); diff --git a/__tests__/fixtures/install/peer-dep-same-subtree/a-1.0.0/package.json b/__tests__/fixtures/install/peer-dep-same-subtree/a-1.0.0/package.json new file mode 100644 index 0000000000..d1fe0d5f4b --- /dev/null +++ b/__tests__/fixtures/install/peer-dep-same-subtree/a-1.0.0/package.json @@ -0,0 +1 @@ +{"name":"a", "version":"1.0.0"} diff --git a/__tests__/fixtures/install/peer-dep-same-subtree/a-1.1.0/package.json b/__tests__/fixtures/install/peer-dep-same-subtree/a-1.1.0/package.json new file mode 100644 index 0000000000..22e2844b7f --- /dev/null +++ b/__tests__/fixtures/install/peer-dep-same-subtree/a-1.1.0/package.json @@ -0,0 +1 @@ +{"name":"a", "version":"1.1.0"} diff --git a/__tests__/fixtures/install/peer-dep-same-subtree/b/package.json b/__tests__/fixtures/install/peer-dep-same-subtree/b/package.json new file mode 100644 index 0000000000..6c0e5c34e2 --- /dev/null +++ b/__tests__/fixtures/install/peer-dep-same-subtree/b/package.json @@ -0,0 +1,7 @@ +{ + "name":"b", + "version":"1.0.0", + "dependencies": { + "c": "file:../c" + } +} diff --git a/__tests__/fixtures/install/peer-dep-same-subtree/c/package.json b/__tests__/fixtures/install/peer-dep-same-subtree/c/package.json new file mode 100644 index 0000000000..36b451ea97 --- /dev/null +++ b/__tests__/fixtures/install/peer-dep-same-subtree/c/package.json @@ -0,0 +1,7 @@ +{ + "name":"c", + "version":"1.0.0", + "peerDependencies": { + "a": "^1.0.0" + } +} diff --git a/__tests__/fixtures/install/peer-dep-same-subtree/d/package.json b/__tests__/fixtures/install/peer-dep-same-subtree/d/package.json new file mode 100644 index 0000000000..cf19c1cd85 --- /dev/null +++ b/__tests__/fixtures/install/peer-dep-same-subtree/d/package.json @@ -0,0 +1,7 @@ +{ + "name":"d", + "version":"1.0.0", + "dependencies": { + "a": "file:../a-1.0.0" + } +} diff --git a/__tests__/fixtures/install/peer-dep-same-subtree/package.json b/__tests__/fixtures/install/peer-dep-same-subtree/package.json new file mode 100644 index 0000000000..053cb276c5 --- /dev/null +++ b/__tests__/fixtures/install/peer-dep-same-subtree/package.json @@ -0,0 +1,9 @@ +{ + "name": "peer-dep-same-subtree", + "dependencies": { + "a": "file:a-1.1.0", + "b": "file:b", + "c": "file:c", + "d": "file:d" + } +} diff --git a/src/package-linker.js b/src/package-linker.js index 680e1f52e1..8f9a75f1d6 100644 --- a/src/package-linker.js +++ b/src/package-linker.js @@ -446,6 +446,21 @@ export default class PackageLinker { } const ref = pkg._reference; invariant(ref, 'Package reference is missing'); + // TODO: We are taking the "shortest" ref tree but there may be multiple ref trees with the same length + const refTree = ref.requests.map(req => req.parentNames).sort((arr1, arr2) => arr1.length - arr2.length)[0]; + + const getLevelDistance = pkgRef => { + let minDistance = Infinity; + for (const req of pkgRef.requests) { + const distance = refTree.length - req.parentNames.length; + + if (distance >= 0 && distance < minDistance && req.parentNames.every((name, idx) => name === refTree[idx])) { + minDistance = distance; + } + } + + return minDistance; + }; for (const peerDepName in peerDeps) { const range = peerDeps[peerDepName]; @@ -459,8 +474,8 @@ export default class PackageLinker { if (!(peerPkgRef && peerPkgRef.patterns)) { continue; } - const levelDistance = ref.level - peerPkgRef.level; - if (levelDistance >= 0 && levelDistance < resolvedLevelDistance) { + const levelDistance = getLevelDistance(peerPkgRef); + if (isFinite(levelDistance) && levelDistance < resolvedLevelDistance) { if (this._satisfiesPeerDependency(range, peerPkgRef.version)) { resolvedLevelDistance = levelDistance; resolvedPeerPkgPattern = peerPkgRef.patterns; @@ -468,7 +483,7 @@ export default class PackageLinker { this.reporter.lang( 'selectedPeer', `${pkg.name}@${pkg.version}`, - `${peerDepName}@${range}`, + `${peerDepName}@${peerPkgRef.version}`, peerPkgRef.level, ), );