@ebowwa/terminal 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/pool.js DELETED
@@ -1,840 +0,0 @@
1
- "use strict";
2
- /**
3
- * SSH Connection Pool Manager
4
- * Maintains persistent SSH connections for reuse across commands
5
- */
6
- var __assign = (this && this.__assign) || function () {
7
- __assign = Object.assign || function(t) {
8
- for (var s, i = 1, n = arguments.length; i < n; i++) {
9
- s = arguments[i];
10
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
11
- t[p] = s[p];
12
- }
13
- return t;
14
- };
15
- return __assign.apply(this, arguments);
16
- };
17
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
18
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
19
- return new (P || (P = Promise))(function (resolve, reject) {
20
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
21
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
22
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
23
- step((generator = generator.apply(thisArg, _arguments || [])).next());
24
- });
25
- };
26
- var __generator = (this && this.__generator) || function (thisArg, body) {
27
- var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
28
- return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
29
- function verb(n) { return function (v) { return step([n, v]); }; }
30
- function step(op) {
31
- if (f) throw new TypeError("Generator is already executing.");
32
- while (g && (g = 0, op[0] && (_ = 0)), _) try {
33
- if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
34
- if (y = 0, t) op = [op[0] & 2, t.value];
35
- switch (op[0]) {
36
- case 0: case 1: t = op; break;
37
- case 4: _.label++; return { value: op[1], done: false };
38
- case 5: _.label++; y = op[1]; op = [0]; continue;
39
- case 7: op = _.ops.pop(); _.trys.pop(); continue;
40
- default:
41
- if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
42
- if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
43
- if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
44
- if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
45
- if (t[2]) _.ops.pop();
46
- _.trys.pop(); continue;
47
- }
48
- op = body.call(thisArg, _);
49
- } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
50
- if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
51
- }
52
- };
53
- Object.defineProperty(exports, "__esModule", { value: true });
54
- exports.SSHConnectionPool = void 0;
55
- exports.getSSHPool = getSSHPool;
56
- exports.closeGlobalSSHPool = closeGlobalSSHPool;
57
- exports.getActiveSSHConnections = getActiveSSHConnections;
58
- var node_ssh_1 = require("node-ssh");
59
- var node_path_1 = require("node:path");
60
- var DEFAULT_CONFIG = {
61
- maxConnections: 50,
62
- maxConnectionsPerHost: 5, // Allow up to 5 connections per host for parallel ops
63
- idleTimeout: 5 * 60 * 1000, // 5 minutes
64
- connectionTimeout: 10 * 1000, // 10 seconds
65
- keepAliveInterval: 30 * 1000, // 30 seconds
66
- };
67
- /**
68
- * SSH Connection Pool Class
69
- */
70
- var SSHConnectionPool = /** @class */ (function () {
71
- function SSHConnectionPool(config) {
72
- if (config === void 0) { config = {}; }
73
- this.connections = new Map();
74
- this.cleanupInterval = null;
75
- this.nextId = 0; // Counter for generating unique connection IDs
76
- this.config = __assign(__assign({}, DEFAULT_CONFIG), config);
77
- this.startCleanup();
78
- }
79
- /**
80
- * Generate a unique key for the connection (host-based)
81
- */
82
- SSHConnectionPool.prototype.getKey = function (host, port, user) {
83
- return "".concat(user, "@").concat(host, ":").concat(port);
84
- };
85
- /**
86
- * Get all connections for a given host
87
- */
88
- SSHConnectionPool.prototype.getConnectionsList = function (key) {
89
- var list = this.connections.get(key);
90
- if (!list) {
91
- list = [];
92
- this.connections.set(key, list);
93
- }
94
- return list;
95
- };
96
- /**
97
- * Get or create a connection (returns least recently used connection)
98
- */
99
- SSHConnectionPool.prototype.getConnection = function (options) {
100
- return __awaiter(this, void 0, void 0, function () {
101
- var connections;
102
- return __generator(this, function (_a) {
103
- switch (_a.label) {
104
- case 0: return [4 /*yield*/, this.getConnections(options, 1)];
105
- case 1:
106
- connections = _a.sent();
107
- return [2 /*return*/, connections[0]];
108
- }
109
- });
110
- });
111
- };
112
- /**
113
- * Get or create a connection using password authentication
114
- */
115
- SSHConnectionPool.prototype.getConnectionWithPassword = function (host_1, user_1, password_1) {
116
- return __awaiter(this, arguments, void 0, function (host, user, password, port) {
117
- if (port === void 0) { port = 22; }
118
- return __generator(this, function (_a) {
119
- return [2 /*return*/, this.getConnection({ host: host, user: user, password: password, port: port })];
120
- });
121
- });
122
- };
123
- /**
124
- * Get or create multiple connections for parallel execution
125
- * @param options - SSH connection options
126
- * @param count - Number of connections to retrieve
127
- * @returns Array of SSH connections
128
- */
129
- SSHConnectionPool.prototype.getConnections = function (options, count) {
130
- return __awaiter(this, void 0, void 0, function () {
131
- var host, _a, user, _b, port, keyPath, password, key, list, now, result, i, existing, _c, _d, _loop_1, _i, result_1, conn, currentHostCount, ssh;
132
- return __generator(this, function (_e) {
133
- switch (_e.label) {
134
- case 0:
135
- host = options.host, _a = options.user, user = _a === void 0 ? 'root' : _a, _b = options.port, port = _b === void 0 ? 22 : _b, keyPath = options.keyPath, password = options.password;
136
- key = this.getKey(host, port, user);
137
- list = this.getConnectionsList(key);
138
- now = Date.now();
139
- result = [];
140
- i = list.length - 1;
141
- _e.label = 1;
142
- case 1:
143
- if (!(i >= 0 && result.length < count)) return [3 /*break*/, 10];
144
- existing = list[i];
145
- // If used recently (within 30 seconds), reuse without verification
146
- if (now - existing.lastUsed < 30000) {
147
- existing.lastUsed = now;
148
- result.push(existing.ssh);
149
- list.splice(i, 1);
150
- return [3 /*break*/, 9];
151
- }
152
- _e.label = 2;
153
- case 2:
154
- _e.trys.push([2, 4, , 9]);
155
- return [4 /*yield*/, existing.ssh.exec('echo', ['ok'])];
156
- case 3:
157
- _e.sent();
158
- existing.lastUsed = now;
159
- result.push(existing.ssh);
160
- list.splice(i, 1);
161
- return [3 /*break*/, 9];
162
- case 4:
163
- _c = _e.sent();
164
- // Connection is dead, remove and dispose
165
- list.splice(i, 1);
166
- _e.label = 5;
167
- case 5:
168
- _e.trys.push([5, 7, , 8]);
169
- return [4 /*yield*/, existing.ssh.dispose()];
170
- case 6:
171
- _e.sent();
172
- return [3 /*break*/, 8];
173
- case 7:
174
- _d = _e.sent();
175
- return [3 /*break*/, 8];
176
- case 8: return [3 /*break*/, 9];
177
- case 9:
178
- i--;
179
- return [3 /*break*/, 1];
180
- case 10:
181
- _loop_1 = function (conn) {
182
- var pooled = list.find(function (c) { return c.ssh === conn; });
183
- if (pooled) {
184
- // Move to end of list (most recently used)
185
- list.splice(list.indexOf(pooled), 1);
186
- list.push(pooled);
187
- }
188
- };
189
- // Put back the connections we're reusing at the end (most recently used)
190
- for (_i = 0, result_1 = result; _i < result_1.length; _i++) {
191
- conn = result_1[_i];
192
- _loop_1(conn);
193
- }
194
- _e.label = 11;
195
- case 11:
196
- if (!(result.length < count)) return [3 /*break*/, 15];
197
- if (!(this.getTotalConnectionCount() >= this.config.maxConnections)) return [3 /*break*/, 13];
198
- return [4 /*yield*/, this.evictOldest()];
199
- case 12:
200
- _e.sent();
201
- _e.label = 13;
202
- case 13:
203
- currentHostCount = list.length + result.length;
204
- if (currentHostCount >= this.config.maxConnectionsPerHost) {
205
- // Can't create more connections for this host
206
- return [3 /*break*/, 15];
207
- }
208
- return [4 /*yield*/, this.createConnection(host, port, user, keyPath, password)
209
- // Add to result and pool
210
- ];
211
- case 14:
212
- ssh = _e.sent();
213
- // Add to result and pool
214
- result.push(ssh);
215
- list.push({
216
- ssh: ssh,
217
- lastUsed: now,
218
- host: host,
219
- port: port,
220
- user: user,
221
- id: "".concat(key, "-").concat(this.nextId++),
222
- });
223
- return [3 /*break*/, 11];
224
- case 15: return [2 /*return*/, result];
225
- }
226
- });
227
- });
228
- };
229
- /**
230
- * Create a new SSH connection
231
- * Tries key-based auth first, then password auth, then SSH agent
232
- */
233
- SSHConnectionPool.prototype.createConnection = function (host, port, user, keyPath, password) {
234
- return __awaiter(this, void 0, void 0, function () {
235
- var ssh, baseConfig, authMethods, resolvedKeyPath, projectRoot, keyName, errors, _loop_2, this_1, _i, authMethods_1, method, state_1;
236
- var _this = this;
237
- return __generator(this, function (_a) {
238
- switch (_a.label) {
239
- case 0:
240
- ssh = new node_ssh_1.NodeSSH();
241
- baseConfig = {
242
- host: host,
243
- port: port,
244
- username: user,
245
- readyTimeout: this.config.connectionTimeout,
246
- keepaliveInterval: this.config.keepAliveInterval,
247
- };
248
- authMethods = [];
249
- // 1. Key-based authentication (if keyPath provided)
250
- if (keyPath) {
251
- resolvedKeyPath = keyPath;
252
- if (!node_path_1.default.isAbsolute(keyPath)) {
253
- projectRoot = node_path_1.default.resolve(import.meta.dir, '../../com.hetzner.codespaces');
254
- if (keyPath.includes('.ssh-keys')) {
255
- keyName = node_path_1.default.basename(keyPath);
256
- resolvedKeyPath = node_path_1.default.join(projectRoot, '.ssh-keys', keyName);
257
- }
258
- else {
259
- resolvedKeyPath = node_path_1.default.resolve(keyPath);
260
- }
261
- }
262
- authMethods.push({
263
- name: 'key',
264
- config: __assign(__assign({}, baseConfig), { privateKeyPath: resolvedKeyPath }),
265
- });
266
- }
267
- // 2. Password authentication (if password provided)
268
- if (password) {
269
- authMethods.push({
270
- name: 'password',
271
- config: __assign(__assign({}, baseConfig), { password: password }),
272
- });
273
- }
274
- // 3. SSH agent (fallback if available)
275
- if (process.env.SSH_AUTH_SOCK) {
276
- authMethods.push({
277
- name: 'agent',
278
- config: __assign(__assign({}, baseConfig), { agent: process.env.SSH_AUTH_SOCK }),
279
- });
280
- }
281
- // If no auth methods available, try with empty config (will fail)
282
- if (authMethods.length === 0) {
283
- authMethods.push({
284
- name: 'default',
285
- config: baseConfig,
286
- });
287
- }
288
- errors = [];
289
- _loop_2 = function (method) {
290
- var underlyingClient, connectionKey_1, error_1, errMsg;
291
- return __generator(this, function (_b) {
292
- switch (_b.label) {
293
- case 0:
294
- _b.trys.push([0, 2, , 3]);
295
- return [4 /*yield*/, ssh.connect(method.config)
296
- // Attach error handler to underlying ssh2 client to catch keepalive and other errors
297
- // The NodeSSH instance exposes the underlying ssh2 Client via the 'ssh' property
298
- ];
299
- case 1:
300
- _b.sent();
301
- // Attach error handler to underlying ssh2 client to catch keepalive and other errors
302
- // The NodeSSH instance exposes the underlying ssh2 Client via the 'ssh' property
303
- if (ssh.ssh) {
304
- underlyingClient = ssh.ssh;
305
- connectionKey_1 = this_1.getKey(host, port, user);
306
- underlyingClient.on('error', function (err) {
307
- // Handle keepalive timeouts gracefully - these are network issues, not fatal errors
308
- var isKeepalive = err.message.includes('Keepalive') || err.level === 'client-timeout';
309
- if (isKeepalive) {
310
- console.warn("[SSH Pool] Keepalive timeout for ".concat(user, "@").concat(host, ":").concat(port, " - removing stale connection"));
311
- }
312
- else {
313
- console.error("[SSH Pool] Connection error for ".concat(user, "@").concat(host, ":").concat(port, ":"), err.message);
314
- }
315
- // Remove failed connection from pool
316
- var list = _this.connections.get(connectionKey_1);
317
- if (list) {
318
- var index = list.findIndex(function (c) { return c.ssh === ssh; });
319
- if (index !== -1) {
320
- var removed = list.splice(index, 1)[0];
321
- // Dispose connection
322
- try {
323
- ssh.dispose();
324
- }
325
- catch (_a) {
326
- // Ignore dispose errors
327
- }
328
- console.log("[SSH Pool] Removed errored connection ".concat(removed.id, " from pool (will reconnect on next use)"));
329
- // Clean up empty lists
330
- if (list.length === 0) {
331
- _this.connections.delete(connectionKey_1);
332
- }
333
- }
334
- }
335
- });
336
- // Also handle 'close' event for unexpected disconnections
337
- underlyingClient.on('close', function () {
338
- var list = _this.connections.get(connectionKey_1);
339
- if (list) {
340
- var index = list.findIndex(function (c) { return c.ssh === ssh; });
341
- if (index !== -1) {
342
- var removed = list.splice(index, 1)[0];
343
- console.log("[SSH Pool] Connection ".concat(removed.id, " closed unexpectedly (will reconnect on next use)"));
344
- if (list.length === 0) {
345
- _this.connections.delete(connectionKey_1);
346
- }
347
- }
348
- }
349
- });
350
- }
351
- return [2 /*return*/, { value: ssh }];
352
- case 2:
353
- error_1 = _b.sent();
354
- errMsg = error_1 instanceof Error ? error_1.message : String(error_1);
355
- errors.push("".concat(method.name, ": ").concat(errMsg));
356
- return [3 /*break*/, 3];
357
- case 3: return [2 /*return*/];
358
- }
359
- });
360
- };
361
- this_1 = this;
362
- _i = 0, authMethods_1 = authMethods;
363
- _a.label = 1;
364
- case 1:
365
- if (!(_i < authMethods_1.length)) return [3 /*break*/, 4];
366
- method = authMethods_1[_i];
367
- return [5 /*yield**/, _loop_2(method)];
368
- case 2:
369
- state_1 = _a.sent();
370
- if (typeof state_1 === "object")
371
- return [2 /*return*/, state_1.value];
372
- _a.label = 3;
373
- case 3:
374
- _i++;
375
- return [3 /*break*/, 1];
376
- case 4:
377
- // All methods failed
378
- throw new Error("SSH connection failed to ".concat(host, " (tried: ").concat(authMethods.map(function (m) { return m.name; }).join(', '), "): ").concat(errors.join('; ')));
379
- }
380
- });
381
- });
382
- };
383
- /**
384
- * Get total number of connections across all hosts
385
- */
386
- SSHConnectionPool.prototype.getTotalConnectionCount = function () {
387
- var count = 0;
388
- for (var _i = 0, _a = this.connections.values(); _i < _a.length; _i++) {
389
- var list = _a[_i];
390
- count += list.length;
391
- }
392
- return count;
393
- };
394
- /**
395
- * Execute a command using a pooled connection
396
- *
397
- * ERROR HANDLING BEHAVIOR:
398
- * =========================
399
- * If result.stderr exists AND result.stdout is empty, we throw an error.
400
- * This is intentional - commands that fail should return fallback values
401
- * via shell redirection (e.g., `|| echo "0"` or `2>/dev/null`).
402
- *
403
- * Example of proper fallback handling:
404
- * `type nvidia-smi 2>/dev/null && nvidia-smi ... || echo NOGPU`
405
- *
406
- * This ensures commands don't silently fail - they must handle their own
407
- * error cases and return sensible defaults.
408
- */
409
- SSHConnectionPool.prototype.exec = function (command, options) {
410
- return __awaiter(this, void 0, void 0, function () {
411
- var ssh, result, output, error_2, host, _a, user, _b, port, key, list, index, removed, _c;
412
- return __generator(this, function (_d) {
413
- switch (_d.label) {
414
- case 0: return [4 /*yield*/, this.getConnection(options)];
415
- case 1:
416
- ssh = _d.sent();
417
- _d.label = 2;
418
- case 2:
419
- _d.trys.push([2, 4, , 10]);
420
- return [4 /*yield*/, ssh.execCommand(command, {
421
- execOptions: {
422
- timeout: (options.timeout || 5) * 1000,
423
- },
424
- })
425
- // If we have stderr but no stdout, the command failed
426
- // Commands should handle their own fallbacks (|| echo fallback, 2>/dev/null, etc)
427
- ];
428
- case 3:
429
- result = _d.sent();
430
- // If we have stderr but no stdout, the command failed
431
- // Commands should handle their own fallbacks (|| echo fallback, 2>/dev/null, etc)
432
- if (result.stderr && !result.stdout) {
433
- output = result.stdout ? "".concat(result.stderr, "\n").concat(result.stdout) : result.stderr;
434
- throw new Error(output);
435
- }
436
- return [2 /*return*/, result.stdout.trim()];
437
- case 4:
438
- error_2 = _d.sent();
439
- host = options.host, _a = options.user, user = _a === void 0 ? 'root' : _a, _b = options.port, port = _b === void 0 ? 22 : _b;
440
- key = this.getKey(host, port, user);
441
- list = this.connections.get(key);
442
- if (!list) return [3 /*break*/, 9];
443
- index = list.findIndex(function (c) { return c.ssh === ssh; });
444
- if (!(index !== -1)) return [3 /*break*/, 8];
445
- removed = list.splice(index, 1)[0];
446
- _d.label = 5;
447
- case 5:
448
- _d.trys.push([5, 7, , 8]);
449
- return [4 /*yield*/, removed.ssh.dispose()];
450
- case 6:
451
- _d.sent();
452
- return [3 /*break*/, 8];
453
- case 7:
454
- _c = _d.sent();
455
- return [3 /*break*/, 8];
456
- case 8:
457
- // Clean up empty lists
458
- if (list.length === 0) {
459
- this.connections.delete(key);
460
- }
461
- _d.label = 9;
462
- case 9: throw error_2;
463
- case 10: return [2 /*return*/];
464
- }
465
- });
466
- });
467
- };
468
- /**
469
- * Check if a connection exists and is alive for a given host
470
- */
471
- SSHConnectionPool.prototype.hasConnection = function (options) {
472
- return __awaiter(this, void 0, void 0, function () {
473
- var host, _a, user, _b, port, key, list, i, conn, _c, _d;
474
- return __generator(this, function (_e) {
475
- switch (_e.label) {
476
- case 0:
477
- host = options.host, _a = options.user, user = _a === void 0 ? 'root' : _a, _b = options.port, port = _b === void 0 ? 22 : _b;
478
- key = this.getKey(host, port, user);
479
- list = this.connections.get(key);
480
- if (!list || list.length === 0) {
481
- return [2 /*return*/, false];
482
- }
483
- i = list.length - 1;
484
- _e.label = 1;
485
- case 1:
486
- if (!(i >= 0)) return [3 /*break*/, 10];
487
- conn = list[i];
488
- _e.label = 2;
489
- case 2:
490
- _e.trys.push([2, 4, , 9]);
491
- return [4 /*yield*/, conn.ssh.exec('echo', ['ok'])];
492
- case 3:
493
- _e.sent();
494
- return [2 /*return*/, true];
495
- case 4:
496
- _c = _e.sent();
497
- // Remove dead connection
498
- list.splice(i, 1);
499
- _e.label = 5;
500
- case 5:
501
- _e.trys.push([5, 7, , 8]);
502
- return [4 /*yield*/, conn.ssh.dispose()];
503
- case 6:
504
- _e.sent();
505
- return [3 /*break*/, 8];
506
- case 7:
507
- _d = _e.sent();
508
- return [3 /*break*/, 8];
509
- case 8: return [3 /*break*/, 9];
510
- case 9:
511
- i--;
512
- return [3 /*break*/, 1];
513
- case 10:
514
- // Clean up empty list
515
- if (list.length === 0) {
516
- this.connections.delete(key);
517
- }
518
- return [2 /*return*/, false];
519
- }
520
- });
521
- });
522
- };
523
- /**
524
- * Close a specific connection by SSH instance
525
- */
526
- SSHConnectionPool.prototype.closeConnectionInstance = function (ssh) {
527
- return __awaiter(this, void 0, void 0, function () {
528
- var _i, _a, _b, key, list, index, removed, _c;
529
- return __generator(this, function (_d) {
530
- switch (_d.label) {
531
- case 0:
532
- _i = 0, _a = this.connections.entries();
533
- _d.label = 1;
534
- case 1:
535
- if (!(_i < _a.length)) return [3 /*break*/, 7];
536
- _b = _a[_i], key = _b[0], list = _b[1];
537
- index = list.findIndex(function (c) { return c.ssh === ssh; });
538
- if (!(index !== -1)) return [3 /*break*/, 6];
539
- removed = list.splice(index, 1)[0];
540
- _d.label = 2;
541
- case 2:
542
- _d.trys.push([2, 4, , 5]);
543
- return [4 /*yield*/, removed.ssh.dispose()];
544
- case 3:
545
- _d.sent();
546
- return [3 /*break*/, 5];
547
- case 4:
548
- _c = _d.sent();
549
- return [3 /*break*/, 5];
550
- case 5:
551
- // Clean up empty lists
552
- if (list.length === 0) {
553
- this.connections.delete(key);
554
- }
555
- return [2 /*return*/];
556
- case 6:
557
- _i++;
558
- return [3 /*break*/, 1];
559
- case 7: return [2 /*return*/];
560
- }
561
- });
562
- });
563
- };
564
- /**
565
- * Close all connections for a specific host
566
- */
567
- SSHConnectionPool.prototype.closeConnection = function (options) {
568
- return __awaiter(this, void 0, void 0, function () {
569
- var host, _a, user, _b, port, key, list, _i, list_1, conn, _c;
570
- return __generator(this, function (_d) {
571
- switch (_d.label) {
572
- case 0:
573
- host = options.host, _a = options.user, user = _a === void 0 ? 'root' : _a, _b = options.port, port = _b === void 0 ? 22 : _b;
574
- key = this.getKey(host, port, user);
575
- list = this.connections.get(key);
576
- if (!list) return [3 /*break*/, 6];
577
- this.connections.delete(key);
578
- _i = 0, list_1 = list;
579
- _d.label = 1;
580
- case 1:
581
- if (!(_i < list_1.length)) return [3 /*break*/, 6];
582
- conn = list_1[_i];
583
- _d.label = 2;
584
- case 2:
585
- _d.trys.push([2, 4, , 5]);
586
- return [4 /*yield*/, conn.ssh.dispose()];
587
- case 3:
588
- _d.sent();
589
- return [3 /*break*/, 5];
590
- case 4:
591
- _c = _d.sent();
592
- return [3 /*break*/, 5];
593
- case 5:
594
- _i++;
595
- return [3 /*break*/, 1];
596
- case 6: return [2 /*return*/];
597
- }
598
- });
599
- });
600
- };
601
- /**
602
- * Evict the oldest connection from the pool
603
- */
604
- SSHConnectionPool.prototype.evictOldest = function () {
605
- return __awaiter(this, void 0, void 0, function () {
606
- var oldest, _i, _a, _b, key, list, i, conn, list, _c;
607
- return __generator(this, function (_d) {
608
- switch (_d.label) {
609
- case 0:
610
- oldest = null;
611
- for (_i = 0, _a = this.connections.entries(); _i < _a.length; _i++) {
612
- _b = _a[_i], key = _b[0], list = _b[1];
613
- for (i = 0; i < list.length; i++) {
614
- conn = list[i];
615
- if (!oldest || conn.lastUsed < oldest.conn.lastUsed) {
616
- oldest = { key: key, conn: conn, listIndex: i };
617
- }
618
- }
619
- }
620
- if (!oldest) return [3 /*break*/, 4];
621
- list = this.connections.get(oldest.key);
622
- if (list) {
623
- list.splice(oldest.listIndex, 1);
624
- // Clean up empty lists
625
- if (list.length === 0) {
626
- this.connections.delete(oldest.key);
627
- }
628
- }
629
- _d.label = 1;
630
- case 1:
631
- _d.trys.push([1, 3, , 4]);
632
- return [4 /*yield*/, oldest.conn.ssh.dispose()];
633
- case 2:
634
- _d.sent();
635
- return [3 /*break*/, 4];
636
- case 3:
637
- _c = _d.sent();
638
- return [3 /*break*/, 4];
639
- case 4: return [2 /*return*/];
640
- }
641
- });
642
- });
643
- };
644
- /**
645
- * Clean up idle connections
646
- */
647
- SSHConnectionPool.prototype.cleanupIdle = function () {
648
- return __awaiter(this, void 0, void 0, function () {
649
- var now, _i, _a, _b, key, list, toRemove, i, _c, _d, index, conn, _e;
650
- return __generator(this, function (_f) {
651
- switch (_f.label) {
652
- case 0:
653
- now = Date.now();
654
- _i = 0, _a = this.connections.entries();
655
- _f.label = 1;
656
- case 1:
657
- if (!(_i < _a.length)) return [3 /*break*/, 9];
658
- _b = _a[_i], key = _b[0], list = _b[1];
659
- toRemove = [];
660
- for (i = 0; i < list.length; i++) {
661
- if (now - list[i].lastUsed > this.config.idleTimeout) {
662
- toRemove.push(i);
663
- }
664
- }
665
- _c = 0, _d = toRemove.reverse();
666
- _f.label = 2;
667
- case 2:
668
- if (!(_c < _d.length)) return [3 /*break*/, 7];
669
- index = _d[_c];
670
- conn = list.splice(index, 1)[0];
671
- _f.label = 3;
672
- case 3:
673
- _f.trys.push([3, 5, , 6]);
674
- return [4 /*yield*/, conn.ssh.dispose()];
675
- case 4:
676
- _f.sent();
677
- return [3 /*break*/, 6];
678
- case 5:
679
- _e = _f.sent();
680
- return [3 /*break*/, 6];
681
- case 6:
682
- _c++;
683
- return [3 /*break*/, 2];
684
- case 7:
685
- // Clean up empty lists
686
- if (list.length === 0) {
687
- this.connections.delete(key);
688
- }
689
- _f.label = 8;
690
- case 8:
691
- _i++;
692
- return [3 /*break*/, 1];
693
- case 9: return [2 /*return*/];
694
- }
695
- });
696
- });
697
- };
698
- /**
699
- * Start periodic cleanup
700
- */
701
- SSHConnectionPool.prototype.startCleanup = function () {
702
- var _this = this;
703
- // Run cleanup every minute
704
- this.cleanupInterval = setInterval(function () {
705
- _this.cleanupIdle().catch(console.error);
706
- }, 60 * 1000);
707
- };
708
- /**
709
- * Stop cleanup interval
710
- */
711
- SSHConnectionPool.prototype.stopCleanup = function () {
712
- if (this.cleanupInterval) {
713
- clearInterval(this.cleanupInterval);
714
- this.cleanupInterval = null;
715
- }
716
- };
717
- /**
718
- * Close all connections and stop cleanup
719
- */
720
- SSHConnectionPool.prototype.closeAll = function () {
721
- return __awaiter(this, void 0, void 0, function () {
722
- var closePromises, _i, _a, list, _loop_3, _b, list_2, conn;
723
- var _this = this;
724
- return __generator(this, function (_c) {
725
- switch (_c.label) {
726
- case 0:
727
- this.stopCleanup();
728
- closePromises = [];
729
- for (_i = 0, _a = this.connections.values(); _i < _a.length; _i++) {
730
- list = _a[_i];
731
- _loop_3 = function (conn) {
732
- closePromises.push((function () { return __awaiter(_this, void 0, void 0, function () {
733
- var _a;
734
- return __generator(this, function (_b) {
735
- switch (_b.label) {
736
- case 0:
737
- _b.trys.push([0, 2, , 3]);
738
- return [4 /*yield*/, conn.ssh.dispose()];
739
- case 1:
740
- _b.sent();
741
- return [3 /*break*/, 3];
742
- case 2:
743
- _a = _b.sent();
744
- return [3 /*break*/, 3];
745
- case 3: return [2 /*return*/];
746
- }
747
- });
748
- }); })());
749
- };
750
- for (_b = 0, list_2 = list; _b < list_2.length; _b++) {
751
- conn = list_2[_b];
752
- _loop_3(conn);
753
- }
754
- }
755
- return [4 /*yield*/, Promise.all(closePromises)];
756
- case 1:
757
- _c.sent();
758
- this.connections.clear();
759
- return [2 /*return*/];
760
- }
761
- });
762
- });
763
- };
764
- /**
765
- * Get pool statistics
766
- */
767
- SSHConnectionPool.prototype.getStats = function () {
768
- var now = Date.now();
769
- var allConnections = [];
770
- for (var _i = 0, _a = this.connections.values(); _i < _a.length; _i++) {
771
- var list = _a[_i];
772
- for (var _b = 0, list_3 = list; _b < list_3.length; _b++) {
773
- var conn = list_3[_b];
774
- allConnections.push({
775
- host: conn.host,
776
- port: conn.port,
777
- user: conn.user,
778
- lastUsed: new Date(conn.lastUsed),
779
- idleMs: now - conn.lastUsed,
780
- id: conn.id,
781
- });
782
- }
783
- }
784
- return {
785
- totalConnections: allConnections.length,
786
- connections: allConnections,
787
- };
788
- };
789
- /**
790
- * Check if a host has an active connection
791
- */
792
- SSHConnectionPool.prototype.isConnected = function (host, user, port) {
793
- if (user === void 0) { user = 'root'; }
794
- if (port === void 0) { port = 22; }
795
- var key = this.getKey(host, port, user);
796
- var list = this.connections.get(key);
797
- return list !== undefined && list.length > 0;
798
- };
799
- return SSHConnectionPool;
800
- }());
801
- exports.SSHConnectionPool = SSHConnectionPool;
802
- // Global singleton instance
803
- var globalPool = null;
804
- /**
805
- * Get the global connection pool instance
806
- */
807
- function getSSHPool(config) {
808
- if (!globalPool) {
809
- globalPool = new SSHConnectionPool(config);
810
- }
811
- return globalPool;
812
- }
813
- /**
814
- * Close the global pool (for cleanup/shutdown)
815
- */
816
- function closeGlobalSSHPool() {
817
- return __awaiter(this, void 0, void 0, function () {
818
- return __generator(this, function (_a) {
819
- switch (_a.label) {
820
- case 0:
821
- if (!globalPool) return [3 /*break*/, 2];
822
- return [4 /*yield*/, globalPool.closeAll()];
823
- case 1:
824
- _a.sent();
825
- globalPool = null;
826
- _a.label = 2;
827
- case 2: return [2 /*return*/];
828
- }
829
- });
830
- });
831
- }
832
- /**
833
- * Get active SSH connections (for monitoring)
834
- */
835
- function getActiveSSHConnections() {
836
- if (!globalPool) {
837
- return { totalConnections: 0, connections: [] };
838
- }
839
- return globalPool.getStats();
840
- }