脚手架开发记录

本文最后更新于:2021/02/15 , 星期一 , 22:00

脚手架开发记录

  • 输入模块
  • 输出模块
  • git操作模块

初始化

首先初始化项目 npm init

package.json中添加脚本的入口文件,

1
2
3
4
5
{
"bin":{
"acbg":'bin/init.js' //格式为“命令名”:“入口文件相对package.json的路径”
}
}

建立入口文件 mkdir bin && touch bin/init.js

在init.js文件中输入:

1
2
#!/usr/bin/env node
console.log('Hello,Cli');

入口文件的第一行一定要添加 #!/usr/bin/env node ,这行代码的意思是指定脚本使用node运行,即当我们输入 acbg时实际上运行的是 node acbg

env中包含了许多系统环境变量,/usr/bin/env node使用这个是为了防止用户没有将node安装在默认的/usr/bin下,当系统看到这一行的时候,首先会找到env里面的node安装路径,在调取node。

通过 package.json配置我们需要的文件(或者是去除不需要的),一定要去除node_modules,不然link的时候会巨慢

1
2
3
{
"files":["./bin","./src"]
}

也可以通过 .gitignore.npmrc等配置文件忽略。

为了方便在本地测试,在当前项目根目录下执行 npm link

npm link在全局node包内建立了当前项目的替身,当访问到全局node包中的当前项目时会转到当前项目实际所在路径,可以简单的理解为相当于执行了 npm install -g 当前项目

在终端运行 acbg测试命令是否成功。

分析工具逻辑

目标:用户在运行cli后输入一些值,可以批量在gitlab上对应项目建立新的分支。

首先将工具分为两部分,工具内部与工具外部(即需要人工干预操作输入的)。

在分析工具外部时,可以将工具内部当成黑盒,只关心输入与输出。

外部流程:

外部流程

内部流程:

内部流程

通过流程图推算出需要做的模块:

模块

由模块图可以得出需要做的功能,按照这些模块分别实现对应的函数就可以了。

命令行交互

命令行交互使用inquirer

具体例子可以看链接内官方提供的示例。

输入gitlab地址模块:

1
2
3
4
5
6
7
8
9
10
11
12
const inquirer = require('inquirer');
const CONFIG = require('./config').CONFIG;

const getAddress = () => {
return inquirer.prompt({
type: 'input',
name: 'address',
message: '请输入gitlab地址'
}).then((ans) => {
CONFIG.address = ans.address;
})
}

其他输入模块也按照这样子做出来。

工具处理过程

获取与token对应地址下的项目信息

1
2
3
const getProjectInfo = () => {
return axios.get(`https://gitlab.${CONFIG.address}.com/api/v4/projects?private_token=${CONFIG.token}`)
}

将获取到的信息格式化输出

1
2
3
4
5
6
7
//展示project信息
const showProjectInfo = () => {
const { projectInfo } = CONFIG;
projectInfo.forEach((value, index) => {
console.log(`project ID:${value.id} project Name:${value.name} project repo:${value.repo}`);
})
}

tips:可以使用chalk来为控制台添加一点颜色更加美观。

克隆项目

使用download-git-repo来clone项目,具体使用方法可以查看说明页。基于该packages中的方法封装clone的方法

20201126更新:download-git-repo在使用过程中有一些问题,再加上这个工具仅需要使用clone功能,因此改成git-clone这个库。

download-git-repo遇到的问题:

image.png

在设置第三个参数为 clone:true后,在没有报错的情况下会引起 .git信息被删除,致使后续操作无法进行。

原代码:

1
2
3
4
5
6
7
8
const downloadRepo = (repo, name) => {
return new Promise((resolve, reject) => {
download(`direct:${repo}`, name, { clone: true }, (err) => {
if (err) reject(err);
resolve('success');
})
})
}

新代码:

1
2
3
4
5
6
7
8
const cloneRepo = (repo, name) => {
return new Promise((resolve, reject) => {
gitClone(repo, name, (err) => {
if (!!err) reject(err);
resolve('download success');
})
})
}

封装执行shell命令方法

1
2
3
4
5
6
7
8
const runCMD = (cmd) => {
return new Promise((resolve, reject) => {
childProcess.exec(cmd, (err) => {
if (!!err) reject(err);
resolve('run CMD success')
})
})
}

后续的打开项目目录,切换到源分支,拉取新分支,推送到远端都可以调用这个方法来做到,因为这些指令之间是有先后顺序依赖关系的,所以封装成一个promise。

20201126更新:本想再将runCMD封装成多个方法,然后将他们链式调用,结果因为childProcess每个命令不在一个进程里,所以失败。

组装

以上步骤已经将所需要的积木准备好,现在只需要将积木拼在一起就可以形成完成的cli了。

完成品可见:all-checkout-branch

持续优化项

  • CI/CD。(20201130完成简单的CI,记录可见CI