Building a Command Line Tool (CLT) in Node

Recently I published a command line tool (CLT) to the npm registry. The module in question was pure-html, a development environment for creating standalone HTML files. I will detail some of the things I learned during that process, and what parts are necessary to publish your module to the npm registry.

Table of Contents

Project Structure

At a minimum, the project should contain these files:

|-- package.json // The manifest of every node project.
|-- npm-shrinkwrap.json // Ensure a deterministic tree of dependencies.
|-- .npmignore // Only publish what's needed.
|-- bin
|   \ -- example-cli // Starting point for the command line tool.


The package.json is the backbone to any node project, including command line tools. Now, there are some fields one should pay attention to regarding CLT’s:

  "name": "example-cli",
  "main": "bin/example-cli",
  "bin": {
    "example-cli": "./bin/example-cli"
  "devDependencies": {
    "tape": "^4.6.3"
  "dependencies": {
    "gulp": "github:gulpjs/gulp#4.0"

I think it’s best practice to use the same value for the name property as for the main file. In this example, the name is example-cli. Installing this tool globally by running the following snippet

$ npm install -g example-cli

would result in the following directory structure (wherever your global npm modules are installed, run npm get prefix to see where):

|-- bin
|   \ -- example-cli
|     -- some-other-cli
|-- lib
|   \ -- node_modules
|       \ -- example-cli/
|         -- some-other-cli/

Then by entering example-cli in your terminal, bin/example-cli would reference lib/example-cli/bin/example-cli.


It’s important to be aware of the main differences between the package.json fields dependencies and devDependencies:

A module under devDependencies will not be installed whereas all modules under dependencies will be installed when a user runs npm install -g example-cli.

In my case, gulp, which is usually installed under devDependencies, was installed under dependencies since pure-html depends on it at runtime.

You can read more about package.json here.


The npm-shrinkwrap.json file ensures a consistent tree of dependencies is installed. This is especially important in node projects since they often depend on many other modules. To generate npm-shrinkwrap.json run the following command:

$ npm shrinkwrap

In case a file called package-lock.json exists, it will rename that file to npm-shrinkwrap.json. One of the differences between npm-shrinkwrap.json and package-lock.json is that package-lock.json is ignored when you publish your module to the npm registry.


Here you put everything that you don’t want to get published. In my case, I had two entries:

  1. A config file I used for development,
  2. and the test folder.


This is the main point of entry, the file which will be executed once the user enters example-cli in the terminal. Other than your business logic, make sure the following line is found at the top of the file:

#!/usr/bin/env node

It tells the terminal that node should be executing your CLT.

Before Publishing

To ensure the tool works, I recommend running the following command:

$ npm install . -g

inside the example-cli directory. Afterward, check that you can execute the CLT from some other directory.

Additionally, if you want to peruse what will be published, run the following command:

$ npm pack

This will produce an archive of your project, for instance, example-cli-0.1.0.tgz, assuming our node project name is example-cli and the version is 0.1.0. The contents of example-cli-0.1.0.tgz will be the same as the one published to the npm registry.

The End

Run npm publish and enjoy the fruits of your labor.