Examples
Introduction
This document specifies examples of using ProgramBuilder that should exemplify common use-cases.
Example invocations of the CLI program are presented using ts-node
and assuming that the main file is called main.ts.
Positional arguments
Positional arguments are specified using the .arg or .optionalArg methods.
The first argument is the destination key into which the argument value will be stored.
.arg is used to specify required arguments. If not enough required arguments are provided
to the program, it will exit with a non-zero code.
const program = ProgramBuilder.newBuilder().arg("foo").optionalArg("bar").build();program.exec(args => {// args: { foo: string, bar: string | null }console.log('Args are:', args);});
Example invocations:
$ ts-node main.tsNot enough positional arguments were specified. Expected: at least 1
$ ts-node main.ts helloArgs are: { foo: 'hello', bar: null }
$ ts-node main.ts hello worldArgs are: { foo: 'hello', bar: 'world' }
Promise-returning main functions
The function that you pass to exec can be an async or Promise-returning function.
If your function returns a Promise, it will be properly awaited on and if it throws an error that error will be output and the process will return a non-zero exit code.
const program = ProgramBuilder.newBuilder().build();program.exec(async args => {await someHTTPCall();});
Specifying your main function ahead of your builder
If you call program.exec with a lambda, its args argument will automatically assume
the correct type of the program's arguments.
However, if you want to specify your main function separately, you must use the
Arguments<T> type helper and pass typeof program.
typeof returns the TypeScript type of a variable, and the Arguments helper
(part of ProgramBuilder) unwraps the arguments type from a Program object.
function main(args: Arguments<typeof program>) {// Do stuff}const program = ProgramBuilder.newBuilder().build();program.exec(main);
Boolean flags
Use .flag() to specify a boolean flag. The first argument is the name of the flag,
including leading dashes (ex: "--count").
Multiple names can be specified by separating them with commas within the same string
(ex: "-c,--count").
The second argument is a list of options for the flag. The only required one of these
is dest, which indicates where to store the flag value within the arguments object
that is passed to your main function.
const program = ProgramBuilder.newBuilder().flag("-v,--verbose", { dest: "verbose" }).build();program.exec(args => {// args: { verbose: boolean }console.log('Args are:', args)});
Example invocations:
$ ts-node main.tsArgs are: { verbose: false }
$ ts-node main.ts -vArgs are: { verbose: true }
Valued flags
Flags can also take on a value, which the user specifies by providing a string immediately succeeding the flag on the commandline.
Valued flags are specified by calling one of several methods that are specialized to the desired type of the flag: string, integer, etc.
The typed value is then stored in the specified dest.
const program = ProgramBuilder.newBuilder().intFlag("--count", { dest: "count" }).stringFlag("--name", { dest: "name" }).build();program.exec(args => {// args: { count: number, name: string }console.log('Args are:', args);});
Example invocations:
$ ts-node main.tsThe following required flags were not specified: --count, --name
$ ts-node main.ts --count 2 --name billArgs are: { count: 2, name: 'bill' }
$ ts-node main.ts --count invalid --name bobPlease provide a valid integer for '--count'. Received: invalid
The current types of valued flags are:
stringFlagintFlagfloatFlagcustomFlag– allows you to provide your own converter function from a string to your custom type.
Optional vs Required Flags
Valued flags are required unless a default is specified. If a required flag
isn't provided, the program will exit with a non-zero return code.
The default may be null, in which case the flag is typed as T | null, or it
may be a value of the appropriate type, in which case that value will be assumed
in the case that the flag is omitted.
In help text, required flags are denoted by surrounding the variable in <braces>,
whereas optional flags are denoted by surrounding the variable in [brackets].
const program = ProgramBuilder.newBuilder().intFlag('-a', { dest: 'a' }).intFlag('-b', { dest: 'b', default: 0 }).intFlag('-c', { dest: 'c', default: null }).build();program.exec(args => {// args: { a: number, b: number, c: number | null }console.log('Args are:', args);});
Example invocations:
$ ts-node main.tsThe following required flags were not specified: -a
$ ts-node main.ts -a 1Args are: { a: 1, b: 0, c: null }
$ ts-node main.ts -hUsage: main.ts [options]Options:-a <a>-b [b]-c [c]
Adding descriptions
Call .description() on a ProgramBuilder
to set the program's description to be used in help text generation.
Add a description key to the options for a flag or an argument to set a description there.
const program = ProgramBuilder.newBuilder().description(`My awesome program`).arg('filename', { description: `Awesome description of a filename` }).optionalArg('optarg', { description: `This argument is optional` }).flag('--enable', { dest: 'enable', description: `Awesome description of the --enable flag` }).flag('--longboi', { dest: 'longboi', description: `Text is automatically wrapped. Text is automatically wrapped. Text is automatically wrapped. Text is automatically wrapped.` }).build();program.exec(() => {});
Example invocations:
$ ts-node main.ts -hUsage: main.ts [options] <filename> [optarg]My awesome programArguments:filename Awesome description of a filenameoptarg This argument is optional (optional)Options:--enable, --enable Awesome description of the --enable flag--longboi, --longboi Text is automatically wrapped. Text is automaticallywrapped. Text is automatically wrapped. Text isautomatically wrapped.
Custom flags
For valued flags, you may also provide your own custom converter function from a string into your desired type.
This is how intFlag and floatFlag, etc are implemented.
To activate this functionality, use the customFlag method, which is similar to
the other methods but takes a third argument which is a converter function.
The converter function should adhere to the Converter type's signature — it takes in two arguments, the string input and the argument name, and returns the converted value.
If the input is invalid, you should throw an Error and use the argName to enrich
your error message.
The return type of the converter (<T>) will be added to the ProgramBuilder's type.
const jsonConverter: Converter<any> = (input, argName) => JSON.parse(input);const program = ProgramBuilder.newBuilder().customFlag('--jsonInput', { dest: 'json' }, jsonConverter).build();program.exec(args => {// args: { json: any }console.log('Args are:', args);});
Example invocations:
$ ts-node main.ts --jsonInput '{"hello": "world"}'Args are: { json: { hello: 'world' } }
Subcommands
Use ProgramBuilder.buildWithSubcommands
to create a program with multiple sub-commands. This accepts a map from subcommand name to a
ProgramWithAction representing the subcommand
flags and the action to be performed when executing the subcommand.
A ProgramWithAction can be created by calling .bind()
on a ProgramBuilder instead of .build(). .bind() accepts a MainFunction as its argument,
indicating the function to be called when that subcommand is specified.
The map can be multiply nested, for subcommands that have child-subcommands which are separated
by spaces. For example {nested: {subcommand: someProgram}} would result in a subcommand named
"nested subcommand".
The second argument to buildWithSubcommands is an optional metadata object which can contain,
for example, the overall program description.
function generateMain(args: Arguments<typeof generate>) {console.log(`Generating to ${args.filename}`);}function cleanMain(args: Arguments<typeof clean>) {console.log('Cleaning output directory');}function nestedMain(args: Arguments<typeof nested>) {console.log(`This is a nested subcommand ${args.x}`);}const generate = ProgramBuilder.newBuilder().description(`Generate something`).arg('filename').bind(generateMain);const clean = ProgramBuilder.newBuilder().description('Clean the output directory').bind(cleanMain);const nested = ProgramBuilder.newBuilder().flag('-x', { dest: 'x' }).bind(nestedMain);const program = ProgramBuilder.buildWithSubcommands({generate,clean,this: {is: {nested}}}, {description: `A description of my subcommand`});// Execute the program. Note this doesn't take a main function, like// a normal Program.exec().program.exec();
Example invocations:
$ ts-node main.ts -hUsage: main.ts COMMAND [options]A description of my subcommandCommands:generate Generate somethingclean Clean the output directorythis is nested
$ ts-node main.ts generate -hUsage: main.ts generate <filename>Generate something
$ ts-node main.ts generate foo.txtGenerating to foo.txt
$ ts-node main.ts this is nested -hUsage: main.ts this is nested [options]Options:-x, -x
$ ts-node main.ts this is nested -xThis is a nested subcommand true