fish + nvm 環境でcd時に自動でnvm useする方法
目標
fishでnvmを使う際に、移動したディレクトリに.nvmrcファイルがあれば自動でnvm use
を実行し、nodeバージョンを切り替える。
環境
- MacBook Pro M1, 2020
- macOS 14.3(Sonoma)
- fish 3.7.0
- fisher 4.4.4
- nvm.fish 2.2.13
fishのnvm導入方法
brewでfishのプラグインマネージャーであるfisherをインストールする。
brew install fisher
fisherを使ってfish版のnvm(nvm.fish)をインストールする。
fisher install jorgebucaran/nvm.fish
cd時にnvm useを行なう設定方法
このページを参考にしてfishのシェルスクリプトを書く。
function __check_nvm --on-variable PWD --description 'Do nvm stuff' # --on-variable PWD: ディレクトリを移動したら実行する
if test -f .nvmrc # .nvmrcファイルがあるかどうかチェックする
set node_version (node -v) # 現在のnodeバージョンを取得
set node_version_target (cat .nvmrc) # .nvmrcに書かれているバージョン
set nvmrc_node_version (nvm list | grep $node_version_target) # .nvmrcファイルの情報とインストール済みのバージョンを一致させる
if not string match -q -- "*$node_version*" $nvmrc_node_version
nvm use $node_version_target # 現在のnodeバージョンと.nvmrcに書かれているバージョンが異なればnvm useを実行する
end
end
end
__check_nvm # 常に実行させる
config.fishで関数をインポートして使う。
# NVM Function Source
source ~/.config/fish/functions/__check_nvm.fish
検証
現在インストール済みのnodeバージョン一覧を表示する。現在はbrewでインストールしたsystem標準のnodeを参照している。
❯ nvm list
▶ system
v18.19.0 lts/hydrogen
v20.11.0 lts/iron
v21.6.1 latest
testディレクトリを作成してtest/.nvmrc内に18(node v18)を記載する。
mkdir test && echo 18 > test/.nvmrc
cdする。
❯ cd test
Now using Node v18.19.0 (npm 10.2.3) ~/.local/share/nvm/v18.19.0/bin/node
バージョンを確認する。
❯ node -v
v18.19.0
期待通りバージョンを変更できた。
nodeバージョンが.nvmrcのバージョンと一致していれば次回以降のcdでnvm useは実行されない。
❯ cd .. && cd test
# nvm useは実行されない
zコマンドにも対応している(PWDが変更されるので当然と言えば当然)
❯ cd
❯ nvm use system
Now using Node v21.6.1 (npm 10.2.4) /opt/homebrew/Cellar/node/21.6.1/bin/node
❯ z test
Now using Node v18.19.0 (npm 10.2.3) ~/.local/share/nvm/v18.19.0/bin/node
同じバージョンが複数ある場合
grepする箇所で2行以上出力される場合でも期待通り動作する。
以下の場合を考える。
❯ nvm list
▶ system
v18.18.2 lts/hydrogen
v18.19.0 lts/hydrogen
v20.11.0 lts/iron
v21.6.1 latest
変数を確認する。
❯ printf "%s\n" $node_version $node_version_target $nvmrc_node_version
v21.6.1
18
v18.18.2 lts/hydrogen
v18.19.0 lts/hydrogen
検証する。
❯ not string match -q -- "*$node_version*" $nvmrc_node_version
# 文字列"v18.18.2 lts/hydrogen\nv18.19.0 lts/hydrogen"が"v21.6.1"が部分文字列を含まないかどうか
❯ echo $status
0
# ないのでステータス0が返る
上記によってif文内のnvm use 18`が実行される。
複数バージョンがあっても文字列として比較を行なうのでロジックに不都合はない。
v18.18.2の状態で.nvmrcに18.19を書いてもしっかりバージョンの変換が行なわれる。(この場合はgrepの段階でターゲットのバージョンが絞られる。)
問題点
iTermから実行すると期待通り動くが、VSCodeのターミナルだと期待通り動かない
❯ mkdir test && echo 18 > test/.nvmrc
❯ cd test
Now using Node v21.6.1 (npm 10.2.4) /opt/homebrew/Cellar/node/21.6.1/bin/node
test/.nvmrcにはバージョン18が指定されているが、cdした後のnodeバージョンはsystem標準の21になっている。
パスを確認してみる。
❯ which -a node
/opt/homebrew/bin/node
/Users/reiyaasuke/.local/share/nvm/v18.19.0/bin/node
パスの順番が期待している順番と逆になっている。本来はnvmのパスがhomebrewのパスより上にくるはずであるが、なぜか上手くいかない。
解決法
パスの順番を入れ替える関数を書く手もあるが、nvm.fish自体がその役割を担っているはずなので競合を恐れてこの方法は回避。
どうやらこの現象はv18固有の現象でv20だと上手く動く。
v20を指定してからnvm useするとパスが期待通りに変更される。
function __check_nvm --on-variable PWD --description 'Do nvm stuff'
if test -f .nvmrc
set node_version (node -v)
set node_version_target (cat .nvmrc)
set nvmrc_node_version (nvm list | grep $node_version_target)
if not string match -q -- "*$node_version*" $nvmrc_node_version
nvm --silent use 20 # 一度v20にする --silentオプションで出力をしないようにする
nvm use $node_version_target
end
end
end
__check_nvm
Discussion