build-to-qiankunserves
Version:
268 lines (238 loc) • 8.27 kB
JavaScript
const pathHierarchy = '../../' //脚本到项目的层级 项目/node_modules/deploy-node/index.js
//logs
const defaultLog = log => console.log(chalk.blue(`☀ ${log}`))
const errorLog = log => console.log(chalk.red(`✘ ${log}`))
// const warningLog = log => console.log(chalk.yellow(`◎ ${log}`))
const successLog = log => console.log(chalk.green(`✔ ${log}`))
const chalk = require('chalk') //命令行颜色
const ora = require('ora') // 加载流程动画
const spinner_style = require('./src/spinner_style') //加载动画样式
const shell = require('shelljs') // 执行shell命令
const node_ssh = require('node-ssh') // ssh连接服务器
const inquirer = require('inquirer') //命令行交互
const zipFile = require('compressing') // 压缩zip
// const fs = require('fs') // nodejs内置文件模块
const path = require('path') // nodejs内置路径模块
const SSH = new node_ssh()
let CONFIG = {};
try {
CONFIG = require(`${pathHierarchy}deploy.config.js`) // 项目配置
} catch (error) {
errorLog('请在项目根目录添加 deploy.config.js 配置文件, 参考说明文档中的配置')
process.exit() //退出流程
}
console.log(chalk.green(`☺ 欢迎使用自动部署工具!`))
let config = {} // 用于保存 inquirer 命令行交互后选择正式|测试版的配置
//文件夹目录
let distDir = "", distZipPath = "", remoteZipPath = "";
//项目打包代码 npm run build
const buildDist = async () => {
const loading = ora(defaultLog('项目开始打包')).start()
loading.spinner = spinner_style[config.loadingStyle || 'arrow4']
shell.cd(path.resolve(__dirname, pathHierarchy))
const res = await shell.exec(config.buildShell || 'npm run build') //执行shell 打包命令
loading.stop()
if (res.code === 0) {
successLog('项目打包成功!')
} else {
errorLog('项目打包失败, 请重试!')
process.exit() //退出流程
}
}
//压缩代码
const zipDist = async () => {
defaultLog('项目正在压缩')
try {
await zipFile.zip.compressDir(distDir, distZipPath)
successLog('压缩成功!')
} catch (error) {
errorLog(error)
errorLog('压缩失败, 退出程序!')
process.exit() //退出流程
}
}
//连接服务器
const connectSSH = async () => {
const loading = ora(defaultLog('正在连接服务器')).start()
loading.spinner = spinner_style[config.loadingStyle || 'arrow4']
//privateKey 秘钥登录(推荐) 方式一
//password 密码登录 方式二
const type = config.password ? 'password' : 'privateKey'
const data = config.password || config.privateKey
const opt = {
host: config.sshIp,
username: config.sshUserName,
[type]: data
}
if (config.readyTimeout) {
opt.readyTimeout = config.readyTimeout
}
try {
await SSH.connect(opt)
successLog('服务器连接成功!')
} catch (error) {
errorLog(error)
errorLog('SSH连接失败! (可能原因: 1:密码不对, 2:privateKey 本机私钥地址不对, 3:服务器未配置本机公钥')
process.exit() //退出流程
}
loading.stop()
}
//线上执行命令
/**
* @param {String} command 命令操作 如 ls
*/
const runCommand = async (command) => {
const result = await SSH.exec(command, [], {
cwd: config.wwwPath
}).catch(err => {
errorLog(err)
process.exit() //退出流程
})
// defaultLog(result)
}
// //清空线上目标目录里的旧文件
// const clearOldFile = async () => {
// const commands = ['ls', `rm -rf ${config.wwwPath}`]
// await Promise.all(commands.map(async (it) => {
// return await runCommand(it)
// }))
// }
// // 创建线上部署目录
// const mkdirDir = async () => {
// console.log('asas');
// await SSH.exec(`mkdir test1`, [], {
// cwd: config.wwwPath
// }).then(err => {
// console.log('创建成功');
// }).catch(err => {
// errorLog(err)
// // process.exit() //退出流程
// })
// // defaultLog(result)
// }
//清空线上目标目录里的旧文件
const clearOldFile = async () => {
// 判断 disFolder 是否是 dist
if (config.distFolder == 'dist') {
await SSH.exec(`rm -rf ${config.wwwPath}`, [], {
cwd: config.wwwPath
}).catch(async err => {
errorLog(err)
// await mkdirDir()
process.exit() //退出流程
})
} else {
await SSH.exec(`rm -rf ${config.wwwPath}/${config.distFolder}`, [], {
cwd: config.wwwPath
}).catch(async err => {
errorLog(err)
// await mkdirDir()
process.exit() //退出流程
})
}
}
//传送zip文件到服务器
const uploadZipBySSH = async () => {
//连接ssh
await connectSSH()
//线上目标文件清空
await runCommand(`rm -rf ${config.wwwPath}/dist`) //移出后删除 dist 文件夹
await clearOldFile()
const loading = ora(defaultLog(`正在上传 ${config.distFolder}.zip 文件`)).start()
loading.spinner = spinner_style[config.loadingStyle || 'arrow4']
try {
await SSH.putFiles([{
local: distZipPath,
remote: remoteZipPath
}]).then(() => {
successLog(`${config.distFolder}.zip 上传成功!`)
}) //local 本地 remote 服务器
loading.text = '正在解压文件...'
await runCommand(`unzip ${remoteZipPath}`) //解压
await runCommand(`rm -rf ${remoteZipPath}`) //解压完删除线上压缩包
//将目标目录的dist里面文件移出到目标文件
//举个例子 假如我们部署在 /test/html 这个目录下 只有一个网站, 那么上传解压后的文件在 /test/html/dist 里
//需要将 dist 目录下的文件 移出到 /test/html 多网站情况, 如 /test/html/h5 或者 /test/html/admin 都和上面同样道理
// await runCommand(`mv -f ${config.wwwPath}/dist/* ${config.wwwPath}`)
SSH.dispose() //断开连接
} catch (error) {
errorLog(error)
errorLog('上传失败!')
process.exit() //退出流程
}
loading.stop()
}
//------------发布程序---------------
const runUploadTask = async () => {
//打包
if (config.buildShell) {
await buildDist()
}
//压缩
await zipDist()
//连接服务器上传文件
await uploadZipBySSH()
successLog('大吉大利, 部署成功!ヾ(@^▽^@)ノ')
process.exit()
}
// 开始前的配置检查
/**
*
* @param {Object} conf 配置对象
*/
const checkConfig = (conf) => {
const checkArr = Object.entries(conf)
checkArr.map(it => {
const key = it[0]
if (conf[key] === '/') { //上传zip前会清空目标目录内所有文件
errorLog('buildShell 不能是服务器根目录!')
process.exit() //退出流程
}
if (!conf[key]) {
errorLog(`配置项 ${key} 不能为空`)
process.exit() //退出流程
}
})
}
let choices = [];
for (const key in CONFIG) {
choices.push({
name: CONFIG[key].title || `发布到 ${CONFIG[key].sshIp} 服务器环境`,
value: key
})
};
if (choices.length === 0) {
choices = [{
name: '测试环境',
value: 'development'
}, {
name: '正式环境',
value: 'production'
}]
}
// 执行交互后 启动发布程序
inquirer
.prompt([{
type: 'list',
message: '请选择发布环境',
name: 'env',
choices
}])
.then(answers => {
config = CONFIG[answers.env];
// config.distFolder = config.distFolder || config.distFolder.replace("/", "");
//文件夹目录
if (!config.distFolder) {
errorLog('本地打包目录不得为空!')
process.exit() //退出流程
};
if (config.wwwPath.indexOf("*") > -1) {
errorLog('wwwPath 目录不得含有“*”字符!')
process.exit() //退出流程
};
distDir = path.resolve(__dirname, `${pathHierarchy + config.distFolder}`) //待打包
distZipPath = path.resolve(__dirname, `${pathHierarchy + config.distFolder}.zip`) //打包后地址(dist.zip是文件名,不需要更改, 主要在config中配置 PATH 即可)
remoteZipPath = config.wwwPath + `/dist.zip`
runUploadTask() // 发布
})