Over 2,000 mentors available, including leaders at Amazon, Airbnb, Netflix, and more. Check it out
Published

How To Create Your Own TypeScript CLI — With Node.js

Get ready to learn to make a simple ‘pizza’ CLI with this guide
Jeroen Ouwehand

Software Engineer | Front-end Developer

Image

In this guide will we make a small pizza CLI in TypeScript with Node.js. After following all the steps you will have a completely working CLI, get an idea of how you set up one, and maybe create a custom one for yourself.

Start by creating a package.json and tsconfig.json

First, we going to initialize a package.json with npm init. You can choose for yourself a name, author, version, description, keywords, and license.

dependencies

  • clear — Clears our terminal screen
  • figlet — Get a nice ASCII art from a string
  • chalk — Terminal string styling is done right
  • commander — Make node.js command-line interfaces easy
  • path — Node.JS path module

We need to install all our dependencies:

npm i clear figlet [email protected] commander path --save

devDependencies

  • types/node — TypeScript definitions for Node.js
  • nodemon — Simple monitor script during the development of a node.js app
  • ts-node — TypeScript execution environment and REPL for node.js
  • typescript — A language for application-scale JavaScript development

Followed by installing our devDependencies:

npm i @types/node nodemon ts-node typescript --save-dev

Bin and main

In our package.json we need to set the entry point of our app (main and bin). This will be our compiled index.js file in the lib folder: ./lib/index.js .

The word pizza is the command which you use to eventually call your CLI.

"main": "./lib/index.js",
"bin": {
  "pizza": "./lib/index.js"
}

Scripts

Now we need some scripts to make it easy for ourselves. We have five scripts:

  • npm start— you can watch your CLI right away
  • npm run create — runs our build and test script together.
  • npm run build—compiles our TypeScriptindex.ts file to index.js and index.d.ts
  • npm run local—Installing our CLI globally with sudo npm i -g and followed by firing our pizza CLI command.
  • npm run refresh—removes the node modules, package-lock.json and runs npm install.

Paste the following into the package.json :

"scripts": {
  "start": "nodemon --watch 'src/**/*.ts' --exec 'ts-node' src/index.ts",
  "start:windows": "nodemon --watch 'src/**/*.ts' --exec \"npx ts-node\" src/index.ts",
  "create": "npm run build && npm run test",
  "build": "tsc -p .",
  "local": "sudo npm i -g && pizza",
  "refresh": "rm -rf ./node_modules ./package-lock.json && npm install"
},

TSconfig

For our CLI we have some TypesSript configurations set in a file named tsconfig.json , create this file in the root and copy the following configurations into it:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "lib": ["es6", "es2015", "dom"],
    "declaration": true,
    "outDir": "lib",
    "rootDir": "src",
    "strict": true,
    "types": ["node"],
    "esModuleInterop": true,
    "resolveJsonModule": true
  }
}

Now let’s start creating the CLI

Environment

Create a file named index.ts in thesrc folder. At the top of our index.ts file we have:

#!/usr/bin/env node

“This is an instance of a shebang line: the very first line in an executable plain-text file on Unix-like platforms that tells the system what interpreter to pass that file to for execution, via the command line following the magic #! prefix (called shebang).” — Stack Overflow

Imports

Then we need some imports to make use of our dependencies:

const chalk = require('chalk');
const clear = require('clear');
const figlet = require('figlet');
const path = require('path');
const program = require('commander');

A nice banner

Next, we call clear(to clear our command line every time we call our pizza command). Then we want to console log a big banner: a red-colored text (pizza-cli’), by using of figlet and chalk.

clear();
console.log(
  chalk.red(
    figlet.textSync('pizza-cli', { horizontalLayout: 'full' })
  )
);

This will be looking like this:

 _ __   (_)  ____  ____   __ _            ___  | | (_)
 | '_ \  | | |_  / |_  /  / _` |  _____   / __| | | | |
 | |_) | | |  / /   / /  | (_| | |_____| | (__  | | | |
 | .__/  |_| /___| /___|  \__,_|          \___| |_| |_|
 |_|

Our CLI with options

Now we came to the part where we can make our CLI interactive. We make use of program . We can set here our CLI version, description, and various options and parse the result. The options contain a short and a long variant, example: for adding peppers we can use pizza -p or pizza --peppers .

program
  .version('0.0.1')
  .description("An example CLI for ordering pizza's")
  .option('-p, --peppers', 'Add peppers')
  .option('-P, --pineapple', 'Add pineapple')
  .option('-b, --bbq', 'Add bbq sauce')
  .option('-c, --cheese <type>', 'Add the specified type of cheese [marble]')
  .option('-C, --no-cheese', 'You do not want any cheese')
  .parse(process.argv);</type>

To see what we currently have, run npm run build followed by npm start , you will see this:

  _ __   (_)  ____  ____   __ _            ___  | | (_)
 | '_ \  | | |_  / |_  /  / _` |  _____   / __| | | | |
 | |_) | | |  / /   / /  | (_| | |_____| | (__  | | | |
 | .__/  |_| /___| /___|  \__,_|          \___| |_| |_|
 |_|
Usage: pizza [options]
An example CLI for ordering pizza's
Options:
  -V, --version        output the version number
  -p, --peppers        Add peppers
  -P, --pineapple      Add pineapple
  -b, --bbq            Add bbq sauce
  -c, --cheese <type>  Add the specified type of cheese [marble]
  -C, --no-cheese      You do not want any cheese</type>

The last part

We want the users to see what they have ordered, and see their options be updated after they have made different choices.

console.log('you ordered a pizza with:');
if (program.peppers) console.log('  - peppers');
if (program.pineapple) console.log('  - pineapple');
if (program.bbq) console.log('  - bbq');
const cheese: string = true === program.cheese ? 'marble' : program.cheese || 'no';
console.log('  - %s cheese', cheese);

With this code users can use pizza -h to get information about our options.

if (!process.argv.slice(2).length) {
  program.outputHelp();
}

After we got all our code in the index.ts we can run npm run create to test our CLI in the command line. You will see our end result:

         _                                      _   _
  _ __   (_)  ____  ____   __ _            ___  | | (_)
 | '_ \  | | |_  / |_  /  / _` |  _____   / __| | | | |
 | |_) | | |  / /   / /  | (_| | |_____| | (__  | | | |
 | .__/  |_| /___| /___|  \__,_|          \___| |_| |_|
 |_|
you ordered a pizza with:
  - marble cheese
Usage: pizza [options]
An example CLI for ordering pizza's
Options:
  -V, --version        output the version number
  -p, --peppers        Add peppers
  -P, --pineapple      Add pineapple
  -b, --bbq            Add bbq sauce
  -c, --cheese <type>  Add the specified type of cheese [marble]
  -C, --no-cheese      You do not want any cheese
  -h, --help           output usage information</type>

Publish to NPM?

You can choose to publish your CLI to npm, I’ve chosen to call the name of the project ‘pizza-cli’ in package.json . If I would run npm publish , it will be published to the npm registry (but I didn’t, but you could). Other people could install my project globally by running npm i pizza-cli -g , and then use the pizza command to get the CLI up and running!

A cool feature to add by yourself would be by using: Inquirer. With this, you can ask questions to users to fill in the information, the same way as the command: npm init for example.

Find an expert mentor

Get the career advice you need to succeed. Find a mentor who can help you with your career goals, on the leading mentorship marketplace.