Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Akash Shyam
Akash Shyam

Posted on

     

A Guide to CLIs with Node.js

Ever wondered howcreate-react-app . orgit init or simplynode -v works? These are CLIs or Command Line Interfaces. I'm pretty sure most of us have used CLIs at some point of time in our lives. Even commands that we use everyday likels orcd are also CLIs.

Today, I'm going to show you how to set up a very simple CLI with some of the common features we find in CLIs. Nothing too complex... Let's dive right into CLIs

What are CLIs

CLIs are commands that we run in our terminal to do something. If you want a wikipedia definition 🙄 -

A command-line interface processes commands to a computer program in the form of lines of text. The program which handles the interface is called a command-line interpreter or command-line processor. Operating systems implement a command-line interface in a shell for interactive access to operating system functions or services.

Why are CLIs necessary?

In the modern world of GUIs(Graphical User Interfaces), you might ask that why should we know about CLIs? Weren't they used in the 80s? I agree with you a 💯 percent. They are outdated but a lot of old applications still use CLIs. The terminal/command prompt generally has more permissions and access compared to GUI applications by default(It's bad user experience to allow 100 permissions to run an app).

Why build CLIs with Node

The main advantage is the ecosystem of over 1 million packages we get with Node. Using these we can avoid boilerplate code and implement functionality easily.

Getting Started with CLIs

Before doing anything else, we need to create apackage.json.

  1. Create an empty folder
  2. Runnpm init and quickly fill in the options.

Let's create our javascript file, I'm naming itcli.js. This is a common convention which is followed as the file immediately tells us about its function. I'm going to add aconsole.log('CLI') to our file so that we can know that everything is working.

We need to update thepackage.json to run our CLI. Add thebin property into the file.

"bin": {  "hello-world": "cli.js"}
Enter fullscreen modeExit fullscreen mode

Thehello-world property is the command name we want to run and thecli.js is the filename. If you want to use another name or your file is stored in a different path, update it accordingly.

Let's install our NPM package. In your terminal/command prompt run the following -

npm i -g .
Enter fullscreen modeExit fullscreen mode

We all have come acrossnpm install in the past. We add the-g flag so that NPM installs the package globally. The. tells NPM to install the package. NPM installs the file that is there in themain property of thepackage.json. If you have changed

Now we can run the command name we set earlier(hello-world) and our CLI should boot up.

hello
Enter fullscreen modeExit fullscreen mode

However, we get the following error -

Screenshot 2021-03-05 at 08.49.55

The error seems to tell us that compiler is not able to understand that we have javascript code. So, to tell the compiler that it should usenode to run our file. For this, we use ashebang. Add the following code at the top of your file.

#!/usr/bin/env node
Enter fullscreen modeExit fullscreen mode

This is the path to node. We tell *nix systems that the interpreter of our file should be at the specified path. IN windows, this line will be be ignored as it is specified as a comment but NPM will pick it up when the package is being installed.

There we go, now it should work.

Screenshot 2021-03-05 at 09.05.06

We have not actually done anything in our CLI except logging some data. Let's begin by implementing common features in CLIs.

Prompts

Prompts are questions that are asked to the user. You might have come across it in Javascript by calling theprompt function. I'm going to be using theprompts package. This package simplifies allows us to prompt the user and get the response with just a few lines of code.

Install it by running -

npm i prompts
Enter fullscreen modeExit fullscreen mode

Next, add the following code into your file and we will walkthrough it together.

const prompts = require('prompts');// IIFE for using async functions(async () => {  const response = await prompts({    type: 'number',    name: 'value',    message: 'Enter your name',   });  console.log(response.value); // the name the user entered.})();
Enter fullscreen modeExit fullscreen mode

Note: IIFE's are function expressions that are immediately called. It shortens saving the function expression to a variable and the calling it.

Our basic CLI should look like this -

Alt Text

Awesome right? I think the terminal looks a bit dull, let's colour it up!

Colours with Chalk

Do you know how colours are added into terminals? ANSI escape codes are used for this(sounds like a code for a prison). If you've ever tried to read binary, reading ANSI is quite similar.

giphy

Thankfully, there are packages that help us with this. I'm going to usechalk for the purpose of this tutorial.

Install it by running -

npm i chalk
Enter fullscreen modeExit fullscreen mode

Let's get started by replacing the originalconsole.log() with the following.

console.log(chalk.bgCyan.white(`Hello ${response.value}`)); console.log(chalk.bgYellowBright.black(`Welcome to "Hello World with CLIs"`));
Enter fullscreen modeExit fullscreen mode

As you can see, the firstlog chains thebgCyan and thewhite methods. Check theofficial documentation to know more.

After this, our code looks like this -

const response = await prompts({  type: 'text',    name: 'value',    message: 'What is your name?',});console.log(chalk.bgCyan.white(`Hello ${response.value}`)); console.log(chalk.bgYellowBright.black(`Welcome to "Hello World with CLIs"`));
Enter fullscreen modeExit fullscreen mode

Here's a demo of our CLI -
Screenshot 2021-03-04 at 21.02.09

That's a good start, now let's bump up the font size. I can barely see anything.

giphy (1)

Playing with Fonts

Changing fonts are quite difficult through javascript and I spent a lot of time searching for a package that does just that. Thankfully, I got it. We'll be using thecfonts package. Go ahead and install it by running the following -

npm i cfonts
Enter fullscreen modeExit fullscreen mode

Begin by replacing our previousconsole.log with the following -

CFonts.say(`Hello ${response.value}`, {  font: 'tiny',  colors: ['cyanBright'],});CFonts.say(`Welcome to Hello World with CLIs`, {  font: 'simple',  colors: ['cyanBright'],});
Enter fullscreen modeExit fullscreen mode

Our Hello World CLI looks like this -

Screenshot 2021-03-04 at 22.25.23.

Let's add some basic arguments like-n or--name and-v or--version. We'll be using theyargs package to simplify the process. If you don't want to use it, you can useprocess.argv to access the arguments.

Begin by installing theyargs package.

npm i yargs
Enter fullscreen modeExit fullscreen mode

Let's import it into our code -

const yargs = require('yargs/yargs');const { hideBin } = require('yargs/helpers');const argv = yargs(hideBin(process.argv)).argv;
Enter fullscreen modeExit fullscreen mode

Theargv stands for all the arguments specified by the user. We use thehideBin() method to remove the-- in front of arguments. Example -

hideBin('--example'); // output -> example
Enter fullscreen modeExit fullscreen mode

Now we can add a series ofIF and ELSEIF checks for the arguments provided. You can also use aSwitch statement.

const packageJson = require('./package.json');if (argv.version || argv.v) {    console.log(packageJson.version);} else if (argv.name || argv.n) {    console.log(packageJson.name);} else if (argv.init) {  // Put our functionality of printing hello world, asking name etc.} else {  console.log('Please specify an argument/command');}
Enter fullscreen modeExit fullscreen mode

Since package.json is essentially a javascript object, we can pull off the properties from it.yargs helpfully provides an object of the specified arguments. We can check if the required properties. There's also anelse check to see if the user has not given any arguments. Now let me show you some other common features in CLIs.

Loaders

Asynchronous and time consuming operations are very common in CLIs. We don't want to leave the user thinking his computer has hung up. We'll use theora package for loaders. Begin by installingora -

npm i ora
Enter fullscreen modeExit fullscreen mode

Let's require the package and setup a simple loader -

const ora = require('ora');const spinner = ora('Loading...').start();
Enter fullscreen modeExit fullscreen mode

This loader will run forever because we haven't stopped it. We don't have any code that takes a substantial amount of time(API requests, data processing etc), We'll usesetTimeout() to simulate the end of loading.

const spinner = ora('Loading...').start();setTimeout(() => {    spinner.stop();    (async () => {        const response = await prompts({            type: 'text',            name: 'value',            message: 'What is your name?',        });        CFonts.say(`Hello ${response.value}`, {            font: 'tiny',            colors: ['cyanBright'],        });        CFonts.say(`Welcome to Hello World with CLIs`, {            font: 'simple',            colors: ['cyanBright'],        });    })();}, 1000);
Enter fullscreen modeExit fullscreen mode

I've put our IIFE function inside oursetTimeout() so that it gets executed only after the loader finishes. Our loader runs for2000 milliseconds or2 seconds.

Let's do a quick refactor. We have a callback in oursetTimeout() which can be madeasync. We can now remove theIIFE function.

setTimeout(async () => {  spinner.stop();  // Body of IIFE function goes here.}, 2000);
Enter fullscreen modeExit fullscreen mode

There's a lot lot more to do withora, check out theofficial docs to know more.

Lists

Remember theIF ELSEIF we had setup for arguments? Let's add a--help argument that lists out all the commands. Lists are a very important part of CLIs. I'm using a helpful packagelistr to handle these lists. Let's add the following code into our file in the place where we have ourIF checks.

else if (argv.help) {  const tasks = new Listr([    {      title: '--init: Start the CLI',    },    {      title: '--name: Gives the name of the package',    },    {      title: '--version: Gives the version of the package',    },    {      title: '--help: Lists all available commands',    },  ]);  tasks    .run()    .then(() => {      console.log('Done');    })    .catch((error) => {    console.log(error);    });}
Enter fullscreen modeExit fullscreen mode

We have created a new if check for--help. Inside that, we passed an array of tasks into theListr class. THis will basically list the following. This can be done byconsole.log() but the reason I have usedlistr is because in each object in the tasks array, we can also specify a task property with an arrow function. Check out thedocumentation to know more.

Now our CLI looks like this -
Alt Text

Executing Commands from Code

A lot of CLIs will want to access other CLIs and run commands such asgit init ornpm install etc. I'm using the packageexeca for the purpose of this tutorial. Begin by installing the module.

npm i execa
Enter fullscreen modeExit fullscreen mode

Then, require the module at the top of the file.

const execa = require('execa');
Enter fullscreen modeExit fullscreen mode

In theELSE block for our arguments condition, add the following code. If you remember, we already had aconsole.log() asking us to specify a command. Let's run our previous--help command and list out the available commands.

else {  console.log('Please specify a command');  const { stdout } = await execa('hello-world', ['--help']);  console.log(stdout);}
Enter fullscreen modeExit fullscreen mode

We call theexeca() function and pass in the name of the command. Then, in an array we pass in the arguments to be provided. We willawait this and then destructure the output. Then we will log the output to simulate running the command.

Our CLI should finally look like this -

Alt Text

That's it guys, thank you for reading this post. I hope you guys liked it. If you found the post useful or liked it, please follow me to get notified about new posts. If you have questions, ask them in the comments and I'll try my best to answer them. As a bonus, I have made a list of some packages that can be used while developing CLIs.

Helpful Packages

These are a list of packages that might help you while developing CLIs. Keep in mind that this is by no means an exhaustive list. There are over 1 million packages on NPM and it is impossible to cover them all.

Inputs

Coloured Responses/Output

Loaders

Boxes Around Output

Top comments(4)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
bmorelax profile image
bmorelax
  • Joined

I realize we all start somewhere, and that is about where I am, conceptually speaking. This has been informative and helpful. Thank you!

CollapseExpand
 
akashshyam profile image
Akash Shyam
Full-stack aficionado | Enhancing SaaS & indie maker products and helping them excel 🔥

Glad to know you liked it.

CollapseExpand
 
eidellev profile image
Lev Eidelman Nagar
Senior front end engineer @LinearB
  • Location
    Israel
  • Education
    Self-taught
  • Work
    Senior front end engineer - LinearB
  • Joined

That's a great introduction to CLIs. If anyone is looking for a more complete solution I strongly recommend checking oclif:
oclif.io/

CollapseExpand
 
akashshyam profile image
Akash Shyam
Full-stack aficionado | Enhancing SaaS & indie maker products and helping them excel 🔥
• Edited on• Edited

Thank you for sharing the resource!

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Full-stack aficionado | Enhancing SaaS & indie maker products and helping them excel 🔥
  • Location
    India
  • Joined

Trending onDEV CommunityHot

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp