blogabout melegal
kobs.IT

How to write a Command Line Tool for Node.js

Friday, September 5th, 2014

3 years ago

With Node.js you can do many things. For example Command Line Tools like Docco and Cleaver. I wanted to write something similar to these programs, but how?

This Tutorial should give you an overview on writing a Node tool, and what were the problems I faced, when I created my first one.

Folder structure

You need at least three files to make a complete command line tool:

In the following, we will discuss every of this files in more detail.

For now, let's just focus on the file organisation:

my-awesome-node-tool
|- bin
|  |- name-of-your-tool
|- README.md
|- package.json

Note: The name-of-your-tool file doesn't have a file extension and it is inside of a bin folder. You don't have to do this, but in other popular tools this is the common structure. You don't even have to name this file after your tool name.

Let's go through every file.

The package.json file

You can start your project by cd in the root folder and run

$ npm init

There will be some questions and some good guesses. After you gone through all the questions, your package.json will be created.

It's a good starting point, but we need to make some additions to create our command line tool.

Add this to your package.json file:

"preferGlobal": "true",
"bin": {
    "your-command": "./bin/name-of-your-tool"
}

your-command is the command you want to specify for your tool. This should be a unique name. To check, whether a command is set or not, run $ which your-command. If it doesn't return anything, it is not taken.

Maybe you can now see, why the name-of-your-tool file doesn't have an extension and is stored in a folder called bin. The json key to set a command is bin, so it is a good idea to put your file in this path. Secondly, most of the times, your-command is the same as name-of-your-tool. That means: When you specify more than one command, you can see in your file organisation structure, what files will be executed by what command.

In my first command line tool, I used the file extension .js for my executable file. That's because I use docco for documenting my code and it didn't work for a file without an extension.

The name-of-your-tool file

This file contains your JavaScript code.

It's first line needs to be:

#!/usr/bin/env node

This tells the system, that we want to execute this script with Node. That is necessary. Why? Now, we need to see how the system works:

When we specify a command in our package.json file, this command will be linked to the given script. When we run the command later in our Terminal/CMD, the file will be executed. If we don't specify Node as the runtime environment, our system will try to execute this script as a bash file. This will crash, because JS isn't Bash. That's why we need the first line.

To make the file executable, you need to run

chmod +x bin/name-of-your-tool

The rest of this file will be your code. You can require other modules, print something via console.log or react to options or commands with help of Commander.js. This is a very easy to use package. The documentation is good enough for a starting point. Maybe there will be a future tutorial on this package as well.

The README.md file

This file is written in Markdown. It describes the fundamentals of your tool, like how to setup and use it.

This file will be rendered and displayed on your package's npm page to give any user an impression on what your tool can and what they can expect from it.

Accessing other files in package

Remember: When we run our specified command inside of a folder, relative links (for example in a fs.readFile call) will not work to access files in your package directory! They will be applied to your current directory.

To access files in your package, you can use something like this:

var dir = path.join(path.dirname(fs.realpathSync(__filename)), '../');

If this is in your name-of-your-tool file, dir will point to the directory above, the package root directory.

From there, you can navigate to all files inside of your package.

Note: require will work locally, so you could use require("../package.json").version to get the program's version number.

Testing

When you want to test your application without installing or publishing it, you can use the following command:

$ npm link

This will link the command you specified in your package.json file to the script without installing it (that means copy all files to the directory, where all globally installed package are stored).

You could just use $ npm install -g to do that, but then, you would need to do this everytime you change something. That would be horrible. Linking won't need you to do this. It's like your package directory is in the global installation folder.

Publishing

Publishing is really easy. First, you need an npm user account. You can create one at www.npmjs.org or you run

$ npm adduser

in your command line and follow the steps. This way, you don't have to login again, when you want to publish your tool.

When you're in your folder, have everything set up, a README.md, a package.json, and the most important, a tool worth to deploy, you run

$ npm publish

Easy, right? Your package will be uploaded to npm's servers and is available for installing by all users.

Installing

Your users can install your application after publishing by running this command:

$ npm install -g name-of-your-tool

They have to add the -g option, because this installation should run globally on the computer.

Updating

Updating your package is very easy as well. You just need to increase the version number in your package.json file and then run

$ npm publish

again. The new version will be uploaded. All your users can then update their version of your tool by running

$ npm update -g name-of-your-tool

Helpful links

Here some links that helped me alot with starting to develop Node command line packages:

I found a lot more sites and tutorials, but these two were the best of them. Together with some peek into the source of Docco and Cleaver, I learned something about the structure. This is what I wanted to share with you.