npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@jib/cli

v0.1.7

Published

Full-featured extensible CLI development framework

Downloads

85

Readme

The jib Command Line Framework

This project is meant to serve as a lightweight, reusable, TypeScript first CLI framework for projects with any level of CLI command functionality.

npm version wercker status codecov GitHub license install size

Why 'jib'

OK, so there's actually some method to the madness... The @jib/cli was inspired by the extremely popular Commander.js CLI development framework. In sailing, the jib is an essential component to commanding a ship - thus the name.

| jib |: A triangular staysail set forward of the forwardmost mast.

While it's not a dependency, this package was also somewhat influenced by Heroku's @oclif/command - namely that it's designed to be highly performant for large CLI applications. Other similiarities are:

  • TypeScript first development experience.
  • Only require(s) a file when its command is called.
  • Minimal dependencies & VERY lightweight.

Some additional benefits of @jib/cli include:

  • Run any combination of single/multi command CLIs in one project.
  • Supports custom command delimiter syntax (such as <command>:<subcommand> or <command> <subcommand>).
  • Built-in logger and ui stream classes.
  • Simple plugin framework.

Quickstart

The best way get started with @jib/cli is to use the @jib/jib project generator.

npx @jib/jib init --help

Structure

The basis for command processing is the project structure where they're defined. The example below shows one possible structure and its resulting commands.

sample $> tree
.
├── bin
├── package.json
├── src
│   └── commands
│       ├── help.ts
│       └── project
│           ├── build.ts
│           └── init
│               ├── desktop.ts
│               ├── mobile.ts
│               └── web.ts
└── tsconfig.json
  1. sample help
  2. sample project build
  3. sample project init <desktop|mobile|web>

Get Started

  1. Install & configure the @jib/cli in package.json:
{
  "jib": {
    "commandDir": "./build/commands", // default "commands"
    "commandDelim": ":", // default " "
  },
  "dependencies": {
    "@jib/cli": "latest"
  }
}

NOTE: that the "jib" configuration should reference the final compiled project outputs. The @jib/cli parser will automatically detect development envs where ts-node is used to transpile on the fly.

  1. Add a "bin" configuration to package.json where the node executable is located within the project, such as ./bin/jib:
#! /usr/bin/env node

/* Example `bin/jib` implementation */
const { CLI } = require('@jib/cli');

const parser = new CLI({ /* options */ });
parser.parse(process.argv);
/* "bin" entry in package.json */
{
  "bin": {
    "myjib": "bin/jib"
  }
}

NOTE: because of certain nuances in local development enviromments, it is best to use a static file as the bin, rather than a file emitted by TypeScript. Generally it's a good idea to ensure this file has executable permissions (ie chmod +x bin/jib in the example above).

  1. Configure the new CLI({ /* options */ }) for your implementation:

| Option | Description | Default | Required | |--|--|--|--| | baseDir | Used to specify an alternate project directory. ⚠️ Unlikely to be necessary - approach with caution | '.' | | | commandDelim | Use a custom delimiter for subcommands. Must have length === 1 | ' ' | | | commandDir | Directory where command implementations are stored. This should be the transpiled output directory, such as "build/commands" | null | ✅ | | rootCommand | Run a single command implementation when arguments don't resolve as subcommands. For example, create a main.ts implementation and specify rootCommand: "main" | null | |

  1. Add files and folders to the commandDir path, then invoke by name:

Example command implementation hello.ts

import { Command, BaseCommand } from '@jib/cli';

export interface IOptsHello {
  /* optionally define option types for `run` method signature */
}

@Command({
  description: 'my hello world command',
  args: [ /* configure command args here */ ],
  options: [ /* configure option flags here */ ],
})
export class Hello extends BaseCommand {
  public async run(options: IOptsHello, ...args: any[]) {
    this.ui.output('hello world');
  }
}

Command Anatomy

This is meant to cover all facets of @jib/cli command implementations. If you'd rather move along and read details of CLI processing, then skip ahead to the run method section.

Command and BaseCommand

The first line of most commands will import two objects from @jib/cli. These objects represent the foundation of any command implementation, however only the use of Command is required.

import { Command, BaseCommand } from '@jib/cli';

| ref | ¯\(ツ)/¯ | |--|--| | Command | Class decorator providing static command configuration as "annotations" | | BaseCommand | Extensible command abstract that declares the public async run() contract, and provides ui and logger member instantiations - use is optional |

@Command decorator

Aside from the project structure, this is the main instruction between a command implementation and the parser. The @Command() decorator is what describes the command and its arguments/options.

@Command({
  description: 'The purpose of your command',
  args: [ /* configure command args here */ ],
  options: [ /* configure option flags here */ ],
})
export class MyCommand extends BaseCommand { /* see below*/ }

Command Arguments

As part of the @Command annotations, args are specified as an array of argument definitions where ICommandArgument represents a single argument definition.

interface {
  args?: ICommandArgument[];
}

| ICommandArgument | Type | Description | Default | Required | |--|--|--|--|--| | name | string | The argument name to be rendered in usage text | '' | ✅ | | optional | boolean | Indication of whether or not the argument is optional | false | | | multi | boolean | Indicate variadic argument (multiple args as one array). If true, then must also be the last | false | |

Command Options

Specifing options for a command is also done with the @Command decorator.

interface {
  options?: ICommandOption[];
}

| ICommandOption | Type | Description | Default | Required | |--|--|--|--|--| | flag | string | The option flag syntax | null | ✅ | | description | string | Option description for help rendering | '' | | | default | any | Default option value | null | | | fn | ((val: any, values: any) => any) | RegExp | Value processing function, especially useful for accepting multiple values with a single flag | null | |

Option Flag Syntax

Each flag may be written in the format [-{short}, ]--{name}[ [value]|<value>]. Some examples:

  • -c, --cheese <type> requires "type" in cheese option
  • -p, --project [path] "path" is optional for the project option
  • -d, --debug simple boolean option
  • --test a longhand only flag

Short boolean flags may be passed as a single argument, such as -abc. Multi-word arguments like --with-token become camelCase, as options.withToken. Also note that multi-word arguments with --no prefix will result in false as the option name value. So --no-output would parse as options.output === false.

If necessary, refer to commander for more information.

The run method

The last part of a command implementation is its public async run method. Once the parser is done parsing, and the program is done programming, it's finally time for a resolved command to run.

interface {
  /* Async method called on the instantiated command */
  run(options: {[name: string]: any}, ...args: any[]): Promise<any>;
}

Naturally it is the job of each command implementation to do whatever magic it must do according to its user's wishes. Hmm... sort of like a genie. All the program needs is for this method to return a Promise.

As is shown in the call signature above, the first argument will be the resolved options as defined in the decorator. Note that each property key will be defined as the long, camelCased option name (unless using the --no prefix as mentioned).

Then, all resolved args will be passed in the order which they are defined, again by using the decorator. Note that if an argument is declared as multi: true, then its value will be the final argument, and of type string[].

Plugins

Support for plugins is in it's early stages. Once stable, more information will be added here. Stay tuned...

TypeScript

This project is designed to embody the many benefits of using TypeScript, and recommends that users do the same. While vanilla JS is technically possible, it is not officially supported at this time.

TODOs

  • [x] Add support for single, named, command - this is configured as "rootCommand"
  • [ ] Enable command ordering with ICommand.priority annotation
  • [ ] Support command group with file implementation (descriptor) & directory of same name
  • [ ] Include test harness for implementations @jib/cli/testing
  • [ ] Add Dockerfile to cli implementations for docker-based execution
  • [ ] Add built-in support for stdin pipes
  • [ ] Add support for custom text additions on -v|--version (copyright, foo, etc.)
  • [ ] Add support for command mispellings
  • [ ] Add support for command aliases
  • [x] Add support for plugins
    • [ ] Yeoman generator framework @jib/cli-codegen
    • [ ] Config Storage framework @jib/cli-storage
    • [ ] Prompting @jib/cli-prompt
    • [ ] UI spinner @jib/cli-spinner
    • [ ] UI table @jib/cli-table
    • [ ] UI image rendering @jib/cli-image
    • [ ] UI video rendering @jib/cli-video