npxなCLIツール作って公開した話
scandogというツール作りました。15時間ぐらいかな?
例えばFoo.tsx
があったらFoo.spec.tsx
、Foo.stories.tsx
もセットで存在しているかチェックしたいとします。そんなときに活躍するツールです。
プロジェクトには以下の言語・ライブラリーを使用しています。
- TypeScript
- yargs
- grobby
- Vitest
ここまでの経緯ですが、
「テストファイルにimportされてないファイルがカバレッジ計測に含まれないなぁ」
「テストファイルを一括チェックするスクリプト作るか」
「どうせならnpxで使えるやつにしよう!」
「よし!できた! さてzennに記事書くか」
「ちょっと待てよ? Jestなら--collectCoverageFrom
があるやん」
「Vitestの方は? c8だと...はい、--all
ですね」 ←イマココ
ということでちょっとしょんぼりしています。
ドキュメントは隅から隅まで読む、これ大事ですね。
すべてのファイルに対応したテストファイルが存在しているかと、カバレッジは少し意味が違いますが、まぁカバレッジ80%~90%ぐらいに設定しておけばテストファイルがあるかどうはそこまで気にしませんよね。。
ちなみにテストファイルからimportされていないファイルもカバレッジ計測に含める設定について、以下のリンクが分かりやすくまとまっています。
scandog より、テストツールの設定を適切に使うことをお勧めします。
scandog が価値を発揮できる場面は限られているかもしれませんが、作ったことについてはいい経験だったと思っています。今後自分で CLI ツールを作ることになったら雛形的に使えると思っています。
この記事では node の CLI ツールの作り方について、誰かの役に立つかもしれないのでまとめておきます。
sort-package-json をじっくり観察する
まず cli として実行できる小さなパッケージを参考にするために sort-package-json を選びました。sort-package-json は package.json をソートしてくれるツールです。対象にする package.json はデフォルトではカレントディレクトリ直下のものだけですが、globを与えることでそのglobに存在している package.json も処理対処にすることが出来ます。
小さいので全体を掴みやすかったです。
sort-package-json のコードは3つのファイルに分けられています。
- cli.js : エントリーポイント
- index.js : メインロジック
- reporter.js : コンソールに流すメッセージの制御
観察してみて分かったのは、「package.json で bin
にファイルパスを指定すると、そこがエントリーポイントになるらしい。」ということです。そこで本家ドキュメントを確認しました。
こちらも参考にしました。
TypeScriptで構成する
sort-package-json は、JavaScript で書かれていますが、私は TypeScript 大好きなので TypeScript で書きます。今回、すごく規模の小さいツールを作る予定なので、tsc
でコンパイルすることにしました。他には esbuild, swc などがモダンな候補になると思います。
TypeScript 大好き、といいつつも React プロジェクトを作るときなど、 CRA や Vite に任せたデフォルトのまま使っていたので、tsconfig.json
のビルド系の設定は詳しくありません。
そこで今回は完全にサバイバルTypeScriptさんにお世話になりました。
まずsort-package-jsonと同じ構成で書いてみる
sort-package-json は glob を処理する部分に globby というパッケージを使っていました。
scandog のメインロジック(index.ts)においても globby でファイルを探索させるようにしました。
reporter の部分はとりあえずそのまま class で宣言した Reporter をメッセージだけ変更して使いました。
公開とリファクタリング
とりあえずこの時点で問題なく動きました。すぐに0.0.1として公開しましたが、いくつか不満がありました。そしてリファクタリングとして以下に取り組みました。
- Reporter をオブジェクト指向ではなく関数型プログラミングで書き直す
- 引数の処理が煩雑なので yargs を採用
- テストツールには Vitest 採用
- ドキュメントきちんとするため Google 翻訳と ChatGPT の行ったり来たり
yargs の使い方
CLIツール作るなら何らか引数のパーサーが欲しくなると思います。面倒なパース処理を任せられるだけではなく、パーサーライブラリーの仕様を受け入れることで自動的に標準的なインターフェースになるというメリットもあります。yargs はしっかり TypeScript 対応しているので使いやすいと思いました。
ただ1つ command(cmd, desc, ...)
の第一引数であるcmd
の書き方を理解するのに時間が掛かりました。
1つのコマンドだけを持つツールを作ろうとしていたのですが、公式ドキュメントは複数のコマンドを持つツールを作ろうとしている例ばかりなのでなかなか頭に描いているインターフェースになりません。以下の記事を参考に、コマンド名を与える代わりにアスタリスク(*)で解決しました。
まとめ
これらの改善を繰り返してパッチバージョンをどんどん上げていきました。そして yargs のドキュメントを隅々まで読んでインターフェースを整えて、もう落ち着いたなというところまで改善したところで 1.0.0 に上げています。
冒頭でも書きましたが scandog はおそらく自分でも使わないツールになってしまいました。しかしプロジェクトの構成としてはかなり気に入っています。npx なツールを作りたいアイディアは他にもあるので、今回学んだことを活かして作ろうと思います。
もちろん次に作るときは、そのツールが解決したいことが、既存の技術でどうにか実現できないかしっかり調査してからですよね。
こんな経験は初めてではなく、 zod を知らない頃に zod らしいバリデーションライブラリーを開発するため100時間ぐらい投資したことがありまして、全く懲りていないですね。
TypeScriptファーストなバリデーションライブラリーの開発にはハイレベルな型レベルプログラミングの勉強が必要だったのでそれも自分を成長させてくれたいい経験だと思っています。
とはいえ、次に作るものは小さくてもいいから誰かに感謝されるものを作りたい!!
誰かに感謝されるものを作りたい!!
以上。
おまけ: 名前について
「もし足りないファイルを自動生成するとしたら、ファイルの中身どうしよう |ω・)チラ scaffdog」
「お! scaffdogは対話スキップできるのか。 これで自動生成できる機能付けたい」
「scaffdogで生成しても結局それぞれ内容書くのだし、チェック機能だけあればいいか」
ということで、名前に dog が付いています。
Discussion