diff --git a/deploy.config.js b/deploy.config.js new file mode 100644 index 0000000..b7de97f --- /dev/null +++ b/deploy.config.js @@ -0,0 +1,25 @@ +module.exports = { + projectName: 'test', + privateKey: '/Users/bin/.ssh/id_rsa', + passphrase: '', + dev: { + name: '开发环境', + + + + port: 22, + username: 'yoyo', + password: '123456.Yoyo', + webDir: '/home/yoyo/temp/deploy', + remoteBeforeCommand: 'ls', + remoteAfterCommand: 'echo "remoteAfterCommand"', + isRemoveRemoteFile: false, + uploadCommand: 'ls', + + servers: [{ + host: '192.168.16.21', + }], + distPath: './bin', + isRemoveRemoteFile: true + } +} diff --git a/lib/commands/deploy.js b/lib/commands/deploy.js index d3d043c..3c8f62a 100644 --- a/lib/commands/deploy.js +++ b/lib/commands/deploy.js @@ -5,6 +5,7 @@ const archiver = require('archiver') const { NodeSSH } = require('node-ssh') const childProcess = require('child_process') const { deployConfigPath } = require('../config') + const { checkDeployConfigExists, log, @@ -12,6 +13,7 @@ const { error, underline } = require('../utils') +const { resolve } = require('path') const ssh = new NodeSSH() const maxBuffer = 5000 * 1024 @@ -32,21 +34,36 @@ const confirmDeploy = (message) => { // 检查环境是否正确 const checkEnvCorrect = (config, env) => { + const keys = [ 'name', - 'script', + 'distPath', + ] + + const serverKeys = [ + 'privateKey', 'host', 'port', 'username', - 'distPath', - 'webDir' - ] + 'password', + 'webDir', + 'remoteBeforeCommand', + 'remoteAfterCommand', + 'uploadCommand' + ]; + + const serverRequiredKeys = [ + 'host', + 'port', + 'username', + 'webDir', + ]; if ( config && (function () { - const { privateKey, password } = config - if (!privateKey && !password) { + const { privateKey, password, servers } = config + if (!privateKey && !password && !(servers && servers.length > 0 && servers[0].password)) { error( `配置错误: 请配置 ${underline('privateKey')} 或 ${underline( 'passwrod' @@ -57,6 +74,20 @@ const checkEnvCorrect = (config, env) => { return true })() ) { + + if (!(config.servers && config.servers.length)) { + config.servers = [{}]; + } + + // 填充server字段 + config.servers.forEach(server => { + serverKeys.forEach(key => { + if ('undefined' !== typeof config[key] && 'undefined' === typeof server[key]) { + server[key] = config[key]; + } + }) + }); + keys.forEach((key) => { if (!config[key] || config[key] === '/') { error( @@ -67,6 +98,23 @@ const checkEnvCorrect = (config, env) => { process.exit(1) } }) + + // 校验server信息 + config.servers.forEach(server => { + serverRequiredKeys.forEach(key => { + if (!server[key] || server[key] === '/') { + error( + `配置错误: ${underline(`${env}环境`)} ${underline( + `${key}属性` + )} 配置不正确` + ) + process.exit(1) + } + }) + }); + + console.log(config); + } else { error('配置错误: 未指定部署环境或指定部署环境不存在') process.exit(1) @@ -134,10 +182,10 @@ const buildZip = async (config, index) => { } // 连接ssh -const connectSSH = async (config, index) => { +const connectSSH = (server) => async (config, index) => { try { - log(`(${index}) ssh连接 ${underline(config.host)}`) - await ssh.connect(config) + log(`(${index}) ssh连接 ${underline(server.host)}`) + await ssh.connect(server) succeed('ssh连接成功') } catch (e) { error(e) @@ -146,13 +194,15 @@ const connectSSH = async (config, index) => { } // 上传本地文件 -const uploadLocalFile = async (config, index) => { +const uploadLocalFile = (server) => async (config, index) => { try { + const { webDir, host } = server; + const localFileName = `${config.distPath}.zip` - const remoteFileName = `${config.webDir}.zip` + const remoteFileName = `${webDir}.zip` const localPath = `${process.cwd()}/${localFileName}` - log(`(${index}) 上传打包zip至目录 ${underline(remoteFileName)}`) + log(`(${index}) 上传打包zip至目录 ${host} ${underline(remoteFileName)}`) const spinner = ora('正在上传中\n') @@ -171,11 +221,11 @@ const uploadLocalFile = async (config, index) => { } // 删除远程文件 -const removeRemoteFile = async (config, index) => { +const removeRemoteFile = (server) => async (config, index) => { try { - const { webDir } = config + const { webDir, host } = server - log(`(${index}) 删除远程文件 ${underline(webDir)}`) + log(`(${index}) 删除远程文件 ${host} ${underline(webDir)}`) await ssh.execCommand(`rm -rf ${webDir}`) @@ -186,13 +236,112 @@ const removeRemoteFile = async (config, index) => { } } +// 执行后置远程命令 +const runAfterRemoteCommand = (server) => async (config, index) => { + try { + const { remoteAfterCommand, host } = server + + log(`(${index}) 执行后置远程命令 ${host} ${underline(remoteAfterCommand)}`) + + const result = await ssh.execCommand(remoteAfterCommand) + + + if (result.stdout) { + log(result.stdout) + } + + if (result.stderr) { + error(result.stderr) + } + if (result.code) { + error('执行后置远程命令失败') + const answers = await confirmDeploy( + `${underline(projectName)} 执行后置远程命令失败 是否继续?` + ) + + if (!answers.confirm) { + process.exit(1) + } + } + + succeed('执行后置远程命令成功') + } catch (e) { + error(e) + process.exit(1) + } +} + +// 执行前置远程命令 +const runBeforeRemoteCommand = (server) => async (config, index) => { + try { + const { remoteBeforeCommand, host } = server + + log(`(${index}) 执行前置远程命令 ${host} ${underline(remoteBeforeCommand)}`) + + const result = await ssh.execCommand(remoteBeforeCommand) + + if (result.stdout) { + log(result.stdout) + } + + if (result.stderr) { + error(result.stderr) + } + if (result.code) { + error('执行前置远程命令失败') + + const answers = await confirmDeploy( + `${underline(projectName)} 执行前置远程命令失败 是否继续?` + ) + + if (!answers.confirm) { + process.exit(1) + } + } + + succeed('执行前置远程命令成功') + } catch (e) { + error(e) + process.exit(1) + } +} + + +// 执行本地命令 +const runLocalCommand = (tip, command) => async (config, index) => { + try { + + log(`(${index}) ${tip} ${underline(command)}`) + + return new Promise((resolve, reject) => { + childProcess.exec( + command, + { cwd: process.cwd(), maxBuffer: maxBuffer }, + (e, stdout, stderr) => { + if (e === null) { + log(stdout); + succeed(`${tip}成功`) + resolve() + } else { + log(stderr); + reject(e.message) + } + } + ) + }); + } catch (e) { + error(e) + process.exit(1) + } +} + // 解压远程文件 -const unzipRemoteFile = async (config, index) => { +const unzipRemoteFile = (server) => async (config, index) => { try { - const { webDir } = config + const { webDir, host } = server const remoteFileName = `${webDir}.zip` - log(`(${index}) 解压远程文件 ${underline(remoteFileName)}`) + log(`(${index}) 解压远程文件 ${host} ${underline(remoteFileName)}`) await ssh.execCommand( `unzip -o ${remoteFileName} -d ${webDir} && rm -rf ${remoteFileName}` @@ -206,7 +355,7 @@ const unzipRemoteFile = async (config, index) => { } // 删除本地打包文件 -const removeLocalFile = (config, index) => { +const removeLocalBuildFile = (config, index) => { const localPath = `${process.cwd()}/${config.distPath}` log(`(${index}) 删除本地打包目录 ${underline(localPath)}`) @@ -226,10 +375,19 @@ const removeLocalFile = (config, index) => { } remove(localPath) - fs.unlinkSync(`${localPath}.zip`) succeed('删除本地打包目录成功') } +// 删除本地打包zip文件 +const removeLocalFile = (config, index) => { + const localPath = `${process.cwd()}/${config.distPath}` + + log(`(${index}) 删除本地打包zip文件 ${localPath}.zip`) + + fs.unlinkSync(`${localPath}.zip`) + succeed('删除本地打包zip文件') +} + // 断开ssh const disconnectSSH = () => { ssh.dispose() @@ -237,17 +395,25 @@ const disconnectSSH = () => { // 创建任务列表 const createTaskList = (config) => { - const { isRemoveRemoteFile = true } = config taskList = [] - taskList.push(execBuild) - taskList.push(buildZip) - taskList.push(connectSSH) - taskList.push(uploadLocalFile) - isRemoveRemoteFile && taskList.push(removeRemoteFile) - taskList.push(unzipRemoteFile) - taskList.push(removeLocalFile) - taskList.push(disconnectSSH) + + config.script && taskList.push(execBuild); + + config.servers.forEach(server => { + server.uploadCommand || taskList.push(buildZip); + taskList.push(connectSSH(server)) + server.uploadCommand && taskList.push(runLocalCommand('上传文件', server.uploadCommand)); + server.uploadCommand || taskList.push(uploadLocalFile(server)); + server.remoteBeforeCommand && taskList.push(runBeforeRemoteCommand(server)); + server.isRemoveRemoteFile && taskList.push(removeRemoteFile(server)) + server.uploadCommand || taskList.push(unzipRemoteFile(server)); + server.remoteAfterCommand && taskList.push(runAfterRemoteCommand(server)); + taskList.push(disconnectSSH) + server.uploadCommand || taskList.push(removeLocalFile) + }) + config.script && taskList.push(removeLocalBuildFile) + config.script && taskList.push(removeLocalFile) } // 执行任务列表 @@ -265,6 +431,7 @@ module.exports = { if (checkDeployConfigExists()) { const config = require(deployConfigPath) const projectName = config.projectName + const envConfig = Object.assign(config[env], { privateKey: config.privateKey, passphrase: config.passphrase diff --git a/package.json b/package.json index 2d48658..2ab84fc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "deploy-cli-service", - "version": "1.1.2", + "version": "1.1.5", "description": "前端一键自动化部署脚手架服务", "main": "lib/service.js", "homepage": "https://github.com/fuchengwei/deploy-cli-service",