It’s well known that Node.js is a great environment to write applications for Web.
A CLI is a program which a user interacts within a terminal. CLI utilities are mostly used by people from Internet Technologies industry — Software Engineers, DevOps and System Administrators. CLIs` main goal is to simplify different tasks by automating and encapsulating operations into small commands.
By performing, wrapping, and combining commands in the terminal a developer can sufficiently enhance everyday working experience and speed up a development process.
The other benefit of creating the CLI application in Node.js technology is NPM — a brilliant built-in tool allowing to organise code in packages. An enormous community allows to adapt and reuse libraries written for all possible purposes. In fact, according to Atwood’s Law
Rich modules infrastructure helps developers to focus more on their application logic and makes Node.js shine in prototyping, which is highly important to prove ideas nowadays when time to market (TTM) is one of the most significant values.
Basic Principles Overview
NPM shows which commands are possible to be executed as well as basic information about the program. Note, that the message is shown without being said to do so. This is default behavior, when there is no input given. It makes the help command accessible. To make it also predictable, it makes sense to enable a — help option without strong syntax.
All four commands examples above output the same NPM help message. Additionally, it is useful to show the help message if the given input is not recognised.
The other tool developers are probably using most of their time is Git - an open-source version control system program for the terminal. It also shows a help message as default action when called without arguments.
However, it’s just a few examples, undoubtedly, this can be treated as the starting point command in all CLIs applications. Its purpose is to allow the developer to understand what he can do with the utility. A program that doesn’t have a “help” command is hard to use. The user might have got stuck and just leave it as it doesn’t allow him to achieve his goal.
Let’s write a requirement for the further implementation in a Gherkin notation, we’ll use it later.
The next thing the one might notice in NPM help message above is the program’s version shown at the bottom of it.
It’s another common CLIs practice, which helps the user to understand how to use the tool and not to be stacked with unexpected behavior. Usually, by searching an answer of how to solve a task the user finds some documentation on the Internet (say, at Stack Overflow). It can be unclear, why the answer does not work. Explicitly showing the version helps to stay in touch with actual software. The program’s — version option would respect user’s expectations in that scenario.
Some CLIs show a warning if there is a newer version available, or even propose an automatic update while using a tool. Let’s write the version requirement for later.
We are going to work with the Linux shell to write the very first CLI program. The basics examples will also work in Mac OS. We’ll need Node.js to be installed on a machine. Its version doesn’t matter much at that point, but we’ll use the Long Term Support (LTS), which is currently 10.
The NPM doesn’t need to be installed separately, as it goes together with the Node.js package. Let’s do the first step — introduce a package, that we’ll work on.
With these commands we’ve created the simplest application in Node.js. In all of them we’ve used different command line tools, such as mkdir, cd, echo, and, finally npm. When running the last step, the output should be like
Our first goal is to create something very simple, and in next tutorials we’ll make the CLI as powerful as the commands above.
By the way, just to be consistent with our intent, we’ll add the generated code to a version control system, and we’ll use Git to save the state.
The created code is still far from being a CLI. We ran it with npm command during the initialisation and could also use node for the same reason.
Here is where NPM really shines. Let’s add a bin property to package.json file
The bin property, when a package is installed signals to NPM, that the command with the package name should be added to PATH. After installing the package globally
It is possible to run the my-cli command in the terminal at any location. NPM made the server.js file executable. A normal install puts the bin file onto node_modules/.bin directory of a project and enables to run it with an npx command. By running an npm link command we create a symbolic link with the package and make it easier to develop and debug.
However, the output is not a desired “Hello, World!” string. That is because in *nix systems when an executable is started, it’s interpreted with shell sh. To let it know, that it needs to be executed with node we need to add a shebang at the beginning of the server.js file.
Now, my-cli outputs “Hello, World!” like it was expected, and it’s time for us to implement version and help behaviours.
We don’t want to repeat ourselves with duplicating information in many places. So, we’ll use package.json fields — name, version, and description as the single source of truth for that program.
Next, we’ll retrieve the data from package.json and show it as the default message when running the CLI.
The last part parses given arguments as options and decides which value to show. We’ll use the global variable process available in Node.js environment and it’s property process.argv. argv is an array, with first item — a path to Node.js runner version and the second — the program path. The rest is all passed arguments split by whitespace. That can be shown with an added expression console.log(process.argv). When running in
my-cli test=me option
the output will be
Remember, we've decided output the program's version when an appropriate version is passed
Here is the code, implementing this requirement
On the fourth line, we slice the passed arguments array, excluding it’s first two items. The latter logic is quite obvious — if options contain — version flag, we output the version from package.json. Otherwise, we still output the help message. Good news is that our first required scenario
also nicely passes now. Our work is done for now!