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 awaynpm run create
— runs ourbuild
andtest
script together.npm run build
—compiles our TypeScriptindex.ts
file toindex.js
andindex.d.ts
npm run local
—Installing our CLI globally withsudo npm i -g
and followed by firing ourpizza
CLI command.npm run refresh
—removes the node modules, package-lock.json and runsnpm 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.