——————————☆☆☆——————————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/ 處得到。