0.1.3 • Published 4 years ago

@darkobits/ridley v0.1.3

Weekly downloads
4
License
WTFPL
Repository
github
Last release
4 years ago

Ridley is an opinionated integration between Yargs and Cosmiconfig, two best-in-class tools for building robust command-line applications in Node. General familiarity with these tools is recommended before using Ridley.

The core feature of Ridley is the utilization of yargs.config() to pass data loaded by Cosmiconfig into Yargs, where we can then perform normalization, validation, set defaults (and more) all in one place.

In addition to this, Ridley applies some opinionated settings to both Yargs and Cosmiconfig to encourage consistency and best practices.

Install

npm i @darkobits/ridley

Getting Started

Let's assume we are building a CLI that will help us reticulate splines. The CLI has the following requirements:

  • It must be provided 1 required positional argument, spline, indicating the spline to be reticulated.
  • It may be provided 1 optional named argument, algorithm, indicating the reticulation algorithm to use. Valid algorithms are RTA-20, RTA-21, and RTA-22. If omitted, the default algorithm should be RTA-21.
  • These options may be provided as command-line arguments or via a file, .spline-reticulator.yml located in or above the user's current directory.

Let's build-out a quick CLI for this application to make sure options/arguments are being processed per the above requirements.

cli.ts

import Ridley from '@darkobits/ridley';

export default Ridley({
  command: '* <spline>',
  description: 'Reticulates splines using various algorithms.'
  builder: command => {
    // Add some addtional information about the "spline" positional argument.
    command.positional('spline', {
      description: 'Identifier of the spline to reticulate.',
      type: 'string'
    });

    // Define the "algorithm" named argument.
    command.option('algorithm', {
      description: 'Reticulation algorithm to use.',
      type: 'string',
      required: false,
      enum: ['RTA-20', 'RTA-21', 'RTA-22'],
      default: 'RTA-21'
    });
  },
  handler: config => {
    // This is where we would normally call our application, but let's just log
    // the configuration we got.
    console.log(config);
  }
})

First, lets try invoking spline-reticulator --help to make sure everything looks okay:

Command Line

$ spline-reticulator --help

Output

spline-reticulator <spline>

Reticulates splines using various algorithms.

Positionals:
  spline  Identifier of the spline to reticulate.                                               [string]

Options:
  --algorithm    Reticulation algorithm to use.
                                    [string] [choices: "RTA-20", "RTA-21", "RTA-22"] [default: "RTA-21"]
  -v, --version  Show version number                                                           [boolean]
  -h, --help     Show help                                                                     [boolean]

We can verify that our CLI works as expected by calling it in various ways, and ensuring it uses a configuration file when present:

Command Line

$ spline-reticulator 402B

Output

{
  '$0': 'spline-reticulator',
  _: [],
  spline: '403b'
  algorithm: 'RTA-21',
}

Let's provide an invalid algorithm and ensure that Yargs catches this mistake:

spline-reticulator 402B --algorithm RTA-16
spline-reticulator <spline>

Positionals:
  spline  Identifier of the spline to reticulate.                                               [string]

Options:
  --algorithm    Reticulation algorithm to use.
                                    [string] [choices: "RTA-20", "RTA-21", "RTA-22"] [default: "RTA-21"]
  -v, --version  Show version number                                                           [boolean]
  -h, --help     Show help                                                                     [boolean]

Invalid values:
  Argument: algorithm, Given: "foo", Choices: "RTA-20", "RTA-21", "RTA-22"

Configuration File

If we are working in a project that contains splines that should always be reticulated using a specific algorithm, and we wanted to track that requirement in source control, we can leverage a configuration file to indicate the algorithm to use:

.spline-reticulator.yml

algorithm: RTA-22
spline-reticulator 402B
{
  '$0': 'spline-reticulator'
  _: [],
  spline: '402B',
  algorithm: 'RTA-22',
}

TypeScript Integration

Ridley is written in TypeScript and leverages Yargs' excellent TypeScript support. If you have created a type definition for your application's configuration/command-line arguments, it may be passed to Ridley as a type argument and Ridley will ensure the object passed to your handler is appropriately typed.

Example

interface SplineReticulatorOptions {
  spline: string;
  algorithm: 'RTA-20' | 'RTA-21' | 'RTA-22';
}

Ridley<SplineReticulatorOptions>({
  handler: ({argv}) => {
    // TypeScript will know that argv.spline is of type 'string' here.
  }
})

API

Ridley accepts a single options object, the API for which is very similar to that of Yargs' command module API, which configures each command using a single options object rather than by chaining several method calls.

The interface for this object is defined below.

command

Type: string Required: Yes Default: '*'

Name of the command being implemented, or * to handle the root command. This is also where any positional arguments should be defined, using <> to wrap required arguments and [] to wrap optional arguments. Positionals may then be annotated with additional metadata in the builder function using .positional.

See: Positional Arguments

aliases

Type: Array<string> Required: No Default: N/A

Only practical when implementing sub-commands, this option allows you to specify a list of aliases for the command.

See: Command Aliases

description

Type: string Required: No Default: See below.

Top-level description for the command itself. If left blank, Ridley will use the description field from your project's package.json.

Note that if you use .usage() in your builder function, it will override this description.

builder

Type: (command: Argv): void Required: Yes Default: N/A

This function will be passed an object that will allow you to further configure the command. The API exposed by this object is almost identical to that of Yargs itself, but the context you are configuring is scoped to the command defined by command, making this API preferable to using the global Yargs object. The .positional() and .option() methods will be the most-used in your builder to define the options for the command.

config

Type: CosmiconfigOptions Required: No Default: See below.

Allows for configuration of Cosmiconfig. This object also supports the moduleName option, which is typically provided as a separate parameter to Cosmiconfig. By default, Ridley will use the unscoped part of the name field from your project's package.json (re: the portion after the /, if it has one, or the entire name otherwise).

Therefore, if our package's name was spline-reticulator, and no config.moduleName option was provided, Ridley will use spline-reticulator as the module name. This will be provided to the call to Cosmiconfig to initialize it. It will also be used to set a custom set of search paths for configuration file names shown below:

[
  // Look for a "spline-reticulator" key in package.json.
  'package.json',
  // Look for .spline-reticulatorrc in JSON or YAML format.
  `.${moduleName}rc`,
  // Look for .spline-reticulator.json in JSON format.
  `.${moduleName}.json`,
  // Look for .spline-reticulator.yaml in YAML format.
  `.${moduleName}.yaml`,
  // Look for .spline-reticulator.yml in YAML format.
  `.${moduleName}.yml`,
  // Look spline-reticulator.config.js that exports a configuration object.
  `${moduleName}.config.js`,
];

strict

Type: boolean Required: No Default: true

Whether to configure Yargs to use strict mode. In strict mode, any additional options passed via the CLI or found in a configuration file will cause Yargs to exit the program. This is generally a good idea because it helps catch typos. However, if your application's configuration file supports more options than you would like to consume via the CLI, then you will need to disable strict mode. Also be aware that because these additional options will not be defined in your builder, Yargs will not perform any validation on them.

handler

Type: (result: RidleyResult): void Required: Yes Default: N/A

Identical to the handler option used when defining a Yargs command module. This function will be provided parsed/validated arguments/options. The signature of this function in Ridley differs slightly from the Yargs version; it is passed a single object with the following keys:

KeyDescription
argvParsed arguments/configuration from Yargs with all kebab-case keys stripped.
rawConfigParsed configuration from Cosmiconfig. This is usually not required. However, if the same option was set via both a command-line argument and a configuration file, the argument will take precedence in argv. Therefore, when conflicts occur, this object can be used to check the value from the configuration file.
configPathPath where Cosmiconfig found a configuration file, if any.
configIsEmptyTrue if a configuration file was found, but was empty. It is often a good idea to warn users in this case.
packageJsonNormalized package.json for the application. Useful if you want to print the current version somewhere, for example.
packageJsonPathAbsolute path to the application's package.json.
packageRootAbsolute path to the application's root (the directory containing package.json)

Caveats

  • If your application has required positional arguments, these must always be provided via the command-line. This is a Yargs limitation.