Skip to content

Commit ce04b97

Browse files
authored
🐛 FIX: Remove disk cache when version not found (#404)
Avoid package sync delay
1 parent dbceaee commit ce04b97

File tree

5 files changed

+93
-69
lines changed

5 files changed

+93
-69
lines changed

.autod.conf.js

Lines changed: 0 additions & 17 deletions
This file was deleted.

LICENSE.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
This software is licensed under the MIT License.
22

3-
Copyright (c) 2016 - present cnpm and other contributors
3+
Copyright (c) 2016-present cnpm and other contributors
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

lib/download/npm.js

Lines changed: 60 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
const debug = require('debug')('npminstall:download:npm');
44
const bytes = require('bytes');
5-
const fs = require('mz/fs');
5+
const { createWriteStream, createReadStream, rmSync } = require('fs');
6+
const fs = require('fs/promises');
67
const path = require('path');
78
const crypto = require('crypto');
89
const tar = require('tar');
@@ -80,6 +81,8 @@ async function resolve(pkg, options) {
8081
let fixScripts;
8182

8283
if (!realPkgVersion) {
84+
// remove disk cache
85+
await removeCacheInfo(pkg.name, options);
8386
throw new Error(`[${pkg.displayName}] Can\'t find package ${pkg.name}@${pkg.rawSpec}`);
8487
}
8588

@@ -113,6 +116,8 @@ async function resolve(pkg, options) {
113116

114117
const realPkg = packageMeta.versions[realPkgVersion];
115118
if (!realPkg) {
119+
// remove disk cache
120+
await removeCacheInfo(pkg.name, options);
116121
throw new Error(`[${pkg.displayName}] Can\'t find package ${pkg.name}\'s version: ${realPkgVersion}`);
117122
}
118123

@@ -131,48 +136,69 @@ async function resolve(pkg, options) {
131136
return realPkg;
132137
}
133138

134-
function getScope(name) {
139+
function _getScope(name) {
135140
if (name[0] === '@') return name.slice(0, name.indexOf('/'));
136141
}
137142

138-
async function getFullPackageMeta(name, globalOptions) {
143+
async function _getCacheInfo(fullname, globalOptions) {
139144
// check name has scope
140145
let registry = globalOptions.registry;
141-
const scope = getScope(name);
146+
const scope = _getScope(fullname);
142147
if (scope) {
143148
registry = config.get(scope + ':registry') || globalOptions.registry;
144149
}
145-
146-
const pkgUrl = utils.formatPackageUrl(registry, name);
150+
const info = {
151+
pkgUrl: utils.formatPackageUrl(registry, fullname),
152+
cacheFile: '',
153+
cache: null,
154+
};
147155
if (!globalOptions.cacheDir) {
148-
const result = await _fetchFullPackageMeta(pkgUrl, globalOptions);
149-
return result.data;
156+
return info;
150157
}
151-
const hash = utility.md5(pkgUrl);
158+
const hash = utility.md5(info.pkgUrl);
152159
const parentDir = path.join(globalOptions.cacheDir, 'manifests', hash[0], hash[1], hash[2]);
153160
// { etag, age, headers, manifests }
154-
const cacheFile = path.join(parentDir, `${hash}.json`);
155-
const exists = await fs.exists(cacheFile);
161+
info.cacheFile = path.join(parentDir, `${hash}.json`);
162+
const exists = await utils.exists(info.cacheFile);
156163
// cache not exists
157164
if (!exists) {
158165
await utils.mkdirp(parentDir);
159-
return await _fetchFullPackageMetaWithCache(pkgUrl, globalOptions, cacheFile);
166+
return info;
160167
}
161168
// cache exists
162-
const cacheContent = await fs.readFile(cacheFile);
163-
let cache;
169+
const cacheContent = await fs.readFile(info.cacheFile);
164170
try {
165-
cache = JSON.parse(cacheContent);
171+
info.cache = JSON.parse(cacheContent);
166172
} catch (_) {
167-
globalOptions.console.warn('[npminstall:download:npm] Ignore invalid cache file %s', cacheFile);
173+
globalOptions.console.warn('[npminstall:download:npm] Ignore invalid cache file %s', info.cacheFile);
174+
}
175+
return info;
176+
}
177+
178+
async function removeCacheInfo(fullname, globalOptions) {
179+
const info = await _getCacheInfo(fullname, globalOptions);
180+
if (info.cache) {
181+
await fs.rm(info.cacheFile, { force: true });
182+
}
183+
}
184+
185+
async function getFullPackageMeta(fullname, globalOptions) {
186+
const info = await _getCacheInfo(fullname, globalOptions);
187+
if (!info.cacheFile) {
188+
const result = await _fetchFullPackageMeta(info.pkgUrl, globalOptions);
189+
return result.data;
190+
}
191+
// cache file not exists
192+
if (!info.cache) {
193+
return await _fetchFullPackageMetaWithCache(info.pkgUrl, globalOptions, info.cacheFile);
168194
}
169195
// check is expired or not
170-
if (cache && cache.expired > Date.now()) {
196+
if (info.cache.expired > Date.now()) {
171197
globalOptions.totalCacheJSONCount += 1;
172-
return cache.manifests;
198+
return info.cache.manifests;
173199
}
174200
// use etag to request
175-
return await _fetchFullPackageMetaWithCache(pkgUrl, globalOptions, cacheFile, cache);
201+
return await _fetchFullPackageMetaWithCache(info.pkgUrl, globalOptions, info.cacheFile, info.cache);
176202
}
177203

178204
async function _fetchFullPackageMetaWithCache(pkgUrl, globalOptions, cacheFile, cache) {
@@ -384,7 +410,7 @@ async function download(pkg, options) {
384410
// ignore https protocol check on: node_modules/node-pre-gyp/lib/util/versioning.js
385411
if (/node-pre-gyp install/.test(pkg.scripts.install)) {
386412
const versioningFile = path.join(ungzipDir, 'node_modules/node-pre-gyp/lib/util/versioning.js');
387-
if (await fs.exists(versioningFile)) {
413+
if (await utils.exists(versioningFile)) {
388414
let content = await fs.readFile(versioningFile, 'utf-8');
389415
content = content.replace('if (protocol === \'http:\') {',
390416
'if (false && protocol === \'http:\') { // hack by npminstall');
@@ -426,7 +452,7 @@ async function download(pkg, options) {
426452
options.console.info('%s download from binary mirror: %j, targetPlatform: %s',
427453
chalk.gray(`${pkg.name}@${pkg.version}`), binaryMirror, targetPlatform);
428454
const downloadFile = path.join(ungzipDir, 'lib/tasks/download.js');
429-
if (await fs.exists(downloadFile)) {
455+
if (await utils.exists(downloadFile)) {
430456
let content = await fs.readFile(downloadFile, 'utf-8');
431457
// return version ? prepend('desktop/' + version) : prepend('desktop');
432458
const afterContent = 'return "' + binaryMirror.host + '/" + version + "/' + targetPlatform + '/cypress.zip"; // hack by npminstall\n';
@@ -446,7 +472,7 @@ async function download(pkg, options) {
446472
if (pkg.name === 'node-pre-gyp') {
447473
// ignore https protocol check on: lib/util/versioning.js
448474
const versioningFile = path.join(ungzipDir, 'lib/util/versioning.js');
449-
if (await fs.exists(versioningFile)) {
475+
if (await utils.exists(versioningFile)) {
450476
let content = await fs.readFile(versioningFile, 'utf-8');
451477
content = content.replace('if (protocol === \'http:\') {',
452478
'if (false && protocol === \'http:\') { // hack by npminstall');
@@ -527,7 +553,7 @@ async function getTarballStream(tarballUrl, pkg, options) {
527553
paths.push(pkg.name);
528554
const parentDir = path.join(...paths);
529555
const tarballFile = path.join(parentDir, `${pkg.version}-${pkg.dist.shasum}.tgz`);
530-
let exists = await fs.exists(tarballFile);
556+
let exists = await utils.exists(tarballFile);
531557
if (!exists) {
532558
// try to remove expired tmp dirs
533559
const dirs = [];
@@ -552,25 +578,25 @@ async function getTarballStream(tarballUrl, pkg, options) {
552578
timeout: options.streamingTimeout || options.timeout,
553579
followRedirect: true,
554580
formatRedirectUrl,
555-
writeStream: fs.createWriteStream(tmpFile),
581+
writeStream: createWriteStream(tmpFile),
556582
}, options);
557583

558584
if (result.status !== 200) {
559585
throw new Error(`Download ${tarballUrl} status: ${result.status} error, should be 200`);
560586
}
561587
// make sure tarball file is not exists again
562-
exists = await fs.exists(tarballFile);
588+
exists = await utils.exists(tarballFile);
563589
if (!exists) {
564590
try {
565591
await fs.rename(tmpFile, tarballFile);
566592
} catch (err) {
567593
if (err.code === 'EPERM') {
568594
// Error: EPERM: operation not permitted, rename
569-
exists = await fs.exists(tarballFile);
595+
exists = await utils.exists(tarballFile);
570596
if (exists) {
571597
// parallel execution case same file exists, ignore rename error
572598
// clean tmpFile
573-
await fs.unlink(tmpFile);
599+
await fs.rm(tmpFile, { force: true });
574600
} else {
575601
// rename error
576602
throw err;
@@ -582,15 +608,15 @@ async function getTarballStream(tarballUrl, pkg, options) {
582608
}
583609
} else {
584610
// clean tmpFile
585-
await fs.unlink(tmpFile);
611+
await fs.rm(tmpFile, { force: true });
586612
}
587613
const stat = await fs.stat(tarballFile);
588614
debug('[%s@%s] saved %s %s => %s',
589615
pkg.name, pkg.version, bytes(stat.size), tarballUrl, tarballFile);
590616
options.totalTarballSize += stat.size;
591617
}
592618

593-
const stream = fs.createReadStream(tarballFile);
619+
const stream = createReadStream(tarballFile);
594620
stream.tarballFile = tarballFile;
595621
stream.tarballUrl = tarballUrl;
596622
return stream;
@@ -752,12 +778,12 @@ function checkShasumAndUngzip(ungzipDir, readstream, pkg, useTarFormat, options)
752778
// make sure readstream will be destroy
753779
destroy(readstream);
754780
err.message += ` (${pkg.name}@${pkg.version})`;
755-
if (readstream.tarballFile && fs.existsSync(readstream.tarballFile)) {
781+
if (readstream.tarballFile && utils.existsSync(readstream.tarballFile)) {
756782
err.message += ` (${readstream.tarballFile})`;
757783
debug('[%s@%s] remove tarball file: %s, because %s',
758784
pkg.name, pkg.version, readstream.tarballFile, err);
759785
// remove tarball cache file
760-
fs.unlinkSync(readstream.tarballFile);
786+
rmSync(readstream.tarballFile, { force: true });
761787
}
762788
return reject(err);
763789
}
@@ -792,8 +818,8 @@ function checkShasumAndUngzip(ungzipDir, readstream, pkg, useTarFormat, options)
792818
});
793819
}
794820

795-
async function replaceHostInFile(pkg, filepath, binaryMirror, options) {
796-
const exists = await fs.exists(filepath);
821+
async function replaceHostInFile(pkg, filepath, binaryMirror, globalOptions) {
822+
const exists = await utils.exists(filepath);
797823
if (!exists) {
798824
return;
799825
}
@@ -827,7 +853,7 @@ async function replaceHostInFile(pkg, filepath, binaryMirror, options) {
827853
}
828854
debug('%s: \n%s', filepath, content);
829855
await fs.writeFile(filepath, content);
830-
options.console.info('%s download from mirrors: %j, changed file: %s',
856+
globalOptions.console.info('%s download from mirrors: %j, changed file: %s',
831857
chalk.gray(`${pkg.name}@${pkg.version}`),
832858
replaceHostMap,
833859
filepath);

lib/utils.js

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
'use strict';
22

33
const debug = require('debug')('npminstall:utils');
4-
const fs = require('mz/fs');
4+
const fs = require('fs/promises');
5+
const { accessSync } = require('fs');
56
const path = require('path');
67
const cp = require('child_process');
78
const urlparse = require('url').parse;
@@ -21,6 +22,24 @@ const url = require('url');
2122
const config = require('./config');
2223
const get = require('./get');
2324

25+
exports.exists = async filepath => {
26+
try {
27+
await fs.access(filepath);
28+
return true;
29+
} catch {
30+
return false;
31+
}
32+
};
33+
34+
exports.existsSync = filepath => {
35+
try {
36+
accessSync(filepath);
37+
return true;
38+
} catch {
39+
return false;
40+
}
41+
};
42+
2443
exports.hasOwnProp = (target, key) => target.hasOwnProperty(key);
2544
/**
2645
*
@@ -44,7 +63,7 @@ exports.pruneJSON = async (filepath, depName) => {
4463
};
4564

4665
exports.readJSON = async filepath => {
47-
if (!(await fs.exists(filepath))) {
66+
if (!(await exports.exists(filepath))) {
4867
return {};
4968
}
5069
const content = await fs.readFile(filepath, 'utf8');
@@ -118,7 +137,7 @@ exports.forceSymlink = async (src, dest, type) => {
118137

119138
const destDir = path.dirname(dest);
120139
// check if destDir is not exist
121-
if (!(await fs.exists(destDir))) {
140+
if (!(await exports.exists(destDir))) {
122141
await mkdirp(destDir);
123142
}
124143

@@ -354,7 +373,7 @@ exports.copyInstall = async (src, options) => {
354373
// 4. if already installed, return with exists = true
355374
// 5. if not installed, copy and return with exists = false
356375
const pkgpath = path.join(src, 'package.json');
357-
if (!(await fs.exists(pkgpath))) {
376+
if (!(await exports.exists(pkgpath))) {
358377
throw new Error(`package.json missed(${pkgpath})`);
359378
}
360379
const realPkg = await exports.readPackageJSON(src);

package.json

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@
2020
"test-cov": "egg-bin cov -t 2000000",
2121
"test-local": "npm_china=true local=true egg-bin test -t 2000000",
2222
"lint": "eslint . --fix",
23-
"ci": "npm run lint && npm run test-cov",
24-
"autod": "autod"
23+
"ci": "npm run lint && npm run test-cov"
2524
},
2625
"dependencies": {
2726
"agentkeepalive": "^4.0.2",
@@ -57,21 +56,18 @@
5756
"uuid": "^3.3.2"
5857
},
5958
"devDependencies": {
60-
"autod": "3",
61-
"coffee": "^5.2.1",
62-
"egg-bin": "^4.13.1",
63-
"eslint": "7",
64-
"eslint-config-egg": "7",
65-
"eslint-plugin-jsdoc": "^4.1.1",
66-
"git-contributor": "^1.0.10",
67-
"http-proxy": "^1.18.1",
68-
"mm": "^2.5.0"
59+
"coffee": "5",
60+
"egg-bin": "5",
61+
"eslint": "8",
62+
"eslint-config-egg": "12",
63+
"git-contributor": "1",
64+
"http-proxy": "1",
65+
"mm": "3"
6966
},
7067
"homepage": "https://github.com/cnpm/npminstall",
7168
"repository": {
7269
"type": "git",
73-
"url": "git://github.com/cnpm/npminstall.git",
74-
"web": "https://github.com/cnpm/npminstall"
70+
"url": "git://github.com/cnpm/npminstall.git"
7571
},
7672
"bugs": {
7773
"url": "https://github.com/cnpm/npminstall/issues"

0 commit comments

Comments
 (0)