タイプセーフなNode.js CLIアプリケーションを簡単に作成する
導入
Node.jsで単純なCLIアプリケーションを作るのはそれほど難しくありませんが、引数やオプション、サブコマンドなどが増えてくると構築や保守が大変になってきます。
特に問題なのが入力値の型安全性です。
フラグオプションと引数オプションの違いや、サブコマンドごとに異なるオプションなど考慮すべき事項は多くあります。
さらにCLIと同じ機能を持つAPIを用意しようと思った場合には、追加のボイラーテンプレートが必要になります。
そこで今回は、このような複雑な機能を持ったCLIアプリケーションをタイプセーフで宣言的に簡単に作れるライブラリ@jill64/ts-cli
を紹介します。
なお、JavaScriptでも利用することはできますが、型安全性を最大限に生かすために今回のチュートリアルではTypeScriptを使用します。
使い方
まずは以下のコマンドで依存関係をインストールします。
npm i @jill64/ts-cli
最小構成
App
を@jill64/ts-cli
からimportして使用します。
import { App } from '@jill64/ts-cli'
import process from 'node:process'
const app = new App({}, () => {
console.log('Hello, World!')
})
app.run(process.argv)
new App
の第一引数には後程、引数やオプションなどの情報を入力します。
まずはこの状態で実行してみましょう。
tsx
を使用するとTypeScriptファイルをNodeで直接実行できます。
npx tsx app.ts
以下の結果が出力されます。
Hello, World!
単純に第二引数で渡した関数が実行されています。
引数
では次にコマンドライン引数を追加してみます。
以下のようにarg
プロパティを追加するようにapp.ts
を書き換えます。
import { App } from '@jill64/ts-cli'
import process from 'node:process'
const app = new App(
{
args: [
[
'foo', // 引数名
'First Argument Description' // 引数の説明
]
] as const
},
({ args: { foo } }) => {
console.log(`Hello, ${foo}!`)
}
)
app.run(process.argv)
第二引数でプロパティargs
が利用可能になります。
この時、VSCodeであれば型補完が効いてfoo
だけが、args
のプロパティとして選択可能になっています。
まずは試しに引数なしで実行してみます。
npx tsx app.ts
以下のエラーが出ます。
Error: Missing required argument: foo
必須の引数foo
を渡さなかったのでエラーが出ました。
では引数を指定して実行してみます。
npx tsx app.ts bar
引数の値を反映させることに成功しました。
Hello, bar!
さらに引数の数を増やします。
配列の順番が引数の順番と一致します。
import { App } from '@jill64/ts-cli'
import process from 'node:process'
const app = new App(
{
args: [
['arg1', 'First Argument Description'],
['arg2', 'Second Argument Description'],
['arg3', 'Third Argument Description']
] as const
},
({ args: { arg1, arg2, arg3 } }) => {
console.log(`${arg1} - ${arg2} - ${arg3}`)
}
)
app.run(process.argv)
引数を指定して実行します。
npx tsx app.ts hoge fuga piyo
hoge - fuga - piyo
このように引数の数が増えてもプロパティ名でタイプセーフに引数にアクセスできることがこのライブラリの強みです。
オプショナル引数
optional
プロパティを使用して、含めるかどうか任意の引数を定義することもできます。
import { App } from '@jill64/ts-cli'
import process from 'node:process'
const app = new App(
{
optional: [
['optional_arg', 'Optional argument'],
] as const
},
({ optional }) => {
console.log(`${optional?.optional_arg ?? 'Fallback value'}!`)
}
)
app.run(process.argv)
まずは引数ありで実行します。
npx tsx app.ts arg_str
引数の値が使用されます。
arg_str!
次に引数なしで実行します。
npx tsx app.ts
引数なしの場合はこのようにundefined
が返され、この場合フォールバック値が使用されます。
Fallback value!
これはargs
プロパティと組み合わせることも可能です。
オプション
options
プロパティを使用して、以下のいずれかの型のオプションを定義することができます。
boolean
boolean[]
string
string[]
options
プロパティはロング名をキーとするオブジェクトで定義されます。
以下に例を示します。
import { App } from '@jill64/ts-cli'
import process from 'node:process'
const app = new App(
{
options: {
help: {
type: 'boolean', // オプションの型
alias: 'h', // ショート名
description: 'Show help message' // オプションの説明
},
out: {
type: 'string',
alias: 'o',
description: 'Output directory',
},
values: {
type: 'string[]',
alias: 'v',
description: 'Values to be printed'
}
}
},
({ options }) => {
if (options?.help) {
console.log('Help message')
return
}
console.log(`Output Dir: ${options?.out}`)
console.log(options?.values?.join(','))
}
)
app.run(process.argv)
まずはオプションなしの場合
npx tsx app.ts
未指定のオプションはundefined
が返ります。
Output Dir: undefined
undefined
--help
フラグを指定してみます。
npx tsx app.ts --help
Help message
が出力されます。
Help message
複数のオプションを同時に指定することが可能です。
GNUガイドラインに従ったショート名でのオプション指定も可能です。
npx tsx app.ts -o dist --values 1 --values 2 --values=3
Output Dir: dist
1,2,3
restパラメーター
例えば子プロセスを起動して任意のコマンドを実行させたい場合、任意個の引数を受け取りたいことがあります。
この場合、restパラメーターを使うことで、これを実現できます。
import { App } from '@jill64/ts-cli'
import process from 'node:process'
const app = new App(
{
rest: {
placeholder: 'command', // restパラメーターの名前
description: 'Command to run' // restパラメーターの説明
}
},
({ rest }) => {
console.log('RUN: ',rest?.join(' '))
}
)
app.run(process.argv)
npx tsx app.ts sub_command --flag
RUN: sub_command --flag
なお、options
とrest
を併用する場合は、options
のパラメーターはrest
の前に配置する必要があります。
import { App } from '@jill64/ts-cli'
import process from 'node:process'
const app = new App(
{
options: {
quiet: {
alias: 'q',
description: 'No output',
type: 'boolean'
}
},
rest: {
placeholder: 'command',
description: 'Command to run'
}
},
({ rest, options }) => {
if (options?.quiet) {
return
}
console.log('RUN: ', rest?.join(' '))
}
)
app.run(process.argv)
npx tsx app.ts -q sub_command --flag
(出力なし)
npx tsx app.ts sub_command -q --flag
RUN: sub_command -q --flag
これもargs
やoptional
と組み合わせることができます。
まとめ
いかがだったでしょうか。
この記事がNode.jsでCLIツールを作成しようとしている方の助けになれば幸いです。
また@jill64/ts-cli
についてバグや不明点がありましたらぜひ以下からIssueを開いてください。
Discussion