自动化部署 vue

自用
第一步, 把项目里 upload 文件夹复制到你项目根目录


image.png

第二步, 下载该js相关依赖
npm 或 cnpm i chalk ora shelljs node-ssh inquirer compressing -D

第三步, 打开 upload/config.js 配置文件, 配置ssh地址, 用户名, 验证方式,需要上传的目录

第四步, 在你项目中 package.json 文件中 加上 "deploy": "node ./upload/upload.js"

image.png

最后 在命令行输入 npm run deploy 选择发布环境

config.js

module.exports = Object.freeze({
  development: {//测试
    SERVER_PATH: '112.71.62.21', // ssh地址 服务器地址
    SSH_USER: 'root', // ssh 用户名
    //方式一 用秘钥登录服务器(推荐), private 本机私钥文件地址(需要在服务器用户目录 一般是 /root/.ssh/authorized_keys 配置公钥 并该文件权限为 600, (.ssh文件夹一般默认隐藏)
    // PRIVATE_KEY: 'C:/Users/Html5/.ssh/id_rsa', 
    PASSWORD: '', //方式二 用密码连接服务器
    PATH: '/usr/local/nginx/html/vue' // 需要上传的服务器目录地址 如 /usr/local/nginx/html
  },
  production: {//正式
    SERVER_PATH: '', 
    SSH_USER: 'root',
    PRIVATE_KEY: '', 
    PATH: '/test/html' 
  }
})

spinner_style.js

const style = {
    "dots": {
        "interval": 80,
        "frames": [
            "⠋",
            "⠙",
            "⠹",
            "⠸",
            "⠼",
            "⠴",
            "⠦",
            "⠧",
            "⠇",
            "⠏"
        ]
    },
    "dots2": {
        "interval": 80,
        "frames": [
            "⣾",
            "⣽",
            "⣻",
            "⢿",
            "⡿",
            "⣟",
            "⣯",
            "⣷"
        ]
    },
    "dots3": {
        "interval": 80,
        "frames": [
            "⠋",
            "⠙",
            "⠚",
            "⠞",
            "⠖",
            "⠦",
            "⠴",
            "⠲",
            "⠳",
            "⠓"
        ]
    },
    "dots4": {
        "interval": 80,
        "frames": [
            "⠄",
            "⠆",
            "⠇",
            "⠋",
            "⠙",
            "⠸",
            "⠰",
            "⠠",
            "⠰",
            "⠸",
            "⠙",
            "⠋",
            "⠇",
            "⠆"
        ]
    },
    "dots5": {
        "interval": 80,
        "frames": [
            "⠋",
            "⠙",
            "⠚",
            "⠒",
            "⠂",
            "⠂",
            "⠒",
            "⠲",
            "⠴",
            "⠦",
            "⠖",
            "⠒",
            "⠐",
            "⠐",
            "⠒",
            "⠓",
            "⠋"
        ]
    },
    "dots6": {
        "interval": 80,
        "frames": [
            "⠁",
            "⠉",
            "⠙",
            "⠚",
            "⠒",
            "⠂",
            "⠂",
            "⠒",
            "⠲",
            "⠴",
            "⠤",
            "⠄",
            "⠄",
            "⠤",
            "⠴",
            "⠲",
            "⠒",
            "⠂",
            "⠂",
            "⠒",
            "⠚",
            "⠙",
            "⠉",
            "⠁"
        ]
    },
    "dots7": {
        "interval": 80,
        "frames": [
            "⠈",
            "⠉",
            "⠋",
            "⠓",
            "⠒",
            "⠐",
            "⠐",
            "⠒",
            "⠖",
            "⠦",
            "⠤",
            "⠠",
            "⠠",
            "⠤",
            "⠦",
            "⠖",
            "⠒",
            "⠐",
            "⠐",
            "⠒",
            "⠓",
            "⠋",
            "⠉",
            "⠈"
        ]
    },
    "dots8": {
        "interval": 80,
        "frames": [
            "⠁",
            "⠁",
            "⠉",
            "⠙",
            "⠚",
            "⠒",
            "⠂",
            "⠂",
            "⠒",
            "⠲",
            "⠴",
            "⠤",
            "⠄",
            "⠄",
            "⠤",
            "⠠",
            "⠠",
            "⠤",
            "⠦",
            "⠖",
            "⠒",
            "⠐",
            "⠐",
            "⠒",
            "⠓",
            "⠋",
            "⠉",
            "⠈",
            "⠈"
        ]
    },
    "dots9": {
        "interval": 80,
        "frames": [
            "⢹",
            "⢺",
            "⢼",
            "⣸",
            "⣇",
            "⡧",
            "⡗",
            "⡏"
        ]
    },
    "dots10": {
        "interval": 80,
        "frames": [
            "⢄",
            "⢂",
            "⢁",
            "⡁",
            "⡈",
            "⡐",
            "⡠"
        ]
    },
    "dots11": {
        "interval": 100,
        "frames": [
            "⠁",
            "⠂",
            "⠄",
            "⡀",
            "⢀",
            "⠠",
            "⠐",
            "⠈"
        ]
    },
    "dots12": {
        "interval": 80,
        "frames": [
            "⢀⠀",
            "⡀⠀",
            "⠄⠀",
            "⢂⠀",
            "⡂⠀",
            "⠅⠀",
            "⢃⠀",
            "⡃⠀",
            "⠍⠀",
            "⢋⠀",
            "⡋⠀",
            "⠍⠁",
            "⢋⠁",
            "⡋⠁",
            "⠍⠉",
            "⠋⠉",
            "⠋⠉",
            "⠉⠙",
            "⠉⠙",
            "⠉⠩",
            "⠈⢙",
            "⠈⡙",
            "⢈⠩",
            "⡀⢙",
            "⠄⡙",
            "⢂⠩",
            "⡂⢘",
            "⠅⡘",
            "⢃⠨",
            "⡃⢐",
            "⠍⡐",
            "⢋⠠",
            "⡋⢀",
            "⠍⡁",
            "⢋⠁",
            "⡋⠁",
            "⠍⠉",
            "⠋⠉",
            "⠋⠉",
            "⠉⠙",
            "⠉⠙",
            "⠉⠩",
            "⠈⢙",
            "⠈⡙",
            "⠈⠩",
            "⠀⢙",
            "⠀⡙",
            "⠀⠩",
            "⠀⢘",
            "⠀⡘",
            "⠀⠨",
            "⠀⢐",
            "⠀⡐",
            "⠀⠠",
            "⠀⢀",
            "⠀⡀"
        ]
    },
    "line": {
        "interval": 130,
        "frames": [
            "-",
            "\\",
            "|",
            "/"
        ]
    },
    "line2": {
        "interval": 100,
        "frames": [
            "⠂",
            "-",
            "–",
            "—",
            "–",
            "-"
        ]
    },
    "pipe": {
        "interval": 100,
        "frames": [
            "┤",
            "┘",
            "┴",
            "└",
            "├",
            "┌",
            "┬",
            "┐"
        ]
    },
    "simpleDots": {
        "interval": 400,
        "frames": [
            ".  ",
            ".. ",
            "...",
            "   "
        ]
    },
    "simpleDotsScrolling": {
        "interval": 200,
        "frames": [
            ".  ",
            ".. ",
            "...",
            " ..",
            "  .",
            "   "
        ]
    },
    "star": {
        "interval": 70,
        "frames": [
            "✶",
            "✸",
            "✹",
            "✺",
            "✹",
            "✷"
        ]
    },
    "star2": {
        "interval": 80,
        "frames": [
            "+",
            "x",
            "*"
        ]
    },
    "flip": {
        "interval": 70,
        "frames": [
            "_",
            "_",
            "_",
            "-",
            "`",
            "`",
            "'",
            "´",
            "-",
            "_",
            "_",
            "_"
        ]
    },
    "hamburger": {
        "interval": 100,
        "frames": [
            "☱",
            "☲",
            "☴"
        ]
    },
    "growVertical": {
        "interval": 120,
        "frames": [
            "▁",
            "▃",
            "▄",
            "▅",
            "▆",
            "▇",
            "▆",
            "▅",
            "▄",
            "▃"
        ]
    },
    "growHorizontal": {
        "interval": 120,
        "frames": [
            "▏",
            "▎",
            "▍",
            "▌",
            "▋",
            "▊",
            "▉",
            "▊",
            "▋",
            "▌",
            "▍",
            "▎"
        ]
    },
    "balloon": {
        "interval": 140,
        "frames": [
            " ",
            ".",
            "o",
            "O",
            "@",
            "*",
            " "
        ]
    },
    "balloon2": {
        "interval": 120,
        "frames": [
            ".",
            "o",
            "O",
            "°",
            "O",
            "o",
            "."
        ]
    },
    "noise": {
        "interval": 100,
        "frames": [
            "▓",
            "▒",
            "░"
        ]
    },
    "bounce": {
        "interval": 120,
        "frames": [
            "⠁",
            "⠂",
            "⠄",
            "⠂"
        ]
    },
    "boxBounce": {
        "interval": 120,
        "frames": [
            "▖",
            "▘",
            "▝",
            "▗"
        ]
    },
    "boxBounce2": {
        "interval": 100,
        "frames": [
            "▌",
            "▀",
            "▐",
            "▄"
        ]
    },
    "triangle": {
        "interval": 50,
        "frames": [
            "◢",
            "◣",
            "◤",
            "◥"
        ]
    },
    "arc": {
        "interval": 100,
        "frames": [
            "◜",
            "◠",
            "◝",
            "◞",
            "◡",
            "◟"
        ]
    },
    "circle": {
        "interval": 120,
        "frames": [
            "◡",
            "⊙",
            "◠"
        ]
    },
    "squareCorners": {
        "interval": 180,
        "frames": [
            "◰",
            "◳",
            "◲",
            "◱"
        ]
    },
    "circleQuarters": {
        "interval": 120,
        "frames": [
            "◴",
            "◷",
            "◶",
            "◵"
        ]
    },
    "circleHalves": {
        "interval": 50,
        "frames": [
            "◐",
            "◓",
            "◑",
            "◒"
        ]
    },
    "squish": {
        "interval": 100,
        "frames": [
            "╫",
            "╪"
        ]
    },
    "toggle": {
        "interval": 250,
        "frames": [
            "⊶",
            "⊷"
        ]
    },
    "toggle2": {
        "interval": 80,
        "frames": [
            "▫",
            "▪"
        ]
    },
    "toggle3": {
        "interval": 120,
        "frames": [
            "□",
            "■"
        ]
    },
    "toggle4": {
        "interval": 100,
        "frames": [
            "■",
            "□",
            "▪",
            "▫"
        ]
    },
    "toggle5": {
        "interval": 100,
        "frames": [
            "▮",
            "▯"
        ]
    },
    "toggle6": {
        "interval": 300,
        "frames": [
            "ဝ",
            "၀"
        ]
    },
    "toggle7": {
        "interval": 80,
        "frames": [
            "⦾",
            "⦿"
        ]
    },
    "toggle8": {
        "interval": 100,
        "frames": [
            "◍",
            "◌"
        ]
    },
    "toggle9": {
        "interval": 100,
        "frames": [
            "◉",
            "◎"
        ]
    },
    "toggle10": {
        "interval": 100,
        "frames": [
            "㊂",
            "㊀",
            "㊁"
        ]
    },
    "toggle11": {
        "interval": 50,
        "frames": [
            "⧇",
            "⧆"
        ]
    },
    "toggle12": {
        "interval": 120,
        "frames": [
            "☗",
            "☖"
        ]
    },
    "toggle13": {
        "interval": 80,
        "frames": [
            "=",
            "*",
            "-"
        ]
    },
    "arrow": {
        "interval": 100,
        "frames": [
            "←",
            "↖",
            "↑",
            "↗",
            "→",
            "↘",
            "↓",
            "↙"
        ]
    },
    "arrow2": {
        "interval": 80,
        "frames": [
            "⬆️ ",
            "↗️ ",
            "➡️ ",
            "↘️ ",
            "⬇️ ",
            "↙️ ",
            "⬅️ ",
            "↖️ "
        ]
  },
    "arrow3": {
        "interval": 120,
        "frames": [
            "▹▹▹▹▹",
            "▸▹▹▹▹",
            "▹▸▹▹▹",
            "▹▹▸▹▹",
            "▹▹▹▸▹",
            "▹▹▹▹▸"
        ]
  },
  "arrow4": {
        "interval": 80,
        "frames": [
      "[>            ]",
      "[>>>          ]",
      "[>>>>>        ]",
      "[>>>>>>>      ]",
      "[>>>>>>>>>    ]",
      "[>>>>>>>>>>>  ]",
      "[>>>>>>>>>>>>>]"
        ]
    },
    "bouncingBar": {
        "interval": 80,
        "frames": [
            "[    ]",
            "[=   ]",
            "[==  ]",
            "[=== ]",
            "[ ===]",
            "[  ==]",
            "[   =]",
            "[    ]",
            "[   =]",
            "[  ==]",
            "[ ===]",
            "[====]",
            "[=== ]",
            "[==  ]",
            "[=   ]"
        ]
    },
    "bouncingBall": {
        "interval": 80,
        "frames": [
            "( ●    )",
            "(  ●   )",
            "(   ●  )",
            "(    ● )",
            "(     ●)",
            "(    ● )",
            "(   ●  )",
            "(  ●   )",
            "( ●    )",
            "(●     )"
        ]
    },
    "smiley": {
        "interval": 200,
        "frames": [
            "😄 ",
            "😝 "
        ]
    },
    "monkey": {
        "interval": 300,
        "frames": [
            "🙈 ",
            "🙈 ",
            "🙉 ",
            "🙊 "
        ]
    },
    "hearts": {
        "interval": 100,
        "frames": [
            "💛 ",
            "💙 ",
            "💜 ",
            "💚 ",
            "❤️ "
        ]
    },
    "clock": {
        "interval": 100,
        "frames": [
            "🕛 ",
            "🕐 ",
            "🕑 ",
            "🕒 ",
            "🕓 ",
            "🕔 ",
            "🕕 ",
            "🕖 ",
            "🕗 ",
            "🕘 ",
            "🕙 ",
            "🕚 "
        ]
    },
    "earth": {
        "interval": 180,
        "frames": [
            "🌍 ",
            "🌎 ",
            "🌏 "
        ]
    },
    "moon": {
        "interval": 80,
        "frames": [
            "🌑 ",
            "🌒 ",
            "🌓 ",
            "🌔 ",
            "🌕 ",
            "🌖 ",
            "🌗 ",
            "🌘 "
        ]
    },
    "runner": {
        "interval": 140,
        "frames": [
            "🚶 ",
            "🏃 "
        ]
    },
    "pong": {
        "interval": 80,
        "frames": [
            "▐⠂       ▌",
            "▐⠈       ▌",
            "▐ ⠂      ▌",
            "▐ ⠠      ▌",
            "▐  ⡀     ▌",
            "▐  ⠠     ▌",
            "▐   ⠂    ▌",
            "▐   ⠈    ▌",
            "▐    ⠂   ▌",
            "▐    ⠠   ▌",
            "▐     ⡀  ▌",
            "▐     ⠠  ▌",
            "▐      ⠂ ▌",
            "▐      ⠈ ▌",
            "▐       ⠂▌",
            "▐       ⠠▌",
            "▐       ⡀▌",
            "▐      ⠠ ▌",
            "▐      ⠂ ▌",
            "▐     ⠈  ▌",
            "▐     ⠂  ▌",
            "▐    ⠠   ▌",
            "▐    ⡀   ▌",
            "▐   ⠠    ▌",
            "▐   ⠂    ▌",
            "▐  ⠈     ▌",
            "▐  ⠂     ▌",
            "▐ ⠠      ▌",
            "▐ ⡀      ▌",
            "▐⠠       ▌"
        ]
    },
    "shark": {
        "interval": 120,
        "frames": [
            "▐|\\____________▌",
            "▐_|\\___________▌",
            "▐__|\\__________▌",
            "▐___|\\_________▌",
            "▐____|\\________▌",
            "▐_____|\\_______▌",
            "▐______|\\______▌",
            "▐_______|\\_____▌",
            "▐________|\\____▌",
            "▐_________|\\___▌",
            "▐__________|\\__▌",
            "▐___________|\\_▌",
            "▐____________|\\▌",
            "▐____________/|▌",
            "▐___________/|_▌",
            "▐__________/|__▌",
            "▐_________/|___▌",
            "▐________/|____▌",
            "▐_______/|_____▌",
            "▐______/|______▌",
            "▐_____/|_______▌",
            "▐____/|________▌",
            "▐___/|_________▌",
            "▐__/|__________▌",
            "▐_/|___________▌",
            "▐/|____________▌"
        ]
    },
    "dqpb": {
        "interval": 100,
        "frames": [
            "d",
            "q",
            "p",
            "b"
        ]
    },
    "weather": {
        "interval": 100,
        "frames": [
            "☀️ ",
            "☀️ ",
            "☀️ ",
            "🌤 ",
            "⛅️ ",
            "🌥 ",
            "☁️ ",
            "🌧 ",
            "🌨 ",
            "🌧 ",
            "🌨 ",
            "🌧 ",
            "🌨 ",
            "⛈ ",
            "🌨 ",
            "🌧 ",
            "🌨 ",
            "☁️ ",
            "🌥 ",
            "⛅️ ",
            "🌤 ",
            "☀️ ",
            "☀️ "
        ]
    },
    "christmas": {
        "interval": 400,
        "frames": [
            "🌲",
            "🎄"
        ]
    },
    "grenade": {
        "interval": 80,
        "frames": [
            "،   ",
            "′   ",
            " ´ ",
            " ‾ ",
            "  ⸌",
            "  ⸊",
            "  |",
            "  ⁎",
            "  ⁕",
            " ෴ ",
            "  ⁓",
            "   ",
            "   ",
            "   "
        ]
    },
    "point": {
        "interval": 125,
        "frames": [
            "∙∙∙",
            "●∙∙",
            "∙●∙",
            "∙∙●",
            "∙∙∙"
        ]
    },
    "layer": {
        "interval": 150,
        "frames": [
            "-",
            "=",
            "≡"
        ]
    },
    "betaWave": {
        "interval": 80,
        "frames": [
            "ρββββββ",
            "βρβββββ",
            "ββρββββ",
            "βββρβββ",
            "ββββρββ",
            "βββββρβ",
            "ββββββρ"
        ]
    }
}
module.exports = style;

upload.js

const chalk = require('chalk') //命令行颜色
const ora = require('ora') // 加载流程动画
const spinner_style = require('./spinner_style') //加载动画样式
const shell = require('shelljs') // 执行shell命令
const node_ssh = require('node-ssh') // ssh连接服务器
const inquirer = require('inquirer') //命令行交互
const zipFile = require('compressing')// 压缩zip
const fs = require('fs') // nodejs内置文件模块
const path = require('path') // nodejs内置路径模块
const CONFIG = require('./config') // 配置

const SSH = new node_ssh();
let config; // 用于保存 inquirer 命令行交互后选择正式|测试版的配置

//logs
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 distDir = path.resolve(__dirname, '../dist'); //待打包
const distZipPath = path.resolve(__dirname, `../dist.zip`); //打包后地址(dist.zip是文件名,不需要更改, 主要在config中配置 PATH 即可)


//项目打包代码 npm run build 
const compileDist = async () => {
  const loading = ora( defaultLog('项目开始打包') ).start();
  loading.spinner = spinner_style.arrow4;
  shell.cd(path.resolve(__dirname, '../'));
  const res = await shell.exec('npm run build'); //执行shell 打包命令
  loading.stop();
  if(res.code === 0) {
    successLog('项目打包成功!');
  } else {
    errorLog('项目打包失败, 请重试!');
    process.exit(); //退出流程
  }
}

//压缩代码
const zipDist = async ()=>{
  defaultLog('项目开始压缩');
  try {
    await zipFile.zip.compressDir(distDir, distZipPath)
    successLog('压缩成功!');
  } catch (error) {
    errorLog(error);
    errorLog('压缩失败, 退出程序!');
    process.exit(); //退出流程
  }
}

//连接服务器
const connectSSH = async ()=>{
  const loading = ora( defaultLog('正在连接服务器') ).start();
  loading.spinner = spinner_style.arrow4;
  try {
    await SSH.connect({
      host: config.SERVER_PATH,
      username: config.SSH_USER,
      // privateKey: config.PRIVATE_KEY, //秘钥登录(推荐) 方式一
      password: config.PASSWORD // 密码登录 方式二
    });
    successLog('SSH连接成功!'); 
  } catch (error) {
    errorLog(error);
    errorLog('SSH连接失败!');
    process.exit(); //退出流程
  }
  loading.stop();
}

//线上执行命令
/**
 * 
 * @param {String} command 命令操作 如 ls
 */
const runCommand = async (command)=> {
  const result = await SSH.exec(command, [], { cwd: config.PATH})
  // defaultLog(result);
}

//清空线上目标目录里的旧文件
const clearOldFile = async () =>{
  const commands = ['ls', 'rm -rf *'];
  await Promise.all(commands.map(async (it)=>{
    return await runCommand(it);
  }));
}

//传送zip文件到服务器
const uploadZipBySSH = async () =>{
  //连接ssh
  await connectSSH();
  //线上目标文件清空
  await clearOldFile();
  const loading = ora( defaultLog('准备上传文件') ).start();
  loading.spinner = spinner_style.arrow4;
  try {
    await SSH.putFiles([{ local: distZipPath, remote: config.PATH + '/dist.zip' }]); //local 本地 ; remote 服务器 ;
    successLog('上传成功!'); 
    loading.text = '正在解压文件';
    await runCommand('unzip ./dist.zip'); //解压
    await runCommand(`rm -rf ${config.PATH}/dist.zip`); //解压完删除线上压缩包
    //将目标目录的dist里面文件移出到目标文件  
    //举个例子 假如我们部署在 /test/html 这个目录下 只有一个网站, 那么上传解压后的文件在 /test/html/dist 里
    //需要将 dist 目录下的文件 移出到 /test/html ;  多网站情况, 如 /test/html/h5  或者 /test/html/admin 都和上面同样道理
    await runCommand(`mv -f ${config.PATH}/dist/*  ${config.PATH}`); 
    await runCommand(`rm -rf ${config.PATH}/dist`); //移出后删除 dist 文件夹
    SSH.dispose(); //断开连接
  } catch (error) {
    errorLog(error);
    errorLog('上传失败!');
    process.exit(); //退出流程
  }
  loading.stop();
}



//------------发布程序---------------
const runUploadTask = async () => {
  console.log(chalk.yellow(`--------->  欢迎使用 2020年自动部署工具  <---------`));
  //打包
  await compileDist();
  //压缩
  await zipDist();
  //连接服务器上传文件
  await uploadZipBySSH(); 
  successLog('大吉大利, 部署成功!'); 
  process.exit();
}

// 开始前的配置检查
/**
 * 
 * @param {Object} conf 配置对象
 */
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(); //退出流程
    }
  })
}

// 执行交互后 启动发布程序
inquirer
  .prompt([{
    type: 'list',
    message: '请选择发布环境',
    name: 'env',
    choices: [{
      name: '测试环境',
      value: 'development'
    },{
      name: '正式环境',
      value: 'production'
    }]
  }])
  .then(answers => {
    config = CONFIG[answers.env];
    checkConfig(config); // 检查
    runUploadTask(); // 发布
  });

另 在部署过程中出现 bash: unzip: command not found 进入远端服务器下载 运行 yum install -y unzip zip 下载解压插件再次运行就行

推荐阅读更多精彩内容