如今,前后端分離越來越流行缆镣,前端項目的各種打包部署工具也越來越多踊兜,可以通過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ù)器密碼: