ssh2-sftp-client 10.0.2 → 11.0.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.org CHANGED
@@ -3,41 +3,148 @@
3
3
 
4
4
  * Overview
5
5
 
6
- an SFTP client for node.js, a wrapper around [[https://github.com/mscdex/ssh2][SSH2]] which provides a high level
7
- convenience abstraction as well as a Promise based API.
6
+ This packan provides the class SftpClient, an SFTP client for node.js. It is a promise
7
+ based decorator class around the excdellent [[https://github.com/mscdex/ssh2][SSH2]] package, which provides a pure node
8
+ Javascript event based ssh2 implementation.
8
9
 
9
10
  Documentation on the methods and available options in the underlying modules can
10
- be found on the [[https://github.com/mscdex/ssh2][SSH2]] project pages. As this module is really just a wrapper around the
11
+ be found on the [[https://github.com/mscdex/ssh2][SSH2]] project pages. As the ssh2-sftp-client package just a wrapper around the
11
12
  ~ssh2~ module, you will find lots of useful information, tips and examples in the ~ssh2~
12
13
  repository.
13
14
 
14
- Current stable release is *v10.0.2
15
+ Current stable release is *v11.0.0.
15
16
 
16
- Code has been tested against Node versions 16.20.2, 18.18.2, 20.10.0 and 21.5.0. However,
17
- only versions from v18 are actively supported. It should also be noted that a significant
18
- performance improvement has been observed with versions >= 18. Version v16 is
19
- significantly slower.
20
-
21
- Node versions < 16.x are not supported.
17
+ Code has been tested against Node versions 18.20.4, 20.16.0 and 22.5.1. Node versions
18
+ prior to v18.x are not supported. Also note there is currently a deprecation warning when
19
+ using node v22+. This is from the ~ssh2~ package and outside control of this package.
22
20
 
23
21
  If you find this module useful and you would like to support the on-going maintenance and
24
22
  support of users, please consider making a small [[https://square.link/u/gB2kSdkY?src=embed][donation]].
25
23
 
26
- ** Version 10.0.0 Changes
27
-
28
- - The main change in this version is adding of limits on the number of promises which can
29
- be active at the same time. Version 9.1.0 extended the use of multiple promises to
30
- improve performance with downloadDir() and uploadDir(). However, for directories with
31
- really large numbers of files, this often resulted in an error because the methods would
32
- try to create more concurrent promises than was possible given available resources. This
33
- issue has been fixed by adding a new property called ~promiseLimit~, which is limited to
34
- 10 by default. A new configuration property, ~promiseLimit~, is now available for setting
35
- the maximum number of concurrent promises the downloadDir()/uploadDir() methods will
36
- create when downloading or uploading directory trees.
24
+ ** Version 11.0.0 Changes
25
+
26
+ The main change in v11 concerns how the package manages events raised by the =ssh2= packagwe
27
+ it depends on. Managing events within the context of asynchronous code as ocurs when
28
+ using promises is challenging. To understand some of the more subtle issues involved, it
29
+ is recommended you read the [[https://nodejs.org/docs/latest/api/events.html#asynchronous-vs-synchronous][Asynchronous vs. synchronous]] and [[https://nodejs.org/docs/latest/api/events.html#error-events][Error events]] sections of the
30
+ Events chapter from the node documentation.
31
+
32
+ The previous versions of this package use a global event handler approach to manage events
33
+ raised outside the execution of any promise. However, this approach is problematic as all
34
+ the global listeners can really do is raise an error. Attempting to catch errors raised
35
+ inside event handlers is extremely difficult to manage within client code because those
36
+ errors are raised inside a separate asynchronous execution context. In version 11,
37
+ this approach has been replaced by a mechanism whereby the client code can pass in
38
+ application specific global handlers if desired. If no handlers are defined, default
39
+ handlers will log the event and when necessary, invalidate any existing connection
40
+ objects. See the [[Background]] section for details.
41
+
42
+ ** Background
43
+
44
+ In basic terms =ssh2-sftp-client= is a simple wrapper around the =ssh2= package which provides
45
+ a promise base API for interacting with a remote SFTP server . The =ssh2= package provides
46
+ an event based API for interacting with the ~ssh~ protcolo. The ~ssh2-sftp-client~ package
47
+ uses the ~sftp~ subsystem of this protocol to implement the basic operations typically
48
+ associated with an ~sftp~ client.
49
+
50
+ Wrapping an event based API with a promised based API comes with a number of
51
+ challenges. In particular, efficiently and reliably managing events within the context of
52
+ asynchrounous code execution. This package uses the following strategies;
53
+
54
+ - All direct interactions with the ~ssh2~ API are wrapped in promise objects. When the
55
+ API call succeeds, the assoiated promise will be sucessfully resolved. When an error occurs,
56
+ the promise is rejected.
57
+
58
+ - An error can either be due to a low level network error, such as a lost connection
59
+ to the remote server or due to an operational error, such as a file not
60
+ existing or not having the appropriate permissions for access.
61
+
62
+ - Each of the available ~SftpClient~ methods wrap the method call inside a promise. In
63
+ crwating eacvh promise, the class adds temporary event listenrs for the error, end
64
+ and close events and links those liseners to the method's promise via it's ~reject()~
65
+ method.
66
+
67
+ - If a promise is waiting to be fulfilled when either of the two types of errors
68
+ occurs, the error will be communicated back to client code via a rejected promise.
69
+
70
+ - When the ~ssh2~ emitter raises an event outside the context of any promise, that event
71
+ will be handled by global event handlers. By default, these event handlers will log
72
+ the event and will invalidate any existing socket connection objects, preventing any
73
+ further API calls until a new connection is extablished.
74
+
75
+ - The ~SftpClient~ class constructor supports an optoinal second argument which is an
76
+ objedct whi8ch can have any of three properties representing event callbaqck
77
+ funct5ions which will be executed for each of the possible events error, end and
78
+ close.
79
+
80
+ The need for both global listeners and temporary promise listeners is because network end,
81
+ close or error events can occur at any time, including in-betwseen API calls. During an
82
+ API call, a promise is active and can be used to communicate event information back to the calling
83
+ code via normal promise communication means i.e. async/await with try/catch or promise chain's then/catch
84
+ mechanism. However, outside API calls, no promise exists and there is no reliable
85
+ mechanism to return error and other event information back to calling code. You cannot
86
+ reliably use try/cdatch to catch errorsx thrown inside event listenrs as you lack control
87
+ over when the listener code runs. Your try/catch block can easily complete before the
88
+ error is raised as there is no equivalent /await/ type functionality in this situation.
89
+
90
+ As there is no simple default way to return error and other event information back to the
91
+ calling code, ~ssh2-sftp-client~ doesn't try to. Instead, the default action is
92
+ to just log the event information and invalidate any existing sftp connections. This
93
+ strategy is often sufficient for many use cases. For those cases where it isn't, client
94
+ code can pass in end, close and/or error listener functions when instantiating the
95
+ ~SftpClient~ object. If provided, these listners will be executed whenever the default
96
+ global listeners are executed, which is whenever the ~ssh2~ event emitter raises an end,
97
+ close or error event which is not handled by one of the temporary promise linked event
98
+ listeners.
99
+
100
+ Version 11 of ~ssh2-sftp-client~ also changes the behaviour of the temporary promise linked
101
+ /end/ and /close/ listeners. Prior to versioln 11, these listeners did not reject
102
+ promises. They would only invalidate the underlying ~ssh~ connection object. Only the /error/
103
+ listener would actually reject the associated promise. This was done because you cannot
104
+ guarantee the order in which events are responded to.
105
+
106
+ In most cases, events will occur in the order /error/, /end/ and then /close/. The error event
107
+ would contain details about the cause of the error while the end and close events just
108
+ communicvate that these events have been raised. The normal flow would be
109
+
110
+ - Error event occurs including error cause description. Listener catches event,
111
+ creates an error object and calls the associated promises reject function to reject
112
+ the promise. The calling process receives a rejected promise object.
113
+
114
+ - End event occurs. The end listener catches the event and marks the connection object
115
+ as invalid as the socket connection has been ended. There is no need to call the
116
+ reject method of the associated promise as it has already been called by the error
117
+ listener and you can only call one promise resolution function.
118
+
119
+ - Close event occurs. This event means the socket connection has been closed. The
120
+ listener will ensure any connection information has been invalidated. Aga8in, ther
121
+ is no need to call the reject method of the associated promise as it has already
122
+ been called by the error listener.
123
+
124
+ In some cases, no ende event is raised and you only get an error event followed by a close
125
+ event. In versions of ~ssh2-sftp-client~ prior to version 11, neither the end or the close
126
+ listeners attempted to call the reject method of the associated promise. It was assumed
127
+ that all sftp servers would raise an error event whenever a connection was unexpectedly
128
+ ended or closed. Unfortunately, it turns out some sftp servers are not well behaved and
129
+ will terminate the connection without providing any error information or raising an error
130
+ event. When this occurred in versions prior to version 11, it could result in either an
131
+ API call hanging because its associated promise never gets rejected or resolved or the
132
+ call gets rejected with a timeout error aftrer a significant delay.
133
+
134
+ In order to handle the possible hanging issue in version 11, the temporary promise linked
135
+ end and close listeners have been updated to always call the promise's reject function if
136
+ they fire. While this works, it can cause a minor issue. As wse cannot gurantee the order
137
+ in which events are resonded to by listeners, it is possible that either the end or close
138
+ listener may be executed before the error listener. When this occurs, the promise is
139
+ rejected, but the only information wse have at that point is that the promise wsas reject
140
+ due to either an end or close event. We don't yet have any details regarding what error
141
+ has caused the unexpected end or close event. Furthermore, because only the first promise
142
+ resolution function call has any effect, calling reject within the error listener
143
+ (assuming an error event does eventually arrive) has no effect and does not communicate
144
+ error information back to the caller. This means that in some circumstances, especially
145
+ when working with some poorly behaved sftp servers, an sftp connection will be lost/closed
146
+ with no indication as to reason. This can make diagnosis and bug tracking frustrating.
37
147
 
38
- - Various minor documentation fixes and some minor fixes for typos in option property
39
- names.
40
-
41
148
  * Installation
42
149
 
43
150
  #+begin_src shell
@@ -144,7 +251,7 @@ refer your issues to the maintainers of those modules.
144
251
 
145
252
  ** Methods
146
253
 
147
- *** new SftpClient(name) ===> SFTP client object
254
+ *** new SftpClient(name, callbacks) ===> SFTP client object
148
255
 
149
256
  Constructor to create a new ~ssh2-sftp-client~ object. An optional ~name~ string
150
257
  can be provided, which will be used in error messages to help identify which
@@ -152,8 +259,14 @@ client has thrown the error.
152
259
 
153
260
  **** Constructor Arguments
154
261
 
155
- - name :: string. An optional name string used in error messages
156
-
262
+ - name :: string. An optional name string used in error messages. Defaults to the string
263
+ 'sftp'
264
+ - callbacks :: object. An object with the properties error, end and close. Associted with
265
+ each property is a function that will be executed whenever the associated global event
266
+ listener for error, end or close is executed. The error function should accept one
267
+ argument. The end and close functions have no arguments. Default functions just print a
268
+ message to ~console.log()~.
269
+
157
270
  **** Example Use
158
271
 
159
272
  #+begin_src javascript
@@ -989,7 +1102,7 @@ remote SFTP server will result in failures.
989
1102
 
990
1103
  *** downloadDir(srcDir, dstDir, options) ==> string
991
1104
 
992
- Download the remote directory specified by ~srcDir~ to the local file system
1105
+ Download the content of the remote directory specified by ~srcDir~ to the local file system
993
1106
  directory specified by ~dstDir~. The ~dstDir~ directory will be created if
994
1107
  required. All sub directories within ~srcDir~ will also be copied. Any existing
995
1108
  files in the local path will be overwritten. No files in the local path will be
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ssh2-sftp-client",
3
- "version": "10.0.2",
3
+ "version": "11.0.0",
4
4
  "description": "ssh2 sftp client for node",
5
5
  "main": "src/index.js",
6
6
  "repository": {
@@ -18,7 +18,7 @@
18
18
  "lint": "eslint \"src/**/*.js\" \"test/**/*.js\""
19
19
  },
20
20
  "engines": {
21
- "node": ">=16.20.2"
21
+ "node": ">=18.20.4"
22
22
  },
23
23
  "author": "Tim Cross",
24
24
  "email": "theophilusx@gmail.com",
@@ -34,20 +34,20 @@
34
34
  ],
35
35
  "license": "Apache-2.0",
36
36
  "devDependencies": {
37
- "chai": "^4.3.10",
38
- "chai-as-promised": "^7.1.1",
37
+ "chai": "^4.5.0",
38
+ "chai-as-promised": "^7.1.2",
39
39
  "chai-subset": "^1.6.0",
40
40
  "checksum": "^1.0.0",
41
41
  "dotenv": "^16.0.0",
42
- "eslint": "^8.51.0",
42
+ "eslint": "^9.6.0",
43
43
  "eslint-config-prettier": "^9.0.0",
44
44
  "eslint-plugin-mocha": "^10.2.0",
45
45
  "eslint-plugin-node": "^11.1.0",
46
46
  "eslint-plugin-promise": "^6.0.0",
47
- "eslint-plugin-unicorn": "^50.0.1",
47
+ "eslint-plugin-unicorn": "^54.0.0",
48
48
  "mocha": "^10.0.0",
49
49
  "moment": "^2.29.1",
50
- "nyc": "^15.1.0",
50
+ "nyc": "^17.0.0",
51
51
  "prettier": "^3.0.3",
52
52
  "through2": "^4.0.2",
53
53
  "winston": "^3.11.0"
package/src/index.js CHANGED
@@ -1,5 +1,4 @@
1
1
  'use strict';
2
-
3
2
  const { Client } = require('ssh2');
4
3
  const fs = require('node:fs');
5
4
  const concat = require('concat-stream');
@@ -19,11 +18,18 @@ const {
19
18
  const { errorCode } = require('./constants');
20
19
 
21
20
  class SftpClient {
22
- constructor(clientName) {
23
- this.version = '10.0.2';
21
+ constructor(
22
+ clientName = 'sftp',
23
+ callbacks = {
24
+ error: (err) => console.error(`Global error listener: ${err.message}`),
25
+ end: () => console.log('Global end listener: end event raised'),
26
+ close: () => console.log('Global close listener: close event raised'),
27
+ },
28
+ ) {
29
+ this.version = '11.0.0';
24
30
  this.client = new Client();
25
31
  this.sftp = undefined;
26
- this.clientName = clientName || 'sftp';
32
+ this.clientName = clientName;
27
33
  this.endCalled = false;
28
34
  this.errorHandled = false;
29
35
  this.closeHandled = false;
@@ -31,9 +37,10 @@ class SftpClient {
31
37
  this.remotePlatform = 'unix';
32
38
  this.debug = undefined;
33
39
  this.promiseLimit = 10;
34
- this.client.on('close', globalListener(this, 'close'));
35
- this.client.on('end', globalListener(this, 'end'));
36
- this.client.on('error', globalListener(this, 'error'));
40
+ this.eventCallbacks = callbacks;
41
+ this.client.on('close', globalListener(this, 'close', this.eventCallbacks));
42
+ this.client.on('end', globalListener(this, 'end', this.eventCallbacks));
43
+ this.client.on('error', globalListener(this, 'error', this.eventCallbacks));
37
44
  }
38
45
 
39
46
  debugMsg(msg, obj) {
@@ -128,14 +135,12 @@ class SftpClient {
128
135
  return new Promise((resolve, reject) => {
129
136
  listeners = addTempListeners(this, 'getConnection', reject);
130
137
  doReady = () => {
131
- this.debugMsg('getConnection ready listener: got connection - promise resolved');
132
138
  resolve(true);
133
139
  };
134
140
  this.on('ready', doReady);
135
141
  try {
136
142
  this.client.connect(config);
137
143
  } catch (err) {
138
- this.debugMsg(`getConnection: ${err.message}`);
139
144
  reject(err);
140
145
  }
141
146
  }).finally(() => {
@@ -219,6 +224,7 @@ class SftpClient {
219
224
  }
220
225
  });
221
226
  const sftp = await this.getSftpChannel();
227
+ this.endCalled = false;
222
228
  return sftp;
223
229
  } catch (err) {
224
230
  this.end();
@@ -246,18 +252,14 @@ class SftpClient {
246
252
  if (addListeners) {
247
253
  listeners = addTempListeners(this, 'realPath', reject);
248
254
  }
249
- this.debugMsg(`realPath -> ${remotePath}`);
250
255
  this.sftp.realpath(remotePath, (err, absPath) => {
251
256
  if (err) {
252
257
  if (err.code === 2) {
253
- this.debugMsg('realPath <- ""');
254
258
  resolve('');
255
259
  } else {
256
- this.debugMsg(`${err.message} ${remotePath}`, 'realPath');
257
260
  reject(this.fmtError(`${err.message} ${remotePath}`, 'realPath', err.code));
258
261
  }
259
262
  }
260
- this.debugMsg(`realPath <- ${absPath}`);
261
263
  resolve(absPath);
262
264
  });
263
265
  }).finally(() => {
@@ -313,7 +315,6 @@ class SftpClient {
313
315
  isFIFO: stats.isFIFO(),
314
316
  isSocket: stats.isSocket(),
315
317
  };
316
- this.debugMsg('_xstat: result = ', result);
317
318
  resolve(result);
318
319
  }
319
320
  };
@@ -382,13 +383,11 @@ class SftpClient {
382
383
  * object if it does
383
384
  */
384
385
  async exists(remotePath) {
385
- this.debugMsg(`exists: remotePath = ${remotePath}`);
386
386
  try {
387
387
  if (remotePath === '.') {
388
388
  return 'd';
389
389
  }
390
390
  const info = await this.lstat(remotePath);
391
- this.debugMsg('exists: <- ', info);
392
391
  if (info.isDirectory) {
393
392
  return 'd';
394
393
  } else if (info.isSymbolicLink) {
@@ -501,20 +500,20 @@ class SftpClient {
501
500
  };
502
501
  rdr = this.sftp.createReadStream(remotePath, options.readStreamOptions);
503
502
  rdr.once('error', (err) => {
504
- if (dst && typeof dst !== 'string' && !dst.destroyed) {
505
- dst.destroy();
503
+ if (dst && typeof dst === 'string' && wtr && !wtr.destroyed) {
504
+ wtr.destroy();
506
505
  }
507
506
  reject(this.fmtError(`${err.message} ${remotePath}`, 'get', err.code));
508
507
  });
509
508
  if (dst === undefined) {
510
509
  // no dst specified, return buffer of data
511
- this.debugMsg('get resolving buffer of data');
510
+ this.debugMsg('get resolving with buffer of data');
512
511
  wtr = concat((buff) => {
513
512
  resolve(buff);
514
513
  });
515
514
  } else if (typeof dst === 'string') {
516
515
  // dst local file path
517
- this.debugMsg('get returning local file');
516
+ this.debugMsg(`get called with file path destination ${dst}`);
518
517
  const localCheck = haveLocalCreate(dst);
519
518
  if (localCheck.status) {
520
519
  wtr = fs.createWriteStream(dst, options.writeStreamOptions);
@@ -528,7 +527,7 @@ class SftpClient {
528
527
  );
529
528
  }
530
529
  } else {
531
- this.debugMsg('get: returning data into supplied stream');
530
+ this.debugMsg('get called with stream destination');
532
531
  wtr = dst;
533
532
  }
534
533
  wtr.once('error', (err) => {
@@ -542,16 +541,17 @@ class SftpClient {
542
541
  });
543
542
  rdr.once('end', () => {
544
543
  if (typeof dst === 'string') {
545
- this.debugMsg('get: resolving with dst filename');
546
544
  resolve(dst);
547
545
  } else if (dst !== undefined) {
548
- this.debugMsg('get: resolving with writer stream object');
549
546
  resolve(wtr);
550
547
  }
551
548
  });
552
549
  rdr.pipe(wtr, options.pipeOptions);
553
550
  }
554
551
  }).finally(() => {
552
+ if (rdr && !rdr.destroyed) {
553
+ rdr.destroy();
554
+ }
555
555
  if (addListeners) {
556
556
  removeTempListeners(this, listeners, 'get');
557
557
  }
@@ -661,7 +661,6 @@ class SftpClient {
661
661
 
662
662
  async fastPut(localPath, remotePath, options) {
663
663
  try {
664
- this.debugMsg(`fastPut -> local ${localPath} remote ${remotePath}`);
665
664
  const localCheck = haveLocalAccess(localPath);
666
665
  if (!localCheck.status) {
667
666
  throw this.fmtError(
@@ -713,6 +712,9 @@ class SftpClient {
713
712
  if (haveConnection(this, '_put', reject)) {
714
713
  wtr = this.sftp.createWriteStream(rPath, opts.writeStreamOptions);
715
714
  wtr.once('error', (err) => {
715
+ if (typeof lPath === 'string' && rdr && !rdr.destroyed) {
716
+ rdr.destroy();
717
+ }
716
718
  reject(
717
719
  this.fmtError(
718
720
  `Write stream error: ${err.message} ${rPath}`,
@@ -728,10 +730,13 @@ class SftpClient {
728
730
  this.debugMsg('put source is a buffer');
729
731
  wtr.end(lPath);
730
732
  } else {
731
- rdr =
732
- typeof lPath === 'string'
733
- ? fs.createReadStream(lPath, opts.readStreamOptions)
734
- : lPath;
733
+ if (typeof lPath === 'string') {
734
+ this.debugMsg('put source is string path');
735
+ rdr = fs.createReadStream(lPath, opts.readStreamOptions);
736
+ } else {
737
+ this.debugMsg('put source is a stream');
738
+ rdr = lPath;
739
+ }
735
740
  rdr.once('error', (err) => {
736
741
  reject(
737
742
  this.fmtError(
@@ -747,6 +752,9 @@ class SftpClient {
747
752
  }
748
753
  }
749
754
  }).finally(() => {
755
+ if (wtr && !wtr.destroyed) {
756
+ wtr.destroy();
757
+ }
750
758
  if (addListeners) {
751
759
  removeTempListeners(this, listeners, '_put');
752
760
  }
@@ -786,7 +794,6 @@ class SftpClient {
786
794
  listeners = addTempListeners(this, '_append', reject);
787
795
  }
788
796
  if (haveConnection(this, '_append', reject)) {
789
- this.debugMsg(`append -> remote: ${rPath} `, opts);
790
797
  opts.flags = 'a';
791
798
  const stream = this.sftp.createWriteStream(rPath, opts);
792
799
  stream.on('error', (err) => {
@@ -826,7 +833,7 @@ class SftpClient {
826
833
  errorCode.badPath,
827
834
  );
828
835
  }
829
- await this._append(input, remotePath, options);
836
+ return await this._append(input, remotePath, options);
830
837
  } catch (e) {
831
838
  throw e.custom ? e : this.fmtError(e.message, 'append', e.code);
832
839
  }
@@ -941,7 +948,6 @@ class SftpClient {
941
948
  let listeners;
942
949
  return new Promise((resolve, reject) => {
943
950
  listeners = addTempListeners(this, '_rmdir', reject);
944
- this.debugMsg(`_rmdir: dir = ${dir}`);
945
951
  this.sftp.rmdir(dir, (err) => {
946
952
  if (err) {
947
953
  reject(this.fmtError(`${err.message} ${dir}`, 'rmdir', err.code));
@@ -957,7 +963,6 @@ class SftpClient {
957
963
  let listeners;
958
964
  return new Promise((resolve, reject) => {
959
965
  listeners = addTempListeners(this, '_delFiles', reject);
960
- this.debugMsg(`_delFiles: path = ${path} fileList = ${fileList}`);
961
966
  const pList = [];
962
967
  for (const f of fileList) {
963
968
  pList.push(this.delete(`${path}/${f.name}`, true, false));
@@ -973,10 +978,8 @@ class SftpClient {
973
978
  };
974
979
 
975
980
  try {
976
- this.debugMsg(`rmdir: dir = ${remoteDir} recursive = ${recursive}`);
977
981
  const absPath = await normalizeRemotePath(this, remoteDir);
978
982
  const existStatus = await this.exists(absPath);
979
- this.debugMsg(`rmdir: ${absPath} existStatus = ${existStatus}`);
980
983
  if (!existStatus) {
981
984
  throw this.fmtError(
982
985
  `Bad Path: ${remoteDir}: No such directory`,
@@ -992,19 +995,14 @@ class SftpClient {
992
995
  );
993
996
  }
994
997
  if (!recursive) {
995
- this.debugMsg('rmdir: non-recursive - just try to remove it');
996
998
  return await _rmdir(absPath);
997
999
  }
998
1000
  const listing = await this.list(absPath);
999
- this.debugMsg(`rmdir: listing count = ${listing.length}`);
1000
1001
  if (!listing.length) {
1001
- this.debugMsg('rmdir: No sub dir or files, just rmdir');
1002
1002
  return await _rmdir(absPath);
1003
1003
  }
1004
1004
  const fileList = listing.filter((i) => i.type !== 'd');
1005
- this.debugMsg(`rmdir: dir content files to remove = ${fileList.length}`);
1006
1005
  const dirList = listing.filter((i) => i.type === 'd');
1007
- this.debugMsg(`rmdir: sub-directories to remove = ${dirList.length}`);
1008
1006
  await _delFiles(absPath, fileList);
1009
1007
  for (const d of dirList) {
1010
1008
  await this.rmdir(`${absPath}/${d.name}`, true);
@@ -1244,11 +1242,7 @@ class SftpClient {
1244
1242
 
1245
1243
  try {
1246
1244
  haveConnection(this, 'uploadDir');
1247
- this.debugMsg(
1248
- `uploadDir: srcDir = ${srcDir} dstDir = ${dstDir} options = ${options}`,
1249
- );
1250
1245
  const { remoteDir, remoteStatus } = await getRemoteStatus(dstDir);
1251
- this.debugMsg(`uploadDir: remoteDir = ${remoteDir} remoteStatus = ${remoteStatus}`);
1252
1246
  checkLocalStatus(srcDir);
1253
1247
  if (!remoteStatus) {
1254
1248
  await this._mkdir(remoteDir, true);
@@ -1257,7 +1251,6 @@ class SftpClient {
1257
1251
  encoding: 'utf8',
1258
1252
  withFileTypes: true,
1259
1253
  });
1260
- this.debugMsg(`uploadDir: dirEntries = ${dirEntries}`);
1261
1254
  if (options?.filter) {
1262
1255
  dirEntries = dirEntries.filter((item) =>
1263
1256
  options.filter(join(srcDir, item.name), item.isDirectory()),
@@ -1265,8 +1258,6 @@ class SftpClient {
1265
1258
  }
1266
1259
  const dirUploads = dirEntries.filter((item) => item.isDirectory());
1267
1260
  const fileUploads = dirEntries.filter((item) => !item.isDirectory());
1268
- this.debugMsg(`uploadDir: dirUploads = ${dirUploads}`);
1269
- this.debugMsg(`uploadDir: fileUploads = ${fileUploads}`);
1270
1261
  await uploadFiles(srcDir, remoteDir, fileUploads, options?.useFastput);
1271
1262
  for (const d of dirUploads) {
1272
1263
  const src = join(srcDir, d.name);
@@ -1527,6 +1518,7 @@ class SftpClient {
1527
1518
  };
1528
1519
  this.on('close', endCloseHandler);
1529
1520
  if (this.sftp) {
1521
+ this.debugMsg('end: Ending SFTP connection');
1530
1522
  this.client.end();
1531
1523
  } else {
1532
1524
  // no actual connection exists - just resolve
@@ -1536,7 +1528,6 @@ class SftpClient {
1536
1528
  }).finally(() => {
1537
1529
  removeTempListeners(this, listeners, 'end');
1538
1530
  this.removeListener('close', endCloseHandler);
1539
- this.endCalled = false;
1540
1531
  });
1541
1532
  }
1542
1533
  }