0.4.3 • Published 3 years ago

artesa v0.4.3

Weekly downloads
-
License
MIT
Repository
github
Last release
3 years ago

Artesa (Artesà)

Artesà is a NodeJS class-oriented complete solution for creating command line interfaces. It's strongly influenced by Laravel artisan CLI

Note! This project is early WIP. Documentation is WIP as well.

Features

  • Class oriented extendable commands.
  • Dynamic autogenerated CLI help feedback and Command help feedback.
  • Expressively argument/option parser.
  • Input/Output utilities (colors, warnings, etc...)

Getting Started

Install artesa as a dependency:

npm install artesa

Then, create the CLI entrypoint:

// cli.ts
import { CLI } from 'artesa'

CLI.execute({
  //Routes
}).run(process.argv.slice(2))
  .then(r => r)
  .catch(e => console.error(e));

In artesa, CLI works like a router. You provide a list of "paths" and which commands must be called for each path. We'll see how to define routes later on the next section.

When we call to run(), CLI will run the suitable command based on the input provided. Usually for CLIs, process.argv is used as input. In case you use process.argv, be sure to remove the first two elements as they refer to the node and script path.

For the time being, we can try it calling CLI without any command (it will print the help):

node cli

Registering Commands

A command is a class which contains the logic to perform a specific action in the CLI. Let's see an example:

import { Command } from 'artesa';
import { ArgumentBag, OptionBag } from 'artesa';

export class SampleCommand extends Command {
  protected async handle(args: ArgumentBag, options: OptionBag): Promise<number> {
    this.io.success(`Command have been executed`);
    return 0;
  }
}

A command must implement the handle() method where stays the action logic. This is a quite simple command which just prints out a message in a green-boxed style (we'll see output helpers later). However, commands can be as complex as the action it is implementing requires. Notice handle() returns a Promise thus you can perform async calls.

Registering the command

Once we have a command created, we must register it into the CLI we defined before. As we explained previously, CLI works like a router. Commands are registered with a "path" to call them. Let's modify our entrypoint to register our command:

As we want to call our command with node cli sample, this will be how CLI entrypoint should look like:

//cli.ts
import { CLI } from 'artesa'
import { SampleCommand } from './commands/sample.command'

CLI.execute({
  'sample': SampleCommand
}).run(process.argv.slice(2))
  .then(r => r)
  .catch(e => console.error(e));

That's it. Now SampleCommand.handle() will be called with:

node cli sample

You can list all command available calling to the CLI without any path or with the option -h|--help:

node cli --help
node cli

The command path cannot contain whitespaces. You can use other kind of separators (for instance, :: sample:one) or use nested commands which is explained in the next section.

Nested commands

Sometimes commands are tightly related. For instance, if we have a Car model in our project, we might need create, update and destroy a Car. In this scenario, would be really nice to be able to create command paths like:

cli cars create
cli cars update
cli cars destroy

This can be done using nested commands in our route list. First, create the commands which performs the actions as explained in the above section:

export class CreateCarCommand extends Command {...}
export class UpdateCarCommand extends Command {...}
export class DestroyCarCommand extends Command {...}

Then, let's register them in our entrypoint as nested commands:

//cli.ts
import { CLI } from 'artesa'
import { SampleCommand } from './commands/sample.command';
import { CreateCarCommand } from './commands/cars/create.command'
import { UpdateCarCommand } from './commands/cars/update.command'
import { DestroyCarCommand } from './commands/cars/destroy.command'

CLI.execute({
  'sample': SampleCommand,
  'cars': {
    'create': CreateCarCommand,
    'update': UpdateCarCommand,
    'destroy': DestroyCarCommand
  },
}).run(process.argv.slice(2))
  .then(r => r)
  .catch(e => console.error(e));

That's it, if we call to the CLI help we'll see our nested commands:

node cli --help // or just node cli
node cli cars --help // or just node cars

Command arguments

TODO

Command options

TODO

Command output utilities

TODO