前端自动化部署
前言
每次开发完项目或者需求时,总是要麻烦运维大佬来部署前端代码。有时因为要修复 bug 的原因,总是要多次更新,多次部署,虽然这是运维大佬的本职工作,但每次总是会觉得很不好意思。所以,思前想后,还是决定用
node.js来写一个自动化部署的脚本,只需要在运维大佬那要一个账号和密码,给一下相关路径的权限,就可以完成自动部署,也不用麻烦运维大佬。
具体步骤
首先安装需要的相关依赖:
# 记得在项目中安装相关依赖的时候,一定要把相关依赖打到开发依赖里面,不然启动项目会报错
yarn add chalk ora node-ssh shelljs inquirer compressing --dev
在package.json同级创建 script 文件夹,并且创建 deploy.js 文件和 config.js 文件;
code
|-- src
|-- package.json
|-- script
|-- deploy.js
|-- config.js
编写 config.js 文件,编写相关配置:
module.exports = Object.freeze({
servers: [
{
publicPath: "dist", // 项目打包之后的文件夹名称,一般都是dist文件夹,如果你的项目打包成别的文件夹名称,填写打包之后文件夹名称即可
name: "测试环境", // 部署环境的名称
username: "xxx", // 部署服务器的账号
password: "xxx", // 部署服务器的密码,如果重要,可以不写在当前配置文件中
path: "/var/home/www/html", //前端代码在服务器下的路径
host: "xx.xx.xx.xxx", //服务器ip
port: "8001", //端口
script: "build", //打包命令
},
],
});
编写 deploy.js 文件,重点:
- 在 deploy.js 中引入需要的相关模块:
const path = require("path");
const fs = require("fs");
const chalk = require("chalk");
const ora = require("ora");
const NodeSSH = require("node-ssh").NodeSSH;
const shell = require("shelljs");
const inquirer = require("inquirer");
const compressing = require("compressing");
const CONFIG = require("./config");
const SSH = new NodeSSH();
let config;
- 使用
chalk来定义不同类型的console.log:
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 compileDist = async () => {
const loading = ora(defaultLog("开始打包!")).start();
shell.cd(path.resolve(__dirname, "../"));
const res = await shell.exec(`yarn ${config.script}`); //执行shell 打包命令
loading.clear();
if (res.code === 0) {
successLog("项目打包成功!");
} else {
errorLog("项目打包失败, 请重试!");
process.exit(); //退出流程
}
};
- 压缩代码
const zipDist = async () => {
defaultLog("项目开始压缩");
try {
const distDir = path.resolve(__dirname, `../${config.publicPath}`);
const distZipPath = path.resolve(
__dirname,
`../${config.publicPath}.zip`
);
await compressing.zip.compressDir(distDir, distZipPath);
successLog("压缩成功!");
} catch (error) {
errorLog(error);
errorLog("压缩失败, 退出程序!");
process.exit(); //退出流程
}
};
- 连接服务器
const connectSSH = async () => {
const loading = ora(defaultLog("正在连接服务器")).start();
try {
await SSH.connect({
host: config.host,
username: config.username,
password: config.password,
});
successLog("SSH连接成功!");
} catch (error) {
errorLog(error);
console.log(error);
errorLog("SSH连接失败!");
process.exit(); //退出流程
}
loading.clear();
};
//
const runCommand = async (command) => {
await SSH.exec(command, [], { cwd: config.path });
};
- 备份、清空线上目标目录里面的旧文件
const clearOldFile = async () => {
const date = new Date().getDate();
const mouth = new Date().getMonth();
await runCommand(`mkdir -p ${config.publicPath}`);
await runCommand(
`cp -r ${config.publicPath} ${config.publicPath}_${mouth + 1}${date}`
);
await runCommand(`rm -rf ${config.publicPath}`);
};
- 连接并上传文件到服务器
const uploadZipBySSH = async () => {
//连接ssh
await connectSSH();
await clearOldFile();
const loading = ora(defaultLog("准备上传文件")).start();
try {
const distZipPath = path.resolve(
__dirname,
`../${config.publicPath}.zip`
);
await SSH.putFiles([
{
local: distZipPath,
remote: config.path + `/${config.publicPath}.zip`,
},
]); //local 本地 ; remote 服务器 ;
successLog("上传成功!");
loading.text = "正在解压文件";
await runCommand(`unzip ./${config.publicPath}.zip`); //解压
await runCommand(`rm -rf ./${config.publicPath}.zip`);
SSH.dispose(); //断开连接
} catch (error) {
errorLog(error);
errorLog("上传失败!");
process.exit(); //退出流程
}
loading.clear();
};
- 清除本地压缩后的 dist.zip:
const clearZipDist = async () => {
const distZipPath = path.resolve(
__dirname,
`../${config.publicPath}.zip`
);
fs.unlink(distZipPath, () => {});
};
- 执行步骤
const runUploadTask = async () => {
//打包
await compileDist();
//压缩
await zipDist();
//连接服务器上传文件
await uploadZipBySSH();
await clearZipDist();
successLog("部署成功!");
process.exit();
};
- 检查相关配置
const checkConfig = (conf) => {
const checkArr = Object.entries(conf);
checkArr.map((it) => {
const key = it[0];
if (key === "PATH" && conf[key] === "/") {
//上传zip前会清空目标目录内所有文件
errorLog("PATH 不能是服务器根目录!");
process.exit(); //退出流程
}
if (!conf[key]) {
errorLog(`配置项 ${key} 不能为空`);
process.exit(); //退出流程
}
});
};
- 手动输入服务器密码
async function inputPwd() {
const data = await inquirer.prompt([
{
type: 'password',
name: 'password',
message: '服务器密码',
},
]);
return data.password;
}
- 主任务执行函数
async function initInquirer() {
const data = await inquirer.prompt([
{
type: "list",
message: "请选择发布环境",
name: "env",
choices: CONFIG.servers.map((sever) => ({
name: sever.name,
value: sever.name,
})),
},
]);
config = CONFIG.servers.find((server) => data.env === server.name);
if (config) {
if (!config.password) {
config.password = await inputPwd();
}
checkConfig(config);
runUploadTask();
} else {
errorLog("未找到该环境");
}
}
以上就是自动化部署脚本的具体相关步骤和实现代码;具体的详细代码如下:
// /script/deploy.js
const path = require("path");
const fs = require("fs");
const chalk = require("chalk");
const ora = require("ora");
const NodeSSH = require("node-ssh").NodeSSH;
const shell = require("shelljs");
const inquirer = require("inquirer");
const compressing = require("compressing");
const CONFIG = require("./config");
const SSH = new NodeSSH();
let config;
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 compileDist = async () => {
const loading = ora(defaultLog("项目开始打包")).start();
shell.cd(path.resolve(__dirname, "../"));
const res = await shell.exec(`yarn ${config.script}`); //执行shell 打包命令
loading.clear();
if (res.code === 0) {
successLog("项目打包成功!");
} else {
errorLog("项目打包失败, 请重试!");
process.exit(); //退出流程
}
};
//压缩代码
const zipDist = async () => {
defaultLog("项目开始压缩");
try {
const distDir = path.resolve(__dirname, `../${config.publicPath}`);
const distZipPath = path.resolve(__dirname, `../${config.publicPath}.zip`);
await compressing.zip.compressDir(distDir, distZipPath);
successLog("压缩成功!");
} catch (error) {
errorLog(error);
errorLog("压缩失败, 退出程序!");
process.exit(); //退出流程
}
};
//连接服务器
const connectSSH = async () => {
const loading = ora(defaultLog("正在连接服务器")).start();
try {
await SSH.connect({
host: config.host,
username: config.username,
password: config.password,
});
successLog("SSH连接成功!");
} catch (error) {
errorLog(error);
console.log(error);
errorLog("SSH连接失败!");
process.exit(); //退出流程
}
loading.clear();
};
const runCommand = async (command) => {
await SSH.exec(command, [], { cwd: config.path });
};
//备份、清空线上目标目录里的旧文件
const clearOldFile = async () => {
const date = new Date().getDate();
const mouth = new Date().getMonth();
await runCommand(`mkdir -p ${config.publicPath}`);
await runCommand(
`cp -r ${config.publicPath} ${config.publicPath}_${mouth + 1}${date}`
);
await runCommand(`rm -rf ${config.publicPath}`);
};
const uploadZipBySSH = async () => {
//连接ssh
await connectSSH();
await clearOldFile();
const loading = ora(defaultLog("准备上传文件")).start();
try {
const distZipPath = path.resolve(__dirname, `../${config.publicPath}.zip`);
await SSH.putFiles([
{ local: distZipPath, remote: config.path + `/${config.publicPath}.zip` },
]); //local 本地 ; remote 服务器 ;
successLog("上传成功!");
loading.text = "正在解压文件";
await runCommand(`unzip ./${config.publicPath}.zip`); //解压
await runCommand(`rm -rf ./${config.publicPath}.zip`);
SSH.dispose(); //断开连接
} catch (error) {
errorLog(error);
errorLog("上传失败!");
process.exit(); //退出流程
}
loading.clear();
};
const clearZipDist = async () => {
const distZipPath = path.resolve(__dirname, `../${config.publicPath}.zip`);
fs.unlink(distZipPath, () => {});
};
const runUploadTask = async () => {
//打包
await compileDist();
//压缩
await zipDist();
//连接服务器上传文件
await uploadZipBySSH();
await clearZipDist();
successLog("部署成功!");
process.exit();
};
const checkConfig = (conf) => {
const checkArr = Object.entries(conf);
checkArr.map((it) => {
const key = it[0];
if (key === "PATH" && conf[key] === "/") {
//上传zip前会清空目标目录内所有文件
errorLog("PATH 不能是服务器根目录!");
process.exit(); //退出流程
}
if (!conf[key]) {
errorLog(`配置项 ${key} 不能为空`);
process.exit(); //退出流程
}
});
};
async function inputPwd() {
const data = await inquirer.prompt([
{
type: "password",
name: "password",
message: "服务器密码",
},
]);
return data.password;
}
async function initInquirer() {
const data = await inquirer.prompt([
{
type: "list",
message: "请选择发布环境",
name: "env",
choices: CONFIG.servers.map((sever) => ({
name: sever.name,
value: sever.name,
})),
},
]);
config = CONFIG.servers.find((server) => data.env === server.name);
if (config) {
if (!config.password) {
config.password = await inputPwd();
}
checkConfig(config);
runUploadTask();
} else {
errorLog("未找到该环境");
}
}
initInquirer();
// /script/config.js
module.exports = Object.freeze({
servers: [
{
publicPath: "dist", // 项目打包之后的文件夹名称,一般都是dist文件夹,如果你的项目打包成别的文件夹名称,填写打包之后文件夹名称即可
name: "测试环境", // 部署环境的名称
username: "xxx", // 部署服务器的账号
password: "xxx", // 部署服务器的密码,如果重要,可以不写在当前配置文件中
path: "/var/home/www/html", //前端代码在服务器下的路径
host: "xx.xx.xx.xxx", //服务器ip
port: "8001", //端口
script: "build", //打包命令
},
],
});
希望能对看到文章的你有所帮助!