はじめに
npm create vite@latest を実行したときに表示される、あの矢印キーでフレームワークを選ぶ画面。「自分のCLIツールにもあんな対話画面を付けたい」と思ったことはありませんか?
標準の readline モジュールで実装しようとすると、カーソル制御やキー入力のハンドリングだけで心が折れそうになります。そこで登場するのが、Node.jsの対話型CLIにおける長年の定番、Inquirer.jsです。
実はこのInquirer.js、現在は @inquirer/prompts という新しいパッケージ構成に生まれ変わり、APIも大幅にモダン化されています。この記事では、新しいAPIを前提に基本から実践までを解説します。
Inquirer.jsとは
Inquirer.jsは、コマンドラインで「質問して答えてもらう」ためのUIコンポーネント集です。テキスト入力、単一選択、複数選択、パスワード入力といった、CLIでよく使う対話パターンが一通り揃っています。
かつては inquirer という単一パッケージで提供されていましたが、現在は機能ごとに分割された @inquirer/prompts が推奨されています。2026年5月にはv8.5系がリリースされており、開発も非常に活発です。
主な特徴
- Promiseベースのシンプルな関数API - 旧来の設定オブジェクトの配列を渡すスタイルから、
await input(...)と1問ずつ書くスタイルに進化し、コードの見通しが劇的に良くなりました - 豊富なプロンプト種別 - input / select / checkbox / confirm / password / search / editor / number など、必要なものがほぼ網羅されています
- TypeScriptファースト - コードベースの大部分がTypeScriptで書かれており、回答の型が自動で推論されます
- モジュール分割による軽量化 -
@inquirer/inputのように必要なプロンプトだけを個別インストールすることも可能です
インストール
npm、yarn、pnpmのいずれでもインストールできます。
# npm
npm install @inquirer/prompts
# yarn
yarn add @inquirer/prompts
# pnpm
pnpm add @inquirer/prompts
基本的な使い方
まずは代表的な4つのプロンプトを試してみましょう。以下のコードをコピーして demo.mjs として保存し、node demo.mjs で実行できます。
import { input, select, confirm, checkbox } from '@inquirer/prompts';
// テキスト入力
const name = await input({
message: 'プロジェクト名を入力してください',
default: 'my-app',
});
// 単一選択(矢印キーで選ぶ)
const framework = await select({
message: 'フレームワークを選択してください',
choices: [
{ name: 'React', value: 'react' },
{ name: 'Vue', value: 'vue' },
{ name: 'Svelte', value: 'svelte' },
],
});
// 複数選択(スペースキーでチェック)
const tools = await checkbox({
message: '追加するツールを選択してください',
choices: [
{ name: 'ESLint', value: 'eslint', checked: true },
{ name: 'Prettier', value: 'prettier' },
{ name: 'Vitest', value: 'vitest' },
],
});
// はい/いいえの確認
const ok = await confirm({ message: 'この内容で作成しますか?' });
console.log({ name, framework, tools, ok });
await で1問ずつ聞いていくだけなので、直前の回答に応じて次の質問を分岐させるのも普通の if 文で書けます。旧APIで苦労した「動的な質問フロー」が、ただのJavaScriptのフロー制御になったのは大きな進化です。
入力値のバリデーション
validate オプションに関数を渡すと、その場で入力チェックができます。文字列を返すとエラーメッセージとして表示されます。
import { input } from '@inquirer/prompts';
const port = await input({
message: 'ポート番号を入力してください',
default: '3000',
validate: (value) => {
const num = Number(value);
if (!Number.isInteger(num) || num < 1 || num > 65535) {
return '1〜65535の整数を入力してください';
}
return true;
},
});
実践的なユースケース
プロジェクト雛形生成CLIを作る
実際のプロジェクトでよくある「設定ファイルを対話的に生成するCLI」を作ってみます。Ctrl+Cによる中断のハンドリングも含めた、実戦仕様の例です。
import { input, select, confirm } from '@inquirer/prompts';
import { writeFile } from 'node:fs/promises';
async function main() {
const config = {
name: await input({
message: 'パッケージ名',
validate: (v) =>
/^[a-z0-9-]+$/.test(v) || '小文字英数字とハイフンのみ使用できます',
}),
language: await select({
message: '言語を選択',
choices: [
{ name: 'TypeScript(推奨)', value: 'ts' },
{ name: 'JavaScript', value: 'js' },
],
}),
};
// TypeScriptを選んだときだけ追加で質問する
if (config.language === 'ts') {
config.strict = await confirm({ message: 'strictモードを有効にしますか?' });
}
const path = `./${config.name}.config.json`;
await writeFile(path, JSON.stringify(config, null, 2));
console.log(`✅ ${path} を作成しました`);
}
main().catch((error) => {
// ユーザーがCtrl+Cで中断した場合はエラー扱いにしない
if (error.name === 'ExitPromptError') {
console.log('👋 中断しました');
process.exit(0);
}
throw error;
});
ユーザーがCtrl+Cでプロンプトを中断すると、Promiseが ExitPromptError でrejectされます。これを握りつぶさずに丁寧に処理しておくと、スタックトレースが吐き出される不格好な終了を防げます。
タイムアウト付きの確認プロンプト
デプロイスクリプトなどで「一定時間応答がなければデフォルトで進める」といった制御をしたい場合は、標準の AbortSignal が使えます。
import { confirm } from '@inquirer/prompts';
try {
const proceed = await confirm(
{ message: '本番環境にデプロイしますか?' },
{ signal: AbortSignal.timeout(10_000) }, // 10秒でタイムアウト
);
console.log(proceed ? 'デプロイを開始します' : '中止しました');
} catch (error) {
if (error.name === 'AbortPromptError') {
console.log('時間切れのため中止しました');
}
}
Web標準の AbortSignal をそのまま受け取れる設計になっているのが、モダン化されたInquirer.jsらしいところです。
まとめ
Inquirer.jsの現行パッケージ @inquirer/prompts について、基本から実践的な使い方までを見てきました。
await input(...)のように1問ずつ書けるPromiseベースのAPIで、質問の分岐がただのif文で書ける- input / select / checkbox / confirm など主要な対話パターンが最初から揃っている
ExitPromptErrorのハンドリングとAbortSignal対応で、実戦的なCLIに必要な制御も可能
社内ツールやプロジェクトの雛形生成スクリプトなど、「引数を覚えなくても使えるCLI」が求められる場面は意外と多いものです。まずはこの記事のサンプルを動かして、普段のスクリプトに対話を一問だけ足すところから始めてみてください。
