Building a command line tool - auttam/easycli GitHub Wiki

Building a command-line tool for an npm package requires the following -

  1. An npm package
  2. A script that serves as a program for the command-line tool. These types of scripts contain hashbang for Node.js scripts and are generally kept in the bin folder of an npm package.
  3. An entry in the bin key of a package.json that maps a command name to the program script. This entry specifies by what command the program will be invoked from the command line.

Building a command-line tool with EasyCli

As mentioned above, building a CLI tool requires a script containing the code that represents the program of the CLI. In EasyCli, this program is represented by a JavaScirpt Class which inherits from base Program class and contains methods that represent the commands that the CLI can accept.

Creating an executable script with a program

To create an executable script, create a file index.js with following contents -

#!/usr/bin/env node
const Program = require('@auttam/easycli').Program

// a class representing the cli program
class SampleProgram extends Program {}

// runs the program
Program.run(new SampleProgram())

The above examples show an executable script that has 3 types of code

  1. hashbang for Node.js scripts,
  2. a class for the sample program and,
  3. a statement that creates an instance of the program class and runs it

default program options --help and --version

A program created with EasyCli accepts 2 options by default - -h or --help and -v or --version. (see Program Settings page to know how to disable default options)

Getting CLI help -

node ./index.js -h
Sample Program v1.0.0

Usage: sample-program

Other usage:

   sample-program --help, -h      To view help
   sample-program --version, -v   To view help

Getting CLI version info -

node ./index.js -v
Sample Program v1.0.0

See CLI Configuration page to know how to change auto-generated name and version.

Program methods

The Program methods are the class methods that are automatically called when the CLI is invoked from the command-line.

EasyCli supports 2 types of programs -

  1. a program that performs a single main operation and,
  2. a program that performs multiple operations each identified and invoked by a command of the program

Based on how a CLI program is configured to operate, there can be a single main() method performing a single main operation of the program or one or more command methods denoting different operations the program supports.

The main() method

This the default method that is called when the program is running in a single operation mode. By default it is main() but can be changed using Program Settings.

Following is an example of using main method:

#!/usr/bin/env node
const Program = require('@auttam/easycli').Program

// a class representing the CLI program
class SampleProgram extends Program {
    // main method
    main(){
        console.log('hello-world')
    }
}

// runs the program
Program.run(new SampleProgram())

Running the program now will print 'hello-world' to the

node ./index

#expected output
hello-world

Command methods

A program can be enabled to perform different operations based on the invoked commands from the command-line interface. Commands, the program accepts, can be configured using the command configuration object, a command decorator or by defining a method that has a name ending with 'Command' suffix.

The following example shows how to configure a command by using a command method name convention. Create or update index.js file with the following content -

#!/usr/bin/env node
const Program = require('@auttam/easycli').Program

// setting to enable program commands
Program.settings({
    enableCommands: true
})

// represents the CLI program
class DemoCommands extends Program {
    // method called when 'test' command is requested
    testCommand() {
        console.log('This is a test command');
    }

}

// Running the program
Program.run(new DemoCommands())

Test the command by running the above script as following -

node ./index test

#expected output
This is a test command

Note: When running in command-mode, the first non-option argument supplied from the command-line is treated as the command name of the CLI program.

Asynchronous methods

The program methods and the program event callbacks are executed asynchronously.

#!/usr/bin/env node
const Program = require('@auttam/easycli').Program

Program.settings({
    enableCommands: true
})

// a class representing the CLI program
class SampleProgram extends Program {

    // async process
    testProcess(value) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve(value)
            }, 1000)
        })
    }

    // command using async/await
    async test1Command() {
        var value = await this.testProcess(1)
        console.log(value)
    }

    // command returning a promise
    test2Command() {
        return this.testProcess(2).then(value => console.log(value))
    }

    // method called on program exit
    onExit(err) {
        console.log('Existing', err)
    }
}

// runs the program
Program.run(new SampleProgram())

Command-line Arguments

Command-line arguments supplied to the CLI are parsed as parameters and options and passed to the program methods.

In the following 2 examples, arg1 & arg2 will be parsed as the parameters whereas --arg3 will be parsed as an option-

# Example 1
node ./hello-world arg1 arg2 --arg3

# Example 2
hello-world arg1 arg2 --arg3

Note: When commands are enabled the first argument supplied to the CLI is treated as the command name. In the example 3, arg0 will be interpreted as the command name argument-

# Example 3
node ./hello-world arg0 arg1 arg2 --arg3

Method Parameters

EasyCli maps the parameter arguments (non-command, non-option arguments) directly to the program methods. There are two ways how it gets the mapping information - 1. from the method signature itself and 2. from the program and command configurations.

Following is a simple no-configuration example showing how command-line arguments are passed as the method parameters-

#!/usr/bin/env node
const Program = require('@auttam/easycli').Program

// a class representing the CLI program
class SampleProgram extends Program {
    // main method
    main(param1, param2){
        console.log('param1:', param1)
        console.log('param2:', param2)
    }
}

// runs the program
Program.run(new SampleProgram())

Assuming the above code is saved as index.js invoke the script as follwoing-

node ./index value1 value2

#expected output
param1: value1
param2: value2

Special parameters $params and $options

A program method (i.e. the main or the command methods) may also have two special parameters called $params and $optionsthat contain both known and unknown arguments supplied from the CLI.

Known arguments are the ones that are identified using internal (auto-generated) or external configuration. Whereas unknown are the unidentified arguments.

main(param1, param2, $params, $options) {
    console.log($params.param1)   // same as param1
    console.log($params.param2)   // same as param2
    console.log($params.$unknown) // array of unknown params
 }

Both $params, $options contains identified arguments as their own properties. All unidentified arguments are stored as $unknown property. Apart from $unknown these objects also provide $has and $get (options only) methods-

main(param1, param2, $params, $options) {
    console.log($params.param1)   // same as param1
    console.log($params.param2)   // same as param2
    console.log($params.$unknown) // array of unknown params
    
    // checks if param1 and param2 are supplied
    console.log($params.$has['param1', 'param2']) 

    // checks if option1 and option2 are supplied
    // note: it checks both known and unknown list
    console.log($options.$has['option1', 'option2'])

    // returns the value of option1 only if it is supplied 
    console.log($options.$get('option1'))
}

Autogenerated Configuration

EasyCli autogenerates a configuration from the program class and the methods to map the command-line arguments to the program methods and their parameters. This configuration can be customized later to provide more information on program, commands, parameters and options.

onInvalidCommand, onExit, onProgramOption methods

Apart from program methods (i.e. the main or the command methods), a program class may also contain 3 program event callback methods.

Program class members

Property Name Description
config program configuration object
Method Name Description
showHelp(commandName?:string) prints program or command help
showVersion() print program version info
static run(target) runs the instance of a program
static settings(settingObj) program settings