🧭 最小工作流 >>>
实战里可以把 Inquirer 理解成“命令行表单引擎”。典型链路不是把所有类型背下来,而是先定问题流,再给每一步补 `validate`、`when` 和 `choices`。
从 0 到可运行问答流
import inquirer from 'inquirer';
const answers = await inquirer.prompt([
{
type: 'input',
name: 'name',
message: '项目名?',
validate(value) {
return value.trim() ? true : '项目名不能为空';
},
filter(value) {
return value.trim();
},
},
{
type: 'confirm',
name: 'useTypeScript',
message: '启用 TypeScript?',
default: true,
},
{
type: 'list',
name: 'packageManager',
message: '包管理器?',
choices: ['npm', 'pnpm', 'yarn'],
},
]);
console.log(answers);
速查:一条典型设计思路
1. 先用 input / confirm / list 跑通主线
2. 用 validate 保证输入质量
3. 用 filter 做归一化
4. 用 when 把分支题延后
5. 需要兼容旧插件时再考虑 registerPrompt
🍳 高频场景 Recipes >>>
重点不是“有哪些题型”,而是“如何把 CLI 决策流组织得不乱”。下面这几组写法覆盖了脚手架、发布脚本和交互式运维工具里最常见的场景。
Recipe 1:脚手架首屏,先收最少关键信息
const answers = await inquirer.prompt([
{
type: 'input',
name: 'appName',
message: '应用名?',
},
{
type: 'list',
name: 'template',
message: '模板?',
choices: ['web', 'node', 'library'],
},
{
type: 'confirm',
name: 'installDeps',
message: '立即安装依赖?',
default: true,
},
]);
Recipe 2:按前一个答案决定后续问题
const answers = await inquirer.prompt([
{
type: 'confirm',
name: 'needProxy',
message: '需要代理配置?',
default: false,
},
{
type: 'input',
name: 'proxyUrl',
message: '代理地址?',
when(answers) {
return answers.needProxy;
},
validate(value) {
return /^https?:\/\//.test(value) ? true : '请输入 http(s) 地址';
},
},
]);
Recipe 3:列表值和展示名分离,避免业务值污染 UI
const answers = await inquirer.prompt([
{
type: 'list',
name: 'runtime',
message: '运行时?',
choices: [
{ name: 'Node.js 20 LTS', value: 'node20' },
{ name: 'Node.js 22 LTS', value: 'node22' },
{ name: 'Bun', value: 'bun' },
],
},
]);
console.log(answers.runtime);
Recipe 4:checkbox 多选 + 最小数量校验
const answers = await inquirer.prompt([
{
type: 'checkbox',
name: 'features',
message: '启用哪些能力?',
choices: [
{ name: 'ESLint', value: 'eslint', checked: true },
{ name: 'Prettier', value: 'prettier' },
{ name: 'Vitest', value: 'vitest' },
],
validate(value) {
return value.length > 0 ? true : '至少选择一项';
},
},
]);
Recipe 5:传入预填答案,跳过已知问题
const presetAnswers = {
projectName: 'demo-app',
};
const answers = await inquirer.prompt(
[
{
type: 'input',
name: 'projectName',
message: '项目名?',
},
{
type: 'confirm',
name: 'gitInit',
message: '初始化 Git?',
default: true,
},
],
presetAnswers,
);
Recipe 6:为旧生态注册自定义 prompt
import inquirer from 'inquirer';
import searchPrompt from 'inquirer-search-list';
inquirer.registerPrompt('search-list', searchPrompt);
const answers = await inquirer.prompt([
{
type: 'search-list',
name: 'packageName',
message: '搜索依赖',
choices: ['react', 'vue', 'svelte'],
},
]);
Recipe 7:在非 TTY 环境给出清晰兜底
try {
const answers = await inquirer.prompt([
{
type: 'confirm',
name: 'deploy',
message: '继续部署?',
},
]);
console.log(answers.deploy);
} catch (error) {
if (error.isTtyError) {
console.error('当前环境不是交互终端,改走 CI 默认参数');
process.exitCode = 1;
}
throw error;
}
🧰 Quick Ref / 题型与配置速查 >>>
这一段只压高频项。要记住的不是所有字段,而是哪些字段最常组成“输入质量 + 分支流 + 展示层”的组合拳。
高频 prompt 类型
input // 单行文本输入
number // 数值输入
confirm // true / false
list // 单选列表
rawlist // 带索引的列表
expand // 单键快捷选择
checkbox // 多选列表
password // 掩码输入
editor // 打开系统编辑器输入长文本
Question 对象里最常用字段
type // 题型
name // answer key
message // 提示文案
default // 默认值
choices // list / checkbox / expand 的选项
validate // 校验输入,返回 true 或报错文本
filter // 标准化输入后再写入 answers
transformer // 只改显示,不改最终值
when // 条件题
pageSize // 长列表分页尺寸
loop // list / checkbox 是否循环滚动
常抄的 choices 写法
choices: ['npm', 'pnpm', 'yarn']
choices: [
{ name: '正式环境', value: 'prod' },
{ name: '预发环境', value: 'staging' },
]
choices: [
'清缓存',
new inquirer.Separator(),
'重新安装依赖',
]
异步校验 / 异步选项
{
type: 'input',
name: 'repo',
message: '仓库名?',
async validate(value) {
const exists = await checkRepo(value);
return exists ? '仓库已存在' : true;
},
}
{
type: 'list',
name: 'branch',
message: '分支?',
async choices() {
return await loadBranches();
},
}
⚠️ 迁移与常见坑 >>>
这部分是这次更新最重要的差异点。旧 cheatsheet 容易把所有第三方 prompt 都算成 Inquirer 内建能力,但新版官方心智已经变了:`inquirer` 是 legacy 主包,现代写法很多时候应转向 `@inquirer/prompts`。
什么时候该转 @inquirer/prompts
// 新项目更倾向这种“按需导入单个 prompt”的风格
import { input, confirm, select } from '@inquirer/prompts';
const projectName = await input({
message: '项目名?',
});
const useTS = await confirm({
message: '启用 TypeScript?',
default: true,
});
const runtime = await select({
message: '运行时?',
choices: [
{ name: 'Node.js', value: 'node' },
{ name: 'Bun', value: 'bun' },
],
});
这次重构里明确去掉的旧认知
// 这些不要再当成“内建 prompt 类型”写进速查主线
search-list // 常见于第三方插件,不是内建
autocomplete // 多来自插件生态
// 这些接口仍可用,但不该当成首推新写法
inquirer.ui.BottomBar
Reactive / RxJS 扩展流
createPromptModule + 旧式事件流
决策速记
旧项目延续 + 兼容插件 -> inquirer
新项目 + 现代 API -> @inquirer/prompts
要保留 question[] 声明式流 -> inquirer
要最小依赖和最直观单题调用 -> @inquirer/prompts