ssh2-sftp-client 7.0.3 → 7.2.1
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 +8 -3
- package/README.org +22 -3
- package/package.json +20 -6
- package/src/constants.js +3 -5
- package/src/index.js +133 -121
- package/src/utils.js +46 -33
package/README.md
CHANGED
|
@@ -61,9 +61,9 @@ an SFTP client for node.js, a wrapper around [SSH2](https://github.com/mscdex/ss
|
|
|
61
61
|
|
|
62
62
|
Documentation on the methods and available options in the underlying modules can be found on the [SSH2](https://github.com/mscdex/ssh2) project pages.
|
|
63
63
|
|
|
64
|
-
Current stable release is **v7.
|
|
64
|
+
Current stable release is **v7.2.1**.
|
|
65
65
|
|
|
66
|
-
Code has been tested against Node versions
|
|
66
|
+
Code has been tested against Node versions 14.18.2, 16.13.1 and 17.2.0
|
|
67
67
|
|
|
68
68
|
Node versions < 10.x are not supported.
|
|
69
69
|
|
|
@@ -99,6 +99,8 @@ sftp.connect({
|
|
|
99
99
|
|
|
100
100
|
- **Breaking Change** Expanded option handling for `get()` and `put()` methods. A number of use cases were identified where setting specific options on the read and write streams and the pipe operation are necessary. For example, disabling `autoClose` on streams or the `end` event in pipes. The `options` argument for `get()` and `put()` calls now supports properties for `readStreamOptions`, `writeStreamOptions` and `pipeOptions`. Note that options are only applied to streams created by the `get()` and `put()` methods. Streams passed into these methods are under the control of the client code and therefore cannot have options supplied in arguments to those streams (you would apply such options when you create the streams). Options are typically only necessary in special use cases. Most of the time, no options are required. However, if you are currently using options to either `put()` or `get()`, you will need to update your code to map these options to the new structure.
|
|
101
101
|
|
|
102
|
+
- **Breaking Change 7.1.0** A race condition was identified when using a put() call with a writeStream option of `autoClose: false`. In some situations, the promise would be resolved before the final close of the write stream. This could result in errors if you immediately attempt to access the uploaded file. To avoid this situatioin, the promise is now resolved once a `close` event is emitted. This means that setting `autoClose: false` can no longer be supported. The write stream for `put()` will autoClose once data writing has completed.
|
|
103
|
+
|
|
102
104
|
- Improved event handling. A listener for a global error event is now defined to catch errors which occur in-between method calls i.e. connection lost in-between calls to the library methods. A new mechanism has also been added for removal of listeners when no longer required.
|
|
103
105
|
|
|
104
106
|
# Documentation<a id="sec-5"></a>
|
|
@@ -497,11 +499,12 @@ Upload data from local system to remote server. If the `src` argument is a strin
|
|
|
497
499
|
flags: 'w', // w - write and a - append
|
|
498
500
|
encoding: null, // use null for binary files
|
|
499
501
|
mode: 0o666, // mode to use for created file (rwx)
|
|
500
|
-
autoClose: true // automatically close the write stream when finished
|
|
501
502
|
}}
|
|
502
503
|
```
|
|
503
504
|
|
|
504
505
|
The most common options to use are mode and encoding. The values shown above are the defaults. You do not have to set encoding to utf-8 for text files, null is fine for all file types. However, using utf-8 encoding for binary files will often result in data corruption.
|
|
506
|
+
|
|
507
|
+
Note that you cannot set `autoClose: false` for `writeStreamOptions`. If you attempt to set this property to false, it will be ignored. This is necessary to avoid a race condition which may exist when setting `autoClose` to false on the writeStream. As there is no easy way to access the writeStream once the promise has been resolved, setting this to autoClose false is not terribly useful as there is no easy way to manually close the stream after the promise has been resolved.
|
|
505
508
|
|
|
506
509
|
2. Example Use
|
|
507
510
|
|
|
@@ -1356,3 +1359,5 @@ Thanks to the following for their contributions -
|
|
|
1356
1359
|
- **anton-erofeev:** Documentation fix
|
|
1357
1360
|
- **Ladislav Jacho:** Contributed solution explanation for connections hanging when transferring larger files.
|
|
1358
1361
|
- **Emma Milner:** Contributed fix for put() bug
|
|
1362
|
+
- **Witni Davis:** Contributed PR to fix put() RCE when using 'finish' rather than 'close' to resolve promise
|
|
1363
|
+
- **Maik Marschner:** Contributed fix for connect() not returning sftp object. Also included test to check for this regression in future.
|
package/README.org
CHANGED
|
@@ -9,9 +9,9 @@ convenience abstraction as well as a Promise based API.
|
|
|
9
9
|
Documentation on the methods and available options in the underlying modules can
|
|
10
10
|
be found on the [[https://github.com/mscdex/ssh2][SSH2]] project pages.
|
|
11
11
|
|
|
12
|
-
Current stable release is *v7.
|
|
12
|
+
Current stable release is *v7.2.1*.
|
|
13
13
|
|
|
14
|
-
Code has been tested against Node versions
|
|
14
|
+
Code has been tested against Node versions 14.18.2, 16.13.1 and 17.2.0
|
|
15
15
|
|
|
16
16
|
Node versions < 10.x are not supported.
|
|
17
17
|
|
|
@@ -62,6 +62,14 @@ npm install ssh2-sftp-client
|
|
|
62
62
|
currently using options to either ~put()~ or ~get()~, you will need to update
|
|
63
63
|
your code to map these options to the new structure.
|
|
64
64
|
|
|
65
|
+
- *Breaking Change 7.1.0* A race condition was identified when using a put()
|
|
66
|
+
call with a writeStream option of ~autoClose: false~. In some situations, the
|
|
67
|
+
promise would be resolved before the final close of the write stream. This
|
|
68
|
+
could result in errors if you immediately attempt to access the uploaded
|
|
69
|
+
file. To avoid this situatioin, the promise is now resolved once a ~close~
|
|
70
|
+
event is emitted. This means that setting ~autoClose: false~ can no longer be
|
|
71
|
+
supported. The write stream for ~put()~ will autoClose once data writing has completed.
|
|
72
|
+
|
|
65
73
|
- Improved event handling. A listener for a global error event is now defined to
|
|
66
74
|
catch errors which occur in-between method calls i.e. connection lost
|
|
67
75
|
in-between calls to the library methods. A new mechanism has also been added
|
|
@@ -546,7 +554,6 @@ option value. For example, you might use the following to set ~writeStream~ opti
|
|
|
546
554
|
flags: 'w', // w - write and a - append
|
|
547
555
|
encoding: null, // use null for binary files
|
|
548
556
|
mode: 0o666, // mode to use for created file (rwx)
|
|
549
|
-
autoClose: true // automatically close the write stream when finished
|
|
550
557
|
}}
|
|
551
558
|
#+end_src
|
|
552
559
|
|
|
@@ -555,6 +562,14 @@ the defaults. You do not have to set encoding to utf-8 for text files, null is
|
|
|
555
562
|
fine for all file types. However, using utf-8 encoding for binary files will
|
|
556
563
|
often result in data corruption.
|
|
557
564
|
|
|
565
|
+
Note that you cannot set ~autoClose: false~ for ~writeStreamOptions~. If you
|
|
566
|
+
attempt to set this property to false, it will be ignored. This is necessary to
|
|
567
|
+
avoid a race condition which may exist when setting ~autoClose~ to false on the
|
|
568
|
+
writeStream. As there is no easy way to access the writeStream once the promise
|
|
569
|
+
has been resolved, setting this to autoClose false is not terribly useful as
|
|
570
|
+
there is no easy way to manually close the stream after the promise has been
|
|
571
|
+
resolved.
|
|
572
|
+
|
|
558
573
|
**** Example Use
|
|
559
574
|
|
|
560
575
|
#+begin_src javascript
|
|
@@ -1765,3 +1780,7 @@ Thanks to the following for their contributions -
|
|
|
1765
1780
|
- Ladislav Jacho :: Contributed solution explanation for connections hanging
|
|
1766
1781
|
when transferring larger files.
|
|
1767
1782
|
- Emma Milner :: Contributed fix for put() bug
|
|
1783
|
+
- Witni Davis :: Contributed PR to fix put() RCE when using 'finish' rather than
|
|
1784
|
+
'close' to resolve promise
|
|
1785
|
+
- Maik Marschner :: Contributed fix for connect() not returning sftp object.
|
|
1786
|
+
Also included test to check for this regression in future.
|
package/package.json
CHANGED
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ssh2-sftp-client",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.2.1",
|
|
4
4
|
"description": "ssh2 sftp client for node",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "https://github.com/theophilusx/ssh2-sftp-client"
|
|
9
9
|
},
|
|
10
|
-
"keywords": [
|
|
10
|
+
"keywords": [
|
|
11
|
+
"sftp",
|
|
12
|
+
"nodejs",
|
|
13
|
+
"promises"
|
|
14
|
+
],
|
|
11
15
|
"scripts": {
|
|
12
16
|
"test": "mocha",
|
|
13
17
|
"coverage": "nyc npm run test",
|
|
14
|
-
"lint": "eslint \"src/**/*.js\"
|
|
18
|
+
"lint": "eslint \"src/**/*.js\" \"test/**/*.js\""
|
|
19
|
+
},
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=10.24.1"
|
|
15
22
|
},
|
|
16
23
|
"author": "Tim Cross",
|
|
17
24
|
"email": "theophilusx@gmail.com",
|
|
@@ -25,17 +32,24 @@
|
|
|
25
32
|
"dependencies": {
|
|
26
33
|
"concat-stream": "^2.0.0",
|
|
27
34
|
"promise-retry": "^2.0.1",
|
|
28
|
-
"ssh2": "^1.
|
|
35
|
+
"ssh2": "^1.5.0"
|
|
29
36
|
},
|
|
30
37
|
"devDependencies": {
|
|
31
|
-
"chai": "^4.
|
|
38
|
+
"chai": "^4.3.4",
|
|
32
39
|
"chai-as-promised": "^7.1.1",
|
|
33
40
|
"chai-subset": "^1.6.0",
|
|
34
41
|
"checksum": "^1.0.0",
|
|
35
42
|
"dotenv": "^10.0.0",
|
|
36
|
-
"
|
|
43
|
+
"eslint": "^8.5.0",
|
|
44
|
+
"eslint-config-prettier": "^8.3.0",
|
|
45
|
+
"eslint-plugin-mocha": "^10.0.3",
|
|
46
|
+
"eslint-plugin-node": "^11.1.0",
|
|
47
|
+
"eslint-plugin-promise": "^6.0.0",
|
|
48
|
+
"eslint-plugin-unicorn": "^39.0.0",
|
|
49
|
+
"mocha": "^9.1.2",
|
|
37
50
|
"moment": "^2.29.1",
|
|
38
51
|
"nyc": "^15.1.0",
|
|
52
|
+
"prettier": "^2.5.0",
|
|
39
53
|
"through2": "^4.0.2",
|
|
40
54
|
"winston": "^3.3.3"
|
|
41
55
|
}
|
package/src/constants.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
1
|
const errorCode = {
|
|
4
2
|
generic: 'ERR_GENERIC_CLIENT',
|
|
5
3
|
connect: 'ERR_NOT_CONNECTED',
|
|
6
4
|
badPath: 'ERR_BAD_PATH',
|
|
7
5
|
permission: 'EACCES',
|
|
8
6
|
notexist: 'ENOENT',
|
|
9
|
-
notdir: 'ENOTDIR'
|
|
7
|
+
notdir: 'ENOTDIR',
|
|
10
8
|
};
|
|
11
9
|
|
|
12
10
|
const targetType = {
|
|
@@ -15,10 +13,10 @@ const targetType = {
|
|
|
15
13
|
writeDir: 3,
|
|
16
14
|
readDir: 4,
|
|
17
15
|
readObj: 5,
|
|
18
|
-
writeObj: 6
|
|
16
|
+
writeObj: 6,
|
|
19
17
|
};
|
|
20
18
|
|
|
21
19
|
module.exports = {
|
|
22
20
|
errorCode,
|
|
23
|
-
targetType
|
|
21
|
+
targetType,
|
|
24
22
|
};
|
package/src/index.js
CHANGED
|
@@ -34,6 +34,7 @@ class SftpClient {
|
|
|
34
34
|
this.remotePathSep = '/';
|
|
35
35
|
this.remotePlatform = 'unix';
|
|
36
36
|
this.debug = undefined;
|
|
37
|
+
this.tempListeners = {};
|
|
37
38
|
|
|
38
39
|
this.client.on('close', () => {
|
|
39
40
|
if (this.endCalled || this.closeHandled) {
|
|
@@ -114,7 +115,7 @@ class SftpClient {
|
|
|
114
115
|
*
|
|
115
116
|
* @param {Object} config - an SFTP configuration object
|
|
116
117
|
*
|
|
117
|
-
* @return {Promise} which will resolve to an sftp client object
|
|
118
|
+
* @return {Promise<Object>} which will resolve to an sftp client object
|
|
118
119
|
*
|
|
119
120
|
*/
|
|
120
121
|
getConnection(config) {
|
|
@@ -133,13 +134,12 @@ class SftpClient {
|
|
|
133
134
|
// .catch((err) => {
|
|
134
135
|
// return Promise.reject(err);
|
|
135
136
|
// })
|
|
136
|
-
.finally(async (
|
|
137
|
+
.finally(async () => {
|
|
137
138
|
this.debugMsg('getConnection: finally clause fired');
|
|
138
139
|
await sleep(500);
|
|
139
140
|
this.removeListener('ready', doReady);
|
|
140
141
|
removeTempListeners(this, 'getConnection');
|
|
141
142
|
this._resetEventFlags();
|
|
142
|
-
return resp;
|
|
143
143
|
})
|
|
144
144
|
);
|
|
145
145
|
}
|
|
@@ -151,6 +151,7 @@ class SftpClient {
|
|
|
151
151
|
this.client.sftp((err, sftp) => {
|
|
152
152
|
if (err) {
|
|
153
153
|
this.debugMsg(`getSftpChannel: SFTP Channel Error: ${err.message}`);
|
|
154
|
+
this.client.end();
|
|
154
155
|
reject(fmtError(err, 'getSftpChannel', err.code));
|
|
155
156
|
} else {
|
|
156
157
|
this.debugMsg('getSftpChannel: SFTP channel established');
|
|
@@ -158,7 +159,7 @@ class SftpClient {
|
|
|
158
159
|
resolve(sftp);
|
|
159
160
|
}
|
|
160
161
|
});
|
|
161
|
-
}).finally((
|
|
162
|
+
}).finally(() => {
|
|
162
163
|
this.debugMsg('getSftpChannel: finally clause fired');
|
|
163
164
|
removeTempListeners(this, 'getSftpChannel');
|
|
164
165
|
this._resetEventFlags();
|
|
@@ -174,7 +175,7 @@ class SftpClient {
|
|
|
174
175
|
*
|
|
175
176
|
* @param {Object} config - an SFTP configuration object
|
|
176
177
|
*
|
|
177
|
-
* @return {Promise} which will resolve to an sftp client object
|
|
178
|
+
* @return {Promise<Object>} which will resolve to an sftp client object
|
|
178
179
|
*
|
|
179
180
|
*/
|
|
180
181
|
async connect(config) {
|
|
@@ -205,7 +206,7 @@ class SftpClient {
|
|
|
205
206
|
minTimeout: config.retry_minTimeout || 1000,
|
|
206
207
|
}
|
|
207
208
|
);
|
|
208
|
-
|
|
209
|
+
return this.getSftpChannel();
|
|
209
210
|
} catch (err) {
|
|
210
211
|
this.debugMsg(`connect: Error ${err.message}`);
|
|
211
212
|
this._resetEventFlags();
|
|
@@ -222,7 +223,7 @@ class SftpClient {
|
|
|
222
223
|
* Returns undefined if the path does not exists.
|
|
223
224
|
*
|
|
224
225
|
* @param {String} remotePath - remote path, may be relative
|
|
225
|
-
* @returns {Promise} - remote absolute path or
|
|
226
|
+
* @returns {Promise<String>} - remote absolute path or ''
|
|
226
227
|
*/
|
|
227
228
|
realPath(remotePath) {
|
|
228
229
|
return new Promise((resolve, reject) => {
|
|
@@ -244,13 +245,19 @@ class SftpClient {
|
|
|
244
245
|
resolve(absPath);
|
|
245
246
|
});
|
|
246
247
|
}
|
|
247
|
-
}).finally((
|
|
248
|
+
}).finally(() => {
|
|
248
249
|
removeTempListeners(this, 'realPath');
|
|
249
250
|
this._resetEventFlags();
|
|
250
|
-
return rsp;
|
|
251
251
|
});
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
+
/**
|
|
255
|
+
* @async
|
|
256
|
+
*
|
|
257
|
+
* Return the current workding directory path
|
|
258
|
+
*
|
|
259
|
+
* @returns {Promise<String>} - current remote working directory
|
|
260
|
+
*/
|
|
254
261
|
cwd() {
|
|
255
262
|
return this.realPath('.');
|
|
256
263
|
}
|
|
@@ -259,7 +266,7 @@ class SftpClient {
|
|
|
259
266
|
* Retrieves attributes for path
|
|
260
267
|
*
|
|
261
268
|
* @param {String} remotePath - a string containing the path to a file
|
|
262
|
-
* @return {Promise} stats - attributes info
|
|
269
|
+
* @return {Promise<Object>} stats - attributes info
|
|
263
270
|
*/
|
|
264
271
|
async stat(remotePath) {
|
|
265
272
|
const _stat = (aPath) => {
|
|
@@ -302,9 +309,8 @@ class SftpClient {
|
|
|
302
309
|
resolve(result);
|
|
303
310
|
}
|
|
304
311
|
});
|
|
305
|
-
}).finally((
|
|
306
|
-
removeTempListeners(this, '
|
|
307
|
-
return rsp;
|
|
312
|
+
}).finally(() => {
|
|
313
|
+
removeTempListeners(this, '_stat');
|
|
308
314
|
});
|
|
309
315
|
};
|
|
310
316
|
|
|
@@ -326,7 +332,7 @@ class SftpClient {
|
|
|
326
332
|
*
|
|
327
333
|
* @param {string} remotePath - path to the object on the sftp server.
|
|
328
334
|
*
|
|
329
|
-
* @return {Promise} returns false if object does not exist. Returns type of
|
|
335
|
+
* @return {Promise<Boolean|String>} returns false if object does not exist. Returns type of
|
|
330
336
|
* object if it does
|
|
331
337
|
*/
|
|
332
338
|
async exists(remotePath) {
|
|
@@ -385,8 +391,7 @@ class SftpClient {
|
|
|
385
391
|
*
|
|
386
392
|
* @param {String} remotePath - path to remote directory
|
|
387
393
|
* @param {RegExp} pattern - regular expression to match filenames
|
|
388
|
-
* @returns {Promise} array of file description objects
|
|
389
|
-
* @throws {Error}
|
|
394
|
+
* @returns {Promise<Array>} array of file description objects
|
|
390
395
|
*/
|
|
391
396
|
list(remotePath, pattern = /.*/) {
|
|
392
397
|
return new Promise((resolve, reject) => {
|
|
@@ -404,15 +409,15 @@ class SftpClient {
|
|
|
404
409
|
if (fileList) {
|
|
405
410
|
newList = fileList.map((item) => {
|
|
406
411
|
return {
|
|
407
|
-
type: item.longname.
|
|
412
|
+
type: item.longname.slice(0, 1),
|
|
408
413
|
name: item.filename,
|
|
409
414
|
size: item.attrs.size,
|
|
410
415
|
modifyTime: item.attrs.mtime * 1000,
|
|
411
416
|
accessTime: item.attrs.atime * 1000,
|
|
412
417
|
rights: {
|
|
413
|
-
user: item.longname.
|
|
414
|
-
group: item.longname.
|
|
415
|
-
other: item.longname.
|
|
418
|
+
user: item.longname.slice(1, 4).replace(reg, ''),
|
|
419
|
+
group: item.longname.slice(4, 7).replace(reg, ''),
|
|
420
|
+
other: item.longname.slice(7, 10).replace(reg, ''),
|
|
416
421
|
},
|
|
417
422
|
owner: item.attrs.uid,
|
|
418
423
|
group: item.attrs.gid,
|
|
@@ -433,10 +438,9 @@ class SftpClient {
|
|
|
433
438
|
}
|
|
434
439
|
});
|
|
435
440
|
}
|
|
436
|
-
}).finally((
|
|
441
|
+
}).finally(() => {
|
|
437
442
|
removeTempListeners(this, 'list');
|
|
438
443
|
this._resetEventFlags();
|
|
439
|
-
return rsp;
|
|
440
444
|
});
|
|
441
445
|
}
|
|
442
446
|
|
|
@@ -453,7 +457,7 @@ class SftpClient {
|
|
|
453
457
|
* @param {Object} options - options object with supported properties of readStreamOptions,
|
|
454
458
|
* writeStreamOptions and pipeOptions.
|
|
455
459
|
*
|
|
456
|
-
* @return {Promise}
|
|
460
|
+
* @return {Promise<String|Stream|Buffer>}
|
|
457
461
|
*/
|
|
458
462
|
get(
|
|
459
463
|
remotePath,
|
|
@@ -511,35 +515,52 @@ class SftpClient {
|
|
|
511
515
|
)
|
|
512
516
|
);
|
|
513
517
|
});
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
518
|
+
if (
|
|
519
|
+
Object.hasOwnProperty.call(options, 'pipeOptions') &&
|
|
520
|
+
Object.hasOwnProperty.call(options.pipeOptions, 'end') &&
|
|
521
|
+
!options.pipeOptions.end
|
|
522
|
+
) {
|
|
523
|
+
rdr.once('end', () => {
|
|
524
|
+
this.debugMsg('get resolved on reader end event');
|
|
525
|
+
if (typeof dst === 'string') {
|
|
526
|
+
resolve(dst);
|
|
527
|
+
} else {
|
|
528
|
+
resolve(wtr);
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
} else {
|
|
532
|
+
wtr.once('finish', () => {
|
|
533
|
+
this.debugMsg('get resolved on writer finish event');
|
|
534
|
+
if (typeof dst === 'string') {
|
|
535
|
+
resolve(dst);
|
|
536
|
+
} else {
|
|
537
|
+
resolve(wtr);
|
|
538
|
+
}
|
|
539
|
+
});
|
|
540
|
+
}
|
|
521
541
|
}
|
|
522
542
|
rdr.pipe(wtr, options.pipeOptions ? options.pipeOptions : {});
|
|
523
543
|
}
|
|
524
|
-
}).finally((
|
|
544
|
+
}).finally(() => {
|
|
525
545
|
removeTempListeners(this, 'get');
|
|
526
546
|
this._resetEventFlags();
|
|
527
547
|
if (
|
|
528
548
|
rdr &&
|
|
529
|
-
options
|
|
549
|
+
Object.hasOwnProperty.call(options, 'readStreamOptions') &&
|
|
550
|
+
Object.hasOwnProperty.call(options.readStreamOptions, 'autoClose') &&
|
|
530
551
|
options.readStreamOptions.autoClose === false
|
|
531
552
|
) {
|
|
532
553
|
rdr.destroy();
|
|
533
554
|
}
|
|
534
555
|
if (
|
|
535
556
|
wtr &&
|
|
536
|
-
options
|
|
557
|
+
Object.hasOwnProperty.call(options, 'writeStreamOptions') &&
|
|
558
|
+
Object.hasOwnProperty.call(options.writeStreamOptions, 'autoClose') &&
|
|
537
559
|
options.writeStreamOptions.autoClose === false &&
|
|
538
560
|
typeof dst === 'string'
|
|
539
561
|
) {
|
|
540
562
|
wtr.destroy();
|
|
541
563
|
}
|
|
542
|
-
return rsp;
|
|
543
564
|
});
|
|
544
565
|
}
|
|
545
566
|
|
|
@@ -548,13 +569,10 @@ class SftpClient {
|
|
|
548
569
|
* Downloads a file at remotePath to localPath using parallel reads
|
|
549
570
|
* for faster throughput.
|
|
550
571
|
*
|
|
551
|
-
* See 'fastGet' at
|
|
552
|
-
* https://github.com/mscdex/ssh2-streams/blob/master/SFTPStream.md
|
|
553
|
-
*
|
|
554
572
|
* @param {String} remotePath
|
|
555
573
|
* @param {String} localPath
|
|
556
574
|
* @param {Object} options
|
|
557
|
-
* @return {Promise} the result of downloading the file
|
|
575
|
+
* @return {Promise<String>} the result of downloading the file
|
|
558
576
|
*/
|
|
559
577
|
async fastGet(remotePath, localPath, options) {
|
|
560
578
|
try {
|
|
@@ -574,7 +592,7 @@ class SftpClient {
|
|
|
574
592
|
err.code = errorCode.badPath;
|
|
575
593
|
throw err;
|
|
576
594
|
}
|
|
577
|
-
await new Promise((resolve, reject) => {
|
|
595
|
+
let rslt = await new Promise((resolve, reject) => {
|
|
578
596
|
if (haveConnection(this, 'fastGet', reject)) {
|
|
579
597
|
this.debugMsg(
|
|
580
598
|
`fastGet -> remote: ${remotePath} local: ${localPath} `,
|
|
@@ -589,10 +607,10 @@ class SftpClient {
|
|
|
589
607
|
resolve(`${remotePath} was successfully download to ${localPath}!`);
|
|
590
608
|
});
|
|
591
609
|
}
|
|
592
|
-
}).finally((
|
|
610
|
+
}).finally(() => {
|
|
593
611
|
removeTempListeners(this, 'fastGet');
|
|
594
|
-
return rsp;
|
|
595
612
|
});
|
|
613
|
+
return rslt;
|
|
596
614
|
} catch (err) {
|
|
597
615
|
this._resetEventFlags();
|
|
598
616
|
throw fmtError(err, 'fastGet');
|
|
@@ -610,7 +628,7 @@ class SftpClient {
|
|
|
610
628
|
* @param {String} localPath
|
|
611
629
|
* @param {String} remotePath
|
|
612
630
|
* @param {Object} options
|
|
613
|
-
* @return {Promise} the result of downloading the file
|
|
631
|
+
* @return {Promise<String>} the result of downloading the file
|
|
614
632
|
*/
|
|
615
633
|
fastPut(localPath, remotePath, options) {
|
|
616
634
|
this.debugMsg(`fastPut -> local ${localPath} remote ${remotePath}`);
|
|
@@ -654,10 +672,9 @@ class SftpClient {
|
|
|
654
672
|
resolve(`${localPath} was successfully uploaded to ${remotePath}!`);
|
|
655
673
|
});
|
|
656
674
|
}
|
|
657
|
-
}).finally((
|
|
675
|
+
}).finally(() => {
|
|
658
676
|
removeTempListeners(this, 'fastPut');
|
|
659
677
|
this._resetEventFlags();
|
|
660
|
-
return rsp;
|
|
661
678
|
});
|
|
662
679
|
}
|
|
663
680
|
|
|
@@ -671,12 +688,16 @@ class SftpClient {
|
|
|
671
688
|
* @param {Object} options - options used for read, write stream and pipe configuration
|
|
672
689
|
* value supported by node. Allowed properties are readStreamOptions,
|
|
673
690
|
* writeStreamOptions and pipeOptions.
|
|
674
|
-
* @return {Promise}
|
|
691
|
+
* @return {Promise<String>}
|
|
675
692
|
*/
|
|
676
693
|
put(
|
|
677
694
|
localSrc,
|
|
678
695
|
remotePath,
|
|
679
|
-
options = {
|
|
696
|
+
options = {
|
|
697
|
+
readStreamOptions: {},
|
|
698
|
+
writeStreamOptions: { autoClose: true },
|
|
699
|
+
pipeOptions: {},
|
|
700
|
+
}
|
|
680
701
|
) {
|
|
681
702
|
let wtr, rdr;
|
|
682
703
|
|
|
@@ -698,13 +719,15 @@ class SftpClient {
|
|
|
698
719
|
addTempListeners(this, 'put', reject);
|
|
699
720
|
wtr = this.sftp.createWriteStream(
|
|
700
721
|
remotePath,
|
|
701
|
-
options.writeStreamOptions
|
|
722
|
+
options.writeStreamOptions
|
|
723
|
+
? { ...options.writeStreamOptions, autoClose: true }
|
|
724
|
+
: {}
|
|
702
725
|
);
|
|
703
726
|
wtr.once('error', (err) => {
|
|
704
727
|
this.debugMsg(`put: write stream error ${err.message}`);
|
|
705
728
|
reject(fmtError(`${err.message} ${remotePath}`, 'put', err.code));
|
|
706
729
|
});
|
|
707
|
-
wtr.once('
|
|
730
|
+
wtr.once('close', () => {
|
|
708
731
|
this.debugMsg('put: promise resolved');
|
|
709
732
|
resolve(`Uploaded data stream to ${remotePath}`);
|
|
710
733
|
});
|
|
@@ -737,25 +760,18 @@ class SftpClient {
|
|
|
737
760
|
rdr.pipe(wtr, options.pipeOptions ? options.pipeOptions : {});
|
|
738
761
|
}
|
|
739
762
|
}
|
|
740
|
-
}).finally((
|
|
763
|
+
}).finally(() => {
|
|
741
764
|
removeTempListeners(this, 'put');
|
|
742
765
|
this._resetEventFlags();
|
|
743
766
|
if (
|
|
744
767
|
rdr &&
|
|
745
|
-
options
|
|
768
|
+
Object.hasOwnProperty.call(options, 'readStreamOptions') &&
|
|
769
|
+
Object.hasOwnProperty.call(options.readStreamOptions, 'autoClose') &&
|
|
746
770
|
options.readStreamOptions.autoClose === false &&
|
|
747
771
|
typeof localSrc === 'string'
|
|
748
772
|
) {
|
|
749
773
|
rdr.destroy();
|
|
750
774
|
}
|
|
751
|
-
if (
|
|
752
|
-
wtr &&
|
|
753
|
-
options.writeStreamOptions &&
|
|
754
|
-
options.writeStreamOptions.autoClose === false
|
|
755
|
-
) {
|
|
756
|
-
wtr.destroy();
|
|
757
|
-
}
|
|
758
|
-
return resp;
|
|
759
775
|
});
|
|
760
776
|
}
|
|
761
777
|
|
|
@@ -765,49 +781,45 @@ class SftpClient {
|
|
|
765
781
|
* @param {Buffer|stream} input
|
|
766
782
|
* @param {String} remotePath
|
|
767
783
|
* @param {Object} options
|
|
768
|
-
* @return {Promise}
|
|
784
|
+
* @return {Promise<String>}
|
|
769
785
|
*/
|
|
770
|
-
append(input, remotePath, options = {}) {
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
786
|
+
async append(input, remotePath, options = {}) {
|
|
787
|
+
const fileType = await this.exists(remotePath);
|
|
788
|
+
if (fileType && fileType === 'd') {
|
|
789
|
+
throw fmtError(
|
|
790
|
+
`Bad path: ${remotePath}: cannot append to a directory`,
|
|
791
|
+
'append',
|
|
792
|
+
errorCode.badPath
|
|
793
|
+
);
|
|
794
|
+
}
|
|
795
|
+
return await new Promise((resolve, reject) => {
|
|
796
|
+
if (haveConnection(this, 'append', reject)) {
|
|
797
|
+
if (typeof input === 'string') {
|
|
798
|
+
reject(fmtError('Cannot append one file to another', 'append'));
|
|
799
|
+
} else {
|
|
800
|
+
this.debugMsg(`append -> remote: ${remotePath} `, options);
|
|
801
|
+
addTempListeners(this, 'append', reject);
|
|
802
|
+
options.flags = 'a';
|
|
803
|
+
let stream = this.sftp.createWriteStream(remotePath, options);
|
|
804
|
+
stream.on('error', (err_1) => {
|
|
805
|
+
reject(
|
|
806
|
+
fmtError(`${err_1.message} ${remotePath}`, 'append', err_1.code)
|
|
807
|
+
);
|
|
808
|
+
});
|
|
809
|
+
stream.on('finish', () => {
|
|
810
|
+
resolve(`Appended data to ${remotePath}`);
|
|
811
|
+
});
|
|
812
|
+
if (input instanceof Buffer) {
|
|
813
|
+
stream.write(input);
|
|
814
|
+
stream.end();
|
|
785
815
|
} else {
|
|
786
|
-
|
|
787
|
-
addTempListeners(this, 'append', reject);
|
|
788
|
-
options.flags = 'a';
|
|
789
|
-
let stream = this.sftp.createWriteStream(remotePath, options);
|
|
790
|
-
stream.on('error', (err) => {
|
|
791
|
-
reject(
|
|
792
|
-
fmtError(`${err.message} ${remotePath}`, 'append', err.code)
|
|
793
|
-
);
|
|
794
|
-
});
|
|
795
|
-
stream.on('finish', () => {
|
|
796
|
-
resolve(`Appended data to ${remotePath}`);
|
|
797
|
-
});
|
|
798
|
-
if (input instanceof Buffer) {
|
|
799
|
-
stream.write(input);
|
|
800
|
-
stream.end();
|
|
801
|
-
} else {
|
|
802
|
-
input.pipe(stream);
|
|
803
|
-
}
|
|
816
|
+
input.pipe(stream);
|
|
804
817
|
}
|
|
805
818
|
}
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
});
|
|
819
|
+
}
|
|
820
|
+
}).finally(() => {
|
|
821
|
+
removeTempListeners(this, 'append');
|
|
822
|
+
this._resetEventFlags();
|
|
811
823
|
});
|
|
812
824
|
}
|
|
813
825
|
|
|
@@ -818,7 +830,7 @@ class SftpClient {
|
|
|
818
830
|
*
|
|
819
831
|
* @param {string} remotePath - remote directory path.
|
|
820
832
|
* @param {boolean} recursive - if true, recursively create directories
|
|
821
|
-
* @return {Promise}
|
|
833
|
+
* @return {Promise<String>}
|
|
822
834
|
*/
|
|
823
835
|
async mkdir(remotePath, recursive = false) {
|
|
824
836
|
const _mkdir = (p) => {
|
|
@@ -847,16 +859,23 @@ class SftpClient {
|
|
|
847
859
|
resolve(`${p} directory created`);
|
|
848
860
|
}
|
|
849
861
|
});
|
|
850
|
-
}).finally((
|
|
862
|
+
}).finally(() => {
|
|
851
863
|
removeTempListeners(this, '_mkdir');
|
|
852
864
|
this._resetEventFlags();
|
|
853
|
-
return rsp;
|
|
854
865
|
});
|
|
855
866
|
};
|
|
856
867
|
|
|
857
868
|
try {
|
|
858
869
|
haveConnection(this, 'mkdir');
|
|
859
870
|
let rPath = await normalizeRemotePath(this, remotePath);
|
|
871
|
+
let targetExists = await this.exists(rPath);
|
|
872
|
+
if (targetExists && targetExists !== 'd') {
|
|
873
|
+
let error = new Error(`Bad path: ${rPath} already exists as a file`);
|
|
874
|
+
error.code = errorCode.badPath;
|
|
875
|
+
throw error;
|
|
876
|
+
} else if (targetExists) {
|
|
877
|
+
return `${rPath} already exists`;
|
|
878
|
+
}
|
|
860
879
|
if (!recursive) {
|
|
861
880
|
return await _mkdir(rPath);
|
|
862
881
|
}
|
|
@@ -885,7 +904,7 @@ class SftpClient {
|
|
|
885
904
|
* @param {string} remotePath - path to directory to be removed
|
|
886
905
|
* @param {boolean} recursive - if true, remove directories/files in target
|
|
887
906
|
* directory
|
|
888
|
-
* @return {Promise}
|
|
907
|
+
* @return {Promise<String>}
|
|
889
908
|
*/
|
|
890
909
|
async rmdir(remotePath, recursive = false) {
|
|
891
910
|
const _rmdir = (p) => {
|
|
@@ -899,9 +918,8 @@ class SftpClient {
|
|
|
899
918
|
}
|
|
900
919
|
resolve('Successfully removed directory');
|
|
901
920
|
});
|
|
902
|
-
}).finally((
|
|
921
|
+
}).finally(() => {
|
|
903
922
|
removeTempListeners(this, 'rmdir');
|
|
904
|
-
return rsp;
|
|
905
923
|
});
|
|
906
924
|
};
|
|
907
925
|
|
|
@@ -939,7 +957,7 @@ class SftpClient {
|
|
|
939
957
|
* @param {string} remotePath - path to the file to delete
|
|
940
958
|
* @param {boolean} notFoundOK - if true, ignore errors for missing target.
|
|
941
959
|
* Default is false.
|
|
942
|
-
* @return {Promise} with string 'Successfully deleted file' once resolved
|
|
960
|
+
* @return {Promise<String>} with string 'Successfully deleted file' once resolved
|
|
943
961
|
*
|
|
944
962
|
*/
|
|
945
963
|
delete(remotePath, notFoundOK = false) {
|
|
@@ -962,10 +980,9 @@ class SftpClient {
|
|
|
962
980
|
resolve(`Successfully deleted ${remotePath}`);
|
|
963
981
|
});
|
|
964
982
|
}
|
|
965
|
-
}).finally((
|
|
983
|
+
}).finally(() => {
|
|
966
984
|
removeTempListeners(this, 'delete');
|
|
967
985
|
this._resetEventFlags();
|
|
968
|
-
return rsp;
|
|
969
986
|
});
|
|
970
987
|
}
|
|
971
988
|
|
|
@@ -977,7 +994,7 @@ class SftpClient {
|
|
|
977
994
|
* @param {string} fromPath - path to the file to be renamed.
|
|
978
995
|
* @param {string} toPath - path to the new name.
|
|
979
996
|
*
|
|
980
|
-
* @return {Promise}
|
|
997
|
+
* @return {Promise<String>}
|
|
981
998
|
*
|
|
982
999
|
*/
|
|
983
1000
|
rename(fromPath, toPath) {
|
|
@@ -999,10 +1016,9 @@ class SftpClient {
|
|
|
999
1016
|
resolve(`Successfully renamed ${fromPath} to ${toPath}`);
|
|
1000
1017
|
});
|
|
1001
1018
|
}
|
|
1002
|
-
}).finally((
|
|
1019
|
+
}).finally(() => {
|
|
1003
1020
|
removeTempListeners(this, 'rename');
|
|
1004
1021
|
this._resetEventFlags();
|
|
1005
|
-
return rsp;
|
|
1006
1022
|
});
|
|
1007
1023
|
}
|
|
1008
1024
|
|
|
@@ -1015,7 +1031,7 @@ class SftpClient {
|
|
|
1015
1031
|
* @param {string} fromPath - path to the file to be renamed.
|
|
1016
1032
|
* @param {string} toPath - path the new name.
|
|
1017
1033
|
*
|
|
1018
|
-
* @return {Promise}
|
|
1034
|
+
* @return {Promise<String>}
|
|
1019
1035
|
*
|
|
1020
1036
|
*/
|
|
1021
1037
|
posixRename(fromPath, toPath) {
|
|
@@ -1037,10 +1053,9 @@ class SftpClient {
|
|
|
1037
1053
|
resolve(`Successful POSIX rename ${fromPath} to ${toPath}`);
|
|
1038
1054
|
});
|
|
1039
1055
|
}
|
|
1040
|
-
}).finally((
|
|
1056
|
+
}).finally(() => {
|
|
1041
1057
|
removeTempListeners(this, 'posixRename');
|
|
1042
1058
|
this._resetEventFlags();
|
|
1043
|
-
return rsp;
|
|
1044
1059
|
});
|
|
1045
1060
|
}
|
|
1046
1061
|
|
|
@@ -1052,7 +1067,7 @@ class SftpClient {
|
|
|
1052
1067
|
* @param {string} remotePath - path to the remote target object.
|
|
1053
1068
|
* @param {number | string} mode - the new octal mode to set
|
|
1054
1069
|
*
|
|
1055
|
-
* @return {Promise}
|
|
1070
|
+
* @return {Promise<String>}
|
|
1056
1071
|
*/
|
|
1057
1072
|
chmod(remotePath, mode) {
|
|
1058
1073
|
return new Promise((resolve, reject) => {
|
|
@@ -1064,10 +1079,9 @@ class SftpClient {
|
|
|
1064
1079
|
}
|
|
1065
1080
|
resolve('Successfully change file mode');
|
|
1066
1081
|
});
|
|
1067
|
-
}).finally((
|
|
1082
|
+
}).finally(() => {
|
|
1068
1083
|
removeTempListeners(this, 'chmod');
|
|
1069
1084
|
this._resetEventFlags();
|
|
1070
|
-
return rsp;
|
|
1071
1085
|
});
|
|
1072
1086
|
}
|
|
1073
1087
|
|
|
@@ -1081,8 +1095,7 @@ class SftpClient {
|
|
|
1081
1095
|
* @param {String} dstDir - remote destination directory
|
|
1082
1096
|
* @param {RegExp} filter - (Optional) a regular expression used to select
|
|
1083
1097
|
* files and directories to upload
|
|
1084
|
-
* @returns {String}
|
|
1085
|
-
* @throws {Error}
|
|
1098
|
+
* @returns {Promise<String>}
|
|
1086
1099
|
*/
|
|
1087
1100
|
async uploadDir(srcDir, dstDir, filter = /.*/) {
|
|
1088
1101
|
try {
|
|
@@ -1141,8 +1154,7 @@ class SftpClient {
|
|
|
1141
1154
|
* @param {String} dstDir - local destination directory
|
|
1142
1155
|
* @param {RegExp} filter - (Optional) a regular expression used to select
|
|
1143
1156
|
* files and directories to upload
|
|
1144
|
-
* @returns {Promise}
|
|
1145
|
-
* @throws {Error}
|
|
1157
|
+
* @returns {Promise<String>}
|
|
1146
1158
|
*/
|
|
1147
1159
|
async downloadDir(srcDir, dstDir, filter = /.*/) {
|
|
1148
1160
|
try {
|
|
@@ -1193,6 +1205,7 @@ class SftpClient {
|
|
|
1193
1205
|
*
|
|
1194
1206
|
* End the SFTP connection
|
|
1195
1207
|
*
|
|
1208
|
+
* @returns {Promise<Boolean>}
|
|
1196
1209
|
*/
|
|
1197
1210
|
end() {
|
|
1198
1211
|
let endCloseHandler;
|
|
@@ -1209,13 +1222,12 @@ class SftpClient {
|
|
|
1209
1222
|
this.debugMsg('end: Have connection - calling end()');
|
|
1210
1223
|
this.client.end();
|
|
1211
1224
|
}
|
|
1212
|
-
}).finally((
|
|
1225
|
+
}).finally(() => {
|
|
1213
1226
|
this.debugMsg('end: finally clause fired');
|
|
1214
1227
|
removeTempListeners(this, 'end');
|
|
1215
1228
|
this.removeListener('close', endCloseHandler);
|
|
1216
1229
|
this.endCalled = false;
|
|
1217
1230
|
this._resetEventFlags();
|
|
1218
|
-
return resp;
|
|
1219
1231
|
});
|
|
1220
1232
|
}
|
|
1221
1233
|
}
|
package/src/utils.js
CHANGED
|
@@ -58,7 +58,13 @@ function fmtError(err, name = 'sftp', eCode, retryCount) {
|
|
|
58
58
|
return newError;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
function addToTempListenerList(obj, name, evt, fn) {
|
|
62
|
+
if (name in obj.tempListeners) {
|
|
63
|
+
obj.tempListeners[name].push([evt, fn]);
|
|
64
|
+
} else {
|
|
65
|
+
obj.tempListeners[name] = [[evt, fn]];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
62
68
|
|
|
63
69
|
/**
|
|
64
70
|
* Simple default error listener. Will reformat the error message and
|
|
@@ -83,7 +89,7 @@ function errorListener(client, name, reject) {
|
|
|
83
89
|
}
|
|
84
90
|
}
|
|
85
91
|
};
|
|
86
|
-
|
|
92
|
+
addToTempListenerList(client, name, 'error', fn);
|
|
87
93
|
return fn;
|
|
88
94
|
}
|
|
89
95
|
|
|
@@ -104,7 +110,7 @@ function endListener(client, name, reject) {
|
|
|
104
110
|
}
|
|
105
111
|
}
|
|
106
112
|
};
|
|
107
|
-
|
|
113
|
+
addToTempListenerList(client, name, 'end', fn);
|
|
108
114
|
return fn;
|
|
109
115
|
}
|
|
110
116
|
|
|
@@ -125,7 +131,7 @@ function closeListener(client, name, reject) {
|
|
|
125
131
|
}
|
|
126
132
|
}
|
|
127
133
|
};
|
|
128
|
-
|
|
134
|
+
addToTempListenerList(client, name, 'close', fn);
|
|
129
135
|
return fn;
|
|
130
136
|
}
|
|
131
137
|
|
|
@@ -138,10 +144,12 @@ function addTempListeners(obj, name, reject) {
|
|
|
138
144
|
|
|
139
145
|
function removeTempListeners(obj, name) {
|
|
140
146
|
obj.debugMsg(`${name}: Removing temp event listeners`);
|
|
141
|
-
|
|
142
|
-
obj.
|
|
143
|
-
|
|
144
|
-
|
|
147
|
+
if (name in obj.tempListeners) {
|
|
148
|
+
obj.tempListeners[name].forEach(([e, fn]) => {
|
|
149
|
+
obj.client.removeListener(e, fn);
|
|
150
|
+
});
|
|
151
|
+
obj.tempListeners = [];
|
|
152
|
+
}
|
|
145
153
|
}
|
|
146
154
|
|
|
147
155
|
/**
|
|
@@ -203,29 +211,33 @@ function haveLocalAccess(filePath, mode = 'r') {
|
|
|
203
211
|
code: 0,
|
|
204
212
|
};
|
|
205
213
|
} catch (err) {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
214
|
+
switch (err.errno) {
|
|
215
|
+
case -2:
|
|
216
|
+
return {
|
|
217
|
+
status: false,
|
|
218
|
+
type: null,
|
|
219
|
+
details: 'not exist',
|
|
220
|
+
code: -2,
|
|
221
|
+
};
|
|
222
|
+
case -13:
|
|
223
|
+
return {
|
|
224
|
+
status: false,
|
|
225
|
+
type: localExists(filePath),
|
|
226
|
+
details: 'permission denied',
|
|
227
|
+
code: -13,
|
|
228
|
+
};
|
|
229
|
+
case -20:
|
|
230
|
+
return {
|
|
231
|
+
status: false,
|
|
232
|
+
type: null,
|
|
233
|
+
details: 'parent not a directory',
|
|
234
|
+
};
|
|
235
|
+
default:
|
|
236
|
+
return {
|
|
237
|
+
status: false,
|
|
238
|
+
type: null,
|
|
239
|
+
details: err.message,
|
|
240
|
+
};
|
|
229
241
|
}
|
|
230
242
|
}
|
|
231
243
|
}
|
|
@@ -281,10 +293,10 @@ async function normalizeRemotePath(client, aPath) {
|
|
|
281
293
|
try {
|
|
282
294
|
if (aPath.startsWith('..')) {
|
|
283
295
|
let root = await client.realPath('..');
|
|
284
|
-
return root + client.remotePathSep + aPath.
|
|
296
|
+
return root + client.remotePathSep + aPath.slice(3);
|
|
285
297
|
} else if (aPath.startsWith('.')) {
|
|
286
298
|
let root = await client.realPath('.');
|
|
287
|
-
return root + client.remotePathSep + aPath.
|
|
299
|
+
return root + client.remotePathSep + aPath.slice(2);
|
|
288
300
|
}
|
|
289
301
|
return aPath;
|
|
290
302
|
} catch (err) {
|
|
@@ -333,6 +345,7 @@ function sleep(ms) {
|
|
|
333
345
|
|
|
334
346
|
module.exports = {
|
|
335
347
|
fmtError,
|
|
348
|
+
addToTempListenerList,
|
|
336
349
|
errorListener,
|
|
337
350
|
endListener,
|
|
338
351
|
closeListener,
|