Command
@tsed/cli-core
is the npm module that provides API to create CLI. It can be used to create your own CLI or to run your Ts.ED application code. Ts.ED cli-core uses commander to parse cli arguments, Inquirer to display prompt and Listr to run tasks.
The cli-core works as a standalone process, like the classic entry point, and will initialize a container for running your code (Service/Provider/etc...).
- Bootstrap (entry point e.g:
bin/index.ts
) is invoked by cli. - Create a headless Ts.ED Application.
- Create command with decorator and inject service from your existing code.
Installation
npm install @tsed/cli-core
yarn add @tsed/cli-core
pnpm add @tsed/cli-core
bun add @tsed/cli-core
TIP
If you start your project from scratch, you can use Ts.ED cli v3+ to bootstrap your project with the Command feature.
Optional
You can install @tsed/cli
globally to run your custom commands directly from the Ts.ED CLI:
npm install -g @tsed/cli
Create the CLI entry point
Create index.ts
file in src/bin
. This file will be dedicated to bootstrap the CLI with your own configuration.
#!/usr/bin/env node
import {CliCore} from "@tsed/cli-core";
import {config} from "../config"; // Import your application configuration
import {HelloCommand} from "./HelloCommand";
CliCore.bootstrap({
...config,
// add your custom commands here
commands: [HelloCommand]
}).catch(console.error);
Create command
Use tsed g command
to create a new Command file. Here is a basic Command example:
import {Command, CommandProvider, QuestionOptions} from "@tsed/cli-core";
export interface HelloCommandContext {}
@Command({
name: "hello-command",
description: "Command description",
args: {},
options: {},
allowUnknownOption: false
})
export class HelloCommand implements CommandProvider {
/**
* Ask questions using Inquirer. Return an empty array or don't implement the method to skip this step
*/
async $prompt(initialOptions: Partial<HelloCommandContext>): Promise<QuestionOptions> {
return [];
}
/**
* This method is called after the $prompt to create / map inputs to a proper context for the next step
*/
$mapContext(ctx: Partial<HelloCommandContext>): HelloCommandContext {
return {
...ctx
// map something, based on ctx
};
}
/**
* This step run your tasks with Listr module
*/
async $exec(ctx: HelloCommandContext): Promise<any> {
return [
{
title: "Doing something",
task: () => {
console.log("HELLO");
}
}
];
}
}
The @Command
decorator allow you to bind a class to Commander. Here the previous example can be run by executing the following command:
tsed run hello-command
By default, you need to provide the name
and description
.
Command Arguments
Arguments are the values given to your command without a flag option:
tsed run hello-command create user
To bind these arguments with your custom command, you have to declare the arguments as follows:
import {Command, CommandProvider, QuestionOptions} from "@tsed/cli-core";
export interface HelloCommandContext {
action: "create";
subAction: "user";
}
@Command({
name: "hello-command",
description: "Command description",
args: {
action: {
type: String,
defaultValue: "create",
description: "Action description"
},
subAction: {
type: String,
defaultValue: "user",
description: "My sub-action"
}
},
options: {},
allowUnknownOption: false
})
export class HelloCommand implements CommandProvider {
$exec(ctx: HelloCommandContext) {
console.log(ctx);
}
}
Command Options
Options are the values given to your command with a specific flag option. Example:
tsed run hello-command -o test
To bind this option with your custom command, you have to declare the option as following:
import {Command, CommandProvider, QuestionOptions} from "@tsed/cli-core";
export interface HelloCommandContext {
option1: string;
}
@Command({
name: "hello-command",
description: "Command description",
args: {},
options: {
"-o, --opt-1 <option1>": {
type: String,
defaultValue: "dev",
description: "My option"
}
},
allowUnknownOption: false
})
export class HelloCommand implements CommandProvider {
$exec(ctx: HelloCommandContext) {
console.log(ctx);
}
}
Allow extra options
By default, commander doesn't accept unknown options. You can change this behaviour, by change the allowUnknownOption
to true
.
Then you'll be able to get all args and options in the rawArgs property. Here is an example:
import {Command, CommandProvider, QuestionOptions} from "@tsed/cli-core";
export interface HelloCommandContext {
rawArgs: string[];
}
@Command({
name: "hello-command",
description: "Command description",
args: {},
options: {},
allowUnknownOption: true
})
export class HelloCommand implements CommandProvider {
$exec(ctx: HelloCommandContext) {
console.log(ctx);
}
}
Injecting service
Use the Inject decorator to inject your service in a command:
import {Command, CommandProvider, QuestionOptions} from "@tsed/cli-core";
import {MyService} from "../services/MyService";
export interface HelloCommandContext {
rawArgs: string[];
}
@Command({
name: "hello-command",
description: "Command description",
args: {},
options: {},
allowUnknownOption: false
})
export class HelloCommand implements CommandProvider {
@Inject()
myService: MyService;
async $exec(ctx: HelloCommandContext): Promise<any> {
return [
{
title: "Update something",
task: () => this.myService.update(ctx)
}
];
}
}
Prompt
You can implement the $prompt
method to provide a CLI prompt to your consumer. Prompt is based on Inquirer.
import {Command, CommandProvider, QuestionOptions} from "@tsed/cli-core";
import {MyService} from "../services/MyService";
export interface HelloCommandContext {
projectName: string;
}
@Command({
name: "hello-command",
description: "Command description",
args: {},
options: {}
})
export class HelloCommand implements CommandProvider {
@Inject()
myService: MyService;
async $prompt(initialOptions: Partial<HelloCommandContext>): Promise<QuestionOptions> {
return [
{
type: "input",
name: "projectName",
message: "What is your project name",
transformer(input) {
return paramCase(input);
}
}
];
}
async $exec(ctx: HelloCommandContext): Promise<any> {
console.log(ctx);
}
}
See Inquirer for more details on creating prompts.