前端自动化部署 基于node.js实现

585 阅读4分钟

前端自动化部署

前言

每次开发完项目或者需求时,总是要麻烦运维大佬来部署前端代码。有时因为要修复 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", //打包命令
    },
  ],
});

希望能对看到文章的你有所帮助!