基于vue cli的前端自动部署,自动备份index.html,可版本退回

期望:

    希望通过npm命令实现远程服务端部署 部署分测试环境、生产环境 打包部署前必须保证本地代码为最新代码,与git服务器同步 需要保留vue cli的版本管理,上传代码前备份index.html,方便版本退回

思路

    定义测试环境与生产环境的配置对象,包括host、port、username、password、path(部署路径)、outputDir(打包文件夹) 拉取所在分支最新代码,保证本地代码与git服务器一致(git fetch --all && git reset --hard origin/branchName && git pull,操作给出提示,确保本地有用代码已经提交) 通过env环境打对应的包 通过ssh2,远程执行linux stat命令,获取index.html文件的最后修改时间,工具这个时间生成版本号 通过ssh2,执行linux的cp命令,拷贝服务器上index.html,重命名为index.201911081804.html(201911081804为index.html的最后修改时间) 用scp2插件上传打包好的文件到服务器目标目录

实现 (代码比较潦草,需要整理)

deploy.js

// /deploy.js
const scpClient = require("scp2");
const exec = require("child_process").exec;
const execSync = require("child_process").execSync;
const readline = require("readline");
/*
 *定义多个服务器账号
 */
const SERVER_LIST = {
  // 测试环境
  test: {
    name: "测试环境",
    host: "", // ip
    port: 22, // 端口
    username: "root", // 登录服务器的账号
    password: "@", // 登录服务器的账号
    path: "/home/static/cnhpec", // 发布至静态服务器的项目路径
    outputDir: "dist/" // 打包的目录 对应vue.config的outputDir配置
  },
  // 开发环境
  prod: {
    name: "生产环境",
    host: "",
    port: 22,
    username: "root",
    password: "@",
    path: "/home/static/cnhpec",
    outputDir: "dist/"
  }
};

const argv = process.argv.slice(2);
const env = argv[0] ? (argv[0] === "prod" ? "prod" : "test") : "test";
const indexFile = SERVER_LIST[env].path + "/index.html";

const Client = require("ssh2").Client;

const conn = new Client();
conn
  .on("ready", function() {
    init();
  })
  .on("error", function(err) {
    if (err) throw err;
  })
  .connect({
    host: SERVER_LIST[env].host,
    port: SERVER_LIST[env].port,
    username: SERVER_LIST[env].username,
    password: SERVER_LIST[env].password
  });

async function init() {
  await pull(); // 拉git最新代码
  await build(); // 打包
  await rename(); // 备份服务器代码,生成版本
  await upload(); // 上传代码
}

function pull() {
  return new Promise(function(resolve, reject) {
    const rl = readline.createInterface({
      input: process.stdin,
      output: process.stdout
    });
    select();
    function select() {
      rl.question(
        "此操作git项目强制与远端同步!!!本地代码没提交不要轻易进行操作!!!\n\n此操作git项目强制与远端同步!!!本地代码没提交不要轻易进行操作!!!\n\n此操作git项目强制与远端同步!!!本地代码没提交不要轻易进行操作!!!(y/n)",
        function(answer) {
          answer = answer.trim().toLowerCase();
          if (answer === "y") {
            const branch = execSync("git symbolic-ref --short HEAD");
            exec(`git fetch --all && git reset --hard origin/${branch.toString()} && git pull`, function(err, stdout, stderr) {
              if (err) {
                console.error("项目强制与远端同步失败!!!");
                reject();
                throw err;
              }
              console.info(stdout.toString());
              console.error(stderr);
              console.log("项目强制与远端同步成功");
              resolve();
            });
            rl.close();
          } else if (answer === "n") {
            reject();
            rl.close();
            process.exit(0);
          } else {
            select();
          }
        }
      );
    }
  });
}

function build() {
  return new Promise(function(resolve, reject) {
    console.log(`开始打包${env === "prod" ? "生产" : "测试"}环境。。。。。。`);
    exec("npm run " + env, function(err, stdout, stderr) {
      if (err) {
        console.error("打包失败!!!");
        reject();
        process.exit(1);
        throw err;
      }
      console.info(stdout.toString());
      console.error(stderr);
      console.log("打包成功");
      resolve();
    });
  });
}

function rename() {
  return new Promise(function(resolve, reject) {
    console.log("开始备份服务器版本。。。。。。");
    conn.exec("stat " + indexFile, function(err, stream) {
      if (err) {
        console.error("服务器版本备份失败。。。。。。");
        conn.end();
        reject();
        process.exit(1);
        throw err;
      }
      let mtime;
      stream
        .on("close", function(code) {
          if (code == 0) {
            copy(mtime, resolve, reject);
          } else {
            resolve();
          }
        })
        .on("data", function(data) {
          console.info(data.toString());
          mtime = data.toString().split("\n")[7];
          mtime = mtime.replace(/[\u4e00-\u9fa5]/g, "").replace(":", "");
          mtime = formatDate(mtime);
        })
        .stderr.on("data", function(data) {
          console.warn(data.toString());
        });
    });
  });
}

function copy(mtime, resolve, reject) {
  conn.exec("/bin/cp " + indexFile + " " + SERVER_LIST[env].path + "/index." + mtime + ".html", function(err, stream) {
    if (err) {
      console.error("服务器版本备份失败。。。。。。");
      conn.end();
      reject();
      process.exit(1);
      throw err;
    }
    stream
      .on("close", function(code) {
        if (code == 0) {
          console.log("服务器版本备份成功");
          resolve();
        } else {
          console.error("服务器版本备份失败。。。。。。");
          conn.end();
          reject();
          process.exit(1);
        }
      })
      .on("data", function(data) {
        console.info(data.toString());
      })
      .stderr.on("data", function(data) {
        console.error(data.toString());
      });
  });
}

function upload() {
  return new Promise(function(resolve, reject) {
    scpClient.scp(
      SERVER_LIST[env].outputDir,
      {
        host: SERVER_LIST[env].host,
        port: SERVER_LIST[env].port,
        username: SERVER_LIST[env].username,
        password: SERVER_LIST[env].password,
        path: SERVER_LIST[env].path
      },
      function(err) {
        conn.end();
        if (err) {
          reject();
          process.exit(1);
          throw err;
        } else {
          console.log("Success! 成功发布到" + (env === "prod" ? "生产" : "测试") + "服务器!");
          resolve();
          process.exit(0);
        }
      }
    );
  });
}

function formatDate(date) {
  const isZero = m => (m < 10 ? "0" + m : m),
    time = new Date(date),
    y = time.getFullYear(),
    m = time.getMonth() + 1,
    d = time.getDate(),
    h = time.getHours(),
    mm = time.getMinutes(),
    s = time.getSeconds();
  return y + "" + isZero(m) + "" + isZero(d) + "" + isZero(h) + "" + isZero(mm) + "" + isZero(s);
}

package.json添加部署命令

{
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build --mode build",
    "test": "vue-cli-service build --mode test",
    "lint": "vue-cli-service lint",
    "i18n:report": "vue-cli-service i18n:report --src './src/**/*.?(js|vue)' --locales './src/locales/**/*.json'",
    "deploy:test": "node ./deploy test",
    "deploy:prod": "node ./deploy prod"
  },
  "devDependencies": {
     "scp2": "^0.5.0"
  }
}

总结

    确保了代码最新来打包 保证版本可以退回 未考虑基于vue cli多页开发,html文件的版本备份 没有做成vue cli的插件  还是本地部署的,没有实现服务端部署,像jenkins一样(做出vue cli插件,在部署服务器上启动vue ui,访问http://ip:8000/tasks在里面点击deploy,可实现类型jenkins部署效果)
栏目
每日精选