Skip to content

Commit 5c46448

Browse files
author
Kaylie Kwon
committed
WIP
1 parent f40f7c1 commit 5c46448

File tree

13 files changed

+192
-45
lines changed

13 files changed

+192
-45
lines changed

__tests__/commands/install/resolutions.js

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,10 @@ test.concurrent('install with simple exact resolutions should override all versi
1717

1818
test.concurrent('install with subtree exact resolutions should override subtree versions', (): Promise<void> => {
1919
return runInstall({}, {source: 'resolutions', cwd: 'subtree-exact'}, async config => {
20-
expect(await getPackageVersion(config, 'a')).toEqual('1.0.0');
21-
expect(await getPackageVersion(config, 'b')).toEqual('1.0.0');
22-
expect(await getPackageVersion(config, 'd1')).toEqual('3.0.0');
23-
expect(await getPackageVersion(config, 'b/d1')).toEqual('2.0.0');
20+
expect(await getPackageVersion(config, 'left-pad')).toEqual('1.0.0');
2421
expect(await getPackageVersion(config, 'd2')).toEqual('1.0.0');
25-
expect(await isPackagePresent(config, 'a/d1')).toEqual(false);
26-
expect(await isPackagePresent(config, 'a/d2')).toEqual(false);
27-
expect(await isPackagePresent(config, 'b/d2')).toEqual(false);
22+
expect(await getPackageVersion(config, 'd2/left-pad')).toEqual('1.1.1');
23+
expect(await getPackageVersion(config, 'c')).toEqual('1.0.0');
24+
expect(await getPackageVersion(config, 'c/left-pad')).toEqual('1.1.2');
2825
});
2926
});

__tests__/fixtures/install/resolutions/c-1/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
"name": "c",
33
"version": "1.0.0",
44
"dependencies": {
5-
"a": "file:../a-2"
5+
"left-pad": "~1.1.1"
66
}
77
}
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
{
22
"name": "d2",
3-
"version": "1.0.0"
3+
"version": "1.0.0",
4+
"dependencies": {
5+
"left-pad": "^1.0.0"
6+
}
47
}

__tests__/fixtures/install/resolutions/subtree-exact/package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
"name": "project",
33
"version": "1.0.0",
44
"dependencies": {
5-
"a": "file:../a-1",
6-
"b": "file:../b-1"
5+
"left-pad": "1.0.0",
6+
"c": "file:../c-1",
7+
"d2": "file:../d2-1"
78
},
89
"resolutions": {
9-
"a/d1": "file:../d1-3"
10+
"d2/left-pad": "1.1.1",
11+
"c/**/left-pad": "1.1.2"
1012
}
1113
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
"gulp-util": "^3.0.7",
7777
"gulp-watch": "^4.3.5",
7878
"jest": "20.0.4",
79+
"minimatch": "^3.0.4",
7980
"mock-stdin": "^0.3.0",
8081
"prettier": "^1.5.2",
8182
"temp": "^0.8.3",

src/cli/commands/import.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ class ImportPackageRequest extends PackageRequest {
177177
}
178178

179179
getParentHumanName(): string {
180-
return [this.getRootName()].concat(this.getParentNames()).join(' > ');
180+
return [this.getRootName()].concat(this.parentNames).join(' > ');
181181
}
182182

183183
reportResolvedRangeMatch(info: Manifest, resolved: Manifest) {

src/cli/commands/install.js

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import * as fs from '../../util/fs.js';
2626
import map from '../../util/map.js';
2727
import {version as YARN_VERSION, getInstallationMethod} from '../../util/yarn-version.js';
2828
import WorkspaceLayout from '../../workspace-layout.js';
29+
import Resolutions from '../../resolutions.js';
2930

3031
const emoji = require('node-emoji');
3132
const invariant = require('invariant');
@@ -165,13 +166,13 @@ export class Install {
165166
constructor(flags: Object, config: Config, reporter: Reporter, lockfile: Lockfile) {
166167
this.rootManifestRegistries = [];
167168
this.rootPatternsToOrigin = map();
168-
this.resolutions = map();
169169
this.lockfile = lockfile;
170170
this.reporter = reporter;
171171
this.config = config;
172172
this.flags = normalizeFlags(config, flags);
173-
174-
this.resolver = new PackageResolver(config, lockfile);
173+
this.resolutions = map(); // Legacy resolutions field used for flat install mode
174+
this._resolutions = new Resolutions(config); // Selective resolutions for nested dependencies
175+
this.resolver = new PackageResolver(config, lockfile, this._resolutions);
175176
this.integrityChecker = new InstallationIntegrityChecker(config);
176177
this.linker = new PackageLinker(config, this.resolver);
177178
this.scripts = new PackageInstallScripts(config, this.resolver, this.flags.force);
@@ -189,6 +190,7 @@ export class Install {
189190
linker: PackageLinker;
190191
rootPatternsToOrigin: {[pattern: string]: string};
191192
integrityChecker: InstallationIntegrityChecker;
193+
_resolutions: Resolutions;
192194

193195
/**
194196
* Create a list of dependency requests from the current directories manifests.
@@ -200,6 +202,7 @@ export class Install {
200202
): Promise<InstallCwdRequest> {
201203
const patterns = [];
202204
const deps: DependencyRequestPatterns = [];
205+
let resolutionDeps: DependencyRequestPatterns = [];
203206
const manifest = {};
204207

205208
const ignorePatterns = [];
@@ -234,6 +237,13 @@ export class Install {
234237
Object.assign(this.resolutions, projectManifestJson.resolutions);
235238
Object.assign(manifest, projectManifestJson);
236239

240+
this._resolutions.init(this.resolutions);
241+
for (const packageName of Object.keys(this._resolutions.resolutionsByPackage)) {
242+
for (const {pattern} of this._resolutions.resolutionsByPackage[packageName]) {
243+
resolutionDeps = [...resolutionDeps, {registry, pattern, optional: false, hint: 'resolution'}];
244+
}
245+
}
246+
237247
const pushDeps = (depType, manifest: Object, {hint, optional}, isUsed) => {
238248
if (ignoreUnusedPatterns && !isUsed) {
239249
return;
@@ -308,7 +318,7 @@ export class Install {
308318
}
309319

310320
return {
311-
requests: deps,
321+
requests: [...resolutionDeps, ...deps],
312322
patterns,
313323
manifest,
314324
usedPatterns,

src/package-request.js

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ type ResolverRegistryNames = $Keys<typeof registryResolvers>;
2525
export default class PackageRequest {
2626
constructor(req: DependencyRequestPattern, resolver: PackageResolver) {
2727
this.parentRequest = req.parentRequest;
28+
this.parentNames = [];
2829
this.lockfile = resolver.lockfile;
2930
this.registry = req.registry;
3031
this.reporter = resolver.reporter;
@@ -38,6 +39,7 @@ export default class PackageRequest {
3839
}
3940

4041
parentRequest: ?PackageRequest;
42+
parentNames: Array<string>;
4143
lockfile: Lockfile;
4244
reporter: Reporter;
4345
resolver: PackageResolver;
@@ -47,20 +49,6 @@ export default class PackageRequest {
4749
optional: boolean;
4850
foundInfo: ?Manifest;
4951

50-
getParentNames(): Array<string> {
51-
const chain = [];
52-
53-
let request = this.parentRequest;
54-
while (request) {
55-
const info = this.resolver.getStrictResolvedPattern(request.pattern);
56-
chain.unshift(info.name);
57-
58-
request = request.parentRequest;
59-
}
60-
61-
return chain;
62-
}
63-
6452
getLocked(remoteType: string): ?Object {
6553
// always prioritise root lockfile
6654
const shrunk = this.lockfile.getLocked(this.pattern);
@@ -110,7 +98,6 @@ export default class PackageRequest {
11098
// "foo": "http://foo.com/bar.tar.gz"
11199
// then we use the foo name
112100
data.name = name;
113-
114101
return data;
115102
}
116103

@@ -267,6 +254,7 @@ export default class PackageRequest {
267254
!info.fresh || frozen
268255
? this.resolver.getExactVersionMatch(name, solvedRange, info)
269256
: this.resolver.getHighestRangeVersionMatch(name, solvedRange, info);
257+
270258
if (resolved) {
271259
this.resolver.reportPackageWithExistingVersion(this, info);
272260
return;
@@ -290,11 +278,10 @@ export default class PackageRequest {
290278
ref.setFresh(fresh);
291279
info._reference = ref;
292280
info._remote = remote;
293-
294281
// start installation of dependencies
295282
const promises = [];
296283
const deps = [];
297-
284+
const parentNames = [...this.parentNames, name];
298285
// normal deps
299286
for (const depName in info.dependencies) {
300287
const depPattern = depName + '@' + info.dependencies[depName];
@@ -306,6 +293,7 @@ export default class PackageRequest {
306293
// dependencies of optional dependencies should themselves be optional
307294
optional: this.optional,
308295
parentRequest: this,
296+
parentNames,
309297
}),
310298
);
311299
}
@@ -320,6 +308,7 @@ export default class PackageRequest {
320308
registry: remote.registry,
321309
optional: true,
322310
parentRequest: this,
311+
parentNames,
323312
}),
324313
);
325314
}
@@ -334,6 +323,7 @@ export default class PackageRequest {
334323
registry: remote.registry,
335324
optional: false,
336325
parentRequest: this,
326+
parentNames,
337327
}),
338328
);
339329
}

src/package-resolver.js

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import BlockingQueue from './util/blocking-queue.js';
1212
import Lockfile from './lockfile/wrapper.js';
1313
import map from './util/map.js';
1414
import WorkspaceLayout from './workspace-layout.js';
15+
import Resolutions from './resolutions';
1516

1617
const invariant = require('invariant');
1718
const semver = require('semver');
@@ -23,11 +24,12 @@ export type ResolverOptions = {|
2324
|};
2425

2526
export default class PackageResolver {
26-
constructor(config: Config, lockfile: Lockfile) {
27+
constructor(config: Config, lockfile: Lockfile, resolutions: ?Resolutions) {
2728
this.patternsByPackage = map();
2829
this.fetchingPatterns = map();
2930
this.fetchingQueue = new BlockingQueue('resolver fetching');
3031
this.patterns = map();
32+
this.resolutions = resolutions || new Resolutions(config);
3133
this.usedRegistries = new Set();
3234
this.flat = false;
3335

@@ -44,6 +46,8 @@ export default class PackageResolver {
4446

4547
workspaceLayout: ?WorkspaceLayout;
4648

49+
resolutions: Resolutions;
50+
4751
// list of registries that have been used in this resolution
4852
usedRegistries: Set<RegistryNames>;
4953

@@ -401,6 +405,7 @@ export default class PackageResolver {
401405

402406
getHighestRangeVersionMatch(name: string, range: string, manifest: ?Manifest): ?Manifest {
403407
const patterns = this.patternsByPackage[name];
408+
404409
if (!patterns) {
405410
return null;
406411
}
@@ -446,12 +451,33 @@ export default class PackageResolver {
446451
return matchedPkg;
447452
}
448453

454+
/**
455+
* Determine if LockfileEntry is incorrect, remove it from lockfile cache and consider the pattern as new
456+
*/
457+
isLockfileEntryOutdated(version: string, range: string, hasVersion: boolean): boolean {
458+
return !!(
459+
semver.validRange(range) &&
460+
semver.valid(version) &&
461+
!getExoticResolver(range) &&
462+
hasVersion &&
463+
!semver.satisfies(version, range)
464+
);
465+
}
466+
449467
/**
450468
* TODO description
451469
*/
452470

453-
async find(req: DependencyRequestPattern): Promise<void> {
471+
async find(initialReq: DependencyRequestPattern): Promise<void> {
472+
const req = this.resolveToResolution(initialReq);
473+
474+
// we've already resolved it with a resolution
475+
if (!req) {
476+
return;
477+
}
478+
454479
const fetchKey = `${req.registry}:${req.pattern}`;
480+
455481
if (this.fetchingPatterns[fetchKey]) {
456482
return;
457483
} else {
@@ -464,16 +490,11 @@ export default class PackageResolver {
464490

465491
const lockfileEntry = this.lockfile.getLocked(req.pattern);
466492
let fresh = false;
493+
467494
if (lockfileEntry) {
468495
const {range, hasVersion} = PackageRequest.normalizePattern(req.pattern);
469-
// lockfileEntry is incorrect, remove it from lockfile cache and consider the pattern as new
470-
if (
471-
semver.validRange(range) &&
472-
semver.valid(lockfileEntry.version) &&
473-
!semver.satisfies(lockfileEntry.version, range) &&
474-
!getExoticResolver(range) &&
475-
hasVersion
476-
) {
496+
497+
if (this.isLockfileEntryOutdated(lockfileEntry.version, range, hasVersion)) {
477498
this.reporter.warn(this.reporter.lang('incorrectLockfileEntry', req.pattern));
478499
this.removePattern(req.pattern);
479500
this.lockfile.removePattern(req.pattern);
@@ -532,4 +553,26 @@ export default class PackageResolver {
532553
req.resolveToExistingVersion(info);
533554
}
534555
}
556+
557+
resolveToResolution(req: DependencyRequestPattern): ?DependencyRequestPattern {
558+
const {parentNames, pattern} = req;
559+
560+
if (!parentNames) {
561+
return req;
562+
}
563+
564+
const resolution = this.resolutions.find(pattern, parentNames);
565+
566+
if (resolution) {
567+
const resolutionManifest = this.getStrictResolvedPattern(resolution);
568+
invariant(resolutionManifest._reference, 'resolutions should have a resolved reference');
569+
570+
resolutionManifest._reference.patterns.push(pattern);
571+
this.lockfile.removePattern(pattern);
572+
573+
return null;
574+
}
575+
576+
return req;
577+
}
535578
}

src/reporters/lang/en.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,10 @@ const messages = {
117117
'Pattern $0 is trying to unpack in the same destination $1 as pattern $2. This could result in a non deterministic behavior, skipping.',
118118
incorrectLockfileEntry: 'Lockfile has incorrect entry for $0. Ignoring it.',
119119

120+
invalidResolutionName: 'Resolution field $0 does not end with a valid package name and will be ignored',
121+
invalidResolutionVersion: 'Resolution field $0 has an invalid version entry and may be ignored',
122+
incompatibleResolutionVersion: 'Resolution field $0 is incompatible with requested version $1',
123+
120124
yarnOutdated: "Your current version of Yarn is out of date. The latest version is $0 while you're on $1.",
121125
yarnOutdatedInstaller: 'To upgrade, download the latest installer at $0.',
122126
yarnOutdatedCommand: 'To upgrade, run the following command:',

0 commit comments

Comments
 (0)