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">95.74% </span>
26
+ <span class="strong">81.08% </span>
27
27
  <span class="quiet">Statements</span>
28
- <span class='fraction'>45/47</span>
28
+ <span class='fraction'>30/37</span>
29
29
  </div>
30
30
 
31
31
 
32
32
  <div class='fl pad1y space-right2'>
33
- <span class="strong">94.59% </span>
33
+ <span class="strong">77.41% </span>
34
34
  <span class="quiet">Branches</span>
35
- <span class='fraction'>35/37</span>
35
+ <span class='fraction'>24/31</span>
36
36
  </div>
37
37
 
38
38
 
39
39
  <div class='fl pad1y space-right2'>
40
40
  <span class="strong">100% </span>
41
41
  <span class="quiet">Functions</span>
42
- <span class='fraction'>6/6</span>
42
+ <span class='fraction'>5/5</span>
43
43
  </div>
44
44
 
45
45
 
46
46
  <div class='fl pad1y space-right2'>
47
- <span class="strong">100% </span>
47
+ <span class="strong">85.71% </span>
48
48
  <span class="quiet">Lines</span>
49
- <span class='fraction'>42/42</span>
49
+ <span class='fraction'>30/35</span>
50
50
  </div>
51
51
 
52
52
 
@@ -150,64 +150,34 @@
150
150
  <a name='L85'></a><a href='#L85'>85</a>
151
151
  <a name='L86'></a><a href='#L86'>86</a>
152
152
  <a name='L87'></a><a href='#L87'>87</a>
153
- <a name='L88'></a><a href='#L88'>88</a>
154
- <a name='L89'></a><a href='#L89'>89</a>
155
- <a name='L90'></a><a href='#L90'>90</a>
156
- <a name='L91'></a><a href='#L91'>91</a>
157
- <a name='L92'></a><a href='#L92'>92</a>
158
- <a name='L93'></a><a href='#L93'>93</a>
159
- <a name='L94'></a><a href='#L94'>94</a>
160
- <a name='L95'></a><a href='#L95'>95</a>
161
- <a name='L96'></a><a href='#L96'>96</a>
162
- <a name='L97'></a><a href='#L97'>97</a>
163
- <a name='L98'></a><a href='#L98'>98</a>
164
- <a name='L99'></a><a href='#L99'>99</a>
165
- <a name='L100'></a><a href='#L100'>100</a>
166
- <a name='L101'></a><a href='#L101'>101</a>
167
- <a name='L102'></a><a href='#L102'>102</a>
168
- <a name='L103'></a><a href='#L103'>103</a>
169
- <a name='L104'></a><a href='#L104'>104</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
170
- <span class="cline-any cline-neutral">&nbsp;</span>
171
- <span class="cline-any cline-neutral">&nbsp;</span>
172
- <span class="cline-any cline-neutral">&nbsp;</span>
173
- <span class="cline-any cline-neutral">&nbsp;</span>
174
- <span class="cline-any cline-neutral">&nbsp;</span>
175
- <span class="cline-any cline-neutral">&nbsp;</span>
153
+ <a name='L88'></a><a href='#L88'>88</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
176
154
  <span class="cline-any cline-neutral">&nbsp;</span>
177
155
  <span class="cline-any cline-neutral">&nbsp;</span>
178
156
  <span class="cline-any cline-neutral">&nbsp;</span>
179
157
  <span class="cline-any cline-neutral">&nbsp;</span>
180
- <span class="cline-any cline-yes">6x</span>
181
- <span class="cline-any cline-yes">6x</span>
182
- <span class="cline-any cline-yes">5x</span>
183
- <span class="cline-any cline-yes">5x</span>
184
- <span class="cline-any cline-yes">6x</span>
185
- <span class="cline-any cline-yes">6x</span>
186
158
  <span class="cline-any cline-neutral">&nbsp;</span>
187
- <span class="cline-any cline-yes">5x</span>
188
159
  <span class="cline-any cline-neutral">&nbsp;</span>
189
160
  <span class="cline-any cline-neutral">&nbsp;</span>
190
161
  <span class="cline-any cline-neutral">&nbsp;</span>
191
162
  <span class="cline-any cline-neutral">&nbsp;</span>
192
163
  <span class="cline-any cline-neutral">&nbsp;</span>
193
164
  <span class="cline-any cline-neutral">&nbsp;</span>
165
+ <span class="cline-any cline-yes">1x</span>
166
+ <span class="cline-any cline-yes">1x</span>
194
167
  <span class="cline-any cline-neutral">&nbsp;</span>
168
+ <span class="cline-any cline-yes">1x</span>
195
169
  <span class="cline-any cline-neutral">&nbsp;</span>
170
+ <span class="cline-any cline-yes">1x</span>
171
+ <span class="cline-any cline-yes">1x</span>
172
+ <span class="cline-any cline-yes">1x</span>
173
+ <span class="cline-any cline-yes">1x</span>
196
174
  <span class="cline-any cline-neutral">&nbsp;</span>
175
+ <span class="cline-any cline-yes">1x</span>
197
176
  <span class="cline-any cline-neutral">&nbsp;</span>
198
- <span class="cline-any cline-yes">15x</span>
199
177
  <span class="cline-any cline-neutral">&nbsp;</span>
200
- <span class="cline-any cline-yes">15x</span>
201
- <span class="cline-any cline-yes">15x</span>
202
- <span class="cline-any cline-yes">15x</span>
203
- <span class="cline-any cline-yes">2x</span>
204
178
  <span class="cline-any cline-neutral">&nbsp;</span>
205
179
  <span class="cline-any cline-neutral">&nbsp;</span>
206
- <span class="cline-any cline-yes">15x</span>
207
- <span class="cline-any cline-yes">15x</span>
208
- <span class="cline-any cline-yes">6x</span>
209
180
  <span class="cline-any cline-neutral">&nbsp;</span>
210
- <span class="cline-any cline-yes">15x</span>
211
181
  <span class="cline-any cline-neutral">&nbsp;</span>
212
182
  <span class="cline-any cline-neutral">&nbsp;</span>
213
183
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -220,56 +190,55 @@
220
190
  <span class="cline-any cline-neutral">&nbsp;</span>
221
191
  <span class="cline-any cline-neutral">&nbsp;</span>
222
192
  <span class="cline-any cline-neutral">&nbsp;</span>
193
+ <span class="cline-any cline-yes">7x</span>
223
194
  <span class="cline-any cline-neutral">&nbsp;</span>
224
195
  <span class="cline-any cline-neutral">&nbsp;</span>
196
+ <span class="cline-any cline-yes">7x</span>
197
+ <span class="cline-any cline-yes">7x</span>
225
198
  <span class="cline-any cline-neutral">&nbsp;</span>
199
+ <span class="cline-any cline-yes">7x</span>
200
+ <span class="cline-any cline-yes">2x</span>
226
201
  <span class="cline-any cline-neutral">&nbsp;</span>
202
+ <span class="cline-any cline-yes">2x</span>
227
203
  <span class="cline-any cline-neutral">&nbsp;</span>
228
- <span class="cline-any cline-yes">22x</span>
204
+ <span class="cline-any cline-yes">2x</span>
205
+ <span class="cline-any cline-yes">2x</span>
206
+ <span class="cline-any cline-yes">2x</span>
207
+ <span class="cline-any cline-no">&nbsp;</span>
208
+ <span class="cline-any cline-no">&nbsp;</span>
229
209
  <span class="cline-any cline-neutral">&nbsp;</span>
210
+ <span class="cline-any cline-yes">2x</span>
211
+ <span class="cline-any cline-yes">2x</span>
212
+ <span class="cline-any cline-yes">2x</span>
213
+ <span class="cline-any cline-no">&nbsp;</span>
214
+ <span class="cline-any cline-no">&nbsp;</span>
230
215
  <span class="cline-any cline-neutral">&nbsp;</span>
231
- <span class="cline-any cline-yes">22x</span>
232
- <span class="cline-any cline-yes">22x</span>
216
+ <span class="cline-any cline-yes">2x</span>
233
217
  <span class="cline-any cline-neutral">&nbsp;</span>
234
- <span class="cline-any cline-yes">22x</span>
235
- <span class="cline-any cline-yes">15x</span>
236
- <span class="cline-any cline-yes">14x</span>
237
- <span class="cline-any cline-yes">15x</span>
238
- <span class="cline-any cline-yes">15x</span>
239
- <span class="cline-any cline-yes">1x</span>
240
- <span class="cline-any cline-yes">1x</span>
241
218
  <span class="cline-any cline-neutral">&nbsp;</span>
242
- <span class="cline-any cline-yes">14x</span>
243
- <span class="cline-any cline-yes">14x</span>
244
- <span class="cline-any cline-yes">14x</span>
245
- <span class="cline-any cline-yes">3x</span>
246
- <span class="cline-any cline-yes">3x</span>
247
219
  <span class="cline-any cline-neutral">&nbsp;</span>
248
- <span class="cline-any cline-yes">11x</span>
220
+ <span class="cline-any cline-yes">7x</span>
249
221
  <span class="cline-any cline-neutral">&nbsp;</span>
222
+ <span class="cline-any cline-no">&nbsp;</span>
250
223
  <span class="cline-any cline-neutral">&nbsp;</span>
251
224
  <span class="cline-any cline-neutral">&nbsp;</span>
252
- <span class="cline-any cline-yes">22x</span>
253
225
  <span class="cline-any cline-neutral">&nbsp;</span>
254
226
  <span class="cline-any cline-yes">7x</span>
227
+ <span class="cline-any cline-yes">7x</span>
228
+ <span class="cline-any cline-yes">7x</span>
255
229
  <span class="cline-any cline-neutral">&nbsp;</span>
256
- <span class="cline-any cline-neutral">&nbsp;</span>
257
- <span class="cline-any cline-neutral">&nbsp;</span>
258
- <span class="cline-any cline-yes">15x</span>
259
- <span class="cline-any cline-yes">15x</span>
260
- <span class="cline-any cline-yes">15x</span>
261
- <span class="cline-any cline-neutral">&nbsp;</span>
262
- <span class="cline-any cline-yes">15x</span>
263
- <span class="cline-any cline-yes">6x</span>
264
- <span class="cline-any cline-yes">6x</span>
265
- <span class="cline-any cline-yes">3x</span>
230
+ <span class="cline-any cline-yes">7x</span>
231
+ <span class="cline-any cline-yes">2x</span>
232
+ <span class="cline-any cline-yes">2x</span>
233
+ <span class="cline-any cline-yes">1x</span>
266
234
  <span class="cline-any cline-neutral">&nbsp;</span>
267
235
  <span class="cline-any cline-neutral">&nbsp;</span>
268
236
  <span class="cline-any cline-neutral">&nbsp;</span>
269
237
  <span class="cline-any cline-neutral">&nbsp;</span>
270
- <span class="cline-any cline-yes">15x</span>
238
+ <span class="cline-any cline-yes">7x</span>
271
239
  <span class="cline-any cline-neutral">&nbsp;</span>
272
240
  <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">// Reusable validations for ports and environment variables
241
+ import { normalizeImageName } from './imageNameUtils.js';
273
242
  &nbsp;
274
243
  /**
275
244
  * Validate a comma-separated list of port mappings in the form "host:container".
@@ -280,39 +249,19 @@
280
249
  */
281
250
  export function validatePorts(portInput) {
282
251
  // Empty or whitespace-only input is considered valid (ports are optional)
283
- if (!portInput || !portInput.trim()) return true;
284
- const ports = portInput.split(",").map(p =&gt; p.trim()).filter(Boolean);
252
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (!portInput || !portInput.trim()) <span class="cstat-no" title="statement not covered" >return true;</span>
253
+ const ports = portInput
254
+ .split(',')
255
+ .map((p) =&gt; p.trim())
256
+ .filter(Boolean);
285
257
  <span class="missing-if-branch" title="if path not taken" >I</span>if (ports.length === 0) <span class="cstat-no" title="statement not covered" >return true;</span>
286
- const invalid = ports.find(pair =&gt; {
287
- const [host, cont] = pair.split(":");
288
- return !host || !cont || isNaN(Number(host)) || isNaN(Number(cont));
258
+ const invalid = ports.find((pair) =&gt; {
259
+ const [host, cont] = pair.split(':');
260
+ return !host || !cont || <span class="branch-2 cbranch-no" title="branch not covered" >isNaN(Number(host)) </span>|| <span class="branch-3 cbranch-no" title="branch not covered" >isNaN(Number(cont));</span>
289
261
  });
290
262
  return !invalid;
291
263
  }
292
264
  &nbsp;
293
- /**
294
- * Normalize an image name to its base name (strips registry prefix and tag).
295
- * e.g. "docker.io/library/postgres:16-alpine" → "postgres"
296
- *
297
- * @param {string} imageName - Raw image name from user
298
- * @returns {string} Base image name (lowercase)
299
- */
300
- function normalizeImageName(imageName) {
301
- <span class="missing-if-branch" title="if path not taken" >I</span>if (!imageName) <span class="cstat-no" title="statement not covered" >return "";</span>
302
- // Remove registry prefix (anything before the last / when it contains a dot or colon before the /)
303
- let name = imageName;
304
- const slashIdx = name.lastIndexOf("/");
305
- if (slashIdx !== -1) {
306
- name = name.slice(slashIdx + 1);
307
- }
308
- // Remove tag
309
- const colonIdx = name.indexOf(":");
310
- if (colonIdx !== -1) {
311
- name = name.slice(0, colonIdx);
312
- }
313
- return name.toLowerCase();
314
- }
315
- &nbsp;
316
265
  /**
317
266
  * Validate environment variable input as comma-separated VAR=value pairs.
318
267
  *
@@ -335,26 +284,29 @@ export function validateEnvVars(envInput, imageName, imageProfiles) {
335
284
  const syntaxErrors = [];
336
285
  &nbsp;
337
286
  if (envInput &amp;&amp; envInput.trim()) {
338
- const vars = envInput.split(",").map(v =&gt; v.trim()).filter(Boolean);
287
+ const vars = envInput
288
+ .split(',')
289
+ .map((v) =&gt; v.trim())
290
+ .filter(Boolean);
339
291
  for (const v of vars) {
340
- const eqIdx = v.indexOf("=");
341
- if (eqIdx === -1) {
342
- syntaxErrors.push(`"${v}" is missing an '=' sign`);
343
- continue;
292
+ const eqIdx = v.indexOf('=');
293
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (eqIdx === -1) {
294
+ <span class="cstat-no" title="statement not covered" > syntaxErrors.push(`"${v}" is missing an '=' sign`);</span>
295
+ <span class="cstat-no" title="statement not covered" > continue;</span>
344
296
  }
345
297
  const varName = v.slice(0, eqIdx).trim();
346
298
  const varValue = v.slice(eqIdx + 1);
347
- if (!/^[A-Z_][A-Z0-9_]*$/i.test(varName)) {
348
- syntaxErrors.push(`"${varName}" is not a valid variable name`);
349
- continue;
299
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (!/^[A-Z_][A-Z0-9_]*$/i.test(varName)) {
300
+ <span class="cstat-no" title="statement not covered" > syntaxErrors.push(`"${varName}" is not a valid variable name`);</span>
301
+ <span class="cstat-no" title="statement not covered" > continue;</span>
350
302
  }
351
303
  parsedEnv[varName] = varValue;
352
304
  }
353
305
  }
354
306
  &nbsp;
355
- if (!contextual) {
307
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (!contextual) {
356
308
  // Legacy: return boolean
357
- return syntaxErrors.length === 0;
309
+ <span class="cstat-no" title="statement not covered" > return syntaxErrors.length === 0;</span>
358
310
  }
359
311
  &nbsp;
360
312
  // Contextual: also check required env vars from the profile
@@ -364,7 +316,7 @@ export function validateEnvVars(envInput, imageName, imageProfiles) {
364
316
  &nbsp;
365
317
  if (profile &amp;&amp; profile.requiredEnv &amp;&amp; profile.requiredEnv.length) {
366
318
  for (const required of profile.requiredEnv) {
367
- if (!parsedEnv[required] || parsedEnv[required].trim() === "") {
319
+ if (!parsedEnv[required] || parsedEnv[required].trim() === '') {
368
320
  errors.push(`Missing required env var: ${required}`);
369
321
  }
370
322
  }
@@ -379,7 +331,7 @@ export function validateEnvVars(envInput, imageName, imageProfiles) {
379
331
  <div class='footer quiet pad2 space-top1 center small'>
380
332
  Code coverage generated by
381
333
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
382
- at 2026-04-25T17:40:28.828Z
334
+ at 2026-04-27T22:13:21.150Z
383
335
  </div>
384
336
  <script src="../prettify.js"></script>
385
337
  <script>
@@ -23,30 +23,30 @@
23
23
  <div class='clearfix'>
24
24
 
25
25
  <div class='fl pad1y space-right2'>
26
- <span class="strong">53.53% </span>
26
+ <span class="strong">73.09% </span>
27
27
  <span class="quiet">Statements</span>
28
- <span class='fraction'>53/99</span>
28
+ <span class='fraction'>125/171</span>
29
29
  </div>
30
30
 
31
31
 
32
32
  <div class='fl pad1y space-right2'>
33
- <span class="strong">66.66% </span>
33
+ <span class="strong">75.8% </span>
34
34
  <span class="quiet">Branches</span>
35
- <span class='fraction'>22/33</span>
35
+ <span class='fraction'>47/62</span>
36
36
  </div>
37
37
 
38
38
 
39
39
  <div class='fl pad1y space-right2'>
40
- <span class="strong">35.71% </span>
40
+ <span class="strong">61.9% </span>
41
41
  <span class="quiet">Functions</span>
42
- <span class='fraction'>5/14</span>
42
+ <span class='fraction'>13/21</span>
43
43
  </div>
44
44
 
45
45
 
46
46
  <div class='fl pad1y space-right2'>
47
- <span class="strong">53.6% </span>
47
+ <span class="strong">73.61% </span>
48
48
  <span class="quiet">Lines</span>
49
- <span class='fraction'>52/97</span>
49
+ <span class='fraction'>120/163</span>
50
50
  </div>
51
51
 
52
52
 
@@ -94,18 +94,18 @@
94
94
  </tr>
95
95
 
96
96
  <tr>
97
- <td class="file medium" data-value="useContainerCreation.js"><a href="useContainerCreation.js.html">useContainerCreation.js</a></td>
98
- <td data-value="73.01" class="pic medium">
99
- <div class="chart"><div class="cover-fill" style="width: 73%"></div><div class="cover-empty" style="width: 27%"></div></div>
97
+ <td class="file high" data-value="useContainerCreation.js"><a href="useContainerCreation.js.html">useContainerCreation.js</a></td>
98
+ <td data-value="87.4" class="pic high">
99
+ <div class="chart"><div class="cover-fill" style="width: 87%"></div><div class="cover-empty" style="width: 13%"></div></div>
100
100
  </td>
101
- <td data-value="73.01" class="pct medium">73.01%</td>
102
- <td data-value="63" class="abs medium">46/63</td>
103
- <td data-value="88" class="pct high">88%</td>
104
- <td data-value="25" class="abs high">22/25</td>
105
- <td data-value="60" class="pct medium">60%</td>
106
- <td data-value="5" class="abs medium">3/5</td>
107
- <td data-value="72.58" class="pct medium">72.58%</td>
108
- <td data-value="62" class="abs medium">45/62</td>
101
+ <td data-value="87.4" class="pct high">87.4%</td>
102
+ <td data-value="135" class="abs high">118/135</td>
103
+ <td data-value="87.03" class="pct high">87.03%</td>
104
+ <td data-value="54" class="abs high">47/54</td>
105
+ <td data-value="91.66" class="pct high">91.66%</td>
106
+ <td data-value="12" class="abs high">11/12</td>
107
+ <td data-value="88.28" class="pct high">88.28%</td>
108
+ <td data-value="128" class="abs high">113/128</td>
109
109
  </tr>
110
110
 
111
111
  <tr>
@@ -131,7 +131,7 @@
131
131
  <div class='footer quiet pad2 space-top1 center small'>
132
132
  Code coverage generated by
133
133
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
134
- at 2026-04-25T17:40:28.828Z
134
+ at 2026-04-27T22:13:21.150Z
135
135
  </div>
136
136
  <script src="../../prettify.js"></script>
137
137
  <script>
@@ -132,7 +132,14 @@
132
132
  <a name='L67'></a><a href='#L67'>67</a>
133
133
  <a name='L68'></a><a href='#L68'>68</a>
134
134
  <a name='L69'></a><a href='#L69'>69</a>
135
- <a name='L70'></a><a href='#L70'>70</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
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>
141
+ <a name='L76'></a><a href='#L76'>76</a>
142
+ <a name='L77'></a><a href='#L77'>77</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
136
143
  <span class="cline-any cline-neutral">&nbsp;</span>
137
144
  <span class="cline-any cline-neutral">&nbsp;</span>
138
145
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -146,8 +153,13 @@
146
153
  <span class="cline-any cline-neutral">&nbsp;</span>
147
154
  <span class="cline-any cline-neutral">&nbsp;</span>
148
155
  <span class="cline-any cline-neutral">&nbsp;</span>
149
- <span class="cline-any cline-yes">24x</span>
150
- <span class="cline-any cline-yes">24x</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>
160
+ <span class="cline-any cline-neutral">&nbsp;</span>
161
+ <span class="cline-any cline-yes">96x</span>
162
+ <span class="cline-any cline-yes">96x</span>
151
163
  <span class="cline-any cline-neutral">&nbsp;</span>
152
164
  <span class="cline-any cline-neutral">&nbsp;</span>
153
165
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -176,11 +188,13 @@
176
188
  <span class="cline-any cline-no">&nbsp;</span>
177
189
  <span class="cline-any cline-neutral">&nbsp;</span>
178
190
  <span class="cline-any cline-no">&nbsp;</span>
191
+ <span class="cline-any cline-neutral">&nbsp;</span>
192
+ <span class="cline-any cline-neutral">&nbsp;</span>
179
193
  <span class="cline-any cline-no">&nbsp;</span>
180
194
  <span class="cline-any cline-neutral">&nbsp;</span>
181
195
  <span class="cline-any cline-neutral">&nbsp;</span>
182
196
  <span class="cline-any cline-neutral">&nbsp;</span>
183
- <span class="cline-any cline-yes">24x</span>
197
+ <span class="cline-any cline-yes">96x</span>
184
198
  <span class="cline-any cline-neutral">&nbsp;</span>
185
199
  <span class="cline-any cline-neutral">&nbsp;</span>
186
200
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -201,9 +215,14 @@
201
215
  <span class="cline-any cline-neutral">&nbsp;</span>
202
216
  <span class="cline-any cline-neutral">&nbsp;</span>
203
217
  <span class="cline-any cline-neutral">&nbsp;</span>
204
- <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import { useState } from "react";
205
- import { safeCall } from "../../helpers/safeCall.js";
206
- import { startContainer as svcStartContainer, stopContainer as svcStopContainer, restartContainer as svcRestartContainer, removeContainer as svcRemoveContainer } from "../../helpers/dockerService/serviceComponents/containerActions.js";
218
+ <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import { useState } from 'react';
219
+ import { safeCall } from '../../helpers/safeCall.js';
220
+ import {
221
+ startContainer as svcStartContainer,
222
+ stopContainer as svcStopContainer,
223
+ restartContainer as svcRestartContainer,
224
+ removeContainer as svcRemoveContainer,
225
+ } from '../../helpers/dockerService/serviceComponents/containerActions.js';
207
226
  &nbsp;
208
227
  /**
209
228
  * Custom hook to manage container actions (start, stop, restart, remove).
@@ -215,8 +234,8 @@ import { startContainer as svcStartContainer, stopContainer as svcStopContainer,
215
234
  * @returns {Object} Action helpers and feedback state
216
235
  */
217
236
  export function useContainerActions({ containers, onAction }) {
218
- const [message, setMessage] = useState("");
219
- const [messageColor, setMessageColor] = useState("yellow");
237
+ const [message, setMessage] = useState('');
238
+ const [messageColor, setMessageColor] = useState('yellow');
220
239
  &nbsp;
221
240
  /**
222
241
  * Handles a generic container action and sets feedback.
@@ -232,20 +251,22 @@ export function useContainerActions({ containers, onAction }) {
232
251
  const checkMsg = <span class="cstat-no" title="statement not covered" >stateCheck(container);</span>
233
252
  <span class="cstat-no" title="statement not covered" > if (checkMsg) {</span>
234
253
  <span class="cstat-no" title="statement not covered" > setMessage(checkMsg);</span>
235
- <span class="cstat-no" title="statement not covered" > setMessageColor("yellow");</span>
254
+ <span class="cstat-no" title="statement not covered" > setMessageColor('yellow');</span>
236
255
  <span class="cstat-no" title="statement not covered" > return;</span>
237
256
  }
238
257
  }
239
258
  <span class="cstat-no" title="statement not covered" > setMessage(`${actionLabel} container...`);</span>
240
- <span class="cstat-no" title="statement not covered" > setMessageColor("yellow");</span>
259
+ <span class="cstat-no" title="statement not covered" > setMessageColor('yellow');</span>
241
260
  <span class="cstat-no" title="statement not covered" > try {</span>
242
261
  <span class="cstat-no" title="statement not covered" > await actionFn(container.id);</span>
243
- <span class="cstat-no" title="statement not covered" > setMessage(`${actionLabel} container successful.`);</span>
244
- <span class="cstat-no" title="statement not covered" > setMessageColor("green");</span>
245
- <span class="cstat-no" title="statement not covered" > safeCall(onAction);</span>
262
+ <span class="cstat-no" title="statement not covered" > setMessage(`${actionLabel} container successful.`);</span>
263
+ <span class="cstat-no" title="statement not covered" > setMessageColor('green');</span>
264
+ <span class="cstat-no" title="statement not covered" > safeCall(onAction);</span>
246
265
  } catch (err) {
247
- <span class="cstat-no" title="statement not covered" > setMessage(`Failed to ${actionLabel.toLowerCase()} container: ${err.message}`);</span>
248
- <span class="cstat-no" title="statement not covered" > setMessageColor("red");</span>
266
+ <span class="cstat-no" title="statement not covered" > setMessage(</span>
267
+ `Failed to ${actionLabel.toLowerCase()} container: ${err.message}`
268
+ );
269
+ <span class="cstat-no" title="statement not covered" > setMessageColor('red');</span>
249
270
  }
250
271
  }
251
272
  &nbsp;
@@ -277,7 +298,7 @@ export function useContainerActions({ containers, onAction }) {
277
298
  <div class='footer quiet pad2 space-top1 center small'>
278
299
  Code coverage generated by
279
300
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
280
- at 2026-04-25T17:40:28.828Z
301
+ at 2026-04-27T22:13:21.150Z
281
302
  </div>
282
303
  <script src="../../prettify.js"></script>
283
304
  <script>