Skip to content

Commit 0529260

Browse files
author
Kaylie Kwon
committed
WIP
1 parent 5e19f03 commit 0529260

File tree

13 files changed

+162
-37
lines changed

13 files changed

+162
-37
lines changed

__tests__/commands/install/resolutions.js

+4-7
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

+1-1
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
}
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

+5-3
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

+1
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

+1-1
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

+6-3
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();
174+
this._resolutions = new Resolutions(config);
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.
@@ -231,6 +233,7 @@ export class Install {
231233
const projectManifestJson = await this.config.readJson(loc);
232234
await normalizeManifest(projectManifestJson, this.config.lockfileFolder, this.config, true);
233235

236+
this._resolutions.init(projectManifestJson.resolutions);
234237
Object.assign(this.resolutions, projectManifestJson.resolutions);
235238
Object.assign(manifest, projectManifestJson);
236239

src/package-request.js

+7-17
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

+26-3
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

@@ -358,7 +362,8 @@ export default class PackageResolver {
358362
*/
359363

360364
getResolvedPattern(pattern: string): ?Manifest {
361-
return this.patterns[pattern];
365+
const resolutionPattern = this.resolutions.getResolutionByPattern(pattern) || pattern;
366+
return this.patterns[resolutionPattern];
362367
}
363368

364369
/**
@@ -450,8 +455,10 @@ export default class PackageResolver {
450455
* TODO description
451456
*/
452457

453-
async find(req: DependencyRequestPattern): Promise<void> {
458+
async find(initialReq: DependencyRequestPattern): Promise<void> {
459+
const req = this.resolveToResolution(initialReq);
454460
const fetchKey = `${req.registry}:${req.pattern}`;
461+
455462
if (this.fetchingPatterns[fetchKey]) {
456463
return;
457464
} else {
@@ -532,4 +539,20 @@ export default class PackageResolver {
532539
req.resolveToExistingVersion(info);
533540
}
534541
}
542+
543+
resolveToResolution(req: DependencyRequestPattern): ?string {
544+
const {parentNames, pattern} = req;
545+
546+
if (!parentNames) {
547+
return req;
548+
}
549+
550+
const resolution = this.resolutions.find(pattern, parentNames);
551+
552+
if (resolution) {
553+
req.pattern = resolution;
554+
}
555+
556+
return req;
557+
}
535558
}

src/reporters/lang/en.js

+4
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:',

src/resolutions.js

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/* @flow */
2+
import path from 'path';
3+
import semver from 'semver';
4+
import minimatch from 'minimatch';
5+
import map from './util/map.js';
6+
import type Config from './config.js';
7+
import type {Reporter} from './reporters/index.js';
8+
import PackageRequest from './package-request';
9+
10+
const DIRECTORY_SEPARATOR = '/';
11+
12+
export type Resolution = {
13+
name: string,
14+
version: string,
15+
pattern: string,
16+
};
17+
18+
export type ResolutionMap = {
19+
[string]: Array<Resolution>,
20+
};
21+
22+
export type ResolvedPatternsMap = {
23+
[string]: string,
24+
};
25+
26+
export default class Resolutions {
27+
constructor(config: Config) {
28+
this._resolutions = map();
29+
this.resolutionsByPackage = map();
30+
this.resolvedPatterns = map();
31+
this.config = config;
32+
this.reporter = config.reporter;
33+
}
34+
35+
_resolutions: {[packageName: string]: string};
36+
resolutionsByPackage: ResolutionMap;
37+
resolvedPatterns: ResolvedPatternsMap;
38+
config: Config;
39+
reporter: Reporter;
40+
41+
init(resolutions) {
42+
this._resolutions = {...this._resolutions, ...resolutions};
43+
this.resolvedPatterns = map();
44+
45+
Object.keys(this._resolutions).map(pattern => {
46+
const info = this.parsePatternInfo(pattern, this._resolutions[pattern]);
47+
48+
if (info) {
49+
const resolution = this.resolutionsByPackage[info.name] || [];
50+
this.resolutionsByPackage[info.name] = [...resolution, info];
51+
}
52+
});
53+
}
54+
55+
getResolutionByPattern(pattern: string): ?string {
56+
return this.resolvedPatterns[pattern];
57+
}
58+
59+
parsePatternInfo(pattern: string, version: string): ?Object {
60+
const directories = pattern.split(DIRECTORY_SEPARATOR);
61+
const name = directories.pop();
62+
63+
if (!name) {
64+
this.reporter.warn(this.reporter.lang('invalidResolutionName', pattern));
65+
return null;
66+
}
67+
68+
return {
69+
name,
70+
version,
71+
pattern,
72+
};
73+
}
74+
75+
find(reqPattern: string, parentNames: Array<string>): ?string {
76+
const {name, version} = PackageRequest.normalizePattern(reqPattern);
77+
const resolutions = this.resolutionsByPackage[name];
78+
79+
if (!resolutions) {
80+
return null;
81+
}
82+
83+
const resolvedPath = path.join(...parentNames, name);
84+
const resolution = resolutions.find(({pattern}) => minimatch(resolvedPath, pattern));
85+
const resolvedPattern = resolution && `${name}@${resolution.version}`;
86+
87+
if (resolvedPattern) {
88+
if (!semver.valid(resolvedPattern)) {
89+
this.reporter.warn(this.reporter.lang('invalidResolutionVersion', resolvedPattern));
90+
}
91+
92+
if (!semver.satisfies(resolution.version, version)) {
93+
this.reporter.warn(this.reporter.lang('incompatibleResolutionVersion', resolvedPattern, reqPattern));
94+
}
95+
96+
this.resolvedPatterns[reqPattern] = resolvedPattern;
97+
}
98+
99+
return resolvedPattern;
100+
}
101+
}

src/resolvers/registries/npm-resolver.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ export default class NpmResolver extends RegistryResolver {
178178
const {deprecated, dist} = info;
179179
if (typeof deprecated === 'string') {
180180
let human = `${info.name}@${info.version}`;
181-
const parentNames = this.request.getParentNames();
181+
const parentNames = this.request.parentNames;
182182
if (parentNames.length) {
183183
human = parentNames.concat(human).join(' > ');
184184
}

0 commit comments

Comments
 (0)