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

@api-typed/command-line

v0.1.3

Published

Command line interface tools.

Downloads

16

Readme

⌨️ 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.