准备工作

1. 创建工程

mkdir my-cli
cd my-cli
npm init -y
npm i commander download-git-repo ora handlebars figlet clear chalk open watch -S
复制代码

2. 创建 bin 目录

  • package.json 添加 bin 字段,将自定义的命令软连到全局环境,同时执行 npm link 创建链接。

 "bin": {
   "wbiao": "./bin//index.js"
 }
复制代码
  • 在根目录下创建 bin 文件夹,新建 index.js 文件,行首增加一行#!/usr/bin/env node指定当前脚本由 node.js 进行解析。

#!/usr/bin/env node

console.log("Hello World!!!");
复制代码
  • 命令行执行npm link,相当于把这个包连接到全局。

如果 npm link 报错,windows/mac 尝试使用管理员身份输入。

  • 在命令行执行wbiao 教你用 Node 创建 CLI 工具_java

2. 搭建命令行界面

插件名称说明
commander一个解析命令行命令和参数工具
inquirer常用交互式命令行用户界面的集合
fuzzy字符串模糊匹配的插件,根据输入关键词进行模糊匹配
json-formatjson 美化/格式化工具

识别命令行 - commander

command 后面可跟一个 name,用空格隔开。

  • alias: 定义一个更短的命令行指令

  • description:描述,他在 help 里展示

  • action:注册一个回调函数

  • parse:解析命令行

const program = require("commander");
program.version(require("../package.json").version);

program
 .command("init <name>")
 .alias("i")
 .description("init project")
 .action((name) => {
   console.log(`项目名称为:${name}`);
 });

// 解析主进程的参数
program.parse(process.argv);
复制代码

输出欢迎界面 - figlet/clear/chalk

输入 wbiao init 就会输出以下欢迎界面,那么需要什么工具? 教你用 Node 创建 CLI 工具_java_02

工具:

  • figlet: 创建字符图案

  • clear: 清屏

  • chalk: 改变 log 颜色的工具

做法:

  • 新建文件夹 lib,新建 init.js

const { promisify } = require("util");

const figlet = promisify(require("figlet")); // 字符画
const clear = require("clear"); // 清屏
const chalk = require("chalk"); // 改变输出log颜色的工具

const log = (content) => console.log(chalk.green(content)); // 封装console

module.exports = async (name) => {
 clear();
 // 输出欢迎界面
 const figlet_data = await figlet("WBIAO");
 log(figlet_data);
};
复制代码

加载进度 - ora

async function clone(repo, desc) {
 const download = promisify(require("download-git-repo")); // 从git下载仓库
 const ora = require("ora"); // 命令行显示加载效果
 const process = ora(`downing ${repo}....`);
 process.start();
 await download(repo, desc);
 process.succeed();
}
复制代码

从 git 下载仓库 - download-git-repo / ora

download-git-repo 有一个 clone 方法,用法如下:

clone(repository, destination, options, callback);
复制代码
参数说明备注
repositorygithub 库地址GitHub - github:owner/name 或者简写为 owner/nameGitLab - gitlab:owner/name
destination目标文件夹-

在下载仓库的过程中需要添加加载的过程,可以使用 ora 库。

const ora = require("ora");
const spinner = ora("Loading unicorns").start();
setTimeout(() => {
 spinner.color = "yellow";
 spinner.text = "Loading rainbows";
}, 1000);
复制代码

显示的效果是: 教你用 Node 创建 CLI 工具_java_03

此时我们封装一个 download 方法,用作下载仓库并显示进度

async function clone(repo, desc) {
 const download = promisify(require("download-git-repo")); // 从git下载仓库
 const ora = require("ora"); // 命令行显示加载效果
 const process = ora(`downloading ${repo}....`);
 process.start();
 await download(repo, desc);
 process.succeed();
}
复制代码

在 init 里引入

module.exports = async (name) => {
 ...

 // 2. 下载项目
 log("创建项目:" + name);
 await clone("github:hopkinson/vue-mock-demo", name);
};
复制代码

选取模板下载仓库 - inquirer

我们如果有多个模板的情况下需要选择一个,那就不能写固定下载地址,而是根据需要选择模板。那么可以用 inquirer 库。

基本用法:

const inquirer = require("inquirer");
const promptList = [
 {
   type: "list",
   name: "type",
   message: "请选择拉取的模板类型: ",
   choices: [
     {
       name: "pc",
       value: {
         url: "hopkinson/vue-mock-demo",
         gitName: "vue-mock-demo",
         val: "PC端模版",
       },
     },
   ],
 },
];

inquirer.prompt(promptList).then((answers) => {
 console.log(answers); // 返回的结果
});
复制代码

在脚手架里,在 init 里引入选择模板并下载:

var promptList = [
 {
   type: "list",
   name: "type",
   message: "请选择拉取的模板类型: ",
   choices: [
     {
       name: "pc",
       value: {
         url: "hopkinson/vue-mock-demo",
         gitName: "vue-mock-demo",
         val: "PC端模版",
       },
     },
     {
       name: "mobile",
       value: {
         url: "littleTreeme/vue-web-template.git",
         gitName: "vue-web-template",
         val: "PC端模版",
       },
     },
   ],
 },
];

inquirer.prompt(promptList).then(async (result) => {
 const { url, gitName, val } = result.type;
 await clone(`github:${url}`, name);
 log(`已下载${gitName}${val})项目,正在安装依赖...`);
});
复制代码

3. 安装依赖 - spawn

封装 spawn 函数,通过子线程来做我们的安装依赖;

const spawn = async (...args) => {
 const { spawn } = require("child_process"); //原生包的子进程
 return new Promise((resolve) => {
   const proc = spawn(...args);
   proc.stdout.pipe(process.stdout); //子进程的输出流与主进程相对接  为了打印子进程的日志
   proc.stderr.pipe(process.stderr); //错误流
   proc.on("close", () => {
     resolve();
   });
 });
};
复制代码

然后再引入到 init 里。

注意的是:window10 中,在 spawn 中执行 npm 报错 [Error: spawn ENOENT]” errors。解决方法是:加入的判断process.platform === "win32" ? "npm.cmd" : "npm"

module.exports = async (name) => {
 ...
// 3. 自动安装依赖
 //npm:要执行的命令,可以是cnpm...
 //  * []:所有参数放数组中
 //  * cwd: 在哪个目录下执行命令
 spawn(process.platform === "win32" ? "npm.cmd" : "npm", ["install"], { cwd: `./${name}` });
  log(`
   安装完成:
   ==============
   cd ${name}
   npm run dev
   ==============
   `
);
};

复制代码

4. 自动启动界面 -open

  • open: 打开浏览器

module.exports = async (name) => {
   ...
 // 4.打开浏览器
 await spawn(
   process.platform === "win32" ? "npm.cmd" : "npm",
   ["run", "dev"],
   { cwd: `./${name}` }
 );
 open("http://localhost:8080");
};
复制代码