想直接使用的,github 傳送門 - git上有詳細配置,記得留個star,筆心
但最近做的項目修改頻繁,每次部署都是先打包,然後手動拷貝到遠程服務器,次數多了有點麻煩,身爲一個程序員,秉着偷懶的原則,程序能完成的重複工作絕不自己完成,於是就寫了個Node小腳本。
在寫腳本之前,我們需要了解下package.json,nodejs工程的自動化是依賴於package.json文件中的scripts配置項來實現的,例如使用vue-cli搭建的工程中就會帶有:
{
...
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
...
}
當我們在命令行執行:npm run serve ,其實是在執行vue-cli-service serve,我們也可以自己寫一個自動部署腳本:
當我們在命令行執行:npm run serve ,其實是在執行vue-cli-service serve,我們也可以自己寫一個自動部署腳本:
我們希望腳本能在npm run build之後自行執行,這裏可以利用npm聲明週期中的post鉤子:
"scripts": {
"build": "node start.js",
"dev": "node satrt.js",
"postbuild": "node deploy.js"
},
這裏稍微解釋下npm 聲明週期,我覺得最有用的就是pre和post,如果你想在某個腳本執行完之後執行其他腳本,可以使用前綴+腳本名,這裏使用的是postbuild,同理,prebuild就是在執行build之前執行其他腳本。
腳本流程如下:
1.登陸服務器,讀取服務器網站目錄列表,選擇上傳的目錄(你也可以新建目錄)
2. 備份服務器之前的文件,然後覆蓋上傳。
3.上傳完畢
const path = require('path')
const moment = require('moment')
const util = require('util')
const events = require('events')
const Client = require('ssh2').Client
const fs = require('fs')
const ProgressBar =require('progress');
const inquirer=require('inquirer')
//cnpm install moment util events ssh2 progress inquirer --dev 請先在deploy所在目錄安裝以上包
/******************************請手動配置以下內容*********************************/
/** 遠程服務器配置
* @type {{password: string, port: number, host: string, username: string}}
*/
const server = {
host: 'xx.xx.xx.xx', //主機ip
port: 22, //SSH 連接端口,默認22
username: 'name', //用戶名
password: 'pwd', //用戶登錄密碼
}
const basePath = '/web' //服務器網站根目錄
let baseDir = '' //項目目錄名稱
let back_up_dir='' //備份目錄名稱,需手動在服務器創建,可選,注意目錄名後有斜槓 比如 back_up/
const bakDirName = baseDir + '.bak' + moment(new Date()).format('YYYY-M-D-HH:mm:ss')//備份文件名
const buildPath = path.resolve('./dist')//本地項目編譯後的文件目錄
/**********************************配置結束***************************************/
function doConnect(server, then) { //連接服務器
const conn = new Client()
conn.on('ready', function () {
then && then(conn)
}).on('error', function (err) {
console.error('connect error!', err)
}).on('close', function () {
conn.end()
}).connect(server)
}
function doShell(server, cmd, then) { //執行遠程命令
doConnect(server, function (conn) {
conn.shell(function (err, stream) {
if (err) throw err
else {
let buf = ''
stream.on('close', function () {
conn.end()
then && then(err, buf)
}).on('data', function (data) {
buf = buf + data
}).stderr.on('data', function (data) {
console.log('stderr: ' + data)
})
stream.end(cmd)
}
})
})
}
function doGetFileAndDirList(localDir, dirs, files) { //遞歸獲取所以文件目錄
const dir = fs.readdirSync(localDir)
for (let i = 0; i < dir.length; i++) {
const p = path.join(localDir, dir[i])
const stat = fs.statSync(p)
if (stat.isDirectory()) {
dirs.push(p)
doGetFileAndDirList(p, dirs, files)
}
else {
files.push(p)
}
}
}
function Control() {
events.EventEmitter.call(this)
}
util.inherits(Control, events.EventEmitter)
const control = new Control()
control.on('doNext', function (todos, then) {
if (todos.length > 0) {
const func = todos.shift()
func(function (err, result) {
if (err) {
then(err)
throw err
}
else {
control.emit('doNext', todos, then)
}
})
}
else {
then(null)
}
})
function doUploadFile(server, localPath, remotePath, then) { //上傳文件
doConnect(server, function (conn) {
conn.sftp(function (err, sftp) {
if (err) {
then(err)
}
else {
sftp.fastPut(localPath, remotePath, function (err, result) {
conn.end()
then(err, result)
})
}
})
})
}
function doUploadDir(server, localDir, remoteDir, then) {
let dirs = []
let files = []
doGetFileAndDirList(localDir, dirs, files)
// 創建遠程目錄
console.log('開始創建遠程目錄')
let todoDir = []
dirs.forEach(function (dir) {
todoDir.push(function (done) {
const to = path.join(remoteDir, dir.slice(localDir.length + 1)).replace(/[\\]/g, '/')
const cmd = 'mkdir -p ' + to + '\r\nexit\r\n'
// console.log(`cmd::${cmd}`)
doShell(server, cmd, done)
})// end of push
})
// 上傳文件
console.log('準備上傳文件:')
let todoFile = []
let total=files.length;
let bar=new ProgressBar('上傳進度:[:bar] :percent 剩餘時長::etas',{total,width: 50});
files.forEach(function (file) {
todoFile.push(function (done) {
const to = path.join(remoteDir, file.slice(localDir.length + 1)).replace(/[\\]/g, '/')
// console.log('upload ' + to)
bar.tick(1);
doUploadFile(server, file, to, done)
})
})
control.emit('doNext', todoDir, function (err) {
if (err) {
throw err
}
else {
control.emit('doNext', todoFile, then)
}
})
}
let mutual={
chooseDir:function (err,dirList){
if(err){
console.log(err)
return false
}
dirList.unshift('我要新建目錄')
const promptList = [
{
type: 'list',
message: '請選擇要上傳到的項目目錄:',
name: 'dir',
choices:dirList,
}
];
inquirer.prompt(promptList).then(answers => {
if(answers.dir==='我要新建目錄'){
mutual.mkNewDir()
return false
}else if(answers.dir.includes(basePath)){
baseDir=basePath
}
else{
baseDir=answers.dir
}
init()
}).catch(err=>{
console.log(err)
})
},
mkNewDir:function(){
const promptList = [
{
type: 'input',
message: '請輸入要創建的目錄名稱:',
name: 'dir',
}
];
inquirer.prompt(promptList).then(answers => {
if(!answers.dir){
console.error('警告:文件名不能爲空')
return false
}
baseDir=answers.dir
init()
})
}
}
/**
* 描述:獲取遠程文件路徑下文件列表信息
* 參數:server 遠程電腦憑證;
* remotePath 遠程路徑;
* isFile 是否是獲取文件,true獲取文件信息,false獲取目錄信息;
* then 回調函數
* 回調:then(err, dirs) : dir, 獲取的列表信息
*/
function getFileOrDirList(server, remotePath, isFile, then){
var cmd = "find " + remotePath + " -type "+ (isFile == true ? "f":"d") + "\r\nexit\r\n";
doShell(server, cmd, function(err, data){
var arr = [];
var remoteFile = [];
arr = data.split("\r\n");
arr.forEach(function(dir){
if(dir.indexOf(remotePath) ==0){
remoteFile.push(dir);
}
});
remoteFile=remoteFile.map(item=>item.split('/')[2]).filter(item=> item&&item.trim()) //只保留第一層目錄
remoteFile.push(basePath+'(項目根目錄)') //上傳網站首頁文件地址
remoteFile=Array.from(new Set(remoteFile)) //去重
then(err, remoteFile);
})
};
function init(){
console.log('\n--------配置如下--------------\n')
console.log(`服務器host: ${server.host}`)
console.log(`項目文件夾: ${baseDir}`)
console.log(`項目部署以及備份目錄: ${basePath}`)
console.log(`備份後的文件夾名: ${bakDirName}`)
console.log('\n--------開始部署--------------\n')
doShell(server, `mv ${basePath}/${baseDir} ${basePath}/${ back_up_dir}${bakDirName}\nexit\n`) //備份遠程目錄文件
doUploadDir(server, buildPath, `${basePath}/${baseDir}`, () => console.log('\n--------部署成功--------------'))
}
getFileOrDirList(server,basePath,false,mutual.chooseDir)
代碼參考:https://github.com/hello-jun/deploy
因爲我本地一個文件目錄下有多個項目,上傳到的文件服務器目錄也不同,因爲我利用inquirer 添加了用戶交互功能,你可以選擇任意一個目錄或新建目錄上傳,此外還新增了上傳進度條,可以對上傳進度一目瞭然。
建議:爲了避免賬號密碼泄露,賬號密碼最好從其他文件夾中導入,免得多人共享git導致密碼泄露,登陸賬號權限最好也不是超級管理員。
使用截圖: