This article walks you through the basics of creating a Node-CLI tool.

Command line parameter parsing

In NodeJS you can get the parameters passed on the command line by using the following code:

  process.argv.slice(2)
Copy the code

However, this is not enough for building a CLI tool. First, you need to consider the various styles of parameter input:

  • Unix parameter style: Preceded by -, but followed by a single character, for example -abc resolves to [‘a’, ‘b’, ‘c’].
  • GNU parameter style: Preceded by –, for example in NPM, NPM –save-dev webpack.
  • BSD parameter style: Not preceded by modifiers.

Here we can process process.argv with regular expressions:

/** * Parses Unix, BSD, and GNU parameter styles * @param {Array} argv command line parameter Array * @returns */
function parseArgv (argv) {
  const max = argv.length
  const result = {
    _: []}for (let i = 0; i < max; i++) {
    const arg = argv[i]
    const next = argv[i + 1]
    if (/ ^ -. + /.test(arg)) {
      / / GNU style
      const key = arg.match(/ / ^ - (. +)) [1]
      if(next ! =null&&!/ ^. + /.test(next)) {
        result[key] = next
        i++
      } else {
        result[key] = true}}else if (+ / / ^ - ^ -].test(arg)) {
      / / Unix style
      const items = arg.match(([/ ^ - ^ -] +) /) [1].split(' ')
      for (let j = 0, max = items.length; j < max; j++) {
        const item = items[j]
        // Non-letters are not parsed
        if (!/[a-zA-Z]/.test(item)) {
          continue
        }
        if(next ! =null&&!/ ^. + /.test(next) && j === max - 1) {
          result[item] = next
          i++
        } else {
          result[item] = true}}}else {
      / / the BSD style
      result._.push(arg)
    }
  }
  return result
}
Copy the code

Through the above methods, the following results can be obtained:

  node example1.js --save-dev -age 20 some
  / / = > results
  {
    _: ['some'].'save-dev': true.a: true.g: true.e: 20
  }
Copy the code

The above example not only shows the results of parsing, but also highlights that the Unix parameter style parses a single letter, so arguments in this style can be ambiguous and have a limited number of meanings, so they need to be used in the right scenarios:

  npm --save-dev webpack
  npm -D webpack
Copy the code

The Unix parameter style shorthand in NPM is a good way to do it, so the -age in the previous example would make more sense semantically to change to –age.

Command line interface

The readline module in NodeJS provides question and prompt methods to build the command line interface. Here is a simple question-and-answer interface:

const readline = require('readline');
const question = ['Please enter your name'.'Please enter your age']
const result = []
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
  prompt: `?${question[0]} `
});
rl.prompt();

rl.on('line', (line) => {
  result.push(line.trim())
  const max = result.length
  if (max === question.length) {
    rl.close()
  }
  rl.setPrompt(`?${question[max]} `)
  rl.prompt();
}).on('close', () = > {console.log('Thank you for joining us ***${result[0]}Age:${result[1]}`);
  process.exit(0);
}); 
Copy the code

Here we can try to implement a single choice interface:

const readline = require('readline')
let selected = 0
const choices = ['javascript'.'css'.'html']
let lineCount = 0
const rl = readline.createInterface(process.stdin, process.stdout)
function reader () {
  let str = ' '
  for (let i = 0; i < choices.length; i++) {
    lineCount++
    str += `${selected === i ? '[X]' : '[]'} ${choices[i]}\r\n`
  }
  process.stdout.write(str)
}

reader()

process.stdin.on('keypress', (s, key) => {
  const name = key.name
  const max = choices.length
  if (name === 'up' && selected > 0) {
    selected--
  } else if (name === 'down' && selected < max - 1) {
    selected++
  } else if (name === 'down' && selected === max - 1) {
    selected = 0
  } else if (name === 'up' && selected === 0) {
    selected = max - 1
  } else {
    return true
  }
  // Move the cursor to the starting position, ensuring that subsequent input overwrites the current content
  readline.moveCursor(process.stdout, 0, -lineCount)
  lineCount -= choices.length
  reader()
})

rl.on('line', () = > {console.log(`you choose ${choices[selected]}`)
  process.exit(0)
}).on('close', () => {
  rl.close()
})
Copy the code

Three, custom style

In order to effectively distinguish the differences of information in the command line interface, we can add appropriate styles to the output information.

Here’s the syntax for string append styles:

\x1b[Background color number; font color number mCopy the code

Each style should begin with \x1b[:

  // \x1b[0m clear style
  process.stdout.write('\x1b[44;37m OK \x1b[0m just do it\n')
Copy the code

Customize the Node command

To customize the Node command, you need to create a file to execute the command:

  The first line of hello.js needs to specify the interpreter for the script
  #!/usr/bin/env node
  console.log('hello')
Copy the code

Reuse the bin configuration in package.json:

  {
    "bin": {
      "hello": "./hello.js"}},Copy the code

Execute the link command of NPM:

NPM link # Enter the custom command hello # Print helloCopy the code

Five, the summary

The basics of Node-CLI development are covered above, but those of you who have used tools such as Webpack-CLI and Vue-CLI may have noticed that these excellent CLI tools also have:

  • Git-style subcommands;
  • Automated help information;
  • .

The following mature frameworks will help you a lot:

  • Commander.js makes CLI development easier
  • Chalk. Js terminal text style library
  • Lnquirer. Js command line interaction information collection library
  • Yargs parses command-line arguments and generates an elegant user interface