Node.jsを使用したCLIの構築方法
Time to read:
Node.jsを使用して構築されたコマンドラインインターフェース(CLI)は、広大なNode.jsのエコシステムを活用し、反復的なタスクを自動化することができます。また、npmやyarnなどのパッケージマネージャーを通じて簡単に配布でき、複数のプラットフォームで利用できます。この記事では、CLIの作成にNode.jsを使用するメリットとその方法、いくつかの便利なパッケージ、そして新しいCLIを配布する方法を解説します。
CLIの作成にNode.jsを使用する理由
Node.jsが人気を博した理由の1つは、npmレジストリに90万以上のパッケージを有するパッケージエコシステムであるという点です。Node.jsを使用してCLIを作成することで大量のCLI向けパッケージを含むエコシステムを利用することができます。たとえば、CLIで次のようなパッケージを利用できます。
- 複雑な入力プロンプト用の
inquirer、enquirer、prompts - 便利な電子メール入力プロンプト用の
email-prompt - カラー出力用の
chalkまたはkleur - 美しいスピナーを作成する
ora - 出力の周囲にボックスを描画するための
boxen tmuxに似たUI作成用のstmux- 進捗状況リスト用の
listr - ReactによるCLI構築用の
ink - 基本的な引数解析のための
meowまたはarg - 複雑な引数の解析とサブコマンドサポートのための
commanderおよびyargs - Herokuによる拡張可能なCLI構築用フレームワーク
oclif(代替としてgluegun)
他にもnpmに公開されているCLIをyarnとnpmの両方から利用する便利な方法がたくさんあります。たとえば、create-flex-pluginはTwilio Flex用のプラグインのブートストラップに使用できるCLIですが、これをグローバルコマンドとしてインストールできます。
またはプロジェクト固有の依存関係としてインストールできます。
また、npxを用いてCLIをインストールせずに実行できます。npx create-flex-pluginを実行すると、ローカルまたはグローバルにインストールされているバージョンが見つからない場合、キャッシュにダウンロードされます。
最後に、npmバージョン6.1以降では、npm init、yarnは、create-*という名前のCLIを使用してプロジェクトをブートストラップする方法をサポートしています。たとえばcreate-flex-pluginの中で実際に使用されている呼び出し方法は次のようになります。
最初のCLIをセットアップする
ここからのセットアップ方法はビデオチュートリアルが用意されています。良ければ、YouTubeのチュートリアルもご覧ください。
> YouTubeビデオを埋め込み
ここまででCLIの作成にNode.jsを使用するメリットを説明しました。ここからはCLIの作成を始めましょう。このチュートリアルではnpmを使用しますが、yarnもほぼすべてのコマンドを網羅しています。システムにNode.jsとnpmがインストールされていることを確認してください。
このチュートリアルでは、npm init @your-username/projectコマンドで実行するCLIを作成し、その内部では新規プロジェクトをブートストラップします。
次を実行し、新しいNode.jsプロジェクトを開始します。
その後、プロジェクトのルートにsrc/というディレクトリを作成し、その中にcli.jsというファイル作成します。作成したファイルには次のコードを配置します。
これは、後にロジックを解析し、実際のビジネスロジックをトリガーするパーツになります。次に、CLIのエントリーポイントを作成します。プロジェクトのルートに新しいディレクトリbin/を作成し、その中にcreate-projectという新しいファイルを作成します。その中に次のコードを配置します。
この小さなスニペットにはいくつかの役割があります。まず、他のファイルでimportを使用できるようにするために、esmというモジュールが必要であると定義しています。これはCLIの構築と直接関係ありませんが、このチュートリアルではESモジュールを使用しており、esmパッケージを使用するとサポートされていないバージョンのNode.jsにトランスパイルを行う必要がなくなります。その後cli.jsファイルをrequireし、cli関数を呼び出します。この呼び出しにはprocess.argvでアクセスできるコマンドラインからこのスクリプトに渡されたすべての引数の配列をそのまま関数に渡しています。
スクリプトをテストする前に次のコマンドを実行し、依存関係にあるesmをインストールします。
CLIスクリプトを公開していることをパッケージマネージャーに通知する必要もあります。そのため、package.jsonに適切なエントリを追加します。また、忘れずにdescription、name、keyword、mainプロパティを適宜更新します。
ここでbinキーを見ると、2つのキーと値のペアを持つオブジェクトを渡しています。これらのオブジェクトはパッケージマネージャーがインストールするCLIコマンドを定義しています。この例では、同じスクリプトを2つのコマンドに登録します。1つはユーザー名を使用して独自のnpmスコープを使用し、もう1つは便宜上の汎用create-projectコマンドとします。
これでスクリプトをテストできます。テストにはnpm linkコマンドを使用するのが一番簡単な方法です。次のようにプロジェクト内のターミナルで実行します。
これにより、現在のプロジェクトにリンクするシンボリックリンクがグローバルにインストールされるため、コード更新の際にこの作業を再度実行する必要はありません。npm linkを実行すると、CLIコマンドが利用できるようになります。次のコマンドを実行してください。
次のような出力が表示されていれば正しく設定されています。
どちらのパスも、プロジェクトの場所やNode.jsがインストールされている場所により異なることに注意してください。この配列は、引数を追加するたびに長くなります。試しに次のコマンドを実行します。
出力には新しい引数が反映されます。
引数を解析し、入力を処理する
これで、スクリプトに渡される引数を解析し、利用を開始するための準備が完了しました。このCLIでは1つの引数と下記に記しているいくつかのオプションをサポートするように実装していきます。
[template]: 異なるテンプレートを直接サポートします。これが渡されない場合、ユーザーにテンプレートの選択を促します--git:git initを実行し、新しいgitプロジェクトのインスタンスを作成します--install: プロジェクトのすべての依存関係を自動的にインストールします--yes: すべてのプロンプトをスキップし、デフォルトのオプションを使用します
このプロジェクトでは、不足している値の入力を促すためにinquirerを使用し、また、CLIの引数を解析するためにargライブラリを使用します。そのため次のコマンドを実行し不足している依存関係をインストールします。
まず、optionsオブジェクトに引数を解析するロジックを記述しましょう。次のコードをcli.jsに追加します。
この状態でcreate-project --yesを実行すると、skipPromptがtrueとなります。あるいは、create-project cliと、引数を渡して実行するとtemplateに値がセットされます。
これでCLIの引数を解析できるようになりました。次に不足している情報の入力を促す機能と、--yesフラグが渡された場合にデフォルトの引数を用いる機能を追加する必要があります。次のコードをcli.jsファイルに追加します。
ファイルを保存し、create-projectを実行すると、テンプレート選択のプロンプトが表示されます。
その後gitを初期化するか否かの質問がプロンプトされます。両方を選択すると、次のような出力が表示されます。
同じコマンドに-yを指定して実行すると、プロンプトがスキップされます。その代わりに決定されたオプションの出力がすぐに表示されます。
ロジックを記述する
プロンプトとコマンドライン引数で各オプションを決定できるようになるため、次にプロジェクトを作成する実際のロジックを記述しましょう。このCLIは、npm initと同様に既存のディレクトリに書き込み、プロジェクトのtemplatesディレクトリからすべてのファイルをコピーします。他のプロジェクトで同じロジックを再利用したい場合に備え、オプションによりターゲットディレクトリを変更できるようにします。
実際のロジックを記述する前に、プロジェクトのルートにtemplatesディレクトリを作成し、その中にtypescriptとjavascriptという名前の2つのディレクトリを配置します。これらは、ユーザーに選択候補を表示した2つの値の小文字版です。この記事ではこれらの名前を使用しますが、他の名前を使用することもできます。このディレクトリには、プロジェクトのベースとなるpackage.jsonや、プロジェクトにコピーしたい任意のファイルを配置します。後で我々が実装するコードはこれらのファイルを新しいプロジェクトにコピーします。どんなファイルを配置するかについてインスピレーションを得たい場合は、github.com/dkundel/create-projectを参考にしてください。
ファイルの再帰的コピーを行うために、ncpというライブラリを使用します。このライブラリはクロスプラットフォームの再帰的コピーをサポートしており、既存のファイルを強制的に上書きするフラグも用意されています。さらに、カラー出力用にchalkをインストールします。これらの依存関係をインストールするには、次のコマンドを実行します。
ここでは、すべてのコアロジックをプロジェクトのsrc/ディレクトリ内にあるmain.jsファイルに配置します。ファイルを作成し、次のコードを追加します。
このコードは、createProjectという新しい関数をエクスポートします。これはfs.accessを使用し、readアクセス(fs.constants.R_OK)をチェックすることにより指定されたテンプレートが本当に利用可能なテンプレートであるか否かをチェックした後に、ncpを使用してターゲットディレクトリにファイルをコピーします。ファイルのコピーに成功した際に完了 プロジェクトの準備ができましたというログをカラーで表示します。
その後cli.jsを更新し、新しいcreateProject関数を呼び出します。
ここまでの進捗状況を確認するため、システム上の~/test-dirなどに新しいディレクトリを作成し、その中でテンプレートのいずれかを使用したコマンドを実行します。例:
プロジェクトが作成され、ファイルがディレクトリにコピーされます。
さて、ここからCLIに実行させたい手順があと2つあります。オプションによるgitの初期化と依存関係のインストールです。ここでは、さらに3つの依存関係を使用します。
execaはgitのような外部コマンドの実行を容易にしますpkg-installはユーザーが何を使用しているかに応じてyarn installまたはnpm installをトリガーしますlistrはタスクのリストを指定でき、ユーザーに進捗状況の概要を提供します
次を実行し、依存関係をインストールします。
その後、次のコードが含まれるようにmain.jsを更新します。
このとき、--gitが渡されるか、ユーザーがプロンプトでgitを選択するとgit initを実行し、ユーザーが--installを渡すとnpm installまたはyarnを実行します。そうでない場合はタスクをスキップし、自動インストールを希望する場合は--installを渡すように通知するメッセージが表示されます。
さきほどのテストフォルダを削除してから、新しいフォルダを作成してみましょう。そして、次のコマンドを実行します。
フォルダ内にgitが初期化されたことを示す.gitフォルダと、インストールされたpackage.jsonで指定された依存関係がインストールされているnode_modulesフォルダの両方があることが分かります。
おめでとうございます。これで、最初のCLIの準備ができました。
自分のコードを実際のモジュールとして利用可能とし、そのロジックを他者が自分のコードに再利用できるようにしたい場合、src/ディレクトリにmain.jsの内容を公開するindex.jsファイルを追加する必要があります。
次のステップ
CLIコードの準備ができましたが、この先は目的ごとに選択肢が異なります。自分専用とし、他者と共有しないのであれば、npm linkを使用します。実際にnpm init projectを実行してみると、コードがトリガーされます。
テンプレートを他者と共有したい場合は、コードをGitHubにプッシュしてそこから利用します。あるいはnpm publishコマンドを使用し、スコープしたパッケージとしてnpmレジストリにプッシュします。ただしその前に、package.jsonにfilesキーを追加し、どのファイルを公開するか指定する必要があります。
公開されるファイルを確認したい場合、npm pack --dry-runを実行し、出力を確認します。その後、npm publishを使用してCLIを公開します。このチュートリアルで作成したプロジェクトは@dkundel/create-projectで見つけることができます。またはnpm init @dkundel/projectを実行してください。
また、さまざまな機能を追加することもできます。実際のパッケージでは、LICENSE、CODE_OF_CONDUCT.mdおよび.gitignoreファイルを作成する依存関係を追加しました。ソースコードはGitHubで公開されています。また、前述のイブラリを使用し、追加機能をチェックすることもできます。掲載してほしいライブラリがある場合や、ご自身のCLIを私に見せたい場合は、お気軽にメッセージをいただければ幸いです。
- Email: dkundel@twilio.com
- Twitter: @dkundel
- GitHub: dkundel
- dkundel.com