以下の内容はhttps://blog.inorinrinrin.com/entry/2025/11/23/160951より取得しました。


TDMaCのススメ - cittyを使ってテストデータ作成を効率化する

TSKaigi Hokurikuの登壇資料です

自己紹介

github.com

npx deno run jsr:@ysknsid25/whois

補足

以下では UnJS製ライブラリ citty 入門 として、モダンで型安全なCLIを最速でつくるチュートリアルを提供する。

cittyとは

unjs.io

Nitroとかh3とかいろいろいい感じのライブラリを作ってるUnJSのライブラリの一つ。いい感じにCLIツールを作れる。

準備

npm install citty

公式のサンプルソースを拝借。

import { defineCommand, runMain } from "citty";

const main = defineCommand({
    meta: {
        name: "hello",
        version: "1.0.0",
        description: "My Awesome CLI App",
    },
    args: {
        name: {
            type: "positional",
            description: "Your name",
            required: true,
        },
        friendly: {
            type: "boolean",
            description: "Use friendly greeting",
        },
    },
    run({ args }) {
        console.log(`${args.friendly ? "Hi" : "Greetings"} ${args.name}!`);
    },
});

runMain(main);

基本

以下、あえて間違った動かし方をしてみる。

node --experimental-strip-types index.ts

かなり親切に怒ってくれる。オプションなしで実行してみると…

node --experimental-strip-types index.ts Kanon

オプションをつける。

node --experimental-strip-types index.ts Kanon --friendly true

フレンドリーになった。

argsのtypeについて

    args: {
        name: {
            type: "positional",
            description: "Your name",
            required: true,
        },
        friendly: {
            type: "boolean",
            description: "Use friendly greeting",
        },
    },

ここで試しにtypenumberを指定してみると怒られる。

どうやら4つを指定できるらしい。string, boolean, undefinedはわかるがpositionalとはなにか。まずは以下のように引数を定義して--helpする。

    args: {
        name: {
            type: "positional",
            description: "Your name",
            required: true,
        },
        friendly: {
            type: "boolean",
            description: "Use friendly greeting",
        },
        repeat: {
            type: "string",
            description: "Number of times to repeat the greeting",
            default: "2",
        },
    },

helpもめっちゃわかりやすい。

では次に、friendlypositionalに変えてみる。

    args: {
        name: {
            type: "positional",
            description: "Your name",
            required: true,
        },
        friendly: {
-            type: "boolean",
+            type: "positional",
            description: "Use friendly greeting",
        },
        repeat: {
            type: "string",
            description: "Number of times to repeat the greeting",
            default: "2",
        },
    },

--friendlyからFRIENDLYに変わり、OPTIONSからARGUMENTSに変わった。つまり、「positionalargsに指定された順番で受け取るよ」ということらしい。試しに以下のように実行してみると…

node --experimental-strip-types index.ts Kanon

FRIENDLYを指定しろと怒られた。

以上からcittyの魅力をまとめると、

  • argsで引数を定義
  • 各パラメーターにはstring, boolean, positional, undefinedのいずれかのtypeを指定することで型安全性を確保
  • required: trueで必須になる
  • defaultにはデフォルトの値を指定することができる
  • descriptionにはヘルプ表示時の説明文を記載できる

という感じ。

サブコマンド

サブコマンドを実装することもできる。以下のように二つのサブコマンドを実装してみる。

import { defineCommand, runMain } from "citty";

const greet = defineCommand({
    meta: {
        name: "greet",
        description: "Greet someone",
    },
    args: {
        name: {
            type: "positional",
            description: "Your name",
            required: true,
        },
        friendly: {
            type: "boolean",
            description: "Use friendly greeting",
        },
    },
    run({ args }) {
        console.log(`${args.friendly ? "Hi" : "Greetings"} ${args.name}!`);
    },
});

const byebye = defineCommand({
    meta: {
        name: "byebye",
        description: "Goodbye someone",
    },
    args: {
        name: {
            type: "positional",
            description: "Your name",
            required: true,
        },
    },
    run({ args }) {
        console.log(`byebye👋 ${args.name}!`);
    },
});

const main = defineCommand({
    meta: {
        name: "hello",
        version: "1.0.0",
        description: "My Awesome CLI App",
    },
    subCommands: {
        greet,
        byebye,
    },
});

runMain(main);

ヘルプを見る。

サブコマンドのヘルプを見るには--helpの後ろに続けるといいらしい。試しにbyebyeを見る。

node --experimental-strip-types index.ts --help byebye

それぞれ実行してみる。

node --experimental-strip-types index.ts greet Kanon

node --experimental-strip-types index.ts byebye Kanon

setup, cleanup

setup, cleanuprunの前後に実行する処理を定義することができる。例えばDB接続などの処理をここに書いたりなど。試しにbyebyeにこれらを追加して実行してみる。

const byebye = defineCommand({
    meta: {
        name: "byebye",
        description: "Goodbye someone",
    },
    args: {
        name: {
            type: "positional",
            description: "Your name",
            required: true,
        },
    },
    run({ args }) {
        console.log(`byebye👋 ${args.name}!`);
    },
    setup({ args }) {
        console.log(`now setup ${args.command}`);
    },
    cleanup({ args }) {
        console.log(`now cleanup ${args.command}`);
    },
});

Hono CLI

github.com

僕の発表はあくまで「人間に使わせる」ことに修正しているが、Hono CLIは「AIにも使わせる」という点で全く新しいコンセプトでできている。すごい。

おわりに

これだけ覚えれば、型安全にさくさくCLI開発できる👏

余談だけど、descriptionにリンクを入れておいて、詳細なhelpは別途Wikiページを参照させる...みたいなことをしておくとツール作成時のコンテキストをもれなく記述できてよさそう。

サブコマンドに対しても同様に可能。

あとHono Advent Calendar 2025 よろしくお願いします。

qiita.com




以上の内容はhttps://blog.inorinrinrin.com/entry/2025/11/23/160951より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14