@milaboratories/pl-deployments 2.15.17 → 2.15.19

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.
Files changed (104) hide show
  1. package/dist/_virtual/_rolldown/runtime.cjs +29 -0
  2. package/dist/common/os_and_arch.cjs +22 -28
  3. package/dist/common/os_and_arch.cjs.map +1 -1
  4. package/dist/common/os_and_arch.js +23 -26
  5. package/dist/common/os_and_arch.js.map +1 -1
  6. package/dist/common/pl_binary.cjs +35 -33
  7. package/dist/common/pl_binary.cjs.map +1 -1
  8. package/dist/common/pl_binary.d.ts +13 -18
  9. package/dist/common/pl_binary.js +33 -31
  10. package/dist/common/pl_binary.js.map +1 -1
  11. package/dist/common/pl_binary_download.cjs +143 -155
  12. package/dist/common/pl_binary_download.cjs.map +1 -1
  13. package/dist/common/pl_binary_download.d.ts +15 -48
  14. package/dist/common/pl_binary_download.js +138 -133
  15. package/dist/common/pl_binary_download.js.map +1 -1
  16. package/dist/common/pl_version.cjs +5 -6
  17. package/dist/common/pl_version.cjs.map +1 -1
  18. package/dist/common/pl_version.d.ts +4 -1
  19. package/dist/common/pl_version.js +5 -4
  20. package/dist/common/pl_version.js.map +1 -1
  21. package/dist/index.cjs +21 -25
  22. package/dist/index.d.ts +6 -6
  23. package/dist/index.js +7 -6
  24. package/dist/local/pid.cjs +14 -13
  25. package/dist/local/pid.cjs.map +1 -1
  26. package/dist/local/pid.js +11 -11
  27. package/dist/local/pid.js.map +1 -1
  28. package/dist/local/pl.cjs +194 -223
  29. package/dist/local/pl.cjs.map +1 -1
  30. package/dist/local/pl.d.ts +65 -65
  31. package/dist/local/pl.js +190 -202
  32. package/dist/local/pl.js.map +1 -1
  33. package/dist/local/process.cjs +37 -59
  34. package/dist/local/process.cjs.map +1 -1
  35. package/dist/local/process.d.ts +10 -10
  36. package/dist/local/process.js +36 -57
  37. package/dist/local/process.js.map +1 -1
  38. package/dist/local/trace.cjs +14 -17
  39. package/dist/local/trace.cjs.map +1 -1
  40. package/dist/local/trace.d.ts +6 -7
  41. package/dist/local/trace.js +15 -15
  42. package/dist/local/trace.js.map +1 -1
  43. package/dist/package.cjs +12 -0
  44. package/dist/package.cjs.map +1 -0
  45. package/dist/package.js +6 -0
  46. package/dist/package.js.map +1 -0
  47. package/dist/ssh/connection_info.cjs +36 -53
  48. package/dist/ssh/connection_info.cjs.map +1 -1
  49. package/dist/ssh/connection_info.d.ts +691 -713
  50. package/dist/ssh/connection_info.js +35 -51
  51. package/dist/ssh/connection_info.js.map +1 -1
  52. package/dist/ssh/pl.cjs +551 -638
  53. package/dist/ssh/pl.cjs.map +1 -1
  54. package/dist/ssh/pl.d.ts +120 -117
  55. package/dist/ssh/pl.js +548 -636
  56. package/dist/ssh/pl.js.map +1 -1
  57. package/dist/ssh/pl_paths.cjs +22 -24
  58. package/dist/ssh/pl_paths.cjs.map +1 -1
  59. package/dist/ssh/pl_paths.js +21 -19
  60. package/dist/ssh/pl_paths.js.map +1 -1
  61. package/dist/ssh/ssh.cjs +554 -618
  62. package/dist/ssh/ssh.cjs.map +1 -1
  63. package/dist/ssh/ssh.d.ts +139 -136
  64. package/dist/ssh/ssh.js +548 -616
  65. package/dist/ssh/ssh.js.map +1 -1
  66. package/dist/ssh/ssh_errors.cjs +45 -60
  67. package/dist/ssh/ssh_errors.cjs.map +1 -1
  68. package/dist/ssh/ssh_errors.js +45 -58
  69. package/dist/ssh/ssh_errors.js.map +1 -1
  70. package/dist/ssh/supervisord.cjs +50 -68
  71. package/dist/ssh/supervisord.cjs.map +1 -1
  72. package/dist/ssh/supervisord.d.ts +11 -21
  73. package/dist/ssh/supervisord.js +50 -66
  74. package/dist/ssh/supervisord.js.map +1 -1
  75. package/package.json +10 -10
  76. package/dist/common/os_and_arch.d.ts +0 -9
  77. package/dist/common/os_and_arch.d.ts.map +0 -1
  78. package/dist/common/pl_binary.d.ts.map +0 -1
  79. package/dist/common/pl_binary_download.d.ts.map +0 -1
  80. package/dist/common/pl_version.d.ts.map +0 -1
  81. package/dist/index.cjs.map +0 -1
  82. package/dist/index.d.ts.map +0 -1
  83. package/dist/index.js.map +0 -1
  84. package/dist/local/options.d.ts +0 -31
  85. package/dist/local/options.d.ts.map +0 -1
  86. package/dist/local/pid.d.ts +0 -4
  87. package/dist/local/pid.d.ts.map +0 -1
  88. package/dist/local/pl.d.ts.map +0 -1
  89. package/dist/local/process.d.ts.map +0 -1
  90. package/dist/local/trace.d.ts.map +0 -1
  91. package/dist/package.json.cjs +0 -8
  92. package/dist/package.json.cjs.map +0 -1
  93. package/dist/package.json.js +0 -6
  94. package/dist/package.json.js.map +0 -1
  95. package/dist/ssh/__tests__/common-utils.d.ts +0 -12
  96. package/dist/ssh/__tests__/common-utils.d.ts.map +0 -1
  97. package/dist/ssh/connection_info.d.ts.map +0 -1
  98. package/dist/ssh/pl.d.ts.map +0 -1
  99. package/dist/ssh/pl_paths.d.ts +0 -20
  100. package/dist/ssh/pl_paths.d.ts.map +0 -1
  101. package/dist/ssh/ssh.d.ts.map +0 -1
  102. package/dist/ssh/ssh_errors.d.ts +0 -29
  103. package/dist/ssh/ssh_errors.d.ts.map +0 -1
  104. package/dist/ssh/supervisord.d.ts.map +0 -1
package/dist/ssh/ssh.js CHANGED
@@ -1,625 +1,557 @@
1
- import ssh, { Client } from 'ssh2';
2
- import net from 'node:net';
3
- import dns from 'node:dns';
4
- import fs from 'node:fs';
5
- import { readFile } from 'node:fs/promises';
6
- import upath from 'upath';
7
- import { RetryablePromise, retry, Retry3TimesWithDelay } from '@milaboratories/ts-helpers';
8
- import { randomBytes } from 'node:crypto';
9
- import { SFTPUploadError, SFTPError } from './ssh_errors.js';
1
+ import { SFTPError, SFTPUploadError } from "./ssh_errors.js";
2
+ import { Retry3TimesWithDelay, RetryablePromise, retry } from "@milaboratories/ts-helpers";
3
+ import fs from "node:fs";
4
+ import { readFile } from "node:fs/promises";
5
+ import upath from "upath";
6
+ import ssh, { Client } from "ssh2";
7
+ import net from "node:net";
8
+ import dns from "node:dns";
9
+ import { randomBytes } from "node:crypto";
10
10
 
11
+ //#region src/ssh/ssh.ts
11
12
  const defaultConfig = {
12
- keepaliveInterval: 60000,
13
- keepaliveCountMax: 10,
13
+ keepaliveInterval: 6e4,
14
+ keepaliveCountMax: 10
15
+ };
16
+ var SshClient = class SshClient {
17
+ config;
18
+ homeDir;
19
+ forwardedServers = [];
20
+ constructor(logger, client) {
21
+ this.logger = logger;
22
+ this.client = client;
23
+ }
24
+ /**
25
+ * Initializes the SshClient and establishes a connection using the provided configuration.
26
+ * @param config - The connection configuration object for the SSH client.
27
+ * @returns A new instance of SshClient with an active connection.
28
+ */
29
+ static async init(logger, config) {
30
+ const withDefaults = {
31
+ ...defaultConfig,
32
+ ...config
33
+ };
34
+ const client = new SshClient(logger, new Client());
35
+ await client.connect(withDefaults);
36
+ return client;
37
+ }
38
+ getForwardedServers() {
39
+ return this.forwardedServers;
40
+ }
41
+ getFullHostName() {
42
+ return `${this.config?.host}:${this.config?.port}`;
43
+ }
44
+ getUserName() {
45
+ return this.config?.username;
46
+ }
47
+ /**
48
+ * Connects to the SSH server using the specified configuration.
49
+ * @param config - The connection configuration object for the SSH client.
50
+ * @returns A promise that resolves when the connection is established or rejects on error.
51
+ */
52
+ async connect(config) {
53
+ this.config = config;
54
+ return await connect(this.client, config, () => {}, () => {});
55
+ }
56
+ /**
57
+ * Executes a command on the SSH server.
58
+ * @param command - The command to execute on the remote server.
59
+ * @returns A promise resolving with the command's stdout and stderr outputs.
60
+ */
61
+ async exec(command) {
62
+ return new Promise((resolve, reject) => {
63
+ this.client.exec(command, (err, stream) => {
64
+ if (err) return reject(/* @__PURE__ */ new Error(`ssh.exec: ${command}: ${err}`));
65
+ let stdout = "";
66
+ let stderr = "";
67
+ stream.on("close", (code) => {
68
+ if (code === 0) resolve({
69
+ stdout,
70
+ stderr
71
+ });
72
+ else reject(/* @__PURE__ */ new Error(`Command ${command} exited with code ${code}, stdout: ${stdout}, stderr: ${stderr}`));
73
+ }).on("data", (data) => {
74
+ stdout += data.toString();
75
+ }).stderr.on("data", (data) => {
76
+ stderr += data.toString();
77
+ });
78
+ });
79
+ });
80
+ }
81
+ /**
82
+ * Retrieves the supported authentication methods for a given host and port.
83
+ * @param host - The hostname or IP address of the server.
84
+ * @param port - The port number to connect to on the server.
85
+ * @returns 'publickey' | 'password'[] A promise resolving with a list of supported authentication methods.
86
+ */
87
+ static async getAuthTypes(host, port) {
88
+ return new Promise((resolve) => {
89
+ let stdout = "";
90
+ const conn = new Client();
91
+ conn.on("ready", () => {
92
+ conn.end();
93
+ const types = this.extractAuthMethods(stdout);
94
+ resolve(types.length === 0 ? ["publickey", "password"] : types);
95
+ });
96
+ conn.on("error", () => {
97
+ conn.end();
98
+ resolve(["publickey", "password"]);
99
+ });
100
+ conn.connect({
101
+ host,
102
+ port,
103
+ username: (/* @__PURE__ */ new Date()).getTime().toString(),
104
+ debug: (err) => {
105
+ stdout += `${err}\n`;
106
+ }
107
+ });
108
+ });
109
+ }
110
+ /**
111
+ * Extracts authentication methods from debug logs.
112
+ * @param log - The debug log output containing authentication information.
113
+ * @returns An array of extracted authentication methods.
114
+ */
115
+ static extractAuthMethods(log) {
116
+ const match = log.match(/Inbound: Received USERAUTH_FAILURE \((.+)\)/);
117
+ return match && match[1] ? match[1].split(",").map((method) => method.trim()) : [];
118
+ }
119
+ /**
120
+ * Sets up port forwarding between a remote port on the SSH server and a local port.
121
+ * A new connection is used for this operation instead of an existing one.
122
+ * @param ports - An object specifying the remote and local port configuration.
123
+ * @param config - Optional connection configuration for the SSH client.
124
+ * @returns { server: net.Server } A promise resolving with the created server instance.
125
+ */
126
+ async forwardPort(ports, config) {
127
+ const log = `ssh.forward:${ports.localPort}:${ports.remotePort}.id_${randomBytes(1).toString("hex")}`;
128
+ config = config ?? this.config;
129
+ const persistentClient = new RetryablePromise((p) => {
130
+ return new Promise((resolve, reject) => {
131
+ const client = new Client();
132
+ client.on("ready", () => {
133
+ this.logger.info(`${log}.client.ready`);
134
+ resolve(client);
135
+ });
136
+ client.on("error", (err) => {
137
+ this.logger.info(`${log}.client.error: ${err}`);
138
+ p.reset();
139
+ reject(err);
140
+ });
141
+ client.on("close", () => {
142
+ this.logger.info(`${log}.client.closed`);
143
+ p.reset();
144
+ });
145
+ client.connect(config);
146
+ });
147
+ });
148
+ await persistentClient.ensure();
149
+ return new Promise((resolve, reject) => {
150
+ const server = net.createServer({ pauseOnConnect: true }, async (localSocket) => {
151
+ const sockLog = `${log}.sock_${randomBytes(1).toString("hex")}`;
152
+ let conn;
153
+ try {
154
+ conn = await persistentClient.ensure();
155
+ } catch (e) {
156
+ this.logger.info(`${sockLog}.persistentClient.catch: ${e}`);
157
+ localSocket.end();
158
+ return;
159
+ }
160
+ conn.setNoDelay(true);
161
+ localSocket.setNoDelay(true);
162
+ let stream;
163
+ try {
164
+ stream = await forwardOut(this.logger, conn, "127.0.0.1", 0, "127.0.0.1", ports.remotePort);
165
+ } catch (e) {
166
+ this.logger.error(`${sockLog}.forwardOut.err: ${e}`);
167
+ localSocket.end();
168
+ return;
169
+ }
170
+ localSocket.pipe(stream);
171
+ stream.pipe(localSocket);
172
+ localSocket.resume();
173
+ stream.on("error", (err) => {
174
+ this.logger.error(`${sockLog}.stream.error: ${err}`);
175
+ localSocket.end();
176
+ stream.end();
177
+ });
178
+ stream.on("close", () => {
179
+ localSocket.end();
180
+ stream.end();
181
+ });
182
+ localSocket.on("close", () => {
183
+ this.logger.info(`${sockLog}.localSocket: closed`);
184
+ localSocket.end();
185
+ stream.end();
186
+ });
187
+ });
188
+ server.listen(ports.localPort, "127.0.0.1", () => {
189
+ this.logger.info(`${log}.server: started listening`);
190
+ this.forwardedServers.push(server);
191
+ resolve({ server });
192
+ });
193
+ server.on("error", (err) => {
194
+ server.close();
195
+ reject(/* @__PURE__ */ new Error(`${log}.server: error: ${JSON.stringify(err)}`));
196
+ });
197
+ server.on("close", () => {
198
+ this.logger.info(`${log}.server: closed ${JSON.stringify(ports)}`);
199
+ this.forwardedServers = this.forwardedServers.filter((s) => s !== server);
200
+ });
201
+ });
202
+ }
203
+ closeForwardedPorts() {
204
+ this.logger.info("[SSH] Closing all forwarded ports...");
205
+ this.forwardedServers.forEach((server) => {
206
+ const rawAddress = server.address();
207
+ if (rawAddress && typeof rawAddress !== "string") {
208
+ const address = rawAddress;
209
+ this.logger.info(`[SSH] Closing port forward for server ${address.address}:${address.port}`);
210
+ }
211
+ server.close();
212
+ });
213
+ this.forwardedServers = [];
214
+ }
215
+ /**
216
+ * Checks if a specified host is available by performing a DNS lookup.
217
+ * @param hostname - The hostname or IP address to check.
218
+ * @returns A promise resolving with `true` if the host is reachable, otherwise `false`.
219
+ */
220
+ static async checkHostAvailability(hostname) {
221
+ return new Promise((resolve) => {
222
+ dns.lookup(hostname, (err) => {
223
+ resolve(!err);
224
+ });
225
+ });
226
+ }
227
+ /**
228
+ * Determines whether a private key requires a passphrase for use.
229
+ * @param privateKey - The private key content to check.
230
+ * @returns A promise resolving with `true` if a passphrase is required, otherwise `false`.
231
+ */
232
+ static async isPassphraseRequiredForKey(privateKey) {
233
+ return new Promise((resolve, reject) => {
234
+ try {
235
+ if (ssh.utils.parseKey(privateKey) instanceof Error) resolve(true);
236
+ return resolve(false);
237
+ } catch (err) {
238
+ console.log("Error parsing privateKey");
239
+ reject(/* @__PURE__ */ new Error(`ssh.isPassphraseRequiredForKey: err ${err}`));
240
+ }
241
+ });
242
+ }
243
+ /**
244
+ * Uploads a local file to a remote server via SFTP.
245
+ * This function creates new SFTP connection
246
+ * @param localPath - The local file path.
247
+ * @param remotePath - The remote file path on the server.
248
+ * @returns A promise resolving with `true` if the file was successfully uploaded.
249
+ */
250
+ async uploadFile(localPath, remotePath) {
251
+ return await retry(async () => {
252
+ return await this.withSftp(async (sftp) => {
253
+ return new Promise((resolve, reject) => {
254
+ sftp.fastPut(localPath, remotePath, (err) => {
255
+ if (err) return reject(new SFTPUploadError(err, localPath, remotePath));
256
+ resolve(true);
257
+ });
258
+ });
259
+ });
260
+ }, Retry3TimesWithDelay, (e) => SFTPError.from(e)?.isGenericFailure ?? false);
261
+ }
262
+ async withSftp(callback) {
263
+ return new Promise((resolve, reject) => {
264
+ this.client.sftp((err, sftp) => {
265
+ if (err) return reject(new Error(`ssh.withSftp: sftp err: ${err}`, { cause: err }));
266
+ callback(sftp).then(resolve).catch((err) => {
267
+ reject(new Error(`ssh.withSftp.callback: err ${err}`, { cause: err }));
268
+ }).finally(() => {
269
+ sftp?.end();
270
+ });
271
+ });
272
+ });
273
+ }
274
+ async writeFileOnTheServer(remotePath, data, mode = 432) {
275
+ return this.withSftp(async (sftp) => {
276
+ return this.writeFile(sftp, remotePath, data, mode);
277
+ });
278
+ }
279
+ async getForderStructure(sftp, remotePath, data = {
280
+ files: [],
281
+ directories: []
282
+ }) {
283
+ return new Promise((resolve, reject) => {
284
+ sftp.readdir(remotePath, async (err, items) => {
285
+ if (err) return reject(err);
286
+ for (const item of items) {
287
+ const itemPath = `${remotePath}/${item.filename}`;
288
+ if (item.attrs.isDirectory()) {
289
+ data.directories.push(itemPath);
290
+ try {
291
+ await this.getForderStructure(sftp, itemPath, data);
292
+ } catch (error) {
293
+ return reject(error instanceof Error ? error : new Error(String(error)));
294
+ }
295
+ } else data.files.push(itemPath);
296
+ }
297
+ resolve(data);
298
+ });
299
+ });
300
+ }
301
+ rmdir(sftp, path) {
302
+ return new Promise((resolve, reject) => {
303
+ sftp.rmdir(path, (err) => err ? reject(err) : resolve(true));
304
+ });
305
+ }
306
+ unlink(sftp, path) {
307
+ return new Promise((resolve, reject) => {
308
+ sftp.unlink(path, (err) => err ? reject(err) : resolve(true));
309
+ });
310
+ }
311
+ async deleteFolder(path) {
312
+ return this.withSftp(async (sftp) => {
313
+ try {
314
+ const list = await this.getForderStructure(sftp, path);
315
+ this.logger.info(`ssh.deleteFolder list of files and directories`);
316
+ this.logger.info(`ssh.deleteFolder list of files: ${list.files}`);
317
+ this.logger.info(`ssh.deleteFolder list of directories: ${list.directories}`);
318
+ for (const filePath of list.files) {
319
+ this.logger.info(`ssh.deleteFolder unlink file ${filePath}`);
320
+ await this.unlink(sftp, filePath);
321
+ }
322
+ list.directories.sort((a, b) => b.length - a.length);
323
+ for (const directoryPath of list.directories) {
324
+ this.logger.info(`ssh.deleteFolder rmdir ${directoryPath}`);
325
+ await this.rmdir(sftp, directoryPath);
326
+ }
327
+ await this.rmdir(sftp, path);
328
+ return true;
329
+ } catch (e) {
330
+ this.logger.error(e);
331
+ const message = e instanceof Error ? e.message : "";
332
+ throw new Error(`ssh.deleteFolder: path: ${path}, message: ${message}`);
333
+ }
334
+ });
335
+ }
336
+ async readFile(remotePath) {
337
+ return this.withSftp(async (sftp) => {
338
+ return new Promise((resolve, reject) => {
339
+ sftp.readFile(remotePath, (err, buffer) => {
340
+ if (err) return reject(/* @__PURE__ */ new Error(`ssh.readFile: ${err}`));
341
+ resolve(buffer.toString());
342
+ });
343
+ });
344
+ });
345
+ }
346
+ async chmod(path, mode) {
347
+ return this.withSftp(async (sftp) => {
348
+ return new Promise((resolve, reject) => {
349
+ sftp.chmod(path, mode, (err) => {
350
+ if (err) return reject(/* @__PURE__ */ new Error(`ssh.chmod: ${err}, path: ${path}, mode: ${mode}`));
351
+ return resolve(void 0);
352
+ });
353
+ });
354
+ });
355
+ }
356
+ async checkFileExists(remotePath) {
357
+ return this.withSftp(async (sftp) => {
358
+ return new Promise((resolve, reject) => {
359
+ sftp.stat(remotePath, (err, stats) => {
360
+ if (err) {
361
+ if (err?.code === 2) return resolve(false);
362
+ return reject(/* @__PURE__ */ new Error(`ssh.checkFileExists: err ${err}`));
363
+ }
364
+ resolve(stats.isFile());
365
+ });
366
+ });
367
+ });
368
+ }
369
+ async checkPathExists(remotePath) {
370
+ return this.withSftp(async (sftp) => {
371
+ return new Promise((resolve, reject) => {
372
+ sftp.stat(remotePath, (err, stats) => {
373
+ if (err) {
374
+ if (err.code === 2) return resolve({
375
+ exists: false,
376
+ isFile: false,
377
+ isDirectory: false
378
+ });
379
+ return reject(/* @__PURE__ */ new Error(`ssh.checkPathExists: ${err}`));
380
+ }
381
+ resolve({
382
+ exists: true,
383
+ isFile: stats.isFile(),
384
+ isDirectory: stats.isDirectory()
385
+ });
386
+ });
387
+ });
388
+ });
389
+ }
390
+ async writeFile(sftp, remotePath, data, mode = 432) {
391
+ return new Promise((resolve, reject) => {
392
+ sftp.writeFile(remotePath, data, { mode }, (err) => {
393
+ if (err) return reject(/* @__PURE__ */ new Error(`ssh.writeFile: err ${err}, remotePath: ${remotePath}`));
394
+ resolve(true);
395
+ });
396
+ });
397
+ }
398
+ uploadFileUsingExistingSftp(sftp, localPath, remotePath, mode = 432) {
399
+ return new Promise((resolve, reject) => {
400
+ readFile(localPath).then(async (result) => {
401
+ return this.writeFile(sftp, remotePath, result, mode).then(() => {
402
+ resolve(void 0);
403
+ }).catch((err) => {
404
+ const msg = `uploadFileUsingExistingSftp: ${err}`;
405
+ this.logger.error(msg);
406
+ reject(new Error(msg));
407
+ });
408
+ });
409
+ });
410
+ }
411
+ async __uploadDirectory(sftp, localDir, remoteDir, mode = 432) {
412
+ return new Promise((resolve, reject) => {
413
+ fs.readdir(localDir, async (err, files) => {
414
+ if (err) return reject(/* @__PURE__ */ new Error(`ssh.__uploadDir: err ${err}, localDir: ${localDir}, remoteDir: ${remoteDir}`));
415
+ try {
416
+ await this.__createRemoteDirectory(sftp, remoteDir);
417
+ for (const file of files) {
418
+ const localPath = upath.join(localDir, file);
419
+ const remotePath = `${remoteDir}/${file}`;
420
+ if (fs.lstatSync(localPath).isDirectory()) await this.__uploadDirectory(sftp, localPath, remotePath, mode);
421
+ else await this.uploadFileUsingExistingSftp(sftp, localPath, remotePath, mode);
422
+ }
423
+ resolve();
424
+ } catch (err) {
425
+ const msg = `ssh.__uploadDir: catched err ${err}`;
426
+ this.logger.error(msg);
427
+ reject(new Error(msg));
428
+ }
429
+ });
430
+ });
431
+ }
432
+ /**
433
+ * Uploads a local directory and its contents (including subdirectories) to the remote server via SFTP.
434
+ * @param localDir - The path to the local directory to upload.
435
+ * @param remoteDir - The path to the remote directory on the server.
436
+ * @returns A promise that resolves when the directory and its contents are uploaded.
437
+ */
438
+ async uploadDirectory(localDir, remoteDir, mode = 432) {
439
+ return new Promise((resolve, reject) => {
440
+ this.withSftp(async (sftp) => {
441
+ try {
442
+ await this.__uploadDirectory(sftp, localDir, remoteDir, mode);
443
+ resolve();
444
+ } catch (e) {
445
+ reject(/* @__PURE__ */ new Error(`ssh.uploadDirectory: ${e}`));
446
+ }
447
+ });
448
+ });
449
+ }
450
+ /**
451
+ * Ensures that a remote directory and all its parent directories exist.
452
+ * @param sftp - The SFTP wrapper.
453
+ * @param remotePath - The path to the remote directory.
454
+ * @returns A promise that resolves when the directory is created.
455
+ */
456
+ __createRemoteDirectory(sftp, remotePath) {
457
+ return new Promise((resolve, reject) => {
458
+ const directories = remotePath.split("/");
459
+ let currentPath = "";
460
+ const createNext = (index) => {
461
+ if (index >= directories.length) return resolve();
462
+ currentPath += `${directories[index]}/`;
463
+ sftp.stat(currentPath, (err) => {
464
+ if (err) sftp.mkdir(currentPath, (err) => {
465
+ if (err) return reject(/* @__PURE__ */ new Error(`ssh.__createRemDir: err ${err}, remotePath: ${remotePath}`));
466
+ createNext(index + 1);
467
+ });
468
+ else createNext(index + 1);
469
+ });
470
+ };
471
+ createNext(0);
472
+ });
473
+ }
474
+ /**
475
+ * Ensures that a remote directory and all its parent directories exist.
476
+ * @param sftp - The SFTP wrapper.
477
+ * @param remotePath - The path to the remote directory.
478
+ * @returns A promise that resolves when the directory is created.
479
+ */
480
+ ensureRemoteDirCreated(remotePath, mode = 493) {
481
+ return this.withSftp(async (sftp) => {
482
+ const directories = remotePath.split("/");
483
+ let currentPath = "";
484
+ for (const directory of directories) {
485
+ currentPath += `${directory}/`;
486
+ try {
487
+ await new Promise((resolve, reject) => {
488
+ sftp.stat(currentPath, (err) => {
489
+ if (!err) return resolve();
490
+ sftp.mkdir(currentPath, { mode }, (err) => {
491
+ if (err) return reject(/* @__PURE__ */ new Error(`ssh.createRemoteDir: err ${err}, remotePath: ${remotePath}`));
492
+ resolve();
493
+ });
494
+ });
495
+ });
496
+ } catch (error) {
497
+ console.error(`Failed to create directory: ${currentPath}`, error);
498
+ throw error;
499
+ }
500
+ }
501
+ });
502
+ }
503
+ /**
504
+ * Downloads a file from the remote server to a local path via SFTP.
505
+ * @param remotePath - The remote file path on the server.
506
+ * @param localPath - The local file path to save the file.
507
+ * @returns A promise resolving with `true` if the file was successfully downloaded.
508
+ */
509
+ async downloadFile(remotePath, localPath) {
510
+ return this.withSftp(async (sftp) => {
511
+ return new Promise((resolve, reject) => {
512
+ sftp.fastGet(remotePath, localPath, (err) => {
513
+ if (err) return reject(/* @__PURE__ */ new Error(`ssh.downloadFile: err ${err}, remotePath: ${remotePath}, localPath: ${localPath}`));
514
+ resolve(true);
515
+ });
516
+ });
517
+ });
518
+ }
519
+ /**
520
+ * Closes the SSH client connection and forwarded ports.
521
+ */
522
+ close() {
523
+ this.closeForwardedPorts();
524
+ this.client.end();
525
+ }
14
526
  };
15
- class SshClient {
16
- logger;
17
- client;
18
- config;
19
- homeDir;
20
- forwardedServers = [];
21
- constructor(logger, client) {
22
- this.logger = logger;
23
- this.client = client;
24
- }
25
- /**
26
- * Initializes the SshClient and establishes a connection using the provided configuration.
27
- * @param config - The connection configuration object for the SSH client.
28
- * @returns A new instance of SshClient with an active connection.
29
- */
30
- static async init(logger, config) {
31
- const withDefaults = {
32
- ...defaultConfig,
33
- ...config,
34
- };
35
- const client = new SshClient(logger, new Client());
36
- await client.connect(withDefaults);
37
- return client;
38
- }
39
- getForwardedServers() {
40
- return this.forwardedServers;
41
- }
42
- getFullHostName() {
43
- return `${this.config?.host}:${this.config?.port}`;
44
- }
45
- getUserName() {
46
- return this.config?.username;
47
- }
48
- /**
49
- * Connects to the SSH server using the specified configuration.
50
- * @param config - The connection configuration object for the SSH client.
51
- * @returns A promise that resolves when the connection is established or rejects on error.
52
- */
53
- async connect(config) {
54
- this.config = config;
55
- return await connect(this.client, config);
56
- }
57
- /**
58
- * Executes a command on the SSH server.
59
- * @param command - The command to execute on the remote server.
60
- * @returns A promise resolving with the command's stdout and stderr outputs.
61
- */
62
- async exec(command) {
63
- return new Promise((resolve, reject) => {
64
- this.client.exec(command, (err, stream) => {
65
- if (err) {
66
- return reject(new Error(`ssh.exec: ${command}: ${err}`));
67
- }
68
- let stdout = "";
69
- let stderr = "";
70
- stream
71
- .on("close", (code) => {
72
- if (code === 0) {
73
- resolve({ stdout, stderr });
74
- }
75
- else {
76
- reject(new Error(`Command ${command} exited with code ${code}, stdout: ${stdout}, stderr: ${stderr}`));
77
- }
78
- })
79
- .on("data", (data) => {
80
- stdout += data.toString();
81
- })
82
- .stderr.on("data", (data) => {
83
- stderr += data.toString();
84
- });
85
- });
86
- });
87
- }
88
- /**
89
- * Retrieves the supported authentication methods for a given host and port.
90
- * @param host - The hostname or IP address of the server.
91
- * @param port - The port number to connect to on the server.
92
- * @returns 'publickey' | 'password'[] A promise resolving with a list of supported authentication methods.
93
- */
94
- static async getAuthTypes(host, port) {
95
- return new Promise((resolve) => {
96
- let stdout = "";
97
- const conn = new Client();
98
- conn.on("ready", () => {
99
- conn.end();
100
- const types = this.extractAuthMethods(stdout);
101
- resolve(types.length === 0 ? ["publickey", "password"] : types);
102
- });
103
- conn.on("error", () => {
104
- conn.end();
105
- resolve(["publickey", "password"]);
106
- });
107
- conn.connect({
108
- host,
109
- port,
110
- username: new Date().getTime().toString(),
111
- debug: (err) => {
112
- stdout += `${err}\n`;
113
- },
114
- });
115
- });
116
- }
117
- /**
118
- * Extracts authentication methods from debug logs.
119
- * @param log - The debug log output containing authentication information.
120
- * @returns An array of extracted authentication methods.
121
- */
122
- static extractAuthMethods(log) {
123
- const match = log.match(/Inbound: Received USERAUTH_FAILURE \((.+)\)/);
124
- return match && match[1] ? match[1].split(",").map((method) => method.trim()) : [];
125
- }
126
- /**
127
- * Sets up port forwarding between a remote port on the SSH server and a local port.
128
- * A new connection is used for this operation instead of an existing one.
129
- * @param ports - An object specifying the remote and local port configuration.
130
- * @param config - Optional connection configuration for the SSH client.
131
- * @returns { server: net.Server } A promise resolving with the created server instance.
132
- */
133
- async forwardPort(ports, config) {
134
- const log = `ssh.forward:${ports.localPort}:${ports.remotePort}.id_${randomBytes(1).toString("hex")}`;
135
- config = config ?? this.config;
136
- // we make this thing persistent so that if the connection
137
- // drops (it happened in the past because of lots of errors and forwardOut opened channels),
138
- // we'll recreate it here.
139
- const persistentClient = new RetryablePromise((p) => {
140
- return new Promise((resolve, reject) => {
141
- const client = new Client();
142
- client.on("ready", () => {
143
- this.logger.info(`${log}.client.ready`);
144
- resolve(client);
145
- });
146
- client.on("error", (err) => {
147
- this.logger.info(`${log}.client.error: ${err}`);
148
- p.reset();
149
- reject(err);
150
- });
151
- client.on("close", () => {
152
- this.logger.info(`${log}.client.closed`);
153
- p.reset();
154
- });
155
- client.connect(config);
156
- });
157
- });
158
- await persistentClient.ensure(); // warm up a connection
159
- return new Promise((resolve, reject) => {
160
- const server = net.createServer({ pauseOnConnect: true }, async (localSocket) => {
161
- const sockLog = `${log}.sock_${randomBytes(1).toString("hex")}`;
162
- // this.logger.info(`${sockLog}.localSocket: start connection`);
163
- let conn;
164
- try {
165
- conn = await persistentClient.ensure();
166
- }
167
- catch (e) {
168
- this.logger.info(`${sockLog}.persistentClient.catch: ${e}`);
169
- localSocket.end();
170
- return;
171
- }
172
- // Remove TCP buffering.
173
- // Although it means less throughput (bad), it also less latency (good).
174
- // It could help when we have
175
- // small messages like in our grpc transactions.
176
- // And it also could help when we have tcp forwarding to not buffer messages in the middle.
177
- conn.setNoDelay(true);
178
- localSocket.setNoDelay(true);
179
- let stream;
180
- try {
181
- stream = await forwardOut(this.logger, conn, "127.0.0.1", 0, "127.0.0.1", ports.remotePort);
182
- }
183
- catch (e) {
184
- this.logger.error(`${sockLog}.forwardOut.err: ${e}`);
185
- localSocket.end();
186
- return;
187
- }
188
- localSocket.pipe(stream);
189
- stream.pipe(localSocket);
190
- localSocket.resume();
191
- // this.logger.info(`${sockLog}.forwardOut: connected`);
192
- stream.on("error", (err) => {
193
- this.logger.error(`${sockLog}.stream.error: ${err}`);
194
- localSocket.end();
195
- stream.end();
196
- });
197
- stream.on("close", () => {
198
- // this.logger.info(`${sockLog}.stream.close: closed`);
199
- localSocket.end();
200
- stream.end();
201
- });
202
- localSocket.on("close", () => {
203
- this.logger.info(`${sockLog}.localSocket: closed`);
204
- localSocket.end();
205
- stream.end();
206
- });
207
- });
208
- server.listen(ports.localPort, "127.0.0.1", () => {
209
- this.logger.info(`${log}.server: started listening`);
210
- this.forwardedServers.push(server);
211
- resolve({ server });
212
- });
213
- server.on("error", (err) => {
214
- server.close();
215
- reject(new Error(`${log}.server: error: ${JSON.stringify(err)}`));
216
- });
217
- server.on("close", () => {
218
- this.logger.info(`${log}.server: closed ${JSON.stringify(ports)}`);
219
- this.forwardedServers = this.forwardedServers.filter((s) => s !== server);
220
- });
221
- });
222
- }
223
- closeForwardedPorts() {
224
- this.logger.info("[SSH] Closing all forwarded ports...");
225
- this.forwardedServers.forEach((server) => {
226
- const rawAddress = server.address();
227
- if (rawAddress && typeof rawAddress !== "string") {
228
- const address = rawAddress;
229
- this.logger.info(`[SSH] Closing port forward for server ${address.address}:${address.port}`);
230
- }
231
- server.close();
232
- });
233
- this.forwardedServers = [];
234
- }
235
- /**
236
- * Checks if a specified host is available by performing a DNS lookup.
237
- * @param hostname - The hostname or IP address to check.
238
- * @returns A promise resolving with `true` if the host is reachable, otherwise `false`.
239
- */
240
- static async checkHostAvailability(hostname) {
241
- return new Promise((resolve) => {
242
- dns.lookup(hostname, (err) => {
243
- resolve(!err);
244
- });
245
- });
246
- }
247
- /**
248
- * Determines whether a private key requires a passphrase for use.
249
- * @param privateKey - The private key content to check.
250
- * @returns A promise resolving with `true` if a passphrase is required, otherwise `false`.
251
- */
252
- static async isPassphraseRequiredForKey(privateKey) {
253
- return new Promise((resolve, reject) => {
254
- try {
255
- const keyOrError = ssh.utils.parseKey(privateKey);
256
- if (keyOrError instanceof Error) {
257
- resolve(true);
258
- }
259
- return resolve(false);
260
- }
261
- catch (err) {
262
- console.log("Error parsing privateKey");
263
- reject(new Error(`ssh.isPassphraseRequiredForKey: err ${err}`));
264
- }
265
- });
266
- }
267
- /**
268
- * Uploads a local file to a remote server via SFTP.
269
- * This function creates new SFTP connection
270
- * @param localPath - The local file path.
271
- * @param remotePath - The remote file path on the server.
272
- * @returns A promise resolving with `true` if the file was successfully uploaded.
273
- */
274
- async uploadFile(localPath, remotePath) {
275
- return await retry(async () => {
276
- return await this.withSftp(async (sftp) => {
277
- return new Promise((resolve, reject) => {
278
- sftp.fastPut(localPath, remotePath, (err) => {
279
- if (err) {
280
- const newErr = new SFTPUploadError(err, localPath, remotePath);
281
- return reject(newErr);
282
- }
283
- resolve(true);
284
- });
285
- });
286
- });
287
- }, Retry3TimesWithDelay, (e) => SFTPError.from(e)?.isGenericFailure ?? false);
288
- }
289
- async withSftp(callback) {
290
- return new Promise((resolve, reject) => {
291
- this.client.sftp((err, sftp) => {
292
- if (err) {
293
- return reject(new Error(`ssh.withSftp: sftp err: ${err}`, { cause: err }));
294
- }
295
- callback(sftp)
296
- .then(resolve)
297
- .catch((err) => {
298
- reject(new Error(`ssh.withSftp.callback: err ${err}`, { cause: err }));
299
- })
300
- .finally(() => {
301
- sftp?.end();
302
- });
303
- });
304
- });
305
- }
306
- async writeFileOnTheServer(remotePath, data, mode = 0o660) {
307
- return this.withSftp(async (sftp) => {
308
- return this.writeFile(sftp, remotePath, data, mode);
309
- });
310
- }
311
- async getForderStructure(sftp, remotePath, data = { files: [], directories: [] }) {
312
- return new Promise((resolve, reject) => {
313
- sftp.readdir(remotePath, async (err, items) => {
314
- if (err) {
315
- return reject(err);
316
- }
317
- for (const item of items) {
318
- const itemPath = `${remotePath}/${item.filename}`;
319
- if (item.attrs.isDirectory()) {
320
- data.directories.push(itemPath);
321
- try {
322
- await this.getForderStructure(sftp, itemPath, data);
323
- }
324
- catch (error) {
325
- return reject(error instanceof Error ? error : new Error(String(error)));
326
- }
327
- }
328
- else {
329
- data.files.push(itemPath);
330
- }
331
- }
332
- resolve(data);
333
- });
334
- });
335
- }
336
- rmdir(sftp, path) {
337
- return new Promise((resolve, reject) => {
338
- sftp.rmdir(path, (err) => (err ? reject(err) : resolve(true)));
339
- });
340
- }
341
- unlink(sftp, path) {
342
- return new Promise((resolve, reject) => {
343
- sftp.unlink(path, (err) => (err ? reject(err) : resolve(true)));
344
- });
345
- }
346
- async deleteFolder(path) {
347
- return this.withSftp(async (sftp) => {
348
- try {
349
- const list = await this.getForderStructure(sftp, path);
350
- this.logger.info(`ssh.deleteFolder list of files and directories`);
351
- this.logger.info(`ssh.deleteFolder list of files: ${list.files}`);
352
- this.logger.info(`ssh.deleteFolder list of directories: ${list.directories}`);
353
- for (const filePath of list.files) {
354
- this.logger.info(`ssh.deleteFolder unlink file ${filePath}`);
355
- await this.unlink(sftp, filePath);
356
- }
357
- list.directories.sort((a, b) => b.length - a.length);
358
- for (const directoryPath of list.directories) {
359
- this.logger.info(`ssh.deleteFolder rmdir ${directoryPath}`);
360
- await this.rmdir(sftp, directoryPath);
361
- }
362
- await this.rmdir(sftp, path);
363
- return true;
364
- }
365
- catch (e) {
366
- this.logger.error(e);
367
- const message = e instanceof Error ? e.message : "";
368
- throw new Error(`ssh.deleteFolder: path: ${path}, message: ${message}`);
369
- }
370
- });
371
- }
372
- async readFile(remotePath) {
373
- return this.withSftp(async (sftp) => {
374
- return new Promise((resolve, reject) => {
375
- sftp.readFile(remotePath, (err, buffer) => {
376
- if (err) {
377
- return reject(new Error(`ssh.readFile: ${err}`));
378
- }
379
- resolve(buffer.toString());
380
- });
381
- });
382
- });
383
- }
384
- async chmod(path, mode) {
385
- return this.withSftp(async (sftp) => {
386
- return new Promise((resolve, reject) => {
387
- sftp.chmod(path, mode, (err) => {
388
- if (err) {
389
- return reject(new Error(`ssh.chmod: ${err}, path: ${path}, mode: ${mode}`));
390
- }
391
- return resolve(undefined);
392
- });
393
- });
394
- });
395
- }
396
- async checkFileExists(remotePath) {
397
- return this.withSftp(async (sftp) => {
398
- return new Promise((resolve, reject) => {
399
- sftp.stat(remotePath, (err, stats) => {
400
- if (err) {
401
- if (err?.code === 2) {
402
- return resolve(false);
403
- }
404
- return reject(new Error(`ssh.checkFileExists: err ${err}`));
405
- }
406
- resolve(stats.isFile());
407
- });
408
- });
409
- });
410
- }
411
- async checkPathExists(remotePath) {
412
- return this.withSftp(async (sftp) => {
413
- return new Promise((resolve, reject) => {
414
- sftp.stat(remotePath, (err, stats) => {
415
- if (err) {
416
- if (err.code === 2) {
417
- return resolve({ exists: false, isFile: false, isDirectory: false });
418
- }
419
- return reject(new Error(`ssh.checkPathExists: ${err}`));
420
- }
421
- resolve({
422
- exists: true,
423
- isFile: stats.isFile(),
424
- isDirectory: stats.isDirectory(),
425
- });
426
- });
427
- });
428
- });
429
- }
430
- async writeFile(sftp, remotePath, data, mode = 0o660) {
431
- return new Promise((resolve, reject) => {
432
- sftp.writeFile(remotePath, data, { mode }, (err) => {
433
- if (err) {
434
- return reject(new Error(`ssh.writeFile: err ${err}, remotePath: ${remotePath}`));
435
- }
436
- resolve(true);
437
- });
438
- });
439
- }
440
- uploadFileUsingExistingSftp(sftp, localPath, remotePath, mode = 0o660) {
441
- return new Promise((resolve, reject) => {
442
- void readFile(localPath).then(async (result) => {
443
- return this.writeFile(sftp, remotePath, result, mode)
444
- .then(() => {
445
- resolve(undefined);
446
- })
447
- .catch((err) => {
448
- const msg = `uploadFileUsingExistingSftp: ${err}`;
449
- this.logger.error(msg);
450
- reject(new Error(msg));
451
- });
452
- });
453
- });
454
- }
455
- async __uploadDirectory(sftp, localDir, remoteDir, mode = 0o660) {
456
- return new Promise((resolve, reject) => {
457
- fs.readdir(localDir, async (err, files) => {
458
- if (err) {
459
- return reject(new Error(`ssh.__uploadDir: err ${err}, localDir: ${localDir}, remoteDir: ${remoteDir}`));
460
- }
461
- try {
462
- await this.__createRemoteDirectory(sftp, remoteDir);
463
- for (const file of files) {
464
- const localPath = upath.join(localDir, file);
465
- const remotePath = `${remoteDir}/${file}`;
466
- if (fs.lstatSync(localPath).isDirectory()) {
467
- await this.__uploadDirectory(sftp, localPath, remotePath, mode);
468
- }
469
- else {
470
- await this.uploadFileUsingExistingSftp(sftp, localPath, remotePath, mode);
471
- }
472
- }
473
- resolve();
474
- }
475
- catch (err) {
476
- const msg = `ssh.__uploadDir: catched err ${err}`;
477
- this.logger.error(msg);
478
- reject(new Error(msg));
479
- }
480
- });
481
- });
482
- }
483
- /**
484
- * Uploads a local directory and its contents (including subdirectories) to the remote server via SFTP.
485
- * @param localDir - The path to the local directory to upload.
486
- * @param remoteDir - The path to the remote directory on the server.
487
- * @returns A promise that resolves when the directory and its contents are uploaded.
488
- */
489
- async uploadDirectory(localDir, remoteDir, mode = 0o660) {
490
- return new Promise((resolve, reject) => {
491
- void this.withSftp(async (sftp) => {
492
- try {
493
- await this.__uploadDirectory(sftp, localDir, remoteDir, mode);
494
- resolve();
495
- }
496
- catch (e) {
497
- reject(new Error(`ssh.uploadDirectory: ${e}`));
498
- }
499
- });
500
- });
501
- }
502
- /**
503
- * Ensures that a remote directory and all its parent directories exist.
504
- * @param sftp - The SFTP wrapper.
505
- * @param remotePath - The path to the remote directory.
506
- * @returns A promise that resolves when the directory is created.
507
- */
508
- __createRemoteDirectory(sftp, remotePath) {
509
- return new Promise((resolve, reject) => {
510
- const directories = remotePath.split("/");
511
- let currentPath = "";
512
- const createNext = (index) => {
513
- if (index >= directories.length) {
514
- return resolve();
515
- }
516
- currentPath += `${directories[index]}/`;
517
- sftp.stat(currentPath, (err) => {
518
- if (err) {
519
- sftp.mkdir(currentPath, (err) => {
520
- if (err) {
521
- return reject(new Error(`ssh.__createRemDir: err ${err}, remotePath: ${remotePath}`));
522
- }
523
- createNext(index + 1);
524
- });
525
- }
526
- else {
527
- createNext(index + 1);
528
- }
529
- });
530
- };
531
- createNext(0);
532
- });
533
- }
534
- /**
535
- * Ensures that a remote directory and all its parent directories exist.
536
- * @param sftp - The SFTP wrapper.
537
- * @param remotePath - The path to the remote directory.
538
- * @returns A promise that resolves when the directory is created.
539
- */
540
- ensureRemoteDirCreated(remotePath, mode = 0o755) {
541
- return this.withSftp(async (sftp) => {
542
- const directories = remotePath.split("/");
543
- let currentPath = "";
544
- for (const directory of directories) {
545
- currentPath += `${directory}/`;
546
- try {
547
- await new Promise((resolve, reject) => {
548
- sftp.stat(currentPath, (err) => {
549
- if (!err)
550
- return resolve();
551
- sftp.mkdir(currentPath, { mode }, (err) => {
552
- if (err) {
553
- return reject(new Error(`ssh.createRemoteDir: err ${err}, remotePath: ${remotePath}`));
554
- }
555
- resolve();
556
- });
557
- });
558
- });
559
- }
560
- catch (error) {
561
- console.error(`Failed to create directory: ${currentPath}`, error);
562
- throw error;
563
- }
564
- }
565
- });
566
- }
567
- /**
568
- * Downloads a file from the remote server to a local path via SFTP.
569
- * @param remotePath - The remote file path on the server.
570
- * @param localPath - The local file path to save the file.
571
- * @returns A promise resolving with `true` if the file was successfully downloaded.
572
- */
573
- async downloadFile(remotePath, localPath) {
574
- return this.withSftp(async (sftp) => {
575
- return new Promise((resolve, reject) => {
576
- sftp.fastGet(remotePath, localPath, (err) => {
577
- if (err) {
578
- return reject(new Error(`ssh.downloadFile: err ${err}, remotePath: ${remotePath}, localPath: ${localPath}`));
579
- }
580
- resolve(true);
581
- });
582
- });
583
- });
584
- }
585
- /**
586
- * Closes the SSH client connection and forwarded ports.
587
- */
588
- close() {
589
- this.closeForwardedPorts();
590
- this.client.end();
591
- }
592
- }
593
527
  async function connect(client, config, onError, onClose) {
594
- return new Promise((resolve, reject) => {
595
- client.on("ready", () => {
596
- resolve(client);
597
- });
598
- client.on("error", (err) => {
599
- reject(new Error(`ssh.connect: ${err}`));
600
- });
601
- client.on("close", () => {
602
- });
603
- client.connect(config);
604
- // Remove TCP buffering.
605
- // Although it means less throughput (bad), it also means less latency (good).
606
- // It could help when we have
607
- // small messages like in our grpc transactions.
608
- // And it also could help when we have tcp forwarding to not buffer messages in the middle.
609
- client.setNoDelay(true);
610
- });
528
+ return new Promise((resolve, reject) => {
529
+ client.on("ready", () => {
530
+ resolve(client);
531
+ });
532
+ client.on("error", (err) => {
533
+ onError(err);
534
+ reject(/* @__PURE__ */ new Error(`ssh.connect: ${err}`));
535
+ });
536
+ client.on("close", () => {
537
+ onClose();
538
+ });
539
+ client.connect(config);
540
+ client.setNoDelay(true);
541
+ });
611
542
  }
612
543
  async function forwardOut(logger, conn, localHost, localPort, remoteHost, remotePort) {
613
- return new Promise((resolve, reject) => {
614
- conn.forwardOut(localHost, localPort, remoteHost, remotePort, (err, stream) => {
615
- if (err) {
616
- logger.error(`forwardOut.error: ${err}`);
617
- return reject(err);
618
- }
619
- return resolve(stream);
620
- });
621
- });
544
+ return new Promise((resolve, reject) => {
545
+ conn.forwardOut(localHost, localPort, remoteHost, remotePort, (err, stream) => {
546
+ if (err) {
547
+ logger.error(`forwardOut.error: ${err}`);
548
+ return reject(err);
549
+ }
550
+ return resolve(stream);
551
+ });
552
+ });
622
553
  }
623
554
 
555
+ //#endregion
624
556
  export { SshClient };
625
- //# sourceMappingURL=ssh.js.map
557
+ //# sourceMappingURL=ssh.js.map