# 手写一个自己的cli

初始化

npm init -y
1

创建一个bin文件夹,在文件夹里创建一个文件 mycli,没有后缀 mycli

#!/usr/bin/env node
console.log('我是第一个cli');
1
2

在package.json里添加一个bin

{
    ...
    "bin": {
        mycli: "bin/mycli"
    }
    ...
}
1
2
3
4
5
6
7

在本地做一个软链,将这个包放在本地全局安装包的地方

npm link
1

在命令行里输入 mycli ,会输出 "我是第一个cli"

但是这个效果就很普通了 安装两个模块 生成asc码的包

npm install figlet --save-dev
1

做渐变色的包

npm install @darkobits/lolcatjs --save-dev
1
#!/usr/bin/env node
const figlet = require('figlet');
const Printer = require('@darkobits/lolcatjs');
const txt = figlet.textSync('MY CLI') + "\n" + "我的脚手架";
console.log(Printer.default.fromString(txt));
1
2
3
4
5

输入mycli 可以看到控制台输出了渐变色的 MY CLI

mycli

添加查看版本,输入 mycli 输出 版本号(刚刚输入的'MY CLI')

#!/usr/bin/env node
const figlet = require('figlet');
const Printer = require('@darkobits/lolcatjs');
// 变色
const txt = figlet.textSync('MY CLI');
// 一个写cli很重要的库, 可以跟用户交互
const program = require('commander');
program.version(Printer.default.fromString(txt), "-v,--version");
program.parse(process.argv);
1
2
3
4
5
6
7
8
9

接下来要跟用户进行交互

const bindHandler = {
    init(params) {
        console.log(params)
    }
}
// 跟用户交互
program.usage("<cmd> [env]")
    .arguments('<cmd> [env]')
    .action(function (cmd, otherParams) {
        // 输出用户输入的内容 cmd 是用户输入的第一个参数 otherParams 是第二个参数
        const handler = bindHandler[cmd];
        if (handler) {
            handler(otherParams);
        } else {
            console.log('暂未实现' + cmd)
        }
    })
program.parse(process.argv);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

输入 mycli init xxx 输出 xxx

安装 inquirer 进行与用户的交互

npm install inquirer --save-dev
1
const inquirer = require('inquirer');
const bindHandler = {
    init() {
        inquirer.prompt([
            {
                type: 'list',
                name: 'jskind',
                message: '请问使用哪种编程语言',
                choices: ['✔ es6', '✔ typescript']
            }
        ])
        .then(answers => {
    
        });
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

mycli

安装 chalk

npm install chalk --save-dev
1

继续和用户交互

const chalk = require('chalk');
// 跟用户交互
program.usage("<cmd> [env]")
    .arguments('<cmd> [env]')
    .action(function (cmd, otherParams) {
        // 输出用户输入的内容 cmd 是用户输入的第一个参数 otherParams 是第二个参数
        const handler = bindHandler[cmd];
        if (handler) {
            handler(otherParams);
        } else {
            console.log(chalk.yellow("非常遗憾") + "【" + chalk.red(cmd) + "】" + chalk.yellow("暂未实现"))
        }
    })
program.parse(process.argv);
1
2
3
4
5
6
7
8
9
10
11
12
13
14

当用户输入了没有预设的指令时

mycli xxx
1

mycli

在我们使用ts过程中经常需要将后台数据转成接口,所以这里添加一个自动转json的东西。

npm install json2ts --save-dev
1

在 bindHandler 中添加这个方法

const bindHandler = {
    json2ts(url) {
        // 拿到后面的参数去请求后台接口地址
        // 这里模拟一下数据
        // 实际用的时候可以在指令后直接加后台地址
        const jsonContent = {
            code:1,
            info: {
                message: '请求成功',
                data: [
                    {
                        num:1,
                        title:"第一条数据"
                    }
                ]
            }
        };
        let result = json2ts.convert(JSON.stringify(jsonContent));
        console.log(result);
        // 拿到 interface 之后就可以把它打到该去的地方了
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

执行命令,后面可以加参数指向后台地址来获取接口 mycli json2ts http://xxxx.json

mycli json2ts
1

mycli

添加让用户等待时的loading 安装 ora

npm install ora --save-dev
1

当用户做完选择后进行loading状态

const ora = require('ora');
const bindHandler = {
    json2ts(url) {
        console.log('接口地址', url);
        // 假装接到了数据
        const jsonContent = {
            code:1,
            info: {
                message: '请求成功',
                data: [
                    {
                        num:1,
                        title:"第一条数据"
                    }
                ]
            }
        };
        let result = json2ts.convert(JSON.stringify(jsonContent));
        console.log(result);
        const spinner = ora("正在帮爷生成代码中,请稍后。。。");
        spinner.start();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

mycli

接下来要进行最重要的部分了。根据用户输入的需要创建生成项目。 分5步:

  1. git准备好一个能容纳百川的项目
  2. git 下载那个包
  3. shelljs 根据用户选择对你下载的包进行修改删除
  4. 在用户的桌面创建最终的项目
  5. 引导开发使用

这里需要使用 shelljs 来进行操作。还需要使用 download-git-repo 来拉取git上的项目

npm install shelljs download-git-repo --save-dev
1
// 获取用户全路径的包
// const userHome = require('user-home');
// 也可以直接用shelljs来取
const shell = require('shelljs');
// console.log(shell.pwd().stdout)
// 拉取git上的项目
const download = require('download-git-repo');
// 项目地址 地址前面要加上 direct: ,我也不知道为啥,反正不加会报错 ╮(╯▽╰)╭
const templateUrl = "direct:https://github.com/xxx/xxx.git";


const bindHandler = {
    init() {
        inquirer.prompt([
            {
                type: 'text',
                name: 'dirname',
                message: '请输入文件夹的名称'
            },
            {
                type: 'list',
                name: 'jskind',
                message: '请问使用哪种编程语言',
                choices: ['✔ es6', '✔ typescript']
            }
        ])
            .then(answers => {
                // 1.git准备好一个能容纳百川的项目
                // 2.git 下载那个包
                // 3.shelljs 根据用户选择对你下载的包进行修改删除
                // 4.在用户的桌面创建最终的项目
                // 5.引导开发使用
                const _dirname = answers.dirname;
                if (_dirname) {
                    const spinner = ora("正在下载模板,请稍后。。。");
                    spinner.start();
                    const _pwd = shell.pwd().stdout;
                    const _projectPath = `${_pwd}/${_dirname}`;
                    shell.cd(_pwd);
                    shell.rm('-rf', _projectPath);
                    shell.mkdir(_dirname);
                    download(templateUrl, _projectPath, { clone: true }, err => {
                        spinner.stop();
                        if (err) {
                            console.log(chalk.red('mycli启动异常'), err);
                        } else {
                            // 在package.json 中查找到要替换的名字,将用户输入的文件夹名替换上去
                            shell.sed("-i", "要替换的名字",_dirname,_projectPath+"/package.json");
                        }
                    })
                }
            });
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

到此一个基本的cli就完成了┓( ´∀` )┏

还需要进行很多完善,比如根据用户不同的输入,来判断使用什么模板,替换不同的东西之类的吧啦吧啦。

贴一下完整代码,

#!/usr/bin/env node
const figlet = require('figlet');/* *********************** 生成asc码的包*/ 
const Printer = require('@darkobits/lolcatjs');/* ********* 做渐变色的包*/
const program = require('commander');/* ******************* 和用户交互的包*/
const inquirer = require('inquirer');/* ******************* 和用户交互的包*/
const chalk = require('chalk');/* ************************* 说话变颜色的包*/
const json2ts = require('json2ts');/* ********************* 把json转换成ts的包*/
const ora = require('ora');/* ***************************** 添加loading等待的包*/
// 获取用户全路径的包
// const userHome = require('user-home');
// 也可以直接用shelljs来取
const shell = require('shelljs');/* *********************** 能调用shell的包*/
// console.log(shell.pwd().stdout)
const download = require('download-git-repo');/* ********** 从git上拉项目的包*/
// git上模板的地址
const templateUrl = "direct:https://github.com/xxx/xxx.git";
// 版本号
const txt = figlet.textSync('MY CLI v1.0.0');
// 添加版本号
program.version(Printer.default.fromString(txt), "-v,--version");
// 根据用户输入不同的初始化指令做不同的骚操作
const bindHandler = {
    init() {
        // 与用户交互的问题
        inquirer.prompt([
            {
                type: 'text',
                name: 'dirname',
                message: '请输入文件夹的名称'
            },
            {
                type: 'list',
                name: 'jskind',
                message: '请问使用哪种编程语言',
                choices: ['✔ es6', '✔ typescript']
            }
        ])
            .then(answers => {
                // 1.git准备好一个能容纳百川的项目
                // 2.git 下载那个包
                // 3.shelljs 根据用户选择对你下载的包进行修改删除
                // 4.在用户的桌面创建最终的项目
                // 5.引导开发使用
                const _dirname = answers.dirname;
                if (_dirname) {
                    const spinner = ora("正在下载模板,请稍后。。。");
                    spinner.start();
                    const _pwd = shell.pwd().stdout;
                    const _projectPath = `${_pwd}/${_dirname}`;
                    shell.cd(_pwd);
                    shell.rm('-rf', _projectPath);
                    shell.mkdir(_dirname);
                    download(templateUrl, _projectPath, { clone: true }, err => {
                        spinner.stop();
                        if (err) {
                            console.log(chalk.red('mycli启动异常'), err);
                        } else {
                            // 在package.json 中查找到要替换的名字,将用户输入的文件夹名替换上去
                            shell.sed("-i", "要替换的名字",_dirname,_projectPath+"/package.json");
                        }
                    })
                }
            });
    },
    json2ts(url) {
        console.log('接口地址', url);
        // 假装接到了数据
        const jsonContent = {
            code: 1,
            info: {
                message: '请求成功',
                data: [
                    {
                        num: 1,
                        title: "第一条数据"
                    }
                ]
            }
        };
        let result = json2ts.convert(JSON.stringify(jsonContent));
        console.log(result);
        const spinner = ora("正在帮爷生成代码中,请稍后。。。");
        spinner.start();
    }
}

// 跟用户交互
program.usage("<cmd> [env]")
    .arguments('<cmd> [env]')
    .action(function (cmd, otherParams) {
        // 输出用户输入的内容 cmd 是用户输入的第一个参数 otherParams 是第二个参数
        const handler = bindHandler[cmd];
        if (handler) {
            handler(otherParams);
        } else {
            console.log(chalk.yellow("非常遗憾") + "【" + chalk.red(cmd) + "】" + chalk.yellow("暂未实现"))
        }
    })
program.parse(process.argv);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99