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

Jeroen Ouwehand
10-12-2018

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

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 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 chalk commander path --save

devDependencies

  • types/node — TypeScript definitions for Node.js
  • nodemon — Simple monitor script during 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 you 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 test—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",
  "create": "npm run build && npm run test",
  "build": "tsc -p .",
  "test": "sudo npm i -g && pizza",
  "refresh": "rm -rf ./node_modules ./package-lock.json && npm install"
},

TSconfig

For our CLI we 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 thetop 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 pizzacommand). 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, 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 .

<br />
<pre id="f680" class="graf graf–pre graf-after–p">program<br />
  .version(&#039;0.0.1&#039;)<br />
  .description("An example CLI for ordering pizza&#039;s")<br />
  .option(&#039;-p, –peppers&#039;, &#039;Add peppers&#039;)<br />
  .option(&#039;-P, –pineapple&#039;, &#039;Add pineapple&#039;)<br />
  .option(&#039;-b, –bbq&#039;, &#039;Add bbq sauce&#039;)<br />
  .option(&#039;-c, –cheese <type>&#039;, &#039;Add the specified type of cheese [marble]&#039;)<br />
  .option(&#039;-C, –no-cheese&#039;, &#039;You do not want any cheese&#039;)<br />
  .parse(process.argv);<br />

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

</p>
<pre id="7907" class="graf graf--pre graf-after--p">  _ __   (_)  ____  ____   __ _            ___  | | (_)
 | '_ \  | | |_  / |_  /  / _` |  _____   / __| | | | |
 | |_) | | |  / /   / /  | (_| | |_____| | (__  | | | |
 | .__/  |_| /___| /___|  \__,_|          \___| |_| |_|
 |_|
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

<h4 id="8057" class="graf graf–h4 graf-after–pre">The last part</h4>
<p id="3c84" class="graf graf–p graf-after–h4">We want the users to see what they have ordered, and see their options be updated after they have made different choices.</p>

</p>
<pre id="f189" class="graf graf--pre graf-after--p">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);

</pre>
<p id="eee0" class="graf graf--p graf-after--pre">With this code users can use <code>pizza -h</code> to get information about our options.</p>
if (!process.argv.slice(2).length) {
  program.outputHelp();
}

<p id="74a7" class="graf graf--p graf-after--pre">After we got all our code in the <code>index.ts</code> we can run <code>npm run create</code> to test our CLI in the command line. You will see our end result:</p>

</p>
<pre id="ea23" class="graf graf--pre graf-after--p">          _                                      _   _
  _ __   (_)  ____  ____   __ _            ___  | | (_)
 | '_ \  | | |_  / |_  /  / _` |  _____   / __| | | | |
 | |_) | | |  / /   / /  | (_| | |_____| | (__  | | | |
 | .__/  |_| /___| /___|  \__,_|          \___| |_| |_|
 |_|
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

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 information, same way like the command: npm init for example.

Thank you for reading! My Github.

LEAVE A REPLY

you might also like