@api-typed/command-line
TypeScript icon, indicating that this package has built-in type declarations

0.1.3 • Public • Published

⌨️ Api-Typed/Command-Line

🥣 An Api-Typed component.

Command-Line helps in creating CLI commands in a structured way, leveraging the goods of TypeScript and making it easy to use powerful tools like Dependency Injection Containers.

Using @Command() decorator to register classes as command handlers is the main and most powerful feature.

It wraps around Commander abstracting away implementation and configuration details so that all you need to worry about is writing your well designed code.

At a glance

Define a command handler:

@Command('hello <name> [nickname]', {
  shout: 's',
})
export class Hello implements CommandInterface {
  public async run(
    name: string,
    nickname?: string,
    options: { shout?: boolean } = {},
  ) {
    // ...
  }
}
  1. Create a class that implements CommandInterface
  2. Decorate it with @Command(): a. Use command signature to define it's name, <arguments> and [optional_arguments] b. Define options in an object.
  3. Get all CLI arguments and options as parameters of .run() method.
  4. Run with $ npx api-typed hello Michael if using Api-Typed framwork.

You can also use a dependency injection container!

Setup

With Api-Typed

To get the best developer experience use it with Api-Typed in your app.api-typed.ts file:

import { App, CommandLineModule } from 'api-typed';

export default new App(__dirname, [
  // ... your other modules
  new CommandLineModule(),
]);

It's already included for you out of the box when using StandardApp.

Then let your app module (or any module you create) implement HasCommands interface with a loadCommands() method that should return an array of class names that implement CommandInterface.

The easiest way to do this:

import { AbstractModule, HasCommands, loadCommands } from 'api-typed';

export class MyModule extends AbstractModule implements HasCommands {
  public readonly name = 'my_module';

  public loadCommands(config: Config) {
    // load all commands from './commands' dir relative to this file
    return loadCommands(`${__dirname}/commands/**/*.{ts,js}`);
  }
}

Call your command with:

$ npx api-typed command-name arg1 arg2 --opt1

Stand-alone

You can easily use this as a stand-alone package in any project.

$ npm i -S @api-typed/command-line

Setup is pretty straight forward:

import CommandRunner, { loadCommands } from '@api-typed/command-line';

// first register your commands using a glob pattern
loadCommands(`${__dirname}/commands/**/*.{ts,js}`);

// and then call the runner to parse CLI arguments
// and delegate execution to appropriate command class
CommandRunner.run();

Registering commands with @Command() decorator

The easiest way to create and register commands is by using the @Command() decorator on a class that implements CommandInterface:

import { Command, CommandInterface } from '@api-typed/command-line';

@Command('hello <name> [nickname]', {
  shout: 's',
})
export class Hello implements CommandInterface {
  public async run(
    name: string,
    nickname?: string,
    options: { shout?: boolean } = {},
  ) {
    // ...
  }
}

Command signature

The command signature is also it's name. It's also a very convenient way to define required and optional arguments for the command.

Given:

@Command('hello <name> [nickname]')

The command will be called hello and require 1 argument and optionally accept 2nd argument. It could be called with:

$ npx api-typed hello Gandalf

or

$ npx api-typed hello Gandalf "The Grey"

These arguments will be mapped to arguments of .run() method of the command class:

import { Command, CommandInterface } from '@api-typed/command-line';

@Command('hello <name> [nickname]')
export class Hello implements CommandInterface {
  public async run(name: string, nickname?: string) {
    // ...
  }
}

Arguments are always strings.

Defining command options

A command can also accept options from the command line in the form of --option-name or -o. But first you need to register them on the command:

import { Command, CommandInterface } from '@api-typed/command-line';

@Command('hello <name> [nickname]', {
  shout: 's',
  level: {
    short: 'l',
    description: 'What log level to use?',
    value: 'required',
    default: 'info',
    choices: ['notice', 'error', 'info'],
  },
})
export class Hello implements CommandInterface {
  public async run(name: string, nickname?: string, options = {}) {
    console.log(options);
  }
}

The options object is always passed as the last argument to the .run() method.

If the above example was called with:

$ npx api-typed hello Gandalf -s --level notice

The options object would look like:

{
  shout: true,
  level: 'notice',
}

Using Dependency Injection

If you want your commands to be bootstrapped using a dependency injection container (like e.g. TypeDI) configure it by calling:

CommandRunner.useContainer(/* your container */);

The passed container MUST implement .get(identifier: Function) method that can construct and retrieve objects based on their class.

Then your command class supports any methods of dependency injection that your container offers.

Misc

Banner

If you want to print out anything before any command output, you can do so using the banner functionality:

CommandRunner.setBanner(/* any string */);

App Banner

You can also output an "app banner" before any other output:

CommandRunner.setAppBanner(/* app name */, /* optional version */);

You can use both banner and app banner at the same time.

Start callback

You can execute a callback just before command execution starts using the .onStart() method:

CommandRunner.onStart((signature?: string): void => {
  /* do something */
});

The callback will receive a single argument with the signature of the executing command.

The callback can be async.

Custom exit handler

You can execute a callback when the command finishes (either by returning or throwing an error) using the .onExit() method:

CommandRunner.onExit((exitCode: number, error?: unknown) => {
  /* do something */
});

The callback will receive an exit code as the 1st argument and an error (if thrown) as 2nd.

Readme

Keywords

Package Sidebar

Install

npm i @api-typed/command-line

Weekly Downloads

11

Version

0.1.3

License

MIT

Unpacked Size

30.2 kB

Total Files

27

Last publish

Collaborators

  • michaldudek