Skip to content

Commit 7515772

Browse files
authored
Fix: Don't do range collapsing when using a frozen lockfile (#3604)
**Summary** Fixes #3313. Yarn automatically optimizes less-than-ideal `yarn.lock` files, usually from older versions. That said when run with the `--frozen-lockfile` argument, it should neither touch the lockfile nor throw an exception if the lockfile satisfies all the needs, even if it can be optimized. **Test plan** Existing unit tests plus a new unit test which fails without the fix applied.
1 parent 472a931 commit 7515772

File tree

13 files changed

+80
-17
lines changed

13 files changed

+80
-17
lines changed

__tests__/commands/install/integration.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ test('changes the cache directory when bumping the cache version', async () => {
144144
const lockfile = await Lockfile.fromDirectory(config.cwd);
145145

146146
const resolver = new PackageResolver(config, lockfile);
147-
await resolver.init([{pattern: 'is-array', registry: 'npm'}]);
147+
await resolver.init([{pattern: 'is-array', registry: 'npm'}], {});
148148

149149
const ref = resolver.getManifests()[0]._reference;
150150
const cachePath = config.generateHardModulePath(ref, true);

__tests__/commands/install/lockfiles.js

+14
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,20 @@ test.concurrent("throws an error if existing lockfile isn't satisfied with --fro
7171
expect(thrown).toEqual(true);
7272
});
7373

74+
test.concurrent(
75+
"doesn't write new lockfile if existing one satisfied but not fully optimized with --frozen-lockfile",
76+
(): Promise<void> => {
77+
return runInstall(
78+
{frozenLockfile: true},
79+
'install-should-not-write-lockfile-if-not-optimized-and-frozen',
80+
async (config): Promise<void> => {
81+
const lockfile = await fs.readFile(path.join(config.cwd, 'yarn.lock'));
82+
expect(lockfile.indexOf('[email protected]:')).toBeGreaterThanOrEqual(0);
83+
},
84+
);
85+
},
86+
);
87+
7488
test.concurrent('install transitive optional dependency from lockfile', (): Promise<void> => {
7589
return runInstall({}, 'install-optional-dep-from-lockfile', (config, reporter, install) => {
7690
expect(install && install.resolver && install.resolver.patterns['fsevents@^1.0.0']).toBeTruthy();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"dependencies": {
3+
"left-pad": "1.1.3",
4+
"node.date-time": "1.2.2"
5+
}
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2+
# yarn lockfile v1
3+
4+
5+
6+
version "1.1.3"
7+
resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.1.3.tgz#612f61c033f3a9e08e939f1caebeea41b6f3199a"
8+
9+
left-pad@^1.1.0:
10+
version "1.1.1"
11+
resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.1.1.tgz#ca566bbdd84b90cc5969ac1726fda51f9d936a3c"
12+
13+
14+
version "1.2.2"
15+
resolved "https://registry.yarnpkg.com/node.date-time/-/node.date-time-1.2.2.tgz#2fc553b0520f1c75625b33fa5a1835c637ad9856"
16+
dependencies:
17+
left-pad "^1.1.0"

__tests__/package-resolver.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ function addTest(pattern, registry = 'npm', init: ?(cacheFolder: string) => Prom
4242
}
4343

4444
const resolver = new PackageResolver(config, lockfile);
45-
await resolver.init([{pattern, registry}]);
45+
await resolver.init([{pattern, registry}], {});
4646

4747
const ref = resolver.getManifests()[0]._reference;
4848
const cachePath = config.generateHardModulePath(ref, true);

src/cli/commands/import.js

+8-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/* @flow */
22

3+
import type {ResolverOptions} from '../../package-resolver.js';
34
import type {Manifest, DependencyRequestPattern, DependencyRequestPatterns} from '../../types.js';
45
import type {Reporter} from '../../reporters/index.js';
56
import type Config from '../../config.js';
@@ -227,7 +228,7 @@ class ImportPackageResolver extends PackageResolver {
227228
this.activity.tick(req.pattern);
228229
}
229230
const request = new ImportPackageRequest(req, this);
230-
await request.find(false);
231+
await request.find({fresh: false});
231232
}
232233

233234
async findAll(deps: DependencyRequestPatterns): Promise<void> {
@@ -254,8 +255,11 @@ class ImportPackageResolver extends PackageResolver {
254255
}
255256
}
256257

257-
async init(deps: DependencyRequestPatterns, isFlat: boolean): Promise<void> {
258-
this.flat = isFlat;
258+
async init(
259+
deps: DependencyRequestPatterns,
260+
{isFlat, isFrozen, workspaceLayout}: ResolverOptions = {isFlat: false, isFrozen: false, workspaceLayout: undefined},
261+
): Promise<void> {
262+
this.flat = Boolean(isFlat);
259263
const activity = (this.activity = this.reporter.activity());
260264
await this.findAll(deps);
261265
this.resetOptional();
@@ -280,7 +284,7 @@ export class Import extends Install {
280284
if (manifest.name && this.resolver instanceof ImportPackageResolver) {
281285
this.resolver.rootName = manifest.name;
282286
}
283-
await this.resolver.init(requests, this.flags.flat);
287+
await this.resolver.init(requests, {isFlat: this.flags.flat, isFrozen: this.flags.frozenLockfile});
284288
const manifests: Array<Manifest> = await fetcher.fetch(this.resolver.getManifests(), this.config);
285289
this.resolver.updateManifests(manifests);
286290
await compatibility.check(this.resolver.getManifests(), this.config, this.flags.ignoreEngines);

src/cli/commands/install.js

+11-2
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ type Flags = {
5555
flat: boolean,
5656
lockfile: boolean,
5757
pureLockfile: boolean,
58+
frozenLockfile: boolean,
5859
skipIntegrityCheck: boolean,
5960
checkFiles: boolean,
6061

@@ -422,7 +423,11 @@ export class Install {
422423

423424
steps.push(async (curr: number, total: number) => {
424425
this.reporter.step(curr, total, this.reporter.lang('resolvingPackages'), emoji.get('mag'));
425-
await this.resolver.init(this.prepareRequests(depRequests), this.flags.flat, workspaceLayout);
426+
await this.resolver.init(this.prepareRequests(depRequests), {
427+
isFlat: this.flags.flat,
428+
isFrozen: this.flags.frozenLockfile,
429+
workspaceLayout,
430+
});
426431
topLevelPatterns = this.preparePatterns(rawPatterns);
427432
flattenedTopLevelPatterns = await this.flatten(topLevelPatterns);
428433
return {bailout: await this.bailout(topLevelPatterns, workspaceLayout)};
@@ -693,7 +698,11 @@ export class Install {
693698
const request = await this.fetchRequestFromCwd([], ignoreUnusedPatterns);
694699
const {requests: depRequests, patterns: rawPatterns, ignorePatterns, workspaceLayout} = request;
695700

696-
await this.resolver.init(depRequests, this.flags.flat, workspaceLayout);
701+
await this.resolver.init(depRequests, {
702+
isFlat: this.flags.flat,
703+
isFrozen: this.flags.frozenLockfile,
704+
workspaceLayout,
705+
});
697706
await this.flatten(rawPatterns);
698707
this.markIgnored(ignorePatterns);
699708

src/cli/commands/list.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg
175175
const lockfile = await Lockfile.fromDirectory(config.lockfileFolder, reporter);
176176
const install = new Install(flags, config, reporter, lockfile);
177177
const {requests: depRequests, patterns} = await install.fetchRequestFromCwd();
178-
await install.resolver.init(depRequests, install.flags.flat);
178+
await install.resolver.init(depRequests, {isFlat: install.flags.flat, isFrozen: install.flags.frozenLockfile});
179179

180180
const opts: ListOptions = {
181181
reqDepth: getReqDepth(flags.depth),

src/cli/commands/upgrade.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,7 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg
5353
throw new MessageError(reporter.lang('scopeNotValid'));
5454
}
5555
} else if (flags.latest && args.length === 0) {
56-
addArgs = Object.keys(allDependencies)
57-
.map(dependency => getDependency(allDependencies, dependency));
56+
addArgs = Object.keys(allDependencies).map(dependency => getDependency(allDependencies, dependency));
5857
} else {
5958
addArgs = args.map(dependency => {
6059
return getDependency(allDependencies, dependency);

src/cli/commands/why.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg
131131
const lockfile = await Lockfile.fromDirectory(config.lockfileFolder, reporter);
132132
const install = new Install(flags, config, reporter, lockfile);
133133
const {requests: depRequests, patterns} = await install.fetchRequestFromCwd();
134-
await install.resolver.init(depRequests, install.flags.flat);
134+
await install.resolver.init(depRequests, {isFlat: install.flags.flat, isFrozen: install.flags.frozenLockfile});
135135
const hoisted = await install.linker.getFlatHoistedTree(patterns);
136136

137137
// finding

src/package-request.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ export default class PackageRequest {
254254
/**
255255
* TODO description
256256
*/
257-
async find(fresh: boolean): Promise<void> {
257+
async find({fresh, frozen}: {fresh: boolean, frozen?: boolean}): Promise<void> {
258258
// find version info for this package pattern
259259
const info: ?Manifest = await this.findVersionInfo();
260260

@@ -270,7 +270,9 @@ export default class PackageRequest {
270270
// check if while we were resolving this dep we've already resolved one that satisfies
271271
// the same range
272272
const {range, name} = PackageRequest.normalizePattern(this.pattern);
273-
const resolved: ?Manifest = this.resolver.getHighestRangeVersionMatch(name, range);
273+
const resolved: ?Manifest = frozen
274+
? this.resolver.getExactVersionMatch(name, range)
275+
: this.resolver.getHighestRangeVersionMatch(name, range);
274276
if (resolved) {
275277
this.resolver.reportPackageWithExistingVersion(this, info);
276278
return;

src/package-resolver.js

+15-3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ import WorkspaceLayout from './workspace-layout.js';
1515
const invariant = require('invariant');
1616
const semver = require('semver');
1717

18+
export type ResolverOptions = {|
19+
isFlat?: boolean,
20+
isFrozen?: boolean,
21+
workspaceLayout?: WorkspaceLayout,
22+
|};
23+
1824
export default class PackageResolver {
1925
constructor(config: Config, lockfile: Lockfile) {
2026
this.patternsByPackage = map();
@@ -33,6 +39,8 @@ export default class PackageResolver {
3339
// whether the dependency graph will be flattened
3440
flat: boolean;
3541

42+
frozen: boolean;
43+
3644
workspaceLayout: ?WorkspaceLayout;
3745

3846
// list of registries that have been used in this resolution
@@ -448,15 +456,19 @@ export default class PackageResolver {
448456
}
449457

450458
const request = new PackageRequest(req, this);
451-
await request.find(fresh);
459+
await request.find({fresh, frozen: this.frozen});
452460
}
453461

454462
/**
455463
* TODO description
456464
*/
457465

458-
async init(deps: DependencyRequestPatterns, isFlat: boolean, workspaceLayout?: WorkspaceLayout): Promise<void> {
459-
this.flat = isFlat;
466+
async init(
467+
deps: DependencyRequestPatterns,
468+
{isFlat, isFrozen, workspaceLayout}: ResolverOptions = {isFlat: false, isFrozen: false, workspaceLayout: undefined},
469+
): Promise<void> {
470+
this.flat = Boolean(isFlat);
471+
this.frozen = Boolean(isFrozen);
460472
this.workspaceLayout = workspaceLayout;
461473
const activity = (this.activity = this.reporter.activity());
462474
await Promise.all(deps.map((req): Promise<void> => this.find(req)));

0 commit comments

Comments
 (0)