inquirer.jsを使用してCLIアプリケーションに対話型インターフェースを導入する

UI開発者 永田

この記事はミツエーリンクス Advent Calendar 2019 - Qiitaの7日目の記事です。

昨今のWebフロントエンド業界では、HTML、CSS、JSの開発を行う際にwebpack、Gulpといった技術の活用や、Vue.js、Reactなどのフレームワークの使用が当たり前となってきています。
それらの開発環境を導入するためには、ターミナルからnpm経由でパッケージをインストールすることになりますが、最近ではターミナルからCLIアプリケーションを操作しながら開発を行う場面も増えてきました。
技術の移り変わりが激しい業界であるゆえにかと思いますが、それはつまり、フロントエンドエンジニアも黒い画面(ターミナル)と向き合い、CLIの操作に慣れておく必要があるということです。

本稿ではNode.jsでCLIアプリケーションを作成する時に役立つnpmパッケージ「inquirer.js」の使い方を紹介しつつ、対話形式の楽しいインターフェースをターミナル上で動作させていきます。
実際にCLIアプリケーションを作成してガッツリとコマンドの設定を書いていくわけではありませんが、CLIを操作したことがない方でも楽しく黒い画面を操作することができる内容となっておりますので、CLIアプリケーションを作ってみたい、黒い画面と仲良くなりたいと思っている方の学習の足がかりになれば幸いです。

inquirer.js

CLIアプリケーションに対話型のインターフェースを組み込むことができるパッケージです。
導入することで、簡単かつ少ないコードでインタラクティブなCLIアプリケーションを作成できます。
オプションも豊富で、text input、list(番号付きも可)、checkbox、passwordなど、さまざまな対話形式に対応しています。

インストール

まずはプロジェクトの初期化と実行用のJSファイルを作成します。

npm init -y

Linux

touch index.js

Windows(PowerShell)

ni index.js

プロジェクトを実行しやすいように、package.jsonのscriptsにstartコマンドを追加しておきましょう。

"scripts": {
    "start": "node index.js"
},

最後に、さきほど作成したindex.jsでモジュールを読み込みます。

const inquirer = require('inquirer');

以上でinquirer.jsを実行する準備が整いました。

基本的な使い方

inquirer.prompt(questions)メソッドを使用して対話を設定します。

引数questionsはArray型ですので、オプションを設定したオブジェクトを配列に追加し、引数に設定します。

今回は以下のオプションを使用します。

  • type: (String) - 対話形式の選択 [ input (default)、 number、 confirm、 list、 rawlist、 expand、 checkbox、 password、 editor ]
  • name: (String) - promptの識別名
  • message: (String | Function) - ターミナルに出力する質問テキスト
  • default: (String | Number | Boolean | Array | Function) - 未入力時に表示するデフォルト値
  • mask: (String) - パスワード入力中のマスク表示で使用する文字列
  • choices: (Array | Function) - 回答項目リスト。new inquirer.Separator()を配列に追加することでセパレータを表示する
  • validate: (Function) - バリデーションを実行する関数。値が有効な場合はtrueを返却する

実行準備

以下のコードをindex.jsに追加します。

typeオプションが設定されていない場合、inputの対話形式として設定されます。

const inquirer = require('inquirer');

inquirer
    .prompt([
        {
            name: 'username',
            message: 'Enter your username.',
            default: 'mlc-user'
        }
    ]);

promptメソッドの戻り地はPromiseオブジェクトですので、then()でつなげていきます。

const inquirer = require('inquirer');

inquirer
    .prompt([
        {
            name: 'username',
            message: 'Enter your username.',
            default: 'mlc-user'
        }
    ])
    .then(answers => {
        console.info('USERNAME:', answers.username);
    });

これで対話型インターフェースの導入が完了しました!

実行

npm startで実行します。

npm start

> node-cli@1.0.0 start D:\works\node-cli
> node index.js

? Enter your username. (mlc-user)

さきほど設定した質問文が表示されました!

続けて回答を入力します。

npm start

> node-cli@1.0.0 start D:\works\node-cli
> node index.js

? Enter your username. mlc-nagata
Username is mlc-nagata

入力した内容が出力されました。簡単です!

さまざまな質問形式

前述したように、inquire.jsはさまざまな対話形式に対応しているので、いろいろ試してみます。

引数の配列に新規オブジェクトを追加するだけで対話を連結させることができます。
質問の回答はanswersにすべて格納される仕組みです。

typeオプションに任意の対話形式を設定します。

Password

ユーザーの入力が表示されます。
maskオプションを設定しない場合は入力中のマスク文字は表示されず、ユーザーの入力はすべて非表示になります。

const inquirer = require('inquirer');

inquirer
    .prompt([
        {
            name: 'username',
            message: 'Enter your username.',
            default: 'mlc-user'
        },
        {
            type: 'password',
            name: 'password',
            message: 'Enter your password.',
            mask: '*'
        }
    ])
    .then(answers => {
        console.info('USERNAME:', answers.username);
        console.info('PASSWORD:', answers.password);
    });

実行します。

$ npm start

> node-cli@1.0.0 start D:\works\node-cli
> node index.js

? Enter your username. mlc-nagata
? Enter your password. ***************

入力中の文字列がマスクされていることが確認できます。

List

任意の選択肢を定義して、リスト形式で提示することができます。

choicesに選択肢の配列を設定します。

const inquirer = require('inquirer');

inquirer
    .prompt([
        {
            name: 'username',
            message: 'Enter your username.',
            default: 'mlc-user'
        },
        {
            type: 'password',
            name: 'password',
            message: 'Enter your password.',
            mask: '*'
        },
        {
            type: 'list',
            name: 'region',
            message: 'Choose A Region.',
            choices: ['Americas', 'Europe', 'Asia Pacific']
        }
    ])
    .then(answers => {
        console.info('USERNAME:', answers.username);
        console.info('PASSWORD:', answers.password);
        console.info('REGION:', answers.region);
    });

実行します。

$ npm start

> node-cli@1.0.0 start D:\works\node-cli
> node index.js

? Enter your username. mlc-nagata
? Enter your password. ***************
? Choose A Region. (Use arrow keys)
> Americas
Europe
Asia Pacific

選択式リストが表示されました。

checkbox

選択肢の複数選択が可能になります。

Listと同じように、choicesを設定します。

const inquirer = require('inquirer');

inquirer
    .prompt([
        {
            name: 'username',
            message: 'Enter your username.',
            default: 'mlc-user'
        },
        {
            type: 'password',
            name: 'password',
            message: 'Enter your password.',
            mask: '*'
        },
        {
            type: 'list',
            name: 'region',
            message: 'Choose A Region.',
            choices: ['Americas', 'Europe', 'Asia Pacific'],
        },
        {
            type: 'checkbox',
            name: 'favoriteFrameworks',
            message: 'Choose your favorite JS frameworks.',
            choices: ['Vue', 'React', 'Angular', 'jQuery', 'Others']
        }
    ])
    .then(answers => {
        console.info('USERNAME:', answers.username);
        console.info('PASSWORD:', answers.password);
        console.info('REGION:', answers.region);
        console.info('FAVORITE FRAMEWORKS:', answers.favorites);
    });

実行します。

$ npm start

> node-cli@1.0.0 start D:\works\node-cli
> node index.js

? Enter your username. mlc-nagata
? Enter your password. ***************
? Choose A Region. Americas
? Choose your favorite JS frameworks. (Press <space> to select, <a> to toggle all, <i> to invert selection)
>( ) Vue
( ) React
( ) Angular
( ) jQuery
( ) Others

チェックボックスと操作説明のテキストが表示されました。

Validate

ユーザーの入力を受け取り、バリデーションをかけることができます。

const inquirer = require('inquirer');

inquirer
    .prompt([
        {
            name: 'username',
            message: 'Enter your username.',
            default: 'mlc-user'
        },
        {
            type: 'password',
            name: 'password',
            message: 'Enter your password.',
            mask: '*'
        },
        {
            type: 'list',
            name: 'region',
            message: 'Choose A Region.',
            choices: ['Americas', 'Europe', 'Asia Pacific'],
        },
        {
            type: 'checkbox',
            name: 'favoriteFrameworks',
            message: 'Choose your favorite JS frameworks.',
            choices: ['Vue', 'React', 'Angular', 'jQuery', 'Others']
        },
        {
            type: 'input',
            name: 'favoriteNumber',
            message: 'Enter any number from 2 to 3 digits.',
            validate: (input) => {
                if (Number.isFinite(Number.parseInt(input)) && input.match(/^[0-9]{2,3}$/)) {
                    return true;
                } else {
                    return 'invalid value'
                }
            }
        }
    ])
    .then(answers => {
        console.info('USERNAME:', answers.username);
        console.info('PASSWORD:', answers.password);
        console.info('REGION:', answers.region);
        console.info('FAVORITE FRAMEWORKS:', answers.favorites);
        console.info('FAVORITE NUMBER:', answers.favorites);
    });

2~3桁までの好きな数字を入力し、値に対してバリデーションを実行させます。

$ npm start

> node-cli@1.0.0 start D:\works\node-cli
> node index.js

? Enter your username. mlc-nagata
? Enter your password. ***************
? Choose A Region. Americas
? Choose your favorite JS frameworks. Vue, jQuery, Others
? Enter any number from 2 to 3 digits. test
>> invalid value

試しにtestと入力してみた結果、バリデーターから返却されたメッセージが出力されました。

実行結果

$ npm start

> node-cli@1.0.0 start D:\works\node-cli
> node index.js

? Enter your username. mlc-nagata
? Enter your password. ***************
? Choose A Region. Americas
? Choose your favorite JS frameworks. Vue, jQuery, Others
? Enter any number from 2 to 3 digits. 328
USERNAME: mlc-nagata
PASSWORD: sample-password
REGION: Americas
FAVORITE FRAMEWORKS: [ 'Vue', 'jQuery', 'Others' ]
FAVORITE NUMBER: 328

すべての対話が完了した後に回答が出力されています。

モジュール化

実際に使用する際にはモジュールとして再利用しやすい状態であってほしいので、これまでに作成したコードをモジュール化しておきます。

以下の手順で新規JSファイルを作成します。

Linux

mkdir ./lib/; touch ./lib/questions.js

Windows (PowerShell)

ni ./lib/ -ItemType Directory; ni ./lib/questions.js

作成した./lib/questions.jsに以下の内容を記述します。

const inquirer = require('inquirer');

module.exports = {
    userInfo: () => {
        return inquirer.prompt([
            {
                name: 'username',
                message: 'Enter your username.',
                default: 'mlc-user'
            },
            {
                type: 'password',
                name: 'password',
                message: 'Enter your password.',
                mask: '*'
            },
            {
                type: 'list',
                name: 'region',
                message: 'Choose A Region.',
                choices: ['Americas', 'Europe', 'Asia Pacific'],
            },
            {
                type: 'checkbox',
                name: 'favoriteFrameworks',
                message: 'Choose your favorite JS frameworks.',
                choices: ['Vue', 'React', 'Angular', 'jQuery', 'Others']
            },
            {
                type: 'input',
                name: 'favoriteNumber',
                message: 'Enter any number from 2 to 3 digits.',
                validate: (input) => {
                    if (Number.isFinite(Number.parseInt(input)) && input.match(/^[0-9]{2,3}$/)) {
                        return true;
                    } else {
                        return 'invalid valuel';
                    }
                }
            }
        ]);
    }
};

次に、例としてindex.jsを以下のように修正します。

const questions  = require('./lib/questions');

const execUserInfoSurvey = async () => {
    return await questions.userInfo();
};

const execute = () => {
    execUserInfoSurvey().then((userInfo) => {
        console.info(userInfo);
    });
};

execute();

asyncawaitと組み合わせるとより使いやすくなるかと思います。

おわりに

inquirer.jsは、少ないコードでリッチな表現を追加することができる素晴らしいライブラリです。

本稿で紹介していないオプションやメソッドを備えていますので、ぜひ試してみてください。

今回の記事はNode.jsの勉強としてCLIアプリケーションを作成した際の備忘録も兼ねているのですが、機会があればCLIアプリケーション自体の作り方についても書くことができればと思います。