ssh2實現vue項目自動化打包發佈

現在,先後端分離愈來愈流行,前端項目的各類打包部署工具也愈來愈多,能夠經過jenkins,pipline等等一鍵部署。本篇記錄使用node的ssh2來進行自動化打包上傳html

首先須要明白自動化上傳的思路,本篇以本人的導航項目我的導航爲藍本。前端

該項目經過vue-cli來生成,默認使用的webpack打包,按照之前的部署方法,應該是npm run build進行打包,而後手動上傳至服務器,如今使用ssh2來進行自動化上傳。vue

  • 首先第一步,是下載相應的模塊,必須的是ssh2模塊ssh2地址

npm install ssh2
這是官方的,詳細使用查看相關文檔,注意的是,我的使用仍是加上dev參數比較好node

  • 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, 個人想法是經過監聽這個參數,來判斷是隻進行打包仍是打包上傳。webpack

  • 修改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文件。web

  • 引入的文件的主要工做是進行壓縮,方法以下
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文件vue-cli

  • 接下來就是上傳至服務器,代碼以下
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方法(命令自行調整,詳情看註釋)->刪除本地壓縮文件shell

  • 至此,已經完成了自動化部署,能夠愉快的使用npm run publish進行自動化的部署了

注:npm

  • 使用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)
  }
}

此時,在打包完成後,它會提示輸入服務器密碼:json

compress completed...ready upload
發佈至服務器 0.0.0.0 請輸入服務器密碼:
相關文章
相關標籤/搜索