node实现自动打包部署前端nuxt项目

作为一个前端开发,本不需要自己部署项目,奈何天不遂人愿,活最终还是落到了自己头上,刚开始只是部署测试环境,只有一台服务器,手动部署以下也就算了,后面线上环境部署4台服务器,人当时就麻了
在这里插入图片描述

对于喜爱摸鱼的我来说,严重耽误我摸鱼的时间,于是乎就在想能否写一个node脚本执行命令自动部署呢,想了一下,还是决定动手试试

最开始是想通过执行shell命令打包项目,然后通过node进行io操作打包,再通过ssh2上传到服务器进行部署

后面一想,都用到了shell,何不干脆直接通过shell命令打包,压缩呢,于是乎,引入了
child_process,使用同步执行命令,手动进度,哈哈哈哈

const { spawnSync } = require('child_process');

const itemPath = "/Volumes/xxxx/" //项目路径
const filePath = "/Users/admin/Desktop/" //项目打包后的路径

function exec(item, cmd) {
  const child = spawnSync(cmd, { shell: true, stdio: ['pipe', 'inherit', 'inherit'] });
  if (child.status != 0) {
    console.log(`[${getNowTime(0)}] ${item}失败`);
  } 
}

// 打包项目
function build() {
  const cmdList = {
    '打包中(0%)': 'cd ' + itemPath + ' && ' + runCmd + ' && touch -m -a ' + itemPath,
    '打包中(10%)': 'cd ' + filePath + ' && rm -rf build build.zip dist dist.zip && mkdir -p build/.nuxt build/static build/node_modules  dist',
    '打包中(20%)': '(cd ' + itemPath + '.nuxt/ && tar cf - .) | (cd ' + filePath + 'build/.nuxt && tar xpf -)',
    '打包中(30%)': '(cd ' + itemPath + 'static/ && tar cf - .) | (cd ' + filePath + 'build/static && tar xpf -)',
    '打包中(40%)': '(cd ' + itemPath + 'node_modules/ && tar cf - .) | (cd ' + filePath + 'build/node_modules && tar xpf -)',
    '打包中(50%)': 'cp -rf ' + itemPath + 'package.json ' + filePath + 'build',
    '打包中(60%)': 'cp -rf ' + itemPath + 'nuxt.config.js ' + filePath + 'build',
    '打包中(70%)': 'cp -rf ' + itemPath + 'config.js ' + filePath + 'build',
    '打包中(80%)': 'cp -rf ' + itemPath + 'package-lock.json ' + filePath + 'build',
    '打包中(90%)': 'cp -rf ' + itemPath + 'ecosystem.config.js ' + filePath + 'build',
    '打包中(100%)': '(cd ' + itemPath + '.nuxt/dist/ && tar cf - .) | (cd ' + filePath + 'dist && tar xpf -)', //xpvf
  }
  try {
    Object.keys(cmdList).forEach(function (item, index) {
      exec(item, cmdList[item])
    })
  } catch (error) {
    console.log(error);
  }
}

(之所以复制node_modules是因为服务器安装会导致出问题,公司大佬也没解决,先凑合用一下)第一步搞定,项目打包并复制到新的文件夹

第二步压缩文件夹

const fs = require('fs')
const archiver = require('archiver')
function compressFile(targetDir, outputDir, fileName) {
  return new Promise((resolve, reject) => {
    if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir); // 如果文件夹不存在,则创建
    let output = fs.createWriteStream(`${outputDir}/${fileName}.zip`) // 创建⽂件写⼊流
    const archive = archiver('zip', { zlib: { level: 9 } }) // 设置压缩等级

    output.on('close', () => {
      resolve(' 压缩完成')
    }).on('error', (err) => {
      reject(console.error(`[${getNowTime(0)}] ${fileName} 压缩失败`, err))
    })

    archive.pipe(output)
    archive.directory(targetDir, fileName) // 存储⽬标⽂件 
    archive.finalize() // 完成归档
  })
}

async function building() {
  // 打包
  build()
  // 压缩
  try {
    await compressFile(`${filePath}build`, filePath, 'build')
    await compressFile(`${filePath}dist`, filePath, 'dist')
  } catch (error) {
    console.log(error);
  }
}

第三步上传到服务器,解压后执行

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

const uploadBuildFilePath = filePath + 'build.zip' // 本地包地址 需加上文件名和后缀
const uploadDistFilePath = filePath + 'dist.zip' // 本地包地址 需加上文件名和后缀

const serverBuildPath = '/home/xxx/build.zip' //服务器上传地址 需加上文件名和后缀
const serverDistPath = '/home/xxx/dist.zip' //服务器静态资源上传地址 需加上文件名和后缀

const projectBuildPath = '/usr/local/xxx/' //服务器项目运行路径
const projectDistPath = '/xxx/'//服务器静态资源路径
let runCmd = 'npm run build' // 打包命令
// 服务器列表
let serverList = []
// 正式服务器
const proServerList = [
  {
    name:'static',
    cmd: 'su',
    shell: 'unzip -o -q ' + serverDistPath + ' -d ' + projectDistPath,
    pm2: '',
    serverName: 'xxx 静态',
    host: '127.0.0.1',
    port: '22',
    username: 'xxx',
    password: 'aaaa',
    rootpass: 'ddddd',
    serverPath: serverDistPath,
    filePath: uploadDistFilePath
  },
  {
    name:'xxxaaa',
    cmd: 'su',
    shell: 'unzip -o -q ' + serverBuildPath + ' -d ' + projectBuildPath + ' && cd ' + projectBuildPath + 'build && pm2 delete all && npm run pm_4',
    pm2: 'npm run pm_4',
    serverName: 'xxx 4节点',
    host: '127.0.0.1',
    port: '22',
    username: 'xxxx',
    password: 'xxx',
    rootpass: 'xxxx',
    serverPath: serverBuildPath,
    filePath: uploadBuildFilePath
  }
  // ....
]
// 测试服务器
const devServerList = [
  {
    cmd: 'su',
    shell: 'unzip -o -q /root/build.zip -d /xxx/ && cd /xxx/build && pm2 delete all && npm run pm2-test',
    pm2: 'npm run pm2-test',
    serverName: 'xxx 测试',
    host: '127.0.0.1',
    port: '22',
    username: 'root',
    password: 'admin',
    rootpass: 'admin',
    serverPath: '/aaa/build.zip',
    filePath: uploadBuildFilePath
  },
]
function Connect(server, then) {
  var conn = new Client();
  conn.on("ready", function () {
    then(conn);
  }).on('error', function (err) {
    // console.log("connect error!");
  }).on('end', function () {
    // console.log("connect end!");
  }).on('close', function (had_error) {
    // console.log("connect close");
  }).connect(server);
}

function UploadFile(server, info, cmd, then) {
  let unzip = false
  let x = 0
  let y = 0
  let end = false

  progressBarC.run(0)
  Connect(server, function (conn) {
    conn.sftp(function (err, sftp) {
      if (err) {
        then(err);
      } else {
        progressBarC.run(5)
        // console.log(`[${getNowTime(0)}] 服务器连接成功`);
        // console.log(`[${getNowTime(0)}] 开始上传压缩包`);
        sftp.fastPut(info.filePath, info.serverPath, function (err, result) {
          if (err) console.log(err);
          // console.log(`[${getNowTime(0)}] 压缩包上传成功`);
          // console.log(`[${getNowTime(0)}] 开始部署`);
          conn.exec(cmd, { pty: true }, function (err, stream) {
            if (err) {
              then(err, 0);
            } else {// end of if
              stream.on('data', async function (data, extended) {
                if (data.toString().indexOf('Password:') >= 0) {
                  // console.log(`[${getNowTime(0)}] 切换root`);
                  stream.write(info.rootpass + '\n');
                }
                //... 自己的逻辑
              }).on('close', async (code, signal) => {
                // console.log(`[${getNowTime(0)}] 部署完成`);
                then(0, 1)
                conn.end();
              });
            }
          });
        });
      }
    });
  });
}

function uploadFile(index) {
  if (index < serverList.length) {
    let item = serverList[index]
    let server = {
      host: item.host, // 服务器 IP
      port: item.port,
      username: item.username,
      password: item.password
    }
    UploadFile(server, item, item.cmd, (err, res) => {
      if (res > 0) {
        index++
        uploadFile(index)
      } else {
        console.log(err);
      }
    });
  }
}

因为要切换root所以,要输入root密码,然后自己根据输出信息判断是否切换成功,再去执行解压操作,然后通过pm2管理进程即可

最后执行这个脚本

async function deploy() {
  // npm run deploy         全部部署 自动打包
  // 全部部署 或 部分部署
  if (process.env.ENV_DEPLOY == 'all') {
    serverList = proServerList
  } else {
    let server = proServerList.find(item => {
      return item.name == process.env.ENV_DEPLOY
    })
    serverList = server
  }
  // 测试环境部署
  if (process.env.NODE_ENV == 'dev') {
    serverList = devServerList
    runCmd = 'npm run build-test'
  }
  // 是否需要打包,npm run deploy-build
  if(process.env.ENV_BUILD == 'true'){
    //打包压缩
    await building()
  }
  // 部署到服务器
  if (process.env.ENV_DEPLOY != 'no') {
    // 服务器部署
    uploadFile(0)
  }
}

然后配置一下package.json

	"deploy": "cross-env NODE_ENV=production ENV_DEPLOY=all ENV_BUILD=true ENV_BASE_URL=server.xxx.com node deploy.js",
    "deploy-build": "cross-env NODE_ENV=production ENV_DEPLOY=no ENV_BUILD=true ENV_BASE_URL=server.xxx.com node deploy.js",
    "deploy-static": "cross-env NODE_ENV=production ENV_DEPLOY=static ENV_BUILD=false ENV_BASE_URL=server.xxx.com node deploy.js",
    "deploy-107": "cross-env NODE_ENV=production ENV_DEPLOY=107 ENV_BUILD=false ENV_BASE_URL=server.xxx.com node deploy.js",
    "deploy-181": "cross-env NODE_ENV=production ENV_DEPLOY=181 ENV_BUILD=false ENV_BASE_URL=server.xxx.com node deploy.js",
    "deploy-150": "cross-env NODE_ENV=production ENV_DEPLOY=150 ENV_BUILD=false ENV_BASE_URL=server.xxx.com node deploy.js",
    "deploy-test": "cross-env NODE_ENV=dev ENV_DEPLOY=all ENV_BUILD=true ENV_BASE_URL=192.168.3.39:19083 node deploy.js",

为了方便观察进度,使用@jyeontu/progress-bar 进行设置,最后附上完整代码

const fs = require('fs')
const path = require("path")
const archiver = require('archiver')
const { spawnSync } = require('child_process');
const Client = require("ssh2").Client;
const progressBar = require('@jyeontu/progress-bar');

const itemPath = "/Volumes/aaa/bbb/ccc/" //项目路径
const filePath = "/Users/admin/Desktop/" //项目打包后的路径

const uploadBuildFilePath = filePath + 'build.zip' // 本地包地址 需加上文件名和后缀
const uploadDistFilePath = filePath + 'dist.zip' // 本地包地址 需加上文件名和后缀

const serverBuildPath = '/home/xxx/build.zip' //服务器上传地址 需加上文件名和后缀
const serverDistPath = '/home/xxx/dist.zip' //服务器静态资源上传地址 需加上文件名和后缀

const projectBuildPath = '/usr/local/ccc/' //服务器项目运行路径
const projectDistPath = '/xxx/aaa/pc/nuxtstatic/'//服务器静态资源路径
let runCmd = 'npm run build' // 打包命令
// 服务器列表
let serverList = []
// 正式服务器
const proServerList = [
  {
    name:'static',
    cmd: 'su',
    shell: 'unzip -o -q ' + serverDistPath + ' -d ' + projectDistPath,
    pm2: '',
    serverName: '219 静态',
    host: '192.168.0.219',
    port: '22',
    username: 'admin',
    password: '123',
    rootpass: '456',
    serverPath: serverDistPath,
    filePath: uploadDistFilePath
  },
  {
    name:'107',
    cmd: 'su',
    shell: 'unzip -o -q ' + serverBuildPath + ' -d ' + projectBuildPath + ' && cd ' + projectBuildPath + 'build && pm2 delete all && npm run pm_4',
    pm2: 'npm run pm_4',
    serverName: '107 4节点',
    host: '192.168.0.107',
    port: '22',
    username: 'admin',
    password: '123022',
    rootpass: '3452022',
    serverPath: serverBuildPath,
    filePath: uploadBuildFilePath
  },
  {
    name:'181',
    cmd: 'su',
    shell: 'unzip -o -q ' + serverBuildPath + ' -d ' + projectBuildPath + ' && cd ' + projectBuildPath + 'build && pm2 delete all && npm run pm_4',
    pm2: 'npm run pm_4',
    serverName: '181 4节点',
    host: '192.168.0.181',
    port: '22',
    username: 'admin',
    password: '123022',
    rootpass: '1231241',
    serverPath: serverBuildPath,
    filePath: uploadBuildFilePath
  },
  {
    name: '150',
    cmd: 'su',
    shell: 'unzip -o -q ' + serverBuildPath + ' -d ' + projectBuildPath + ' && cd ' + projectBuildPath + 'build && pm2 delete all && npm run pm_8',
    pm2: 'npm run pm_8',
    serverName: '150 8节点',
    host: '192.168.0.150',
    port: '22',
    username: 'admin',
    password: 'nsda2021',
    rootpass: 'nsdasda022',
    serverPath: serverBuildPath,
    filePath: uploadBuildFilePath
  }
]
// 测试服务器
const devServerList = [
  {
    cmd: 'su',
    shell: 'unzip -o -q /root/build.zip -d /www/wwwroot/ccc/ && cd /www/wwwroot/ccc/build && pm2 delete all && npm run pm2-test',
    pm2: 'npm run pm2-test',
    serverName: '195 测试',
    host: '192.168.0.195',
    port: '22',
    username: 'root',
    password: 'test',
    rootpass: 'aaaa',
    serverPath: '/root/build.zip',
    filePath: uploadBuildFilePath
  },
]

// 进度条配置
let config = {
  duration: 100,
  current: 0,
  block: '█',
  showNumber: true,
  tip: {
    0: `打包中...`,
    100: `打包完成`
  },
  color: 'green'
}
let progressBarC = new progressBar(config);

// 获取当前时间
function getNowTime(type) {
  let date = new Date()
  let year = date.getFullYear()
  let month = date.getMonth() + 1
  let day = date.getDate()
  let hours = date.getHours();
  let minute = date.getMinutes();
  let second = date.getSeconds();
  month = month < 10 ? ('0' + month) : month;
  day = day < 10 ? ('0' + day) : day;
  hours = hours < 10 ? ('0' + hours) : hours;
  minute = minute < 10 ? ('0' + minute) : minute;
  second = second < 10 ? ('0' + second) : second;
  if (type == 0) {
    return `${year}-${month}-${day} ${hours}:${minute}:${second}`
  }
  if (type == 1) {
    return `${year}-${month}-${day}`
  }
  if (type == 2) {
    return `${year}`
  }
}

// 执行shell 命令打包项目
function execSyncs(item, cmd, index) {
  // console.log(`[${getNowTime(0)}] ${item}`);
  const child = spawnSync(cmd, { shell: true, stdio: ['pipe', 'inherit', 'inherit'] });
  if (child.status != 0) {
    console.log(`[${getNowTime(0)}] ${item}失败`);
  } else {
    config.tip['0'] = '打包中...'
    config.tip['100'] = '打包完成'
    progressBarC.run(index * 10)
  }
}

// 打包项目
function build() {
  const cmdList = {
    '打包中(0%)': 'cd ' + itemPath + ' && ' + runCmd + ' && touch -m -a ' + itemPath,
    '打包中(10%)': 'cd ' + filePath + ' && rm -rf build build.zip dist dist.zip && mkdir -p build/.nuxt build/static build/node_modules  dist',
    '打包中(20%)': '(cd ' + itemPath + '.nuxt/ && tar cf - .) | (cd ' + filePath + 'build/.nuxt && tar xpf -)',
    '打包中(30%)': '(cd ' + itemPath + 'static/ && tar cf - .) | (cd ' + filePath + 'build/static && tar xpf -)',
    '打包中(40%)': '(cd ' + itemPath + 'node_modules/ && tar cf - .) | (cd ' + filePath + 'build/node_modules && tar xpf -)',
    '打包中(50%)': 'cp -rf ' + itemPath + 'package.json ' + filePath + 'build',
    '打包中(60%)': 'cp -rf ' + itemPath + 'nuxt.config.js ' + filePath + 'build',
    '打包中(70%)': 'cp -rf ' + itemPath + 'config.js ' + filePath + 'build',
    '打包中(80%)': 'cp -rf ' + itemPath + 'package-lock.json ' + filePath + 'build',
    '打包中(90%)': 'cp -rf ' + itemPath + 'ecosystem.config.js ' + filePath + 'build',
    '打包中(100%)': '(cd ' + itemPath + '.nuxt/dist/ && tar cf - .) | (cd ' + filePath + 'dist && tar xpf -)', //xpvf
  }
  try {
    Object.keys(cmdList).forEach(function (item, index) {
      execSyncs(item, cmdList[item], index)
    })
  } catch (error) {
    console.log(error);
  }
}


function Connect(server, then) {
  var conn = new Client();
  conn.on("ready", function () {
    then(conn);
  }).on('error', function (err) {
    // console.log("connect error!");
  }).on('end', function () {
    // console.log("connect end!");
  }).on('close', function (had_error) {
    // console.log("connect close");
  }).connect(server);
}


// 上传文件
function UploadFile(server, info, cmd, then) {
  let unzip = false
  let x = 0
  let y = 0
  let end = false

  config.tip['0'] = `[${info.serverName}]部署中...`
  config.tip['100'] = `[${info.serverName}]部署完成`

  progressBarC.run(0)
  Connect(server, function (conn) {
    conn.sftp(function (err, sftp) {
      if (err) {
        then(err);
      } else {
        progressBarC.run(5)
        // console.log(`[${getNowTime(0)}] 服务器连接成功`);
        // console.log(`[${getNowTime(0)}] 开始上传压缩包`);
        sftp.fastPut(info.filePath, info.serverPath, function (err, result) {
          if (err) console.log(err);
          progressBarC.run(20)
          // console.log(`[${getNowTime(0)}] 压缩包上传成功`);
          // console.log(`[${getNowTime(0)}] 开始部署`);
          conn.exec(cmd, { pty: true }, function (err, stream) {
            if (err) {
              then(err, 0);
            } else {// end of if
              stream.on('data', async function (data, extended) {
                if (data.toString().indexOf('Password:') >= 0) {
                  progressBarC.run(25)
                  // console.log(`[${getNowTime(0)}] 切换root`);
                  stream.write(info.rootpass + '\n');
                }
                if (data.toString().indexOf('[root@') >= 0) {
                  if (!unzip) {
                    progressBarC.run(27)
                    // console.log(`[${getNowTime(0)}] 登录root成功`);
                    // console.log(`[${getNowTime(0)}] 开始解压压缩包`);
                    stream.write(info.shell + '\n');
                    unzip = true
                    if (y == 0) {
                      progressBarC.run(45)
                      y++
                    }
                  }
                }

                // ...自己的逻辑
              }).on('close', async (code, signal) => {
                // console.log(`[${getNowTime(0)}] 部署完成`);
                then(0, 1)
                conn.end();
              });
            }
          });
        });
      }
    });
  });
}

// 按序连接服务器 上传文件
function uploadFile(index) {
  if (index < serverList.length) {
    let item = serverList[index]
    let server = {
      host: item.host, // 服务器 IP
      port: item.port,
      username: item.username,
      password: item.password
    }
    UploadFile(server, item, item.cmd, (err, res) => {
      if (res > 0) {
        index++
        uploadFile(index)
      } else {
        console.log(err);
      }
    });
  }
}


// 压缩文件 zip
function compressFile(targetDir, outputDir, fileName) {
  return new Promise((resolve, reject) => {
    if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir); // 如果文件夹不存在,则创建
    let output = fs.createWriteStream(`${outputDir}/${fileName}.zip`) // 创建⽂件写⼊流
    const archive = archiver('zip', { zlib: { level: 9 } }) // 设置压缩等级

    output.on('close', () => {
      resolve(' 压缩完成')
    }).on('error', (err) => {
      reject(console.error(`[${getNowTime(0)}] ${fileName} 压缩失败`, err))
    })
    let unzip = false
    getdirsize(targetDir, function (err, data) {
      if (!err) {
        config.tip['0'] = `[${fileName}.zip]压缩中...`
        config.tip['100'] = `[${fileName}.zip]压缩完成`
        archive.on('data', function (param) {
          let num = Math.floor((archive._fsEntriesTotalBytes / data) * 100)
          if (!unzip) {
            progressBarC.run(num)
          }
          if (num == 100) {
            unzip = true
          }
        })
      }
    })

    archive.pipe(output)
    archive.directory(targetDir, fileName) // 存储⽬标⽂件 
    archive.finalize() // 完成归档
  })
}

// 获取文件大小
function getdirsize(dir, callback) {
  var size = 0;
  fs.stat(dir, function (err, stats) {
    if (err) return callback(err);//如果出错
    if (stats.isFile()) return callback(null, stats.size);//如果是文件

    fs.readdir(dir, function (err, files) {//如果是目录
      if (err) return callback(err);//如果遍历目录出错
      if (files.length == 0) return callback(null, 0);//如果目录是空的

      var count = files.length;//哨兵变量
      for (var i = 0; i < files.length; i++) {
        getdirsize(path.join(dir, files[i]), function (err, _size) {
          if (err) return callback(err);
          size += _size;
          if (--count <= 0) {//如果目录中所有文件(或目录)都遍历完成
            callback(null, size);
          }
        });
      }
    });
  });
}

async function building() {
  // 打包
  build()
  console.log('');
  // 压缩
  try {
    await compressFile(`${filePath}build`, filePath, 'build')
    console.log('');
    await compressFile(`${filePath}dist`, filePath, 'dist')
    console.log('');
  } catch (error) {
    console.log(error);
  }
}
// 部署
async function deploy() {
  // npm run deploy         全部部署 自动打包
  // npm run deploy-build   手动打包  // npm run deploy-static  部署静态 不打包
  // npm run deploy-107     部署107 不打包
  // npm run deploy-181     部署184 不打包
  // npm run deploy-150     部署150 不打包
  // 全部部署 或 部分部署
  serverList = []
  if (process.env.ENV_DEPLOY == 'all') {
    serverList = proServerList
  } else {
    let server = proServerList.find(item => {
      return item.name == process.env.ENV_DEPLOY
    })
    serverList.push(server)
  }
  // 测试环境部署
  if (process.env.NODE_ENV == 'dev') {
    serverList = devServerList
    runCmd = 'npm run build-test'
  }
  // 是否需要打包,npm run deploy-build
  if(process.env.ENV_BUILD == 'true'){
    //打包压缩
    await building()
  }
  // 部署到服务器
  if (process.env.ENV_DEPLOY != 'no') {
    // 服务器部署
    uploadFile(0)
  }
}


deploy()

大功告成,大家可以根据自己需求自己改动
最后运行一下看下效果npm run deploy-build(我这里是分步骤执行,也可以直接全部部署)
在这里插入图片描述
npm run deploy-static
在这里插入图片描述
npm run deploy-107
在这里插入图片描述
npm run deploy 全部部署
在这里插入图片描述
对node使用不是很熟悉,不足之处请指正

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值