1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
/* eslint-disable */
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 serveEnv = {
stage: {
host: '8.142.143.40',
port: '22', //SSH 连接端口
username: 'changfa', //用户名
password: 'changFA123', //用户登录密码
},
}
// changfa07
// 公网IP 8.142.143.40 端口22
// changfa / changFA123
// 目录 /home/changfa/app/apache-tomcat-8.5.5new/webapps/dist/
let env = process.argv[process.argv.length - 1]
console.log('当前环境', env)
let server = serveEnv[env]
if (!server) {
console.log('请检查当前环境')
}
const basePath = '/home' //服务器网站根目录
let baseDir = '/changfa/app/apache-tomcat-8.5.5new/webapps/dist' //项目目录名称8.5.5new
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') //本地项目编译后的文件目录
console.log(basePath, );
/**********************************配置结束***************************************/
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) {
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
}
if (baseDir) {
init()
return
}
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 = 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)