New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

cilly

Package Overview
Dependencies
Maintainers
1
Versions
26
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

cilly

The last library you'll ever need for building intuitive, robust and flexible CLI tools with Node.js and TypeScript.

1.0.8
Source
npm
Version published
Weekly downloads
44
-2.22%
Maintainers
1
Weekly downloads
 
Created
Source

Cilly

ci coverage size version

The last library you'll ever need for building intuitive, robust and flexible CLI tools with Node.js and TypeScript.

Table of contents

Installation

npm install cilly

Basic usage

With the file build.ts:

#!/usr/local/bin/ts-node

import { CliCommand } from 'cilly'

function buildApartment(args, opts, extra) { ... }
function buildHouse(args, opts, extra) { 
   return `Built a house with ${opts.numRooms} rooms for ${opts.residents.length} people at ${args.address}, ${args.state}!` 
}

// Main command, the CLI entrypoint
const cmd = new CliCommand('build')
   .withHandler(() => { cmd.help() })
   .withDescription('Builds houses or apartments')
   .withOptions({ 
      name: ['-r', '--num-rooms'], 
      defaultValue: 1, 
      description: 'The number of rooms',
      args: [{ name: 'n', required: true }]
   })
   .withSubCommands(
   
      // Subcommand for building houses
      new CliCommand('house', { inheritOpts: true })
         .withHandler(buildHouse)
         .withDescription('Builds a house')
         .withArguments(
            { name: 'address', required: true },
            { name: 'state', required: false }
         )
         .withOptions(
            { 
              name: ['-re', '--residents'], 
              required: true, 
              description: 'The people living in the house ',
              args: [{ name: 'residents', variadic: true }]
            }
         ),
         
      // Subcommand for building apartments
      new CliCommand('apartment', { inheritOpts: true })
         .withHandler(buildApartment)
         .withDescription('Builds an apartment')
         .withArguments({ name: 'address', required: true })
         .withOptions(
            { name: ['-f', '--floor'], required: true, description: 'The floor of the apartment' },
            { name: ['-re', '--residents'], required: true, args: [{ name: 'residents', variadic: true }]},
         )
   )

const result = await cmd.process(process.argv)
// Input: build house "Code St. 12" "CA" -r=4 --residents "Anders Brams"
// Result: "Built a house with 4 rooms for 1 people at Code St. 12, CA!"

Running build.ts --help gives:

Usage: build [options]

Builds houses or apartments

Options:
  -h, --help           Display help for command
  -r, --n-rooms [n]    The number of rooms (default: 1)

Commands:
  house <address> [state] [options]
  apartment <address> [options]

Running build.ts house --help:

Usage: house <address> [state] [options]

Builds a house

Options:
  -h, --help                           Display help for command
  -r, --n-rooms <n>                    The number of rooms (default: 1)
  -re, --residents [...residents]      The people living in the house (required)

Running build.ts house "Code St. 12, CA" -r 4 --residents "Anders Brams" "Joe Schmoe" "Jane Dane" invokes buildHouse() with the arguments:

args: {
   address: 'Code St. 12',
   state: 'CA'
},
opts: {
   numRooms: 4,
   residents: [
      "Anders Brams",
      "Joe Schmoe",
      "Jane Dane"
   ]
},
extra: []

Documentation

Before delving into the specifics of the package, a definition of the fundamental concepts is in order: arguments and options.

Arguments are simply values passed directly to a command or an option.

Options are named flags, e.g. --option that can also be assigned their own arguments.

Arguments

Arguments are provided to a command with the withArguments() chain method. The withArguments() method takes a list of Argument type options:

type Argument = {
  name: string,                    // The name of the argument
  required?: boolean,              // If true, throws an error if it's not provided
  variadic?: boolean,              // If true, parses a list of of argument values
  description?: string,            // Description of the argument (not shown in help, but provided in .dump())
  defaultValue?: ArgumentValue,    // The value of the argument if it's not provided
  onParse?: OnParseHook,           // Hook to run immediately when the argument is parsed from the command line
  onProcess?: OnProcessHook,       // Hook to run when all arguments and options have been parsed from the command line
  validator?: Validator            // Validation function used to validate the parsed value of the argument
}

Argument names must be dash-separated, alpabetic strings e.g. my-house, email, docker-compose-file, etc. After parsing, arguments are accessible through their camelCased names in the args argument to the command handler, e.g.:

new CliCommand('args-documentation-example')
   .withArguments(
      { name: 'my-house' },
      { name: 'email' },
      { name: 'docker-compose-file' }
   ).withHandler((args, opts, extra) => {
      console.log(args.myHouse)
      console.log(args.email)
      console.log(args.dockerComposeFile)
   })

Variadic arguments

To let an argument be parsed as a list of values, mark it as variadic:

new CliCommand('download')
   .withArguments({ name: 'websites', variadic: true })
   
// Terminal: download https://github.com https://abrams.dk https://npmjs.com
// args.websites = ['https://github.com', 'https://...'] in the command handler

Variadic arguments parse values from the command line until either

  • The variadic terminator -- is parsed
  • An option name is parsed
  • The input stops

So it's perfectly possible to have two variadic arguments, they just need to be terminated:

new CliCommand('download')
   .withArguments(
      { name: 'websites', variadic: true },
      { name: 'files', variadic: true }
   )

// Terminal: download https://github.com -- cilly-cli/cilly.git robots.txt
// args.websites = ['https://github.com', ...]
// args.files = ['cilly-cli/cilly.git', 'robots.txt']

Options

Options are provided to a command with the withOptions() chain method. The withOptions() method takes a list of Option type arguments:

type Option = {
  name: [string, string],       // The short and long flag for the option
  required?: boolean,           // If true, throws an error if the option is not provided
  negatable?: boolean,          // Automatically registers a negating --no-* flag
  args?: Argument[],            // Parses arguments as the option value instead of a boolean flag
  defaultValue?: OptionValue,   // The default value of the option if it is not provided
  description?: string,         // Description of the option (shown in help)  
  onParse?: OnParseHook,        // Hook to run immediately when the option is parsed from the command line
  onProcess?: OnProcessHook,    // Hook to run when all arguments and options have been parsed from the command line
  validator?: Validator         // Validation function used to validate the parsed value of the option
}

The option's name is provided as an array of two strings; the short and long flag name.

  • Short option names == argument names starting with -
  • Long option names == argument names starting with --
new CliCommand('build')
   .withOptions(
      { name: ['-r', '--rooms'] },
      { name: ['-s', '--street-name'] }
   )

Option arguments

Options can take arguments just like a command can. In the command line, options can be assigned in two ways:

  • With = assignment, e.g. build house --rooms=4
  • With normal assignment, e.g. build house --rooms 4

Here's an example of an option with three arguments - in the help text, this would be shown as

Options:
  -r, --residents <owner> [...adults] [...children]
new CliCommand('build')
   .withHandler((args, opts, extra) => {
      console.log(opts)
   })
   .withOptions(
      { name: ['-r', '--residents'], args: [
         { name: 'owner', required: true }
         { name: 'adults', variadic: true, required: false, },
         { name: 'children', variadic: true, required: false }
      ]}
   )

The above handler would print the following:

{
   residents: {
      owner: ...,
      adults: [...],
      children: [...]
   }
}

If an option only has a single argument, that argument is then collapsed into the option value so it's simpler to access:

new CliCommand('build')
   .withOptions(
      { name: ['-o', '--owner'], args: [
         { name: 'owner', required: true }
      ]}
   )

Parsing the input build --owner=anders (or build --owner anders) would produce the following opts object:

{
   owner: 'anders'
}

Negating flags

Sometimes, it's useful to allow users to explicitly negate an option flag. For example, in the hooks section we cover how hooks can be used to prompt users for option values if they are not provided.

It's good UX to allow the user to explicitly negate the flag when they don't want it so they can avoid being prompted.

To register a negating flag, simply set negatable: true:

new CliCommand('batman')
   .withOptions(
      { name: ['-p', '--parents'], negatable: true, description: 'Name of (living) parents' }
   )

With this, users can pass --no-parents in the command line, which will set opts.parents to false. This is also shown in the help text:

Usage: batman [options]

Options:
  -p, --parents (--no-parents)     Name of (living) parents

Commands

Subcommands

Option inheritance

Validators

Hooks

onParse()

onProcess()

Generating documentation

Custom help handlers

Custom exception handling

Keywords

cli

FAQs

Package last updated on 14 Feb 2021

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts