auto-deploy/upload.js

265 lines
7.0 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 inquirer = require('inquirer') //命令行交互
const zipFile = require('compressing2') // 压缩zip
const fs = require('fs') // nodejs内置文件模块
const path = require('path') // nodejs内置路径模块
const CONFIG = require('./config') // 配置
const SSH = new node_ssh();
let config; // 用于保存 inquirer 命令行交互后选择正式|测试版的配置
//logs
const defaultLog = log => console.log(chalk.blue(`---------------- ${log} ----------------`));
const errorLog = log => console.log(chalk.red(`---------------- ${log} ----------------`));
const successLog = log => console.log(chalk.green(`---------------- ${log} ----------------`));
//文件夹目录
const distZipPath = path.resolve(__dirname, `./smx-bundle.tar.gz`); //打包后地址(smx-bundle.tar.gz是文件名,不需要更改, 主要在config中配置 PATH 即可)
//压缩代码
const zipDist = async () => {
const distDir = path.resolve(__dirname, '../' + config.OUTPUT_PATH);
const loading = ora(defaultLog('正在压缩代码')).start();
loading.spinner = spinner_style.arrow4;
try {
await zipFile.tgz.compressDir(distDir, distZipPath)
successLog('压缩成功!');
} catch (error) {
errorLog(error);
errorLog('压缩失败, 退出程序!');
process.exit(); //退出流程
}
loading.stop();
}
//连接服务器
const connectSSH = async () => {
const loading = ora(defaultLog('正在连接服务器')).start();
loading.spinner = spinner_style.arrow4;
try {
const options = config.privateKey ? {
host: config.SERVER_PATH,
username: config.SSH_USER,
privateKey: config.PRIVATE_KEY, //秘钥登录(推荐) 方式一
tryKeyboard: true,
port: config.PORT
} : {
host: config.SERVER_PATH,
username: config.SSH_USER,
password: config.PASSWORD, // 密码登录 方式二
tryKeyboard: true,
port: config.PORT
}
await SSH.connect(options);
successLog('SSH连接成功!');
} catch (error) {
errorLog(error);
errorLog('SSH连接失败!');
process.exit(); //退出流程
}
loading.stop();
}
//传送zip文件到服务器
const uploadZipBySSH = async () => {
//连接ssh
await connectSSH();
//线上目标文件清空
await clearOldFile();
const loading = ora(defaultLog('准备上传文件')).start();
loading.spinner = spinner_style.arrow4;
try {
await SSH.putFiles([{
local: distZipPath,
remote: config.PATH + '/smx-bundle.tar.gz'
}]); //local 本地 ; remote 服务器 ;
successLog('上传成功!');
loading.text = '正在解压文件';
await runCommand('tar -zxvf ./smx-bundle.tar.gz'); //解压
// 是否备份文件
if (config.BACKUP) {
await runCommand(`mv ${config.PATH}/smx-bundle.tar.gz ${config.PATH}/${config.OLD_NAME}.${new Date().toLocaleDateString()}.tar.gz`); //解压完删除线上压缩包
} else {
await runCommand(`rm -rf ${config.PATH}/smx-bundle.tar.gz`); //解压完删除线上压缩包
}
//将目标目录改名
if (config.RENAME) {
await runCommand(`mv ${config.PATH}/${config.OLD_NAME} ${config.PATH}/${config.RENAME}`);
}
// 后续扩展命令
if (config.EXTENDS) {
for (let v of config.EXTENDS) {
await runCommand(v)
}
}
SSH.dispose(); //断开连接
} catch (error) {
errorLog(error);
errorLog('上传失败!');
process.exit(); //退出流程
}
loading.stop();
}
//清空线上目标目录里的旧文件
const clearOldFile = async () => {
try {
await runCommand(`rm -r ${config.PATH}/${config.OLD_NAME}`);
} catch {
// console.log('服务器未发现原始部署文档');
}
if (config.RENAME) {
try {
await runCommand(`rm -r ${config.PATH}/${config.RENAME}`);
} catch {
// console.log('服务器未发现原始部署文档');
}
}
if (config.CLEAR_OLDFILES) {
const commands = ['ls', 'rm -rf *'];
await Promise.all(commands.map(async (it) => {
return await runCommand(it);
}));
}
}
// 删除本地上传后的打包文件
const deleteFile = function async () {
delPath = distZipPath;
try {
/**
* @des 判断文件或文件夹是否存在
*/
if (fs.existsSync(delPath)) {
fs.unlinkSync(delPath);
} else {
console.log('inexistence path', delPath);
}
} catch (error) {
console.log('删除本地打包文件失败', error);
}
}
/**
* 线上执行命令
* @param {String} command 命令操作 如 ls
*/
const runCommand = async (command) => {
const result = await SSH.exec(command, [], {
cwd: config.PATH
})
// defaultLog(result);
}
//----------------------------------------发布程序---------------------------------------------------------//
const runUploadTask = async () => {
console.log(chalk.yellow(`---------> 欢迎使用 Sm@rtMapX前端组 自动部署工具 <---------`));
//压缩代码
await zipDist();
//连接服务器上传文件
await uploadZipBySSH();
//删除本地打包文件
await deleteFile();
successLog('大吉大利, 部署成功!');
process.exit();
}
// 开始前的配置检查
/**
*
* @param {Object} conf 配置对象
*/
const checkConfig = (conf) => {
const checkArr = Object.entries(conf);
checkArr.map(it => {
const key = it[0];
if (key === 'PATH' && conf[key] === '/') { //上传压缩包前会清空目标目录内所有文件
errorLog('PATH 不能是服务器根目录!');
process.exit(); //退出流程
}
if (!conf[key]) {
errorLog(`配置项 ${key} 不能为空`);
process.exit(); //退出流程
}
})
}
//--------------------------------------------------------------------------------------------------//
// 执行交互后 启动发布程序
inquirer
.prompt([{
type: 'confirm',
message: '请确认您的上传目录及修改目录(RENAME)在服务器上没有被用于存储其他数据,以下部署将会删除这些目录',
name: 'confirm',
}, {
type: 'list',
message: '选择您的部署环境',
name: 'env',
choices: [{
name: '测试环境',
value: 'development'
}, {
name: '正式环境',
value: 'production'
}],
when: function (answers) { // 当watch为true的时候才会提问当前问题
return answers.confirm
}
}])
.then(answers => {
if (answers.confirm) {
config = CONFIG[answers.env];
if (!config.OUTPUT_PATH) {
config.OUTPUT_PATH = 'dist';
}
if (!config.PORT) {
config.PORT = 22;
}
const names = config.OUTPUT_PATH.split('/');
if (names[names.length - 1]) {
config.OLD_NAME = names[names.length - 1];
} else {
config.OLD_NAME = names[names.length - 2];
}
checkConfig(config); // 检查
runUploadTask(); // 发布
}
});