You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
functionclean(args){if(!args)args=[]if(args.length){returnBB.reject(newError('npm cache clear does not accept arguments'))}// 重点在这// npm.cache就是 ~/.npm// 所以cachePath的值应该是 ~/.npm/_cacacheconstcachePath=path.join(npm.cache,'_cacache')if(!npm.config.get('force')){returnBB.reject(newError("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 versionsreturnrm(cachePath)}
functionwithTarballStream(spec,opts,streamHandler){opts=optCheck(opts)spec=npa(spec,opts.where)// 读本地文件consttryFile=(!opts.preferOnline&&opts.integrity&&opts.resolved&&opts.resolved.startsWith('file:'))
? BB.try(()=>{constfile=path.resolve(opts.where||'.',opts.resolved.substr(5))returnstatAsync(file).then(()=>{constverifier=ssri.integrityStream({integrity: opts.integrity})conststream=fs.createReadStream(file).on('error',err=>verifier.emit('error',err)).pipe(verifier)returnstreamHandler(stream)})
: BB.reject(Object.assign(newError('no file!'),{code: 'ENOENT'}))// 上一步reject之后,从缓存中读consttryDigest=tryFile.catch(err=>{if(opts.preferOnline||!opts.cache||!opts.integrity||!RETRIABLE_ERRORS.has(err.code)){throwerr}else{// 通过cacache来读缓存中的数据conststream=cacache.get.stream.byDigest(opts.cache,opts.integrity,opts)stream.once('error',err=>stream.on('newListener',(ev,l)=>{if(ev==='error'){l(err)}}))returnstreamHandler(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时,清除缓存returncleanUpCached(opts.cache,opts.integrity,opts).then(()=>{throwerr})}else{throwerr}})}})// 上一步reject之后,再下载consttrySpec=tryDigest.catch(err=>{if(!RETRIABLE_ERRORS.has(err.code)){// If it's not one of our retriable errors, bail out and give up.throwerr}else{returnBB.resolve(retry((tryAgain,attemptNum)=>{// 下载包,这边其实是通过npm-registry-fetch来下载的consttardata=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})})}returnBB.try(()=>streamHandler(tardata))},{retries: 1}))}})returntrySpec.catch(err=>{if(err.code==='EINTEGRITY'){err.message=`Verification failed while extracting ${spec}:\n${err.message}`}throwerr})}
缘起
一次在安装项目依赖的时候,终端报了下面这个错,导致依赖安装失败。
通过报错信息可以看出是
@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
的代码,会发现其实就做了一件事:就是下载个可执行的文件,然后检查下版本号。
checkVersion
先按下不表,不是重点,就只是判断下版本号,来看downloadBinary
(我简化了一下代码,加了点注释,具体代码可查看https://github.com/getsentry/sentry-cli/blob/master/scripts/install.js#L100):根据刚才本地的执行情况来看,并没有进行下载,可知那个可执行文件是从缓存中拿的,那就打个断点看一下缓存路径:
根据得到的路径,删除对应文件,然后重新安装,everything is ok~
下面的才是重点
虽然问题解决了,但是回想了一下之前的一通操作,其中是有做过缓存清除的,包括yarn和npm,当时的做法是通过下面两个命令做的:
根据上面得到的缓存路径,可以知道
sentry-cli
缓存在~/.npm
文件夹下,所以跟yarn应该没关系,先排除掉。然后来看npm,发现通过npm cache clean --force
来清除缓存,并没有清掉~/.npm
文件夹下的文件,那么这个命令清的是哪里呢?先看下文档怎么说:npm-cache为了阅读方便,我截了几个图:
乍一看貌似没什么毛病,检查了一下自己的cache配置,也没有发现什么异常:
那么到底是哪里的问题呢,看来只能看下源码了,目标很直接,找到npm中跟cache相关的代码,然后直接看clean方法的实现(具体代码可以看lib/cache.js):
看到这就很明白了,
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:
pacote.extract
把相应的包解压在对应的node_modules
下面。npm 源码入口:lib/install/action/extract-worker.js,pacote 源码入口:extract.js)pacote.tarball.stream
往~/.npm/_cacache
里增加缓存数据。npm 源码入口:lib/cache.js,pacote 源码入口:tarball.js#tarballStream)pacote.tarball.toFile
在当前路径生成对应的压缩文件。npm 源码入口:lib/pack.js,pacote 源码入口:tarball.js#tarballToFile)对比上述三个 pacote 的方法可以发现,其主要依赖的方法是 lib/withTarballStream.js,代码比较多,简化一下,主要看中文注释就好:
从上述代码中,可以知道 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
来操作这些缓存数据。写在最后
回顾了一下整件事情,发现文档看仔细是多么重要!谨记!谨记!但是也把平时不怎么关注的点梳理了一遍,也算是有所收获,以文字的形式记录下来,便于回顾。
The text was updated successfully, but these errors were encountered: