node脚本实现前端轻量化自动部署

    想直接使用的,github 传送门 -   git上有详细配置,记得留个star,笔心

    但最近做的项目修改频繁,每次部署都是先打包,然后手动拷贝到远程服务器,次数多了有点麻烦,身为一个程序员,秉着偷懒的原则,程序能完成的重复工作绝不自己完成,于是就写了个Node小脚本。

    在写脚本之前,我们需要了解下package.json,nodejs工程的自动化是依赖于package.json文件中的scripts配置项来实现的,例如使用vue-cli搭建的工程中就会带有:

{
   ...
   "scripts": {
       "serve": "vue-cli-service serve",
       "build": "vue-cli-service build",
       "lint": "vue-cli-service lint"
     },
   ...
}

     当我们在命令行执行:npm run serve ,其实是在执行vue-cli-service serve,我们也可以自己写一个自动部署脚本:

当我们在命令行执行:npm run serve ,其实是在执行vue-cli-service serve,我们也可以自己写一个自动部署脚本:

      我们希望脚本能在npm run build之后自行执行,这里可以利用npm声明周期中的post钩子:

  

  "scripts": {
    "build": "node start.js",
    "dev": "node satrt.js",
    "postbuild": "node deploy.js"
  },

      这里稍微解释下npm 声明周期,我觉得最有用的就是pre和post,如果你想在某个脚本执行完之后执行其他脚本,可以使用前缀+脚本名,这里使用的是postbuild,同理,prebuild就是在执行build之前执行其他脚本。

      脚本流程如下:

          1.登陆服务器,读取服务器网站目录列表,选择上传的目录(你也可以新建目录)

          2. 备份服务器之前的文件,然后覆盖上传。

          3.上传完毕

  const path = require('path')
  const moment = require('moment')
  const util = require('util')
  const events = require('events')
  const Client = require('ssh2').Client
  const fs = require('fs')
  const ProgressBar =require('progress');
  const  inquirer=require('inquirer')

  //cnpm install moment util events ssh2 progress inquirer --dev  请先在deploy所在目录安装以上包

  /******************************请手动配置以下内容*********************************/  
   /** 远程服务器配置
   * @type {{password: string, port: number, host: string, username: string}}
   */

  const server = {
    host: 'xx.xx.xx.xx',  //主机ip
    port: 22,                //SSH 连接端口,默认22
    username: 'name',        //用户名
    password: 'pwd',     //用户登录密码
  }
  const basePath = '/web'     //服务器网站根目录
  let baseDir = ''            //项目目录名称
  let  back_up_dir='' //备份目录名称,需手动在服务器创建,可选,注意目录名后有斜杠  比如  back_up/
  const bakDirName = baseDir + '.bak' + moment(new Date()).format('YYYY-M-D-HH:mm:ss')//备份文件名
  const buildPath = path.resolve('./dist')//本地项目编译后的文件目录

  /**********************************配置结束***************************************/

  
  function doConnect(server, then) { //连接服务器
    const conn = new Client()
    conn.on('ready', function () {
      then && then(conn)
    }).on('error', function (err) {
      console.error('connect error!', err)
    }).on('close', function () {
      conn.end()
    }).connect(server)
  }
  
  function doShell(server, cmd, then) { //执行远程命令
    doConnect(server, function (conn) {
      conn.shell(function (err, stream) {
        if (err) throw err
        else {
          let buf = ''
          stream.on('close', function () {
            conn.end()
            then && then(err, buf)
          }).on('data', function (data) {
            buf = buf + data
          }).stderr.on('data', function (data) {
            console.log('stderr: ' + data)
          })
          stream.end(cmd)
        }
      })
    })
  }
   
  function doGetFileAndDirList(localDir, dirs, files) { //递归获取所以文件目录
    const dir = fs.readdirSync(localDir)
    for (let i = 0; i < dir.length; i++) {
      const p = path.join(localDir, dir[i])
      const stat = fs.statSync(p)
      if (stat.isDirectory()) {
        dirs.push(p)
        doGetFileAndDirList(p, dirs, files)
      }
      else {
        files.push(p)
      }
    }
  }
  
  function Control() {
    events.EventEmitter.call(this)
  }
  
  util.inherits(Control, events.EventEmitter)
  
  const control = new Control()
  
  control.on('doNext', function (todos, then) {
    if (todos.length > 0) {
      const func = todos.shift()
      func(function (err, result) {
        if (err) {
          then(err)
          throw err
        }
        else {
          control.emit('doNext', todos, then)
        }
      })
    }
    else {
      then(null)
    }
  })
  
  function doUploadFile(server, localPath, remotePath, then) { //上传文件
    doConnect(server, function (conn) {
      conn.sftp(function (err, sftp) {
        if (err) {
          then(err)
        }
        else {
          sftp.fastPut(localPath, remotePath, function (err, result) {
            conn.end()
            then(err, result)
          })
        }
      })
    })
  }
  
  function doUploadDir(server, localDir, remoteDir, then) { 
    let dirs = []
    let files = []
    doGetFileAndDirList(localDir, dirs, files)
  
    // 创建远程目录
    console.log('开始创建远程目录')
    let todoDir = []
    dirs.forEach(function (dir) {
      todoDir.push(function (done) {
        const to = path.join(remoteDir, dir.slice(localDir.length + 1)).replace(/[\\]/g, '/')
        const cmd = 'mkdir -p ' + to + '\r\nexit\r\n'
        // console.log(`cmd::${cmd}`)
        doShell(server, cmd, done)
      })// end of push
    })
  
    // 上传文件
    console.log('准备上传文件:')
    let todoFile = []
    let total=files.length;
    let bar=new ProgressBar('上传进度:[:bar] :percent   剩余时长::etas',{total,width: 50});
    files.forEach(function (file) { 
      todoFile.push(function (done) {
        const to = path.join(remoteDir, file.slice(localDir.length + 1)).replace(/[\\]/g, '/')
        // console.log('upload ' + to)
        bar.tick(1);
        doUploadFile(server, file, to, done)
      })
    })
    control.emit('doNext', todoDir, function (err) {
      if (err) {
        throw err
      }
      else {
        control.emit('doNext', todoFile, then)
      }
    })
  }

  
  let mutual={
    chooseDir:function (err,dirList){
        if(err){
            console.log(err)
            return false
        }
        dirList.unshift('我要新建目录')
          const promptList = [
              {
                  type: 'list',
                  message: '请选择要上传到的项目目录:',
                  name: 'dir',
                  choices:dirList,
              }
          ];
      
          inquirer.prompt(promptList).then(answers => {
              
              if(answers.dir==='我要新建目录'){
                  mutual.mkNewDir()
                  return false
              }else if(answers.dir.includes(basePath)){
                baseDir=basePath
              }
              else{
                baseDir=answers.dir
              }
              init()
        
          }).catch(err=>{
              console.log(err)
          })
    },
    mkNewDir:function(){
        const promptList = [
            {
                type: 'input',
                message: '请输入要创建的目录名称:',
                name: 'dir',    
        
            }
        ];
    
        inquirer.prompt(promptList).then(answers => {
            if(!answers.dir){
                console.error('警告:文件名不能为空')
                return false
            }
            baseDir=answers.dir
            init()
        })
    }

  }




/**
* 描述:获取远程文件路径下文件列表信息
* 参数:server 远程电脑凭证;
*		remotePath 远程路径;
*		isFile 是否是获取文件,true获取文件信息,false获取目录信息;
*		then 回调函数
* 回调:then(err, dirs) : dir, 获取的列表信息
*/
function getFileOrDirList(server, remotePath, isFile, then){
	var cmd = "find " + remotePath + " -type "+ (isFile == true ? "f":"d") + "\r\nexit\r\n";	
	doShell(server, cmd, function(err, data){
		var arr = [];
		var remoteFile = [];
		arr = data.split("\r\n");
		arr.forEach(function(dir){
			if(dir.indexOf(remotePath) ==0){
				remoteFile.push(dir);
			}
        });
        remoteFile=remoteFile.map(item=>item.split('/')[2]).filter(item=> item&&item.trim()) //只保留第一层目录
        remoteFile.push(basePath+'(项目根目录)') //上传网站首页文件地址
        remoteFile=Array.from(new Set(remoteFile)) //去重
		then(err, remoteFile);
    })
};

  
function init(){
    console.log('\n--------配置如下--------------\n')
    console.log(`服务器host:            ${server.host}`)
    console.log(`项目文件夹:            ${baseDir}`)
    console.log(`项目部署以及备份目录:  ${basePath}`)
    console.log(`备份后的文件夹名:      ${bakDirName}`)
    console.log('\n--------开始部署--------------\n')
    doShell(server, `mv ${basePath}/${baseDir} ${basePath}/${ back_up_dir}${bakDirName}\nexit\n`) //备份远程目录文件
    doUploadDir(server, buildPath, `${basePath}/${baseDir}`, () => console.log('\n--------部署成功--------------'))
}


getFileOrDirList(server,basePath,false,mutual.chooseDir)

     代码参考:https://github.com/hello-jun/deploy

     因为我本地一个文件目录下有多个项目,上传到的文件服务器目录也不同,因为我利用inquirer 添加了用户交互功能,你可以选择任意一个目录或新建目录上传,此外还新增了上传进度条,可以对上传进度一目了然。

     建议:为了避免账号密码泄露,账号密码最好从其他文件夹中导入,免得多人共享git导致密码泄露,登陆账号权限最好也不是超级管理员。

    使用截图:

node脚本实现前端轻量化自动部署_第1张图片

 

你可能感兴趣的:(前端,javascript,node.js,npm)