nodeをアップデートしたのに、npmだけなぜかアップデートされない罠
まえがき
少し前のことですが、、、
あるプロジェクトにてnodistを使ってnode.js
を動かしてるWindowsマシンに、nodistじゃない通常のインストーラーを使用して別のnodeを入れることになりました。
nodeコマンド自体は新たに入れた方のnode.js
を参照するようになったのですが、npmはnodist側で入れたnpmを見たまま、、、
なぜなの?ということで対応したときのことをメモとして残しておきます。
(何かの参考になることはないと思いますが、npm内の処理について少しだけ勉強になりました)
その前に前置き(モヤモヤ払いのため)
nodistのnode.js -> node.js !?
こういうケースはなかなかないように思えますし、そもそもそういうやり方ってどうなの?(nodistでversion入れ替えればいいじゃん!)と突っ込まれるかもしれません
もちろん私含め、誰もが突っ込みたい状況でした、、、が、
ここについてはTHE 大人の事情
の一言で片付けてもらえたらと思います、、、
モヤモヤ払い、終了。
起こった事象
では、まず状況からまとめます。
- windowsマシンにてnodistを使ってnodeをインストールしていた。
- 今回、とある事情により、nodistを使わないでnodeをインストールし、そちらのnodeを使うことになった。
- nodeのインストール時にpathの設定は自動で行われたようで、node自体はコマンドプロンプト上で
node -v
と叩くと、新しく入れた方のnodeのバージョン番号が出てくる。 - ところが
npm -v
と打っても、nodist使用時に使っていたnpmのバージョン番号が出てきてしまう。 - 新しくインストールした方のnode側のnpmのディレクトリに移動して直接
npm.cmd -v
と叩いても、古いnpmのバージョン番号(nodist使用時のもの)が出てきてしまう。 - なぜっっっ!?????????????
- ちなみに新しく入れた方のnpmのbin配下で
node npm-cli.js -v
と叩いてみると、意図したバージョン番号が出てくる。
問題の原因
で、結論から言うとホームディレクトリ配下にある.npmrc
内にて、
prefixに古い方のnpmのパスが記載されていたために起こっていた事象でした。
ここらへんも問題の背景を話し出すとモヤモヤした感じになっちゃうので割愛しますが、要はnpm prefix -g
って打つと、nodist側のnpmを指すように.npmrc
に設定されていたために起きていたのです。
prefix=hoge
例えば、上のように.npmrc
に書くと、
npm prefix -g
/Users/username/hoge
となります。
上記は自身のmac環境で叩いていますが、windowsでも同じような動作をします。
これにより、prefix側で設定されているnpmを叩きに行くような動作をしていたために今回の事象が起きていました。
とりあえず原因も分かり、問題自体はこれで解決したのですが、ちょっと面白い(?)と思ったのが、
例えば.npmrc
側でprefix
を書いてしまうと、インストール先のnpn.cmdを直接叩こうが、問答無用でprefixに設定されているnpmを見に行ってしまうという点です。
ここの部分が最大のハマるポイントだったように思います。
(ま、自身のwindowsバッチファイルの読解力が低すぎたというのもありますが、、、)
実際にnpm.cmdを読んでみる
実際にどういうことか内部のソースを少しだけ追ってみようと思います。
これはversionが9.7.1
のnpm/bin/npm.cmd
になります。
FOR /F "delims=" %%F IN ('CALL "%NODE_EXE%" "%NPM_CLI_JS%" prefix -g') DO (
SET "NPM_PREFIX_NPM_CLI_JS=%%F\node_modules\npm\bin\npm-cli.js"
)
割愛しまくって話すと、このCALL "%NODE_EXE%" "%NPM_CLI_JS%" prefix -g'
という箇所でnpm prefix -g
で出力されるパスにあるnpmを指すように設定しています。
そのため、直接npm.cmd
を叩いているにも関わらず、nodist側のnpmが呼ばれるようになっていたのです。
疑問点
問題の原因は無事に判明しました。
が、このように直接実行ファイルを叩いているのにもかかわらず、違うバージョンのnpmが実行されてしまうということに、同時に違和感のようなものも感じました。
せめて、対象のnpmの実行ファイルを直接叩いた場合は、直接叩いた先のnpmが動くべきなのではないでしょうか?
以上の疑問点から、他のパッケージマネージャーの実装を見てみようと思います。
(といっても時間がないので、ひとまずyarn
だけ。他の実装も後々確認してみたい)
yarnにおける実装
npmオルタナティヴであるyarnのbin/yarn.cmd
を見てみます。
@echo off
node "%~dp0\yarn.js" %*
yarnでは実装内容はこの2行だけでした。
一応grep
でprefix使ってないか確認してみましたが、少なくともbin配下では使われていません。
(bin配下で実行される処理で、js側に処理が移るのでprefixを調べるのはbin
のみで良いと判断しています。)
ちなみに%~dp0
については下記の記事で以下のように解説されています。
%~dp0は、当該バッチが配置されているディレクトリのフルパスを示します。
つまりyarn.cmd
の場合、同じディレクトリ内に在るyarn.js
に対してnodeコマンドを叩いているだけということになります。
こちらのほうが直感的で分かりやすいと思ったのですが、何か意図があるのかもしれません。
後々、機会を見つけてnpm.cmd
のcommit logを追ってみようと思います。
実装の意図を調べる(TODO:まだ、未実施)
該当するコミットログは下記のようです。
windows: search for a user-installed npm
時間を見つけて調べてみようと思います。