Node.jsでCLIツールを作るなら「citty」がオススメ！

Node.jsでCLIツールを作ろうとすると、どのライブラリを選べばいいか迷うことはありませんか？ 本稿では、シンプルで使いやすい「citty」というライブラリをご紹介します。

cittyには次のような魅力的な特徴があります:

- TypeScriptによる型安全な引数(args)の定義
- 直感的なサブコマンドの作り方
- 美しいヘルプ表示

僕自身、cittyを使ってCLIツールを作ってみて、その使いやすさに感動しました。本稿では、cittyの基本的な使い方から実践的な実装方法まで、順を追って解説していきます。

https://unjs.io/packages/citty

## cittyのインストール

まずは、新しいプロジェクトを作成し、cittyをインストールしましょう。

```sh
# プロジェクトの作成
mkdir my-cli && cd my-cli
npm init -y

# package.jsonの設定
# "type": "module" を追加する（ESモジュールとして実行するため）

# cittyのインストール
npm install citty
```

## 最もシンプルなCLIの作成

cittyを使った最も基本的なCLIを作ってみましょう。以下のコードを`index.ts`に保存します:

```ts:index.ts
import { defineCommand, runMain } from "citty";

const main = defineCommand({
  meta: {
    name: "my-cli",
    description: "My first CLI using citty",
  },
  run() {
    console.log("Hello from my CLI!");
  },
});

runMain(main);
```

このコードについて説明しましょう:

- `defineCommand`で新しいコマンドを定義
- `meta`オブジェクトでコマンドの基本情報を設定
- `run`関数に実行時の処理を記述
- `runMain`でコマンドを実行

実行してみましょう:

```sh
node --experimental-strip-types index.ts
```

```text:出力結果
Hello from my CLI!
```

/\* 最近のNode.jsはTypeScriptが直接実行できて便利ですね！ \*/

## 型安全な引数の定義

cittyの大きな魅力の一つは、型安全に引数を定義できることです。例えば、ユーザーから名前(`--name`)を受け取る引数を追加してみましょう:

```ts:index.ts
import { defineCommand, runMain } from "citty";

const main = defineCommand({
  meta: {
    name: "my-cli",
    description: "CLI with arguments",
  },
  args: {
    name: {
      type: "string",
      description: "Your name",
      required: true,
    },
  },
  run({ args }) {
    console.log(`Hello, ${args.name}!`);
  },
});

runMain(main);
```

このコードのポイントを説明します:

- `args`オブジェクトで引数を定義
- 各引数に`type`を指定することで型安全性を確保
- `required: true`で必須パラメータとして設定
- `description`でヘルプ表示時の説明文を設定

```sh
node --experimental-strip-types index.ts --name Taro
```

```text:出力結果
Hello, Taro!
```

## サブコマンドの実装

大規模なCLIツールでは、複数のコマンドを提供したいケースがあります。cittyではサブコマンドもかなり直感的に実装できます:

```ts:index.ts
import { defineCommand, runMain } from "citty";

// サブコマンドの定義
const greetCommand = defineCommand({
  meta: { 
    name: "greet", 
    description: "Say hello" 
  },
  args: {
    name: { type: "string", required: true },
  },
  run({ args }) {
    console.log(`Hello, ${args.name}!`);
  },
});

const main = defineCommand({
  meta: { 
    name: "my-cli", 
    description: "CLI with subcommands" 
  },
  subCommands: {
    greet: greetCommand, // サブコマンドの組み入れ
  },
});

runMain(main);
```

サブコマンドごとに`defineCommand`を使って定義し、それを`subCommands`に加えることで、サブコマンドを追加できます。簡単ですね。

実行例：

```sh
node --experimental-strip-types index.ts greet --name Taro
```

```text:出力結果
Hello, Taro!
```

## 美しいヘルプ表示

cittyの魅力の一つは、デフォルトできれいなヘルプを自動生成してくれることです。`--help`オプションを付けて実行してみましょう：

```sh
node --experimental-strip-types index.ts --help
```

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/889/0a92df6e-32f4-4a70-9886-7f2cca3dee0f.png)


このように、コマンドの使い方が見やすく表示されます。サブコマンドのヘルプも確認できます：

```sh
node --experimental-strip-types index.ts greet --help
```

## まとめ

cittyは、以下のような点がいい感じです:

- TypeScriptの型システムを活かした安全な引数定義
- 直感的なAPIによるシンプルな実装
- サブコマンドの柔軟な管理
- 美しく見やすいヘルプ表示

Node.jsでCLIツールを作る際は、ぜひcittyの採用を検討してみてください。シンプルさと型安全性を両立した素晴らしいライブラリだと思います。

最後までお読みいただき、ありがとうございました。

---

Twitterでは、Node.js、TypeScriptに関する話題を発信しています。よろしければフォローをお願いします！ [@suin](https://twitter.com/suin)