Skip to content

Commit 8918b6c

Browse files
imsnifBYK
authored andcommitted
feat(install): Add --update-checksums to cli install (#4860)
**Summary** Fixes #4817. When the `--update-checksums` flag is set, yarn would know to ignore a checksum mismatch between `yarn.lock` and the repository, and instead update the yarn.lock file with the proper checksum(s). **Test plan** Added new tests. To manually check this: 1. Change one or more of the package checksums in `yarn.lock` 2. Delete node_modules (optionally also run `yarn cache clean`) 3. Run `yarn` => checksum mismatch error will be received. 4. Run `yarn --update-checksums` => will install successfully and fix the damaged checksums in `yarn.lock`
1 parent cb6bf44 commit 8918b6c

File tree

13 files changed

+101
-8
lines changed

13 files changed

+101
-8
lines changed

__tests__/commands/_helpers.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ export function makeConfigFromDirectory(cwd: string, reporter: Reporter, flags:
8888
linkFolder: flags.linkFolder || path.join(cwd, '.yarn-link'),
8989
prefix: flags.prefix,
9090
production: flags.production,
91+
updateChecksums: !!flags.updateChecksums,
9192
},
9293
reporter,
9394
);

__tests__/commands/install/integration.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -732,6 +732,21 @@ test.concurrent('install should add missing deps to yarn and mirror (PR import s
732732
});
733733
});
734734

735+
test.concurrent('install should update checksums in yarn.lock (--update-checksums)', (): Promise<void> => {
736+
const packageRealHash = '5faad9c2c07f60dd76770f71cf025b62a63cfd4e';
737+
const packageCacheName = `npm-abab-1.0.4-${packageRealHash}`;
738+
return runInstall({updateChecksums: true}, 'install-update-checksums', async config => {
739+
const lockFileContent = await fs.readFile(path.join(config.cwd, 'yarn.lock'));
740+
const lockFileLines = explodeLockfile(lockFileContent);
741+
const packageHashInLockfile = lockFileLines[2].replace(/(^.*#)|("$)/g, '');
742+
const installedPackageJson = path.resolve(config.cwd, 'node_modules', 'abab', 'package.json');
743+
const cachePackageJson = path.resolve(config.cwd, '.yarn-cache/v1/', packageCacheName, 'package.json');
744+
expect(packageHashInLockfile).toEqual(packageRealHash);
745+
expect(await fs.exists(installedPackageJson)).toBe(true);
746+
expect(await fs.exists(cachePackageJson)).toBe(true);
747+
});
748+
});
749+
735750
test.concurrent('install should update a dependency to yarn and mirror (PR import scenario 2)', (): Promise<void> => {
736751
// [email protected] is gets updated to [email protected] via
737752
// a change in package.json,

__tests__/fetchers.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,28 @@ test('TarballFetcher.fetch throws on invalid hash', async () => {
171171
expect(readdirSync(path.join(offlineMirrorDir))).toEqual([]);
172172
});
173173

174+
test('TarballFetcher.fetch fixes hash if updateChecksums flag is true', async () => {
175+
const wrongHash = 'foo';
176+
const dir = await mkdir(`tarball-fetcher-${wrongHash}`);
177+
const config = await Config.create({}, new Reporter());
178+
config.updateChecksums = true;
179+
const url = 'https://github.com/sindresorhus/beeper/archive/master.tar.gz';
180+
const fetcher = new TarballFetcher(
181+
dir,
182+
{
183+
type: 'tarball',
184+
hash: wrongHash,
185+
reference: url,
186+
registry: 'npm',
187+
},
188+
config,
189+
);
190+
await fetcher.fetch();
191+
const dirWithProperHash = dir.replace(wrongHash, fetcher.hash);
192+
const name = (await fs.readJson(path.join(dirWithProperHash, 'package.json'))).name;
193+
expect(name).toBe('beeper');
194+
});
195+
174196
test('TarballFetcher.fetch supports local ungzipped tarball', async () => {
175197
const dir = await mkdir('tarball-fetcher');
176198
const fetcher = new LocalTarballFetcher(
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "install-update-checksums",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1"
8+
},
9+
"author": "",
10+
"license": "ISC",
11+
"dependencies": {
12+
"abab": "^1.0.4",
13+
"leftpad": "^0.0.1"
14+
}
15+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2+
# yarn lockfile v1
3+
4+
5+
abab@^1.0.4:
6+
version "1.0.4"
7+
resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#foo"
8+
9+
leftpad@^0.0.1:
10+
version "0.0.1"
11+
resolved "https://registry.yarnpkg.com/leftpad/-/leftpad-0.0.1.tgz#86b1a4de4face180ac545a83f1503523d8fed115"

src/cli/commands/install.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ function normalizeFlags(config: Config, rawFlags: Object): Flags {
133133
flat: !!rawFlags.flat,
134134
lockfile: rawFlags.lockfile !== false,
135135
pureLockfile: !!rawFlags.pureLockfile,
136+
updateChecksums: !!rawFlags.updateChecksums,
136137
skipIntegrityCheck: !!rawFlags.skipIntegrityCheck,
137138
frozenLockfile: !!rawFlags.frozenLockfile,
138139
linkDuplicates: !!rawFlags.linkDuplicates,

src/cli/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ export function main({
8484
commander.option('--no-lockfile', "don't read or generate a lockfile");
8585
commander.option('--pure-lockfile', "don't generate a lockfile");
8686
commander.option('--frozen-lockfile', "don't generate a lockfile and fail if an update is needed");
87+
commander.option('--update-checksums', 'update package checksums from current repository');
8788
commander.option('--link-duplicates', 'create hardlinks to the repeated modules in node_modules');
8889
commander.option('--link-folder <path>', 'specify a custom folder to store global links');
8990
commander.option('--global-folder <path>', 'specify a custom folder to store global packages');
@@ -488,6 +489,7 @@ export function main({
488489
networkTimeout: commander.networkTimeout,
489490
nonInteractive: commander.nonInteractive,
490491
scriptsPrependNodePath: commander.scriptsPrependNodePath,
492+
updateChecksums: commander.updateChecksums,
491493
})
492494
.then(() => {
493495
// lockfile check must happen after config.init sets lockfileFolder

src/config.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ export type ConfigOptions = {
5757

5858
commandName?: ?string,
5959
registry?: ?string,
60+
61+
updateChecksums?: boolean,
6062
};
6163

6264
type PackageMetadata = {
@@ -102,6 +104,7 @@ export default class Config {
102104
linkFileDependencies: boolean;
103105
ignorePlatform: boolean;
104106
binLinks: boolean;
107+
updateChecksums: boolean;
105108

106109
//
107110
linkedModules: Array<string>;
@@ -364,6 +367,7 @@ export default class Config {
364367
this.linkFolder = opts.linkFolder || constants.LINK_REGISTRY_DIRECTORY;
365368
this.offline = !!opts.offline;
366369
this.binLinks = !!opts.binLinks;
370+
this.updateChecksums = !!opts.updateChecksums;
367371

368372
this.ignorePlatform = !!opts.ignorePlatform;
369373
this.ignoreScripts = !!opts.ignoreScripts;

src/fetchers/base-fetcher.js

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,28 +43,27 @@ export default class BaseFetcher {
4343
}
4444

4545
fetch(defaultManifest: ?Object): Promise<FetchedMetadata> {
46-
const {dest} = this;
47-
return fs.lockQueue.push(dest, async (): Promise<FetchedMetadata> => {
48-
await fs.mkdirp(dest);
46+
return fs.lockQueue.push(this.dest, async (): Promise<FetchedMetadata> => {
47+
await fs.mkdirp(this.dest);
4948

5049
// fetch package and get the hash
5150
const {hash} = await this._fetch();
5251

5352
const pkg = await (async () => {
5453
// load the new normalized manifest
5554
try {
56-
return await this.config.readManifest(dest, this.registry);
55+
return await this.config.readManifest(this.dest, this.registry);
5756
} catch (e) {
5857
if (e.code === 'ENOENT' && defaultManifest) {
59-
return normalizeManifest(defaultManifest, dest, this.config, false);
58+
return normalizeManifest(defaultManifest, this.dest, this.config, false);
6059
} else {
6160
throw e;
6261
}
6362
}
6463
})();
6564

6665
await fs.writeFile(
67-
path.join(dest, constants.METADATA_FILENAME),
66+
path.join(this.dest, constants.METADATA_FILENAME),
6867
JSON.stringify(
6968
{
7069
manifest: pkg,
@@ -80,7 +79,7 @@ export default class BaseFetcher {
8079

8180
return {
8281
hash,
83-
dest,
82+
dest: this.dest,
8483
package: pkg,
8584
cached: false,
8685
};

src/fetchers/tarball-fetcher.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,25 @@ export default class TarballFetcher extends BaseFetcher {
7878
error.message = `${error.message}${tarballPath ? ` (${tarballPath})` : ''}`;
7979
reject(error);
8080
})
81-
.on('finish', () => {
81+
.on('finish', async () => {
8282
const expectHash = this.hash;
8383
const actualHash = validateStream.getHash();
8484

8585
if (!expectHash || expectHash === actualHash) {
8686
resolve({
8787
hash: actualHash,
8888
});
89+
} else if (this.config.updateChecksums) {
90+
// checksums differ and should be updated
91+
// update hash, destination and cached package
92+
const destUpdatedHash = this.dest.replace(this.hash || '', actualHash);
93+
await fsUtil.unlink(destUpdatedHash);
94+
await fsUtil.rename(this.dest, destUpdatedHash);
95+
this.dest = this.dest.replace(this.hash || '', actualHash);
96+
this.hash = actualHash;
97+
resolve({
98+
hash: actualHash,
99+
});
89100
} else {
90101
reject(
91102
new SecurityError(

src/package-fetcher.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,18 @@ export function fetch(pkgs: Array<Manifest>, config: Config): Promise<Array<Mani
108108
// update with new remote
109109
// but only if there was a hash previously as the tarball fetcher does not provide a hash.
110110
if (ref.remote.hash) {
111+
// if the checksum was updated, also update resolved and cache
112+
if (ref.remote.hash !== res.hash && config.updateChecksums) {
113+
const oldHash = ref.remote.hash;
114+
if (ref.remote.resolved) {
115+
ref.remote.resolved = ref.remote.resolved.replace(oldHash, res.hash);
116+
}
117+
ref.config.cache = Object.keys(ref.config.cache).reduce((cache, entry) => {
118+
const entryWithNewHash = entry.replace(oldHash, res.hash);
119+
cache[entryWithNewHash] = ref.config.cache[entry];
120+
return cache;
121+
}, {});
122+
}
111123
ref.remote.hash = res.hash;
112124
}
113125
}

0 commit comments

Comments
 (0)