部署工具脚手架实现原理

239 阅读5分钟

需求分析

部署工具的构建部署原理主要是通过脚手架将当前分支名提交到服务端,服务端读取到配置好的项目gitlab地址,根据gitlab地址和收到的分支名,将分支代码clone到服务器进行构建,将构建完的代码存放到以分支名命名的文件夹下;

已有部署分支的项目通过弹窗sdk选择分支后种到cookie,nginx转发到路由服务,路由服务拿到cookie里的当前环境env和选择的分支名、项目id,到对应的项目下的分支名目录下取到代码,压缩后返回给前端。

如何实现这样一个部署工具的脚手架呢?

调研

我们先了解下脚手架运行的原理,像我们执行vue项目初始化vue create xxx,这种命令是怎么实现的呢?其实是:

1、先有一个可以通过node执行的js文件A

2、package.json里的bin需要定义好命令的开头和可执行文件,例如

image.png

3、文件A中最前面必须加上 #!/usr/bin/env node,这句话实际是告诉cmd使用node来解析执行此文件

这样,当你运行st xxx xxx时,就是相当于执行node dist/src/cli.js

开始开发

部署工具的脚手架采用ts来开发

首先我们定好脚手架命令的格式,比如 st p {env} 代表将当前分支部署到{env}环境;

接着我们先写一个cli.ts,这里面需要定义一个run的主方法,当脚手架命令执行的时候,其实就相当于node跑脚本一样去执行run方法,我们就可以把执行命令时所需要进行的操作写在这个run方法里。

process.argv

当我们输入 st p dev 这样一个命令的时候,我们要怎么知道这个命令对应执行哪些操作呢?

这需要我们利用node进程的process.argv,argv实际是拿到了命令的分隔的结果数组,st p {env}我们可以通过process.argv[2]拿到p这个命令,通过process.argv[3]拿到 dev 这个环境参数,所以我们可以得知这个命令就是要执行publish操作。

在执行一些关键操作前,我们需要去检查下本地脚手架的版本是否是最新的,因为有些功能在新版本有所改变,我们通过开启一个node子进程去跑一个npm命令即可:child_process.execSync('npm view st-cli versions --json --registry=xxxx').toString(),得到的是一个版本数组字符串,我们取最后一个(版本A)即可,再通过读取本地脚手架包的package.json文件可以得到本地版本号(版本B),两者相比较,如果A <= B 则是最新,若不是则询问用户是否安装新版本。

inquirer

在命令行如何实现一问一答的确认操作呢?

我们可以通过inquirer这个npm包来实现,inquirer提供一个prompt方法,(其他具体用法可以查看官方文档)

他接收的是一个questions问题数组,返回的是个回答的promise,比如我们询问用户是否安装新版本:

let answers: { ifInstall: string };
answers = await inquirer.prompt([
      {
            type: 'input',
            name: 'ifInstall',
            message: 'if you want to install the lastest version from origin(y/n)'
       }
 ]);
 const { ifInstall } = answers;

运行的时候,命令行会提示 'if you want to install the lastest version from origin(y/n)',用户输入的 y 或者 n时,则是ifInstall的值,answers中的key对应的是questions数组对象里的name属性。

如果用户本地脚手架版本已是最新,则可以根据 argv[2] === 'p'是否为true来执行publish操作。

首先我们要先获取下自己本地代码正在开发的当前分支名,可以通过子进程跑一个git命令去获取,

child_process.execSync('git rev-parse --abbrev-ref HEAD').toString().replace(/\s+/, '')

git 命令

然后我们判断下argv[3]这个env参数是否是pre-prod和prod,表示部署预发布环境和生产环境,部署这两个环境需要用户再次确认。

不是这两个环境,则检查当前是否有未commit的代码,通过git命令:

git status

如果有,则询问用户是否部署远程分支的代码,选择否,则直接跳出命令行操作;选择是则继续进行调用接口部署流程;

如果没有,则通过 git diff origin/${当前分支}来检查当前是否有未push到远程的代码,

有则再次询问用户是否部署远程分支的代码,选择否,则直接跳出命令行操作;选择是则继续进行调用接口部署流程;

没有则也是正常进行调用接口部署流程。

这时候我们就拿到了部署接口所需要的三个参数,gitlab地址、env环境以及当前分支名branch,然后调用部署接口,接口去创建一个任务,任务是异步执行clone代码并且调用构建服务builder去执行构建等一系列操作,接口返回的只有一个任务id。

所以脚手架在调用部署接口得到任务id后,需要再去调用一个日志接口,来得到构建部署的进度。

日志接口是通过一个定时器去轮询构建服务builder的日志接口,将每次获得的日志遍历不断输出给脚手架,那脚手架如何去将这种流形式的日志展示出来呢?

其实也很简单,我们通过设置axios的responseType为stream,然后通过axios的promise去监听数据的返回即可:

const logRes = await axios({
  url: '',
  method: 'GET',
  params: {
  	taskId
  },
  responseType: 'stream'
})
logRes.data.on('data', (chunk: any) => {
	console.log(chunk.toString())
})

然后我们再监听流结束:

logRes.data.on("end", async () => {
       console.log("日志输出结束");
//在这里我们可以再去调用一个部署服务deploy的接口去查询最终部署任务的状态,因为部署服务deploy也会自身不断轮询构建服务builder来设置任务的状态
}

部署工具cli脚手架的实现大致就是这样~