As great as Node.js is for “traditional” web applications, its potential uses are far broader. Microservices, REST APIs, tooling, working with the Internet of Things and even desktop applications: it’s got your back.
Another area where Node.js is really useful is for building command-line applications — and that’s what we’re going to be doing in this article. We’re going to start by looking at a number of third-party packages designed to help work with the command line, then build a real-world example from scratch.
What we’re going to build is a tool for initializing a Git repository. Sure, it’ll run
git init under the hood, but it’ll do more than just that. It will also create a remote repository on GitHub right from the command line, allow the user to interactively create a
.gitignore file, and finally perform an initial commit and push.
As ever, the code accompanying this tutorial can be found on our GitHub repo.
Why Build a Command-line Tool with Node.js?
Before we dive in and start building, it’s worth looking at why we might choose Node.js to build a command-line application.
Another key advantage, as we’ll see as we go along, is that the strong Node.js ecosystem means that among the hundreds of thousands of packages available for all manner of purposes, there are a number which are specifically designed to help build powerful command-line tools.
Finally, we can use
npm to manage any dependencies, rather than have to worry about OS-specific package managers such as Aptitude, Yum or Homebrew.
Tip: that isn’t necessarily true, in that your command-line tool may have other external dependencies.
What We’re Going to Build: ginit
For this tutorial, we’re going to create a command-line utility which I’m calling ginit. It’s
git init, but on steroids.
You’re probably wondering what on earth that means.
As you no doubt already know,
git init initializes a Git repository in the current folder. However, that’s usually only one of a number of repetitive steps involved in the process of hooking up a new or existing project to Git. For example, as part of a typical workflow, you may well:
- initialize the local repository by running
- create a remote repository, for example on GitHub or Bitbucket — typically by leaving the command line and firing up a web browser
- add the remote
- create a
- add your project files
- commit the initial set of files
- push up to the remote repository.
There are often more steps involved, but we’ll stick to those for the purposes of our app. Nevertheless, these steps are pretty repetitive. Wouldn’t it be better if we could do all this from the command line, with no copy-pasting of Git URLs and such like?
So what ginit will do is create a Git repository in the current folder, create a remote repository — we’ll be using GitHub for this — and then add it as a remote. Then it will provide a simple interactive “wizard” for creating a
.gitignore file, add the contents of the folder and push it up to the remote repository. It might not save you hours, but it’ll remove some of the initial friction when starting a new project.
With that in mind, let’s get started.
The Application Dependencies
One thing is for certain: in terms of appearance, the console will never have the sophistication of a graphical user interface. Nevertheless, that doesn’t mean it has to be plain, ugly, monochrome text. You might be surprised by just how much you can do visually, while at the same time keeping it functional. We’ll be looking at a couple of libraries for enhancing the display: chalk for colorizing the output and clui to add some additional visual components. Just for fun, we’ll use figlet to create a fancy ASCII-based banner, and we’ll also use clear to clear the console.
In terms of input and output, the low-level Readline Node.js module could be used to prompt the user and request input, and in simple cases is more than adequate. But we’re going to take advantage of a third-party package which adds a greater degree of sophistication — Inquirer. As well as providing a mechanism for asking questions, it also implements simple input controls: think radio buttons and checkboxes, but in the console.
We’ll also be using minimist to parse command-line arguments.
Here’s a complete list of the packages we’ll use specifically for developing on the command line:
- chalk — colorizes the output
- clear — clears the terminal screen
- clui — draws command-line tables, gauges and spinners
- figlet — creates ASCII art from text
- inquirer — creates interactive command-line user interface
- minimist — parses argument options
- configstore — easily loads and saves config without you having to think about where and how.
Additionally, we’ll also be using the following:
- @octokit/rest — a GitHub REST API client for Node.js
- @octokit/auth-basic — an implementation of one of GitHub’s authentication strategies
- simple-git — a tool for running Git commands in a Node.js application
- touch — a tool for implementing the Unix touch command.
Although we’re going to create the application from scratch, don’t forget that you can also grab a copy of the code from the repository which accompanies this article.
Create a new directory for the project. You don’t have to call it
ginit, of course:
mkdir ginit cd ginit
Create a new
npm init -y
And edit it to look like so:
"name": "ginit", "version": "1.0.0", "description": "'git init' on steroids", "main": "index.js", "scripts": "test": "echo "Error: no test specified" && exit 1" , "keywords": [ "Git", "CLI" ], "author": "<YOUR NAME>", "license": "ISC"
Now install the dependencies:
npm install chalk clear clui figlet inquirer minimist configstore @octokit/rest @octokit/auth-basic lodash simple-git touch
Now create an
index.js file in the same folder and
require the following dependencies:
const chalk = require('chalk'); const clear = require('clear'); const figlet = require('figlet');
Adding Some Helper Methods
We’re going to create a
lib folder where we’ll split our helper code into modules:
- files.js — basic file management
- inquirer.js — command-line user interaction
- github.js — access token management
- repo.js — Git repository management.
Let’s start with
lib/files.js. Here, we need to:
- get the current directory (to get a default repo name)
- check whether a directory exists (to determine whether the current folder is already a Git repository by looking for a folder named
This sounds straightforward, but there are a couple of gotchas to take into consideration.
Firstly, you might be tempted to use the
fs module’s realpathSync method to get the current directory:
This will work when we’re calling the application from the same directory (for example, using
node index.js), but bear in mind that we’re going to be making our console application available globally. This means we’ll want the name of the directory we’re working in, not the directory where the application resides. For this purpose, it’s better to use process.cwd:
Secondly, the preferred method of checking whether a file or directory exists keeps changing. The current way is to use
existsSync. This returns
true if the path exists,
Finally, it’s worth noting that when you’re writing a command-line application, using the synchronous version of these sorts of methods is just fine.
Putting that all together, let’s create a utility package in
const fs = require('fs'); const path = require('path'); module.exports = getCurrentDirectoryBase: () => return path.basename(process.cwd()); , directoryExists: (filePath) => return fs.existsSync(filePath); ;
Go back to
index.js and ensure you
require the new file:
const files = require('./lib/files');
With this in place, we can start developing the application.
Initializing the Node CLI
Now let’s implement the start-up phase of our console application.
In order to demonstrate some of the packages we’ve installed to enhance the console output, let’s clear the screen and then display a banner:
// index.js clear(); console.log( chalk.yellow( figlet.textSync('Ginit', horizontalLayout: 'full' ) ) );
You can run the application using
node index.js. The output from this is shown below.
Next up, let’s run a simple check to ensure that the current folder isn’t already a Git repository. That’s easy: we just check for the existence of a
.git folder using the utility method we just created:
//index.js if (files.directoryExists('.git')) console.log(chalk.red('Already a Git repository!')); process.exit();
Tip: notice we’re using the chalk module to show a red-colored message.