Categories


Archives


Recent Posts


Categories


Yargs and Command Line Argument Processing in NodeJS

astorm

Frustrated by Magento? Then you’ll love Commerce Bug, the must have debugging extension for anyone using Magento. Whether you’re just starting out or you’re a seasoned pro, Commerce Bug will save you and your team hours everyday. Grab a copy and start working with Magento instead of against it.

Updated for Magento 2! No Frills Magento Layout is the only Magento front end book you'll ever need. Get your copy today!

The pile of one-off command line scripts I’ve written for my current weekend projecting finally reached the critical mass where they needed some organizing, which led me to discover the yargs project. Yargs provides mature argument and option parsing for command line programs (both --foo and -b style options) and features for defining individual sub-commands (think programs like git sub-command). It’s also been around for six plus years and is still receiving updates.

I’m coming up on the one year anniversary of NodeJS being my primary programming language, and I’m starting to get a better feel for how folks organize their programs and libraries. I’ve noticed the ongoing challenges/evolution of single threaded asynchronous programming means there’s a few distinct styles out there

Yargs appears to fall mostly in the When All we had was Callbacks camp. The rest of this post is a few places where the choices of the yargs engineers clashed with my own expectations.

First — here’s their Hello World example.

require('yargs')
  .command('serve [port]', 'start the server', (yargs) => {
    yargs
      .positional('port', {
        describe: 'port to bind on',
        default: 5000
      })
  }, (argv) => {
    if (argv.verbose) console.info(`start server on :${argv.port}`)
    serve(argv.port)
  })
  .option('verbose', {
    alias: 'v',
    type: 'boolean',
    description: 'Run with verbose logging'
  })
  .argv

Although there’s nothing visible in this code that says “async”, its APIs are designed around passing in anonymous functions. This specific example is also made a little hard to follow since it uses the “arrow function” declaration syntax.

// using the `function` keyword to define a function
const myFunction = function() { //function code here}

// using the arrow function syntax instead
const myFunctionAlso = () => { //function code here}

// arrow functions may also look like this -- the value
// return by the single line is the value returned by
// the function
const myFunctionAlsoUgh = () => /* single line of function code here/*;

I’m still not a fan of this particular style of function declaration, but that’s another post for another time. If we write that hello world code in a different way —

require('yargs')
  .command(
      'serve [port]',
      'start the server',
      functionThatWillDefineCommandArguments,
      functionThatWillDoTheWorkOfTheCommand
  )
  .option('verbose', {
    alias: 'v',
    type: 'boolean',
    description: 'Run with verbose logging'
  })
  .argv

the API becomes a bit clearer. To setup a command you call the command method of the object returned by the yargs module. The command method accepts four arguments — one is the command’s name with its position arguments, the second is a description, the third is a function you can use to configure things about your command’s positional arguments, and the fourth is a command that does the actual work.

It may look like the option method is setting up a --verbose/-v flag for the command, but these options appear to be global for every command. The call to command does not return a command object, it returns the same object instance returned by the call to require('yargs'). Yargs does not appear to let you define options that are specific to a command. Instead, the same options be available for all your commands.

The final bit I was confused by was the .argv at the end. This appears to be a simple property access that has no reason to be there. However, without it, yargs doesn’t work. After digging into it a bit, is discovered that argv is setup as a javascript getter, which means accessing this property actually invokes a method. This explains how a simple looking property access actually invokes the functionality we expect from yargs.

Copyright © Alan Storm 1975 – 2020 All Rights Reserved

Originally Posted: 20th April 2020