内容简介:一次在安装项目依赖的时候,终端报了下面这个错,导致依赖安装失败。通过报错信息可以看出是接下来就是一通操作:google搜、github issue搜、换成npm安装、切换npm源、切换node版本、安装别的版本
一次在安装项目依赖的时候,终端报了下面这个错,导致依赖安装失败。
通过报错信息可以看出是 @sentry/cli
这个包的原因,因为项目中并没有直接依赖这个包,为了排除包之间的影响,就新建了一个文件夹,单独安装这个包,发现还是报一样的错。然后就让同事安装下这个包试一下,发现一切正常,并没有报错。
接下来就是一通操作:google搜、github issue搜、换成npm安装、切换npm源、切换node版本、安装别的版本 @sentry/cli
、清除yarn和npm的缓存、重启电脑。。。然而发现并没有什么卵用。。。
看来事情并没有那么简单
再回过头来看报错信息,可以发现是在执行 node scripts/install.js
时出现的错误,那就把代码拉下来本地跑一下看看咯。说干就干,把 @sentry/cli
clone到本地之后,先安装下依赖,然后执行 node scripts/install.js
发现如下报错:
发现实际上是在执行 /Users/sliwey/githome/sentry-cli/sentry-cli --version
命令时发生的错误,根据上面的路径发现在项目根目录下多了一个叫 sentry-cli
的可执行文件。
所以应该是这个文件有问题,那么这个文件是哪里来的呢,看一下 scripts/install.js
的代码,会发现其实就做了一件事:
downloadBinary() .then(() => checkVersion()) .then(() => process.exit(0)) .catch(e => { console.error(e.toString()); process.exit(1); }); 复制代码
就是下载个可执行的文件,然后检查下版本号。 checkVersion
先按下不表,不是重点,就只是判断下版本号,来看 downloadBinary
(我简化了一下代码,加了点注释,具体代码可查看 github.com/getsentry/s… ):
function downloadBinary() { const arch = os.arch(); const platform = os.platform(); const outputPath = helper.getPath(); // 根据不同系统获取对应的下载链接 const downloadUrl = getDownloadUrl(platform, arch); // 根据下载链接生成缓存路径 const cachedPath = getCachedPath(downloadUrl); // 缓存命中,就把文件复制到当前路径下 if (fs.existsSync(cachedPath)) { copyFileSync(cachedPath, outputPath); return Promise.resolve(); } // 缓存未命中,就下载,并把文件写入缓存 return fetch(downloadUrl, { redirect: 'follow', agent }).then(response => { const tempPath = getTempFile(cachedPath); mkdirp.sync(path.dirname(tempPath)); return new Promise((resolve, reject) => { response.body .pipe(fs.createWriteStream(tempPath, { mode: '0755' })) }).then(() => { copyFileSync(tempPath, cachedPath); copyFileSync(tempPath, outputPath); fs.unlinkSync(tempPath); }); }); } 复制代码
根据刚才本地的执行情况来看,并没有进行下载,可知那个可执行文件是从缓存中拿的,那就打个断点看一下缓存路径:
根据得到的路径,删除对应文件,然后重新安装,everything is ok~
下面的才是重点
虽然问题解决了,但是回想了一下之前的一通操作,其中是有做过缓存清除的,包括yarn和npm,当时的做法是通过下面两个命令做的:
yarn cache clean npm cache clean --force 复制代码
根据上面得到的缓存路径,可以知道 sentry-cli
缓存在 ~/.npm
文件夹下,所以跟yarn应该没关系,先排除掉。然后来看npm,发现通过 npm cache clean --force
来清除缓存,并没有清掉 ~/.npm
文件夹下的文件,那么这个命令清的是哪里呢?先看下文档怎么说:npm-cache
为了阅读方便,我截了几个图:
乍一看貌似没什么毛病,检查了一下自己的cache配置,也没有发现什么异常:
那么到底是哪里的问题呢,看来只能看下源码了,目标很直接,找到npm中跟cache相关的代码,然后直接看clean方法的实现(具体代码可以看 lib/cache.js):
function clean (args) { if (!args) args = [] if (args.length) { return BB.reject(new Error('npm cache clear does not accept arguments')) } // 重点在这 // npm.cache就是 ~/.npm // 所以cachePath的值应该是 ~/.npm/_cacache const cachePath = path.join(npm.cache, '_cacache') if (!npm.config.get('force')) { return BB.reject(new Error("As of npm@5, the npm cache self-heals from corruption issues and data extracted from the cache is guaranteed to be valid. If you want to make sure everything is consistent, use 'npm cache verify' instead. On the other hand, if you're debugging an issue with the installer, you can use `npm install --cache /tmp/empty-cache` to use a temporary cache instead of nuking the actual one.\n\nIf you're sure you want to delete the entire cache, rerun this command with --force.")) } // TODO - remove specific packages or package versions return rm(cachePath) } 复制代码
看到这就很明白了, npm cache clean --force
清的是 ~/.npm/_cacache
文件夹中的数据。
转念一想,这一点在文档中不应该不提啊,再回去看一下文档,发现漏看了一块内容。。。
内容如下:
简单来说在 npm@5
之后,npm把缓存数据放在配置文件中 cache
字段配置的路径下面的 _cacache
文件夹中。结合上面两段文档的内容,可得出:
- 配置文件中的
cache
字段配置的是根目录 - 缓存数据放在根目录中的
_cacache
文件夹中 -
clean
命令清除的是_cacache
文件夹
npm缓存到底存了什么
打开 _cacache
文件夹,发现里面并不是像 node_modules
里面一样一个个的包,而是这样的:
打开可以发现 content-v2
里面基本都是一些二进制文件,把二进制文件的扩展名改为 .tgz
再解压之后,会发现就是在我们熟知的npm包。 index-v5
里面是一些描述性的文件,也是 content-v2
里文件的索引,仔细看会发现有点像HTTP的响应头,而且还有缓存相关的值:
那么这些文件是怎么生成的呢?从上面的文档中,可以得知,npm 主要是用pacote 来安装包的,我们来看一下 npm 在代码中是怎么使用pacote的吧。npm主要有以下三个地方会用到 pacote:
- npm install xxx (通过
pacote.extract
把相应的包解压在对应的node_modules
下面。npm 源码入口: lib/install/action/extract-worker.js ,pacote 源码入口: extract.js ) - npm cache add xxx (通过
pacote.tarball.stream
往~/.npm/_cacache
里增加缓存数据。npm 源码入口: lib/cache.js ,pacote 源码入口: tarball.js#tarballStream ) - npm pack xxx (通过
pacote.tarball.toFile
在当前路径生成对应的压缩文件。npm 源码入口: lib/pack.js ,pacote 源码入口: tarball.js#tarballToFile )
对比上述三个 pacote 的方法可以发现,其主要依赖的方法是 lib/withTarballStream.js ,代码比较多,简化一下,主要看中文注释就好:
function withTarballStream (spec, opts, streamHandler) { opts = optCheck(opts) spec = npa(spec, opts.where) // 读本地文件 const tryFile = ( !opts.preferOnline && opts.integrity && opts.resolved && opts.resolved.startsWith('file:') ) ? BB.try(() => { const file = path.resolve(opts.where || '.', opts.resolved.substr(5)) return statAsync(file) .then(() => { const verifier = ssri.integrityStream({ integrity: opts.integrity }) const stream = fs.createReadStream(file) .on('error', err => verifier.emit('error', err)) .pipe(verifier) return streamHandler(stream) }) : BB.reject(Object.assign(new Error('no file!'), { code: 'ENOENT' })) // 上一步reject之后,从缓存中读 const tryDigest = tryFile .catch(err => { if ( opts.preferOnline || !opts.cache || !opts.integrity || !RETRIABLE_ERRORS.has(err.code) ) { throw err } else { // 通过cacache来读缓存中的数据 const stream = cacache.get.stream.byDigest( opts.cache, opts.integrity, opts ) stream.once('error', err => stream.on('newListener', (ev, l) => { if (ev === 'error') { l(err) } })) return streamHandler(stream) .catch(err => { if (err.code === 'EINTEGRITY' || err.code === 'Z_DATA_ERROR') { opts.log.warn('tarball', `cached data for ${spec} (${opts.integrity}) seems to be corrupted. Refreshing cache.`) // 当错误码为EINTEGRITY或Z_DATA_ERROR时,清除缓存 return cleanUpCached(opts.cache, opts.integrity, opts) .then(() => { throw err }) } else { throw err } }) } }) // 上一步reject之后,再下载 const trySpec = tryDigest .catch(err => { if (!RETRIABLE_ERRORS.has(err.code)) { // If it's not one of our retriable errors, bail out and give up. throw err } else { return BB.resolve(retry((tryAgain, attemptNum) => { // 下载包,这边其实是通过npm-registry-fetch来下载的 const tardata = fetch.tarball(spec, opts) if (!opts.resolved) { tardata.on('manifest', m => { opts = opts.concat({ resolved: m._resolved }) }) tardata.on('integrity', i => { opts = opts.concat({ integrity: i }) }) } return BB.try(() => streamHandler(tardata)) }, { retries: 1 })) } }) return trySpec .catch(err => { if (err.code === 'EINTEGRITY') { err.message = `Verification failed while extracting ${spec}:\n${err.message}` } throw err }) } 复制代码
从上述代码中,可以知道 pacote 是依赖 npm-registry-fetch 来下载包的。查看 npm-registry-fetch 的文档发现,在请求时有个 cache
属性可以设置: npm-registry-fetch#opts.cache
可知,如果设置了 cache
的值(npm中是 ~/.npm/_cacache
),便会在给定的路径下创建根据IETF RFC 7234生成的缓存数据。打开那个rfc的地址,发现就是描述 HTTP 缓存的文档,所以本段开头说的 index-v5
下面的文件也就好理解了。
简单总结一下:
~/.npm/_cacache node_modules cacache
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- coredns 排错记
- 译文 | 推荐信:程序排错
- OpenStack排错常用步骤和命令
- Hbase+Hadoop+Zookeeper集群(含排错)
- mysqldump备份表中有大字段失败的排错过程
- Rainbond v5.1.7,应用展示清晰透明,优化应用排错
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Rework
Jason Fried、David Heinemeier Hansson / Crown Business / 2010-3-9 / USD 22.00
"Jason Fried and David Hansson follow their own advice in REWORK, laying bare the surprising philosophies at the core of 37signals' success and inspiring us to put them into practice. There's no jarg......一起来看看 《Rework》 这本书的介绍吧!