cdd-cli 3.2.0 → 3.2.2

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 (120) 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/CHANGELOG.md +29 -0
  7. package/SECURITY.md +5 -0
  8. package/__mocks__/ink.cjs +17 -0
  9. package/coverage/clover.xml +526 -436
  10. package/coverage/coverage-final.json +19 -15
  11. package/coverage/lcov-report/components/ContainerCreationPrompt.jsx.html +376 -0
  12. package/coverage/lcov-report/components/PromptField.jsx.html +229 -0
  13. package/coverage/lcov-report/components/SuggestionPanel.jsx.html +244 -0
  14. package/coverage/lcov-report/components/index.html +146 -0
  15. package/coverage/lcov-report/helpers/constants.js.html +80 -17
  16. package/coverage/lcov-report/helpers/containerOptionsBuilder.js.html +41 -8
  17. package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerActions.js.html +162 -84
  18. package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerList.js.html +50 -32
  19. package/coverage/lcov-report/helpers/dockerService/serviceComponents/index.html +21 -21
  20. package/coverage/lcov-report/helpers/imageNameUtils.js.html +181 -0
  21. package/coverage/lcov-report/helpers/index.html +27 -12
  22. package/coverage/lcov-report/helpers/logger.js.html +19 -19
  23. package/coverage/lcov-report/helpers/safeCall.js.html +3 -3
  24. package/coverage/lcov-report/helpers/validationHelpers.js.html +28 -76
  25. package/coverage/lcov-report/hooks/creation/index.html +20 -20
  26. package/coverage/lcov-report/hooks/creation/useContainerActions.js.html +38 -17
  27. package/coverage/lcov-report/hooks/creation/useContainerCreation.js.html +416 -89
  28. package/coverage/lcov-report/hooks/creation/useLogsViewer.js.html +7 -7
  29. package/coverage/lcov-report/hooks/debug/index.html +5 -5
  30. package/coverage/lcov-report/hooks/debug/useDebugLogs.js.html +35 -20
  31. package/coverage/lcov-report/hooks/index.html +40 -40
  32. package/coverage/lcov-report/hooks/navigation/index.html +19 -19
  33. package/coverage/lcov-report/hooks/navigation/useContainerSelection.js.html +38 -29
  34. package/coverage/lcov-report/hooks/useContainerCommandRouter.js.html +119 -80
  35. package/coverage/lcov-report/hooks/useControls.js.html +263 -122
  36. package/coverage/lcov-report/hooks/useEraseConfirmation.js.html +8 -8
  37. package/coverage/lcov-report/hooks/useExitHandler.js.html +50 -41
  38. package/coverage/lcov-report/index.html +67 -52
  39. package/coverage/lcov.info +991 -805
  40. package/dist/App.js +11 -11
  41. package/dist/components/ContainerCreationPrompt.js +31 -10
  42. package/dist/components/ContainerList.js +3 -3
  43. package/dist/components/ContainerRow.js +20 -20
  44. package/dist/components/ContainerSection.js +3 -3
  45. package/dist/components/Footer.js +2 -2
  46. package/dist/components/Header.js +7 -7
  47. package/dist/components/LogViewer.js +3 -3
  48. package/dist/components/MessageFeedback.js +2 -2
  49. package/dist/components/PromptField.js +4 -4
  50. package/dist/components/StatsBar.js +5 -5
  51. package/dist/components/SuggestionPanel.js +44 -0
  52. package/dist/components/UsageMenu.js +3 -3
  53. package/dist/helpers/appInfo.js +7 -7
  54. package/dist/helpers/constants.js +114 -0
  55. package/dist/helpers/containerOptionsBuilder.js +3 -3
  56. package/dist/helpers/dockerService/dockerService.js +1 -1
  57. package/dist/helpers/dockerService/serviceComponents/containerActions.js +99 -40
  58. package/dist/helpers/dockerService/serviceComponents/containerList.js +11 -11
  59. package/dist/helpers/dockerService/serviceComponents/containerLogs.js +9 -9
  60. package/dist/helpers/dockerService/serviceComponents/containerStats.js +5 -5
  61. package/dist/helpers/dockerService/serviceComponents/imageUtils.js +1 -1
  62. package/dist/helpers/exitWithMessage.js +10 -10
  63. package/dist/helpers/imageNameUtils.js +32 -0
  64. package/dist/helpers/logger.js +13 -13
  65. package/dist/helpers/safeCall.js +2 -2
  66. package/dist/helpers/validationHelpers.js +76 -21
  67. package/dist/hooks/creation/useContainerActions.js +9 -9
  68. package/dist/hooks/creation/useContainerCreation.js +138 -36
  69. package/dist/hooks/creation/useLogsViewer.js +2 -2
  70. package/dist/hooks/debug/useDebugLogs.js +4 -4
  71. package/dist/hooks/useContainerCommandRouter.js +13 -13
  72. package/dist/hooks/useContainers.js +3 -3
  73. package/dist/hooks/useControls.js +66 -35
  74. package/dist/hooks/useEraseConfirmation.js +3 -3
  75. package/dist/index.js +2 -2
  76. package/jest.config.cjs +7 -0
  77. package/package.json +7 -3
  78. package/src/App.jsx +33 -16
  79. package/src/components/ContainerCreationPrompt.jsx +43 -9
  80. package/src/components/ContainerList.jsx +3 -3
  81. package/src/components/ContainerRow.jsx +19 -16
  82. package/src/components/ContainerSection.jsx +3 -3
  83. package/src/components/Footer.jsx +2 -2
  84. package/src/components/Header.jsx +7 -7
  85. package/src/components/LogViewer.jsx +5 -3
  86. package/src/components/MessageFeedback.jsx +2 -2
  87. package/src/components/PromptField.jsx +4 -4
  88. package/src/components/StatsBar.jsx +11 -7
  89. package/src/components/SuggestionPanel.jsx +53 -0
  90. package/src/components/UsageMenu.jsx +18 -13
  91. package/src/helpers/appInfo.js +7 -7
  92. package/src/helpers/constants.js +30 -9
  93. package/src/helpers/containerOptionsBuilder.js +16 -5
  94. package/src/helpers/dockerService/dockerService.js +1 -1
  95. package/src/helpers/dockerService/serviceComponents/containerActions.js +87 -61
  96. package/src/helpers/dockerService/serviceComponents/containerList.js +31 -25
  97. package/src/helpers/dockerService/serviceComponents/containerLogs.js +41 -26
  98. package/src/helpers/dockerService/serviceComponents/containerStats.js +13 -11
  99. package/src/helpers/dockerService/serviceComponents/imageUtils.js +5 -4
  100. package/src/helpers/exitWithMessage.js +13 -7
  101. package/src/helpers/imageNameUtils.js +32 -0
  102. package/src/helpers/logger.js +15 -15
  103. package/src/helpers/safeCall.js +2 -2
  104. package/src/helpers/validationHelpers.js +13 -29
  105. package/src/hooks/creation/useContainerActions.js +19 -12
  106. package/src/hooks/creation/useContainerCreation.js +150 -41
  107. package/src/hooks/creation/useLogsViewer.js +2 -2
  108. package/src/hooks/debug/useDebugLogs.js +15 -10
  109. package/src/hooks/navigation/useContainerSelection.js +17 -14
  110. package/src/hooks/useContainerCommandRouter.js +71 -58
  111. package/src/hooks/useContainers.js +4 -4
  112. package/src/hooks/useControls.js +115 -68
  113. package/src/hooks/useEraseConfirmation.js +3 -3
  114. package/src/hooks/useExitHandler.js +29 -26
  115. package/src/index.js +4 -4
  116. package/test/ContainerCreationPrompt.dom.test.js +68 -0
  117. package/test/SuggestionPanel.dom.test.js +53 -0
  118. package/test/constants.test.js +34 -0
  119. package/test/useContainerCreation.dom.test.js +123 -0
  120. package/test/useControls.dom.test.js +198 -2
@@ -44,9 +44,9 @@
44
44
 
45
45
 
46
46
  <div class='fl pad1y space-right2'>
47
- <span class="strong">74.07% </span>
47
+ <span class="strong">68.96% </span>
48
48
  <span class="quiet">Lines</span>
49
- <span class='fraction'>20/27</span>
49
+ <span class='fraction'>20/29</span>
50
50
  </div>
51
51
 
52
52
 
@@ -131,7 +131,13 @@
131
131
  <a name='L66'></a><a href='#L66'>66</a>
132
132
  <a name='L67'></a><a href='#L67'>67</a>
133
133
  <a name='L68'></a><a href='#L68'>68</a>
134
- <a name='L69'></a><a href='#L69'>69</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
134
+ <a name='L69'></a><a href='#L69'>69</a>
135
+ <a name='L70'></a><a href='#L70'>70</a>
136
+ <a name='L71'></a><a href='#L71'>71</a>
137
+ <a name='L72'></a><a href='#L72'>72</a>
138
+ <a name='L73'></a><a href='#L73'>73</a>
139
+ <a name='L74'></a><a href='#L74'>74</a>
140
+ <a name='L75'></a><a href='#L75'>75</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
135
141
  <span class="cline-any cline-neutral">&nbsp;</span>
136
142
  <span class="cline-any cline-neutral">&nbsp;</span>
137
143
  <span class="cline-any cline-yes">3x</span>
@@ -147,10 +153,17 @@
147
153
  <span class="cline-any cline-neutral">&nbsp;</span>
148
154
  <span class="cline-any cline-neutral">&nbsp;</span>
149
155
  <span class="cline-any cline-yes">3x</span>
156
+ <span class="cline-any cline-neutral">&nbsp;</span>
157
+ <span class="cline-any cline-neutral">&nbsp;</span>
158
+ <span class="cline-any cline-neutral">&nbsp;</span>
159
+ <span class="cline-any cline-neutral">&nbsp;</span>
150
160
  <span class="cline-any cline-yes">1x</span>
151
161
  <span class="cline-any cline-yes">2x</span>
152
162
  <span class="cline-any cline-no">&nbsp;</span>
153
163
  <span class="cline-any cline-yes">2x</span>
164
+ <span class="cline-any cline-neutral">&nbsp;</span>
165
+ <span class="cline-any cline-neutral">&nbsp;</span>
166
+ <span class="cline-any cline-neutral">&nbsp;</span>
154
167
  <span class="cline-any cline-yes">1x</span>
155
168
  <span class="cline-any cline-neutral">&nbsp;</span>
156
169
  <span class="cline-any cline-yes">1x</span>
@@ -180,17 +193,16 @@
180
193
  <span class="cline-any cline-neutral">&nbsp;</span>
181
194
  <span class="cline-any cline-neutral">&nbsp;</span>
182
195
  <span class="cline-any cline-neutral">&nbsp;</span>
183
- <span class="cline-any cline-neutral">&nbsp;</span>
184
196
  <span class="cline-any cline-yes">3x</span>
185
197
  <span class="cline-any cline-no">&nbsp;</span>
186
- <span class="cline-any cline-neutral">&nbsp;</span>
198
+ <span class="cline-any cline-no">&nbsp;</span>
187
199
  <span class="cline-any cline-yes">3x</span>
188
200
  <span class="cline-any cline-no">&nbsp;</span>
189
201
  <span class="cline-any cline-neutral">&nbsp;</span>
190
202
  <span class="cline-any cline-neutral">&nbsp;</span>
191
203
  <span class="cline-any cline-yes">3x</span>
192
204
  <span class="cline-any cline-no">&nbsp;</span>
193
- <span class="cline-any cline-neutral">&nbsp;</span>
205
+ <span class="cline-any cline-no">&nbsp;</span>
194
206
  <span class="cline-any cline-yes">3x</span>
195
207
  <span class="cline-any cline-neutral">&nbsp;</span>
196
208
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -199,10 +211,10 @@
199
211
  <span class="cline-any cline-no">&nbsp;</span>
200
212
  <span class="cline-any cline-neutral">&nbsp;</span>
201
213
  <span class="cline-any cline-neutral">&nbsp;</span>
202
- <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import { docker } from "../dockerService.js";
203
- import { logger } from "../../logger.js";
214
+ <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import { docker } from '../dockerService.js';
215
+ import { logger } from '../../logger.js';
204
216
  &nbsp;
205
- const DEFAULT_CONTAINER_NAME = "Unknown";
217
+ const DEFAULT_CONTAINER_NAME = 'Unknown';
206
218
  &nbsp;
207
219
  /**
208
220
  * Produce a safe, display-friendly container name from the raw Docker payload.
@@ -214,21 +226,28 @@ function normalizeContainerName(container) {
214
226
  const names = container?.Names;
215
227
  &nbsp;
216
228
  let rawName;
217
- if (Array.isArray(names) &amp;&amp; names.length &gt; 0 &amp;&amp; typeof names[0] === "string") {
229
+ if (
230
+ Array.isArray(names) &amp;&amp;
231
+ names.length &gt; 0 &amp;&amp;
232
+ typeof names[0] === 'string'
233
+ ) {
218
234
  rawName = names[0];
219
- } else <span class="missing-if-branch" title="if path not taken" >I</span>if (typeof names === "string" &amp;&amp; <span class="branch-1 cbranch-no" title="branch not covered" >names.trim().length &gt; 0)</span> {
235
+ } else <span class="missing-if-branch" title="if path not taken" >I</span>if (typeof names === 'string' &amp;&amp; <span class="branch-1 cbranch-no" title="branch not covered" >names.trim().length &gt; 0)</span> {
220
236
  <span class="cstat-no" title="statement not covered" > rawName = names;</span>
221
- } else if (typeof container?.Name === "string" &amp;&amp; container.Name.trim().length &gt; 0) {
237
+ } else if (
238
+ typeof container?.Name === 'string' &amp;&amp;
239
+ container.Name.trim().length &gt; 0
240
+ ) {
222
241
  rawName = container.Name;
223
242
  } else {
224
243
  rawName = DEFAULT_CONTAINER_NAME;
225
244
  }
226
245
  &nbsp;
227
- <span class="missing-if-branch" title="if path not taken" >I</span>if (typeof rawName !== "string") {
246
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (typeof rawName !== 'string') {
228
247
  <span class="cstat-no" title="statement not covered" > return DEFAULT_CONTAINER_NAME;</span>
229
248
  }
230
249
  &nbsp;
231
- const cleanedName = rawName.replace(/^\/+/u, "").trim();
250
+ const cleanedName = rawName.replace(/^\/+/u, '').trim();
232
251
  return cleanedName.length &gt; 0 ? cleanedName : <span class="branch-1 cbranch-no" title="branch not covered" >DEFAULT_CONTAINER_NAME;</span>
233
252
  }
234
253
  &nbsp;
@@ -237,33 +256,32 @@ function normalizeContainerName(container) {
237
256
  * @returns {Promise&lt;Array&lt;Object&gt;&gt;}
238
257
  */
239
258
  export async function getContainers() {
240
- logger.debug("Listing containers");
259
+ logger.debug('Listing containers');
241
260
  try {
242
261
  const containers = await docker.listContainers({ all: true });
243
- logger.debug("Retrieved %d containers", containers.length);
262
+ logger.debug('Retrieved %d containers', containers.length);
244
263
  return containers.map((container) =&gt; ({
245
264
  id: container.Id,
246
265
  name: normalizeContainerName(container),
247
266
  image: container.Image,
248
267
  state: container.State,
249
268
  status: container.Status,
250
- ports:
251
- (() =&gt; {
252
- const publicPorts = container.Ports.filter(<span class="fstat-no" title="function not covered" >(p</span>ort) =&gt; <span class="cstat-no" title="statement not covered" >port.PublicPort)</span>.map(
253
- <span class="fstat-no" title="function not covered" > (p</span>ort) =&gt; <span class="cstat-no" title="statement not covered" >`${port.PublicPort}:${port.PrivatePort}`</span>
254
- );
255
- <span class="missing-if-branch" title="if path not taken" >I</span>if (publicPorts.length &gt; 0) {
256
- <span class="cstat-no" title="statement not covered" > return [...new Set(publicPorts)];</span>
257
- }
258
- // Fall back to exposed private ports when there are no bindings.
259
- const privatePorts = container.Ports.filter(<span class="fstat-no" title="function not covered" >(p</span>ort) =&gt; <span class="cstat-no" title="statement not covered" >port.PrivatePort)</span>.map(
260
- <span class="fstat-no" title="function not covered" > (p</span>ort) =&gt; <span class="cstat-no" title="statement not covered" >`${port.PrivatePort}`</span>
261
- );
262
- return [...new Set(privatePorts)];
263
- })()
269
+ ports: (() =&gt; {
270
+ const publicPorts = container.Ports.filter(
271
+ <span class="fstat-no" title="function not covered" > (p</span>ort) =&gt; <span class="cstat-no" title="statement not covered" >port.PublicPort</span>
272
+ ).map(<span class="fstat-no" title="function not covered" >(p</span>ort) =&gt; <span class="cstat-no" title="statement not covered" >`${port.PublicPort}:${port.PrivatePort}`)</span>;
273
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (publicPorts.length &gt; 0) {
274
+ <span class="cstat-no" title="statement not covered" > return [...new Set(publicPorts)];</span>
275
+ }
276
+ // Fall back to exposed private ports when there are no bindings.
277
+ const privatePorts = container.Ports.filter(
278
+ <span class="fstat-no" title="function not covered" > (p</span>ort) =&gt; <span class="cstat-no" title="statement not covered" >port.PrivatePort</span>
279
+ ).map(<span class="fstat-no" title="function not covered" >(p</span>ort) =&gt; <span class="cstat-no" title="statement not covered" >`${port.PrivatePort}`)</span>;
280
+ return [...new Set(privatePorts)];
281
+ })(),
264
282
  }));
265
283
  } catch (err) {
266
- <span class="cstat-no" title="statement not covered" > logger.error("Failed to list containers", err);</span>
284
+ <span class="cstat-no" title="statement not covered" > logger.error('Failed to list containers', err);</span>
267
285
  <span class="cstat-no" title="statement not covered" > throw err;</span>
268
286
  }
269
287
  }
@@ -274,7 +292,7 @@ export async function getContainers() {
274
292
  <div class='footer quiet pad2 space-top1 center small'>
275
293
  Code coverage generated by
276
294
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
277
- at 2026-04-25T17:40:28.828Z
295
+ at 2026-04-27T20:17:25.625Z
278
296
  </div>
279
297
  <script src="../../../prettify.js"></script>
280
298
  <script>
@@ -23,30 +23,30 @@
23
23
  <div class='clearfix'>
24
24
 
25
25
  <div class='fl pad1y space-right2'>
26
- <span class="strong">71.69% </span>
26
+ <span class="strong">71.14% </span>
27
27
  <span class="quiet">Statements</span>
28
- <span class='fraction'>114/159</span>
28
+ <span class='fraction'>106/149</span>
29
29
  </div>
30
30
 
31
31
 
32
32
  <div class='fl pad1y space-right2'>
33
- <span class="strong">63.21% </span>
33
+ <span class="strong">64.19% </span>
34
34
  <span class="quiet">Branches</span>
35
- <span class='fraction'>55/87</span>
35
+ <span class='fraction'>52/81</span>
36
36
  </div>
37
37
 
38
38
 
39
39
  <div class='fl pad1y space-right2'>
40
- <span class="strong">70.83% </span>
40
+ <span class="strong">69.56% </span>
41
41
  <span class="quiet">Functions</span>
42
- <span class='fraction'>17/24</span>
42
+ <span class='fraction'>16/23</span>
43
43
  </div>
44
44
 
45
45
 
46
46
  <div class='fl pad1y space-right2'>
47
- <span class="strong">73.68% </span>
47
+ <span class="strong">71.42% </span>
48
48
  <span class="quiet">Lines</span>
49
- <span class='fraction'>112/152</span>
49
+ <span class='fraction'>105/147</span>
50
50
  </div>
51
51
 
52
52
 
@@ -80,17 +80,17 @@
80
80
  </thead>
81
81
  <tbody><tr>
82
82
  <td class="file medium" data-value="containerActions.js"><a href="containerActions.js.html">containerActions.js</a></td>
83
- <td data-value="72.09" class="pic medium">
84
- <div class="chart"><div class="cover-fill" style="width: 72%"></div><div class="cover-empty" style="width: 28%"></div></div>
83
+ <td data-value="71.42" class="pic medium">
84
+ <div class="chart"><div class="cover-fill" style="width: 71%"></div><div class="cover-empty" style="width: 29%"></div></div>
85
85
  </td>
86
- <td data-value="72.09" class="pct medium">72.09%</td>
87
- <td data-value="129" class="abs medium">93/129</td>
88
- <td data-value="60.29" class="pct medium">60.29%</td>
89
- <td data-value="68" class="abs medium">41/68</td>
90
- <td data-value="81.25" class="pct high">81.25%</td>
91
- <td data-value="16" class="abs high">13/16</td>
92
- <td data-value="73.6" class="pct medium">73.6%</td>
93
- <td data-value="125" class="abs medium">92/125</td>
86
+ <td data-value="71.42" class="pct medium">71.42%</td>
87
+ <td data-value="119" class="abs medium">85/119</td>
88
+ <td data-value="61.29" class="pct medium">61.29%</td>
89
+ <td data-value="62" class="abs medium">38/62</td>
90
+ <td data-value="80" class="pct high">80%</td>
91
+ <td data-value="15" class="abs high">12/15</td>
92
+ <td data-value="72.03" class="pct medium">72.03%</td>
93
+ <td data-value="118" class="abs medium">85/118</td>
94
94
  </tr>
95
95
 
96
96
  <tr>
@@ -104,8 +104,8 @@
104
104
  <td data-value="19" class="abs medium">14/19</td>
105
105
  <td data-value="50" class="pct medium">50%</td>
106
106
  <td data-value="8" class="abs medium">4/8</td>
107
- <td data-value="74.07" class="pct medium">74.07%</td>
108
- <td data-value="27" class="abs medium">20/27</td>
107
+ <td data-value="68.96" class="pct medium">68.96%</td>
108
+ <td data-value="29" class="abs medium">20/29</td>
109
109
  </tr>
110
110
 
111
111
  </tbody>
@@ -116,7 +116,7 @@
116
116
  <div class='footer quiet pad2 space-top1 center small'>
117
117
  Code coverage generated by
118
118
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
119
- at 2026-04-25T17:40:28.828Z
119
+ at 2026-04-27T20:17:25.625Z
120
120
  </div>
121
121
  <script src="../../../prettify.js"></script>
122
122
  <script>
@@ -0,0 +1,181 @@
1
+
2
+ <!doctype html>
3
+ <html lang="en">
4
+
5
+ <head>
6
+ <title>Code coverage report for helpers/imageNameUtils.js</title>
7
+ <meta charset="utf-8" />
8
+ <link rel="stylesheet" href="../prettify.css" />
9
+ <link rel="stylesheet" href="../base.css" />
10
+ <link rel="shortcut icon" type="image/x-icon" href="../favicon.png" />
11
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
12
+ <style type='text/css'>
13
+ .coverage-summary .sorter {
14
+ background-image: url(../sort-arrow-sprite.png);
15
+ }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <div class='wrapper'>
21
+ <div class='pad1'>
22
+ <h1><a href="../index.html">All files</a> / <a href="index.html">helpers</a> imageNameUtils.js</h1>
23
+ <div class='clearfix'>
24
+
25
+ <div class='fl pad1y space-right2'>
26
+ <span class="strong">90% </span>
27
+ <span class="quiet">Statements</span>
28
+ <span class='fraction'>9/10</span>
29
+ </div>
30
+
31
+
32
+ <div class='fl pad1y space-right2'>
33
+ <span class="strong">83.33% </span>
34
+ <span class="quiet">Branches</span>
35
+ <span class='fraction'>5/6</span>
36
+ </div>
37
+
38
+
39
+ <div class='fl pad1y space-right2'>
40
+ <span class="strong">100% </span>
41
+ <span class="quiet">Functions</span>
42
+ <span class='fraction'>1/1</span>
43
+ </div>
44
+
45
+
46
+ <div class='fl pad1y space-right2'>
47
+ <span class="strong">100% </span>
48
+ <span class="quiet">Lines</span>
49
+ <span class='fraction'>9/9</span>
50
+ </div>
51
+
52
+
53
+ </div>
54
+ <p class="quiet">
55
+ Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
56
+ </p>
57
+ <template id="filterTemplate">
58
+ <div class="quiet">
59
+ Filter:
60
+ <input type="search" id="fileSearch">
61
+ </div>
62
+ </template>
63
+ </div>
64
+ <div class='status-line high'></div>
65
+ <pre><table class="coverage">
66
+ <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
67
+ <a name='L2'></a><a href='#L2'>2</a>
68
+ <a name='L3'></a><a href='#L3'>3</a>
69
+ <a name='L4'></a><a href='#L4'>4</a>
70
+ <a name='L5'></a><a href='#L5'>5</a>
71
+ <a name='L6'></a><a href='#L6'>6</a>
72
+ <a name='L7'></a><a href='#L7'>7</a>
73
+ <a name='L8'></a><a href='#L8'>8</a>
74
+ <a name='L9'></a><a href='#L9'>9</a>
75
+ <a name='L10'></a><a href='#L10'>10</a>
76
+ <a name='L11'></a><a href='#L11'>11</a>
77
+ <a name='L12'></a><a href='#L12'>12</a>
78
+ <a name='L13'></a><a href='#L13'>13</a>
79
+ <a name='L14'></a><a href='#L14'>14</a>
80
+ <a name='L15'></a><a href='#L15'>15</a>
81
+ <a name='L16'></a><a href='#L16'>16</a>
82
+ <a name='L17'></a><a href='#L17'>17</a>
83
+ <a name='L18'></a><a href='#L18'>18</a>
84
+ <a name='L19'></a><a href='#L19'>19</a>
85
+ <a name='L20'></a><a href='#L20'>20</a>
86
+ <a name='L21'></a><a href='#L21'>21</a>
87
+ <a name='L22'></a><a href='#L22'>22</a>
88
+ <a name='L23'></a><a href='#L23'>23</a>
89
+ <a name='L24'></a><a href='#L24'>24</a>
90
+ <a name='L25'></a><a href='#L25'>25</a>
91
+ <a name='L26'></a><a href='#L26'>26</a>
92
+ <a name='L27'></a><a href='#L27'>27</a>
93
+ <a name='L28'></a><a href='#L28'>28</a>
94
+ <a name='L29'></a><a href='#L29'>29</a>
95
+ <a name='L30'></a><a href='#L30'>30</a>
96
+ <a name='L31'></a><a href='#L31'>31</a>
97
+ <a name='L32'></a><a href='#L32'>32</a>
98
+ <a name='L33'></a><a href='#L33'>33</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
99
+ <span class="cline-any cline-neutral">&nbsp;</span>
100
+ <span class="cline-any cline-neutral">&nbsp;</span>
101
+ <span class="cline-any cline-neutral">&nbsp;</span>
102
+ <span class="cline-any cline-neutral">&nbsp;</span>
103
+ <span class="cline-any cline-neutral">&nbsp;</span>
104
+ <span class="cline-any cline-neutral">&nbsp;</span>
105
+ <span class="cline-any cline-neutral">&nbsp;</span>
106
+ <span class="cline-any cline-neutral">&nbsp;</span>
107
+ <span class="cline-any cline-neutral">&nbsp;</span>
108
+ <span class="cline-any cline-neutral">&nbsp;</span>
109
+ <span class="cline-any cline-neutral">&nbsp;</span>
110
+ <span class="cline-any cline-neutral">&nbsp;</span>
111
+ <span class="cline-any cline-neutral">&nbsp;</span>
112
+ <span class="cline-any cline-neutral">&nbsp;</span>
113
+ <span class="cline-any cline-neutral">&nbsp;</span>
114
+ <span class="cline-any cline-neutral">&nbsp;</span>
115
+ <span class="cline-any cline-neutral">&nbsp;</span>
116
+ <span class="cline-any cline-yes">18x</span>
117
+ <span class="cline-any cline-neutral">&nbsp;</span>
118
+ <span class="cline-any cline-yes">18x</span>
119
+ <span class="cline-any cline-yes">18x</span>
120
+ <span class="cline-any cline-yes">18x</span>
121
+ <span class="cline-any cline-yes">2x</span>
122
+ <span class="cline-any cline-neutral">&nbsp;</span>
123
+ <span class="cline-any cline-neutral">&nbsp;</span>
124
+ <span class="cline-any cline-yes">18x</span>
125
+ <span class="cline-any cline-yes">18x</span>
126
+ <span class="cline-any cline-yes">9x</span>
127
+ <span class="cline-any cline-neutral">&nbsp;</span>
128
+ <span class="cline-any cline-yes">18x</span>
129
+ <span class="cline-any cline-neutral">&nbsp;</span>
130
+ <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">/**
131
+ * Utility for normalizing Docker image names.
132
+ * Extracted from validationHelpers.js and containerActions.js — both had
133
+ * identical implementations; consolidated here to avoid duplication.
134
+ */
135
+ &nbsp;
136
+ /**
137
+ * Normalize an image name to its base name (strips registry prefix and tag).
138
+ *
139
+ * Examples:
140
+ * "docker.io/library/postgres:16-alpine" → "postgres"
141
+ * "nginx:alpine" → "nginx"
142
+ * "myregistry.io/myorg/myapp:latest" → "myapp"
143
+ *
144
+ * @param {string} imageName - Raw image name (from user input or Docker API)
145
+ * @returns {string} Base image name in lowercase, empty string if input is falsy
146
+ */
147
+ export function normalizeImageName(imageName) {
148
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (!imageName) <span class="cstat-no" title="statement not covered" >return '';</span>
149
+ // Remove registry prefix (everything up to and including the last '/')
150
+ let name = imageName;
151
+ const slashIdx = name.lastIndexOf('/');
152
+ if (slashIdx !== -1) {
153
+ name = name.slice(slashIdx + 1);
154
+ }
155
+ // Remove tag (everything from the first ':' onward)
156
+ const colonIdx = name.indexOf(':');
157
+ if (colonIdx !== -1) {
158
+ name = name.slice(0, colonIdx);
159
+ }
160
+ return name.toLowerCase();
161
+ }
162
+ &nbsp;</pre></td></tr></table></pre>
163
+
164
+ <div class='push'></div><!-- for sticky footer -->
165
+ </div><!-- /wrapper -->
166
+ <div class='footer quiet pad2 space-top1 center small'>
167
+ Code coverage generated by
168
+ <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
169
+ at 2026-04-27T20:17:25.625Z
170
+ </div>
171
+ <script src="../prettify.js"></script>
172
+ <script>
173
+ window.onload = function () {
174
+ prettyPrint();
175
+ };
176
+ </script>
177
+ <script src="../sorter.js"></script>
178
+ <script src="../block-navigation.js"></script>
179
+ </body>
180
+ </html>
181
+
@@ -44,9 +44,9 @@
44
44
 
45
45
 
46
46
  <div class='fl pad1y space-right2'>
47
- <span class="strong">91.42% </span>
47
+ <span class="strong">91.74% </span>
48
48
  <span class="quiet">Lines</span>
49
- <span class='fraction'>96/105</span>
49
+ <span class='fraction'>100/109</span>
50
50
  </div>
51
51
 
52
52
 
@@ -105,7 +105,22 @@
105
105
  <td data-value="100" class="pct high">100%</td>
106
106
  <td data-value="4" class="abs high">4/4</td>
107
107
  <td data-value="100" class="pct high">100%</td>
108
- <td data-value="17" class="abs high">17/17</td>
108
+ <td data-value="19" class="abs high">19/19</td>
109
+ </tr>
110
+
111
+ <tr>
112
+ <td class="file high" data-value="imageNameUtils.js"><a href="imageNameUtils.js.html">imageNameUtils.js</a></td>
113
+ <td data-value="90" class="pic high">
114
+ <div class="chart"><div class="cover-fill" style="width: 90%"></div><div class="cover-empty" style="width: 10%"></div></div>
115
+ </td>
116
+ <td data-value="90" class="pct high">90%</td>
117
+ <td data-value="10" class="abs high">9/10</td>
118
+ <td data-value="83.33" class="pct high">83.33%</td>
119
+ <td data-value="6" class="abs high">5/6</td>
120
+ <td data-value="100" class="pct high">100%</td>
121
+ <td data-value="1" class="abs high">1/1</td>
122
+ <td data-value="100" class="pct high">100%</td>
123
+ <td data-value="9" class="abs high">9/9</td>
109
124
  </tr>
110
125
 
111
126
  <tr>
@@ -140,17 +155,17 @@
140
155
 
141
156
  <tr>
142
157
  <td class="file high" data-value="validationHelpers.js"><a href="validationHelpers.js.html">validationHelpers.js</a></td>
143
- <td data-value="95.74" class="pic high">
144
- <div class="chart"><div class="cover-fill" style="width: 95%"></div><div class="cover-empty" style="width: 5%"></div></div>
158
+ <td data-value="97.29" class="pic high">
159
+ <div class="chart"><div class="cover-fill" style="width: 97%"></div><div class="cover-empty" style="width: 3%"></div></div>
145
160
  </td>
146
- <td data-value="95.74" class="pct high">95.74%</td>
147
- <td data-value="47" class="abs high">45/47</td>
148
- <td data-value="94.59" class="pct high">94.59%</td>
149
- <td data-value="37" class="abs high">35/37</td>
161
+ <td data-value="97.29" class="pct high">97.29%</td>
162
+ <td data-value="37" class="abs high">36/37</td>
163
+ <td data-value="96.77" class="pct high">96.77%</td>
164
+ <td data-value="31" class="abs high">30/31</td>
150
165
  <td data-value="100" class="pct high">100%</td>
151
- <td data-value="6" class="abs high">6/6</td>
166
+ <td data-value="5" class="abs high">5/5</td>
152
167
  <td data-value="100" class="pct high">100%</td>
153
- <td data-value="42" class="abs high">42/42</td>
168
+ <td data-value="35" class="abs high">35/35</td>
154
169
  </tr>
155
170
 
156
171
  </tbody>
@@ -161,7 +176,7 @@
161
176
  <div class='footer quiet pad2 space-top1 center small'>
162
177
  Code coverage generated by
163
178
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
164
- at 2026-04-25T17:40:28.828Z
179
+ at 2026-04-27T20:17:25.625Z
165
180
  </div>
166
181
  <script src="../prettify.js"></script>
167
182
  <script>
@@ -282,11 +282,11 @@
282
282
  <span class="cline-any cline-neutral">&nbsp;</span>
283
283
  <span class="cline-any cline-neutral">&nbsp;</span>
284
284
  <span class="cline-any cline-neutral">&nbsp;</span>
285
- <span class="cline-any cline-yes">4x</span>
285
+ <span class="cline-any cline-yes">16x</span>
286
286
  <span class="cline-any cline-no">&nbsp;</span>
287
287
  <span class="cline-any cline-neutral">&nbsp;</span>
288
- <span class="cline-any cline-yes">4x</span>
289
- <span class="cline-any cline-yes">4x</span>
288
+ <span class="cline-any cline-yes">16x</span>
289
+ <span class="cline-any cline-yes">16x</span>
290
290
  <span class="cline-any cline-neutral">&nbsp;</span>
291
291
  <span class="cline-any cline-neutral">&nbsp;</span>
292
292
  <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">/**
@@ -297,7 +297,7 @@ const LOG_LEVELS = {
297
297
  error: 0,
298
298
  warn: 1,
299
299
  info: 2,
300
- debug: 3
300
+ debug: 3,
301
301
  };
302
302
  &nbsp;
303
303
  /**
@@ -315,7 +315,7 @@ const LOG_LEVELS = {
315
315
  */
316
316
  &nbsp;
317
317
  function parseLevel(value) {
318
- <span class="missing-if-branch" title="else path not taken" >E</span>if (typeof value !== "string") {
318
+ <span class="missing-if-branch" title="else path not taken" >E</span>if (typeof value !== 'string') {
319
319
  return null;
320
320
  }
321
321
  const normalized = <span class="cstat-no" title="statement not covered" >value.trim().toLowerCase();</span>
@@ -330,13 +330,13 @@ const envLevel =
330
330
  parseLevel(process.env.LOG_LEVEL) ??
331
331
  LOG_LEVELS.info;
332
332
  &nbsp;
333
- const currentLevel = typeof envLevel === "number" ? envLevel : <span class="branch-1 cbranch-no" title="branch not covered" >LOG_LEVELS.info;</span>
333
+ const currentLevel = typeof envLevel === 'number' ? envLevel : <span class="branch-1 cbranch-no" title="branch not covered" >LOG_LEVELS.info;</span>
334
334
  &nbsp;
335
335
  const listeners = new Set();
336
336
  &nbsp;
337
337
  function shouldLog(level) {
338
338
  const target = LOG_LEVELS[level];
339
- return typeof target === "number" &amp;&amp; target &lt;= currentLevel;
339
+ return typeof target === 'number' &amp;&amp; target &lt;= currentLevel;
340
340
  }
341
341
  &nbsp;
342
342
  function log(level, message, ...args) {
@@ -350,11 +350,11 @@ function log(level, message, ...args) {
350
350
  const entry = { level, message, args, timestamp, formatted };
351
351
  &nbsp;
352
352
  const shouldWriteToConsole =
353
- listeners.size === 0 || <span class="branch-1 cbranch-no" title="branch not covered" >level === "error" </span>|| <span class="branch-2 cbranch-no" title="branch not covered" >level === "warn";</span>
353
+ listeners.size === 0 || <span class="branch-1 cbranch-no" title="branch not covered" >level === 'error' </span>|| <span class="branch-2 cbranch-no" title="branch not covered" >level === 'warn';</span>
354
354
  &nbsp;
355
355
  <span class="missing-if-branch" title="else path not taken" >E</span>if (shouldWriteToConsole) {
356
- const method = level === "debug" ? <span class="branch-0 cbranch-no" title="branch not covered" >"log" </span>: level;
357
- if (typeof console[method] === "function") {
356
+ const method = level === 'debug' ? <span class="branch-0 cbranch-no" title="branch not covered" >'log' </span>: level;
357
+ if (typeof console[method] === 'function') {
358
358
  console[method](formatted, ...args);
359
359
  } else <span class="missing-if-branch" title="else path not taken" >E</span>{
360
360
  <span class="cstat-no" title="statement not covered" > console.log(formatted, ...args);</span>
@@ -377,30 +377,30 @@ function log(level, message, ...args) {
377
377
  */
378
378
  export const logger = {
379
379
  debug(message, ...args) {
380
- log("debug", message, ...args);
380
+ log('debug', message, ...args);
381
381
  },
382
382
  info(message, ...args) {
383
- log("info", message, ...args);
383
+ log('info', message, ...args);
384
384
  },
385
385
  <span class="fstat-no" title="function not covered" > wa</span>rn(message, ...args) {
386
- <span class="cstat-no" title="statement not covered" > log("warn", message, ...args);</span>
386
+ <span class="cstat-no" title="statement not covered" > log('warn', message, ...args);</span>
387
387
  },
388
388
  error(message, ...args) {
389
- log("error", message, ...args);
389
+ log('error', message, ...args);
390
390
  },
391
391
  /**
392
392
  * Subscribe to log events.
393
393
  *
394
- * @param {LogListener} listener Listener invoked for every log entry that passes the current level filter.
395
- * @returns {function(): void} Cleanup function that removes the listener when called.
394
+ * @param {LogListener} listener Listener invoked for every log entry that passes the current level filter.
395
+ * @returns {function(): void} Cleanup function that removes the listener when called.
396
396
  */
397
397
  subscribe(listener) {
398
- <span class="missing-if-branch" title="if path not taken" >I</span>if (typeof listener !== "function") {
398
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (typeof listener !== 'function') {
399
399
  <span class="cstat-no" title="statement not covered" > return <span class="fstat-no" title="function not covered" >()</span> =&gt; {};</span>
400
400
  }
401
401
  listeners.add(listener);
402
402
  return () =&gt; listeners.delete(listener);
403
- }
403
+ },
404
404
  };
405
405
  &nbsp;</pre></td></tr></table></pre>
406
406
 
@@ -409,7 +409,7 @@ export const logger = {
409
409
  <div class='footer quiet pad2 space-top1 center small'>
410
410
  Code coverage generated by
411
411
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
412
- at 2026-04-25T17:40:28.828Z
412
+ at 2026-04-27T20:17:25.625Z
413
413
  </div>
414
414
  <script src="../prettify.js"></script>
415
415
  <script>