DEV Community

Pablo Garcia
Pablo Garcia

Posted on

Creating a CLI tool using NodeJS and npm.

August 5, 2019

TLDR at the end of the blog post.

Introduction

If you are like me, you want to read less and do more. I'll try to be brief.

We are going to build a simple CLI tool that can be installed globally using npm or executed without prior installation using npx. This tool will simply log "Hello World!" and I'll name it cli-tool.

In other words, the goal is to be able to run npx cli-tool or npm i -g cli-tool && cli-tool.

MVP (Minimum Viable Product)

Show me the code!

The simplest CLI tool consists of 3 steps:

  1. package.json (must include "name" and "bin")
  2. index.js (must include the node shebang)
  3. npm link
{
  "name": "cli-tool",
  "bin": "./path/to/bin.js"
}
Enter fullscreen mode Exit fullscreen mode
#!/usr/bin/env node
console.log("Hello World!")
Enter fullscreen mode Exit fullscreen mode

We can now run npm link inside the repo and enjoy running cli-tool in the terminal. Note, you might want to run npm unlink if you want to revert this.

How does it work? npm link grabs the name from package.json and creates a symlink to the global modules. You can read more here.

MVP + npm + npx

We could also publish our module using npm publish. We would have to add "version" and "fields" props to package.json.

{
  "name": "cli-tool",
  "version": "1.0.0",
  "bin": "./path/to/bin/bin.js",
  "fields": ["./path/to/bin"]
}
Enter fullscreen mode Exit fullscreen mode

Note: I modified the bin path to make this easier to understand.

The version will be displayed in npmjs.com when published and the fields property is a list of whitelisted paths to include. Meaning, only those fields will be uploaded. You can read more about the "fields" property here.

Now, after publishing our module in npmjs.com, running npx cli-tool or npm i -g cli-tool && cli-tool is possible.

Conclusion / What next?

The MVP is very important! It shows us our goal. No matter what we do, we MUST end with a package.json and a binary (node script).

Now that we know the goal, we can use bundlers like Webpack, Rollup, or Brunch to use the latest ECMAScript features and bundle the app into our ./path/to/bin directory.

We can also use tools that will help us define the behavior of our CLI tool, like Commander or Yargs.

And lastly, we can use tools that allow us to release the module without all the groin pains of keeping track of the version, changes, tags, and more, like release-it and release.

I myself do not like to rebuild the wheel. I love create-react-app and there is this tool which uses Rollup and create-react-app internally to create react libraries, this tool is called create-react-library. I simply remove the unneeded dependencies (i.e. React) and make sure that package.json contains what is needed for the module to run as a binary.

But all this is not part of this blog post. I will release part 2 where I add these tools.

TLDR

  1. npm init
  2. add "name", "bin", "version", and "fields" to package.json
  3. add node "shebang" to the JavaScript script
  4. npm publish
  5. npx cli-tool or npm i -g cli-tool && cli-tool

End result:

package.json

{
  "name": "cli-tool",
  "version": "1.0.0",
  "bin": "./path/to/bin/bin.js",
  "fields": ["./path/to/bin"]
}
Enter fullscreen mode Exit fullscreen mode

./path/to/bin/bin.js

#!/usr/bin/env node
console.log("Hello World!")
Enter fullscreen mode Exit fullscreen mode

Top comments (0)