Creating a CLI tool using NodeJS and npm (part 2)
Tags: typescript, javascript, node, npm
Published: 2022-09-21 20:36:22

**September 21, 2022**

In [Creating a CLI tool using NodeJS and npm (part 1)](https://dev.to/pgarciacamou/creating-a-cli-tool-using-nodejs-and-npm-315) I talked about the bare minimum requirements to create a CLI tool with npm and NodeJS. 

In summary, our goal is to have two files:

* `package.json` that includes `"name"`, `"bin"`, `"version"`, and `"fields"`.
* `path/to/bin/index.js` (must include the node "shebang")

In this blog post, I want to talk about how to quickly start using the latest [ECMAScript](https://developer.mozilla.org/en-US/docs/Glossary/ECMAScript) features without worrying about setting up bundlers of any kind such as webpack or rollup and make it 100% compatible across platforms.

How?

## Using TypeScript!

Using TypeScript, we can bundle our code into a JavaScript "binary" ([not really a binary](https://en.wikipedia.org/wiki/Just-in-time_compilation)), have access to the latest [ECMAScript](https://developer.mozilla.org/en-US/docs/Glossary/ECMAScript) features, and export types to make it easy for other developers to work with our tool.

First, we modify our `package.json` to have types, and a build script (see [Creating a CLI tool using NodeJS and npm (part 1)](https://dev.to/pgarciacamou/creating-a-cli-tool-using-nodejs-and-npm-315) for Node CLI minimum viable product):

`./package.json`

```diff
{
  "name": "cli-tool",
  "version": "1.0.0",
  "fields": ["./dist/"],
  "bin": "./dist/index.js",
+ "types": "./dist/index.d.ts",
+ "script": {
+   "build": "rm -rf dist && tsc index.ts --declaration --allowJs --outDir dist"
+ }
}
```

Second, we create an `index.ts` file that will have our source code:

`./index.ts`

```ts
#!/usr/bin/env node

export default function ourTool(options: { message: string }) {
  console.log(`Hello ${options.message}`);
}

if (require.main === module) {
  // run directly from the command line as in "node xxx.js"
  const program = require("commander");
  const defaults = { message: "World" };

  program
    .version("0.1.0")
    .option("-m, --message [value]", "Message", defaults.message)
    .parse(process.argv);

  const options = program.opts();

  ourTool(options);
} else {
  // probably loaded by as a module or something else.
}
```

Notice that we use `require.main === module` to detect if the script is being executed as a command, otherwise we assume it is being imported as a module.

Also, I'm using [`commander`](https://www.npmjs.com/package/commander) to help us define the behavior of our CLI tool with ease.

## Usage

Now we can build and run the tool like:

```bash
$ npm run build
$ node ./dist/index.js
```

Our you could also publish it to https://www.npmjs.com/ and install it our use it with `npx`:

```bash
$ npm i -g cli-tool

$ cli-tool --version
0.1.0

$ cli-tool
Hello World

$ cli-tool -m Pablo
Hello Pablo

$ npx cli-tool --help
Usage: cli-tool [options]

Options:
  -V, --version          output the version number
  -m, --message [value]  Message (default: "World")
  -h, --help             output usage information
```

## Conclusion / What next?

Being able to use a single tool (i.e. TypeScript) to create an MVP and _set us up and running_ is essential and underrated for a power developer.

Also, because we avoid magic (Babel, Webpack, Rollup, ...) and focus only on using a **compiler** to bundle our code, it's now easier to understand the barebones needed for NodeJS to work. This is extremely helpful for beginner NodeJS developers.