ssh2-sftp-client 2.4.3 → 2.5.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/README.md CHANGED
@@ -1,6 +1,10 @@
1
1
  ## SSH2 SFTP Client
2
2
  a SFTP client for node.js, a wrapper for [ssh2](https://github.com/mscdex/ssh2)
3
3
 
4
+ Additional documentation on the methods and available options can be found in
5
+ the [ssh2](https://github.com/mscdex/ssh2) and
6
+ [ssh2-streams](https://github.com/mscdex/ssh2-streams) documentation.
7
+
4
8
  ### Installation
5
9
  ```shell
6
10
  npm install ssh2-sftp-client
@@ -24,11 +28,57 @@ sftp.connect({
24
28
  });
25
29
  ```
26
30
 
31
+ ### Breaking Changes
32
+
33
+ Due to some incompatibilities with stream handling which breaks this module when
34
+ used with Node 10.x, some changes have been implemented that should enhance the
35
+ interface, but which also break compatibility with previous versions.
36
+
37
+ #### Option Changes
38
+
39
+ - The default encoding is null not utf8 as it was previously. This is consistent
40
+ with the defaults for the underlying SSH2 module.
41
+ - The usedCompressed option has been removed. None of the shh2-steams methods
42
+ actually support this option. The 'compress' option can be set as part of the
43
+ connection options. See [ssh2 client event](https://github.com/mscdex/ssh2#user-content-client-methods).
44
+ - The separate explicit option arguments for encoding and useCompression for some methods
45
+ have been replaced with a single 'options' argument, which is an object that
46
+ can have the following properties (defaults shown). See the
47
+ [ssh2-streams](https://github.com/mscdex/ssh2-streams) documentation for an
48
+ explination of the opt8ons.
49
+
50
+ ```javascript
51
+ const defaults = {
52
+ highWaterMark: 32 * 1024,
53
+ debug: undefined,
54
+ concurrency: 64,
55
+ chunkSize: 32768,
56
+ step: undefined,
57
+ mode: 0o666,
58
+ autoClose: true,
59
+ encoding: null
60
+ };
61
+ ```
62
+
63
+ #### Method Changes
64
+
65
+ #### get(srcPath, dst, options)
66
+
67
+ Used to retrieve a file from a remote SFTP server.
68
+
69
+ - srcPath: path to the file on the remote server
70
+ - dst: Either a string, which will be used as the path to store the file on the
71
+ local system or a writable stream, which will be used as the destination for a
72
+ stream pipe. If undefined, the remote file will be read into a Buffer and
73
+ the buffer returned.
74
+ - options: Options for the get operation e.g. encoding.
75
+
27
76
  ### Documentation
28
77
  the connection to server config pls see [ssh2 client event](https://github.com/mscdex/ssh2#user-content-client-methods).
29
78
 
30
79
  list of methods:
31
80
  all the methods will return a Promise;
81
+
32
82
  #### List
33
83
  Retrieves a directory listing.
34
84
 
@@ -54,10 +104,10 @@ group: // group ID
54
104
  ```
55
105
 
56
106
  #### Get
57
- Get a `ReadableStream` from remotePath. The encoding is passed to Node Stream (https://nodejs.org/api/stream.html) and it controls how the content is encoded. For example, when downloading binary data, 'null' should be passed (check node stream documentation). Default to 'utf8'.
107
+ Get a `ReadableStream` from remotePath. The encoding is passed to Node Stream (https://nodejs.org/api/stream.html) and it controls how the content is encoded. For example, when downloading binary data, 'null' should be passed (check node stream documentation). Default to 'null'.
58
108
 
59
109
  ```javascript
60
- sftp.get(remoteFilePath, [useCompression], [encoding], [addtionalOptions]);
110
+ sftp.get(remoteFilePath, [options]);
61
111
  ```
62
112
 
63
113
  #### FastGet
@@ -71,9 +121,9 @@ sftp.fastGet(remotePath, localPath, [options]);
71
121
  upload a file from `localPath` or `Buffer`, `Stream` data to `remoteFilePath`.The encoding is passed to Node Stream to control how the content is encoded. Default to 'utf8'.
72
122
 
73
123
  ```javascript
74
- sftp.put(localFilePath, remoteFilePath, [useCompression], [encoding], [addtionalOptions]);
75
- sftp.put(Buffer, remoteFilePath, [useCompression], [encoding], [addtionalOptions]);
76
- sftp.put(Stream, remoteFilePath, [useCompression], [encoding], [addtionalOptions]);
124
+ sftp.put(localFilePath, remoteFilePath, [optons]);
125
+ sftp.put(Buffer, remoteFilePath, [options]);
126
+ sftp.put(Stream, remoteFilePath, [options]);
77
127
  ```
78
128
 
79
129
  #### FastPut
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ssh2-sftp-client",
3
- "version": "2.4.3",
3
+ "version": "2.5.0",
4
4
  "description": "ssh2 sftp client for node",
5
5
  "main": "src/index.js",
6
6
  "repository": {
@@ -12,18 +12,20 @@
12
12
  "nodejs"
13
13
  ],
14
14
  "scripts": {
15
- "test": "mocha test/index.js"
15
+ "test": "mocha"
16
16
  },
17
17
  "author": "见见",
18
18
  "email": "jyu213@gmail.com",
19
19
  "license": "MIT",
20
20
  "dependencies": {
21
- "ssh2": "^0.6.1"
21
+ "concat-stream": "^2.0.0",
22
+ "ssh2": "^0.8.2"
22
23
  },
23
24
  "devDependencies": {
24
25
  "chai": "^4.2.0",
25
26
  "chai-as-promised": "^7.1.1",
26
27
  "chai-subset": "^1.6.0",
28
+ "checksum": "^0.1.1",
27
29
  "dotenv": "^6.1.0",
28
30
  "mocha": "^5.2.0"
29
31
  }
package/src/index.js CHANGED
@@ -7,8 +7,10 @@
7
7
  const Client = require('ssh2').Client;
8
8
  const osPath = require('path').posix;
9
9
  const utils = require('./utils');
10
+ const fs = require('fs');
11
+ const concat = require('concat-stream');
10
12
 
11
- let SftpClient = function(){
13
+ let SftpClient = function() {
12
14
  this.client = new Client();
13
15
  };
14
16
 
@@ -43,7 +45,7 @@ SftpClient.prototype.list = function(path) {
43
45
  accessTime: item.attrs.atime * 1000,
44
46
  rights: {
45
47
  user: item.longname.substr(1, 3).replace(reg, ''),
46
- group: item.longname.substr(4,3).replace(reg, ''),
48
+ group: item.longname.substr(4, 3).replace(reg, ''),
47
49
  other: item.longname.substr(7, 3).replace(reg, '')
48
50
  },
49
51
  owner: item.attrs.uid,
@@ -82,10 +84,14 @@ SftpClient.prototype.exists = function(path) {
82
84
  if (err.code === 2) {
83
85
  resolve(false);
84
86
  } else {
85
- reject(new Error(`Error listing ${dir}: code: ${err.code} ${err.message}`));
87
+ reject(
88
+ new Error(`Error listing ${dir}: code: ${err.code} ${err.message}`)
89
+ );
86
90
  }
87
91
  } else {
88
- let [type] = list.filter(item => item.filename === base).map(item => item.longname.substr(0, 1));
92
+ let [type] = list
93
+ .filter(item => item.filename === base)
94
+ .map(item => item.longname.substr(0, 1));
89
95
  if (type) {
90
96
  resolve(type);
91
97
  } else {
@@ -108,18 +114,18 @@ SftpClient.prototype.stat = function(remotePath) {
108
114
  let sftp = this.sftp;
109
115
 
110
116
  if (!sftp) {
111
- return reject(Error('sftp connect error'));
117
+ return reject(Error('sftp connect error'));
112
118
  }
113
- sftp.stat(remotePath, function (err, stats) {
114
- if (err){
119
+ sftp.stat(remotePath, function(err, stats) {
120
+ if (err) {
115
121
  reject(new Error(`Failed to stat ${remotePath}: ${err.message}`));
116
122
  } else {
117
- // format similarly to sftp.list
123
+ // format similarly to sftp.list
118
124
  resolve({
119
125
  mode: stats.mode,
120
126
  permissions: stats.permissions,
121
127
  owner: stats.uid,
122
- group: stats.guid,
128
+ group: stats.gid,
123
129
  size: stats.size,
124
130
  accessTime: stats.atime * 1000,
125
131
  modifyTime: stats.mtime * 1000
@@ -133,34 +139,53 @@ SftpClient.prototype.stat = function(remotePath) {
133
139
  /**
134
140
  * get file
135
141
  *
136
- * @param {String} path, path
137
- * @param {Object} useCompression, config options
138
- * @param {String} encoding. Encoding for the ReadStream, can be any value
139
- * supported by node streams. Use 'null' for binary
140
- * (https://nodejs.org/api/stream.html#stream_readable_setencoding_encoding)
141
- * @return {Promise} stream, readable stream
142
+ * If a dst argument is provided, it must be either a string, representing the
143
+ * local path to where the data will be put, a stream, in which case data is
144
+ * piped into the stream or undefined, in which case the data is returned as
145
+ * a Buffer object.
146
+ *
147
+ * @param {String} path, remote file path
148
+ * @param {string|stream|undefined} dst, data destination
149
+ * @param {Object} userOptions, options passed to get
150
+ *
151
+ * @return {Promise}
142
152
  */
143
- SftpClient.prototype.get = function(path, useCompression, encoding, otherOptions) {
144
- let options = this.getOptions(useCompression, encoding, otherOptions);
145
-
153
+ SftpClient.prototype.get = function(path, dst, options) {
146
154
  return new Promise((resolve, reject) => {
147
155
  let sftp = this.sftp;
148
156
 
149
157
  if (sftp) {
150
158
  try {
151
- this.client.on('error', reject);
152
-
153
- let stream = sftp.createReadStream(path, options);
154
-
155
- stream.on('error', (err) => {
156
- this.client.removeListener('error', reject);
157
- return reject(new Error(`Failed get for ${path}: ${err.message}`));
158
- });
159
- stream.on('readable', () => {
160
- this.client.removeListener('error', reject);
161
- return resolve(stream);
159
+ let rdr = sftp.createReadStream(path, options);
160
+
161
+ rdr.on('error', err => {
162
+ return reject(new Error(`Failed to get ${path}: ${err.message}`));
162
163
  });
163
- } catch(err) {
164
+
165
+ if (dst === undefined) {
166
+ // no dst specified, return buffer of data
167
+ let concatStream = concat(buff => {
168
+ return resolve(buff);
169
+ });
170
+ rdr.pipe(concatStream);
171
+ } else if (typeof dst === 'string') {
172
+ // dst local file path
173
+ let wtr = fs.createWriteStream(dst);
174
+ wtr.on('error', err => {
175
+ return reject(new Error(`Failed get for ${path}: ${err.message}`));
176
+ });
177
+ wtr.on('finish', () => {
178
+ return resolve(dst);
179
+ });
180
+ rdr.pipe(wtr);
181
+ } else {
182
+ // assume dst is a writeStream
183
+ dst.on('finish', () => {
184
+ return resolve(dst);
185
+ });
186
+ rdr.pipe(dst);
187
+ }
188
+ } catch (err) {
164
189
  this.client.removeListener('error', reject);
165
190
  return reject(new Error(`Failed get on ${path}: ${err.message}`));
166
191
  }
@@ -180,15 +205,14 @@ SftpClient.prototype.get = function(path, useCompression, encoding, otherOptions
180
205
  * @return {Promise} the result of downloading the file
181
206
  */
182
207
  SftpClient.prototype.fastGet = function(remotePath, localPath, options) {
183
- options = options || {concurrency: 64, chunkSize: 32768};
184
208
  return new Promise((resolve, reject) => {
185
209
  let sftp = this.sftp;
186
210
 
187
211
  if (!sftp) {
188
- return reject(Error('sftp connect error'));
212
+ return reject(Error('sftp connect error'));
189
213
  }
190
- sftp.fastGet(remotePath, localPath, options, function (err) {
191
- if (err){
214
+ sftp.fastGet(remotePath, localPath, options, function(err) {
215
+ if (err) {
192
216
  reject(new Error(`Failed to get ${remotePath}: ${err.message}`));
193
217
  }
194
218
  resolve(`${remotePath} was successfully download to ${localPath}!`);
@@ -207,16 +231,19 @@ SftpClient.prototype.fastGet = function(remotePath, localPath, options) {
207
231
  * @return {Promise} the result of downloading the file
208
232
  */
209
233
  SftpClient.prototype.fastPut = function(localPath, remotePath, options) {
210
- options = options || {};
211
234
  return new Promise((resolve, reject) => {
212
235
  let sftp = this.sftp;
213
236
 
214
237
  if (!sftp) {
215
- return reject(new Error('sftp connect error'));
238
+ return reject(new Error('sftp connect error'));
216
239
  }
217
- sftp.fastPut(localPath, remotePath, options, function (err) {
240
+ sftp.fastPut(localPath, remotePath, options, function(err) {
218
241
  if (err) {
219
- reject(new Error(`Failed to upload ${localPath} to ${remotePath}: ${err.message}`));
242
+ reject(
243
+ new Error(
244
+ `Failed to upload ${localPath} to ${remotePath}: ${err.message}`
245
+ )
246
+ );
220
247
  }
221
248
  resolve(`${localPath} was successfully uploaded to ${remotePath}!`);
222
249
  });
@@ -224,7 +251,6 @@ SftpClient.prototype.fastPut = function(localPath, remotePath, options) {
224
251
  });
225
252
  };
226
253
 
227
-
228
254
  /**
229
255
  * Create file
230
256
  *
@@ -234,17 +260,19 @@ SftpClient.prototype.fastPut = function(localPath, remotePath, options) {
234
260
  * @param {String} encoding. Encoding for the WriteStream, can be any value supported by node streams.
235
261
  * @return {[type]} [description]
236
262
  */
237
- SftpClient.prototype.put = function(input, remotePath, useCompression, encoding, otherOptions) {
238
- let options = this.getOptions(useCompression, encoding, otherOptions);
239
-
263
+ SftpClient.prototype.put = function(input, remotePath, options) {
240
264
  return new Promise((resolve, reject) => {
241
265
  let sftp = this.sftp;
242
266
 
243
267
  if (sftp) {
244
268
  if (typeof input === 'string') {
245
- sftp.fastPut(input, remotePath, options, (err) => {
269
+ sftp.fastPut(input, remotePath, options, err => {
246
270
  if (err) {
247
- return reject(new Error(`Failed to upload ${input} to ${remotePath}: ${err.message}`));
271
+ return reject(
272
+ new Error(
273
+ `Failed to upload ${input} to ${remotePath}: ${err.message}`
274
+ )
275
+ );
248
276
  }
249
277
  return resolve(`Uploaded ${input} to ${remotePath}`);
250
278
  });
@@ -253,13 +281,58 @@ SftpClient.prototype.put = function(input, remotePath, useCompression, encoding,
253
281
  let stream = sftp.createWriteStream(remotePath, options);
254
282
 
255
283
  stream.on('error', err => {
256
- return reject(new Error(`Failed to upload data stream to ${remotePath}: ${err.message}`));
284
+ return reject(
285
+ new Error(
286
+ `Failed to upload data stream to ${remotePath}: ${err.message}`
287
+ )
288
+ );
289
+ });
290
+
291
+ stream.on('finish', () => {
292
+ return resolve(`Uploaded data stream to ${remotePath}`);
293
+ });
294
+
295
+ if (input instanceof Buffer) {
296
+ stream.end(input);
297
+ return false;
298
+ }
299
+ input.pipe(stream);
300
+ } else {
301
+ return reject(Error('sftp connect error'));
302
+ }
303
+ });
304
+ };
305
+
306
+ /**
307
+ * Append to file
308
+ *
309
+ * @param {Buffer|stream} input
310
+ * @param {String} remotePath,
311
+ * @param {Object} options
312
+ * @return {[type]} [description]
313
+ */
314
+ SftpClient.prototype.append = function(input, remotePath, options) {
315
+ return new Promise((resolve, reject) => {
316
+ let sftp = this.sftp;
317
+
318
+ if (sftp) {
319
+ if (typeof input === 'string') {
320
+ throw new Error('Cannot append a file to another');
321
+ }
322
+ let stream = sftp.createWriteStream(remotePath, options);
323
+
324
+ stream.on('error', err => {
325
+ return reject(
326
+ new Error(
327
+ `Failed to upload data stream to ${remotePath}: ${err.message}`
328
+ )
329
+ );
257
330
  });
258
-
259
- stream.on('close', () => {
331
+
332
+ stream.on('finish', () => {
260
333
  return resolve(`Uploaded data stream to ${remotePath}`);
261
334
  });
262
-
335
+
263
336
  if (input instanceof Buffer) {
264
337
  stream.end(input);
265
338
  return false;
@@ -271,13 +344,20 @@ SftpClient.prototype.put = function(input, remotePath, useCompression, encoding,
271
344
  });
272
345
  };
273
346
 
347
+ /**
348
+ * @async
349
+ *
350
+ * Make a dirextory on remote server
351
+ *
352
+ * @param {string} path, remote directory path.
353
+ * @param {boolean} recursive, if true, recursively create directories
354
+ * @return {Promise}.
355
+ */
274
356
  SftpClient.prototype.mkdir = function(path, recursive = false) {
275
357
  let sftp = this.sftp;
276
358
 
277
359
  let doMkdir = p => {
278
360
  return new Promise((resolve, reject) => {
279
-
280
-
281
361
  if (!sftp) {
282
362
  return reject(new Error('sftp connect error'));
283
363
  }
@@ -295,24 +375,34 @@ SftpClient.prototype.mkdir = function(path, recursive = false) {
295
375
  return doMkdir(path);
296
376
  }
297
377
  let mkdir = p => {
298
- let {dir} = osPath.parse(p);
299
- return this.exists(dir).then((type) => {
378
+ let {dir} = osPath.parse(p);
379
+ return this.exists(dir)
380
+ .then(type => {
300
381
  if (!type) {
301
382
  return mkdir(dir);
302
383
  }
303
- }).then(() => {
384
+ })
385
+ .then(() => {
304
386
  return doMkdir(p);
305
387
  });
306
388
  };
307
389
  return mkdir(path);
308
390
  };
309
391
 
392
+ /**
393
+ * @async
394
+ *
395
+ * Remove directory on remote server
396
+ *
397
+ * @param {string} path, path to directory to be removed
398
+ * @param {boolean} recursive, if true, remove direcories/files in target
399
+ * @return {Promise}..
400
+ */
310
401
  SftpClient.prototype.rmdir = function(path, recursive = false) {
311
402
  let sftp = this.sftp;
312
403
 
313
404
  let doRmdir = p => {
314
405
  return new Promise((resolve, reject) => {
315
-
316
406
  if (!sftp) {
317
407
  return reject(new Error('sftp connect error'));
318
408
  }
@@ -334,20 +424,23 @@ SftpClient.prototype.rmdir = function(path, recursive = false) {
334
424
  let list;
335
425
  let files;
336
426
  let dirs;
337
- return this.list(p).then((res) => {
338
- list = res;
339
- files = list.filter(item => item.type === '-');
340
- dirs = list.filter(item => item.type === 'd');
341
- return utils.forEachAsync(files, (f) => {
342
- return this.delete(osPath.join(p, f.name));
343
- });
344
- }).then(() => {
345
- return utils.forEachAsync(dirs, (d) => {
346
- return rmdir(osPath.join(p, d.name));
427
+ return this.list(p)
428
+ .then(res => {
429
+ list = res;
430
+ files = list.filter(item => item.type === '-');
431
+ dirs = list.filter(item => item.type === 'd');
432
+ return utils.forEachAsync(files, f => {
433
+ return this.delete(osPath.join(p, f.name));
434
+ });
435
+ })
436
+ .then(() => {
437
+ return utils.forEachAsync(dirs, d => {
438
+ return rmdir(osPath.join(p, d.name));
439
+ });
440
+ })
441
+ .then(() => {
442
+ return doRmdir(p);
347
443
  });
348
- }).then(() => {
349
- return doRmdir(p);
350
- });
351
444
  };
352
445
  return rmdir(path);
353
446
  };
@@ -359,16 +452,16 @@ SftpClient.prototype.rmdir = function(path, recursive = false) {
359
452
  *
360
453
  * @param {string} path - path to the file to delete
361
454
  * @return {Promise} with string 'Successfully deleeted file' once resolved
362
- *
455
+ *
363
456
  */
364
457
  SftpClient.prototype.delete = function(path) {
365
458
  return new Promise((resolve, reject) => {
366
459
  let sftp = this.sftp;
367
460
 
368
461
  if (!sftp) {
369
- return reject(new Error('sftp connect error'));
462
+ return reject(new Error('sftp connect error'));
370
463
  }
371
- sftp.unlink(path, (err) => {
464
+ sftp.unlink(path, err => {
372
465
  if (err) {
373
466
  reject(new Error(`Failed to delete file ${path}: ${err.message}`));
374
467
  }
@@ -387,18 +480,22 @@ SftpClient.prototype.delete = function(path) {
387
480
  * @param {string} remotePath - path to the new name.
388
481
  *
389
482
  * @return {Promise}
390
- *
483
+ *
391
484
  */
392
485
  SftpClient.prototype.rename = function(srcPath, remotePath) {
393
486
  return new Promise((resolve, reject) => {
394
487
  let sftp = this.sftp;
395
488
 
396
489
  if (!sftp) {
397
- return reject(new Error('sftp connect error'));
490
+ return reject(new Error('sftp connect error'));
398
491
  }
399
- sftp.rename(srcPath, remotePath, (err) => {
492
+ sftp.rename(srcPath, remotePath, err => {
400
493
  if (err) {
401
- reject(new Error(`Failed to rename file ${srcPath} to ${remotePath}: ${err.message}`));
494
+ reject(
495
+ new Error(
496
+ `Failed to rename file ${srcPath} to ${remotePath}: ${err.message}`
497
+ )
498
+ );
402
499
  }
403
500
  resolve(`Successfully renamed ${srcPath} to ${remotePath}`);
404
501
  });
@@ -421,11 +518,13 @@ SftpClient.prototype.chmod = function(remotePath, mode) {
421
518
  let sftp = this.sftp;
422
519
 
423
520
  if (!sftp) {
424
- return reject(new Error('sftp connect error'));
521
+ return reject(new Error('sftp connect error'));
425
522
  }
426
- sftp.chmod(remotePath, mode, (err) => {
523
+ sftp.chmod(remotePath, mode, err => {
427
524
  if (err) {
428
- reject(new Error(`Failed to change mode for ${remotePath}: ${err.message}`));
525
+ reject(
526
+ new Error(`Failed to change mode for ${remotePath}: ${err.message}`)
527
+ );
429
528
  }
430
529
  resolve('Successfully change file mode');
431
530
  });
@@ -442,7 +541,7 @@ SftpClient.prototype.chmod = function(remotePath, mode) {
442
541
  * @param {string} connectMethod - ???
443
542
  *
444
543
  * @return {Promise} which will resolve to an sftp client object
445
- *
544
+ *
446
545
  */
447
546
  SftpClient.prototype.connect = function(config, connectMethod) {
448
547
  connectMethod = connectMethod || 'on';
@@ -469,33 +568,18 @@ SftpClient.prototype.connect = function(config, connectMethod) {
469
568
  * @async
470
569
  *
471
570
  * Close the SFTP connection
472
- *
571
+ *
473
572
  */
474
573
  SftpClient.prototype.end = function() {
475
- return new Promise((resolve) => {
574
+ return new Promise(resolve => {
476
575
  this.client.end();
477
576
  resolve();
478
577
  });
479
578
  };
480
579
 
481
- SftpClient.prototype.getOptions = function(useCompression, encoding, otherOptions) {
482
- if(encoding === undefined){
483
- encoding = 'utf8';
484
- }
485
- let options = Object.assign({}, otherOptions || {}, {encoding: encoding}, useCompression);
486
- return options;
487
- };
488
-
489
580
  // add Event type support
490
581
  SftpClient.prototype.on = function(eventType, callback) {
491
582
  this.client.on(eventType, callback);
492
583
  };
493
584
 
494
-
495
585
  module.exports = SftpClient;
496
-
497
- // sftp = new SftpClient()
498
- // sftp.client.on('event')
499
- //
500
- // sftp.on('end', ()=>{}) => this.client.on('event', callback)
501
- // sftp.on('error', () => {})