美文网首页vue
ssh2实现vue项目自动化打包发布

ssh2实现vue项目自动化打包发布

作者: echo帅 | 来源:发表于2018-10-16 11:50 被阅读889次

    如今,前后端分离越来越流行,前端项目的各种打包部署工具也越来越多,可以通过jenkins,pipline等等一键部署。本篇记录使用node的ssh2来进行自动化打包上传

    首先需要明白自动化上传的思路,本篇以本人的导航项目个人导航为蓝本。

    该项目通过vue-cli来生成,默认使用的webpack打包,按照以前的部署方法,应该是npm run build进行打包,然后手动上传至服务器,现在使用ssh2来进行自动化上传。

    • 首先第一步,是下载相应的模块,必须的是ssh2模块ssh2地址
      npm install ssh2
      这是官方的,详细使用查看相关文档,注意的是,个人使用还是加上dev参数比较好

    • ssh2是连接远程服务器的,配置一些基本的服务器配置,我是在config/prod.env.js进行了配置,包括了服务器名称,账号,密码,项目名称,路径等等,这些配置都不一定非要提取出来,可以自由配置设置通过shell交互进行输入。

    'use strict'
    // const DEFAULT_SERVER = '"localhost:8080"'
    const REMOTE_SERVER = '0.0.0.0'
    const DEFAULT_HOST = {host: REMOTE_SERVER, user: '******', password: '******', key: '', name: 'navigation', path: '/opt/lampp/htdocs'}
    
    module.exports = {
      NODE_ENV: '"production"',
      REMOTE_HOST: REMOTE_SERVER,
      DEFAULT_HOST: DEFAULT_HOST,
    }
    
    • webpack打包后一般为一个dist文件夹,里面包含了一个static文件夹和index.html文件,这里再使用压缩工具压缩为zip文件,减少上传数量,我使用的是archiver,这里仍加上dev参数

    • 现在我的目标是一行命令进行打包上传,在package.json文件里配置相应的命令

    ...
      "scripts": {
        "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
        "start": "npm run dev",
        "lint": "eslint --ext .js,.vue src",
        "build": "node build/build.js",
        "publish": "node build/build.js -p"
      },
    ...
    

    我加了一行publish,他和build相似,区别就是多了一个默认参数-p, 我的想法是通过监听这个参数,来判断是只进行打包还是打包上传。

    • 修改buiild/build.js文件以便能实现上一步所说的监听
    'use strict'
    require('./check-versions')()
    
    process.env.NODE_ENV = 'production'
    const program = require('commander')
    const ora = require('ora')
    const rm = require('rimraf')
    const path = require('path')
    const chalk = require('chalk')
    const webpack = require('webpack')
    const config = require('../config')
    const webpackConfig = require('./webpack.prod.conf')
    
    const spinner = ora('building for production...')
    spinner.start()
    program
      .version('0.0.1')
      .option('-p, --publish', 'Publish Remote')
      .parse(process.argv)
    
    rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
      if (err) throw err
      webpack(webpackConfig, (err, stats) => {
        spinner.stop()
        if (err) throw err
        process.stdout.write(stats.toString({
          colors: true,
          modules: false,
          children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.
          chunks: false,
          chunkModules: false
        }) + '\n\n')
    
        if (stats.hasErrors()) {
          console.log(chalk.red('  Build failed with errors.\n'))
          process.exit(1)
        }
    
        console.log(chalk.cyan('  Build complete.\n'))
        console.log(chalk.yellow(
          '  Tip: built files are meant to be served over an HTTP server.\n' +
          '  Opening index.html over file:// won\'t work.\n'
        ))
        if (program.publish) {
          require('../publish/publish-zip')()
        }
      })
    })
    

    这一步引入了一个commander,并在最后对program.publish进行了判断,若存在则引入publish/publish-zip文件。到这一步为止,如果输入了npm run publish,这会完成相应的打包工作,并且引入了publish/publish-zip文件。

    • 引入的文件的主要工作是进行压缩,方法如下
    const fs = require('fs')
    const archiver = require('archiver')
    const env = require('../config/prod.env')
    // const chalk = require('chalk')
    
    module.exports = function () {
    //   console.log(chalk.cyan('  Zip files.\n'))
    //   console.time('key')
      var output = fs.createWriteStream(`publish/${env.DEFAULT_HOST.name}.zip`)
      var archive = archiver('zip')
    
      output.on('close', function () {
        // console.log(chalk.cyan('  Zip files.\n'))
        // console.timeEnd('key')
        console.log('compress completed...ready upload')
        require('./publish')()
      })
    
      output.on('end', function () {
      })
    
      archive.on('error', function (err) {
        throw err
      })
    
      archive.pipe(output)
      archive.glob('./dist' + '/**')
      archive.finalize()
    }
    
    

    该文件的主要作用就是,将打包后的文件进行压缩,压缩名为配置中的navigation.zip,压缩完成后引入publish.js文件

    • 接下来就是上传至服务器,代码如下
    const env = require('../config/prod.env')
    const chalk = require('chalk')
    var Client = require('ssh2').Client
    var conn = new Client()
    var fs = require('fs')
    
    const user = {
      host: env.DEFAULT_HOST.host,
      port: 22,
      username: env.DEFAULT_HOST.user,
      password: env.DEFAULT_HOST.password
    }
    
    /**
     * 1.进入目录
     * 2.删除旧的备份项目
     * 3.将原项目名称加上bak标志为备份文件
     * 4.解压缩上传的zip文件并将名称改为项目名称
     * 5.删除zip文件
     * 6.退出
     * @type {string[]}
     */
    const uploadShellList = [
      `cd ${env.DEFAULT_HOST.path}\n`,
      `rm -rf ${env.DEFAULT_HOST.name}.bak\n`,
      `mv ${env.DEFAULT_HOST.name} ${env.DEFAULT_HOST.name}.bak\n`,
      `unzip ${env.DEFAULT_HOST.name}.zip\n`,
      `mv dist ${env.DEFAULT_HOST.name}\n`,
      `rm -rf ${env.DEFAULT_HOST.name}.zip\n`,
      `exit\n`
    ]
    const params = {file: `./publish/${env.DEFAULT_HOST.name}.zip`, target: `${env.DEFAULT_HOST.path}/${env.DEFAULT_HOST.name}.zip`}
    
    /**
     * 上传文件
     * @param conn
     * @param params
     * @constructor
     */
    function UploadFile (conn, params) {
      const file = params.file
      const target = params.target
      if (!conn) {
        return
      }
      conn.sftp(function (err, sftp) {
        if (err) {
          throw err
        }
        sftp.fastPut(file, target, {}, function (err, result) {
          if (err) {
            console.log(chalk.red(err.message))
            throw err
          }
          Shell(conn)
        })
      })
    }
    
    function Ready () {
      conn.on('ready', function () {
        console.log('Client :: ready')
        UploadFile(conn, params)
      }).connect(user)
    }
    
    /**
     * 上传完成后服务器需要执行的内容
     * 删除本地压缩文件
     * @param conn
     * @constructor
     */
    function Shell (conn) {
      conn.shell(function (err, stream) {
        if (err) throw err
        stream.on('close', function () {
          console.log('Stream :: close')
          conn.end()
          fs.unlinkSync(`./publish/${env.DEFAULT_HOST.name}.zip`)
        }).on('data', function (data) {
          console.log('STDOUT: ' + data)
        }).stderr.on('data', function (data) {
          console.log('STDERR: ' + data)
        })
        stream.end(uploadShellList.join(''))
      })
    }
    
    module.exports = function () {
      try {
        Ready()
      } catch (err) {
        console.log(err)
      }
    }
    
    

    思路就是:链接服务器->调用uploadFile方法->调用Shell方法(命令自行调整,详情看注释)->删除本地压缩文件

    • 至此,已经完成了自动化部署,可以愉快的使用npm run publish进行自动化的部署了

    注:

    • 使用sftp时,远程路径不加后缀会报错

    • Shell不能进行交互,可以换exec,使用shell可能会遇到的问题就是,假如服务器项目未删除,又对其进行了覆盖操作,他会提示是否覆盖,而你并不能在本地进行交互,结果就是卡在那。

    • 假如不想把密码放在项目中,可以自行研究readline

    • 密码不放在项目时publish文件代码

    const env = require('../config/prod.env')
    const chalk = require('chalk')
    var Client = require('ssh2').Client
    var fs = require('fs')
    const readline = require('readline')
    
    const rl = readline.createInterface({
      input: process.stdin,
      output: process.stdout
    })
    
    /**
     * 1.进入目录
     * 2.删除旧的备份项目
     * 3.将原项目名称加上bak标志为备份文件
     * 4.解压缩上传的zip文件并将名称改为项目名称
     * 5.删除zip文件
     * 6.退出
     * @type {string[]}
     */
    const uploadShellList = [
      `cd ${env.DEFAULT_HOST.path}\n`,
      `rm -rf ${env.DEFAULT_HOST.name}.bak\n`,
      `mv ${env.DEFAULT_HOST.name} ${env.DEFAULT_HOST.name}.bak\n`,
      `unzip ${env.DEFAULT_HOST.name}.zip\n`,
      `mv dist ${env.DEFAULT_HOST.name}\n`,
      `rm -rf ${env.DEFAULT_HOST.name}.zip\n`,
      `exit\n`
    ]
    const params = {file: `./publish/${env.DEFAULT_HOST.name}.zip`, target: `${env.DEFAULT_HOST.path}/${env.DEFAULT_HOST.name}.zip`}
    
    /**
     * 上传文件
     * @param conn
     * @param params
     * @constructor
     */
    function UploadFile (conn, params) {
      const file = params.file
      const target = params.target
      if (!conn) {
        return
      }
      conn.sftp(function (err, sftp) {
        if (err) {
          throw err
        }
        sftp.fastPut(file, target, {}, function (err, result) {
          if (err) {
            console.log(chalk.red(err.message))
            throw err
          }
          Shell(conn)
        })
      })
    }
    
    function Ready () {
      var conn = new Client()
      const user = {
        host: env.DEFAULT_HOST.host,
        port: 22,
        username: env.DEFAULT_HOST.user,
        password: env.DEFAULT_HOST.password
      }
      if (user.password) {
        Publish(conn, user)
      } else {
        rl.question(chalk.green(`发布至服务器 ${env.DEFAULT_HOST.host} 请输入服务器密码:`), (answer) => {
          // console.log(chalk.green(`发布至服务器 ${host.host} 请输入服务器密码:`))
          if (answer !== null) {
            user.password = answer.replace(/\r\n$/, '')
            Publish(conn, user)
          }
        })
      }
    }
    
    function Publish (conn, user) {
      conn.on('ready', function () {
        console.log('Client :: ready')
        UploadFile(conn, params)
      }).connect(user)
    }
    
    /**
     * 上传完成后服务器需要执行的内容
     * 删除本地压缩文件
     * @param conn
     * @constructor
     */
    function Shell (conn) {
      conn.shell(function (err, stream) {
        if (err) throw err
        stream.on('close', function () {
          console.log('Stream :: close')
          conn.end()
          fs.unlinkSync(`./publish/${env.DEFAULT_HOST.name}.zip`)
        }).on('data', function (data) {
          console.log('STDOUT: ' + data)
        }).stderr.on('data', function (data) {
          console.log('STDERR: ' + data)
        })
        stream.end(uploadShellList.join(''))
      })
    }
    
    module.exports = function () {
      try {
        Ready()
      } catch (err) {
        console.log(err)
      }
    }
    

    此时,在打包完成后,它会提示输入服务器密码:

    compress completed...ready upload
    发布至服务器 0.0.0.0 请输入服务器密码:
    

    相关文章

      网友评论

        本文标题:ssh2实现vue项目自动化打包发布

        本文链接:https://www.haomeiwen.com/subject/mpzczftx.html