Command-Line Interfaces(CLI) are great tools for automating repetitive tasks or allowing your computer to take care of the boring stuffs.
Node.js is an interesting choice for building CLIs as you can leverage its vast ecosystem. And thanks to package managers like npm
and yarn
, these can be easily distributed and consumed across multiple platforms. In this post, we'll look at why you might want to write a CLI, and how to use Node.js for it.
The CLI tool we'll be building will be called Prtfy
. This will simply set up a Prettifier
in javascript directory. It will write the Prettier Config
and prompts the user for their configuration settings.
Let's get started.
We'll familiarize ourself with the npm
modules that will make coding process more simplified.
- chalk - Terminal Styling, i.e colors etc.
- figlet - For making large letters out of ordinary text.
- inquirer - Collects user inputs from the command line.
- shelljs - Portable Unix shell commands for Node.js
Let's jump to the code.
First, we need to setup a Node project. Go to the command line. and type
mkdir prtfy
cd prtfy
npm init
Follow all the prompts to get the project going. You could also skip all the prompts by simply usingnpm init -y
instead of npm init
. By now you should have a basic Node project with the package.json
file.
Next, we'll install all the core depedencies listed above. Type the below for this.
npm install chalk figlet inquirer shelljs
index.js file
Now create an index.js
file and import the installed modules.
const inquirer = require("inquirer");
const chalk = require("chalk");
const figlet = require("figlet");
const shell = require("shelljs");
Let's plan the CLI
It does a couple of things.
- Asks user their preffered prettier config.
- Install the prettier locally.
- Writes the config file.
- Configures a pre-commit hook.
With this in mind, let's wrote a pseudo-code for the this.index.js
const run = async () => {
// show prtfy introduction
// install GitHook
// ask questions
// create the files
// configures pre-commit hook
// show success message
};
run();
For ease, we'll have a default configuration. For additional challenge, you can ask all this from the user. Our default config will be stored in a variable.
prettierConfig = {
trailingComma: "es5",
tabWidth: 4,
semi: false,
singleQuote: true,
useTabs: false,
printWidth: 100,
bracketSpacing: true,
jsxBracketSameLine: false,
arrowParens: "avoid",
}
Let's create these tasks one after the other.
// initializes and displays the welcome screen
const init = async () => {
clear()
console.log(
chalk.green(
figlet.textSync('PrTfY', {
horizontalLayout: 'full',
})
)
)
}
You will notice we have a clear()
function. This clears the console of any clutter when we run prtfy
. We need to install the clear
module. Run
npm install clear
Let's configure Git hook
more info an what that means here
const installGitHook = async () => {
const spinner = new Spinner('Configuring Git Hook..')
return installHelper(
'npx mrm lint-staged',
() => console.log(chalk.green('Git hook configured π')),
spinner
)
}
Next, we need to prompt user for some answers.
const askIfJsorTs= () => {
const questions = [
{
name: 'ENV',
type: 'list',
choices: ['.Typescript', '.Javascript'],
message: 'Please, select if this is a JavaScript or Typescript project',
filter: function (val) {
return (val === '.Typescript') ? 'ts' : 'js'
},
},
]
return inquirer.prompt(questions)
}
What askIfJsorTs()
does basically is to ask if the user wants to setup prettier for Javascript or Typescript. The filter then returns 'ts' or 'js' based on the selection.
Next, we'll setup the configuration files based on user input. But, to make things more snazzy and realistic. We'll add a spinner to indicate when an asynchronous process like installing prettier or writing files is ongoing and when it's done. Something like below
This is probably the trickiest part as we need to handle some async
logic elegantly. We'll start by installing the spinner
. Run
npm install clui
Also, don't forget to add the spinner to your list of imports. Like so
const clui = require('clui')
const Spinner = clui.Spinner
Now, we write the async
logic to help us out with this. We need to await
the child process installing the prettier and other modules before writing the config files. You can check the clui docs for more info
const installHelper = (command, onSuccess, spinner) => {
return new Promise((resolve, reject) => {
var process = spawn(command, { shell: true })
spinner.start()
process.on('exit', () => {
spinner.stop()
onSuccess()
resolve()
})
})
}
Install prettier
const installPrettier = async () => {
const spinner = new Spinner('Installing Prettier...')
return installHelper(
'yarn add -D prettier',
() => console.log(chalk.green('Prettier has been installed! π')),
spinner
)
}
Finally, putting everything toeghter we write a prettier
file based on all the information we have.
#!/usr/bin / env node
const cli = require('clui')
const shell = require('shelljs')
const Spinner = cli.Spinner
const clear = require('clear')
const spawn = require('child_process').spawn
const chalk = require('chalk')
const inquirer = require('inquirer')
const figlet = require('figlet')
const config = require('./config')
// initializes and displays the welcome screen
const init = async () => {
clear()
console.log(
chalk.green(
figlet.textSync('PrTfY', {
horizontalLayout: 'full',
})
)
)
}
const installHelper = (command, onSuccess, spinner) => {
return new Promise((resolve, reject) => {
var process = spawn(command, { shell: true })
spinner.start()
process.on('exit', () => {
spinner.stop()
onSuccess()
resolve()
})
})
}
const installPrettier = async () => {
const spinner = new Spinner('Installing Prettier...')
return installHelper(
'yarn add -D prettier',
() => console.log(chalk.green('Prettier has been installed! π')),
spinner
)
}
const installGitHook = async () => {
const spinner = new Spinner('Configuring Git Hook..')
return installHelper(
'npx mrm lint-staged',
() => console.log(chalk.green('Git hook configured π')),
spinner
)
}
const askIfJsorTs = () => {
const questions = [
{
name: 'ENV',
type: 'list',
choices: ['.Typescript', '.Javascript'],
message: 'Please, select if this is a JavaScript or Typescript project',
filter: function(val) {
return val === '.Typescript' ? 'ts' : 'js'
},
},
]
return inquirer.prompt(questions)
}
const setPrettierConfig = async () => {
shell.ShellString(config).to(`.prettierrc.js`)
}
const success = () => {
console.log(chalk.blue.bold(`Prettier Config completed`))
};
(async () => {
init()
await installPrettier()
await setPrettierConfig()
await installGitHook()
const answer = await askIfJsorTs()
const { ENV } = answer
if (ENV === 'js') {
await installPrettier()
await setPrettierConfig()
}
if (ENV == 'ts') {
const tsConfig = {
parser: '@typescript-eslint/parser',
extends: [
'plugin:react/recommended',
'plugin:@typescript-eslint/recommended',
'prettier/@typescript-eslint',
'plugin:prettier/recommended',
],
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
rules: {},
settings: {
react: {
version: 'detect',
},
},
}
// install eslint plugins
const pluginSpinner = new Spinner('Installing plugin configs...')
await installHelper(
'npm install eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin --dev',
() => console.log(chalk.green('Eslint Typescript plugin installed π')),
pluginSpinner
)
// write eslintrc.js
await shell.ShellString(tsConfig).to(`.eslintrc.js`)
// install typescript prettier config
const tsSpinner = new Spinner('Installing Typescript prettier configs...')
await installHelper(
'npm install prettier eslint-config-prettier eslint-plugin-prettier --dev',
() => console.log(chalk.green('Eslint Typescript prettier configs installed π')),
tsSpinner
)
}
success()
})()
To test the CLI, simply run the below inside the root directory
node index
One last thing, notice the expression on the first line of index.js
#!/usr/bin / env node
It enables you to simply run prtfy
inside any directory and have the cli run and install the configurations. I will leave you to do this. You can also publish as an npm module
if you so wish.
Top comments (0)