头图

——————————☆☆☆——————————

Corresponding address of Node series:

——————————☆☆☆——————————

I. Introduction

After the establishment of the previous TypeScript environment and commander.js , we can now write the corresponding instructions .ts npm run xxx , but this method has a bug:

  • When there are too many instructions, we simply cannot remember so many instructions!

Therefore, an intelligent prompt is needed to simplify and visualize the instructions.

Two integrated Inquirer.js

Here jsliang thinking of a way to solve this problem through the question and answer form of the terminal (the follow-up may arrange a page or a Chrome plug-in, etc.)

So, stop talking nonsense, Here we go~

first , install the necessary packages:

  • Install Inquirer.js : npm i inquirer
  • Install @types/inquirer (optional, TS must be installed): npm i @types/inquirer -D

and then . We can start playing around, access the previous TypeScript and commander.js , picking up index.ts and package.json is just a modification:

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);

Note here that sort changed to jsliang (shameless people are invincible in the world).

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"
  }
}

So there is an effect:

Inquirer-01.png

The same silky smooth and easy to use, you can also control the folder path~

but! Friends seeing the above code, do you have a feeling of vomiting?

  • Question 1: Yeah, what is this? What functions did you write in these codes?
  • Question 2: Isn’t it disgusting, it doesn’t support async/await ?

OK, solve the problems one by one, let's first explain some operations in Inquirer.js

Three Inquirer.js use skills

In the above code, .prompt(Array<Object>) , and then the answer can be obtained through the callback, for example an input box:

inquirer.prompt([
  { 
    type: 'input',
    name: 'question',
    message: '请问需要什么服务?',
  }
]).then((res) => {
  console.log('成功!', res);
}).catch((err) => {
  console.error('报错!', err);
});

Among them, Object can be plugged in:

  • type : String [] prompt type, default input , contains input , number , confirm , list , rawlist , expand , checkbox , password , editor
  • name : [String] The variable that stores the answer to the current question
  • message : [String|Function] Question content
  • default : [String|Number|Boolean|Array|Function] default value
  • choices : [Array|Function] list options
  • validate : [Function] verification method, check whether the input value is feasible, valid return true , otherwise return a string to indicate error information (return false is the default error information)
  • filter : [Function] Filter the answer and return the processed value
  • transformer : [Function] The display effect of the operation answer
  • when : [Function|Boolean] Accept the answer, and judge whether the question needs to be displayed based on the previous content
  • pageSize : [Number] In multiple options such as list , rawlist , expand , checkbox
  • prefix : [String] modify the default prefix
  • suffix : [String] modify the default suffix
  • askAnswered : [Boolean] Is the question mandatory for the existing answer?
  • loop : [Boolean] list can scroll through the selection, the default is true

Believe that you can't understand it, let's write down some use cases that may be used.

The follow-up code is abbreviated, the whole written is probably as shown in the following code, and there will be no trembling later
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);
note:
① The following examples, you can also find them in Inquires.js , but jsliang hope to carry them to this article to facilitate subsequent retrieval.
② If there is a comment that does not see this comment, spit copy the README, then jsliang has nothing to say, just be spit out several times, write a little comment

3.1 Input box

input text :

Inquirer-02.png

Available parameters: type, name, message[, default, filter, validate, transformer]

inquirer.prompt([
  { 
    type: 'input',
    name: 'question',
    message: '问题?',
    default: 'liangjunrong',
  }
]);

enter the number :

Inquirer-03.png

Available parameters: type, name, message[, default, filter, validate, transformer]

inquirer.prompt([
  { 
    type: 'number',
    name: 'question',
    message: '问题?',
    default: '1',
  }
]);

enter the password :

Inquirer-04.png

Available parameters: type, name, message, mask,[, default, filter, validate]

inquirer.prompt([
  { 
    type: 'password',
    name: 'question',
    message: '问题?',
  }
]);

3.2 Single option

Single option without subscript :

Inquirer-05.png

Available parameters: type, name, message, choices[, default, filter, loop]

inquirer.prompt([
  { 
    type: 'list',
    name: 'question',
    message: '问题?',
    default: 'jsliang',
    choices: ['liangjunrong', 'jsliang']
  }
]);

add separator :

Inquirer-06.png

inquirer.prompt([
  { 
    type: 'list',
    name: 'question',
    message: '问题?',
    default: 'jsliang',
    choices: [
      'liangjunrong',
      new inquirer.Separator(), // 添加分隔符
      'jsliang',
    ]
  }
]);

Single option with :

Inquirer-07.png

Available parameters: type, name, message, choices[, default, filter, loop]

inquirer.prompt([
  { 
    type: 'rawlist',
    name: 'question',
    message: '问题?',
    default: 'jsliang',
    choices: ['liangjunrong', 'jsliang']
  }
]);

3.3 Multiple options

Inquirer-08.png

Available parameters: type, name, message, choices[, filter, validate, default, loop]

inquirer.prompt([
  { 
    type: 'checkbox',
    name: 'question',
    message: '问题?',
    choices: ['liangjunrong', 'jsliang']
  }
]);

3.4 Confirmation box

Inquirer-09.png

Available parameters: type, name, message, [default]

inquirer.prompt([
  { 
    type: 'confirm',
    name: 'question',
    message: '问题?',
  }
]);

3.5 Check input

Inquirer-10.png

inquirer.prompt([
  { 
    type: 'input',
    name: 'phone',
    message: '请输入手机号',
    validate: (val) => {
      if (val.match(/\d{11}/g)) {
        return true;
      }
      return '请输入 11 位数字';
    },
  }
]);

Four dynamic questions

We mentioned 2 questions above:

  • Question 1: Yeah, what is this? What functions did you write in these codes?
  • Question 2: Isn’t it disgusting, it doesn’t support async/await ?

Problem 1 has been solved just now (this Inquires.js function), let's see how to operate problem 2.

In fact, in order to solve this problem, we need to follow Inquires.js installation recommended Rx.js , Rx.js References:

start installation:

  • Install rxjs : npm i rxjs@5

The current version is v7.1.0 , but looked under Inquirer.js example is v5.x version, looking for a usage will not find the new version, this can only be the next move

Secondly, jsliang is really lazy. I don’t want to know what Rx.js does. I just hope that the project can async/await way.

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);

This completes the encapsulation, making it easier to process information. (It is conceivable that there will be a pile of switch...case... judgments behind)

However, unexpectedly, after multiple modules were connected to Inquire.js , there was a problem.

Examples of multiple modules
+ src
  - index.ts
  + base
    - config.ts
  + common
    - inquirer.ts
  + jsliang
    - inquirer.ts

There is no need to change the interface according to this catalog for the time being, the following one shall prevail

Personally suspect that Rx.js is a single instance

Error prompt during runtime:

Inquirer-11.png

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

After investigating for a long time, it should be related to my unfamiliarity with RX.js, so I wondered if I could update it:

[Standard] Change the folder/file according to this directory
+ 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 ———————————————— 业务功能文件

By the way, give a catalog map:

Inquirer-12.png

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,
}

After setting it up like this, you can play happily in other places:

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;

Pass the problem array, and then the callback function will process the content to meet my current needs, so we won’t remodel it anymore.

The contents of other detailed documents are as follows:

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;
};

Then, Inquirer.js access is done, try our npm run jsliang , it can be used normally!

You can write functions happily later~

Five references


jsliang's document library is by 160d1756015ad4 Liang Junrong with Creative Commons Attribution-Non-Commercial Use-Same Way Sharing 4.0 International License Agreement . <br/>Based on the works https://github.com/LiangJunrong/document-library <br/> usage rights other than this license authorization from https://creativecommons.org/licenses/by-nc-sa/2.5/cn/ obtained at.

jsliang
393 声望32 粉丝

一个充满探索欲,喜欢折腾,乐于扩展自己知识面的终身学习斜杠程序员