Run yarn/npm scripts with fzf

In a Node enviroment or a project with a package.json, It’s very common to have a field "scripts" inside the package.json which stores and lists the scripts available to build, test and run the project.

In order to see which scripts are available in the project without opening the package.json with an editor, yarn and npm have a command that lists them: yarn run or npm run.

Which is a prompt that allows you to write which command you want to run or list them, depend if you are using npm or yarn.

This is the output in a recently created expo app:

yarn run v1.22.4
info Project commands
   - android
      expo start --android
   - eject
      expo eject
   - ios
      expo start --ios
   - start
      expo start
   - web
      expo start --web
question Which command would you like to run?:

It’s very handy and allows you to not spend energy trying to remember those and have a fast lookup, but it’s very slow (around 250ms!) since needs to start a node process, parse the json file and output something to stdin and the UX can be better since it forces you to type the command again.

I was a little frustrated by it and I created a small bash script that solves it, it’s aliased to run in my local enviroment, take a look how it works:

yarn-run.gif

It uses fzf to filter the commands available and allows a fast search for them. If you don’t know what’s fzf, I can’t recommend enough using it. fzf stands for fuzzy finder and it’s used by default to navigate the bash history and the common use of it it’s with a bind to open a file with your editor.

fzf.gif

What’s the script about

if cat package.json > /dev/null 2>&1; then
    scripts=$(cat package.json | jq .scripts | sed '1d;$d' | fzf --height 40%)

    if [[ -n $scripts ]]; then
        script_name=$(echo $scripts | awk -F ': ' '{gsub(/"/, "", $1); print $1}')

        yarn run $script_name
    else
        echo "Exit: You haven't selected any script"
    fi
else
    echo "Error: There's no package.json"
fi

This can be intimidating at first so in order to understand it better, let’s break it down to explain a little what each command does.

The first interesting bit is to preview the content of package.json and make it appear on a fzf menu.

cat package.json | jq .scripts | sed '1d;$d'

cat package.json prints the content of the package.json

jq .scripts parses the json and selects only “scripts”

sed '1d;$d' removes the first and the last line

"predeploy": "yarn build",
"deploy": "gh-pages -d public",
"build": "gatsby build",
"develop": "gatsby develop",
"format": "prettier --write src/**/*.{js,jsx}",
"start": "npm run develop",
"serve": "gatsby serve",
"test": "echo \"Write tests! -> https://gatsby.dev/unit-testing\""

This output gets passed to fzf, which creates the floating menu, and once you select one of the rows, the $script value is set to "start": "npm run develop" and finally you are able to get the command name "start" and finishes running "yarn run start".

I appended yarn run instead of yarn start which is also valid, to avoid shadowing any binary.

Custom

This script lives under my dotfiles that lives in my personal setup repo, which is an easy way to install and setup your enviroment each time you change laptops.

Feel free to can change this script to use npm or do any sort of change, I found playing with my terminal very rewarding and getting those productivity improvements allowed me to do stuff that I wasn’t capable before. I will keep sharing more as they pop in my head.

Resources

  • For an introduction to the fzf, see its excellent blog posts
  • My personal setup davesnx/setup using zsh
  • If you aren’t familiar with the dotfiles this would give a little clarity.
  • For the spanish speakers I found this course amazing