auto-deploy/ssh/upload.js

341 lines
9.5 KiB
JavaScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const chalk = require('chalk') //命令行颜色
const ora = require('ora') // 加载流程动画
const spinner_style = require('../spinner_style') //加载动画样式
const shell = require('shelljs') // 执行shell命令
const node_ssh = require('node-ssh') // ssh连接服务器
const zipFile = require('compressing2') // 压缩zip
const fs = require('fs') // nodejs内置文件模块
const path = require('path') // nodejs内置路径模块
const SSH = new node_ssh();
let config; // 用于保存 inquirer 命令行交互后选择正式|测试版的配置
let pathHierarchy; //测试目录
let distZipPath; //打包后地址(smx-bundle.tar.gz是文件名,不需要更改, 主要在config中配置 REMOTE_ROOT 即可)//文件夹目录
let AesConfig = require('../utils/Base')
let CryptoJS = require('../utils/crypto-js')
//logs
const defaultLog = log => console.log(chalk.blue(`---------------- ${log} ----------------`));
const errorLog = log => console.log(chalk.red(`---------------- ${log} ----------------`));
const successLog = log => console.log(chalk.green(`---------------------- OK ----------------------`));
const endLog = log => console.log(chalk.green(`--------------- ${log} ---------------`));
/**
* 项目打包
* @returns {Promise<void>}
*/
const compileDist = 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.BUILD_SHELL) //执行shell 打包命令
loading.stop()
if (res.code === 0) {
successLog();
} else {
errorLog('项目打包失败, 请重试!')
process.exit() //退出流程
}
}
/**
* 压缩代码
* @returns {Promise<void>}
*/
const zipDist = async () => {
const loading = ora(defaultLog('正在进行代码压缩')).start();
loading.spinner = spinner_style[config.LOADINGSTYLE || 'arrow4']
try {
const distDir = path.resolve(__dirname, `../${pathHierarchy}${config.LOCAL_PATH}`);
await zipFile.tgz.compressDir(distDir, distZipPath);
} catch (error) {
loading.stop();
errorLog(error);
errorLog('压缩失败, 退出程序!');
process.exit(); //退出流程
}
loading.stop();
successLog();
}
/**
* 连接服务器
* @returns {Promise<void>}
*/
const connectSSH = async () => {
const loading = ora(defaultLog('正在连接服务器')).start();
loading.spinner = spinner_style[config.LOADINGSTYLE || 'arrow4']
const type = config.PASSWORD ? 'password' : 'privateKey'
const data = config.PASSWORD || config.PRIVATE_KEY
const opt = {
host: config.SERVER_HOST,
username: config.USER,
[type]: data,
tryKeyboard: true,
port: config.PORT
}
try {
await SSH.connect(opt);
} catch (error) {
loading.stop();
errorLog(error);
errorLog('SSH连接失败,请检查密码或者私钥以及网络状态!')
process.exit(); //退出流程
}
loading.stop();
successLog();
}
/**
* 上传前服务器文档操作
* @returns {Promise<void>}
*/
const clearOldFile = async () => {
const loading = ora(defaultLog('正在进行部署准备')).start();
loading.spinner = spinner_style[config.LOADINGSTYLE || 'arrow4']
try {
await runCommand(`mkdir ${config.NAME}`);
} catch (e) {
if (e.message && e.message.indexOf('File exists') === -1) {
loading.stop();
errorLog(e);
errorLog('创建目录失败!');
process.exit(); //退出流程
}
}
if (config.BACKUP) {
try {
loading.text = '正在进行备份';
const time = new Date().getTime();
await runCommand(`tar -zcvf ${config.NAME}_${time}.tar.gz ${config.NAME}`);
console.log('-ok!')
} catch (error) {
errorLog(error);
errorLog('备份失败!')
}
}
try {
loading.text = '正在回收部署目录';
const commands = [`cd ${config.NAME} && ls`, `cd ${config.NAME} && rm -rf *`];
await Promise.all(commands.map(async (it) => {
return await runCommand(it);
}));
console.log('-ok!')
} catch (error) {
loading.stop();
errorLog(error);
errorLog('回收部署目录失败!');
process.exit(); //退出流程
}
loading.stop();
successLog()
}
/**
* 上传文件
* @returns {Promise<void>}
*/
const uploadFiles = async () => {
// 上传文件
const loading = ora(defaultLog('正在进行部署')).start();
loading.spinner = spinner_style[config.LOADINGSTYLE || 'arrow4']
try {
loading.text = '正在进行代码上传';
await SSH.putFiles([{
local: distZipPath,
remote: `${config.REMOTE_ROOT}/${config.NAME}/smx-bundle.tar.gz`
}]); //local 本地 ; remote 服务器 ;
console.log('-ok!')
} catch (error) {
loading.stop();
errorLog(error);
errorLog('上传失败!');
process.exit(); //退出流程
}
try {
loading.text = '正在解压整理部署文件';
await runCommand(`cd ${config.NAME} && tar -mzxvf smx-bundle.tar.gz`); //解压
await runCommand(`rm -rf ${config.REMOTE_ROOT}/${config.NAME}/smx-bundle.tar.gz`) //解压完删除线上压缩包
//
// // 移除文件夹
await runCommand(`mv -f ${config.REMOTE_ROOT}/${config.NAME}/${config.OLD_NAME}/* ${config.REMOTE_ROOT}/${config.NAME}`)
await runCommand(`rm -rf ${config.REMOTE_ROOT}/${config.NAME}/${config.OLD_NAME}`) //移出后删除 dist 文件夹
console.log('-ok!')
} catch (error) {
loading.stop();
errorLog(error);
errorLog('解压整理出现异常!');
process.exit(); //退出流程
}
// 后续扩展命令
if (config.EXTENDS) {
for (let v of config.EXTENDS) {
await runCommand(v)
}
}
SSH.dispose(); //断开连接
loading.stop();
successLog()
}
// 删除本地上传后的打包文件
const deleteFile = async () => {
const loading = ora(defaultLog('正在进行后续处理')).start();
loading.spinner = spinner_style[config.LOADINGSTYLE || 'arrow4']
delPath = distZipPath;
try {
/**
* @des 判断文件或文件夹是否存在
*/
if (fs.existsSync(delPath)) {
fs.unlinkSync(delPath);
} else {
console.log('inexistence path', delPath);
}
} catch (error) { }
loading.stop();
successLog()
}
/**
* 线上执行命令
* @param {String} command 命令操作 如 ls
*/
const runCommand = async (command) => {
const result = await SSH.exec(command, [], {
cwd: config.REMOTE_ROOT
})
// defaultLog(result);
}
//传送zip文件到服务器
const uploadZipBySSH = async () => {
//连接ssh
await connectSSH();
//线上目标文件清空
await clearOldFile();
// 上传文件
await uploadFiles();
}
//----------------------------------------发布程序---------------------------------------------------------//
const runUploadTask = async (cf, pt) => {
if (cf.REMOTE_ROOT.substr(-1) === '\/') {
cf.REMOTE_ROOT = cf.REMOTE_ROOT.substring(0, cf.REMOTE_ROOT.length - 1)
}
if (cf.LOCAL_PATH.substr(-1) === '\/') {
cf.LOCAL_PATH = cf.LOCAL_PATH.substring(0, cf.LOCAL_PATH.length - 1)
}
let index = cf.REMOTE_ROOT.lastIndexOf("\/"); // 后面
let l_index = cf.LOCAL_PATH.lastIndexOf("\/"); // 后面
config = {
SERVER_HOST: cf.SERVER_HOST,
USER: cf.USER,
PASSWORD: decrypt(cf.PASSWORD),
REMOTE_ROOT: cf.REMOTE_ROOT.substring(0, index),
LOCAL_PATH: cf.LOCAL_PATH,
OLD_NAME: cf.LOCAL_PATH.substring((l_index + 1), cf.LOCAL_PATH.length),
NAME: cf.REMOTE_ROOT.substring((index + 1), cf.REMOTE_ROOT.length),
PORT: cf.PORT
}
cf.LOADINGSTYLE ? config['LOADINGSTYLE'] = cf.LOADINGSTYLE : "";
cf.PRIVATE_KEY ? config['PRIVATE_KEY'] = cf.PRIVATE_KEY : "";
if (cf.SSH_CONFIG) {
cf.SSH_CONFIG.BUILD_SHELL ? config['BUILD_SHELL'] = cf.SSH_CONFIG.BUILD_SHELL : "";
cf.SSH_CONFIG.RENAME ? config['RENAME'] = cf.SSH_CONFIG.RENAME : "";
cf.SSH_CONFIG.BACKUP ? config['BACKUP'] = cf.SSH_CONFIG.BACKUP : "";
cf.SSH_CONFIG.EXTENDS ? config['EXTENDS'] = cf.SSH_CONFIG.EXTENDS : "";
cf.SSH_CONFIG.DELETE_LOCAL_PACKAGE ? config['DELETE_LOCAL_PACKAGE'] = cf.SSH_CONFIG.DELETE_LOCAL_PACKAGE : "";
}
pathHierarchy = pt;
distZipPath = path.resolve(__dirname, `../${pathHierarchy}/dllcnx-bundle.tar.gz`);
console.log(chalk.yellow(`------------> 欢迎使用自动部署工具 <------------`));
//打包
if (config.BUILD_SHELL) {
await compileDist()
}
//压缩代码
await zipDist();
//连接服务器上传文件
await uploadZipBySSH();
//删除本地打包文件
if (config.DELETE_LOCAL_PACKAGE) {
await deleteFile();
}
endLog('大吉大利, 部署成功');
process.exit();
}
function decrypt(data) {
const key = CryptoJS.enc.Utf8.parse(AesConfig.AES_KEY);
const iv = CryptoJS.enc.Utf8.parse(AesConfig.AES_IV);
const decrypted = CryptoJS.AES.decrypt(data, key, {
iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7,
});
// console.log(decrypted);
return CryptoJS.enc.Utf8.stringify(decrypted).toString();
}
module.exports = runUploadTask;