😶‍🌫️

fish + nvm 環境でcd時に自動でnvm useする方法

2024/02/07に公開

目標

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のシェルスクリプトを書く。

~/.config/fish/funcitons/__check_nvm.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で関数をインポートして使う。

~/.config/fish/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するとパスが期待通りに変更される。

~/.config/fish/funcitons/__check_nvm.fish
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
GitHubで編集を提案

Discussion