Production Ready Node: Command Line Tooling

ommand Line tools are an important component to development, administration and maintenance of most all software projects - large or small. A good command line tool greatly speeds up repetitive operations through simple and flexible abstractions.

The Node.js community has a number of tools out there for quickly building out a command line interfaces, commander being one of the more popular. However, most all of them suffer from the same set of problems, making them difficult to integrate into applications in a clean an re-usable manner. Primarily speaking, there is no way to separate general functionality, from a command, from the argument parsing, or a command from the command line tool itself. This makes building for code re-use exceptionally difficult.

Separation of Concerns

When building a command line interface for your applications, you want to keep commands separate from the primary tool, and general functionality separate from each of the commands. As a rule of thumb, your commands should not do anything that can not be done from within the application itself. Ideally, you want the command line tool to be a small, simple program that knows how to find, register and run your defined commands. To accomplish this, we are going to being using the Seeli framework.

npm install --save seeli

Primary Tool

We can use npm to define the name of our command line tool, or binary script through the bin property in the package.json of our project:

When our package is installed globally ( npm install -g ), or dynamically linked ( npm link ), this will add our program cli.js to our systems execution path as fancyname. You can call it whatever you like however.

Next, we just need to define command line tool. All our tool will need to do is load and register commands. Following the project structure we have previously established, we can assume all commands will be located in a commands folder of packages under our primary internal package directory.

Commands

Commands are simply javascript file that export a command instance as the module. To illustrate this, we can start with the obligatory hello world command. This command will be called hello and it will simply say hello to a name, which will be passed via a name flag.

It is pretty easy to understand what this command does. Everything we've defined is effectively optional, with the exception of the run function. The run function what the command actually does and needs to call the done function if and when it is done. The done is a node style callback that accepts an error, if there is one, and an optional string which will be written to stdout.

Functionality

We can take this one step further and create a command that is a little more involved. We'll make a command that displays all of the version information of our internal packages. Keeping our rule of thumb about commands, we'll encapsulate the primary functionality of the command in a separate module.

The implementation of the loader is not important here, but our little loader class has two functions, find and load. Find, as the name would imply locates a kind of file in our packages returning absolute paths to the files. In this situation it is looking for packages.json files. The load method, calls the find method and runs require over the results so we end up with an array containing each of the package manifest modules that we have found. We can use the information in this to find version information for use in our command.

Our module does most of the work, so our command really just needs to format the data, and print it out.

That is now we can do fancyname version and get version information for all of the private packages. Our tool is independant of the actual commands, and our commands are loosely coupled to the functionality they expose.

While these commands are simple, you could image commands that generate documentation or perform some kind of database initialization, etc. Moreover, because of our commands as modules set up, if we have to, we could directly require and invoke the commands programmatic-ally, making them multi-purpose and highly testible.

Lastly, because seeli commands don't alter argv, if you have your configuration system set up like we have talked about, you can pass additional flags through the command line tool to the configuration handlers to alter application set up and behavior.

I'm a software developer and system architect working at help.com. Most of my day is spent working with Javascript & Node.js. I've also done a good deal of web and print design work in my day. I created this space to share my experiences with the world and hopefully learn something in the process.

This Space

Here you will find my ramblings and rants about web development. My focus is around JavaScript( MooTools, Sencha, NodeJS ), Python & Django, HTML & CSS. Most things here target a wide range of skill levels - from the very simple to the moderately complicated. You may also find the occasionaly personal ranting and I may stand on a soap box from time to time.