cdd-cli 3.2.0 → 4.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.
Files changed (136) hide show
  1. package/.atl/skill-registry.md +30 -0
  2. package/.editorconfig +12 -0
  3. package/.eslintrc.json +1 -1
  4. package/.prettierignore +4 -0
  5. package/.prettierrc +8 -0
  6. package/.~lock.ROADMAP.md# +1 -0
  7. package/CHANGELOG.md +80 -0
  8. package/README.es.md +198 -0
  9. package/README.md +81 -23
  10. package/ROADMAP.md +647 -0
  11. package/SECURITY.md +5 -0
  12. package/__mocks__/ink.cjs +17 -0
  13. package/coverage/clover.xml +471 -500
  14. package/coverage/coverage-final.json +16 -16
  15. package/coverage/lcov-report/ContainerCreationPrompt.jsx.html +400 -0
  16. package/coverage/lcov-report/ControlsHUD.jsx.html +256 -0
  17. package/coverage/lcov-report/components/ContainerCreationPrompt.jsx.html +394 -0
  18. package/coverage/lcov-report/components/PromptField.jsx.html +229 -0
  19. package/coverage/lcov-report/components/SuggestionPanel.jsx.html +268 -0
  20. package/coverage/lcov-report/components/index.html +146 -0
  21. package/coverage/lcov-report/helpers/constants.js.html +139 -22
  22. package/coverage/lcov-report/helpers/containerOptionsBuilder.js.html +75 -42
  23. package/coverage/lcov-report/helpers/dockerHubService.js.html +310 -0
  24. package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerActions.js.html +162 -84
  25. package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerList.js.html +50 -32
  26. package/coverage/lcov-report/helpers/dockerService/serviceComponents/index.html +21 -21
  27. package/coverage/lcov-report/helpers/imageNameUtils.js.html +181 -0
  28. package/coverage/lcov-report/helpers/index.html +84 -54
  29. package/coverage/lcov-report/helpers/logger.js.html +68 -68
  30. package/coverage/lcov-report/helpers/safeCall.js.html +15 -15
  31. package/coverage/lcov-report/helpers/validationHelpers.js.html +66 -114
  32. package/coverage/lcov-report/hooks/creation/index.html +20 -20
  33. package/coverage/lcov-report/hooks/creation/useContainerActions.js.html +38 -17
  34. package/coverage/lcov-report/hooks/creation/useContainerCreation.js.html +593 -89
  35. package/coverage/lcov-report/hooks/creation/useLogsViewer.js.html +7 -7
  36. package/coverage/lcov-report/hooks/debug/index.html +5 -5
  37. package/coverage/lcov-report/hooks/debug/useDebugLogs.js.html +35 -20
  38. package/coverage/lcov-report/hooks/index.html +40 -40
  39. package/coverage/lcov-report/hooks/navigation/index.html +19 -19
  40. package/coverage/lcov-report/hooks/navigation/useContainerSelection.js.html +38 -29
  41. package/coverage/lcov-report/hooks/useContainerCommandRouter.js.html +119 -80
  42. package/coverage/lcov-report/hooks/useControls.js.html +306 -123
  43. package/coverage/lcov-report/hooks/useEraseConfirmation.js.html +8 -8
  44. package/coverage/lcov-report/hooks/useExitHandler.js.html +50 -41
  45. package/coverage/lcov-report/index.html +52 -67
  46. package/coverage/lcov.info +899 -952
  47. package/dist/App.js +18 -12
  48. package/dist/components/ContainerCreationPrompt.js +50 -13
  49. package/dist/components/ContainerList.js +3 -3
  50. package/dist/components/ContainerRow.js +20 -20
  51. package/dist/components/ContainerSection.js +3 -3
  52. package/dist/components/ControlsHUD.js +106 -0
  53. package/dist/components/Footer.js +2 -2
  54. package/dist/components/Header.js +7 -7
  55. package/dist/components/LogViewer.js +3 -3
  56. package/dist/components/MessageFeedback.js +2 -2
  57. package/dist/components/PromptField.js +4 -4
  58. package/dist/components/StatsBar.js +5 -5
  59. package/dist/components/SuggestionPanel.js +53 -0
  60. package/dist/components/UsageMenu.js +3 -3
  61. package/dist/helpers/appInfo.js +7 -7
  62. package/dist/helpers/constants.js +154 -1
  63. package/dist/helpers/containerOptionsBuilder.js +3 -3
  64. package/dist/helpers/dockerHubService.js +111 -0
  65. package/dist/helpers/dockerService/dockerService.js +1 -1
  66. package/dist/helpers/dockerService/serviceComponents/containerActions.js +99 -40
  67. package/dist/helpers/dockerService/serviceComponents/containerList.js +11 -11
  68. package/dist/helpers/dockerService/serviceComponents/containerLogs.js +9 -9
  69. package/dist/helpers/dockerService/serviceComponents/containerStats.js +5 -5
  70. package/dist/helpers/dockerService/serviceComponents/imageUtils.js +1 -1
  71. package/dist/helpers/exitWithMessage.js +10 -10
  72. package/dist/helpers/imageNameUtils.js +32 -0
  73. package/dist/helpers/logger.js +13 -13
  74. package/dist/helpers/safeCall.js +2 -2
  75. package/dist/helpers/validationHelpers.js +76 -21
  76. package/dist/hooks/creation/useContainerActions.js +28 -11
  77. package/dist/hooks/creation/useContainerCreation.js +452 -62
  78. package/dist/hooks/creation/useLogsViewer.js +2 -2
  79. package/dist/hooks/debug/useDebugLogs.js +4 -4
  80. package/dist/hooks/useContainerCommandRouter.js +14 -14
  81. package/dist/hooks/useContainers.js +3 -3
  82. package/dist/hooks/useControls.js +85 -38
  83. package/dist/hooks/useEraseConfirmation.js +3 -3
  84. package/dist/index.js +9 -2
  85. package/jest.config.cjs +7 -0
  86. package/package.json +7 -3
  87. package/src/App.jsx +39 -16
  88. package/src/components/ContainerCreationPrompt.jsx +54 -10
  89. package/src/components/ContainerList.jsx +3 -3
  90. package/src/components/ContainerRow.jsx +19 -16
  91. package/src/components/ContainerSection.jsx +3 -3
  92. package/src/components/ControlsHUD.jsx +66 -0
  93. package/src/components/Footer.jsx +2 -2
  94. package/src/components/Header.jsx +7 -7
  95. package/src/components/LogViewer.jsx +5 -3
  96. package/src/components/MessageFeedback.jsx +2 -2
  97. package/src/components/PromptField.jsx +4 -4
  98. package/src/components/StatsBar.jsx +11 -7
  99. package/src/components/SuggestionPanel.jsx +61 -0
  100. package/src/components/UsageMenu.jsx +18 -13
  101. package/src/helpers/appInfo.js +7 -7
  102. package/src/helpers/constants.js +48 -9
  103. package/src/helpers/containerOptionsBuilder.js +16 -5
  104. package/src/helpers/dockerHubService.js +75 -0
  105. package/src/helpers/dockerService/dockerService.js +1 -1
  106. package/src/helpers/dockerService/serviceComponents/containerActions.js +87 -61
  107. package/src/helpers/dockerService/serviceComponents/containerList.js +31 -25
  108. package/src/helpers/dockerService/serviceComponents/containerLogs.js +41 -26
  109. package/src/helpers/dockerService/serviceComponents/containerStats.js +13 -11
  110. package/src/helpers/dockerService/serviceComponents/imageUtils.js +5 -4
  111. package/src/helpers/exitWithMessage.js +13 -7
  112. package/src/helpers/imageNameUtils.js +32 -0
  113. package/src/helpers/logger.js +15 -16
  114. package/src/helpers/safeCall.js +2 -2
  115. package/src/helpers/validationHelpers.js +13 -29
  116. package/src/hooks/creation/useContainerActions.js +32 -12
  117. package/src/hooks/creation/useContainerCreation.js +293 -47
  118. package/src/hooks/creation/useLogsViewer.js +2 -2
  119. package/src/hooks/debug/useDebugLogs.js +15 -10
  120. package/src/hooks/navigation/useContainerSelection.js +17 -14
  121. package/src/hooks/useContainerCommandRouter.js +71 -58
  122. package/src/hooks/useContainers.js +4 -4
  123. package/src/hooks/useControls.js +135 -72
  124. package/src/hooks/useEraseConfirmation.js +3 -3
  125. package/src/hooks/useExitHandler.js +29 -26
  126. package/src/index.js +8 -4
  127. package/test/ContainerCreationPrompt.dom.test.js +313 -0
  128. package/test/ControlsHUD.dom.test.js +125 -0
  129. package/test/SuggestionPanel.dom.test.js +83 -0
  130. package/test/constants.test.js +41 -0
  131. package/test/dockerHubService.test.js +213 -0
  132. package/test/logger.test.js +58 -0
  133. package/test/resolveImageTag.test.js +26 -0
  134. package/test/useContainerCreation.dom.test.js +428 -2
  135. package/test/useContainerCreationHub.dom.test.js +184 -0
  136. package/test/useControls.dom.test.js +406 -4
@@ -23,30 +23,30 @@
23
23
  <div class='clearfix'>
24
24
 
25
25
  <div class='fl pad1y space-right2'>
26
- <span class="strong">72.09% </span>
26
+ <span class="strong">71.42% </span>
27
27
  <span class="quiet">Statements</span>
28
- <span class='fraction'>93/129</span>
28
+ <span class='fraction'>85/119</span>
29
29
  </div>
30
30
 
31
31
 
32
32
  <div class='fl pad1y space-right2'>
33
- <span class="strong">60.29% </span>
33
+ <span class="strong">61.29% </span>
34
34
  <span class="quiet">Branches</span>
35
- <span class='fraction'>41/68</span>
35
+ <span class='fraction'>38/62</span>
36
36
  </div>
37
37
 
38
38
 
39
39
  <div class='fl pad1y space-right2'>
40
- <span class="strong">81.25% </span>
40
+ <span class="strong">80% </span>
41
41
  <span class="quiet">Functions</span>
42
- <span class='fraction'>13/16</span>
42
+ <span class='fraction'>12/15</span>
43
43
  </div>
44
44
 
45
45
 
46
46
  <div class='fl pad1y space-right2'>
47
- <span class="strong">73.6% </span>
47
+ <span class="strong">72.03% </span>
48
48
  <span class="quiet">Lines</span>
49
- <span class='fraction'>92/125</span>
49
+ <span class='fraction'>85/118</span>
50
50
  </div>
51
51
 
52
52
 
@@ -319,7 +319,34 @@
319
319
  <a name='L254'></a><a href='#L254'>254</a>
320
320
  <a name='L255'></a><a href='#L255'>255</a>
321
321
  <a name='L256'></a><a href='#L256'>256</a>
322
- <a name='L257'></a><a href='#L257'>257</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
322
+ <a name='L257'></a><a href='#L257'>257</a>
323
+ <a name='L258'></a><a href='#L258'>258</a>
324
+ <a name='L259'></a><a href='#L259'>259</a>
325
+ <a name='L260'></a><a href='#L260'>260</a>
326
+ <a name='L261'></a><a href='#L261'>261</a>
327
+ <a name='L262'></a><a href='#L262'>262</a>
328
+ <a name='L263'></a><a href='#L263'>263</a>
329
+ <a name='L264'></a><a href='#L264'>264</a>
330
+ <a name='L265'></a><a href='#L265'>265</a>
331
+ <a name='L266'></a><a href='#L266'>266</a>
332
+ <a name='L267'></a><a href='#L267'>267</a>
333
+ <a name='L268'></a><a href='#L268'>268</a>
334
+ <a name='L269'></a><a href='#L269'>269</a>
335
+ <a name='L270'></a><a href='#L270'>270</a>
336
+ <a name='L271'></a><a href='#L271'>271</a>
337
+ <a name='L272'></a><a href='#L272'>272</a>
338
+ <a name='L273'></a><a href='#L273'>273</a>
339
+ <a name='L274'></a><a href='#L274'>274</a>
340
+ <a name='L275'></a><a href='#L275'>275</a>
341
+ <a name='L276'></a><a href='#L276'>276</a>
342
+ <a name='L277'></a><a href='#L277'>277</a>
343
+ <a name='L278'></a><a href='#L278'>278</a>
344
+ <a name='L279'></a><a href='#L279'>279</a>
345
+ <a name='L280'></a><a href='#L280'>280</a>
346
+ <a name='L281'></a><a href='#L281'>281</a>
347
+ <a name='L282'></a><a href='#L282'>282</a>
348
+ <a name='L283'></a><a href='#L283'>283</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
349
+ <span class="cline-any cline-neutral">&nbsp;</span>
323
350
  <span class="cline-any cline-neutral">&nbsp;</span>
324
351
  <span class="cline-any cline-neutral">&nbsp;</span>
325
352
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -369,19 +396,6 @@
369
396
  <span class="cline-any cline-neutral">&nbsp;</span>
370
397
  <span class="cline-any cline-neutral">&nbsp;</span>
371
398
  <span class="cline-any cline-neutral">&nbsp;</span>
372
- <span class="cline-any cline-yes">3x</span>
373
- <span class="cline-any cline-yes">3x</span>
374
- <span class="cline-any cline-yes">3x</span>
375
- <span class="cline-any cline-yes">3x</span>
376
- <span class="cline-any cline-yes">3x</span>
377
- <span class="cline-any cline-yes">3x</span>
378
- <span class="cline-any cline-yes">3x</span>
379
- <span class="cline-any cline-neutral">&nbsp;</span>
380
- <span class="cline-any cline-neutral">&nbsp;</span>
381
- <span class="cline-any cline-neutral">&nbsp;</span>
382
- <span class="cline-any cline-neutral">&nbsp;</span>
383
- <span class="cline-any cline-neutral">&nbsp;</span>
384
- <span class="cline-any cline-neutral">&nbsp;</span>
385
399
  <span class="cline-any cline-neutral">&nbsp;</span>
386
400
  <span class="cline-any cline-neutral">&nbsp;</span>
387
401
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -415,6 +429,8 @@
415
429
  <span class="cline-any cline-yes">6x</span>
416
430
  <span class="cline-any cline-neutral">&nbsp;</span>
417
431
  <span class="cline-any cline-neutral">&nbsp;</span>
432
+ <span class="cline-any cline-neutral">&nbsp;</span>
433
+ <span class="cline-any cline-neutral">&nbsp;</span>
418
434
  <span class="cline-any cline-yes">6x</span>
419
435
  <span class="cline-any cline-neutral">&nbsp;</span>
420
436
  <span class="cline-any cline-yes">6x</span>
@@ -425,6 +441,9 @@
425
441
  <span class="cline-any cline-yes">6x</span>
426
442
  <span class="cline-any cline-yes">6x</span>
427
443
  <span class="cline-any cline-yes">6x</span>
444
+ <span class="cline-any cline-neutral">&nbsp;</span>
445
+ <span class="cline-any cline-neutral">&nbsp;</span>
446
+ <span class="cline-any cline-neutral">&nbsp;</span>
428
447
  <span class="cline-any cline-yes">6x</span>
429
448
  <span class="cline-any cline-yes">1x</span>
430
449
  <span class="cline-any cline-yes">1x</span>
@@ -436,6 +455,9 @@
436
455
  <span class="cline-any cline-no">&nbsp;</span>
437
456
  <span class="cline-any cline-neutral">&nbsp;</span>
438
457
  <span class="cline-any cline-neutral">&nbsp;</span>
458
+ <span class="cline-any cline-neutral">&nbsp;</span>
459
+ <span class="cline-any cline-neutral">&nbsp;</span>
460
+ <span class="cline-any cline-neutral">&nbsp;</span>
439
461
  <span class="cline-any cline-yes">6x</span>
440
462
  <span class="cline-any cline-yes">4x</span>
441
463
  <span class="cline-any cline-yes">4x</span>
@@ -465,7 +487,13 @@
465
487
  <span class="cline-any cline-neutral">&nbsp;</span>
466
488
  <span class="cline-any cline-neutral">&nbsp;</span>
467
489
  <span class="cline-any cline-yes">6x</span>
490
+ <span class="cline-any cline-neutral">&nbsp;</span>
491
+ <span class="cline-any cline-neutral">&nbsp;</span>
492
+ <span class="cline-any cline-neutral">&nbsp;</span>
493
+ <span class="cline-any cline-neutral">&nbsp;</span>
468
494
  <span class="cline-any cline-yes">6x</span>
495
+ <span class="cline-any cline-neutral">&nbsp;</span>
496
+ <span class="cline-any cline-neutral">&nbsp;</span>
469
497
  <span class="cline-any cline-yes">6x</span>
470
498
  <span class="cline-any cline-neutral">&nbsp;</span>
471
499
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -483,26 +511,42 @@
483
511
  <span class="cline-any cline-yes">4x</span>
484
512
  <span class="cline-any cline-yes">4x</span>
485
513
  <span class="cline-any cline-yes">4x</span>
514
+ <span class="cline-any cline-neutral">&nbsp;</span>
515
+ <span class="cline-any cline-neutral">&nbsp;</span>
486
516
  <span class="cline-any cline-yes">4x</span>
487
517
  <span class="cline-any cline-yes">4x</span>
488
518
  <span class="cline-any cline-yes">4x</span>
489
519
  <span class="cline-any cline-neutral">&nbsp;</span>
490
520
  <span class="cline-any cline-yes">4x</span>
521
+ <span class="cline-any cline-neutral">&nbsp;</span>
522
+ <span class="cline-any cline-neutral">&nbsp;</span>
523
+ <span class="cline-any cline-neutral">&nbsp;</span>
491
524
  <span class="cline-any cline-yes">4x</span>
492
525
  <span class="cline-any cline-yes">4x</span>
493
526
  <span class="cline-any cline-yes">4x</span>
494
527
  <span class="cline-any cline-yes">4x</span>
495
528
  <span class="cline-any cline-yes">4x</span>
529
+ <span class="cline-any cline-neutral">&nbsp;</span>
530
+ <span class="cline-any cline-neutral">&nbsp;</span>
496
531
  <span class="cline-any cline-yes">4x</span>
497
532
  <span class="cline-any cline-neutral">&nbsp;</span>
498
533
  <span class="cline-any cline-neutral">&nbsp;</span>
499
534
  <span class="cline-any cline-neutral">&nbsp;</span>
500
535
  <span class="cline-any cline-neutral">&nbsp;</span>
501
536
  <span class="cline-any cline-neutral">&nbsp;</span>
537
+ <span class="cline-any cline-neutral">&nbsp;</span>
538
+ <span class="cline-any cline-neutral">&nbsp;</span>
539
+ <span class="cline-any cline-neutral">&nbsp;</span>
540
+ <span class="cline-any cline-neutral">&nbsp;</span>
541
+ <span class="cline-any cline-neutral">&nbsp;</span>
502
542
  <span class="cline-any cline-no">&nbsp;</span>
503
543
  <span class="cline-any cline-neutral">&nbsp;</span>
504
544
  <span class="cline-any cline-neutral">&nbsp;</span>
505
545
  <span class="cline-any cline-neutral">&nbsp;</span>
546
+ <span class="cline-any cline-neutral">&nbsp;</span>
547
+ <span class="cline-any cline-neutral">&nbsp;</span>
548
+ <span class="cline-any cline-neutral">&nbsp;</span>
549
+ <span class="cline-any cline-neutral">&nbsp;</span>
506
550
  <span class="cline-any cline-no">&nbsp;</span>
507
551
  <span class="cline-any cline-no">&nbsp;</span>
508
552
  <span class="cline-any cline-no">&nbsp;</span>
@@ -514,8 +558,16 @@
514
558
  <span class="cline-any cline-neutral">&nbsp;</span>
515
559
  <span class="cline-any cline-neutral">&nbsp;</span>
516
560
  <span class="cline-any cline-neutral">&nbsp;</span>
561
+ <span class="cline-any cline-neutral">&nbsp;</span>
562
+ <span class="cline-any cline-neutral">&nbsp;</span>
563
+ <span class="cline-any cline-neutral">&nbsp;</span>
564
+ <span class="cline-any cline-neutral">&nbsp;</span>
565
+ <span class="cline-any cline-neutral">&nbsp;</span>
517
566
  <span class="cline-any cline-yes">6x</span>
518
567
  <span class="cline-any cline-yes">6x</span>
568
+ <span class="cline-any cline-neutral">&nbsp;</span>
569
+ <span class="cline-any cline-neutral">&nbsp;</span>
570
+ <span class="cline-any cline-neutral">&nbsp;</span>
519
571
  <span class="cline-any cline-yes">6x</span>
520
572
  <span class="cline-any cline-yes">6x</span>
521
573
  <span class="cline-any cline-yes">6x</span>
@@ -575,10 +627,11 @@
575
627
  <span class="cline-any cline-no">&nbsp;</span>
576
628
  <span class="cline-any cline-neutral">&nbsp;</span>
577
629
  <span class="cline-any cline-neutral">&nbsp;</span>
578
- <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import { docker } from "../dockerService.js";
579
- import { imageExists, pullImage } from "./imageUtils.js";
580
- import { TIMEOUTS, IMAGE_PROFILES } from "../../constants.js";
581
- import { logger } from "../../logger.js";
630
+ <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import { docker } from '../dockerService.js';
631
+ import { imageExists, pullImage } from './imageUtils.js';
632
+ import { TIMEOUTS, IMAGE_PROFILES } from '../../constants.js';
633
+ import { logger } from '../../logger.js';
634
+ import { normalizeImageName } from '../../imageNameUtils.js';
582
635
  &nbsp;
583
636
  /**
584
637
  * Helper to add timeout to promises. If the provided promise does not settle
@@ -592,9 +645,9 @@ import { logger } from "../../logger.js";
592
645
  function withTimeout(promise, ms = <span class="branch-0 cbranch-no" title="branch not covered" >TIMEOUTS.CONTAINER_OP)</span> {
593
646
  return Promise.race([
594
647
  promise,
595
- new Promise((_, reject) =&gt;
648
+ new Promise((_, reject) =&gt;
596
649
  setTimeout(<span class="fstat-no" title="function not covered" >()</span> =&gt; <span class="cstat-no" title="statement not covered" >reject(new Error('Operation timed out')),</span> ms)
597
- )
650
+ ),
598
651
  ]);
599
652
  }
600
653
  &nbsp;
@@ -606,34 +659,17 @@ function withTimeout(promise, ms = <span class="branch-0 cbranch-no" title="bran
606
659
  * @throws {Error} If Docker reports an error
607
660
  */
608
661
  export async function removeContainer(containerId) {
609
- logger.debug("Removing container %s", containerId);
662
+ logger.debug('Removing container %s', containerId);
610
663
  const container = docker.getContainer(containerId);
611
664
  try {
612
665
  await withTimeout(container.remove({ force: true }), TIMEOUTS.CONTAINER_OP);
613
- logger.info("Removed container %s", containerId);
666
+ logger.info('Removed container %s', containerId);
614
667
  } catch (err) {
615
- <span class="cstat-no" title="statement not covered" > logger.error("Failed to remove container %s", containerId, err);</span>
668
+ <span class="cstat-no" title="statement not covered" > logger.error('Failed to remove container %s', containerId, err);</span>
616
669
  <span class="cstat-no" title="statement not covered" > throw new Error('Error removing container: ' + err.message);</span>
617
670
  }
618
671
  }
619
672
  &nbsp;
620
- /**
621
- * Normalize image name to base name (strips registry prefix and tag).
622
- * e.g. "docker.io/library/postgres:16" → "postgres"
623
- *
624
- * @param {string} imageName - Raw image name
625
- * @returns {string} Base name (lowercase)
626
- */
627
- function normalizeImageName(imageName) {
628
- <span class="missing-if-branch" title="if path not taken" >I</span>if (!imageName) <span class="cstat-no" title="statement not covered" >return "";</span>
629
- let name = imageName;
630
- const slashIdx = name.lastIndexOf("/");
631
- <span class="missing-if-branch" title="if path not taken" >I</span>if (slashIdx !== -1) <span class="cstat-no" title="statement not covered" >name = name.slice(slashIdx + 1);</span>
632
- const colonIdx = name.indexOf(":");
633
- <span class="missing-if-branch" title="else path not taken" >E</span>if (colonIdx !== -1) name = name.slice(0, colonIdx);
634
- return name.toLowerCase();
635
- }
636
- &nbsp;
637
673
  /**
638
674
  * Create a new container from an image. If the image is missing locally, it will be pulled.
639
675
  *
@@ -643,22 +679,26 @@ function normalizeImageName(imageName) {
643
679
  * @returns {Promise&lt;{id: string, ports: Array&lt;{containerPort: string, hostPort: string, protocol: string, source: string}&gt;}&gt;}
644
680
  * @throws {Error} If image listing/pull or creation fails
645
681
  */
646
- export async function createContainer(imageName, options = {}, imageProfiles = IMAGE_PROFILES) {
647
- logger.info("Creating container from image %s", imageName);
682
+ export async function createContainer(
683
+ imageName,
684
+ options = {},
685
+ imageProfiles = IMAGE_PROFILES
686
+ ) {
687
+ logger.info('Creating container from image %s', imageName);
648
688
  let exists;
649
689
  try {
650
690
  exists = await withTimeout(imageExists(imageName), 10000);
651
691
  } catch (err) {
652
- <span class="cstat-no" title="statement not covered" > logger.error("Could not check local images for %s", imageName, err);</span>
692
+ <span class="cstat-no" title="statement not covered" > logger.error('Could not check local images for %s', imageName, err);</span>
653
693
  <span class="cstat-no" title="statement not covered" > throw new Error('Error listing local images: ' + err.message);</span>
654
694
  }
655
695
  if (!exists) {
656
- logger.info("Image %s not found locally, pulling", imageName);
696
+ logger.info('Image %s not found locally, pulling', imageName);
657
697
  try {
658
698
  await withTimeout(pullImage(imageName), TIMEOUTS.PULL_IMAGE);
659
- logger.info("Pulled image %s", imageName);
699
+ logger.info('Pulled image %s', imageName);
660
700
  } catch (err) {
661
- <span class="cstat-no" title="statement not covered" > logger.error("Failed to pull image %s", imageName, err);</span>
701
+ <span class="cstat-no" title="statement not covered" > logger.error('Failed to pull image %s', imageName, err);</span>
662
702
  <span class="cstat-no" title="statement not covered" > throw new Error('Could not pull image: ' + err.message);</span>
663
703
  }
664
704
  }
@@ -668,7 +708,9 @@ export async function createContainer(imageName, options = {}, imageProfiles = I
668
708
  ...options,
669
709
  };
670
710
  &nbsp;
671
- const hasUserDefinedPorts = Boolean(createOpts.ExposedPorts &amp;&amp; <span class="branch-1 cbranch-no" title="branch not covered" >Object.keys(createOpts.ExposedPorts).length)</span>;
711
+ const hasUserDefinedPorts = Boolean(
712
+ createOpts.ExposedPorts &amp;&amp; <span class="branch-1 cbranch-no" title="branch not covered" >Object.keys(createOpts.ExposedPorts).length</span>
713
+ );
672
714
  &nbsp;
673
715
  /** @type {Array&lt;{containerPort: string, hostPort: string, protocol: string, source: string}&gt;} */
674
716
  const assignedPorts = [];
@@ -676,11 +718,14 @@ export async function createContainer(imageName, options = {}, imageProfiles = I
676
718
  if (!hasUserDefinedPorts) {
677
719
  try {
678
720
  const image = docker.getImage(imageName);
679
- <span class="missing-if-branch" title="else path not taken" >E</span>if (image &amp;&amp; typeof image.inspect === "function") {
721
+ <span class="missing-if-branch" title="else path not taken" >E</span>if (image &amp;&amp; typeof image.inspect === 'function') {
680
722
  let usedHostPorts = new Set();
681
- <span class="missing-if-branch" title="else path not taken" >E</span>if (typeof docker.listContainers === "function") {
723
+ <span class="missing-if-branch" title="else path not taken" >E</span>if (typeof docker.listContainers === 'function') {
682
724
  try {
683
- const containers = await withTimeout(docker.listContainers({ all: true }), TIMEOUTS.CONTAINER_OP);
725
+ const containers = await withTimeout(
726
+ docker.listContainers({ all: true }),
727
+ TIMEOUTS.CONTAINER_OP
728
+ );
684
729
  containers.forEach((container) =&gt; {
685
730
  (container.Ports || <span class="branch-1 cbranch-no" title="branch not covered" >[])</span>.forEach((portInfo) =&gt; {
686
731
  <span class="missing-if-branch" title="else path not taken" >E</span>if (portInfo &amp;&amp; portInfo.PublicPort) {
@@ -689,7 +734,10 @@ export async function createContainer(imageName, options = {}, imageProfiles = I
689
734
  });
690
735
  });
691
736
  } catch (err) {
692
- <span class="cstat-no" title="statement not covered" > logger.warn("Could not inspect existing containers to auto-map ports", err);</span>
737
+ <span class="cstat-no" title="statement not covered" > logger.warn(</span>
738
+ 'Could not inspect existing containers to auto-map ports',
739
+ err
740
+ );
693
741
  }
694
742
  }
695
743
  const reservePort = (port) =&gt; {
@@ -720,8 +768,14 @@ export async function createContainer(imageName, options = {}, imageProfiles = I
720
768
  return String(candidate);
721
769
  };
722
770
  &nbsp;
723
- const inspectData = await withTimeout(image.inspect(), TIMEOUTS.CONTAINER_OP);
724
- let exposed = inspectData?.Config?.ExposedPorts || <span class="branch-1 cbranch-no" title="branch not covered" >inspectData?.ContainerConfig?.ExposedPorts </span>|| <span class="branch-2 cbranch-no" title="branch not covered" >{};</span>
771
+ const inspectData = await withTimeout(
772
+ image.inspect(),
773
+ TIMEOUTS.CONTAINER_OP
774
+ );
775
+ let exposed =
776
+ inspectData?.Config?.ExposedPorts ||
777
+ <span class="branch-1 cbranch-no" title="branch not covered" > inspectData?.ContainerConfig?.ExposedPorts </span>||
778
+ <span class="branch-2 cbranch-no" title="branch not covered" > {};</span>
725
779
  let exposedKeys = Object.keys(exposed || <span class="branch-1 cbranch-no" title="branch not covered" >{})</span>;
726
780
  &nbsp;
727
781
  // If no ExposedPorts in image, fall back to IMAGE_PROFILES defaultPort
@@ -738,45 +792,69 @@ export async function createContainer(imageName, options = {}, imageProfiles = I
738
792
  if (exposedKeys.length) {
739
793
  createOpts.ExposedPorts = createOpts.ExposedPorts || {};
740
794
  createOpts.HostConfig = { ...(createOpts.HostConfig || {}) };
741
- createOpts.HostConfig.PortBindings = { ...(createOpts.HostConfig.PortBindings || {}) };
795
+ createOpts.HostConfig.PortBindings = {
796
+ ...(createOpts.HostConfig.PortBindings || {}),
797
+ };
742
798
  exposedKeys.forEach((portKey) =&gt; {
743
799
  <span class="missing-if-branch" title="else path not taken" >E</span>if (!createOpts.ExposedPorts[portKey]) {
744
800
  createOpts.ExposedPorts[portKey] = exposed[portKey] || <span class="branch-1 cbranch-no" title="branch not covered" >{};</span>
745
801
  }
746
- <span class="missing-if-branch" title="else path not taken" >E</span>if (!createOpts.HostConfig.PortBindings[portKey] || <span class="branch-1 cbranch-no" title="branch not covered" >!createOpts.HostConfig.PortBindings[portKey].length)</span> {
747
- const parts = portKey.split("/");
802
+ <span class="missing-if-branch" title="else path not taken" >E</span>if (
803
+ !createOpts.HostConfig.PortBindings[portKey] ||
804
+ <span class="branch-1 cbranch-no" title="branch not covered" > !createOpts.HostConfig.PortBindings[portKey].length</span>
805
+ ) {
806
+ const parts = portKey.split('/');
748
807
  const containerPort = parts[0];
749
- const protocol = parts[1] || <span class="branch-1 cbranch-no" title="branch not covered" >"tcp";</span>
808
+ const protocol = parts[1] || <span class="branch-1 cbranch-no" title="branch not covered" >'tcp';</span>
750
809
  const hostPort = pickNextAvailablePort(containerPort);
751
- createOpts.HostConfig.PortBindings[portKey] = [{ HostPort: hostPort }];
752
- assignedPorts.push({ containerPort, hostPort, protocol, source: "auto" });
810
+ createOpts.HostConfig.PortBindings[portKey] = [
811
+ { HostPort: hostPort },
812
+ ];
813
+ assignedPorts.push({
814
+ containerPort,
815
+ hostPort,
816
+ protocol,
817
+ source: 'auto',
818
+ });
753
819
  }
754
820
  });
755
821
  }
756
822
  }
757
823
  } catch (err) {
758
- <span class="cstat-no" title="statement not covered" > logger.warn("Could not inspect image %s for default ports", imageName, err);</span>
824
+ <span class="cstat-no" title="statement not covered" > logger.warn(</span>
825
+ 'Could not inspect image %s for default ports',
826
+ imageName,
827
+ err
828
+ );
759
829
  }
760
830
  } else <span class="missing-if-branch" title="else path not taken" >E</span>{
761
831
  // User-defined ports: collect them with source: "user"
762
832
  const portBindings = <span class="cstat-no" title="statement not covered" >createOpts.HostConfig?.PortBindings || {};</span>
763
833
  <span class="cstat-no" title="statement not covered" > Object.entries(portBindings).forEach(<span class="fstat-no" title="function not covered" >([</span>portKey, bindings]) =&gt; {</span>
764
- const parts = <span class="cstat-no" title="statement not covered" >portKey.split("/");</span>
834
+ const parts = <span class="cstat-no" title="statement not covered" >portKey.split('/');</span>
765
835
  const containerPort = <span class="cstat-no" title="statement not covered" >parts[0];</span>
766
- const protocol = <span class="cstat-no" title="statement not covered" >parts[1] || "tcp";</span>
836
+ const protocol = <span class="cstat-no" title="statement not covered" >parts[1] || 'tcp';</span>
767
837
  <span class="cstat-no" title="statement not covered" > (bindings || []).forEach(<span class="fstat-no" title="function not covered" >(b</span>) =&gt; {</span>
768
- <span class="cstat-no" title="statement not covered" > assignedPorts.push({ containerPort, hostPort: b.HostPort, protocol, source: "user" });</span>
838
+ <span class="cstat-no" title="statement not covered" > assignedPorts.push({</span>
839
+ containerPort,
840
+ hostPort: b.HostPort,
841
+ protocol,
842
+ source: 'user',
843
+ });
769
844
  });
770
845
  });
771
846
  }
772
847
  &nbsp;
773
848
  try {
774
- const container = await withTimeout(docker.createContainer(createOpts), TIMEOUTS.CONTAINER_OP);
849
+ const container = await withTimeout(
850
+ docker.createContainer(createOpts),
851
+ TIMEOUTS.CONTAINER_OP
852
+ );
775
853
  const containerId = container.id || container.Id;
776
- logger.info("Created container %s from image %s", containerId, imageName);
854
+ logger.info('Created container %s from image %s', containerId, imageName);
777
855
  return { id: containerId, ports: assignedPorts };
778
856
  } catch (err) {
779
- <span class="cstat-no" title="statement not covered" > logger.error("Failed to create container from image %s", imageName, err);</span>
857
+ <span class="cstat-no" title="statement not covered" > logger.error('Failed to create container from image %s', imageName, err);</span>
780
858
  <span class="cstat-no" title="statement not covered" > throw new Error('Error creating container: ' + err.message);</span>
781
859
  }
782
860
  }
@@ -788,12 +866,12 @@ export async function createContainer(imageName, options = {}, imageProfiles = I
788
866
  */
789
867
  export async function startContainer(containerId) {
790
868
  const container = docker.getContainer(containerId);
791
- logger.debug("Starting container %s", containerId);
869
+ logger.debug('Starting container %s', containerId);
792
870
  try {
793
871
  await withTimeout(container.start(), TIMEOUTS.CONTAINER_OP);
794
- logger.info("Started container %s", containerId);
872
+ logger.info('Started container %s', containerId);
795
873
  } catch (err) {
796
- <span class="cstat-no" title="statement not covered" > logger.error("Failed to start container %s", containerId, err);</span>
874
+ <span class="cstat-no" title="statement not covered" > logger.error('Failed to start container %s', containerId, err);</span>
797
875
  <span class="cstat-no" title="statement not covered" > throw err;</span>
798
876
  }
799
877
  }
@@ -805,12 +883,12 @@ export async function startContainer(containerId) {
805
883
  */
806
884
  export async function stopContainer(containerId) {
807
885
  const container = docker.getContainer(containerId);
808
- logger.debug("Stopping container %s", containerId);
886
+ logger.debug('Stopping container %s', containerId);
809
887
  try {
810
888
  await withTimeout(container.stop(), TIMEOUTS.CONTAINER_OP);
811
- logger.info("Stopped container %s", containerId);
889
+ logger.info('Stopped container %s', containerId);
812
890
  } catch (err) {
813
- <span class="cstat-no" title="statement not covered" > logger.error("Failed to stop container %s", containerId, err);</span>
891
+ <span class="cstat-no" title="statement not covered" > logger.error('Failed to stop container %s', containerId, err);</span>
814
892
  <span class="cstat-no" title="statement not covered" > throw err;</span>
815
893
  }
816
894
  }
@@ -822,12 +900,12 @@ export async function stopContainer(containerId) {
822
900
  */
823
901
  export async function restartContainer(containerId) {
824
902
  const container = docker.getContainer(containerId);
825
- logger.debug("Restarting container %s", containerId);
903
+ logger.debug('Restarting container %s', containerId);
826
904
  try {
827
905
  await withTimeout(container.restart(), TIMEOUTS.CONTAINER_OP);
828
- logger.info("Restarted container %s", containerId);
906
+ logger.info('Restarted container %s', containerId);
829
907
  } catch (err) {
830
- <span class="cstat-no" title="statement not covered" > logger.error("Failed to restart container %s", containerId, err);</span>
908
+ <span class="cstat-no" title="statement not covered" > logger.error('Failed to restart container %s', containerId, err);</span>
831
909
  <span class="cstat-no" title="statement not covered" > throw err;</span>
832
910
  }
833
911
  }
@@ -838,7 +916,7 @@ export async function restartContainer(containerId) {
838
916
  <div class='footer quiet pad2 space-top1 center small'>
839
917
  Code coverage generated by
840
918
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
841
- at 2026-04-25T17:40:28.828Z
919
+ at 2026-04-27T21:36:40.677Z
842
920
  </div>
843
921
  <script src="../../../prettify.js"></script>
844
922
  <script>