diff --git a/__tests__/commands/install/integration.js b/__tests__/commands/install/integration.js index 02f936afca..c3206e893d 100644 --- a/__tests__/commands/install/integration.js +++ b/__tests__/commands/install/integration.js @@ -1049,8 +1049,7 @@ test.concurrent('transitive file: dependencies should work', (): Promise = }); }); -// Unskip once https://github.com/yarnpkg/yarn/issues/3778 is resolved -test.skip('unbound transitive dependencies should not conflict with top level dependency', async () => { +test('unbound transitive dependencies should not conflict with top level dependency', async () => { await runInstall({flat: true}, 'install-conflicts', async config => { expect((await fs.readJson(path.join(config.cwd, 'node_modules', 'left-pad', 'package.json'))).version).toEqual( '1.0.0', diff --git a/src/package-resolver.js b/src/package-resolver.js index 887cfde1c3..80799892ee 100644 --- a/src/package-resolver.js +++ b/src/package-resolver.js @@ -228,6 +228,14 @@ export default class PackageResolver { getAllInfoForPackageName(name: string): Array { const patterns = this.patternsByPackage[name] || []; + return this.getAllInfoForPatterns(patterns); + } + + /** + * Retrieve all the package info stored for a list of patterns. + */ + + getAllInfoForPatterns(patterns: string[]): Array { const infos = []; const seen = new Set(); @@ -284,6 +292,13 @@ export default class PackageResolver { collapseAllVersionsOfPackage(name: string, version: string): string { const patterns = this.dedupePatterns(this.patternsByPackage[name]); + return this.collapsePackageVersions(name, version, patterns); + } + + /** + * Make all given patterns resolve to version. + */ + collapsePackageVersions(name: string, version: string, patterns: string[]): string { const human = `${name}@${version}`; // get manifest that matches the version we're collapsing too @@ -530,10 +545,37 @@ export default class PackageResolver { this.resolveToResolution(req); } + for (const dep of deps) { + const name = normalizePattern(dep.pattern).name; + this.optimizeResolutions(name); + } + activity.end(); this.activity = null; } + // for a given package, see if a single manifest can satisfy all ranges + optimizeResolutions(name: string) { + const patterns: Array = this.dedupePatterns(this.patternsByPackage[name] || []); + + // don't optimize things that already have a lockfile entry: + // https://github.com/yarnpkg/yarn/issues/79 + const collapsablePatterns = patterns.filter(pattern => { + const remote = this.patterns[pattern]._remote; + return !this.lockfile.getLocked(pattern) && (!remote || remote.type !== 'workspace'); + }); + if (collapsablePatterns.length < 2) { + return; + } + + const availableVersions = this.getAllInfoForPatterns(collapsablePatterns).map(manifest => manifest.version); + const combinedRange = collapsablePatterns.map(pattern => normalizePattern(pattern).range).join(' '); + const singleVersion = semver.maxSatisfying(availableVersions, combinedRange); + if (singleVersion) { + this.collapsePackageVersions(name, singleVersion, collapsablePatterns); + } + } + /** * Called by the package requester for packages that this resolver already had * a matching version for. Delay the resolve, because better matches can still be