citty で CLI ツールをつくってみる
- 投稿した日
- 更新した日
- 書いたひと
- ひらたけ
このウェブサイトでも使用している Vue.js のフレームワーク Nuxt。 先月リリースされた [email protected]
に以下のような記述がありました。
We've refactored
nuxi
using unjs/citty and this marks the first Nuxt release that depends on the new version, safely in its own repository.
Google 翻訳にそのまま入れてみると、nuxi
(Nuxt CLI)を unjs/citty
というものを使って作り直したよ的な話っぽい。なんとなく気になったので、unjs/citty を触ってみることにしました。
エレガントな CLI ビルダーらしい。記事執筆時点では citty
の詳細なドキュメントやテンプレートのようなものはなく(issue を見ると将来的には用意するのを予定しているっぽいですが)、UnJS の他のリポジトリで結構使われていたので、そちらの実装を参考に動かしてみることに。
環境
今回作業した環境は以下のとおりです。
- Node.js - 18.17.1
- pnpm - 8.7.4
- unjs/citty - 0.1.4
- unjs/unbuild - 2.0.0
GitHub の UnJS オーガニゼーションのリポジトリ一覧でたまたま上の方にあった unjs/untun とか unjs/mkdist とかを参考に進めます。
root/
├ bin/
│ └ command.mjs
├ src/
│ ├ cli.ts
│ ├ command.ts
│ └ index.ts
├ package.json
└ tsconfig.json
必要なパッケージをインストールする
まず必要なパッケージをインストールします。エレガントな CLI ビルダーである citty
、記述した TypeScript のコードをビルドする unbuild
をインストールします。
$ pnpm add citty
$ pnpm add -D unbuild
unjs/unbuild は、package.json
に記述した情報から自動で構成を推測して、いい感じにビルドしてくれるものっぽい。citty
を使用している他のリポジトリを参考に、以下のように package.json
を変更します。
{
...
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"require": "./dist/index.cjs",
"import": "./dist/index.mjs",
"types": "./dist/index.d.ts"
},
"./cli": {
"require": "./dist/cli.cjs",
"import": "./dist/cli.mjs",
"types": "./dist/cli.d.ts"
}
},
"bin": {
"command": "./bin/command.mjs"
},
"files": ["dist", "bin"],
"dependencies": {
"citty": "0.1.4"
},
"devDependencies": {
"unbuild": "2.0.0"
},
"scripts": {
"build": "unbuild"
}
}
これで、src
にコードを書いて、TypeScript の場合は tsconfig.json
で設定をいい感じに書いて、 pnpm build
を実行すると、dist/
ディレクトリにいい感じにファイルを作ってくれるっぽい。便利。
コマンドを実装する
CLI ツールなので、コマンドを実行すると何かしらの処理が実行されるプログラムを用意する必要があります。今回はとりあえず、文字列が表示されるだけの簡単なものを用意します。
src/command.ts
に、コマンドを実行したときの処理を実装します。コンソールにただ「fuga」と出るだけの hoge 関数です。
export const hoge = () => {
console.log('fuga')
}
src/index.ts
には、CLI ではなく import {} from 'command'
みたいにインポートして使うとき用に関数をそのままエクスポートする記述を入れておきます。
export * from './command'
続いて、CLI で実行するときの設定を src/cli.ts
に書きます。defineCommand
という関数でコマンドを定義し、そのコマンドを runMain
という関数で実行するっぽい。
import { defineCommand, runMain } from 'citty'
import { hoge as hogeCommand } from './command'
export const hoge = defineCommand({
meta: { name: 'hoge' },
run: () => hogeCommand(),
})
export const main = defineCommand({
meta: {
name: 'command',
version: '1.0.0',
},
subCommands: { hoge },
})
export const runCommand = () => runMain(main)
最後に、エクスポートした runCommand
関数を実行する用のファイルを bin/command.mjs
に作成します。
#!/usr/bin/env node
import { runCommand } from '../dist/cli.mjs'
runCommand()
ビルドする
書いたコードをビルドします。ビルドを実行すると、dist/
に必要なファイルが生成されます。
$ pnpm build
試しに今回作成した CLI ツールを、他のプロジェクトでインストールして実行してみると、設定した文字列が出力されることが確認できます。
$ mkdir -p ../example
$ cd ../example
$ pnpm add [作成したCLIツールのパス]
$ pnpm [作成したCLIツールのパッケージ名] hoge
--help
オプションをつけるとよくある感じでコマンドのヘルプが表示されたり、--version
オプションをつけるとバージョンが出力されたりと、特に処理を書くとかしなくても用意してくれるのは便利だなと思いました。
今は特につくりたいなあと思うようなものが思いつかないですが、何かつくりたさはあるなあ。