现在,先后端分离愈来愈流行,前端项目的各类打包部署工具也愈来愈多,能够经过jenkins,pipline等等一键部署。本篇记录使用node的ssh2来进行自动化打包上传html
首先须要明白自动化上传的思路,本篇以本人的导航项目我的导航为蓝本。前端
该项目经过vue-cli来生成,默认使用的webpack打包,按照之前的部署方法,应该是npm run build
进行打包,而后手动上传至服务器,如今使用ssh2来进行自动化上传。vue
npm install ssh2
这是官方的,详细使用查看相关文档,注意的是,我的使用仍是加上dev参数比较好node
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, }
... "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
'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
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 请输入服务器密码: