UNPKG

qspark-deploy

Version:

齐圣园区自动化部署

483 lines (434 loc) 14.9 kB
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() // 部署下一个 }) }) }) }