ssh2實現(xiàn)vue項目自動化打包發(fā)布

如今,前后端分離越來越流行缆镣,前端項目的各種打包部署工具也越來越多踊兜,可以通過jenkins,pipline等等一鍵部署。本篇記錄使用node的ssh2來進行自動化打包上傳

首先需要明白自動化上傳的思路芭碍,本篇以本人的導(dǎo)航項目個人導(dǎo)航為藍本徒役。

該項目通過vue-cli來生成,默認使用的webpack打包窖壕,按照以前的部署方法忧勿,應(yīng)該是npm run build進行打包杉女,然后手動上傳至服務(wù)器,現(xiàn)在使用ssh2來進行自動化上傳鸳吸。

  • 首先第一步熏挎,是下載相應(yīng)的模塊,必須的是ssh2模塊ssh2地址
    npm install ssh2
    這是官方的晌砾,詳細使用查看相關(guān)文檔坎拐,注意的是,個人使用還是加上dev參數(shù)比較好

  • ssh2是連接遠程服務(wù)器的养匈,配置一些基本的服務(wù)器配置,我是在config/prod.env.js進行了配置哼勇,包括了服務(wù)器名稱,賬號呕乎,密碼积担,項目名稱,路徑等等楣嘁,這些配置都不一定非要提取出來磅轻,可以自由配置設(shè)置通過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文件,減少上傳數(shù)量叭爱,我使用的是archiver撮躁,這里仍加上dev參數(shù)

  • 現(xiàn)在我的目標是一行命令進行打包上傳,在package.json文件里配置相應(yīng)的命令

...
  "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相似把曼,區(qū)別就是多了一個默認參數(shù)-p, 我的想法是通過監(jiān)聽這個參數(shù)漓穿,來判斷是只進行打包還是打包上傳嗤军。

  • 修改buiild/build.js文件以便能實現(xiàn)上一步所說的監(jiān)聽
'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僚饭,這會完成相應(yīng)的打包工作震叮,并且引入了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文件

  • 接下來就是上傳至服務(wù)器偿乖,代碼如下
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)
}

/**
 * 上傳完成后服務(wù)器需要執(zhí)行的內(nèi)容
 * 刪除本地壓縮文件
 * @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)
  }
}

思路就是:鏈接服務(wù)器->調(diào)用uploadFile方法->調(diào)用Shell方法(命令自行調(diào)整击罪,詳情看注釋)->刪除本地壓縮文件

  • 至此哲嘲,已經(jīng)完成了自動化部署,可以愉快的使用npm run publish進行自動化的部署了

注:

  • 使用sftp時外邓,遠程路徑不加后綴會報錯

  • Shell不能進行交互撤蚊,可以換exec,使用shell可能會遇到的問題就是,假如服務(wù)器項目未刪除损话,又對其進行了覆蓋操作,他會提示是否覆蓋槽唾,而你并不能在本地進行交互丧枪,結(jié)果就是卡在那。

  • 假如不想把密碼放在項目中庞萍,可以自行研究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(`發(fā)布至服務(wù)器 ${env.DEFAULT_HOST.host} 請輸入服務(wù)器密碼:`), (answer) => {
      // console.log(chalk.green(`發(fā)布至服務(wù)器 ${host.host} 請輸入服務(wù)器密碼:`))
      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)
}

/**
 * 上傳完成后服務(wù)器需要執(zhí)行的內(nèi)容
 * 刪除本地壓縮文件
 * @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)
  }
}

此時拧烦,在打包完成后,它會提示輸入服務(wù)器密碼:

compress completed...ready upload
發(fā)布至服務(wù)器 0.0.0.0 請輸入服務(wù)器密碼:
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末钝计,一起剝皮案震驚了整個濱河市恋博,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌私恬,老刑警劉巖债沮,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異本鸣,居然都是意外死亡疫衩,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進店門荣德,熙熙樓的掌柜王于貴愁眉苦臉地迎上來闷煤,“玉大人,你說我怎么就攤上這事涮瞻±鹉茫” “怎么了?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵署咽,是天一觀的道長近顷。 經(jīng)常有香客問我,道長艇抠,這世上最難降的妖魔是什么幕庐? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮家淤,結(jié)果婚禮上异剥,老公的妹妹穿的比我還像新娘。我一直安慰自己絮重,他們只是感情好冤寿,可當我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布歹苦。 她就那樣靜靜地躺著,像睡著了一般督怜。 火紅的嫁衣襯著肌膚如雪殴瘦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天号杠,我揣著相機與錄音蚪腋,去河邊找鬼。 笑死姨蟋,一個胖子當著我的面吹牛屉凯,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播眼溶,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼悠砚,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了堂飞?” 一聲冷哼從身側(cè)響起灌旧,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎绰筛,沒想到半個月后枢泰,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡别智,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年宗苍,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片薄榛。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡讳窟,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出敞恋,到底是詐尸還是另有隱情丽啡,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布硬猫,位于F島的核電站补箍,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏啸蜜。R本人自食惡果不足惜坑雅,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望衬横。 院中可真熱鬧裹粤,春花似錦、人聲如沸蜂林。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至矮锈,卻和暖如春霉翔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背苞笨。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工债朵, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人猫缭。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓葱弟,卻偏偏與公主長得像,于是被迫代替她去往敵國和親猜丹。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 當時是讓我來掙錢的吧硅卢。
    愛珍珍閱讀 180評論 0 0
  • 南陽十日如夢 籠中過客相送 茫然行匆匆 唯我前程不問 恍悟 恍悟 過眼云煙飄逝射窒。
    云中漫步游閱讀 233評論 0 4
  • 昨天周日,下雨 9:30 早飯将塑,兩個雞蛋脉顿,一杯豆奶,幾個提子点寥,出發(fā)完成任務(wù)艾疟。摔了一跤,unlucky. 在雨中奔波...
    云眩風怡閱讀 211評論 0 0
  • 蟬鳴的那瞬間 我閉上了雙眼 不安 散布在整個房間 這個世界多少的東西 到底往哪走 靜靜的我睜開雙眼 蟬也是愣在那兒...
    風狼志閱讀 122評論 0 0
  • 《執(zhí)劍天涯》簡介 林夫人早拿了創(chuàng)傷藥和繃帶過來幫林正海包扎傷口敢辩,說道:“還好婉兒沒受傷蔽莱,一身功夫總算沒白學(xué)∑莩ぃ” 林...
    小草莓園閱讀 1,554評論 34 31