Page cover

command

Define your CLI's commands.

command(config: SaffronCommand): void

Saffron's command function accepts a single object, SaffronCommand. The API for which is very similar to that of Yargs' command module API, which configures each command for an application using a single object as opposed to the more idiomatic Yargs approach of chaining method calls.

A SaffronCommand object may have the following properties:

command?: string

Default: *

Name of the command being implemented. If the application only implements a root command and does not take any positional arguments, this option may be omitted. If the application takes positional arguments, they must be defined here using <> to wrap required arguments and [] to wrap optional arguments. Positionals may then be further annotated in the builder function using .positional.

See: Positional Arguments

This option is a pass-through to Yargs' command property.

Example:

The following example illustrates how to define required and optional positional arguments in the command property and how to further annotate them in builder:

cli.ts
import * as cli from '@darkobits/saffron';

cli.command({
  // Because positionals are sensitive to order, they must be initially declared
  // here.
  command: '* <requiredPositional> [optionalPositional]',
  builder: ({ command }) => {
    // They can then be further annotated in the command's builder.
    command.positional('requiredPositional', {
      // ...
    });

    command.positional('optionalPositional', {
      // ...
    });

    // Only positionals need to be declared in the command property above.
    // Named arguments may be declared and annotated at the same time using
    // command.option().
    command.option('someFlag', {
      // ...
    });
  }
});

cli.init();

aliases?: Array<string>

Default: N/A

This option allows you to specify a list of aliases for the command.

See: Command Aliases

This option is a pass-through to Yargs' aliases property.

Example:

cli.ts
import * as cli from '@darkobits/saffron';

cli.command({
  command: 'configure',
  aliases: ['config']
});

cli.init();

description?: string

Default: See below.

Description for the command. If left blank, Saffron 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.

This option is a pass-through to Yargs' describe property.

Example:

cli.ts
import * as cli from '@darkobits/saffron';

cli.command({
  description: 'Reticulates a spline.'
});

cli.init();

Default: See below.

Settings for Cosmiconfig, which is responsible for locating and parsing an application's configuration file. In addition to the below options, this object may contain any valid Cosmiconfig option.

Alternatively, this option may be set to false to disable configuration file support entirely.

config.auto?: boolean

Default: true

By default, after loading an application's configuration file, Saffron will call Yargs' .config() method, passing it the data from the configuration file. This will have the effect of allowing configuration data to serve as default values for any arguments the application accepts, including positionals. This is referred to as auto-configuration.

If an application's command-line argument schema and configuration schema differ, auto-configuration can be disabled by setting auto to false. In such cases, Saffron will only load an application's configuration file and pass the parsed result to the command's handler.

Example:

cli.ts
import * as cli from '@darkobits/saffron';

cli.command({
  config: { auto: false },
  handler: ({ argv, config }) => {
    // argv = parsed and validated arguments
    // config = parsed configuration
  }
});

cli.init();

config.fileName?: string

Default: See below.

By default, Saffron will use the un-scoped portion of an application's name from its package.json. If you would prefer to use a different base name for configuration files, you may provide your own fileName option. This is equivalent to the moduleName value used throughout the Cosmiconfig documentation.

Example:

cli.ts
import * as cli from '@darkobits/saffron';

cli.command({
  config: {
    // Look for files like .my-app.yml, my-app.config.js, etc.
    fileName: 'my-app'
  }
});

cli.init();

config.key?: string

Default: N/A

For applications with multiple commands, it may be desirable to scope configuration for a particular command to a matching key in the application's configuration file. If this option is set, Saffron will only use data under this key (rather than the entire object) to configure the command.

One could opt to simply drill-down to these sub-keys explicitly in a handler, but providing a key here lets Saffron's auto-configuration feature pass the data at the indicated key through Yargs' validator.

Example:

cli.ts
import * as cli from '@darkobits/saffron';

cli.command({
  command: 'build',
  // Drill-down to this key in users' configuration object when applying
  // auto-configuration / validation for this sub-command.
  config: { key: 'build' },
  builder: ({ command }) => {
    command.positional('outDir', {
      type: 'string',
      required: true
    });
  }
});

cli.command({
  command: 'serve',
  // Drill-down to this key in users' configuration object when applying
  // auto-configuration / validation for this sub-command.
  config: { key: 'serve' },
  builder: ({ command }) => {
    command.positional('port', {
      type: 'number',
      required: true
    });
  }
});

cli.init();
app.config.js
export default {
  build: { outDir: 'dist' },
  serve: { port: 3000 }
};

config.searchFrom?: string

Default: process.cwd()

Directory to begin searching for a configuration file. Cosmiconfig will then walk up the directory tree from this location until a configuration file is found or the root is reached.

config.searchPlaces?: Array<string>

Default: See below.

Saffron overrides the default searchPlaces option in Cosmiconfig with the below defaults, where fileName is the base file name for your application as derived from package.json or as indicated at config.fileName (see above). Using our example package name of @fluffykins/spline-reticulator, the below snippet has been annotated with examples of the exact file names Saffron would tell Cosmiconfig to search for.

[
  // Look for a "spline-reticulator" key in package.json.
  'package.json',
  // Look for .spline-reticulator.json in JSON format.
  `.${fileName}.json`,
  // Look for .spline-reticulator.yaml in YAML format.
  `.${fileName}.yaml`,
  // Look for .spline-reticulator.yml in YAML format.
  `.${fileName}.yml`,
  // Look for spline-reticulator.config.js that should have a default export.
  `${fileName}.config.js`
];

For comparison, the default Cosmiconfig searchPlaces can be found here.

config.explicitConfigFileParam?: string

Default: N/A

Many applications that support configuration files implement a flag like --config that allows the end-user to specify an explicit path to a configuration file. When such a flag is provided, the application will bypass searching for a file and instead load the file provided. If your application implements such a feature, provide the name of the command-line parameter that your application uses to allow users to indicate an explicit configuration file. When this parameter is present, Saffron will skip the search phase and load the indicated file instead.

cli.ts
import * as cli from '@darkobits/saffron';

cli.command({
  config: { explicitConfigFileParam: 'config' },
  builder: ({ command }) => {
    command.positional('config', {
      type: 'string',
      description: 'Load a configuration file at the provided path.'
      required: false
    });
  }
});

cli.init();
$ spline-reticulator --config custom.config.js

strict?: boolean

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 (if config.auto is true) will cause Yargs to exit the program and report an error. This is generally a good idea as it can help catch common mistakes like typos from user input.

Default: N/A

This function allows a command's arguments to be configured and annotated. Saffron builders are passed a single object, SaffronBuilderContext which has the following properties:

Property
Type
Description

pkg.json

Normalized package.json for the application.

pkg.root

string

Absolute path to the application's root directory.

Example:

cli.js
import * as cli from '@darkobits/saffron';

cli.command({
  builder: ({ command, pkg }) => {
    // Use the provided `command` API here to define arguments and configure
    // behavior.
    command.positional('requiredPositional', {
      type: 'boolean'
    });
    
    command.option('optionalArgument', {
      type: 'string'
    });
    
    // Use `pkg` to introspect your application's package metadata or location.
    command.epilogue(`Report a bug: ${pkg.json.bugs}`);
  }
});

Default: N/A

This function is invoked at the end of Saffron's lifecycle once arguments and configuration have been parsed. It is analogous to the handler option used when defining a Yargs command.

Saffron handlers are passed a single object, SaffronHandlerContext, which has the following properties:

Property
Type
Description

argv

Parsed arguments/configuration from Yargs and Cosmiconfig.

config

any

Parsed configuration file.

configPath

string

undefined

configIsEmpty

boolean

true if a configuration file was found, but was empty. It is often a good idea to warn users in this case.

pkg.json

Normalized package.json for the application.

pkg.root

string

Absolute path to the application's root directory.

Example:

cli.js
import * as cli from '@darkobits/saffron';

cli.command({
  command: '* <requiredPositional>',
  builder: // omitted for brevity
  handler: ({ argv, config, pkg }) => {
    // Pass `argv`, `config`, etc. to the command's implementation as needed.
    // Use `pkg` to introspect your application's package metadata or location.
  }
});

Last updated