Skip to content

Using decorators

Alex Hermann edited this page Jun 20, 2024 · 4 revisions

As a simple example, we will define a cli with 2 commands, new and list.
The new command will have 2 sub commands, directory and file

Commands stack example

Cli -> new -> [
        directory -> execution,
        file -> execution
    ]
    -> list -> execution

Registering methods using class decorators is very easy. Nestjs providers that are decorated with @Console will be scanned and each member method that is decorated with @Command will be registered on the cli.

// service.ts - a nestjs provider using console decorators
import { Console, Command, createSpinner } from 'nestjs-console';

@Console()
export class MyService {
    @Command({
        command: 'list <directory>',
        description: 'List content of a directory'
    })
    async listContent(directory: string): Promise<void> {
        // simulate a long task of 1 seconds
        const files = await new Promise((done) => setTimeout(() => done(['fileA', 'fileB']), 1000));

        // send the response to the  cli
        // you could also use process.stdout.write()
        console.log(JSON.stringify(files));
    }
}

Register a command with sub commands

By default, the @Console will tell the module to register all decorated methods at root of the cli.
Example of Usage: [options] [command]

You can name your provider to be registered in a group command container. This is useful when you have a lot of commands and you want to group them as sub command. (git style)

To achieve this, you have to group your methods into class. You have to pass options to the @Console decorator to configure the name of the parent cli. Decorated methods of the providers will be registered as a sub command instead of being registered at root.

// service.new.ts - a nestjs provider using console decorators (sub commands)
@Console({
    command: 'new',
    description: 'A command to create an item'
})
export class MyNewService {
    @Command({
        command: 'file <name>',
        description: 'Create a file'
    })
    async createFile(name: string): void | Promise<void> {
        console.log(`Creating a file named ${name}`);
        // your code...
    }

    @Command({
        command: 'directory <name>',
        description: 'Create a directory'
    })
    async createDirectory(name: string): void | Promise<void> {
        console.log(`Creating a directory named ${name}`);
        // your code...
    }
}

Register sub commands to an existing cli

If you want to register sub commands to an other group command container, you have to decorate your class using the @Console decorator with the same name.

Here the command is the same as the one from MyNewService, grouping all commands

@Console({
    command: 'new' 
})
export class MyOtherService {...}

Register sub commands to a new cli

If you want to register a new cli of sub commands, you have to decorate your class using the @Console decorator with the full name of the command.
Full name of a command is like a path separated by dot. It represents the full hierarchy of the cli. This way we know were to attach the cli.
eg: cli.child-cli.sub-child.

Here the all commands will be grouped in a cli something and attached to the cli new

@Console({
    command: 'new.something'
})
export class MyOtherService {...}

Next: Command handler signature