commander.jsを利用したCLIコマンドの作成

2023/08/11に公開

node の実行コマンドを自作できる commander.js を初めて使ったため、その備忘録として纏めてみました。

※基本は、公式 Github の README を自分なりに解釈したものになります。

https://github.com/tj/commander.js

Install

まずは、JavaScript or TypeScript の環境を作り、yarn run でファイル実行できる様にしておきましょう。(yarn initで簡単に作れます)

次に、commander.js をインストールします。

# npm
$ npm install commander

# yarn
$ yarn add commander

使い方

commander.js には program 変数があるので、それを使って作成していくことが可能です。

しかし、複数の方法で commander を使用する可能性がある大規模なプログラムでは、ローカルに Commander オブジェクトを作成して使用する方が良いです。

// CommonJS (.cjs)
const { Command } = require('commander');
const program = new Command();

// ECMAScript (.mjs)
import { Command } from 'commander';
const program = new Command();

// TypeScript (.ts)
import { Command } from 'commander';
const program = new Command();

オプションの設定

コマンドにオプションを設定する場合は、.option()メソッドを利用します。オプションは複数設定でき、オプション名は1文字と長い文字のオプション名を持たせることができます。

オプションには2種類存在し、オプションに引数を渡す方法とオプションを指定することで boolean 判定をさせる方法があります。

解析されたオプションは、Command オブジェクトに対して.opts()をコールすることでアクセスでき、アクションハンドラに渡されます。

-template-engineのような複数語のオプションはキャメルケースになり、program.opts().templateEngine などになります。

例えば、下記の様にコードを設定した場合のコードを示します。

import { Command } from "commander";
const program = new Command();

program.option("-p, --port <port>", "server port setting", "80");

program.parse();

console.log(program.opts());

この場合、yarn run ts-node index.tsを実行すると port の指定はありませんが、port=80 で指定することになります。

yarn run v1.22.19
{ port: '80' }
✨  Done in 0.50s.

今度は port の指定方法ですが、指定方法は複数あります。

いずれも port=3000 で指定されます。

yarn run ts-node index.ts -p 3000
yarn run ts-node index.ts -p3000
yarn run ts-node index.ts --port 3000
yarn run ts-node index.ts --port=3000

オプションの設定は柔軟性があり、下記の様な特性を持っています。

  • オプションは複数指定可能
  • フラグとして boolean を指定できる
  • 否定フラグも作成できる
import { Command } from "commander";
const program = new Command();

program
  .option("-p, --port <port>", "server port setting", "80")
  .option("-d, --debug", "development debug")
  .option("-e, --server-environment <env>", "env mode");

program.parse();

console.log(program.opts());
# 全て指定した場合
$ yarn run test-command --port=3000 -d -e local
{ port: '3000', debug: true, serverEnvironment: 'local' }
✨  Done in 0.41s.

# 指定しない場合、デフォルト値があるものだけになる
$ yarn run test-command
{ port: '80' }
✨  Done in 0.76s.

# 引数が必要なオプションに引数無しで実行するとエラーとなる
$ yarn run test-command --port=3000 -d -e
error: option '-e, --server-environment <env>' argument missing
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

# boolean引数はまとめることが可能
$ yarn run test-command --port=3000 -de local
{ port: '3000', debug: true, serverEnvironment: 'local' }
✨  Done in 0.74s.

否定オプションbooleanの設定

基本オプションは指定するとtrueを示しますが、指定するとfalseを示す方法もあります。

それは、オプションにno-をつけることです。

import { Command } from "commander";
const program = new Command();

program
  .option("-p, --port <port>", "server port setting", "80")
  .option("-d, --debug", "development debug")
  .option("-e, --server-environment <env>", "env mode")
  .option("-l, --no-error-log", "error log output");

program.parse();

console.log(program.opts());
$ yarn run test-command --port=3000 -de local -l
{
  port: '3000',
  errorLog: false,
  debug: true,
  serverEnvironment: 'local'
}
✨  Done in 0.51s.

# 否定に関しては、指定しなければtrueとなる。
$ yarn run test-command --port=3000 -de local
{
  port: '3000',
  errorLog: true,
  debug: true,
  serverEnvironment: 'local'
}
✨  Done in 0.80s.

必須オプション

オプションに必須設定可能で、その必須オプションの指定がなければエラーとなります。

program
  .requiredOption('-c, --cheese <type>', 'pizza must have cheese');

program.parse();
$ yarn run test-command
error: required option '-c, --cheese <type>' not specified
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

オプションの配列指定

import { Command } from "commander";
const program = new Command();

program
  .requiredOption("-c, --cheese <type>", "pizza must have cheese")
  .option("-n, --number <numbers...>", "specify numbers");

program.parse();

console.log(program.opts());
$ yarn run test-command -c cheeze -n 1 2 3
{ cheese: 'cheeze', number: [ '1,2,3' ] }
✨  Done in 0.43s.

細かなオプション設定

さらに細かいオプションを指定する場合は、.addOption()メソッドを使い、その中でOptionオブジェクトを作成することでオプションの設定が可能です。

例えば、下記の様に option に .choices([]) を使うことで、指定した値しか使えなくなります。

import { Command, Option } from "commander";
const program = new Command();

program.addOption(
  new Option("-d, --drink <size>", "drink size").choices([
    "small",
    "medium",
    "large",
  ])
);

program.parse();

console.log(program.opts());
$ yarn run test-command -d small
{ drink: 'small' }
✨  Done in 0.58s.

# choiceにない値を指定するとエラーとなる
$ yarn run test-command -d llll
error: option '-d, --drink <size>' argument 'llll' is invalid. Allowed choices are small, medium, large.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

オプションのカスタム設定

カスタム・オプション処理
オプション引数のカスタム処理を行う関数を指定できます。コールバック関数は、ユーザーが指定したオプション引数と、オプションの前回値の 2 つのパラメータを受け取ります。コールバック関数は、オプションの新しい値を返します。

これにより、オプション引数を希望の型に強制したり、値を累積したり、完全にカスタムな処理を行うことができます。

オプションのデフォルト値/開始値を、関数パラメータの後に指定可能です。

import { Command, InvalidArgumentError } from "commander";
const program = new Command();

function increaseVerbosity(value: string, previous: number) {
  const parsedValue = parseInt(value, 10);
  if (isNaN(parsedValue)) {
    throw new InvalidArgumentError("Not a number.");
  }
  return parsedValue;
}

program
  .option("-p, --port <port>", "server port setting", "80")
  .option("-i --increase <number>", "increase number", increaseVerbosity);

program.parse();

console.log(program.opts());
$ yarn run test-command -p 5000 --increase 5
{ port: '5000', increase: 5 }
✨  Done in 0.56s.

コマンド作成

例えば、yarn run test-command hogeのように hoge を指定することで、コマンドとして扱うことが可能です。

import { Command } from "commander";
const program = new Command();

// バージョン情報
program.version("0.0.1", "-v, --version");

// hogeコマンド
program
  .command("hoge")
  .description("my first example")
  .action(() => console.log("run hoge command"));

program.parse();

console.log(program.args);
$ yarn run test-command hoge
run hoge command
[ 'hoge' ]

alias も設定できます、詳細は下記を参考にしてください。

https://github.com/tj/commander.js/blob/master/examples/alias.js

引数指定

.argument()を使い、コマンドに対して引数を指定します。

argument では、引数の順番がそのままコードに適用されます。

下記の場合、server が第一引数、user が第二引数となります。

import { Command } from "commander";
const program = new Command();

program
  .name("connect")
  .argument("<server>", "connect to the specified server")
  .argument("[user]", "user account for connection", "guest")
  .description("Example program with argument descriptions")
  .action((server, user) => {
    console.log("server:", server);
    console.log("user:", user);
  });

program.parse();

console.log(program.args);

$ yarn run test-command ec2 mattu
server: ec2
user: mattu
[ 'ec2', 'mattu' ]

引数をスプレッド演算子で指定できます。

import { Command } from "commander";
const program = new Command();

program
  .version("0.1.0")
  .command("rmdir")
  .argument("<dirs...>")
  .action(function (dirs: string[]) {
    dirs.forEach((dir) => {
      console.log("rmdir %s", dir);
    });
  });
program.parse();

console.log(program.args);
$ yarn run test-command rmdir test add
rmdir test
rmdir add
[ 'rmdir', 'test', 'add' ]
✨  Done in 0.43s.
GitHubで編集を提案

Discussion