如何花十分钟搭建自己的 cli 玩具

576 阅读3分钟

背景

最近来了几个新同事,由于没有及时沟通,导致好几个同事在开发新项目的时候自己去搭建或者网上找了模板,去开发新的项目。然后我就想能不能像 vue-cli 一样 通过 vue create xxx 一样自动生成一个项目模版

思考

针对现有的业务例如我们有 钉钉 h5、pc、electorn 等等, 如何用一套开发环境感觉太冗余了,所以大概分三个模版。然后模版维护在哪也是一个问题,现在目前有两种方案第一种像 vue 一样维护到远端,每次 create 的时候去拉最新的代码生成模版。第二种就是维护到自己的工程下面,但是这样每次改动都要发新版本太过麻烦。所以思来想去维护到远端,我这里是维护到公司的 giatlab 上面。

预期

  1. npm i my-cli -g
  2. my-cli init myapp 将展示如下选项

image.png

  1. 🌰如我们选择 spa 项目以后会填一些基本信息,然后去执行下载操作

image.png
4. 然后根据所填信息针对模版开启一些配置,写入一些信息在这里我们会将这两项写入 package.json 里面

  1. 最终结果如下
    image.png

同时在当前目录下生成一个叫 myapp 的模版

image.png

准备一些库

chalk --node终端样式库

const chalk = require('chalk');
// 输出蓝色的MCC
console.log(chalk.blue('MCC'));

image.png

commander --node.js命令行界面的方案

var program = require('commander');

program
  .version('0.1.0')

inquirer.js --用户与命令行交互的工具

const promptList = [{
    type: 'input',
    message: '设置一个用户名:',
    name: 'name',
    default: "test_user" // 默认值
},{
    type: 'input',
    message: '请输入手机号:',
    name: 'phone',
    validate: function(val) {
        if(val.match(/\d{11}/g)) { // 校验位数
            return val;
        }
        return "请输入11位数字";
    }
}];

image.png

fs-extra 文件操作相关工具库

fs-extra模块是系统fs模块的扩展,提供了更多便利的 API,并继承了fs模块的 API

var fs = require('fs-extra');
fs.copy('/tmp/myfile', '/tmp/mynewfile', function (err) {
   if (err) return console.error(err); 

ora 终端 loading 库

image.png

figlet 输出一些特殊的文字,这些文字只包含 ANSI 对应的字符

思路

image.png

实现

#!/usr/bin/env node
const fs = require("fs");
const { program } = require("commander");
const inquirer = require("inquirer");
const ora = require("ora");
const chalk = require("chalk");
const symbols = require("log-symbols");
const figlet = require("figlet");
const sh = require("../src/shell");
const config = require("../src/config");
const getRepoName = require("../src/getRepoName");
const buildProject = require("../src/buildProject");
const fileName = process.argv[3];
const filePath = process.cwd();
console.log(fileName, filePath, "aa");
program
    .version("0.0.1", "-v, --version")
    .command("init <name>")
    .action((name) => {
        if (!fs.existsSync(name)) {
            inquirer
                .prompt([config.projectList, config.description, config.author])
                .then(async (answers) => {
                    let spinner = ora("正在生成项目中...");
                    spinner.start();
                    try {
                        //获取要下载的仓库地址
                        const url = config.repo[answers.projectType];
                        if (!url) {
                            spinner.fail();
                            return console.log(
                                symbols.error,
                                chalk.green("模版正在开发中--")
                            );
                        }
                        const repoName = getRepoName(url);
                        // 获取下载下来的仓库名字
                        spinner.text = "正在下载新的的模版";
                        // 下载之前无脑删除
                        await sh(`rm -rf ${repoName}`);
                        // 然后开始下载模版
                        await sh("git clone " + url);
                        // 开始构建新的目录
                        buildProject({
                            ...answers,
                            fileName,
                            filePath,
                            repoName,
                        });
                        // 正在生成项目
                        spinner.text = "生成完成";
                        spinner.succeed();
                        // 生成图标
                        figlet("REACT CLI", function (err, data) {
                            if (err) {
                                console.log("Something went wrong...");
                                console.dir(err);
                                return;
                            }
                            console.log(data);
                        });
                        // 转移文件夹
                    } catch (error) {
                        spinner.fail();
                        console.log(symbols.error, chalk.red(error));
                    }
                });
        } else {
            // 错误提示项目已存在,避免覆盖原有项目
            console.log(symbols.error, chalk.red("项目已存在"));
        }
    });
program.parse(process.argv);

第一行一定要加这句话

#!/usr/bin/env node

/usr/bin/env就是告诉系统可以在PATH目录中查找。 所以配置#!/usr/bin/env node, 就是解决了不同的用户node路径不同的问题,可以让系统动态的去查找node来执行你的脚本文件

目录

image.png

核心文件放在 bin 下面, 同时在 package.json 里面加上如下的命令

"bin": {

"my-cli": "bin/index.js"

}

最后

代码放在这里