fzfでpackage.jsonのscriptsを選んで実行

npm run foopnpm run barを毎回タイピングして実行するのは面倒ですし、実行される内容まで細かく覚えていられません。そこで、fzfを使って実行します。

以前Zennに書いた記事ではpnpmの方に対応していませんでしたが、今回は対応させました!

動作例

次のように、コマンド名 = 実行内容のリストをfzfで選んで実行できます。

fzfを使っているため当然絞り込みできます。

動作例

必要なもの

今回はzshを使った例です。zsh以外の人はよしなに書き換えてください。

実行方法

Node.jsのプロジェクトにあるpackage.jsonにて、Ctrl+x,nを入力すると実行できます。
ホットキーは必要に応じて書き換えましょう。

コード

いったんコード全体を載せます。次の項目から分けて解説します。

function fzf_npm_scripts() {
local prefix="npm"
if [ -e pnpm-lock.yaml ]; then
prefix="pnpm"
fi
if [ ! -e package.json ]; then
echo 'fzf_npm_scripts'
echo 'There is no package.json'
zle send-break
return 1
fi
if ! type jq > /dev/null; then
echo 'fzf_npm_scripts'
echo 'jq command is required'
zle send-break
return 1
fi
local scripts=`jq -r '.scripts | to_entries | .[] | .key + " = " + .value' package.json 2>/dev/null || echo ''`
if [[ -z $scripts ]]; then
echo 'fzf_npm_scripts'
echo 'There is no scripts in package.json'
zle send-break
return 1
fi
local selected=`echo $scripts | FZF_DEFAULT_OPTS='' fzf --height=50% --reverse --exit-0 | awk -F ' = ' '{ print $1}'`
zle reset-prompt
if [[ -z $selected ]]; then
return 0
fi
# 再実行したくなるときのために履歴に残して実行
BUFFER="$prefix run $selected"
zle accept-line
}
zle -N fzf_npm_scripts
bindkey "^Xn" fzf_npm_scripts

pnpmだった場合に切り替える

まずpnpmを使っている場合は書き換えます。

local prefix="npm"
if [ -e pnpm-lock.yaml ]; then
prefix="pnpm"
fi

必要なファイル・コマンドが無い場合

package.jsonjqが無い場合はエラーにします。

if [ ! -e package.json ]; then
echo 'fzf_npm_scripts'
echo 'There is no package.json'
zle send-break
return 1
fi
if ! type jq > /dev/null; then
echo 'fzf_npm_scripts'
echo 'jq command is required'
zle send-break
return 1
fi

send-breakを使わないと、エラーになったときに次のプロンプトが表示されません。Enterを押せばよいのですが、せっかくなので気持ちを新たにプロンプトも切り替えたいところです。

send-breakを使わなかった場合

send-breakを使えば、エラーになったときに次のプロンプトに切り替えてくれます。

send-breakを使った例

scriptsを取得

jqを使ってpackage.jsonscriptsの中身を取っています。

local scripts=`jq -r '.scripts | to_entries | .[] | .key + " = " + .value' package.json 2>/dev/null || echo ''`

取得した中身はつぎのようにコマンド名 = 実行内容のリストになります。

dev = astro dev
start = astro dev
build = astro check && astro build
preview = astro preview
astro = astro
format = prettier -w .

jqの書き方を詳しく知りたい方は公式ドキュメントとほほのjq入門あたりを読みましょう。

package.jsonscriptsが無かったらエラーにします。

if [[ -z $scripts ]]; then
echo 'fzf_npm_scripts'
echo 'There is no scripts in package.json'
zle send-break
return 1
fi

fzfにリストを渡す

ここまできたらあとはいよいよfzfにリストを渡します。FZF_DEFAULT_OPTSの設定に左右されないよう、デフォルトオプションを空にしています。

local selected=`echo $scripts | FZF_DEFAULT_OPTS='' fzf --height=50% --reverse --exit-0 | awk -F ' = ' '{ print $1}'`

awkを使って=の前、つまりコマンド名を抜き出して変数に入れます。

何も選ばれなかったらそこで終了します。プロンプトのテーマによってはreset-promptを使わないとカーソルの位置がずれます。

zle reset-prompt
if [[ -z $selected ]]; then
return 0
fi

ZLE経由で実行

あとは現在のプロンプトの入力をpnpm run devのように書き換えて実行です。BUFFER=で入力を書き換えることで、コマンドの履歴として残すことができます。

BUFFER="$prefix run $selected"
zle accept-line

今回はZLEを使ったため、実行にはZLEのウィジェットとしての登録とキーバインドの設定が必要です。

zle -N fzf_npm_scripts
bindkey "^Xn" fzf_npm_scripts

キーバインドの割り当て状況を知りたいならbindkeyコマンドを実行しましょう。一覧がずらっと出てきます。

Terminal window
bindkey
"^@" set-mark-command
"^A" beginning-of-line
"^B" backward-char
"^C" send-break
"^D" delete-char-or-list
"^E" end-of-line
"^F" forward-char
"^G" send-break
"^H" backward-delete-char
"^I" fzf-completion
"^J" accept-line
"^K" my_fzf_completion
省略

npm run ◯◯pnpm run ◯◯をfzfで実行できるようなスクリプトの紹介でした。fzfとZLEって本当に便利です。