一度のエラー履歴を記入する-npmキャッシュの概要
15365 ワード
に縁を付ける
一度インストールプロジェクトに依存したとき、端末は以下のエラーを報告し、依存インストールに失敗した.
エラーメッセージから
@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
という実行可能ファイルが1つ追加されたことを発見しました.だからこのファイルに問題があるはずですが、このファイルはどこから来たのでしょうか.
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を含むキャッシュクリアが行われたことを思い出すと、次の2つのコマンドで行われていました.
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.
If 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
フォルダにキャッシュデータを格納する.上記の2つのドキュメントの内容を組み合わせると、次のようになります.cache
フィールドは、ルートディレクトリ_cacache
フォルダのclean
コマンドクリアは_cacache
フォルダnpmキャッシュに何が保存されているのか
_cacache
フォルダを開くと、node_modules
の中のように一つ一つのバッグではなく、こうなっています.開くと
content-v2
の中には基本的にいくつかのバイナリファイルがあり、バイナリファイルの拡張子を.tgz
に変更して解凍した後、私たちがよく知っているnpmパッケージであることがわかります.index-v5
の中にはいくつかの記述的なファイルがあり、content-v2
のファイルのインデックスでもあります.よく見るとHTTPの応答ヘッダに似ています.キャッシュ関連の値もあります.では、これらのファイルはどのように生成されますか?上記のドキュメントから、npmは主にpacoteでパッケージをインストールしていることがわかりますが、npmがコードでpacoteをどのように使用しているかを見てみましょう.npmは主にpacoteを使用する3つの場所があります.
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) 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}:
${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
によってこれらのキャッシュデータを操作することができる.最後に書く
全体を振り返ってみると、ドキュメントをよく見ることがどんなに重要かがわかります.覚えておいて!覚えておいて!しかし、普段あまり注目されていない点も整理しておくと、収穫もあり、文字の形で記録されていて、振り返るのに便利です.
原文リンク:github.com/sliwey/blog…