qspark-deploy
Version:
齐圣园区自动化部署
483 lines (434 loc) • 14.9 kB
JavaScript
const simpleGit = require('simple-git');
const inquirer = require('inquirer');
const path = require('path')
const {
exec
} = require('child_process');
let targetBranch = '测试自动合并' // 目标分支
let sourceBranch = '园区临时修改' // 这里是主分支 默认是‘园区临时修改’
let repoDir = path.resolve('../') // 仓库根目录
const simplegit = simpleGit(repoDir)
const dirArr = path.resolve('./').split(path.sep)
const activeProjectName = dirArr[dirArr.length - 1]
let tempBranch = 'develop_temp'
async function autoMerge(localStorage) {
sourceBranch = localStorage.getItem('__cache-sourceBranch')
const taskList = JSON.parse(localStorage.getItem('__cache-merge')).allServes
targetBranch = taskList[0].branch
try {
const hasBranch = await checkBranch(targetBranch)
if (!hasBranch) {
const brance = await gitBranch(targetBranch, `origin/${targetBranch}`)
console.log(`没有本地分支 ${targetBranch} `, `去创建:`, brance);
} else {
/* const brance = await gitBranch('-D', targetBranch)
console.log(`删除本地分支${targetBranch}`, `去删除:`, brance); */
}
const checkout = await gitCheckout(targetBranch)
console.log(`切换到本地分支 ${targetBranch} `, checkout);
const pull = await gitPull()
console.log(`拉取远端 ${targetBranch} 分支`, pull);
if(!await checkBranch(tempBranch)){
await simplegit.checkout(['-b',tempBranch]);
await simplegit.checkout(`origin/${sourceBranch}`,['--',activeProjectName]);
await simplegit.commit(`临时提交分支:${targetBranch},系统:${activeProjectName}`);
await simplegit.checkout(targetBranch);
const merge = await gitMerge(tempBranch, targetBranch)
console.log(`合并远端 ${sourceBranch} 分支`, merge);
}
await prompt(`将‘${targetBranch}’、‘${sourceBranch}’的合并内容,推送到远端‘${targetBranch}’`)
const commitPush = await gitCommitPush(targetBranch)
console.log(`推送 ${targetBranch} 分支到仓库`, commitPush);
if(!await checkBranch(tempBranch)){
await gitBranch('-D', tempBranch)
}
const command = await runCommand('npm run build')
console.log(`执行打包命令npm run build`, command);
deploy(localStorage);
} catch (error) {
console.log(error);
if (error.message.indexOf('CONFLICTS:') > -1) {
console.log(`本地分支 ${targetBranch} 合并远端 ${sourceBranch} 分支代码冲突了,请解决冲突!`);
await revertedFile()
}
}
}
module.exports = autoMerge
function prompt(mess){
return new Promise((resolve,reject)=>{
inquirer.prompt([{
type: 'confirm',
name: 'choice',
message: mess,
default: true,
}]).then(answers => {
if(answers.choice){
resolve()
}else{
reject(new Error("用户停止"))
}
}).catch(error => {
reject(error)
});
})
}
function runCommand(command) {
return new Promise((resolve, reject) => {
exec(command, (err, stdout, stderr) => {
if (err) {
reject(err)
return;
}
resolve({
stdout,
stderr
})
});
})
}
function revertedFile(){
return new Promise((resolve, reject) => {
simplegit.status().then(status => {
let fileList = []
status.files.forEach(file=>{
if(file.path.indexOf(activeProjectName)==-1){
fileList.push(file.path)
}
})
simplegit.reset(fileList, (err, result) => { //取消所有非当前项目暂存
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
})
}
function checkBranch(targetBranch) { // 获取本地分支列表,如果包含指定分支则返回true,否则返回false
return new Promise((resolve, reject) => {
simplegit.branchLocal((err, branches) => {
const branchList = branches.all;
const hasTargetBranch = branchList.includes(targetBranch);
if (hasTargetBranch) {
resolve(true)
} else {
resolve(false)
}
});
})
}
function gitPull() {
return new Promise((resolve, reject) => {
simplegit.pull((err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
});
})
}
function gitCheckout(targetBranch) {
return new Promise((resolve, reject) => {
simplegit.checkout(targetBranch, ['-f'], (err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
});
})
}
function gitBranch(newBranch, remoteBranch) { // 从远程分支创建一个新的本地分支,也可删除一个分支
return new Promise((resolve, reject) => {
simplegit.branch([newBranch, remoteBranch], (err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
});
})
}
function gitMerge() {
return new Promise((resolve, reject) => {
simplegit.mergeFromTo(...arguments, { '--no-commit': true, '--squash': true }, (err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
});
})
}
function gitCommitPush(targetBranch) {
return new Promise((resolve, reject) => {
revertedFile().then(res=>{
simplegit.add(`${activeProjectName}/`)
.commit(`自动提交分支${targetBranch}`)
.exec((err, result) => {
if (err) reject(err);
console.log(`提交到本地仓库成功!`);
simplegit.push('origin', targetBranch, (err, result) => {
if (err) reject(err);
console.log(`推送到远程仓库成功!`);
resolve(result)
});
});
})
})
}
const moment = require('moment')
const util = require('util')
const events = require('events')
const Client = require('ssh2').Client
const fs = require('fs')
const ProgressBar = require('progress')
const gitInfo = require("../git_info");
const axios = require("axios");
//cnpm install moment util events ssh2 progress inquirer@^8.0.0 simple-git axios --dev 请先在deploy所在目录安装以上包
/******************************请手动配置以下内容*********************************/
/** 远程服务器配置
* @type {{password: string, port: number, host: string, username: string}}
*/
const server = {
host: '', //主机ip
port: 22, //SSH 连接端口
username: '', //用户名
password: '', //用户登录密码
}
let basePath = '/opt/project/***/html' //服务器网站根目录
const bakDirName = '.bak' + moment(new Date()).format('YYYY-M-D-HH:mm:ss') //备份文件名
const buildPath = path.resolve('./dist') //本地项目编译后的文件目录
const maxBakNumber = 3 //最多备份数
const serverPath = 'http://212.64.24.247:3005'
// const serverPath = 'http://127.0.0.1:3005'
/**********************************配置结束***************************************/
let taskData = {}
let currentBranch = ''
let access_token
let repositoryName = ''
let localStorage
async function deploy(storage) {
localStorage = storage
taskData = JSON.parse(localStorage.getItem('__cache-merge'))
access_token = taskData.access_token
await prompt(`部署'${taskData.allServes[0].name}' 的 '${taskData.allServes[0].systemName }'系统`)
init(taskData.allServes[0])
}
function doBakDir(server, basePath, callback){
getFileOrDirList(server, basePath, (err,list)=>{
if(err){
console.log(err)
return false
}
let obj = {}
const dir = fs.readdirSync(buildPath)
if(list.length){
dir.forEach(item=>{
doShell(server, `mv ${basePath}/${item} ${basePath}/${item+bakDirName}\nexit\n`) //备份远程目录文件
obj[item] = []
list.forEach(dir=>{
if(dir.indexOf(item+'.bak')>-1){
obj[item].push(dir)
}
})
obj[item].sort()
if(obj[item].length >= maxBakNumber){
doShell(server, `rm ${item.indexOf('.html')>-1?'-f':'-rf'} ${basePath}/${obj[item][0]} \nexit\n`) //删除多余备份
}
})
}
callback()
})
}
/**
* 描述:获取远程文件路径下文件列表信息
* 参数:server 远程电脑凭证;
* basePath 远程路径;
* then 回调函数
* 回调:then(err, dirs) : dir, 获取的列表信息
*/
function getFileOrDirList(server, basePath, then){
var cmd = "find " + basePath + " -maxdepth 1 "+ "\r\nexit\r\n";
doShell(server, cmd, function(err, data){
var arr = [];
var remoteFile = [];
arr = data.split("\r\n");
arr.forEach(function(dir){
if(dir.indexOf(basePath+'/') ==0){
remoteFile.push(dir);
}
});
remoteFile=remoteFile.map(item=>item.split('/')[item.split('/').length-1]).filter(item=> item&&item.trim()) //只保留第一层目录
remoteFile=Array.from(new Set(remoteFile)) //去重
then(err, remoteFile);
})
};
function doConnect(server, then) {
const conn = new Client()
conn.on('ready', function () {
then && then(conn)
}).on('error', function (err) {
console.error('connect error!', err)
}).on('close', function () {
conn.end()
}).connect(server)
}
function doShell(server, cmd, then) {
doConnect(server, function (conn) {
conn.shell(function (err, stream) {
if (err) throw err
else {
let buf = ''
stream.on('close', function () {
conn.end()
then && then(err, buf)
}).on('data', function (data) {
buf = buf + data
}).stderr.on('data', function (data) {
console.log('stderr: ' + data)
})
stream.end(cmd)
}
})
})
}
function doGetFileAndDirList(localDir, dirs, files) {
const dir = fs.readdirSync(localDir)
for (let i = 0; i < dir.length; i++) {
const p = path.join(localDir, dir[i])
const stat = fs.statSync(p)
if (stat.isDirectory()) {
dirs.push(p)
doGetFileAndDirList(p, dirs, files)
} else {
files.push(p)
}
}
}
function Control() {
events.EventEmitter.call(this)
}
util.inherits(Control, events.EventEmitter)
const control = new Control()
control.on('doNext', function (todos, then) {
if (todos.length > 0) {
const func = todos.shift()
func(function (err, result) {
if (err) {
then(err)
throw err
} else {
control.emit('doNext', todos, then)
}
})
} else {
then(null)
}
})
function doUploadFile(server, localPath, remotePath, then) {
doConnect(server, function (conn) {
conn.sftp(function (err, sftp) {
if (err) {
then(err)
} else {
sftp.fastPut(localPath, remotePath, function (err, result) {
conn.end()
then(err, result)
})
}
})
})
}
function doUploadDir(server, localDir, remoteDir, then) {
let dirs = []
let files = []
doGetFileAndDirList(localDir, dirs, files)
// 创建远程目录
console.log('开始创建远程目录')
let todoDir = []
dirs.forEach(function (dir) {
todoDir.push(function (done) {
const to = path.join(remoteDir, dir.slice(localDir.length + 1)).replace(/[\\]/g, '/')
const cmd = 'mkdir -p ' + to + '\r\nexit\r\n'
// console.log(`cmd::${cmd}`)
doShell(server, cmd, done)
}) // end of push
})
// 上传文件
console.log('准备上传文件:')
let todoFile = []
let total = files.length;
let bar = new ProgressBar('上传进度:[:bar] :percent 剩余时长::etas', {
total,
width: 50
});
files.forEach(function (file) {
todoFile.push(function (done) {
const to = path.join(remoteDir, file.slice(localDir.length + 1)).replace(/[\\]/g, '/')
// console.log('upload ' + to)
bar.tick(1);
doUploadFile(server, file, to, done)
})
})
control.emit('doNext', todoDir, function (err) {
if (err) {
throw err
} else {
control.emit('doNext', todoFile, then)
}
})
}
function sendRobot(item){
if(!access_token){
console.log('\x1b[91m','未配置钉钉事件消息token!\033[0m');
}
const gitinfo = gitInfo('global')
item.access_token = access_token
item.userName = gitinfo&&gitinfo.user&&gitinfo.user.name
item.currentBranch = currentBranch;
item.repositoryName = repositoryName;
axios.post(`${serverPath}/sendRobot`, item).then(res=>{
if(res.data.code!=1){
console.log(res.data.message);
}else{
console.log(res.data.message);
}
})
}
function loop(){
taskData.allServes.shift()
if(taskData.allServes.length==0){
localStorage.removeItem('__cache-merge')
console.log('\n--------恭喜!!全部部署成功!!--------------')
return
}
localStorage.setItem('__cache-merge',JSON.stringify(taskData))
autoMerge(localStorage)
}
function init(item) {
return new Promise((resolve, reject)=>{
server.host = item.remoteHost
server.port = item.remotePort||22
server.username = item.remoteUsername
server.password = item.remotePassword
basePath = item.remoteDic
const serverCopy = JSON.parse(JSON.stringify(server))
const basePathCopy = basePath
console.log('\n--------配置如下--------------\n')
console.log(`服务器host: ${server.host}`)
console.log(`项目部署以及备份目录: ${basePath}`)
console.log(`备份后的文件夹名: ${bakDirName}`)
console.log('\n--------开始部署--------------\n')
doBakDir(serverCopy,basePathCopy,()=>{
doUploadDir(serverCopy, buildPath, `${basePathCopy}`, () => {
resolve();
console.log('\n--------部署成功--------------')
sendRobot(item)
loop() // 部署下一个
})
})
})
}