CLI (Command-line interface) is a very common way to run a program in a terminal. As a software engineer you use different CLIs every day – git, Docker, npm, etc.
Today I want to share my experience building CLIs using Node.js and a few helpful packages.
Goal
I’m going to only focus on npm in this article, simply because I don’t have enough experience with other package managers for Node.js and apparently npm is still the most popular choice.
So, our goal is to have something like this in the end:
1 2 |
|
npm setup
First of all, let’s make sure we have a proper npm configuration and folder structure!
bin/awesometool.js:
1 2 3 |
|
Snippet above should look really straightforward – we simply require the main file (or entry point) of the app, in our case ./lib/awesometool.js
.
package.json:
1 2 3 4 5 6 7 8 9 10 |
|
The most important things in the package.json are:
name
: this is what we use for naming (in npm ecosystem)version
: every time you change your program you’ll have to update the version and publish itmain
: simply an entry pointbin.awesometool
: should point to ourbin/awesometool.js
file. Also, this name is going to be used after the installation as a terminal command for our program
Designing the CLI
Now we have the basic setup and it’s time to think about the CLI itself. Usually every Command-line interface description consists from a few sections:
- usage information
- a list of available commands
- a list of available options
So, you need to decide how to structure the CLI – what commands do you need, what options are available, what are the safe defaults, etc.
A few resources that can help with that:
- https://trevorsullivan.net/2016/07/11/designing-command-line-tools/
- https://softwareengineering.stackexchange.com/questions/307467/what-are-good-habits-for-designing-command-line-arguments
- http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.html#tag_12_01c
Also I encourage to get some inspiration from well-known tools like Docker or Heroku CLI.
After you realize what kind of commands and options you would need, instead of implementing command-line arguments parsing from scratch (yay) and solving a bunch of terminal rendering issues we’re going to use commander.
commander is an awesome tool to help us define CLI commands, options and related actions. Here’s an example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
I like how concise and expressive it is. We define one command with an action plus an option that’s available for all commands.
This is how it looks in the terminal:
1 2 3 4 5 6 7 8 9 10 |
|
Bonus: inquiry session flow
If you build a complex CLI you might need to ask user a few questions before proceeding. To make things easier for the end user it’s usually a good idea to introduce validation, default values and some other helpers. Also, sometimes user needs to choose from a set of predefined values instead of entering a custom one. All these things are handled by inquirer!
Let me show you the wonderful pizza example they have:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
|
I like the balance between using declarative rules and writing custom logic for validation and default values.
Bonus: colors
Yes, colors in your terminal! Or colours…
So easy to use:
1 2 3 4 5 6 |
|