场景:
前端工程发布部署 有时需区分测试环境 生产环境 预发行环境等等主流方式 jinkens/ftp工具/命令行ssh 或者手动
试试这个
node环境实现前端工程一键自动化打包,服务器备份,部署
演示效果(实际命令 npm run deploy)
配置:
目录结构
deploy.js 自动部署主程序
deployConfig.js 自动部署配置文件
deploy文件夹 自动打包部署备份所需配置文件等
步骤:
node 下安装几个工具库
archiver 文件归档压缩工具
inquirer 命令行交互
node-ssh node下加密网络传输 用于连接服务器
runjs 执行命令行
npm install archiver --save
npm i inquirer --save
npm i node-ssh --save
npm install runjs --save-dev
部署主文件
/**
* @author deep1nBlur
* @email
* @create date 2020-11-11 17:09:45
* @modify date 2020-11-13 17:17:19
* @desc [自动部署主程序]
*/
const deployConfig = require('./deployConfig')
const helper = require('./deploy/helper')
const compressFile = require('./deploy/compressFile')
const sshServer = require('./deploy/ssh')
const uploadFile = require('./deploy/uploadFile')
const runCommand = require('./deploy/handleCommand')
const path = require('path')
const projectHelper = helper.projectHelper
const {
run
} = require('runjs') // node 执行终端
// 主程序
async function main() {
console.log('\033[40;31m ###### \033[40;31m 注意!!! 确认 BASE_API 配置正确 && 执行 npm 打包脚本 再进行部署!(ctrl+c)可退出进程!\033[0m')
try {
console.log('\033[41;37m START \033[40;35m ================================PARK-WEB-自动部署================================\033[0m')
const SELECT_CONFIG = (await projectHelper(deployConfig)).value // 所选部署项目的配置信息
console.log('--------------------------------项目名称' + SELECT_CONFIG.name)
const localFile = path.join(__dirname, '/') + SELECT_CONFIG.targetFile // 待上传本地文件
SELECT_CONFIG.openCompress ? await compressFile(SELECT_CONFIG.targetDir, localFile) : '' // 压缩
await sshServer.connectServe(SELECT_CONFIG.ssh) // 连接
await uploadFile(sshServer.ssh, SELECT_CONFIG, localFile) // 上传
await runCommand(sshServer.ssh, 'unzip -o ' + SELECT_CONFIG.targetFile, SELECT_CONFIG.deployDir) // 解压
await run('rm -rf ' + SELECT_CONFIG.targetDir) //删除本地
await run('rm -r ' + SELECT_CONFIG.targetFile) //删除本地
} catch (err) {
console.log('部署过程出现错误!', err)
} finally {
console.log('\033[42;30m DONE \033[40;35m ===========================项目-自动部署已完成!=============================\033[0m')
process.exit()
}
}
main()
部署服务器配置文件
自行配置 看注释/**
* @author deep1nBlur
* @email
* @create date 2020-11-11 17:10:53
* @modify date 2020-11-13 17:17:27
* @desc [自动部署配置文件]
* 说明:
ssh: 连接服务器用户信息
targetDir: 需要压缩的文件目录(启用本地压缩后生效)
targetFile: 指定上传文件名称(config.js同级目录)
openCompress: 关闭后,将跳过本地文件压缩,直接上传同级目录下指定文件
openBackUp: 开启后,若远端存在相同目录,则会修改原始目录名称,不会直接覆盖
deployDir: 指定远端部署地址
releaseDir: 指定远端部署地址下的发布目录名称
bakDir:远端服务器 备份目录
*/
const deployConfig = [{
name: '测试环境',
ssh: {
host: '测试环境服务器ip',
port: 测试端口,
username: '用户名',
password: '密码',
// privateKey: 'E:/id_rsa', // ssh私钥(不使用此方法时请勿填写, 注释即可)
passphrase: '' // ssh私钥对应解密密码(不存在设为''即可)
},
targetDir: 'dist', // 目标压缩目录(可使用相对地址)
targetFile: 'dist.zip', // 目标文件
openCompress: true, // 是否开启本地压缩
openBackUp: true, // 是否开启远端备份
deployDir: '/xxx/xxxxx/xxxxxx' + '/', // 远端目录
releaseDir: 'xxxx', // 发布目录
backDir: 'BACKUP' // 远程服务器备份目录
},
{
name: '生产环境',
ssh: {
host: '生产环境服务器ip',
port: 生产端口,
username: '用户名',
password: '密码',
passphrase: ''
},
targetDir: 'admin',
targetFile: 'admin.zip',
openCompress: true,
openBackUp: true,
deployDir: '/xxx/xxxxx/xxxxx/xxxx' + '/',
releaseDir: 'xxxxx',
backDir: 'bak'
}
]
module.exports = deployConfig
压缩本地文件 compressFile.js
/**
* @author deep1nBlur
* @email
* @create date 2020-11-11 17:06:26
* @modify date 2020-11-12 15:24:59
* @desc [压缩本地文件]
*/
const fs = require('fs')
const archiver = require('archiver')
function compressFile(targetDir, localFile) {
return new Promise((resolve, reject) => {
fs.open(targetDir, 'r', (err, fd) => {
if (err) {
if (err.code === 'ENOENT') {
console.error('本地部署文件不存在')
return
}
throw err
}
console.log('--------------------------------正在压缩...')
const output = fs.createWriteStream(localFile) // 创建文件写入流
const archive = archiver('zip', {
zlib: {
level: 9
} // 设置压缩等级
})
output.on('close', () => {
resolve(
console.log('--------------------------------压缩完成! 共计 ' + (archive.pointer() / 1024 / 1024).toFixed(3) + 'MB')
)
}).on('error', (err) => {
reject(console.log('%c 2-项目-自动部署 压缩失败!', 'color:#ff1744;', err))
})
archive.pipe(output) // 管道存档数据到文件
archive.directory(targetDir, 'admin') // 存储目标文件并重命名
archive.finalize() // 完成文件追加 确保写入流完成
})
})
}
module.exports = compressFile
调用远端命令 handleCommand.js
/**
* @author deep1nBlur
* @email
* @create date 2020-11-11 17:06:54
* @modify date 2020-11-12 13:26:21
* @desc [调用远端命令]
*/
// run linux shell(ssh对象、shell指令、执行路径)
function runCommand(ssh, command, path) {
return new Promise((resolve, reject) => {
ssh.execCommand(command, {
cwd: path
}).then((res) => {
if (res.stderr) {
reject(console.error('命令执行发生错误:' + res.stderr))
process.exit()
} else {
resolve(console.log('--------------------------------执行指令:' + command))
}
})
})
}
module.exports = runCommand
命令行选择交互提示 helper.js
/**
* @author deep1nBlur
* @email
* @create date 2020-11-11 17:07:47
* @modify date 2020-11-12 13:26:26
* @desc [部署项目选择提示]
*/
const inquirer = require('inquirer')
// 项目选择
const selectProjectName = 'PROJECT_NAME'
const projectOptions = [{
type: 'list',
name: selectProjectName,
message: '上下键=>选择要部署的项目名称',
choices: []
}]
// 部署方式选择
const selectDeployMode = 'DEPLOY_TYPE'
const deployModeOptions = [{
type: 'list',
name: selectDeployMode,
message: 'Which deployment mode do you want to use?',
choices: [{
name: 'legacy'
}]
}]
// 云端编译选择
const selectBuildMode = 'BUILD_MODE'
const buildModeOptions = [{
type: 'list',
name: selectBuildMode,
message: 'Which dir do you want to upload?',
choices: [{
name: 'admin'
}]
}]
// 项目选择提示窗
function projectHelper(config) {
return new Promise((resolve, reject) => {
initHelper(config) // init helper
inquirer.prompt(projectOptions).then(answers => {
resolve({
value: findInfoByName(config, answers[selectProjectName])
}) // 查找所选配置项
}).catch((err) => {
reject(console.error('helper显示或选择出错!'.error, err))
})
})
}
// 部署方式选择提示窗
function deployModeHelper() {
return new Promise((resolve, reject) => {
inquirer.prompt(deployModeOptions).then(answers => {
resolve(answers[selectDeployMode])
}).catch((err) => {
reject(console.error('helper显示或选择出错!'.error, err))
})
})
}
// 远端源码编译提示窗
function buildModeHelper() {
return new Promise((resolve, reject) => {
inquirer.prompt(buildModeOptions).then(answers => {
resolve(answers[selectBuildMode])
}).catch((err) => {
reject(console.error('helper显示或选择出错!'.error, err))
})
})
}
function initHelper(config) {
for (const item of config) {
projectOptions[0].choices.push(item.name)
}
// 检查是否存在相同name
if (new Set(projectOptions[0].choices).size !== projectOptions[0].choices.length) {
console.error('请检查配置信息,存在相同name!'.warn)
process.exit()
}
}
// 查找符合条件的配置项
function findInfoByName(config, name) {
for (const item of config) {
if (item.name === name) {
return item
}
}
}
module.exports = {
projectHelper,
deployModeHelper,
buildModeHelper
}
SSH连接服务器 ssh.js
/**
* @author deep1nBlur
* @email
* @create date 2020-11-11 17:08:03
* @modify date 2020-11-12 13:26:29
* @desc [连接远端务器]
*/
const {
NodeSSH
} = require('node-ssh')
const ssh = new NodeSSH()
function connectServe(sshInfo) {
return new Promise((resolve, reject) => {
ssh.connect({
...sshInfo
}).then(() => {
resolve(console.log('--------------------------------' + sshInfo.host + '连接成功!'))
}).catch((err) => {
reject(console.log('--------------------------------' + sshInfo.host + '连接失败!', err))
})
})
}
module.exports = {
ssh,
connectServe
}
本地文件上传服务器 uploadFile.js
/**
* @author deep1nBlur
* @email
* @create date 2020-11-11 17:08:19
* @modify date 2020-11-13 09:12:53
* @desc [上传本地文件]
*/
const runCommand = require('./handleCommand')
// 获取当前时间
function getCurrentTime() {
const date = new Date()
const yyyy = date.getFullYear()
const MM = coverEachUnit(date.getMonth() + 1)
const dd = coverEachUnit(date.getDate())
const HH = coverEachUnit(date.getHours())
const mm = coverEachUnit(date.getMinutes())
const ss = coverEachUnit(date.getSeconds())
return `${yyyy}-${MM}-${dd}_${HH}_${mm}_${ss}`
}
// 转换时间中一位至两位
function coverEachUnit(val) {
return val < 10 ? '0' + val : val
}
// 文件上传(ssh对象、配置信息、本地待上传文件)
async function uploadFile(ssh, config, localFile) {
return new Promise((resolve, reject) => {
handleSourceFile(ssh, config)
console.log('--------------------------------开始文件上传...')
ssh.putFile(localFile, config.deployDir + config.targetFile).then(async () => {
resolve(console.log('--------------------------------文件上传完成'))
}, (err) => {
reject(console.error('--------------------------------文件上传失败!', err))
})
})
}
// 处理源文件(ssh对象、配置信息)
async function handleSourceFile(ssh, config) {
if (config.openBackUp) {
console.log('--------------------------------执行远程备份...')
// 将部署文件夹 移动到 备份目录 并更名时间后缀
await runCommand(
ssh,
`
if [ -d ${config.releaseDir} ];
then
mv ${config.targetFile} ${config.backDir}/${config.releaseDir}_${getCurrentTime() + '.zip'}
fi
`,
config.deployDir)
} else {
console.log('%c 5-项目-自动部署-未开启远端备份!', 'color:#ff1744;')
await runCommand(
ssh,
`
if [ -d ${config.releaseDir} ];
then mv ${config.releaseDir} /tmp/${config.releaseDir}_${getCurrentTime()}
fi
`,
config.deployDir)
}
}
module.exports = uploadFile
npm脚本命令配置 package.json[“scripts”]
"deploy": "npm run build && node deploy.js",
运行自动部署
npm run deploy
芜湖 起飞~