Experiments Never Fail

CLI のテンプレートプロジェクト by node and TypeScript

node で CLI(Command Line Interface) を開発する機会が数回あって、せっかくなのでテンプレートプロジェクトとしてまとめてみた。

テンプレートプロジェクト #

必要なモノ #

試し方 #

  1. 上記のリポジトリを Clone する
  2. リポジトリのディレクトリに cd して npm ci する
  3. npm run build する
  4. npm link する
  5. source ~/.bash_profile を行うかまたはターミナルを再起動する

これでどのディレクトリでも my-great コマンドが使用できるようになる。

#

$ my-great hello -f Echizen -s Ooka -a 42

Hello Echizen Ooka.
You're 42 years old.
$ my-great something wrong param

Command Line Interface for My great service

  Sample for CLI.

Commands

  my-great hello -f <first_name> -s <second_name>   Say Hello.
  my-great version                                  Show version.

アンインストール #

npm uninstall -g @amay/my-great-cli

要点 #

コマンドライン引数の解析と使用方法の表示 #

yargs とかいろいろあったけど、自分的に使いやすかったのでこれを選択。

プログラム構成 #

my-great hello -f <first_name> -s <second_name>   Say Hello.
my-great version                                  Show version.

のように、第一引数を「コマンド」とし、第2引数以降をそのコマンド専用の引数群としたかったので、index.ts で第一引数のみを parse して取得し、コマンド毎に command-xxxx.ts へ委譲している。

command-line-args では commandLineArgs(this.paramDef, { partial: true })partial:true を設定すると、引数定義(paramDef) に存在しない引数があっても無視する。

cli コマンド名 #

cliコマンド名 my-greatpackage.jsonbin: で指定している。

package.json

{
"name": "@amay/my-great-cli",
<省略>
"bin": {
"my-great": "build/index.js"
},
<省略>

ビルドされた ./build/index.js を指すように設定している。
ちなみに npm run 経由で node を実行する場合は、引数の前に -- を付ける(例: node ./build/index.js -- version)。

必須引数のチェック #

command-line-args では 引数の必須チェックを自力で行わなければならない ようなので、定義体の paramDefrequire: boolean を追加し、パースした実際の引数である XxxxConfigrequire = true な項目が含まれているかをチェックするようにした。

// Valid require params
const requiresNotSetted = this.paramDef
.filter(x => x.require)
.filter(x => cfg[x.name] == null)
.map(x => `--${x.name}`);

if (requiresNotSetted.length > 0) {
console.log(`Param: ${requiresNotSetted.join(' ')} is required.`);
console.log(`------------------------------------`);
this.usage[1].optionList = this.paramDef;
const usg = commandLineUsage(this.usage)
console.log(usg);
return -1;
}

kebab-case VS camelCase VS snake_case #

コマンドの引数は kebab-case がデファクトスタンダードの模様。
command-line-args では commandLineArgs(this.paramDef, { camelCase: true }) とすると、--first-name に渡された引数を、firstName 変数に格納してくれる。

が、前述の必須引数のチェックが(定義体と実体の変数名が異なるため)正しく機能しなくなるので妥協案として snake_case の --first_name を採用している。

コマンドを追加するには #

  1. index.tsCommandTypexxxx を増やす
  2. command-xxx.ts(CommandXxxx クラス) を作る
  3. index.tscommandMap に追加する
  4. mainUsage になんか書く

参考 #

published at tags: Node.js JavaScript TypeScript cli