Skip to content

Commit 83b5fbc

Browse files
authored
Fixes how peer dependencies are resolved multiple levels deep (#6443)
1 parent a7ee6da commit 83b5fbc

File tree

8 files changed

+124
-7
lines changed

8 files changed

+124
-7
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/* @flow */
2+
3+
module.exports = require(`./package.json`);
4+
5+
for (const key of [`dependencies`, `devDependencies`, `peerDependencies`]) {
6+
for (const dep of Object.keys(module.exports[key] || {})) {
7+
// $FlowFixMe The whole point of this file is to be dynamic
8+
module.exports[key][dep] = require(dep);
9+
}
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"name": "peer-deps-lvl0",
3+
"version": "1.0.0",
4+
"dependencies": {
5+
"no-deps": "1.0.0",
6+
"peer-deps-lvl1": "1.0.0"
7+
}
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/* @flow */
2+
3+
module.exports = require(`./package.json`);
4+
5+
for (const key of [`dependencies`, `devDependencies`, `peerDependencies`]) {
6+
for (const dep of Object.keys(module.exports[key] || {})) {
7+
// $FlowFixMe The whole point of this file is to be dynamic
8+
module.exports[key][dep] = require(dep);
9+
}
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "peer-deps-lvl1",
3+
"version": "1.0.0",
4+
"dependencies": {
5+
"peer-deps-lvl2": "1.0.0"
6+
},
7+
"peerDependencies": {
8+
"no-deps": "*"
9+
}
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/* @flow */
2+
3+
module.exports = require(`./package.json`);
4+
5+
for (const key of [`dependencies`, `devDependencies`, `peerDependencies`]) {
6+
for (const dep of Object.keys(module.exports[key] || {})) {
7+
// $FlowFixMe The whole point of this file is to be dynamic
8+
module.exports[key][dep] = require(dep);
9+
}
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"name": "peer-deps-lvl2",
3+
"version": "1.0.0",
4+
"peerDependencies": {
5+
"no-deps": "*"
6+
}
7+
}

packages/pkg-tests/pkg-tests-specs/sources/basic.js

+45
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,51 @@ module.exports = (makeTemporaryEnv: PackageDriver) => {
284284
),
285285
);
286286

287+
test(
288+
`it should install in such a way that peer dependencies can be resolved (two levels deep)`,
289+
makeTemporaryEnv(
290+
{
291+
dependencies: {[`peer-deps-lvl0`]: `1.0.0`},
292+
},
293+
async ({path, run, source}) => {
294+
await run(`install`);
295+
296+
await expect(source(`require('peer-deps-lvl0')`)).resolves.toMatchObject({
297+
name: `peer-deps-lvl0`,
298+
version: `1.0.0`,
299+
dependencies: {
300+
[`peer-deps-lvl1`]: {
301+
name: `peer-deps-lvl1`,
302+
version: `1.0.0`,
303+
dependencies: {
304+
[`peer-deps-lvl2`]: {
305+
name: `peer-deps-lvl2`,
306+
version: `1.0.0`,
307+
peerDependencies: {
308+
[`no-deps`]: {
309+
name: `no-deps`,
310+
version: `1.0.0`,
311+
},
312+
},
313+
},
314+
},
315+
peerDependencies: {
316+
[`no-deps`]: {
317+
name: `no-deps`,
318+
version: `1.0.0`,
319+
},
320+
},
321+
},
322+
[`no-deps`]: {
323+
name: `no-deps`,
324+
version: `1.0.0`,
325+
},
326+
},
327+
});
328+
},
329+
),
330+
);
331+
287332
test(
288333
`it should cache the loaded modules`,
289334
makeTemporaryEnv(

src/util/generate-pnp-map.js

+24-7
Original file line numberDiff line numberDiff line change
@@ -202,8 +202,12 @@ async function getPackageInformationStores(
202202
return {pkg, ref, loc};
203203
};
204204

205-
const visit = async (seedPatterns: Array<string>, parentData: Array<string> = []) => {
206-
const resolutions = new Map();
205+
const visit = async (
206+
precomputedResolutions: Map<string, string>,
207+
seedPatterns: Array<string>,
208+
parentData: Array<string> = [],
209+
) => {
210+
const resolutions = new Map(precomputedResolutions);
207211
const locations = new Map();
208212

209213
// This first pass will compute the package reference of each of the given patterns
@@ -337,11 +341,14 @@ async function getPackageInformationStores(
337341
return !pkg || !peerDependencies.has(pkg.name);
338342
});
339343

340-
// We do this in two steps to prevent cyclic dependencies from looping indefinitely
344+
// We inject the partial information in the store right now so that we won't cycle indefinitely
341345
packageInformationStore.set(packageReference, packageInformation);
342-
packageInformation.packageDependencies = await visit(directDependencies, [packageName, packageReference]);
343346

344-
// We now have to inject the peer dependencies
347+
// We must inject the peer dependencies before iterating; one of our dependencies might have a peer dependency
348+
// on one of our peer dependencies, so it must be available from the start (we don't have to do that for direct
349+
// dependencies, because the "visit" function that will iterate over them will automatically add the to the
350+
// candidate resolutions as part of the first step, cf above)
351+
345352
for (const dependencyName of peerDependencies) {
346353
const dependencyReference = resolutions.get(dependencyName);
347354

@@ -350,6 +357,16 @@ async function getPackageInformationStores(
350357
}
351358
}
352359

360+
const childResolutions = await visit(packageInformation.packageDependencies, directDependencies, [
361+
packageName,
362+
packageReference,
363+
]);
364+
365+
// We can now inject into our package the resolutions we got from the visit function
366+
for (const [name, reference] of childResolutions.entries()) {
367+
packageInformation.packageDependencies.set(name, reference);
368+
}
369+
353370
// Finally, unless a package depends on a previous version of itself (that would be weird but correct...), we
354371
// inject them an implicit dependency to themselves (so that they can require themselves)
355372
if (!packageInformation.packageDependencies.has(packageName)) {
@@ -389,7 +406,7 @@ async function getPackageInformationStores(
389406

390407
packageInformationStore.set(pkg.version, {
391408
packageLocation: normalizeDirectoryPath(loc),
392-
packageDependencies: await visit(ref.dependencies, [name, pkg.version]),
409+
packageDependencies: await visit(new Map(), ref.dependencies, [name, pkg.version]),
393410
});
394411
}
395412
}
@@ -403,7 +420,7 @@ async function getPackageInformationStores(
403420
null,
404421
{
405422
packageLocation: normalizeDirectoryPath(config.lockfileFolder),
406-
packageDependencies: await visit(seedPatterns),
423+
packageDependencies: await visit(new Map(), seedPatterns),
407424
},
408425
],
409426
]),

0 commit comments

Comments
 (0)