工程化实践:设计 CLI 交互 Prompts
概述
将 CLI 工具从固定逻辑升级为动态交互式工具。通过命令行交互界面(而非命令行参数)引导用户输入项目名称、选择模板类型等配置信息。本节选用轻量的 prompts 库(与 create-vue 官方一致),介绍其核心用法和与 inquirer 的选型对比。
交互库选型对比
| 维度 | prompts | inquirer |
|---|---|---|
| 包体积 | ~10KB | ~100KB+ |
| 功能丰富度 | 基础够用 | 非常全面 |
| 依赖数量 | 极少 | 较多 |
| 适用场景 | 轻量 CLI 工具 | 复杂表单式交互 |
| 官方采用 | create-vue 使用 | create-react-app 使用 |
| 安装速度 | 快 | 相对慢 |
选型建议:轻量 CLI 工具选 prompts,复杂交互(如条件分支、嵌套表单)选 inquirer。
prompts 核心用法
支持的交互类型
| 类型 | 说明 | 返回值 |
|---|---|---|
text | 文本输入 | string |
number | 数值输入 | number |
confirm | 确认选择 | boolean |
select | 单选列表 | string |
multiselect | 多选列表 | string[] |
autocomplete | 自动补全选择 | string |
password | 密码输入(掩码) | string |
toggle | 开关切换 | boolean |
基础示例
// src/prompts.ts
import prompts from 'prompts'
interface ProjectOptions {
projectName: string
template: string
features: string[]
useTypescript: boolean
}
export async function promptProjectOptions(): Promise<ProjectOptions> {
const questions: prompts.PromptObject[] = [
{
type: 'text',
name: 'projectName',
message: '项目名称:',
initial: 'my-vue-app',
validate: (value: string) =>
value.length > 0 ? true : '项目名称不能为空'
},
{
type: 'select',
name: 'template',
message: '选择模板类型:',
choices: [
{ title: '基础模板 (Base)', value: 'base', description: 'Vue 3 + Vite 基础项目' },
{ title: 'CDN 模板', value: 'cdn', description: 'CDN 加载核心库' },
{ title: '完整模板 (Full)', value: 'full', description: '包含路由、状态管理、组件库' }
],
initial: 0
},
{
type: 'multiselect',
name: 'features',
message: '选择需要的功能:',
choices: [
{ title: 'Vue Router', value: 'router', selected: true },
{ title: 'Pinia', value: 'pinia', selected: true },
{ title: 'Element Plus', value: 'element-plus' },
{ title: 'UnoCSS', value: 'unocss' },
{ title: 'i18n 国际化', value: 'i18n' },
{ title: 'PWA 支持', value: 'pwa' }
],
hint: '- 空格选择,回车确认'
},
{
type: 'toggle',
name: 'useTypescript',
message: '使用 TypeScript?',
initial: true,
active: 'Yes',
inactive: 'No'
}
]
const response = await prompts(questions, {
onCancel: () => {
console.log('操作已取消')
process.exit(0)
}
})
return response as ProjectOptions
}
typescript
自动补全选择
{
type: 'autocomplete',
name: 'packageManager',
message: '选择包管理器:',
choices: [
{ title: 'pnpm (推荐)', value: 'pnpm' },
{ title: 'npm', value: 'npm' },
{ title: 'yarn', value: 'yarn' },
{ title: 'bun', value: 'bun' }
]
}
typescript
条件式提问(基于前一个回答)
const questions: prompts.PromptObject[] = [
{
type: 'select',
name: 'template',
message: '选择模板:',
choices: [
{ title: 'Vue 3', value: 'vue' },
{ title: 'React', value: 'react' }
]
},
// 仅当选择 Vue 3 时才显示此问题
{
type: (prev) => prev === 'vue' ? 'select' : null,
name: 'vueVersion',
message: '选择 Vue 版本:',
choices: [
{ title: 'Vue 3 (推荐)', value: 'vue3' },
{ title: 'Vue 2', value: 'vue2' }
]
}
]
typescript
测试支持(prompts.inject)
import prompts from 'prompts'
// 注入预设回答,跳过交互(用于测试和 CI)
prompts.inject(['my-project', 'base', ['router', 'pinia'], true])
const response = await prompts(questions)
// => { projectName: 'my-project', template: 'base', features: ['router', 'pinia'], useTypescript: true }
typescript
集成到 CLI 主流程
// index.ts
#!/usr/bin/env node
import { promptProjectOptions } from './prompts'
import { createTemplate } from './core'
async function main(): Promise<void> {
console.log('🚀 Vue Template Creator\n')
// 1. 交互式收集用户输入
const options = await promptProjectOptions()
console.log('\n📦 正在创建项目...')
console.log(` 名称: ${options.projectName}`)
console.log(` 模板: ${options.template}`)
console.log(` 功能: ${options.features.join(', ')}`)
// 2. 根据用户选择创建项目
await createTemplate(options)
console.log('\n✅ 项目创建成功!')
}
main().catch((err) => {
console.error('❌ 创建失败:', err.message)
process.exit(1)
})
typescript
命令行参数解析库对比
| 库 | 特点 | 适用场景 |
|---|---|---|
prompts | 轻量交互提示 | 项目脚手架 |
inquirer | 功能全面 | 复杂表单交互 |
commander | 命令/参数解析 | 多命令 CLI |
yargs | 全功能参数解析 | 复杂 CLI 应用 |
minimist | 极简参数解析 | 简单参数需求 |
实践要点
prompts通过数组配置多个问题,每个问题通过type指定交互类型name字段是后续获取用户回答的 key,返回值为{ [name]: answer }结构- 使用
validate函数校验输入,返回true通过,返回字符串显示错误信息 type可以是函数,根据前一个回答动态决定是否显示(返回null跳过)prompts.inject()用于 CI/CD 和单元测试场景,预设回答跳过交互- 设置
onCancel回调处理用户按 Ctrl+C 取消操作的场景
↑