@qse/ssh-sftp 1.0.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.js ADDED
@@ -0,0 +1,471 @@
1
+ import Client from 'ssh2-sftp-client'
2
+ import ora from 'ora'
3
+ import * as glob from 'glob'
4
+ import fs from 'fs'
5
+ import * as minimatch from 'minimatch'
6
+ import inquirer from 'inquirer'
7
+ import { exitWithError, warn } from './utils.js'
8
+ import chalk from 'chalk'
9
+ import { presets, servers } from './presets.js'
10
+ import path from 'path'
11
+
12
+ /** @type {Options} */
13
+ const defualtOpts = {
14
+ localPath: 'dist',
15
+ noWarn: false,
16
+ keepAlive: false,
17
+ cleanRemoteFiles: true,
18
+ ignore: ['**/*.LICENSE.txt'],
19
+ securityLock: true,
20
+ }
21
+
22
+ /**
23
+ * get upload files
24
+ *
25
+ * @param {string} localPath
26
+ * @param {string} remotePath
27
+ * @param {string[]} ignore
28
+ */
29
+ function getFilesPath(localPath, remotePath, ignore) {
30
+ const files = glob.sync(`${localPath}/**/*`, {
31
+ ignore: getSafePattern(ignore, localPath),
32
+ dot: true,
33
+ })
34
+ return files.map((localFilePath) => {
35
+ return {
36
+ localPath: localFilePath,
37
+ remotePath: localFilePath.replace(localPath, remotePath),
38
+ }
39
+ })
40
+ }
41
+
42
+ /**
43
+ * get remote ls deep
44
+ *
45
+ * @typedef {Object} FilesOptions
46
+ * @property {true|string[]} [patterns]
47
+ *
48
+ * @param {Client} sftp
49
+ * @param {string} remotePath
50
+ * @param {FilesOptions} [options]
51
+ * @return {Promise<{isDir:boolean;path:string}[]>}
52
+ */
53
+ async function getRemoteDeepFiles(sftp, remotePath, options) {
54
+ const { patterns } = options
55
+ /**
56
+ * @param {string} remotePath
57
+ * @returns {Promise<string[]|string>}
58
+ */
59
+ async function getFiles(remotePath, data = []) {
60
+ const list = await sftp.list(remotePath)
61
+ for (const item of list) {
62
+ const path = remotePath + '/' + item.name
63
+ if (item.type === 'd') {
64
+ data.push({ isDir: true, path })
65
+ await getFiles(path, data)
66
+ } else {
67
+ data.push({ isDir: false, path })
68
+ }
69
+ }
70
+ return data
71
+ }
72
+ const ls = (await getFiles(remotePath)).filter((o) => o.path)
73
+
74
+ if (patterns.length > 0) {
75
+ let tmp = ls
76
+ const safePatterns = getSafePattern(patterns, remotePath)
77
+ tmp = tmp.filter((o) => safePatterns.some((reg) => minimatch(o.path, reg)))
78
+ return tmp
79
+ }
80
+ return ls
81
+ }
82
+
83
+ function ensureDiff(local, remote) {
84
+ return remote.map((file) => {
85
+ const isSame = local.some((local) => local.remotePath === file.path)
86
+ return { ...file, isSame }
87
+ })
88
+ }
89
+
90
+ /**
91
+ * @typedef {Object} Options
92
+ * @property {string} [localPath]
93
+ * @property {string} [remotePath]
94
+ * @property {{context?:string;folder?:string;server?:string}} [preset]
95
+ * @property {import('ssh2').ConnectConfig} [connectOptions]
96
+ * @property {string[]} [ignore]
97
+ * @property {boolean|string[]} [cleanRemoteFiles]
98
+ * @property {boolean} [securityLock]
99
+ * @property {boolean} [keepAlive]
100
+ * @property {boolean} [noWarn]
101
+ * @property {boolean} [skipPrompt]
102
+ * @property {{remotePath:string;connectOptions?:import('ssh2').ConnectConfig}[]} [targets]
103
+ * @param {Options} opts
104
+ */
105
+
106
+ async function sshSftp(opts) {
107
+ opts = parseOpts(opts)
108
+
109
+ const results = []
110
+ // 这里只返回第一个目标的结果,是为了兼容以前的代码
111
+ for (const target of opts.targets) {
112
+ results.push(await _sshSftp({ ...opts, ...target }))
113
+ }
114
+ return results[0]
115
+ }
116
+
117
+ /**
118
+ * @param {Options} opts
119
+ */
120
+ async function _sshSftp(opts) {
121
+ const { deployedURL, sftpURL } = getDeployURL(opts)
122
+
123
+ console.log('部署网址:', chalk.green(deployedURL))
124
+ const spinner = ora(`连接服务器 ${sftpURL}`).start()
125
+ const sftp = new Client()
126
+
127
+ try {
128
+ await sftp.connect(opts.connectOptions)
129
+ spinner.succeed(`已连接 ${sftpURL}`)
130
+
131
+ if (!(await sftp.exists(opts.remotePath))) {
132
+ let confirm = false
133
+ if (opts.skipPrompt) {
134
+ confirm = true
135
+ } else {
136
+ const ans = await inquirer.prompt({
137
+ name: 'confirm',
138
+ message: `远程文件夹不存在,是否要创建一个`,
139
+ type: 'confirm',
140
+ })
141
+ confirm = ans.confirm
142
+ }
143
+
144
+ if (confirm) {
145
+ await sftp.mkdir(opts.remotePath, true)
146
+ } else {
147
+ process.exit()
148
+ }
149
+ }
150
+
151
+ let remoteDeletefiles = []
152
+ let localUploadFiles = []
153
+
154
+ spinner.start('对比本地/远程的文件数量')
155
+
156
+ localUploadFiles = getFilesPath(opts.localPath, opts.remotePath, opts.ignore || [])
157
+
158
+ spinner.succeed(`本地文件数量:${localUploadFiles.length}`)
159
+ if (opts.cleanRemoteFiles) {
160
+ remoteDeletefiles = await getRemoteDeepFiles(sftp, opts.remotePath, {
161
+ patterns: opts.cleanRemoteFiles === true ? [] : opts.cleanRemoteFiles,
162
+ })
163
+
164
+ spinner.succeed(`远程文件数量:${remoteDeletefiles.length}`)
165
+
166
+ const Confirm = {
167
+ delete: Symbol(),
168
+ skip: Symbol(),
169
+ stop: Symbol(),
170
+ showDeleteFile: Symbol(),
171
+ }
172
+ let confirm = Confirm.delete
173
+ if (remoteDeletefiles.length > localUploadFiles.length && !opts.skipPrompt) {
174
+ const showSelect = async () => {
175
+ const { confirm } = await inquirer.prompt({
176
+ name: 'confirm',
177
+ message: `远程需要删除的文件数(${remoteDeletefiles.length})比本地(${localUploadFiles.length})多,确定要删除吗?`,
178
+ type: 'list',
179
+ choices: [
180
+ { name: '删除', value: Confirm.delete },
181
+ { name: '不删除,继续部署', value: Confirm.skip },
182
+ { name: '中止部署', value: Confirm.stop },
183
+ { name: '显示需要删除的文件', value: Confirm.showDeleteFile },
184
+ ],
185
+ })
186
+ return confirm
187
+ }
188
+
189
+ do {
190
+ confirm = await showSelect()
191
+ if (confirm === Confirm.stop) {
192
+ process.exit()
193
+ }
194
+ if (confirm === Confirm.showDeleteFile) {
195
+ const diffFiles = ensureDiff(localUploadFiles, remoteDeletefiles)
196
+
197
+ diffFiles.forEach((o) => {
198
+ const path = o.isSame ? o.path : chalk.red(o.path)
199
+ console.log(` - ${path}`)
200
+ })
201
+ }
202
+ } while (confirm === Confirm.showDeleteFile)
203
+ }
204
+
205
+ if (confirm === Confirm.delete) {
206
+ spinner.start('开始删除远程文件')
207
+ remoteDeletefiles = mergeDelete(remoteDeletefiles)
208
+ for (const i in remoteDeletefiles) {
209
+ const o = remoteDeletefiles[i]
210
+ spinner.text = `[${i + 1}/${remoteDeletefiles.length}] 正在删除 ${o.path}`
211
+ if (o.isDir) await sftp.rmdir(o.path, true)
212
+ else await sftp.delete(o.path)
213
+ }
214
+ spinner.succeed(`已删除 ${opts.remotePath}`)
215
+ }
216
+ }
217
+
218
+ spinner.start(`开始上传 ${opts.localPath} 到 ${opts.remotePath}`)
219
+
220
+ if (Array.isArray(opts.ignore) && opts.ignore.length > 0) {
221
+ for (const i in localUploadFiles) {
222
+ const o = localUploadFiles[i]
223
+ spinner.text = `[${i}/${localUploadFiles.length}] 正在上传 ${o.localPath} 到 ${o.remotePath}`
224
+ if (fs.statSync(o.localPath).isDirectory()) {
225
+ if (!(await sftp.exists(o.remotePath))) {
226
+ await sftp.mkdir(o.remotePath)
227
+ }
228
+ continue
229
+ }
230
+ await sftp.fastPut(o.localPath, o.remotePath)
231
+ }
232
+ } else {
233
+ await sftp.uploadDir(opts.localPath, opts.remotePath)
234
+ }
235
+ spinner.succeed(`已上传 ${opts.localPath} 到 ${opts.remotePath}`)
236
+
237
+ return { sftp, opts }
238
+ } catch (error) {
239
+ spinner.fail('异常中断')
240
+ if (error.message.includes('sftpConnect')) {
241
+ exitWithError(`登录失败,请检查 connectOptions 配置项\n原始信息:${error.message}`)
242
+ } else {
243
+ console.error(error)
244
+ }
245
+ } finally {
246
+ if (!opts.keepAlive) {
247
+ await sftp.end()
248
+ }
249
+ }
250
+ }
251
+
252
+ /**
253
+ * @param {Options} opts
254
+ * @param {{d:boolean,u:boolean,i:boolean}} lsOpts
255
+ */
256
+ async function sshSftpLS(opts, lsOpts) {
257
+ opts = parseOpts(opts)
258
+
259
+ for (const target of opts.targets) {
260
+ await _sshSftpLS({ ...opts, ...target }, lsOpts)
261
+ }
262
+ }
263
+
264
+ async function _sshSftpLS(opts, lsOpts) {
265
+ const sftp = new Client()
266
+ try {
267
+ await sftp.connect(opts.connectOptions)
268
+
269
+ if (lsOpts.d) {
270
+ if (opts.cleanRemoteFiles) {
271
+ const ls = await getRemoteDeepFiles(sftp, opts.remotePath, {
272
+ patterns: opts.cleanRemoteFiles === true ? [] : opts.cleanRemoteFiles,
273
+ })
274
+ console.log(`删除文件 ${opts.remotePath}(${ls.length}):`)
275
+ for (const o of ls) {
276
+ console.log(` - ${o.path}`)
277
+ }
278
+ }
279
+ }
280
+
281
+ if (lsOpts.i && Array.isArray(opts.ignore) && opts.ignore.length > 0) {
282
+ let ls = glob.sync(`${opts.localPath}/**/*`)
283
+ ls = ls.filter((s) =>
284
+ getSafePattern(opts.ignore, opts.localPath).some((reg) => minimatch(s, reg))
285
+ )
286
+
287
+ console.log(`忽略文件 (${ls.length}):`)
288
+ for (const s of ls) {
289
+ console.log(` - ${s}`)
290
+ }
291
+ }
292
+
293
+ if (lsOpts.u) {
294
+ if (opts.ignore && opts.ignore.length > 0) {
295
+ const ls = getFilesPath(opts.localPath, opts.remotePath, opts.ignore)
296
+ console.log(`上传文件 (${ls.length}): `)
297
+ for (const o of ls) {
298
+ console.log(` + ${o.localPath}`)
299
+ }
300
+ } else {
301
+ console.log(`上传 ${opts.localPath} 全部文件到 ${opts.remotePath}`)
302
+ }
303
+ }
304
+ } catch (error) {
305
+ console.error(error)
306
+ } finally {
307
+ await sftp.end()
308
+ }
309
+ }
310
+
311
+ /**
312
+ * @param {{isDir:boolean;path:string}[]} files
313
+ */
314
+ function mergeDelete(files) {
315
+ let dirs = files.filter((o) => o.isDir)
316
+ dirs.forEach(({ path }) => {
317
+ files = files.filter((o) => !(o.path.startsWith(path) && path !== o.path))
318
+ })
319
+ return files
320
+ }
321
+
322
+ /**
323
+ * @param {string[]} patterns
324
+ * @param {string} prefixPath
325
+ */
326
+ function getSafePattern(patterns, prefixPath) {
327
+ const safePatterns = patterns
328
+ .map((s) => s.replace(/^[\.\/]*/, prefixPath + '/'))
329
+ .reduce((acc, s) => [...acc, s, s + '/**/*'], [])
330
+ return safePatterns
331
+ }
332
+
333
+ /**
334
+ * @param {Options} opts
335
+ */
336
+ function parseOpts(opts) {
337
+ opts = Object.assign({}, defualtOpts, opts)
338
+
339
+ process.env.__SFTP_NO_WARN = opts.noWarn
340
+
341
+ const pkg = JSON.parse(fs.readFileSync(path.resolve('package.json'), 'utf-8'))
342
+ if (!pkg.name) {
343
+ exitWithError('package.json 中的 name 字段不能为空')
344
+ }
345
+ if (pkg.name.startsWith('@')) {
346
+ // 如果包含scope需要无视掉
347
+ pkg.name = pkg.name.replace(/@.*\//, '')
348
+ }
349
+
350
+ if (opts.preset && opts.preset.server) {
351
+ if (!servers[opts.preset.server]) exitWithError('未知的 preset.server')
352
+ const server = { ...servers[opts.preset.server] }
353
+ opts.connectOptions = server
354
+ }
355
+ if (opts.preset && opts.preset.context) {
356
+ if (!presets[opts.preset.context]) exitWithError('未知的 preset.context')
357
+ const preset = { ...presets[opts.preset.context] }
358
+
359
+ const folder = opts.preset.folder || pkg.name
360
+ preset.remotePath = path.join(preset.remotePath, folder)
361
+
362
+ opts = Object.assign({}, preset, opts)
363
+ }
364
+
365
+ if (!fs.existsSync(opts.localPath)) {
366
+ exitWithError(`localPath 配置错误,未找到需要上传的文件夹(${opts.localPath})`)
367
+ }
368
+
369
+ if (!fs.statSync(opts.localPath).isDirectory()) {
370
+ exitWithError('localPath 配置错误,必须是一个文件夹')
371
+ }
372
+
373
+ opts.connectOptions = {
374
+ ...opts.connectOptions,
375
+ readyTimeout: 2 * 60 * 1000,
376
+ }
377
+
378
+ opts.targets = opts.targets || []
379
+ if (opts.targets.length === 0 && !opts.remotePath) {
380
+ exitWithError('remotePath 或 targets 未配置')
381
+ }
382
+ if (opts.targets.length === 0) {
383
+ opts.targets.push({ remotePath: opts.remotePath, connectOptions: opts.connectOptions })
384
+ } else {
385
+ opts.targets.forEach((target) => {
386
+ target.connectOptions = target.connectOptions || opts.connectOptions
387
+ })
388
+ }
389
+
390
+ if (opts.targets.length > 1 && opts.keepAlive) {
391
+ exitWithError('keepAlive 选项在多目标上传时不支持,请设置为 false')
392
+ }
393
+
394
+ if (typeof opts.securityLock !== 'boolean') {
395
+ opts.securityLock = true
396
+ }
397
+
398
+ if (opts.securityLock === false) {
399
+ warn('请确保自己清楚关闭安全锁(securityLock)后的风险')
400
+ } else {
401
+ for (const target of opts.targets) {
402
+ if (!target.remotePath.includes(pkg.name)) {
403
+ exitWithError(
404
+ [
405
+ `remotePath 中不包含项目名称`,
406
+ `为防止错误上传/删除和保证服务器目录可读性,你必须让remotePath中包含你的项目名称`,
407
+ `remotePath:${target.remotePath}`,
408
+ `项目名称:${pkg.name} // 源自 package.json 中的 name 字段,忽略scope字段`,
409
+ `\n你可以设置 "securityLock": false 来关闭这个验证`,
410
+ ].join('\n')
411
+ )
412
+ }
413
+ }
414
+ }
415
+
416
+ if (opts.ignore) {
417
+ opts.ignore = [opts.ignore].flat(1).filter(Boolean)
418
+ }
419
+
420
+ if (opts.cleanRemoteFiles === true) {
421
+ opts.cleanRemoteFiles = []
422
+ }
423
+
424
+ if (opts.cleanRemoteFiles) {
425
+ opts.cleanRemoteFiles = [opts.cleanRemoteFiles].flat(1).filter(Boolean)
426
+ }
427
+
428
+ return opts
429
+ }
430
+
431
+ function getDeployURL(target) {
432
+ const _r = target.connectOptions
433
+ const sftpURL = `sftp://${_r.username}:${_r.password}@${_r.host}:${_r.port}/${target.remotePath}`
434
+
435
+ let deployedURL = '未知'
436
+ for (const context in presets) {
437
+ const preset = presets[context]
438
+ if (!target.remotePath.startsWith(preset.remotePath)) continue
439
+
440
+ const fullPath = target.remotePath.replace(preset.remotePath, '').slice(1)
441
+
442
+ deployedURL = ['http://www.zhidianbao.cn:8088', context, fullPath, ''].join('/')
443
+ break
444
+ }
445
+
446
+ return { sftpURL, deployedURL }
447
+ }
448
+
449
+ /**
450
+ * @param {Options} opts
451
+ */
452
+ function sshSftpShowUrl(opts) {
453
+ opts = parseOpts(opts)
454
+
455
+ for (const target of opts.targets) {
456
+ const { deployedURL } = getDeployURL(target)
457
+ console.log('部署网址:', chalk.green(deployedURL))
458
+ }
459
+ }
460
+
461
+ function sshSftpShowConfig(opts) {
462
+ opts = parseOpts(opts)
463
+
464
+ console.log(JSON.stringify(opts, null, 2))
465
+
466
+ sshSftpShowUrl(opts)
467
+ sshSftpLS(opts, { u: true, d: true, i: true })
468
+ }
469
+
470
+ export default sshSftp
471
+ export { sshSftp, sshSftpLS, sshSftpShowUrl, sshSftpShowConfig, parseOpts, Client }
@@ -1,34 +1,31 @@
1
- "use strict";
2
-
3
1
  const servers = {
4
2
  19: {
5
3
  host: '192.168.10.19',
6
4
  port: 22,
7
5
  username: 'root',
8
- password: 'eduweb19@'
6
+ password: 'eduweb19@',
9
7
  },
10
8
  171: {
11
9
  host: '192.168.10.171',
12
10
  port: 22,
13
11
  username: 'root',
14
- password: 'qsweb1@'
15
- }
16
- };
12
+ password: 'qsweb1@',
13
+ },
14
+ }
15
+
17
16
  const presets = {
18
17
  qsxxwapdev: {
19
18
  remotePath: '/erp/edumaven/edu-page-v1',
20
- connectOptions: servers[19]
19
+ connectOptions: servers[19],
21
20
  },
22
21
  eduwebngv1: {
23
22
  remotePath: '/erp/edumaven/edu-web-page-v1',
24
- connectOptions: servers[19]
23
+ connectOptions: servers[19],
25
24
  },
26
25
  qsxxadminv1: {
27
26
  remotePath: '/erp/edumaven/edu-admin-page-dev',
28
- connectOptions: servers[19]
29
- }
30
- };
31
- module.exports = {
32
- presets,
33
- servers
34
- };
27
+ connectOptions: servers[19],
28
+ },
29
+ }
30
+
31
+ export { presets, servers }
@@ -1,40 +1,35 @@
1
- "use strict";
1
+ import chalk from 'chalk'
2
2
 
3
- const chalk = require('chalk');
4
3
  /**
5
4
  * @param {string} message
6
5
  * @return {never}
7
6
  */
8
-
9
-
10
7
  function exitWithError(message) {
11
- console.log(`${chalk.bgRed.white.bold(' ERROR ')} ${chalk.redBright(message)}`);
12
- process.exit();
8
+ console.log(`${chalk.bgRed.white.bold(' ERROR ')} ${chalk.redBright(message)}`)
9
+ process.exit()
13
10
  }
14
11
 
15
12
  function warn(message) {
16
- if (process.env.__SFTP_NO_WARN) return;
17
- console.log(`${chalk.bgYellow.white.bold(' WARN ')} ${chalk.yellow.bold(message)}`);
13
+ if (process.env.__SFTP_NO_WARN) return
14
+ console.log(`${chalk.bgYellow.white.bold(' WARN ')} ${chalk.yellow.bold(message)}`)
18
15
  }
19
16
 
20
17
  function splitOnFirst(string, separator) {
21
18
  if (!(typeof string === 'string' && typeof separator === 'string')) {
22
- throw new TypeError('Expected the arguments to be of type `string`');
19
+ throw new TypeError('Expected the arguments to be of type `string`')
23
20
  }
24
21
 
25
22
  if (string === '' || separator === '') {
26
- return [];
23
+ return []
27
24
  }
28
25
 
29
- const separatorIndex = string.indexOf(separator);
26
+ const separatorIndex = string.indexOf(separator)
30
27
 
31
28
  if (separatorIndex === -1) {
32
- return [];
29
+ return []
33
30
  }
34
31
 
35
- return [string.slice(0, separatorIndex), string.slice(separatorIndex + separator.length)];
32
+ return [string.slice(0, separatorIndex), string.slice(separatorIndex + separator.length)]
36
33
  }
37
34
 
38
- module.exports.splitOnFirst = splitOnFirst;
39
- module.exports.exitWithError = exitWithError;
40
- module.exports.warn = warn;
35
+ export { splitOnFirst, exitWithError, warn }
package/lib/cli.js DELETED
@@ -1,126 +0,0 @@
1
- #!/usr/bin/env node
2
- "use strict";
3
-
4
- const {
5
- sshSftp,
6
- sshSftpLS,
7
- sshSftpShowUrl,
8
- sshSftpShowConfig
9
- } = require('./index');
10
-
11
- const fs = require('fs');
12
-
13
- const ora = require('ora');
14
-
15
- const {
16
- exitWithError
17
- } = require('./utils');
18
-
19
- const updateNotifier = require('update-notifier');
20
-
21
- const pkg = require('../package.json');
22
-
23
- const presets = require('./presets');
24
-
25
- updateNotifier({
26
- pkg,
27
- updateCheckInterval: 1000 * 60 * 60 * 24 * 7,
28
- shouldNotifyInNpmScript: true
29
- }).notify();
30
- require('yargs').usage('使用: $0 [command] \n\n代码:svn://192.168.10.168/edu/code/A0.New-system/0A2.front-end-component/ssh-sftp/trunk').command('*', '上传文件', yargs => yargs.option('no-clear', {
31
- desc: '不删除文件'
32
- }).option('yes', {
33
- alias: 'y',
34
- desc: '不存在的目录不再询问,直接创建',
35
- type: 'boolean'
36
- }), upload).command('init', '生成 .sftprc.json 配置文件', {}, generateDefaultConfigJSON).command(['list', 'ls'], '列出所有需要上传/忽略/删除的文件', yargs => yargs.option('u', {
37
- desc: '列出需要上传的文件'
38
- }).option('d', {
39
- desc: '列出需要删除的文件'
40
- }).option('i', {
41
- desc: '列出忽略的文件'
42
- }), ls).command(['show-config', 'sc'], '显示部署的完整信息', {}, showConfig).command(['show-presets', 'sp'], '显示预设配置', {}, showPresets).command(['show-url', 'su'], '显示部署网址', {}, showUrl).alias({
43
- v: 'version',
44
- h: 'help'
45
- }).argv;
46
-
47
- function getOpts() {
48
- isRoot();
49
-
50
- if (!fs.existsSync('.sftprc.json')) {
51
- return exitWithError('没找到 .sftprc.json 文件,请先执行 ssh-sftp init');
52
- }
53
-
54
- const opts = fs.readFileSync('.sftprc.json', 'utf-8');
55
- return JSON.parse(opts);
56
- }
57
-
58
- function isRoot() {
59
- if (!fs.existsSync('package.json')) {
60
- exitWithError('请在项目的根目录运行(package.json所在的目录)');
61
- }
62
- }
63
-
64
- function upload({
65
- clear,
66
- yes
67
- }) {
68
- const opts = getOpts();
69
-
70
- if (clear === false) {
71
- opts.cleanRemoteFiles = false;
72
- }
73
-
74
- if (yes) {
75
- opts.skipPrompt = true;
76
- }
77
-
78
- sshSftp(opts);
79
- }
80
-
81
- function generateDefaultConfigJSON() {
82
- isRoot();
83
-
84
- if (fs.existsSync('.sftprc.json')) {
85
- return exitWithError('已存在 .sftprc.json 文件,请勿重复生成');
86
- }
87
-
88
- fs.writeFileSync('.sftprc.json', JSON.stringify({
89
- $schema: 'http://www.zhidianbao.cn:8088/qsxxwapdev/edu-ssh-sftp/sftprc.schema.json',
90
- localPath: '/path/to/localDir',
91
- remotePath: '/path/to/remoteDir',
92
- connectOptions: {
93
- host: '127.0.0.1',
94
- port: 22,
95
- username: '',
96
- password: ''
97
- },
98
- ignore: ['**/something[optional].js'],
99
- cleanRemoteFiles: false
100
- }, null, 2), {
101
- encoding: 'utf-8'
102
- });
103
- ora().succeed('.sftprc.json 生成在项目根目录');
104
- }
105
-
106
- function ls(argv) {
107
- if (!argv.u && !argv.d && !argv.i) {
108
- argv.u = true;
109
- argv.d = true;
110
- argv.i = true;
111
- }
112
-
113
- sshSftpLS(getOpts(), argv);
114
- }
115
-
116
- function showUrl() {
117
- sshSftpShowUrl(getOpts());
118
- }
119
-
120
- function showConfig() {
121
- sshSftpShowConfig(getOpts());
122
- }
123
-
124
- function showPresets() {
125
- console.log(JSON.stringify(presets, null, 2));
126
- }