Tips for making a CLI-based tool with node

I got this question on my AMA and decided to turn the answer into a formal blogpost.

https://unsplash.com/photos/8Pd8yCjjKIQ (obligatory post photo that has nothing to do with this blogpost)

Hey @amandeepmittal!
Thanks for reaching out to me! I’m glad you enjoyed my course :D

I’ve made a few CLIs. Probably my simplest recent one is split-guide. You’ll see the CLI code lives here. And I tell npm to use the transpiled version of that file as the bin in the package.json here where the key in that object is the name of the binary (so what you'd type in the terminal to use the CLI) and the value is the path in the package where the binary is located (in my case it's in the dist directory which I transpile to with babel here (I'm using p-s for my scripts).

With that configuration, when npm (or yarn) installs my package, it will create a symlink to that file in the node_modules/.bin directory (or, if it's globally installed, it'll put it wherever your global packages are loaded into your $PATH). For locally installed packages, you can use those binaries with your npm scripts (this behavior is kinda explained here in the official docs).

So, for example, because I have setup split-guide like that, I can use it in my react-jest-workshop here.

Let’s look again at the actual bin file itself now. There are a few things to note:

  1. The first line has #!/usr/bin/env node. This is called a shebang which effectively tells the system to run the script with node.
  2. Pretty much the rest of the file is configuring yargs to accept the arguments I want it to. There are actually countless npm packages to help parse process.argv into something that's usable (flags etc.) I've used commander, meow and a few others I can't recall, but I've been most happy with yargs. It's pretty darn powerful.
  3. You’ll also notice that I’m not doing a lot of logic in here. That’s because unit testing this file is a bit of a pain. You’ll find my tests here and see that it is a bit complex, but it’s actually a pretty solid integration test. Most of it is actually just using Jest snapshots which is actually super enabling and makes me pretty certain of the impact my changes will make on the tool. I couldn’t be happier with Jest snapshot testing and definitely recommend it for testing your CLIs!
  4. The main logic for the package lives in the rest of the src directory. Normally I would unit test this, but this package in particular is one that I don't expect a lot of people to use so I didn't take the time. In fact, I don't even report coverage on this project which is kinda rare for me 😅

I hope this was helpful! Good luck!

See you on Twitter