Command Line Tool enables the user to complete required tasks directly from the terminal. User-friendly commands are interpreted as system calls internally and commands are executed. Node.js provides packages that provide another layer of abstraction and encapsulation for creating these command-line tools (Node-cmd tools).
Let’s see how to create a CLI tool and create our first command-line tool in Node.js (Nodejs CLI) for manipulating and triggering API calls of AWS ECS Cluster.
Table of contents
Introduction to Task
To create CLI tool with Node it's important to know that AWS Elastic Container Service (AWS ECS) is a collection of AWS EC2s. In simple terms, Linux machines run similar configurations, and in turn, create a network to develop a networking system. AWS ECS provides an API layer to directly manipulate and work with the AWS EC2s i.e., Linux machines.
For example, using AWS ECS APIs one can get a particular EC2 configuration, available memory space, idle time of the network e.t.c. So, let's start the Node js create command line tool operation by setting up project.
Setting up Project
To learn how to make a CLI tool, Node.js developers should start with creating a simple Node.js project. After npm init provides answers to questions asked. This is general information about the new npm Nodejs build command package that you are creating.
mkdir nodejs-cli-tool
cd nodejs-cli-tool
mkdir ecs-cluster-control
cd ecs-cluster-control
npm init
> ecs-cluster-control$ npm init
> This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
> See `npm help json` for definitive documentation on these fields
and exactly what they do.
> Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.
> Press ^C at any time to quit.
> package name: (ecs-cluster-control)
> version: (1.0.0)
> description: how to build a command line tool in nodejs
> entry point: (index.js)
> test command:
> git repository:
> keywords:
> author:
> license: (ISC)
> About to write to /home/username/nodejs-cli-tool/ecs-cluster-control/package.json:
{
"name": "ecs-cluster-control",
"version": "1.0.0",
"description": "how to build a command line tool in nodejs",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
Is this OK? (yes)
You can also set all the answers to defaults using npm init -y.
Node.js Packages as Dependencies
Let’s install the following packages for our Node build command:
easy-table | Nice utility for rendering text tables with javascript. |
eslint | ESLint is a tool for identifying and reporting on patterns found in ECMAScript/JavaScript code. In many ways, it is similar to JSLint and JSHint with a few exceptions: |
eslint-plugin-node | Additional ESLint's rules for Node.js |
meow | Nodejs CLI app helper |
simple-statistics | A JavaScript implementation of descriptive, regression, and inference statistics. |
sinon | Standalone and test framework agnostic JavaScript test spies, stubs, and mocks (pronounced "sigh-non", named after Sinon, the warrior). |
tap-spec | Formatted TAP output like Mocha's spec reporter |
tape | tap-producing test harness for node and browsers |
@mapbox/mock-aws-sdk-js | A library that provides sinon-style stubs for aws-sdk-js service methods for use in testing. |
npm i easy-table eslint eslint-plugin-node meow simple-statistics sinon tap-spec tape @mapbox/mock-aws-sdk-js
Initializing Node Command Line Tool Creation
Let’s create four main directories to provide a modular structure for our Node CLI tool.
bin → cli.js
commands → ecs-cluster-control.js
lib → terminate-instance.js
→ pending-tasks.js
→ persist-disk-metrics.js
→ task-churn.js
test → ecs-cluster-control.test.js
→ pending-tasks.test.js
→ task-churn.test.js
bin/cli.js
In this file, we will instantiate meow.js and provide the following:
- usage command for the CLI tool Nodejs
- commands
- options
- description
- aliases i.e., short forms for our long commands
- include the file describing our commands
- error messages for the Nodejs command line tool
So, let's move on with our Nodejs create CLI tool process.
The cli.js file will look something like this:
#!/usr/bin/env node
/* eslint-disable no-console */
var meow = require('meow');
var cli = meow({
help: `
USAGE: ecs-cluster-control <command> [OPTIONS]
Commands:
get-capacity print spot fleet capacity information
set-capacity adjust the spot fleet's target capacity
terminate-instance gracefully shut down a single EC2
teardown gracefully delete an ecs-cluster stack
scaling-activity print recent spot fleet scaling activity
fleet-events print spot fleet event history
instance-balance print a table of instance type / region distribution
docker-disk-metrics open CloudWatch metrics for docker disk utilization
persist-disk-metrics open CloudWatch metrics for persistent disk utilization
pending-metrics open CloudWatch metrics for pending tasks per instance
metric-offenders find cluster instance max from the last 10 min for any System/Linux metric
task-churn print min/avg/max durations for tasks stopped in the last hour
disable-scaledown disables the scale-down schedule rule and error alarm actions
enable-scaledown enables the scale-down schedule rule and error alarm actions
worker-capacity calculates how many service tasks can be placed on cluster at current capacity
Options:
-h, --help show this help message
-r, --region the region the cluster is in
-cl, --cluster-name the cluster name, e.g. production
-c, --capacity the desired spot fleet target capacity
-i, --instance-id an EC2 instance id
-a, --app-name the application's service name on the cluster
-m, --me your slack handle, e.g. '@rclark'
--metric-name the MetricName for a System/Linux metric
`,
description: 'Helper utilities for interacting with AWS ECS clusters'
}, {
alias: {
r: 'region',
cl: 'cluster-name',
c: 'capacity',
i: 'instance-id',
a: 'app-name',
m: 'me'
},
string: ['region', 'cluster-name', 'instance-id', 'me'],
number: ['capacity']
});
var command = cli.input[0];
var fn;
try { fn = require(`../lib/${command}`); }
catch(err) {
console.error(err.message);
cli.showHelp(1);
}
const preamble = cli.flags.region && cli.flags.clusterName ?
require('../lib/cluster-info.js') : () => Promise.resolve();
preamble(cli.flags)
.then((info) => fn(cli.flags, info))
.catch((err) => {
console.error(`ERROR: ${err.message}`);
process.exit(1);
});
commands/ecs-cluster-control.js
This will be our core file to provide an abstraction layer over every command code file inclusion.
The ECS-cluster-control.js will look something like this:
'use strict';
const fs = require('fs');
const path = require('path');
/**
* Description of ecs-cluster-control commands to be used in registration with mbxcli.
*/
module.exports.configuration = {
description: 'Helper utilities for interacting with AWS ECS clusters',
help: `
USAGE: source ecs-cluster-control <command> [OPTIONS]
command:
task-churn print min/avg/max durations for tasks stopped in the last hour
terminate-instance gracefully terminates the specified instance
pending-tasks giveb a cluster name and region print currently pending tasks.
Options:
-a, --account [default] a comma-delimited list of accounts
to create the stack in
-r, --region [us-east-1] a comma-delimited list of regions
to create the stack in
-h, --help shows this help message
`
};
/**
* Function called when command is run through source cli tool.
* @param {object} cli parsed command-line arguments
* @param {object} context information about the context in which the command was requested
*/
module.exports.run = (cli, context) => {
const command = cli.input[0];
const files = fs.readdirSync(path.resolve(__dirname, '..', 'lib'));
const commands = new Set(files.map((filename) => filename.replace('.js', '')));
//check if command corresponds with a file in ./lib
const valid = commands.has(command);
if(valid){
const run = require(`../lib/${command}.js`).run;
return run(cli, context);
} else {
return Promise.resolve()
.then(() => { console.log(`Command ${cli.input[0]} not valid`); });
}
};
lib/command-name.js
This will contain every command the file system calls triggers. Let’s take as an example terminating an AWS ECS cluster instance, i.e., calling a Terminate Your Instance AWS ECS API with an ID.
Our command file terminate-instance.js will look something like this:
'use strict';
/**
* Given a cluster name, region and instance id, terminates that instance.
*
* @param {String} A region, a cluster name and instance id.
*
* @returns {Object} Terminating the instance.
*/
module.exports.run = (cli, context) => {
const instanceID = cli.input[1];
const region = cli.flags.regions[0] || 'us-east-1';
const accounts = cli.flags.accounts[0] || 'default';
if(!instanceID)
return Promise.reject(new Error('Please provide an instance ID'));
console.log(`Terminating AutoScaling Instance ${cli.input[1]}`);
return Promise.resolve()
.then(() => {
const params = {
InstanceId: instanceID,
ShouldDecrementDesiredCapacity: false
};
context.access.getClient('AutoScaling', accounts, { region: region })
.then((autoscaling) => new Promise((resolve, reject) => {
autoscaling.terminateInstanceInAutoScalingGroup(params, function(err, data) {
if(err) return reject(err);
else resolve(data);
});
}));
});
};
This completes our first tool with Nodejs command line.
Usage
ecs-cluster-control terminate-instance <id>
Sample output:
Terminating AutoScaling Instance instance-id
Tests
Now let’s write some tests about the Nodejs create command line tool process.
test/file-name.js: will contain tests per file.
- Instantiate, mock-aws-api, tape and sinon.
- Replicate ideal instance object values and assign dummy values.
- Call mocked AWS API, this will not trigger the AWS API but replication present in your node_modules/.
- Assert true and false based on the values that you should get and what you are getting.
Full code files can be found here. Let me know what you create once you build command for Node js!
Final Word
Did we just simplify your most difficult and time-consuming problem of interacting with AWS APIs? Let us know! Now you know how to create Node CLI tool, so don't waste any more time to start the process.
Aside from this Node CLI tutorial, if you want to broaden your knowledge of Node.js, check out these articles on Sails js tutorial, Node js architecture, Node js discord bot, containerizing Node.js apps with docker, and Node js queue.