@hoseaguo/deploy
Version:
deploy project to server with node-ssh npm package
278 lines (240 loc) • 8.15 kB
text/typescript
/*
部署流程
打包后:
通过ssh连接服务器
对打包文件里的所有文件到文件压缩 -> 压缩文件
上传到服务器,服务器进行解压
删除远程文件
删除本地文件
远程进行package.json依赖安装
pm2启动项目
*/
const fs = require('fs')
const path = require('path')
import DraftLog from 'draftlog';
import archiver from 'archiver';
import { NodeSSH, Config } from "node-ssh";
import { exit } from 'process';
import chalk from 'chalk'
DraftLog(console)
const ssh = new NodeSSH();
interface Options extends Config {
/* 本地打包路径 */
distPath?: string,
/* 服务器部署路径 */
serverPath?: string,
/* 是否安装npm包 */
installNpmPackage?: boolean,
/* 打包文件名 */
zipFileame?: string,
/* 清空文件 排除 */
cleanExclude?: string,
/* pm2配置文件,假设存在的话,会在部署最后一步,进行 `pm2 startOrReload pm2进程配置文件名`,来重启pm2 服务 */
pm2ConfigFileName?: string,
}
let defaultOptions = {
/* 本地打包路径 */
distPath: "./dist",
/* 服务器部署路径 */
serverPath: "/data/www/_test",
/* 是否安装npm包 */
installNpmPackage: false,
/* 打包文件名 */
zipFileame: '_dist.zip',
/* 打包发送到服务器的目录,会清空服务器上文件夹的内容,可以配置清空排除 正则表达式 */
cleanExclude: "node_modules|.*\.json",
/* pm2配置文件,假设存在的话,会在部署最后一步,进行 `pm2 startOrReload pm2进程配置文件名`,来重启pm2 服务 */
pm2ConfigFileName: ""
}
let customOptions: Options = {};
// 压缩文件
async function compressZip() {
const output = fs.createWriteStream('./' + customOptions.zipFileame);
const archive = archiver('zip', {
zlib: { level: 9 } // Sets the compression level.
});
archive.pipe(output);
archive.directory(customOptions.distPath!, false);
archive.finalize();
// let update = console.draft("本地打包文件压缩:开始")
let endLoaing = loadingLog('本地打包文件压缩')
return new Promise((resolve, reject) => {
archive.on('error', function (err) {
console.log(err)
reject(err)
});
output.on('close', function () {
endLoaing()
resolve(true)
});
})
}
// 上传压缩文件
async function uploadZip() {
let endLoaing = loadingLog('上传打包文件到远程')
try {
await ssh.putFile(customOptions.zipFileame!, `${customOptions.serverPath}/${customOptions.zipFileame}`)
endLoaing()
} catch (e) {
console.log(e);
endLoaing(false)
}
}
// 移除服务器上原有文件
async function cleanServerDir() {
let endLoaing = loadingLog('删除远程源文件')
try {
// 进入远程部署目录
await ssh.execCommand(`cd ${customOptions.serverPath}`, { cwd: customOptions.serverPath });
// 排除删除的文件,除了设置的,还会加上pm2 config 文件,假设存在
let cleanExclude = [customOptions.pm2ConfigFileName, customOptions.cleanExclude];
await ssh.execCommand(`rm \`find ./* |egrep -v '`+ cleanExclude!.join('|') +`'\` -rf`, { cwd: customOptions.serverPath });
} catch (e) {
console.log(e)
endLoaing(false);
}
endLoaing()
}
async function uncompressZip() {
let endLoaing = loadingLog('解压远程压缩文件及删除')
try {
// 进入远程部署目录
await ssh.execCommand(`cd ${customOptions.serverPath}`, { cwd: customOptions.serverPath });
// 解压压缩文件
await ssh.execCommand(`unzip -o ${customOptions.zipFileame} -d .`, { cwd: customOptions.serverPath });
// 删除压缩文件
await ssh.execCommand(`rm ${customOptions.zipFileame}`, { cwd: customOptions.serverPath });
} catch (e) {
console.log(e);
endLoaing(false);
}
endLoaing();
}
// 删除本地打包文件
const deleteLocalZip = async () => {
return new Promise((resolve, reject) => {
let endLoaing = loadingLog('删除本地zip包')
fs.unlink(customOptions.zipFileame, (err: any) => {
if (err) {
console.log(err);
endLoaing(false)
}
endLoaing();
resolve(true);
});
});
};
async function installPackage() {
let endLoaing = loadingLog('远程安装npm包')
// 进入远程部署目录
ssh.execCommand(`cd ${customOptions.serverPath}`, { cwd: customOptions.serverPath });
// package npm 安装
try {
await ssh.execCommand(`npm i`, { cwd: customOptions.serverPath });
// 试着加1秒延迟
await delay(1000);
} catch (e) {
console.log(e);
endLoaing(false);
}
endLoaing();
}
function loadingLog(str: string) {
let startTimestamp = process.hrtime.bigint();
let frames = ['-', '\\', '|', '/']
let index = 0;
let update = console.draft(str + frames[index]);
let timer = setInterval(() => {
index = (index + 1) % frames.length
update(`[${frames[index]}] ${str}`);
}, 50)
return function end(isSuccess: boolean = true) {
clearInterval(timer);
if (isSuccess) {
let endTimestamp = process.hrtime.bigint();
update(chalk.rgb(92, 175, 158)(`[√] ${str} `) + chalk.whiteBright(formatNs(endTimestamp - startTimestamp)));
} else {
update(chalk.red(`[×] ${str}`));
// 退出程序
exit(1);
}
}
}
function formatNs(nsTime: any) {
// console.log(nsTime)
nsTime = Number(nsTime);
// console.log(nsTime)
// 1542 928 800
if (nsTime < 1000) { // 100n
return `${(nsTime / 1000).toFixed(3)}ms`
} else if (nsTime < 1000 ** 2) { // 100 000n
return `${(nsTime / 1000).toFixed(3)}ms`
} else if (nsTime < 1000 ** 3) { // 100s 000m 000n
return `${(nsTime / (1000 ** 3)).toFixed(3)}s`
} else {
return `${(nsTime / (1000 ** 3)).toFixed(3)}s`
}
}
async function startOrReloadPm2(){
let endLoaing = loadingLog('start or reload pm2 by config file')
try {
// 进入远程部署目录
await ssh.execCommand(`cd ${customOptions.serverPath}`, { cwd: customOptions.serverPath });
// 重启pm2配置
await ssh.execCommand(`pm2 startOrReload ${customOptions.pm2ConfigFileName}`, { cwd: customOptions.serverPath });
} catch (e) {
console.log(e);
endLoaing(false);
}
endLoaing();
}
async function delay(time: number){
return new Promise( resolve => {
setTimeout( () => {
resolve(true)
}, time)
})
}
export default function deploy(options: Options) {
let {
distPath,
serverPath,
installNpmPackage,
zipFileame,
cleanExclude,
pm2ConfigFileName,
...sshOptions
} = options;
customOptions = {
distPath: distPath || defaultOptions.distPath,
serverPath: serverPath || defaultOptions.serverPath,
installNpmPackage: installNpmPackage !== undefined ? installNpmPackage : defaultOptions.installNpmPackage,
zipFileame: zipFileame || defaultOptions.zipFileame,
cleanExclude: cleanExclude || defaultOptions.cleanExclude,
pm2ConfigFileName: pm2ConfigFileName || defaultOptions.pm2ConfigFileName
};
let startDeployTimestamp = process.hrtime.bigint();
console.log(chalk.rgb(25, 181, 255)("开始部署>>>"))
let sshLoading = loadingLog("ssh连接服务器");
ssh.connect(sshOptions).then(async function () {
sshLoading();
await cleanServerDir();
await compressZip();
await uploadZip();
await deleteLocalZip();
await uncompressZip();
if (customOptions.installNpmPackage) {
await installPackage();
}
if(customOptions.pm2ConfigFileName){
await startOrReloadPm2();
}
ssh.dispose(); //断开连接
let endDeployTimestamp = process.hrtime.bigint();
console.log(chalk.rgb(25, 181, 255)("<<<结束部署"));
console.log(chalk.rgb(255, 189, 75)('Total time: ' + formatNs(endDeployTimestamp - startDeployTimestamp)))
}).catch(e => {
console.log(e)
sshLoading(false);
})
}