CLI のテンプレートプロジェクト by node and TypeScript
node で CLI(Command Line Interface) を開発する機会が数回あって、せっかくなのでテンプレートプロジェクトとしてまとめてみた。
テンプレートプロジェクト #
必要なモノ #
- nodejs: v11.13.0+
- typescript: v3.7.3+
試し方 #
- 上記のリポジトリを Clone する
- リポジトリのディレクトリに cd して
npm ci
する npm run build
するnpm link
する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-great
は package.json
の bin:
で指定している。
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 では 引数の必須チェックを自力で行わなければならない ようなので、定義体の paramDef
に require: boolean
を追加し、パースした実際の引数である XxxxConfig
に require = 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
を採用している。
コマンドを追加するには #
index.ts
のCommandType
にxxxx
を増やすcommand-xxx.ts
(CommandXxxx
クラス) を作るindex.ts
のcommandMap
に追加するmainUsage
になんか書く
参考 #
published at tags: Node.js JavaScript TypeScript cli- Next: 先輩に捧げる全国の飛び地リストを作る(市区町村編)
- Previous: トグルスイッチとチェックボックスの違い