项目创建准备阶段——判断当前目录是否为空功能开发
// commands/init/lib/index.js 文件部分内容class InitCommand extends Command {......async exec() {try {// 1. 准备阶段this.prepare();// 2. 下载模板// 3. 安装模板} catch (e) {log.error(e.message);}}......async prepare() {// 1. 判断当前目录是否为空if (!this.isCwdEmpty()) {// 1.1 询问是否继续创建}// 2. 是否启动强制更新// 3. 选择创建项目或组件// 4. 获取项目的基本信息}isCwdEmpty() {// 获取当前文件目录// console.log(path.resolve('.'))const localPath = process.cwd();let fileList = fs.readdirSync(localPath);// 文件过滤的逻辑fileList = fileList.filter(file => (!file.startsWith('.') && ['node_modules'].indexOf(file) < 0));return !fileList || fileList.length <= 0;}
}
inquirer 基本用法和常用属性入门
作用:命令行交互库
npm install --save inquirer@^8.0.0
const inquirer = require('inquirer');inquirer.prompt([/* Pass your questions in here *//* 参考文档 Objects Question A question object is a hash containing question related values: */{type: 'input',name: 'yourName',message: 'your name:',default: 'noname',validate: function(v) {return v === 'sam'},transformer: function(v) { //作补充信息的,备注说明,不会影响最终结果。return 'name:' + v},filter: function(v) { // 会改变最终结果。console.log(v);}},{ ...... }]).then((answers) => {// Use user feedback for... whatever!!console.log(answers.yourName)}).catch((error) => {if (error.isTtyError) {// Prompt couldn't be rendered in the current environment} else {// Something else went wrong}});
inquirer 其他交互形式演示
type: 'confirm' 二选一 Y/N
type: 'list' 需要提供 choices: [{value: xx, name: xx}]
type: 'rawlist' 和上面类似
type: 'expand' 需要提供 choices: [{value: xx, key: xx}],会显示key的首字母拼接如Rgbh
type: 'checkbox' 需要提供 choices: [{value: xx, name: xx}],会有多选功能
type: 'number' 非数字会返回 NaN
type: 'password'
type: 'editor' 和vim效果类似
强制清空当前目录功能开发
lerna add inquirer commands/init/lerna add fs-extra commands/init/# core/cli 引用了 commands/init/,commands/init/加入新包时,core/cli 要 npm link
// commands/init/lib/index.js 文件内容//命令行中 --force,有两层含义。一:强制清空目录后安装,二:有文件时,直接安装模版async exec() {try {// 1. 准备阶段const ret = await this.prepare();if (ret) {// 2. 下载模板// 3. 安装模板}} catch (e) {log.error(e.message);}}async prepare() {// 1. 判断当前目录是否为空const localPath = process.cwd();if (!this.isDirEmpty(localPath)) {let ifContinue = false;if (!this.force) {// 询问是否继续创建ifContinue = (await inquirer.prompt({type: 'confirm',name: 'ifContinue',default: false,message: '当前文件夹不为空,是否继续创建项目?',})).ifContinue;if (!ifContinue) {return;}}// 是否启动强制更新if (ifContinue || this.force) {// 给用户做二次确认const { confirmDelete } = await inquirer.prompt({type: 'confirm',name: 'confirmDelete',default: false,message: '是否确认清空当前目录下的文件?',});if (confirmDelete) {// 清空当前目录// fse.removeSync()// 不会把当前目录删除fse.emptyDirSync(localPath)}}}}
获取项目基本信息功能开发
项目名称和版本号合法性校验
lerna add semver commands/init/
async getProjectInfo() {function isValidName(v) {return /^[a-zA-Z]+([-][a-zA-Z][a-zA-Z0-9]*|[_][a-zA-Z][a-zA-Z0-9]*|[a-zA-Z0-9])*$/.test(v);}const TYPE_PROJECT = 'project'const TYPE_COMPONENT = 'component'let projectInfo = {};// 1. 选择创建项目或组件const { type } = await inquirer.prompt({type: 'list',name: 'type',message: '请选择初始化类型',default: TYPE_PROJECT,choices: [{name: '项目',value: TYPE_PROJECT,}, {name: '组件',value: TYPE_COMPONENT,}],});log.verbose('type', type);if (type === TYPE_PROJECT) {const o = await inquirer.prompt([{type: 'input',name: 'projectName',message: `请输入项目名称`,default: '',validate: function(v) {// 根据文档,可对用户输入做提示const done = this.async();setTimeout(function() {// 1.首字符必须为英文字符// 2.尾字符必须为英文或数字,不能为字符// 3.字符仅允许"-_"if (!isValidName(v)) {done(`请输入合法的项目名称`);return;}done(null, true);}, 0);},filter: function(v) {return v;},},{type: 'input',name: 'projectVersion',message: `请输入项目版本号`,default: '1.0.0',validate: function(v) {// 根据文档,可对用户输入做提示const done = this.async();setTimeout(function() {// semver.valid() 可以format版本号,不规范的返回nullif (!(!!semver.valid(v))) {done('请输入合法的版本号');return;}done(null, true);}, 0);},filter: function(v) {if (!!semver.valid(v)) {return semver.valid(v);} else {return v;}},}]);} else if (type === TYPE_COMPONENT) {}}