DEV Community

Pablo Garcia
Pablo Garcia

Posted on

Creating a CLI tool using NodeJS and npm (part 2)

September 21, 2022

In Creating a CLI tool using NodeJS and npm (part 1) 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 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), have access to the latest 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) for Node CLI minimum viable product):

./package.json

{
  "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"
+ }
}
Enter fullscreen mode Exit fullscreen mode

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

./index.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.
}
Enter fullscreen mode Exit fullscreen mode

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 to help us define the behavior of our CLI tool with ease.

Usage

Now we can build and run the tool like:

$ npm run build
$ node ./dist/index.js
Enter fullscreen mode Exit fullscreen mode

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

$ 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
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)