はじめに
Node.js で CLI ツールを作成する際に知っておきたい知識をまとめました。設定ファイルの配置場所、コマンド設計、テストしやすい設計など、実践的な内容を解説します。
設定ファイルの配置場所
CLI ツールを作ると、ほぼ必ず「設定ファイルをどこに置くか」という問題に直面します。
よくある配置パターン
| パターン | 例 | 用途 |
|---|---|---|
| ホームディレクトリ直下 | ~/.mytoolrc, ~/.mytool/ | レガシー。古いツールに多い |
| XDG 準拠 | ~/.config/mytool/ | モダン。新しいツールの主流 |
| プロジェクトローカル | ./.mytool/, ./mytool.config.js | プロジェクト固有の設定 |
有名ツールの設定ファイル配置
| ツール | 配置場所 | 備考 |
|---|---|---|
| Git | ~/.gitconfig | 歴史的標準。現在は ~/.config/git/config も公式サポート。 |
| SSH | ~/.ssh/config | 変更不可。ディレクトリ権限(700)に非常に厳しい。 |
| AWS CLI | ~/.aws/ | AWS_SHARED_CREDENTIALS_FILE 等の環境変数で変更は可能。 |
| npm | ~/.npmrc | レガシー。NPM_CONFIG_USERCONFIG で移動自体は可能。 |
| Neovim | ~/.config/nvim/init.lua | XDG準拠。macOSでも ~/.config を使うのが一般的。 |
| VS Code | ~/.config/Code/ (Linux) | macOS: ~/Library/Application Support/Code/Windows: %APPDATA%\Code\ |
| Docker | ~/.docker/config.json | DOCKER_CONFIG 環境変数で場所を変更可能。 |
| Zsh | ~/.zshrc | 基本はホーム直下。ZDOTDIR を設定すれば移動可能。 |
古いツールはホームディレクトリ直下、新しいツールは ~/.config/ 以下に配置する傾向があります。
XDG Base Directory Specification
XDG とは?
XDG Base Directory Specification は、freedesktop.org が策定した Linux/Unix 系 OS での設定ファイルやデータの保存場所の標準仕様です。
「ホームディレクトリがドットファイルだらけになる問題」を解決するために生まれました。
主なディレクトリ
| 環境変数 | デフォルト | 用途 | 例 |
|---|---|---|---|
XDG_CONFIG_HOME | ~/.config | 設定ファイル | ~/.config/git/config |
XDG_DATA_HOME | ~/.local/share | アプリデータ | ~/.local/share/nvim/ |
XDG_CACHE_HOME | ~/.cache | キャッシュ | ~/.cache/npm/ |
XDG_STATE_HOME | ~/.local/state | ログ、履歴 | ~/.local/state/bash/history |
実装のポイント
XDG 準拠の設定パスを取得する際は、以下の順序で決定します:
- 環境変数
XDG_CONFIG_HOMEが設定されていればそれを使用 - 未設定なら
~/.configをデフォルトとして使用
なぜ環境変数をチェックするのか?
ユーザーが XDG_CONFIG_HOME=/custom/path と設定している場合、それを尊重するのがマナーです。CI 環境やコンテナ環境で設定場所を変えたいケースは意外と多いです。
Windows での扱い
XDG は Linux/Unix 向けの仕様ですが、Windows でも ~/.config/ を使うツールが増えています。
選択肢は2つあります:
- Windows ネイティブを尊重 -
%APPDATA%(C:\Users\username\AppData\Roaming) を使用 - XDG で統一 - Windows でも
~/.config/を使用
開発者向け CLI なら Windows でも ~/.config/ で統一するのもアリです。WSL を使う開発者にとっては、パスが統一されている方が便利だからです。
XDG 準拠のメリット
- ホームディレクトリがすっきり -
ls -la ~がドットファイルだらけにならない - バックアップが容易 -
~/.configをバックアップすれば全設定が保存される - 一貫性 - どのツールも同じ場所に設定がある
- カスタマイズ可能 - 環境変数で配置場所を変更できる
コマンド設計パターン
サブコマンドパターン
大きな CLI ツールは、サブコマンドで機能を分割します。
# Git スタイル
git config --list
git config get user.name
git config set user.name "John"
# npm スタイル
npm config list
npm config get registry
npm config set registry https://...
# AWS CLI スタイル
aws configure list
aws configure get region
aws configure set region ap-northeast-1共通オプションの設計
# スコープを指定するオプション
git config --global user.name # ユーザー設定
git config --local user.name # プロジェクト設定
git config --system user.name # システム設定設計のポイント:
-g, --globalのように短縮形も用意する- デフォルトの挙動を明確にする(読み取りはマージ、書き込みはローカル、など)
出力形式のオプション
# 人間向け(デフォルト)
mytool config list
# スクリプト向け
mytool config list --json
mytool config get key --rawスクリプトから使われることを想定して、機械可読な出力形式を用意しておくと喜ばれます。
テストしやすい設計
CLI ツールでもテストは重要です。
依存性注入(Dependency Injection)
外部依存(ファイルシステム、ネットワーク、ユーザー入力など)を注入可能にすることで、テスト時にモックに差し替えられます。
テストしにくい設計の特徴:
- グローバルな
fsモジュールを直接使用 process.cwd()やos.homedir()を関数内で直接呼び出しconsole.logを直接使用
テストしやすい設計の特徴:
- 外部依存を引数として受け取る(デフォルト値は本物を使用)
- パスを返す関数を注入可能にする
- ロガーを注入可能にする
注入すべき主な依存
| 依存 | 理由 |
|---|---|
ファイルシステム (fs) | ファイルの存在や内容をモックしたい |
プロンプト (inquirer) | ユーザー入力をモックしたい |
ロガー (console) | 出力をキャプチャしたい |
現在時刻 (Date.now) | 日付に依存するテストを安定させたい |
| 環境変数 | 異なる環境をシミュレートしたい |
ユーザーフレンドリーな出力
色付き出力
ターミナル出力に色をつけると、情報が読みやすくなります。
| 色 | 用途 |
|---|---|
| 緑 | 成功、完了 |
| 赤 | エラー |
| 黄 | 警告 |
| シアン | 情報、キー名 |
| 薄い色(dim) | 補足情報、パス |
| 太字 | 強調 |
主なライブラリ:
| ライブラリ | サイズ | 特徴 |
|---|---|---|
picocolors | 2.6 KB | 最軽量、依存なし |
kleur | 8 KB | シンプル |
chalk | 44 KB | 機能豊富 |
軽量さを求めるなら picocolors がおすすめです。
スピナー(進捗表示)
時間のかかる処理には進捗表示を出すと、ユーザーが「固まった?」と不安にならずに済みます。
ora ライブラリが定番です。
テーブル出力
設定一覧などを表示する際は、テーブル形式が見やすいです。cli-table3 などのライブラリが使えます。
エラーハンドリング
終了コード
CLI ツールは適切な終了コードを返すべきです。
| コード | 意味 |
|---|---|
| 0 | 成功 |
| 1 | 一般的なエラー |
| 2 | 引数エラー |
ユーザーフレンドリーなエラーメッセージ
悪い例:
Error: ENOENT: no such file or directory良い例:
Error: Configuration file not found
Expected: ~/.config/mytool/config.json
Run "mytool init" to create a configuration file.ポイント:
- 何が問題かを明確に
- どこで問題が起きたかを示す
- 解決方法を提案する
Ctrl+C のハンドリング
インタラクティブプロンプト中に Ctrl+C が押された場合、エラーではなく「キャンセルされた」として扱い、正常終了(exit code 0)するのが一般的です。