——————————☆☆☆——————————html
Node 系列相应地址:前端
——————————☆☆☆——————————node
通过前面 TypeScript 环境的搭建和 commander.js
的配合,咱们如今能够在 .ts
文件中编写对应指令,而后经过 npm run xxx
来运行项目了,可是这种方式有个 Bug:git
因此,就须要一个智能提示,将指令简化并可视化。github
这边 jsliang 想的一个法子就是经过终端那种问答形式的来解决这个问题(后续可能安排页面或者 Chrome 插件等)typescript
那么,废话少说,Here we go~npm
首先,安装必须的包:json
Inquirer.js
:npm i inquirer
@types/inquirer
(可选,TS 必装):npm i @types/inquirer -D
而后。咱们就能够开始耍起来了,接入前面的 TypeScript 和 commander.js
,拿起 index.ts
和 package.json
就是一顿修改:数组
src/index.ts
import program from 'commander'; import inquirer from 'inquirer'; import { sortCatalog } from './sortCatalog'; program .version('0.0.1') .description('工具库') program .command('jsliang') .description('jsliang 帮助指令') .action(() => { inquirer .prompt([ { type: 'rawlist', name: 'question1', message: '请问须要什么服务?', choices: ['公共服务', '其余'] }, ]) .then((answers) => { if (answers.question1 === '公共服务') { inquirer.prompt([ { type: 'rawlist', name: 'question', message: '当前公共服务有:', choices: ['文件排序'] } ]).then((answers) => { if (answers.question === '文件排序') { inquirer.prompt([ { type: 'input', name: 'question', message: '须要排序的文件夹为?(绝对路径)', default: 'D:/xx', } ]).then(async (answers) => { const result = await sortCatalog(answers.question); if (result) { console.log('排序成功!'); } }).catch((error) => { console.error('出错啦!', error); }); } }).catch((error) => { console.error('出错啦!', error); }); } else if (answers === '其余') { // 作其余事情 } }).catch((error) => { console.error('出错啦!', error); }); }); program.parse(process.argv);
注意这里 sort
改为 jsliang
了(人不要脸天下无敌)。async
package.json
{ "name": "jsliang", "version": "1.0.0", "description": "Fe-util, Node 工具库", "main": "index.js", "scripts": { "jsliang": "ts-node ./src/index.ts jsliang" }, "keywords": [ "jsliang", "Node 工具库", "Node" ], "author": "jsliang", "license": "ISC", "devDependencies": { "@types/inquirer": "^7.3.1", "@types/node": "^15.12.2", "@typescript-eslint/eslint-plugin": "^4.26.1", "@typescript-eslint/parser": "^4.26.1", "eslint": "^7.28.0", "ts-node": "^10.0.0", "typescript": "^4.3.2" }, "dependencies": { "commander": "^7.2.0", "inquirer": "^8.1.0" } }
因而就有了效果:
同样的丝滑好用,还能够控制文件夹路径了~
可是!小伙伴们看到上面代码,是否是有种想吐的感受。
async/await
?OK,一一解决问题,我们先讲解下 Inquirer.js
里面的一些操做。
在上面的代码中,经过 .prompt(Array<Object>)
能够传递多个问题信息,而后经过回调获取答案,举例一个输入框:
inquirer.prompt([ { type: 'input', name: 'question', message: '请问须要什么服务?', } ]).then((res) => { console.log('成功!', res); }).catch((err) => { console.error('报错!', err); });
其中 Object
里面能够塞:
type
:【String】提示的类型,默认 input
,包含 input
、number
、confirm
、list
、rawlist
、expand
、checkbox
、password
、editor
name
:【String】存储当前问题回答的变量message
:【String|Function】提问的问题内容default
:【String|Number|Boolean|Array|Function】默认值choices
:【Array|Function】列表选项validate
:【Function】验证方法,校验输入值是否可行,有效返回 true
,不然返回字符串表示错误信息(返回 false
则为默认的错误信息)filter
:【Function】对答案进行过滤处理,返回处理后的值transformer
:【Function】操做答案的显示效果when
:【Function|Boolean】接受答案,根据前面的内容判断是否须要展现该问题pageSize
:【Number】在 list
、rawlist
、expand
、checkbox
这种多选项中,进行分页拆分prefix
:【String】修改默认前缀suffix
:【String】修改默认后缀askAnswered
:【Boolean】已有答案是否强制提问loop
:【Boolean】list
是否能循环滚动选择,默认 true
相信你也看不懂,我们将一些可能用到的写一写用例吧。
后续代码为简写,全写大概为下面代码所示,后面就不哆嗦了
import program from 'commander'; import inquirer from 'inquirer'; program .version('0.0.1') .description('工具库') program .command('jsliang') .description('jsliang 帮助指令') .action(() => { inquirer .prompt([ { type: 'rawlist', name: 'question', message: '请问须要什么服务?', choices: ['公共服务', '其余'] }, ]) .then((answers) => { console.log('答案:', answers); }).catch((error) => { console.error('出错啦!', error); }); }); program.parse(process.argv);
注意:
① 下面这些举例,你也能够在Inquires.js
中找到,可是 jsliang 但愿搬运到本身这篇文章中方便后续检索。
② 若是有评论没看到这个注释就吐槽 jsliang 抄写人家 README,那 jsliang 也无话可说,只是被吐槽了几回,稍微写点注释
输入文本:
可配合参数:type, name, message[, default, filter, validate, transformer]
inquirer.prompt([ { type: 'input', name: 'question', message: '问题?', default: 'liangjunrong', } ]);
输入数字:
可配合参数:type, name, message[, default, filter, validate, transformer]
inquirer.prompt([ { type: 'number', name: 'question', message: '问题?', default: '1', } ]);
输入密码:
可配合参数:type, name, message, mask,[, default, filter, validate]
inquirer.prompt([ { type: 'password', name: 'question', message: '问题?', } ]);
没下标的单选项:
可配合参数:type, name, message, choices[, default, filter, loop]
inquirer.prompt([ { type: 'list', name: 'question', message: '问题?', default: 'jsliang', choices: ['liangjunrong', 'jsliang'] } ]);
添加分隔符:
inquirer.prompt([ { type: 'list', name: 'question', message: '问题?', default: 'jsliang', choices: [ 'liangjunrong', new inquirer.Separator(), // 添加分隔符 'jsliang', ] } ]);
有下标的单选项:
可配合参数:type, name, message, choices[, default, filter, loop]
inquirer.prompt([ { type: 'rawlist', name: 'question', message: '问题?', default: 'jsliang', choices: ['liangjunrong', 'jsliang'] } ]);
可配合参数:type, name, message, choices[, filter, validate, default, loop]
inquirer.prompt([ { type: 'checkbox', name: 'question', message: '问题?', choices: ['liangjunrong', 'jsliang'] } ]);
可配合参数:type, name, message, [default]
inquirer.prompt([ { type: 'confirm', name: 'question', message: '问题?', } ]);
inquirer.prompt([ { type: 'input', name: 'phone', message: '请输入手机号', validate: (val) => { if (val.match(/\d{11}/g)) { return true; } return '请输入 11 位数字'; }, } ]);
上面咱们说了 2 个问题:
async/await
?刚才已经将问题 1 解决了(就是这个 Inquires.js
功能支持),下面咱们看看问题 2 怎么操做。
其实为了解决这个问题,咱们须要按照 Inquires.js
中的推荐安装 Rx.js
,Rx.js
参考文献:
开始安装:
rxjs
:npm i rxjs@5
当前版本为
v7.1.0
,可是看了下Inquirer.js
举例的是v5.x
版本,找了一会找不到新版本的用法,只能出此下举其次 jsliang 是真的懒,不想了解
Rx.js
作啥子的,我只但愿项目能按照async/await
方式跑起来
import program from 'commander'; import Rx from 'rxjs/Rx'; import inquirer from 'inquirer'; const prompts = new Rx.Subject(); // 无情的信息处理器 inquirer.prompt(prompts).ui.process.subscribe((result) => { console.log('成功:', result); }, (error: unknown) => { console.error('失败', error); }, () => { console.log('完成'); }); program .version('0.0.1') .description('工具库') program .command('jsliang') .description('jsliang 帮助指令') .action(() => { prompts.next({ type: 'confirm', name: 'question', message: '问题?', }); prompts.complete(); }); program.parse(process.argv);
这样就完成了封装,更方便处理信息了。(能够想象后面会有一堆 switch...case...
判断)
可是,预想不到的是,在多个模块接入 Inquire.js
后,出问题了。
多个模块示例
+ src - index.ts + base - config.ts + common - inquirer.ts + jsliang - inquirer.ts
暂不须要按照这个目录更改接口,如下一个目录为准
我的怀疑
Rx.js
是单实例缘故
运行时报错提示:
npm ERR! code ELIFECYCLE npm ERR! errno 1 npm ERR! jsliang@1.0.0 test: `ts-node ./src/index.ts test` npm ERR! Exit status 1 npm ERR! npm ERR! Failed at the jsliang@1.0.0 test script. npm ERR! This is probably not a problem with npm. There is likely additional logging output above. npm ERR! A complete log of this run can be found in: npm ERR! C:\Users\wps\AppData\Roaming\npm-cache\_logs\2021-06-08T11_46_58_005Z-debug.log
排查了老久,应该跟我不熟悉 RX.js 有关,因此就想着能不能更新一波:
【准】按照这个目录更改文件夹/文件
+ src —————————————————————— src 文件夹 - index.ts ——————————————— 主入口 + base ——————————————————— 基础文件夹,例如 config/math 等 - config.ts ———————————— 经常使用配置项 - inquirer.ts —————————— inquirer 总处理口,统一封装 async/await - interface.ts ————————— 暂时将全部通用的 interface.ts 放到这里 + common ————————————————— 通用功能 - index.ts ————————————— common 处理问题的入口 - sortCatalog.ts —————— inquirer 调用具体的功能文件 + jsliang ———————————————— 业务功能 - xx.ts ———————————————— 业务功能文件
顺带给个目录图吧:
src/base/inquirer.ts
import * as myInquirer from 'inquirer'; import Rx from 'rxjs/Rx'; import { Question } from './interface'; export const inquirer = (questions: Question[], answers: any): void => { const prompts = new Rx.Subject(); // 长度判断 if (questions.length !== answers.length) { console.error('问题和答案长度不一致!'); } // 问题列表 const questionList = questions.map((item, index) => { return () => { prompts.next(Object.assign({}, item, { name: String(index), })); }; }); // 问题处理器 myInquirer.prompt(prompts).ui.process.subscribe(async (res) => { console.log('执行成功,输入信息为:', res); const index = Number(res.name); // 回调函数:结果、问题列表、prompts(控制是否须要中止) answers[index](res, questionList, prompts); // 默认最后一个问题就自动终止 if (index === answers.length - 1) { prompts.complete(); // 回调函数能够手动控制终止询问时机 } }, (error: unknown) => { console.error('执行失败,报错信息为:', error); }, () => { // console.log('完成'); // 一定会执行的代码 }); // 执行第一个问题 questionList[0](); };
src/base/interface.ts
export interface Question { type: string, name?: string, message: string, default?: string, choices?: string[], validate?(): boolean, } export interface Result { name: string, answer: string, }
按照这样子设置后,就能够在其余地方愉快玩耍了:
src/common/index.ts
import { inquirer } from '../base/inquirer'; import { Result } from '../base/interface'; import { sortCatalog } from './sortCatalog'; const common = (): void => { // 测试新特性 const questionList = [ { type: 'list', message: '请问须要什么服务?', choices: ['公共服务', '其余'] }, { type: 'list', message: '当前公共服务有:', choices: ['文件排序'] }, { type: 'input', message: '须要排序的文件夹为?(绝对路径)', default: 'D:/xx', }, ]; const answerList = [ async (result: Result, questions: any) => { if (result.answer === '公共服务') { questions[1](); } else if (result.answer === '其余') { // 作其余事情 console.log('暂未开通该服务'); } }, async (result: Result, questions: any) => { console.log(result); if (result.answer === '文件排序') { questions[2](); } }, async (result: Result) => { const sortResult = await sortCatalog(result.answer); if (sortResult) { console.log('排序成功!'); } }, ]; inquirer(questionList, answerList); }; export default common;
传递问题数组,而后回调函数处理内容,知足我当前的需求,咱就再也不改造了。
其余详细文件内容以下:
src/index.ts
import program from 'commander'; import common from './common'; program .version('0.0.1') .description('工具库') program .command('jsliang') .description('jsliang 帮助指令') .action(() => { common(); }); program.parse(process.argv);
src/base/config.ts
/** * @name 默认的全局配置 * @time 2021-05-22 16:12:21 */ import path from 'path'; // 基础目录 export const BASE_PATH = path.join(__dirname, './docs'); // 忽略目录 export const IGNORE_PATH = [ '.vscode', 'node_modules', ];
src/common/sortCatalog.ts
/** * @name 文件排序功能 * @time 2021-05-22 16:08:06 * @description 规则 1. 系统顺序 1/10/2/21/3,但愿排序 1/2/3/10/21 2. 插入文件 1/2/1-1,但愿排序 1/2/3(将 1-1 变成 2,2 变成 3) */ import fs from 'fs'; import path from 'path'; import { IGNORE_PATH } from '../base/config'; const recursion = (filePath: string, level = 0) => { const files = fs.readdirSync(filePath); files .filter((item => !IGNORE_PATH.includes(item))) // 过滤忽略文件/文件夹 .sort((a, b) => Number((a.split('.')[0]).replace('-', '.')) - Number((b.split('.')[0]).replace('-', '.')) ) // 排序文件夹 .forEach((item, index) => { // 遍历文件夹 // 设置旧文件名称和新文件名称 const oldFileName = item; const newFileName = `${index + 1}.${oldFileName.slice(oldFileName.indexOf('.') + 1)}`; // 设置旧文件路径和新文件路径 const oldPath = `${filePath}/${oldFileName}`; const newPath = `${filePath}/${newFileName}`; // 判断文件格式 const stat = fs.statSync(oldPath); // 判断是文件夹仍是文件 if (stat.isFile()) { fs.renameSync(oldPath, newPath); // 重命名文件 } else if (stat.isDirectory()) { fs.renameSync(oldPath, newPath); // 重命名文件夹 recursion(newPath, level + 1); // 递归文件夹 } }); }; export const sortCatalog = (filePath: string): boolean => { // 绝对路径 if (path.isAbsolute(filePath)) { recursion(filePath); } else { // 相对路径 recursion(path.join(__dirname, filePath)); } return true; };
那么,Inquirer.js
接入就搞定了,试试咱们的 npm run jsliang
,能够正常使用!
后面能够愉快写功能啦~
jsliang 的文档库由 梁峻荣 采用 知识共享 署名-非商业性使用-相同方式共享 4.0 国际 许可协议 进行许可。<br/>基于 https://github.com/LiangJunrong/document-library 上的做品创做。<br/>本许可协议受权以外的使用权限能够从 https://creativecommons.org/licenses/by-nc-sa/2.5/cn/ 处得到。