visit
Command-line utilities are the most basic and beautiful apps ever created, the apps that started it all.
We use command-line utilities every day, whether it be git, grep, awk, npm, or any other terminal app. CLIs are super useful and usually the fastest way to get something done. Do you have something in your specific workflow that you have to do over and over again? Chances are that can be automated with a CLI.We are going to use to make our CLI if it wasn’t clear from the title itself. Why? Because the Node.js ecosystem has thousands of extremely useful packages that we can utilize to achieve what we are trying to do. Whatever it may be that you are trying to do, it is highly probable that there exists a package for it on also node has built-in libraries to do a lot of things like handling files, launching other applications, asserting tests, etc. Apart from that CLIs built in Node.js are highly portable, meaning they are easy to install on different OSs.
For the purpose of this tutorial, we’ll be building a simple CLI to translate between languages. We’ll accept string type arguments, parse them into a sentence, shoot them off to a translation API which will fetch us the translations, and then display the result. The complete code for this can be found on the . Let’s dive right into it!
~$mkdir termTranslate
~$cd termTranslate
~$npm init
1. Create a folder named
bin
in the root directory of your project.2. Inside
bin
create a file called index.js
This is going to be the entry point of our CLI.3. Now open the
package.json
file and change the “main” part to bin/index.js
.4. Now manually add another entry into the
package.json
file called bin and set it’s key to tran
and it’s value to ./bin/index.js
. The addition should look something like this:"bin": {
"tran": "./bin/index.js"
}
The key,
tran
, is the keyword for calling the CLI. This is the keyword that people will type in the terminal for using your CLI. Be free to name it whatever you like, although I would suggest keeping the name short and semantic so that it’s quick to type and easy to remember.The name defined here is not permanent and can be changed whenever you like.
Your entire
package.json
file should look something like this:{
"name": "termtran",
"version": "1.0.0",
"description": "A CLI to translate between languages in the terminal",
"main": "bin/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"cli"
],
"bin": {
"tran": "./bin/index.js"
},
"author": "Your Name",
"license": "ISC"
}
Note: Don’t forget to add the extra comma after adding the new entry into the5. Open the index.js file in the bin folder. And put the following code in it:file. That is an easy mistake.package.json
#! /usr/bin/env node
console.log("Hello World!");
The first line starting withNow let’s install and test our CLI.is called a#!
line or ashebang
line. A shebang line is used to specify the absolute path to the interpreter that will run the below code. The shebang line used here is for Linux or UNIX type systems but node requires it for Windows and macOS too, for proper installation and execution of the script.bang
People may call our CLI from anywhere in the system so let's install it globally.
6. Navigate to the root directory of the project and then run~$npm install -g .
The
-g
flag tells npm to install the package globally on the system.Test the CLI by typing the specified keyword in the terminal.~$tran
If everything was done correctly then you should be greeted with the message which we console.logged in the
index.js
file. Something like this:All good!
The most basic task that any CLI does is handling command-line arguments. In our CLI, we will be receiving the language name and the sentence to be translated as arguments and then we will parse it.
Although Node.js offers built-in functionality for handling command line arguments, we are going to use an npm package called 🏴☠ which is specifically made for building CLIs. yargs will simplify our process of parsing arguments and help us organize command line flags.1. Install yargs~$npm i yargs
2. After installing it, include the module in your
index.js
:const yargs = require("yargs");
3. Then create the
options
object containing all your command line flags:const usage = "\nUsage: tran <lang_name> sentence to be translated";
const options = yargs
.usage(usage)
.option("l", {alias:"languages", describe: "List all supported languages.", type: "boolean", demandOption: false })
.help(true)
.argv;
In the above code, I have defined an option
-l
which, when passed will print all the supported languages by the API, we will implement this later. Yargs provides us with --help
and --version
flags by default.If you want an option to be compulsory then you can set it’s
demandOption
value to true
, this will get yargs to throw a Missing argument
error if the flag is not provided.Testing it:Nice!
All the arguments that you pass with the command gets stored under the listunless the argument begins with a — or ayargs.argv._
in that case it is treated as a flag with a default value of boolean. You can console.log yargs.argv to get a better picture about how the arguments are stored.--
Access the value of the passed flags using
yargs.argv.flagname
.~$tran lang_name the sentence to be translated
We can write all the utility functions in our
index.js
but that wouldn’t look neat so I will make a separate file utils.js
for all functions. Here’s what we need to do:Create another file called
utils.js
in the bin folder.Include the file in your
index.js
:const utils = require('./utils.js')
Write the function in
utils.js
and then export it:module.exports = { parseSentence: parseSentence };
function parseSentence(words) {
var sentence = "";
for(var i = 1; i < words.length; i++) {
sentence = sentence + words[i] + " ";
}
Call it in
index.js
:var sentence = utils.parseSentence(yargs.argv._);
Create a function in your
utils.js
:module.exports = { showHelp: showHelp, parseSentence: parseSentence };
const usage = "\nUsage: tran <lang_name> sentence to be translated";
function showHelp() {
console.log(usage);
console.log('\nOptions:\r')
console.log('\t--version\t ' + 'Show version number.' + '\t\t' + '[boolean]\r')
console.log(' -l, --languages\t' + ' ' + 'List all languages.' + '\t\t' + '[boolean]\r')
console.log('\t--help\t\t ' + 'Show help.' + '\t\t\t' + '[boolean]\n')
}
Call it in
index.js
:if(yargs.argv._[0] == null){
utils.showHelp();
return;
}
5. Write a function in
utils.js
to show all supported languages:module.exports = { showAll: showAll, showHelp: showHelp, parseSentence: parseSentence};
function showAll(){
console.log(chalk.magenta.bold("\nLanguage Name\t\tISO-639-1 Code\n"))
for(let [key, value] of languages) {
console.log(key + "\t\t" + value + "\n")
}
}
let languages = new Map();
languages.set('afrikaans', 'af')
languages.set('albanian', 'sq')
languages.set('amharic', 'am')
languages.set('arabic', 'ar')
languages.set('armenian', 'hy')
languages.set('azerbaijani', 'az')
languages.set('basque', 'eu')
languages.set('belarusian', 'be')
languages.set('bengali', 'bn')
languages.set('bosnian', 'bs')
languages.set('bulgarian', 'bg')
languages.set('catalan', 'ca')
languages.set('cebuano', 'ceb')
languages.set('chinese', 'zh')
languages.set('corsican', 'co')
languages.set('croatian', 'hr')
languages.set('czech', 'cs')
languages.set('danish', 'da')
languages.set('dutch', 'nl')
languages.set('english', 'en')
languages.set('esperanto', 'eo')
languages.set('estonian', 'et')
languages.set('finnish', 'fi')
languages.set('french', 'fr')
languages.set('frisian', 'fy')
languages.set('galician', 'gl')
languages.set('georgian', 'ka')
languages.set('german', 'de')
languages.set('greek', 'el')
languages.set('gujarati', 'gu')
languages.set('haitian creole', 'ht')
languages.set('hausa', 'ha')
languages.set('hawaiian', 'haw') // (iso-639-2)
languages.set('hebrew', 'he') //or iw
languages.set('hindi', 'hi')
languages.set('hmong', 'hmn') //(iso-639-2)
languages.set('hungarian', 'hu')
languages.set('icelandic', 'is')
languages.set('igbo', 'ig')
languages.set('indonesian', 'id')
languages.set('irish', 'ga')
languages.set('italian', 'it')
languages.set('japanese', 'ja')
languages.set('javanese', 'jv')
languages.set('kannada', 'kn')
languages.set('kazakh', 'kk')
languages.set('khmer', 'km')
languages.set('kinyarwanda', 'rw')
languages.set('korean', 'ko')
languages.set('kurdish', 'ku')
languages.set('kyrgyz', 'ky')
languages.set('lao', 'lo')
languages.set('latin', 'la')
languages.set('latvian', 'lv')
languages.set('lithuanian', 'lt')
languages.set('luxembourgish', 'lb')
languages.set('macedonian', 'mk')
languages.set('malagasy', 'mg')
languages.set('malay', 'ms')
languages.set('malayalam', 'ml')
languages.set('maltese', 'mt')
languages.set('maori', 'mi')
languages.set('marathi', 'mr')
languages.set('mongolian', 'mn')
languages.set('burmese', 'my')
languages.set('nepali', 'ne')
languages.set('norwegian', 'no')
languages.set('nyanja', 'ny')
languages.set('odia', 'or')
languages.set('pashto', 'ps')
languages.set('persian', 'fa')
languages.set('polish', 'pl')
languages.set('portuguese', 'pt')
languages.set('punjabi', 'pa')
languages.set('romanian', 'ro')
languages.set('russian', 'ru')
languages.set('samoan', 'sm')
languages.set('scots', 'gd')//gd gaelic
languages.set('serbian', 'sr')
languages.set('sesotho', 'st')
languages.set('shona', 'sn')
languages.set('sindhi', 'sd')
languages.set('sinhalese', 'si')
languages.set('slovak', 'sk')
languages.set('slovenian', 'sl')
languages.set('somali', 'so')
languages.set('spanish', 'es')
languages.set('sundanese', 'su')
languages.set('swahili', 'sw')
languages.set('swedish', 'sv')
languages.set('tagalog', 'tl')
languages.set('tajik', 'tg')
languages.set('tamil', 'ta')
languages.set('tatar', 'tt')
languages.set('telugu', 'te')
languages.set('thai', 'th')
languages.set('turkish', 'tr')
languages.set('turkmen', 'tk')
languages.set('ukrainian', 'uk')
languages.set('urdu', 'ur')
languages.set('uyghur', 'ug')
languages.set('uzbek', 'uz')
languages.set('vietnamese', 'vi')
languages.set('welsh', 'cy')
languages.set('xhosa', 'xh')
languages.set('yiddish', 'yi')
languages.set('yoruba', 'yo')
languages.set('zulu', 'zu')
Here I have created a hash map to map all the language names to their ISO-639–1 code. This will serve two purposes, firstly it will help display all languages when needed, secondly, the API only takes the language code so even if the user enters the language name we can swap it with the language code before passing it to the API. Sneaky! 🤫. The swap would be in constant time since we are using a hash map.
Call the
showAll()
function in your index.js
if the -l
or --languages
flag is true:if(yargs.argv.l == true || yargs.argv.languages == true){
utils.showAll();
return;
}
6. Now write the function to do the dirty deed we talked about in your
utils.js
:module.exports = { parseLanguage: parseLanguage, showAll: showAll, showHelp: showHelp, parseSentence: parseSentence };
function parseLanguage (language) {
if(language.length == 2){
return language;
}
if(languages.has(language)){
return languages.get(language)
}
else {
console.error("Language not supported!")
return; //returning null if the language is unsupported.
}
};
Convert the language to lower case and then call the function in
index.js
:if(yargs.argv._[0])
var language = yargs.argv._[0].toLowerCase(); // stores the language.
//parsing the language specified to the ISO-639-1 code.
language = utils.parseLanguage(language);
Include the API at the top of your
index.js
:const translate = require('@vitalets/google-translate-api');
if(sentence == ""){
console.error("\nThe entered sentence is like John Cena, I can't see it!\n")
console.log("Enter tran --help to get started.\n")
return;
}
translate(sentence, {to: language}).then(res => {
console.log("\n" + "\n" + res.text + "\n" + "\n";
}).catch(err => {
console.error(err);
});
npm install chalk boxen
2. Include them in your
index.js
and utils.js
const chalk = require('chalk');
const boxen = require('boxen');
const usage = chalk.hex('#83aaff')("\nUsage: tran <lang_name> sentence to be translated");
translate(sentence, {to: language}).then(res => {
console.log("\n" + boxen(chalk.green("\n" + res.text + "\n"), {padding: 1, borderColor: 'green', dimBorder: true}) + "\n");
}).catch(err => {
console.error(err);
});
Ahh yes
Hope you had fun learning how to build your own and fully portable CLI :) because I had a lot of fun.Happy coding!The complete code for this can be found at:
Fork me on maybe :)Connect with me on .Also published at