Crafting a Basic Command Line Tool with Node.js

Empowering Your Projects with Custom Command-Line Tools ā€” A Hands-On Guide

Sagini Navaratnam
LinkIT
7 min readFeb 15, 2024

--

Artwork by author

Have you ever wondered how those cool command-line tools are builtšŸ¤”? In this practical guide, I'm going to take you through a way to build a basic CLI using Node.js and publish to npm. Letā€™s dive in and transform your code into powerful commands that you can run from your terminal with ease!

Prerequisites

  1. Node.js installed.
  2. VS code or a text editor

The code I am going to use here is available here. Letā€™s first set up a basic node.js project:

Setting Up the Basic Node.js Project

  1. Open the terminal
  2. Create a folder for project

mkdir cli

3. Navigate to the folder

cd cli

4. Initialize the Node.js project

npm init

5. Fill in the prompt

screenshot of prompt
Screenshot of Prompt

Creating a Basic CLI

  1. Create a folder named ā€˜srcā€™ in the root directory of your project.
  2. Inside the ā€˜srcā€™ folder, create a file named ā€˜index.jsā€™. This file will serve as the entry point for your CLI.
  3. Open the ā€˜package.jsonā€™ file in your project and modify the value of ā€œmainā€ attribute from ā€˜index.jsā€™ to ā€˜src/index.jsā€™.
  4. Add a new entry to the ā€˜package.jsonā€™ file. Create a key called ā€˜binā€™ and set its value to an object with the key animal(whatever you wish) and its value as ā€˜./src/index.jsā€™.
  5. Add a key called ā€˜typeā€™, and itā€™s value to ā€˜moduleā€™ inside the package.json. It enables ECMAScript module syntax in Node.js, allowing you to use import and export statements instead of require() calls.

The package.json file should now appear as follows:

{
"name": "cli",
"version": "1.0.0",
"description": "Basic cli ",
"main": "src/index.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"bin": {
"animal": "./src/index.js"
},
"author": "",
"license": "ISC",
}

The key animal serves as the terminal keyword to invoke your CLI. Feel free to change as needed. Itā€™s not a permanent label and can be modified at any time to suit your preferences or project requirements.

Letā€™s make our CLI even more awesome! Open up the ā€˜index.jsā€™ file inside the ā€˜binā€™ folder, and sprinkle in the magic with the following code:

#!/usr/bin/env node
// Greet the world with your first program!
console.log("Hello, World! This is your pet speaking.");

The line#!/usr/bin/env node is called a shebang or hashbang. Itā€™s used in Unix-like operating systems (such as Linux and macOS) to indicate the interpreter that should be used to execute the script that follows.

  • #!: This symbol is called the shebang or hashbang.
  • /usr/bin/env: This is the path to the envcommand, which is a Unix/Linux utility that looks for executables in the userā€™s environment.
  • node: This specifies the interpreter to be used, in this case, Node.js.

So, when you see `#!/usr/bin/env node` at the beginning of a script file, it means that the script should be executed using the Node.js interpreter, regardless of where Node.js is installed on the system. This allows for greater portability of scripts across different environments.

Ready to witness your CLI magic?

npm install -g

Since itā€™s installed globally, you can invoke it from anywhere in your system! Take your CLI for a spinā€”run the command animal in your terminal to see the magic unfold!

Screenshot of Terminal

How to Handle CLI Arguments?

With the foundation of our basic CLI in place, itā€™s time to elevate its capabilities. A fundamental component of any CLI is the ability to handle command-line arguments.

Weā€™ll use the wonderful commander npm package to help us with this process.

  1. Install commander:

npm i commander

2. Supercharge your `index.js` by including the commander module after installing it:

import {program} from ā€œcommanderā€;

3. creating a command that requires arguments:

program
.command("who")
.description("who am i")
.argument("<animal_name>", "name of the animal")
.action((animal_name) => {
console.log(`I am a ${animal_name}`);
});

program.parse(process.argv);
  1. .command("who"): This line defines a new command named "who".
  2. .description("who am i"): This sets the description for the "who" command as "who am i".
  3. .argument("<animal_name>", "name of the animal"): This declares an argument named "<animal_name>" for the "who" command with a description of the argument as "name of the animal".
  4. .action((animal_name) => { console.log(I am a ${animal_name}); }): This specifies the action to be taken when the "who" command is executed. It defines a function that takes the provided "animal_name" argument and logs "I am a [animal_name]" to the console.
  5. program.parse(process.argv): This line tells the program to parse the command-line arguments provided when the script is executed using process.argv.

You can execute through this command animal who ā€˜animal nameā€™ Here is an example:

Screenshot of Terminal Using Command with Argument

How to Create Commands with Flags?

Think of flags like switches you can toggle when youā€™re giving commands to your computer through the command line. Theyā€™re like little helpers that let you customize how a command works by adding extra bits of information.

You can recognize them because they usually start with a hyphen (-) if they're short, like -h for help, or a double hyphen (--) if they're longer like, --help

Letā€™s go through an example:


program
.command("food")
.description("what does animal like to eat")
.requiredOption("-a, --animal <animal_name>", "Name of the animal")
.requiredOption("-f, --food <food_name>", "The food the animal likes")
.action((options) => {
console.log(`${options.animal} likes to eat ${options.food}`);
});
  1. .command("food"): This line defines a new command named "food".
  2. .description("what does animal like to eat"): This sets the description for the "food" command as "what does animal like to eat".
  3. .requiredOption("-a, --animal <animal_name>", "Name of the animal"): This declares a required option -a or --animal for the "food" command, specifying the name of the animal. The <animal_name> indicates that this option expects an argument.
  4. .requiredOption("-f, --food <food_name>", "The food the animal likes"): This declares another required option -f or --food for the "food" command, specifying the food the animal likes. Similarly, <food_name> indicates that this option expects an argument.
  5. .action((options) => { console.log(${options.animal} likes to eat ${options.food}); }): This specifies the action to be taken when the "food" command is executed. It defines a function that takes the provided options (including animal and food specified by the user) and logs the message indicating what the animal likes to eat.

So, when you run this script from the command line with the ā€œfoodā€ command followed by the required options or known as flags-a or --animal and -f or --food, it will execute the specified action and output the message indicating what the animal likes to eat.

Using flags makes your commands super flexible! You could type something like animal food -a cat -f milk or animal food -f milk -a cat you can mix it up however you want! With flags, the order of inputs doesnā€™t matter. But if youā€™re using arguments, then the order you type things in does matter.

Screenshot of Terminal using Command with Flags

Congratulations on creating your CLI tool! šŸŽ‰ Now, letā€™s share it with the world by publishing it to npm. If youā€™d like to provide guidance on how to install and use the commands, itā€™s a great idea to include a readme.md file. You can take idea from this published package made by me.

How to Publish the CLI to NPM?

  1. Ensure that you have an npm account. If you donā€™t have one, you can create it by running npm adduser in your terminal and following the prompts.
  2. Once you have an npm account, navigate to the root directory of your CLI project in your terminal.
  3. Run the command npm login and enter your npm username, password, and email when prompted.
  4. After logging in successfully, run npm publish in your terminal. This will publish your CLI package to the npm registry.

Thatā€™s it! Youā€™ve officially published your CLI tool to npm. Now, go ahead and spread the word.

How to Update the Published Package?

Once youā€™ve published your package, if you ever want to make changes to it, follow these simple steps:

  1. Make your changes: Update your code as needed. Fix bugs, add features, or make any other adjustments you see fit.
  2. Push changes to your repository: After making your changes, donā€™t forget to push them to your repository.
  3. Bump the version: Before publishing your changes, itā€™s a good practice to bump the version of your package. This ensures that users who depend on your package can easily identify that there are new changes available. To bump the version, run: npm version patch This will automatically increment the patch version of your package in your ā€˜package.jsonā€™ file.
  4. Publish your changes: Once youā€™ve bumped the version, youā€™re ready to publish your changes to npm. Run: npm publish This will update your package on the npm registry with the new changes.

And thatā€™s itšŸ¤—! Your changes are now live and available for users to install.

I hope you found this information helpful! Keep coding and exploring new possibilities! Happy codingšŸ’»!

--

--

Sagini Navaratnam
LinkIT
Writer for

Final year undergraduate at University of Moratuwa