JavaScript
Windows
Node.js
npm
nodist

お、お、お前は僕の知っているnpmじゃないなっ!!!!!

nodeをアップデートしたのに、npmだけなぜかアップデートされない罠

まえがき

少し前のことですが、、、
あるプロジェクトにてnodistを使ってnode.jsを動かしてるWindowsマシンに、nodistじゃない通常のインストーラーを使用して別のnodeを入れることになりました。
nodeコマンド自体は新たに入れた方のnode.jsを参照するようになったのですが、npmはnodist側で入れたnpmを見たまま、、、
なぜなの?ということで対応したときのことをメモとして残しておきます。
(何かの参考になることはないと思いますが、npm内の処理について少しだけ勉強になりました)

その前に前置き(モヤモヤ払いのため)

nodistのnode.js -> node.js !?

こういうケースはなかなかないように思えますし、そもそもそういうやり方ってどうなの?(nodistでversion入れ替えればいいじゃん!)と突っ込まれるかもしれません:fearful:
もちろん私含め、誰もが突っ込みたい状況でした、、、が、
ここについてはTHE 大人の事情の一言で片付けてもらえたらと思います、、、:cry:

モヤモヤ払い、終了。

起こった事象

では、まず状況からまとめます。

  • 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のパスが記載されていたために起こっていた事象でした。

ここらへんも問題の背景を話し出すとモヤモヤした感じになっちゃうので割愛しますが:cry:、要はnpm prefix -gって打つと、nodist側のnpmを指すように.npmrcに設定されていたために起きていたのです。

npm prefix

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.1npm/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オルタナティヴであるyarnbin/yarn.cmdを見てみます。

@echo off
node "%~dp0\yarn.js" %*

yarnでは実装内容はこの2行だけでした。
一応grepでprefix使ってないか確認してみましたが、少なくともbin配下では使われていません。
(bin配下で実行される処理で、js側に処理が移るのでprefixを調べるのはbinのみで良いと判断しています。)

ちなみに%~dp0については下記の記事で以下のように解説されています。

Windowsバッチまとめ

%~dp0は、当該バッチが配置されているディレクトリのフルパスを示します。

つまりyarn.cmdの場合、同じディレクトリ内に在るyarn.jsに対してnodeコマンドを叩いているだけということになります。

こちらのほうが直感的で分かりやすいと思ったのですが、何か意図があるのかもしれません。
後々、機会を見つけてnpm.cmdのcommit logを追ってみようと思います。

実装の意図を調べる(TODO:まだ、未実施)

該当するコミットログは下記のようです。

windows: search for a user-installed npm

時間を見つけて調べてみようと思います。