@@ -49,8 +49,6 @@ const config = new Conf({
49
49
} ,
50
50
} ) ;
51
51
52
-
53
-
54
52
const SYNC_INTERVAL = 1000 * 60 * 60 ; // 1 hour
55
53
56
54
// Fancy banner
@@ -168,30 +166,93 @@ const initAWS = async () => {
168
166
} ;
169
167
170
168
/**
171
- * Lists ECS clusters
169
+ * Lists ECS clusters along with their service, task, and container instance counts
172
170
* @param {ECS } ecs - AWS ECS client
173
- * @returns {Promise<string[]> } Array of cluster names
171
+ * @returns {Promise<Array<{clusterName: string, servicesCount: number, tasksCount: number, containerInstancesCount: number}>> } Clusters with details
174
172
*/
175
173
async function listClusters ( ecs ) {
176
174
try {
177
175
const spinner = ora ( "Fetching clusters..." ) . start ( ) ;
178
176
const { clusterArns } = await ecs . listClusters ( { } ) ;
179
177
spinner . succeed ( "Clusters fetched" ) ;
180
178
181
- return clusterArns . map ( ( arn ) => arn . split ( "/" ) . pop ( ) ) ;
179
+ const clusters = await Promise . all (
180
+ clusterArns . map ( async ( arn ) => {
181
+ const clusterName = arn . split ( "/" ) . pop ( ) ;
182
+
183
+ let servicesCount = 0 ;
184
+ let tasksCount = 0 ;
185
+ let containerInstancesCount = 0 ;
186
+
187
+ // Fetch services count
188
+ try {
189
+ const servicesResult = await ecs . listServices ( {
190
+ cluster : clusterName ,
191
+ } ) ;
192
+ servicesCount = servicesResult . serviceArns
193
+ ? servicesResult . serviceArns . length
194
+ : 0 ;
195
+ } catch ( err ) {
196
+ logger . error (
197
+ chalk . red (
198
+ `Error fetching services for cluster ${ clusterName } : ${ err . message } `
199
+ )
200
+ ) ;
201
+ }
202
+
203
+ // Fetch tasks count
204
+ try {
205
+ const tasksResult = await ecs . listTasks ( { cluster : clusterName } ) ;
206
+ tasksCount = tasksResult . taskArns ? tasksResult . taskArns . length : 0 ;
207
+ } catch ( err ) {
208
+ logger . error (
209
+ chalk . red (
210
+ `Error fetching tasks for cluster ${ clusterName } : ${ err . message } `
211
+ )
212
+ ) ;
213
+ }
214
+
215
+ // Fetch container instances count
216
+ try {
217
+ const containerInstancesResult = await ecs . listContainerInstances ( {
218
+ cluster : clusterName ,
219
+ } ) ;
220
+ containerInstancesCount =
221
+ containerInstancesResult . containerInstanceArns
222
+ ? containerInstancesResult . containerInstanceArns . length
223
+ : 0 ;
224
+ } catch ( err ) {
225
+ logger . error (
226
+ chalk . red (
227
+ `Error fetching container instances for cluster ${ clusterName } : ${ err . message } `
228
+ )
229
+ ) ;
230
+ }
231
+
232
+ return {
233
+ clusterName,
234
+ servicesCount,
235
+ tasksCount,
236
+ containerInstancesCount,
237
+ } ;
238
+ } )
239
+ ) ;
240
+
241
+ return clusters ;
182
242
} catch ( err ) {
183
243
logger . error ( chalk . red ( err . message ) ) ;
184
244
}
185
245
}
186
246
187
247
/**
188
- * Prompts user to select an ECS cluster
248
+ * Prompts user to select an ECS cluster,
249
+ * now displaying the number of services, tasks, and container instances.
189
250
* @param {ECS } ecs - AWS ECS client
190
251
* @returns {Promise<string> } Selected cluster name
191
252
*/
192
253
async function selectCluster ( ecs ) {
193
254
const clusters = await listClusters ( ecs ) ;
194
- if ( clusters . length === 0 ) {
255
+ if ( ! clusters || clusters . length === 0 ) {
195
256
logger . warn ( chalk . yellow ( "No clusters found." ) ) ;
196
257
process . exit ( 0 ) ;
197
258
}
@@ -202,21 +263,26 @@ async function selectCluster(ecs) {
202
263
message : chalk . blue ( "Select ECS cluster:" ) ,
203
264
prefix : "🚀" ,
204
265
choices : clusters . map ( ( c ) => ( {
205
- name : chalk . green ( c ) ,
206
- value : c ,
266
+ name :
267
+ chalk . green ( c . clusterName ) +
268
+ chalk . yellow (
269
+ ` (Services: ${ c . servicesCount } , Tasks: ${ c . tasksCount } , Container Instances: ${ c . containerInstancesCount } )`
270
+ ) ,
271
+ value : c . clusterName ,
207
272
} ) ) ,
208
273
} ,
209
274
] ) ;
210
275
return cluster ;
211
276
}
212
277
213
278
/**
214
- * Prompts user to select a task within a cluster
279
+ * Prompts user to select a task within a cluster, optionally allowing going back.
215
280
* @param {ECS } ecs - AWS ECS client
216
281
* @param {string } cluster - Cluster name
217
- * @returns {Promise<string> } Selected task ARN
282
+ * @param {boolean } allowBack - Whether to allow going back (adds a "← Go Back" option)
283
+ * @returns {Promise<string> } Selected task ARN or '__BACK__'
218
284
*/
219
- async function selectTask ( ecs , cluster ) {
285
+ async function selectTask ( ecs , cluster , allowBack = false ) {
220
286
try {
221
287
const spinner = ora ( "Fetching tasks..." ) . start ( ) ;
222
288
const { taskArns } = await ecs . listTasks ( { cluster } ) ;
@@ -234,23 +300,56 @@ async function selectTask(ecs, cluster) {
234
300
235
301
spinner . succeed ( "Tasks fetched" ) ;
236
302
303
+ // Sort the tasks alphabetically by the task definition name.
304
+ tasks . sort ( ( a , b ) => {
305
+ const aName = ( a . taskDefinitionArn . split ( "/" ) . pop ( ) || "" ) . toLowerCase ( ) ;
306
+ const bName = ( b . taskDefinitionArn . split ( "/" ) . pop ( ) || "" ) . toLowerCase ( ) ;
307
+ return aName . localeCompare ( bName ) ;
308
+ } ) ;
309
+
310
+ // Build choices array
311
+ const choices = tasks . map ( ( task ) => {
312
+ // Extract the task definition name
313
+ const taskDefName = task . taskDefinitionArn . split ( "/" ) . pop ( ) ;
314
+ // Extract the task ID from the task ARN and get its last 6 characters
315
+ const taskId = task . taskArn . split ( "/" ) . pop ( ) ;
316
+ const shortTaskId = taskId . slice ( - 6 ) ;
317
+ // Format the task started at time if available
318
+ const startedAt = task . startedAt
319
+ ? new Date ( task . startedAt ) . toLocaleString ( )
320
+ : "N/A" ;
321
+ return {
322
+ name : `${ chalk . green ( taskDefName ) } ${ chalk . yellow (
323
+ `(ID: ${ shortTaskId } , ${ task . lastStatus } , started at: ${ startedAt } )`
324
+ ) } `,
325
+ value : task . taskArn ,
326
+ } ;
327
+ } ) ;
328
+
329
+ // Add the "Go Back" option if allowed
330
+ if ( allowBack ) {
331
+ choices . unshift ( {
332
+ name : chalk . blue ( "← Go Back" ) ,
333
+ value : "__BACK__" ,
334
+ } ) ;
335
+ }
336
+
237
337
const { taskArn } = await inquirer . prompt ( [
238
338
{
239
339
type : "list" ,
240
340
name : "taskArn" ,
241
341
message : chalk . blue ( "Select task:" ) ,
242
342
prefix : "📦" ,
243
- choices : tasks . map ( ( task ) => ( {
244
- name : `${ chalk . green (
245
- task . taskDefinitionArn . split ( "/" ) . pop ( )
246
- ) } ${ chalk . yellow ( `(${ task . lastStatus } )` ) } `,
247
- value : task . taskArn ,
248
- } ) ) ,
343
+ choices,
344
+ loop : false ,
345
+ pageSize : choices . length ,
249
346
} ,
250
347
] ) ;
348
+
251
349
return taskArn ;
252
350
} catch ( err ) {
253
351
logger . error ( chalk . red ( err . message ) ) ;
352
+ process . exit ( 1 ) ;
254
353
}
255
354
}
256
355
@@ -279,21 +378,42 @@ async function getTaskDetails(ecs, cluster, taskArn) {
279
378
}
280
379
281
380
/**
282
- * Prompts user to select a container within a task
381
+ * Prompts user to select a container within a task, optionally allowing to go back.
283
382
* @param {ECS } ecs - AWS ECS client
284
383
* @param {string } cluster - Cluster name
285
384
* @param {string } taskArn - Task ARN
286
- * @returns {Promise<string> } Selected container name
385
+ * @param {boolean } allowBack - Whether to allow going back (adds a "← Go Back" option)
386
+ * @returns {Promise<string> } Selected container name or '__BACK__'
287
387
*/
288
- async function selectContainer ( ecs , cluster , taskArn ) {
388
+ async function selectContainer ( ecs , cluster , taskArn , allowBack = false ) {
289
389
const spinner = ora ( "Fetching container details..." ) . start ( ) ;
290
390
const task = await getTaskDetails ( ecs , cluster , taskArn ) ;
291
391
const containers = task . containers ;
292
392
spinner . succeed ( "Container details fetched" ) ;
293
393
294
- if ( containers . length === 1 ) {
394
+ let choices = [ ] ;
395
+
396
+ // If only one container and back navigation is not requested, auto-select it.
397
+ // Otherwise, present a list including a "Go Back" option if allowed.
398
+ if ( containers . length === 1 && ! allowBack ) {
295
399
logger . info ( chalk . dim ( "Single container detected, auto-selecting..." ) ) ;
296
400
return containers [ 0 ] . name ;
401
+ } else {
402
+ choices = containers . map ( ( container ) => {
403
+ return {
404
+ name : `${ chalk . green ( container . name ) } ${ chalk . yellow (
405
+ `(${ container . lastStatus } )`
406
+ ) } `,
407
+ value : container . name ,
408
+ } ;
409
+ } ) ;
410
+
411
+ if ( allowBack ) {
412
+ choices . unshift ( {
413
+ name : chalk . blue ( "← Go Back" ) ,
414
+ value : "__BACK__" ,
415
+ } ) ;
416
+ }
297
417
}
298
418
299
419
const { containerName } = await inquirer . prompt ( [
@@ -302,12 +422,7 @@ async function selectContainer(ecs, cluster, taskArn) {
302
422
name : "containerName" ,
303
423
message : chalk . blue ( "Select container:" ) ,
304
424
prefix : "🐳" ,
305
- choices : containers . map ( ( container ) => ( {
306
- name : `${ chalk . green ( container . name ) } ${ chalk . yellow (
307
- `(${ container . lastStatus } )`
308
- ) } `,
309
- value : container . name ,
310
- } ) ) ,
425
+ choices,
311
426
} ,
312
427
] ) ;
313
428
@@ -497,16 +612,37 @@ program
497
612
. action ( async ( ) => {
498
613
try {
499
614
const ecs = await initAWS ( ) ;
500
- const cluster = await selectCluster ( ecs ) ;
501
- const taskArn = await selectTask ( ecs , cluster ) ;
502
- const containerName = await selectContainer ( ecs , cluster , taskArn ) ;
615
+ let cluster , taskArn , containerName ;
616
+
617
+ // Cluster selection (top-level; no back option here)
618
+ cluster = await selectCluster ( ecs ) ;
619
+
620
+ // Wrap prompts in loops to allow backward navigation.
621
+ while ( true ) {
622
+ // Task selection: allow going back to re-select cluster.
623
+ taskArn = await selectTask ( ecs , cluster , true ) ;
624
+ if ( taskArn === "__BACK__" ) {
625
+ cluster = await selectCluster ( ecs ) ;
626
+ continue ; // go back and select a new cluster
627
+ }
503
628
504
- logger . info (
505
- chalk . green (
506
- `🚀 Connecting to container ${ chalk . bold ( containerName ) } ...`
507
- )
508
- ) ;
509
- await executeCommand ( cluster , taskArn , containerName ) ;
629
+ // Container selection: allow going back to re-select task.
630
+ while ( true ) {
631
+ containerName = await selectContainer ( ecs , cluster , taskArn , true ) ;
632
+ if ( containerName === "__BACK__" ) {
633
+ // Go back to task selection
634
+ break ;
635
+ }
636
+
637
+ logger . info (
638
+ chalk . green (
639
+ `🚀 Connecting to container ${ chalk . bold ( containerName ) } ...`
640
+ )
641
+ ) ;
642
+ await executeCommand ( cluster , taskArn , containerName ) ;
643
+ process . exit ( 0 ) ;
644
+ }
645
+ }
510
646
} catch ( err ) {
511
647
logger . error ( chalk . red ( err . message ) ) ;
512
648
process . exit ( 1 ) ;
0 commit comments