🦁

Nest.js で CLI ベースの処理を定義する

2022/03/21に公開

導入

まずは、nest-commander を インストールします。

$ npm i nest-commander

CLI 用のモジュールを作成してください。

$ nest g module cli
src/commands/cli.module.ts
import {Module} from '@nestjs/common';

@Module({
    imports: [
    ],
})
export class CliModule {
}

実行用のファイル、cli.tsを作成します。

import { CommandFactory } from 'nest-commander';
import {CliModule} from "./src/commands/cli.module";

async function bootstrap() {
    await CommandFactory.run(CliModule,[
        "log", "error", "warn" , "debug", "verbose"
    ]);
}
bootstrap();

CommandFactory.run の第2引数には、ログレベルが指定可能です。
アプリケーションの必要に応じて、必要な項目を指定してください(上記は全出力の例)。

ts-node cli.ts で実行して以下の表示が得られれば準備は完了です。

% ts-node cli.ts --help
[Nest] 27150  - 03/21/2022, 6:14:49 PM     LOG [NestFactory] Starting Nest application...
[Nest] 27150  - 03/21/2022, 6:14:49 PM     LOG [InstanceLoader] CommandRootModule dependencies initialized +14ms
[Nest] 27150  - 03/21/2022, 6:14:49 PM     LOG [InstanceLoader] CliModule dependencies initialized +0ms
[Nest] 27150  - 03/21/2022, 6:14:49 PM     LOG [InstanceLoader] DiscoveryModule dependencies initialized +0ms
[Nest] 27150  - 03/21/2022, 6:14:49 PM     LOG [InstanceLoader] CommandRunnerModule dependencies initialized +1ms
Usage: cli [options]

Options:
  -h, --help  display help for command

コマンドの追加

src/cli/commands/debug.command.ts を以下の内容で作成します。

import { Command, CommandRunner, Option } from 'nest-commander';

interface BasicCommandOptions {
    string?: string;
    boolean?: boolean;
    number?: number;
}

@Command({ 
    name: 'debug', 
    description: 'A parameter parse' 
})
export class DebugCommand implements CommandRunner {
    constructor() {}

    async run(
        passedParam: string[],
        options?: BasicCommandOptions,
    ): Promise<void> {
        if (options?.boolean !== undefined && options?.boolean !== null) {
            this.runWithBoolean(passedParam, options.boolean);
        } else if (options?.number) {
            this.runWithNumber(passedParam, options.number);
        } else if (options?.string) {
            this.runWithString(passedParam, options.string);
        } else {
            this.runWithNone(passedParam);
        }
    }

    @Option({
        flags: '-n, --number [number]',
        description: 'A basic number parser',
    })
    parseNumber(val: string): number {
        return Number(val);
    }

    @Option({
        flags: '-s, --string [string]',
        description: 'A string return',
    })
    parseString(val: string): string {
        return val;
    }

    @Option({
        flags: '-b, --boolean [boolean]',
        description: 'A boolean parser',
    })
    parseBoolean(val: string): boolean {
        return JSON.parse(val);
    }

    runWithString(param: string[], option: string): void {
        console.log({ param, string: option });
    }

    runWithNumber(param: string[], option: number): void {
        console.log({ param, number: option });
    }

    runWithBoolean(param: string[], option: boolean): void {
        console.log({ param, boolean: option });
    }

    runWithNone(param: string[]): void {
        console.log({ param });
    }
}

CliModule の providers セクションに 上記のコマンドを追加し、
ts-node cli.ts basic --help で以下の出力が得られれば成功です。。

% ts-node cli.ts basic --help
[Nest] 27327  - 03/21/2022, 6:27:03 PM     LOG [NestFactory] Starting Nest application...
[Nest] 27327  - 03/21/2022, 6:27:03 PM     LOG [InstanceLoader] CommandRootModule dependencies initialized +18ms
[Nest] 27327  - 03/21/2022, 6:27:03 PM     LOG [InstanceLoader] CliModule dependencies initialized +1ms
[Nest] 27327  - 03/21/2022, 6:27:03 PM     LOG [InstanceLoader] DiscoveryModule dependencies initialized +0ms
[Nest] 27327  - 03/21/2022, 6:27:03 PM     LOG [InstanceLoader] CommandRunnerModule dependencies initialized +0ms
Usage: cli basic [options]

A parameter parse

Options:
  -n, --number [number]    A basic number parser
  -s, --string [string]    A string return
  -b, --boolean [boolean]  A boolean parser
  -h, --help               display help for command

コマンドにおける引数

コマンドの引数は @Command デコレータで指定します。

処理の本体 run にて 第一引数で配列の形式にて値を取得できます。

import { Command, CommandRunner, Option } from 'nest-commander';

@Command({
    name: 'run',
    arguments: '<task> <task2>',
    options: { isDefault: true }
})
export class DebugCommand implements CommandRunner {
    async run(
        inputs: string[],
    ): Promise<void> {
        console.log(inputs)
    }
}

上記のコマンドは実行すると以下のような出力が得られます。

$ ts-node cli.ts run hoge fuga
[ 'hoge', 'fuga' ]

定義している分の引数が不足していると missing argument のエラーとなりますが、
定義している以上の引数が渡された場合、エラーにはならずそのまま inputs に入ってくるので注意が必要です。

$ ts-node cli.ts run hoge fuga piyo
[ 'hoge', 'fuga', 'piyo']

なお、得られる値は配列の形式ですが、それぞれに適切な名前を与えておくことで --help 上の表示がわかりやすくなります。

オプションの登録

オプションは @Option デコレータで一つづつ定義します。

import { Command, CommandRunner, Option } from 'nest-commander';

@Command({
    name: 'run',
})
export class DebugCommand implements CommandRunner {
    async run(
        inputs: string[],
        options: {[key:string]:string}
    ): Promise<void> {
        console.log(options)
    }
    
    @Option({
        flags: '-n, --name <shell>',
        description: 'A different shell to spawn than the default'
    })
    parseName(name: string) {
        return name;
    }
}

実行すると以下のような出力を得られます。

$ ts-node cli.ts run --name hoge
{ name: 'hoge' }

備考

https://docs.nestjs.com/recipes/nest-commander

https://nest-commander.jaymcdoniel.dev/

https://www.npmjs.com/package/nest-commander

Discussion