node编写个cli命令

71 阅读3分钟

需求背景:bff接口服务,定义参数类型以及返回类型。前端ts项目如果能讲定义的类型引用过来,就不需要重复定义类型,而且可以随着接口定义而改动,提高开发效率。

功能:运行该 cli 命令后,会拉取指定的bff项目,然后将克隆下来的项目里一个声明类型的目录拷贝到对应 cli 项目下,最后删除克隆的项目。

1、先编写node脚本

(1)创建文件夹 bff-types-cli。

(2)在改文件夹下创建 index.js。

console.log('bff-types-cli')

(3)此时,运行 node .\index.js。

// bff-types-cli

2、脚本制作cli命令行

(1) 新建一个package.json 文件

npm init -y

(2)添加package.json的bin字段

{
    "main": "index.js",
    "bin":{
        "bff-types-cli":"index.js"
    },
}

(3)index.js文件顶部声明执行环境。

#!/usr/bin/env node
console.log('bff-types-cli')

// 添加 #!/usr/bin/env node 或者 #!/usr/bin/node ,这是告诉系统,下面这个脚本,使用nodejs来执行。

// #!/usr/bin/env node 的意思是让系统自己去找node的执行程序。

// #!/usr/bin/node 的意思是,明确告诉系统,node的执行程序在路径为 /usr/bin/node 。

(4)执行 npm link。

在当前package.json目录下,打开命令行工具,执行 npm link ,将当前的代码在npm全局目录下留个快捷方式。相当于全局安装了 bff-types-cli 这个脚手架了。

(5)在终端执行 bff-types-cli 命令,执行index.js中的脚本。

// bff-types-cli

3、编写查看版本-v指令

(1)安装 commander 库,node.js 命令行界面的完整解决方案。

yarn add commander

(2)编写index.js 文件

console.log('bff-types-cli')
const program = require("commander");
// 参数'-v, --version', 作用: -v --version 指令 替换 -V
program.version(require('./package.json').version, '-v, --version');
program.parse(process.argv);

(3)执行 bff-types-cli -v 或者 bff-types-cli --version 命令。

// bff-types-cli 
// 1.0.0

4、编写 -h 指令的提示信息

(1)执行 bff-types-cli -h 命令

// 下面是默认的提示信息
bff-types-cli 
Usage: index [options]

Options:
  -v, --version  output the version number
  -h, --help     display help for command

(2)添加一个 options 选项 (可供后面定义的指令使用该选项,获取选项的属性 program.xxx )

program.version(require('./package.json').version, '-v, --version');
program.option('-i, --integer <n>', 'An integer argument')

(3)执行 bff-types-cli -h 命令

bff-types-cli
Usage: index [options]

Options:
  -v, --version      output the version number
  -i, --integer <n>  An integer argument
  -h, --help         display help for command

(4)添加 other 提示

program.on('--help', function(){
  console.log('')
  console.log('Other:');
  console.log('  $ bff-types-cli --help');
  console.log('  $ bff-types-cli -h');
  console.log('  $ bff-types-cli -v');
  console.log('  $ bff-types-cli --interger 1');
});

(5)执行 bff-types-cli -h 命令

bff-types-cli
Usage: index [options]

Options:
  -v, --version      output the version number
  -i, --integer <n>  An integer argument
  -h, --help         display help for command

Other:
  $ bff-types-cli --help
  $ bff-types-cli -h
  $ bff-types-cli -v
  $ bff-types-cli --interger 1

5、编写功能

(1)安装依赖包

// chalk 和 ora 需要指定版本。因为需要commonjs的格式
yarn add chalk@4.1.2 ora@5.4.1 clear commander download-git-repo figlet--save-dev

(2)创建文件,bff-types-cli/lib/creatTypeFile.js。

const { promisify } = require("util");
const figlet = promisify(require("figlet"));
const clear = require("clear");
const chalk = promisify(require("chalk"));
const log = (content) => console.log(chalk.green(content));
const ora = require("ora");
const fs = require("fs");
// const fsPromises = require('fs').promises;
const path = require("path");
const download = promisify(require("download-git-repo"));
const projectRelation = require('../util/data');

const cloneProject = async (url, fileName, typesFile) => {
  // 克隆项目
  const process = ora(`下载中...`);
  process.start();
  await download(url, fileName, { clone: true }, async(err) => {
    if(err) {
      process.fail(`可能已经存在,${err}`)
    } else {
      process.succeed('下载成功');
      const typesLoading = ora(`生成types文件中...`);
      typesLoading.start();
      copyDirectory(`${fileName}/src/enum`, typesFile); // 复制类型文件到指定目录
      typesLoading.succeed('types类型目录生成成功!');

      rmDirDeepSync(fileName);
      setTimeout(() => {
        clearFile(fileName);
      }, 500);
    }
  });
};

const clearFile = (fileName) => {
  if(fs.existsSync(fileName)) {
    fs.rmdirSync(fileName, { recursive: true })
  }
}

function rmDirDeepSync(dir) {
  let statObj = fs.statSync(dir);
  if (statObj.isDirectory()) {
      let dirs = fs.readdirSync(dir);
      for(let i = 0; i < dirs.length; i++ ) {
          rmDirDeepSync(path.join(dir, dirs[i]))
      }
      fs.rmdirSync(dir, { recursive: true });
  } else {    
      fs.unlinkSync(dir);
  }
}

function copyDirectory(src, dest) {
  if (fs.existsSync(dest) == false) {
    fs.mkdirSync(dest);
  }
  if (fs.existsSync(src) == false) {
    return false;
  }
  // 拷贝新的内容进去
  let dirs = fs.readdirSync(src);
  dirs.forEach(function(item){
    let item_path = path.join(src, item);
    let temp = fs.statSync(item_path);
    if (temp.isFile()) { // 是文件
      // console.log("Item Is File:" + item);
      fs.copyFileSync(item_path, path.join(dest, item));
      // fsPromises.copyFile(item_path, path.join(dest, item))
    } else if (temp.isDirectory()){ // 是目录
      // console.log("Item Is Directory:" + item);
      copyDirectory(item_path, path.join(dest, item));
    }
  });
}

module.exports = async (name) => {
  // 清楚日志
  clear();
  const data = await figlet("bff-types-cli");
  输出日志
  log(data);
  // 暂时用 bff-marketing-app,后面通过命令传进来
  const bffProjectName = 'bff-marketing-app';
  const typesFile ='bff-types';
  if(!projectRelation[bffProjectName]) {
    console.log('Error: bff项目不存在!')
    return;
  }
  const url = `${projectRelation[bffProjectName].url}#${projectRelation[bffProjectName].branch}`;
  clearFile(bffProjectName); // 删除bff项目
  clearFile(typesFile); // 删除类型文件夹
  cloneProject(url, bffProjectName, typesFile); // 克隆bff项目
};

(3)添加运行的文件

program.action(require("./lib/creatTypeFile")); // 这里执行的文件所在

(4)运行 bff-types-cli