Creating a CLI tool using NodeJS and npm.
Tags: javascript, node, npm, npx
Published: 2019-08-06 06:18:30

**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!](https://github.com/pgarciacamou/pgc--cli-tool/pull/1)

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`


```json
{
  "name": "cli-tool",
  "bin": "./path/to/bin.js"
}
```

```js
#!/usr/bin/env node
console.log("Hello World!")
```

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](https://docs.npmjs.com/cli/link).

## MVP + npm + npx

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

```json
{
  "name": "cli-tool",
  "version": "1.0.0",
  "bin": "./path/to/bin/bin.js",
  "fields": ["./path/to/bin"]
}
```

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](https://medium.com/@jdxcode/for-the-love-of-god-dont-use-npmignore-f93c08909d8d).

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](https://webpack.js.org/), [Rollup](https://rollupjs.org/guide/en/), or [Brunch](https://brunch.io/) 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](https://github.com/tj/commander.js#readme) or [Yargs](https://yargs.js.org/).

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](https://www.npmjs.com/package/release-it) and [release](https://www.npmjs.com/package/release).

I myself do not like to rebuild the wheel. I love [create-react-app](https://facebook.github.io/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](https://github.com/transitive-bullshit/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
3. `npm publish`
4. `npx cli-tool` or `npm i -g cli-tool && cli-tool`

End result:

`package.json`

```json
{
  "name": "cli-tool",
  "version": "1.0.0",
  "bin": "./path/to/bin/bin.js",
  "fields": ["./path/to/bin"]
}
```

`./path/to/bin/bin.js`

```js
#!/usr/bin/env node
console.log("Hello World!")
```