一句话介绍:它是可以一行命令将代码更新到服务器的脚本
轻量级更新方案最开始源于掘金的文章,后来从零实现了一个更新脚本,并且已在是生产环境中进行使用很长时间,算是非常稳定的版本,个人认为轻量化更新方案是非常使用小型开发团队
现在切换到了 gitlab 的 CI/CD。所以这种方案已经不再是主流方案,但是一路使用过来,非常稳定的解决了更新问题,还是非常不错的
优点: 快速,稳定,自动备份指定文件夹(灵活性高,但是需要自己实现)
缺点:需要手动回滚(自动回滚需要编码),相对来说没那么规范,没有留下记录,敏感数据存储在电脑中,配置文件可以 git 忽略
核心流程
确认并打包项目
通过 node-ssh 连接线上服务器
将打包代码指定名称进行压缩
备份之前的代码,删除以前的代码包,并解压压缩包
删除本次打包代码,断开 ssh 链接
如何使用
- 将仓库文件放入项目
- 安装依赖
npm install node-ssh inquirer archiver -D - 修改 upload.config.js 内容
- 增加脚本命令
"upload": "node build/upload.js" - 运行命名,验证是否功能正常
核心代码
build/upload.js
const fs = require("fs");
const path = require("path");
const { NodeSSH } = require("node-ssh");
const archiver = require("archiver");
const inquirer = require("inquirer");
const exec = require("child_process").exec;
const ssh = new NodeSSH();
const uploadFun = require("../upload.js");
/**
* 获取当前平台
*/
let objName = process.argv[2]; // 更新名字
let startTime = null; // 程序开始更新的时间
// 获取上传服务器配置
let config = uploadFun(objName);
const verifyList = [
{
type: "input",
message: "您正在更新到线上环境,请确认接口域名",
name: "objName",
},
];
inquirer.prompt(verifyList).then(() => {
uploadBuild();
});
function uploadBuild() {
startTime = new Date();
console.log(`${objName}开始更新`);
let buildcmd = exec(config.buildScript, (error, stdout, stderr) => {
if (!error) {
console.log("打包完成", stdout);
app();
} else {
console.error("打包出现错误", stderr);
process.exit(0);
}
});
buildcmd.stdout.on("data", (data) => {
console.log(data.toString());
});
}
/**
* 通过ssh链接服务器
*/
function app() {
ssh
.connect({
host: config.host,
username: config.username,
password: config.password,
})
.then((res) => {
// 上传代码压缩包
uploadData();
})
.catch((err) => {
console.log(err);
});
}
/**
* 上传代码 压缩现有代码
*/
function uploadData() {
// 创建文件输出流
let output = fs.createWriteStream(
`${path.join(__dirname, "../")}${config.buildPath}/${config.objname}.zip`
);
// 设置压缩级别
let archive = archiver("zip", {
zlib: {
level: 8,
},
});
// 存档警告
archive.on("warning", function (err) {
if (err.code === "ENOENT") {
console.warn("stat故障和其他非阻塞错误");
} else {
throw err;
}
});
// 存档出错
archive.on("error", function (err) {
throw err;
});
// 通过管道方法将输出流存档到文件
archive.pipe(output);
archive.directory(
`${path.join(__dirname, "../")}${config.buildPath}/${config.buildobj}`,
"/"
);
archive.finalize();
// 文件输出流结束
output.on("close", function () {
console.log(
`总共 ${(archive.pointer() / 1024 / 1024).toFixed(2)} MB,完成源代码压缩`
);
ssh
.putFile(
`${path.join(__dirname, "../")}${config.buildPath}/${
config.objname
}.zip`,
`${config.uploadDir}/${config.objname}.zip`
)
.then(() => {
console.log("程序zip上传成功,判断线上是否需要备份");
runcmd();
})
.catch((err) => {
console.log(err);
});
});
}
/**
* 执行ssh命令 判断当前是否存在备份
*/
function runcmd() {
ssh
.execCommand("ls", {
cwd: config.uploadDir,
})
.then((res) => {
if (res.stdout) {
let fileList = res.stdout.split("\n");
if (config.objname == config.backObject) {
if (fileList.includes(config.objname)) {
console.log("当前更新为线上正常环境,开始进行备份");
backupData();
} else {
console.log("当前更新为线上正常环境,并且是第一次,将跳过备份");
cmdunzip();
}
} else {
console.log("当前为测试环境,无需备份,直接解压上传压缩包");
cmdunzip();
}
} else if (res.stderr) {
console.log("查询指定目录失败");
} else {
console.log("ssh链接发生了错误");
}
});
}
/**
* 备份项目
*/
function backupData() {
ssh
.execCommand(
`mv ${config.objname} backup/${
config.objname
}_backup${new Date().getTime()}`,
{
cwd: config.uploadDir,
}
)
.then((res) => {
if (res.stderr) {
console.log("备份发生错误", res.stderr);
} else {
console.log("完成备份,解压最新代码");
cmdunzip();
}
})
.catch((err) => {
console.log("备份发生未知链接错误", err);
});
}
/**
* 解压最新代码zip
*/
function cmdunzip() {
// 解压程序
ssh
.execCommand(
`rm -rf ${config.objname} && unzip -o -d ${config.uploadDir}/${config.objname} ${config.objname}.zip && rm -f ${config.objname}.zip`,
{
cwd: config.uploadDir,
}
)
.then(() => {
console.log(`项目包完成解压,${config.objname}项目部署成功了!`);
console.log(
`项目更新时长${(new Date().getTime() - startTime.getTime()) / 1000}s`
);
return deletelocalFile().then(() => {
console.log("本地缓存zip清除完毕");
});
})
.then(() => {
ssh
.execCommand(`rm -rf ${config.objname}/static/.DS_Store`, {
cwd: config.uploadDir,
})
.then(() => {
console.log("线上项目.DS_Store删除完成");
ssh.dispose();
process.exit(0);
})
.catch((err) => {
console.log(err);
});
})
.catch((err) => {
console.log("解压出现错误", err);
});
}
/**
*删除本地生成的压缩包
*/
function deletelocalFile() {
return new Promise((resolve, reject) => {
fs.unlink(
`${path.join(__dirname, "../")}${config.buildPath}/${config.objname}.zip`,
(err) => {
if (err) {
reject(err);
throw err;
} else {
resolve();
}
}
);
});
}
配置文件
upload.config.js
// 打包核心配置文件
let Available = ["dist-a", "dist-b"]; // dist-a 环境a代码包 dist-b 环境b代码包 npm run upload dist-a
/**
* 获取更新配置
* @param {String} objName 当前更新名称
* @returns
*/
module.exports = (objName) => {
if (!Available.includes(objName)) {
console.log("当前项目不存在您输入的更新命令,请检查更新名称");
process.exit(0);
}
return {
host: "xx.xx.xx.xx", // 服务器地址
username: "root",
password: "xxxxxxxxxx",
buildPath: "", // 本地打包项目地址(多层路径用这个)
buildobj: "dist", // 本地打包文件名称
uploadDir: "/xx/xx/xx", // 服务端项目地址
objname: objName, // 打包项目名称
backObject: "objName", // 备份的文件夹名称
buildScript: "npm run build", // 更新命令
};
};
触发命令
最后在 package.json 增加一行命令,运行前面的脚本文件
"scripts": {
// ......
"upload": "node build/upload.js"
},
依赖版本
因为更新脚本是在项目里面的,所以需要额外安装依赖
推荐版本号
"node-ssh": "^12.0.0",
"inquirer": "^7.3.3",
"archiver": "^3.1.1",
实际使用
npm run upload xxxx // 线上代码文件夹名称
upload 命令后面的字符串就是服务器上的文件夹名称,这里为了防止更新命名敲错了,需要首先在 upload.config.js 中进行更新白名单声明,如果配置都正确的情况下,你就可以看到,这就代表成功了~
标签:console,log,err,前端,更新,轻量化,ssh,config,objname From: https://www.cnblogs.com/wp-leonard/p/17129416.html