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
@@ -25,28 +25,28 @@
25
25
  <div class='fl pad1y space-right2'>
26
26
  <span class="strong">100% </span>
27
27
  <span class="quiet">Statements</span>
28
- <span class='fraction'>6/6</span>
28
+ <span class='fraction'>15/15</span>
29
29
  </div>
30
30
 
31
31
 
32
32
  <div class='fl pad1y space-right2'>
33
- <span class="strong">100% </span>
33
+ <span class="strong">85.71% </span>
34
34
  <span class="quiet">Branches</span>
35
- <span class='fraction'>0/0</span>
35
+ <span class='fraction'>6/7</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'>0/0</span>
42
+ <span class='fraction'>1/1</span>
43
43
  </div>
44
44
 
45
45
 
46
46
  <div class='fl pad1y space-right2'>
47
47
  <span class="strong">100% </span>
48
48
  <span class="quiet">Lines</span>
49
- <span class='fraction'>6/6</span>
49
+ <span class='fraction'>12/12</span>
50
50
  </div>
51
51
 
52
52
 
@@ -113,12 +113,72 @@
113
113
  <a name='L48'></a><a href='#L48'>48</a>
114
114
  <a name='L49'></a><a href='#L49'>49</a>
115
115
  <a name='L50'></a><a href='#L50'>50</a>
116
- <a name='L51'></a><a href='#L51'>51</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
116
+ <a name='L51'></a><a href='#L51'>51</a>
117
+ <a name='L52'></a><a href='#L52'>52</a>
118
+ <a name='L53'></a><a href='#L53'>53</a>
119
+ <a name='L54'></a><a href='#L54'>54</a>
120
+ <a name='L55'></a><a href='#L55'>55</a>
121
+ <a name='L56'></a><a href='#L56'>56</a>
122
+ <a name='L57'></a><a href='#L57'>57</a>
123
+ <a name='L58'></a><a href='#L58'>58</a>
124
+ <a name='L59'></a><a href='#L59'>59</a>
125
+ <a name='L60'></a><a href='#L60'>60</a>
126
+ <a name='L61'></a><a href='#L61'>61</a>
127
+ <a name='L62'></a><a href='#L62'>62</a>
128
+ <a name='L63'></a><a href='#L63'>63</a>
129
+ <a name='L64'></a><a href='#L64'>64</a>
130
+ <a name='L65'></a><a href='#L65'>65</a>
131
+ <a name='L66'></a><a href='#L66'>66</a>
132
+ <a name='L67'></a><a href='#L67'>67</a>
133
+ <a name='L68'></a><a href='#L68'>68</a>
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>
141
+ <a name='L76'></a><a href='#L76'>76</a>
142
+ <a name='L77'></a><a href='#L77'>77</a>
143
+ <a name='L78'></a><a href='#L78'>78</a>
144
+ <a name='L79'></a><a href='#L79'>79</a>
145
+ <a name='L80'></a><a href='#L80'>80</a>
146
+ <a name='L81'></a><a href='#L81'>81</a>
147
+ <a name='L82'></a><a href='#L82'>82</a>
148
+ <a name='L83'></a><a href='#L83'>83</a>
149
+ <a name='L84'></a><a href='#L84'>84</a>
150
+ <a name='L85'></a><a href='#L85'>85</a>
151
+ <a name='L86'></a><a href='#L86'>86</a>
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></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</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-yes">4x</span>
161
+ <span class="cline-any cline-neutral">&nbsp;</span>
162
+ <span class="cline-any cline-neutral">&nbsp;</span>
163
+ <span class="cline-any cline-neutral">&nbsp;</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>
167
+ <span class="cline-any cline-neutral">&nbsp;</span>
168
+ <span class="cline-any cline-yes">4x</span>
169
+ <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>
176
+ <span class="cline-any cline-yes">4x</span>
117
177
  <span class="cline-any cline-neutral">&nbsp;</span>
118
178
  <span class="cline-any cline-neutral">&nbsp;</span>
119
179
  <span class="cline-any cline-neutral">&nbsp;</span>
120
180
  <span class="cline-any cline-neutral">&nbsp;</span>
121
- <span class="cline-any cline-yes">11x</span>
181
+ <span class="cline-any cline-yes">4x</span>
122
182
  <span class="cline-any cline-neutral">&nbsp;</span>
123
183
  <span class="cline-any cline-neutral">&nbsp;</span>
124
184
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -126,7 +186,7 @@
126
186
  <span class="cline-any cline-neutral">&nbsp;</span>
127
187
  <span class="cline-any cline-neutral">&nbsp;</span>
128
188
  <span class="cline-any cline-neutral">&nbsp;</span>
129
- <span class="cline-any cline-yes">11x</span>
189
+ <span class="cline-any cline-yes">4x</span>
130
190
  <span class="cline-any cline-neutral">&nbsp;</span>
131
191
  <span class="cline-any cline-neutral">&nbsp;</span>
132
192
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -134,20 +194,18 @@
134
194
  <span class="cline-any cline-neutral">&nbsp;</span>
135
195
  <span class="cline-any cline-neutral">&nbsp;</span>
136
196
  <span class="cline-any cline-neutral">&nbsp;</span>
137
- <span class="cline-any cline-yes">11x</span>
138
197
  <span class="cline-any cline-neutral">&nbsp;</span>
139
198
  <span class="cline-any cline-neutral">&nbsp;</span>
140
199
  <span class="cline-any cline-neutral">&nbsp;</span>
141
200
  <span class="cline-any cline-neutral">&nbsp;</span>
142
- <span class="cline-any cline-yes">11x</span>
143
201
  <span class="cline-any cline-neutral">&nbsp;</span>
144
202
  <span class="cline-any cline-neutral">&nbsp;</span>
145
203
  <span class="cline-any cline-neutral">&nbsp;</span>
204
+ <span class="cline-any cline-yes">4x</span>
146
205
  <span class="cline-any cline-neutral">&nbsp;</span>
147
206
  <span class="cline-any cline-neutral">&nbsp;</span>
148
207
  <span class="cline-any cline-neutral">&nbsp;</span>
149
208
  <span class="cline-any cline-neutral">&nbsp;</span>
150
- <span class="cline-any cline-yes">11x</span>
151
209
  <span class="cline-any cline-neutral">&nbsp;</span>
152
210
  <span class="cline-any cline-neutral">&nbsp;</span>
153
211
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -155,7 +213,6 @@
155
213
  <span class="cline-any cline-neutral">&nbsp;</span>
156
214
  <span class="cline-any cline-neutral">&nbsp;</span>
157
215
  <span class="cline-any cline-neutral">&nbsp;</span>
158
- <span class="cline-any cline-yes">11x</span>
159
216
  <span class="cline-any cline-neutral">&nbsp;</span>
160
217
  <span class="cline-any cline-neutral">&nbsp;</span>
161
218
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -163,6 +220,27 @@
163
220
  <span class="cline-any cline-neutral">&nbsp;</span>
164
221
  <span class="cline-any cline-neutral">&nbsp;</span>
165
222
  <span class="cline-any cline-neutral">&nbsp;</span>
223
+ <span class="cline-any cline-neutral">&nbsp;</span>
224
+ <span class="cline-any cline-neutral">&nbsp;</span>
225
+ <span class="cline-any cline-neutral">&nbsp;</span>
226
+ <span class="cline-any cline-neutral">&nbsp;</span>
227
+ <span class="cline-any cline-neutral">&nbsp;</span>
228
+ <span class="cline-any cline-neutral">&nbsp;</span>
229
+ <span class="cline-any cline-neutral">&nbsp;</span>
230
+ <span class="cline-any cline-neutral">&nbsp;</span>
231
+ <span class="cline-any cline-neutral">&nbsp;</span>
232
+ <span class="cline-any cline-neutral">&nbsp;</span>
233
+ <span class="cline-any cline-neutral">&nbsp;</span>
234
+ <span class="cline-any cline-neutral">&nbsp;</span>
235
+ <span class="cline-any cline-neutral">&nbsp;</span>
236
+ <span class="cline-any cline-neutral">&nbsp;</span>
237
+ <span class="cline-any cline-yes">29x</span>
238
+ <span class="cline-any cline-yes">28x</span>
239
+ <span class="cline-any cline-yes">23x</span>
240
+ <span class="cline-any cline-yes">23x</span>
241
+ <span class="cline-any cline-yes">23x</span>
242
+ <span class="cline-any cline-yes">20x</span>
243
+ <span class="cline-any cline-neutral">&nbsp;</span>
166
244
  <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">/**
167
245
  * Time intervals used across the app (milliseconds).
168
246
  * - CONTAINER_LIST: how often to refresh the list of containers
@@ -197,22 +275,61 @@ export const TIMEOUTS = {
197
275
  /**
198
276
  * List of well-known database image names (without tags or registry prefixes).
199
277
  */
200
- export const DB_IMAGES = ["mysql", "mariadb", "postgres", "mongo", "mssql", "redis"];
278
+ export const DB_IMAGES = [
279
+ 'mysql',
280
+ 'mariadb',
281
+ 'postgres',
282
+ 'mongo',
283
+ 'mssql',
284
+ 'redis',
285
+ ];
201
286
  &nbsp;
202
287
  /**
203
- * Per-image profiles with required env vars and default exposed port.
288
+ * Per-image profiles with required env vars, default exposed port, and suggested env vars.
204
289
  * Keys are normalized image names (no tag, no registry prefix).
205
290
  *
206
- * @type {Record&lt;string, { requiredEnv: string[], defaultPort: string }&gt;}
291
+ * @type {Record&lt;string, { requiredEnv: string[], defaultPort: string, suggestedEnv: string[] }&gt;}
207
292
  */
208
293
  export const IMAGE_PROFILES = {
209
- mysql: { requiredEnv: ["MYSQL_ROOT_PASSWORD"], defaultPort: "3306" },
210
- mariadb: { requiredEnv: ["MARIADB_ROOT_PASSWORD"], defaultPort: "3306" },
211
- postgres: { requiredEnv: ["POSTGRES_PASSWORD"], defaultPort: "5432" },
212
- mongo: { requiredEnv: [], defaultPort: "27017" },
213
- mssql: { requiredEnv: ["ACCEPT_EULA", "SA_PASSWORD"], defaultPort: "1433" },
214
- redis: { requiredEnv: [], defaultPort: "6379" },
294
+ nginx: { requiredEnv: [], defaultPort: '80', suggestedEnv: [], defaultTag: '1.27-alpine' },
295
+ httpd: { requiredEnv: [], defaultPort: '80', suggestedEnv: [], defaultTag: '2.4-alpine' },
296
+ caddy: { requiredEnv: [], defaultPort: '80', suggestedEnv: [], defaultTag: '2-alpine' },
297
+ node: { requiredEnv: [], defaultPort: '3000', suggestedEnv: ['NODE_ENV=production'], defaultTag: '22-alpine' },
298
+ python: { requiredEnv: [], defaultPort: '8000', suggestedEnv: ['PYTHONUNBUFFERED=1'], defaultTag: '3.12-slim' },
299
+ openjdk: { requiredEnv: [], defaultPort: '8080', suggestedEnv: ['JAVA_OPTS=-Xms256m -Xmx512m'], defaultTag: '21-slim' },
300
+ golang: { requiredEnv: [], defaultPort: '8080', suggestedEnv: ['GOMAXPROCS=2'], defaultTag: '1.22-alpine' },
301
+ redis: { requiredEnv: [], defaultPort: '6379', suggestedEnv: ['REDIS_PASSWORD=change-me'], defaultTag: '7-alpine' },
302
+ memcached: { requiredEnv: [], defaultPort: '11211', suggestedEnv: [], defaultTag: '1.6-alpine' },
303
+ postgres: { requiredEnv: ['POSTGRES_PASSWORD'], defaultPort: '5432', suggestedEnv: ['POSTGRES_USER=postgres', 'POSTGRES_DB=app'], defaultTag: '17-alpine' },
304
+ mysql: { requiredEnv: ['MYSQL_ROOT_PASSWORD'], defaultPort: '3306', suggestedEnv: ['MYSQL_DATABASE=app', 'MYSQL_USER=app', 'MYSQL_PASSWORD=app123'], defaultTag: '8.0' },
305
+ mariadb: { requiredEnv: ['MARIADB_ROOT_PASSWORD'], defaultPort: '3306', suggestedEnv: ['MARIADB_DATABASE=app', 'MARIADB_USER=app', 'MARIADB_PASSWORD=app123'], defaultTag: '11-alpine' },
306
+ mongo: { requiredEnv: [], defaultPort: '27017', suggestedEnv: ['MONGO_INITDB_ROOT_USERNAME=admin', 'MONGO_INITDB_ROOT_PASSWORD=secret'], defaultTag: '7.0' },
307
+ mssql: { requiredEnv: ['ACCEPT_EULA', 'SA_PASSWORD'], defaultPort: '1433', suggestedEnv: ['ACCEPT_EULA=Y', 'MSSQL_PID=Developer'], defaultTag: '2022-latest' },
308
+ rabbitmq: { requiredEnv: [], defaultPort: '5672', suggestedEnv: ['RABBITMQ_DEFAULT_USER=guest', 'RABBITMQ_DEFAULT_PASS=guest'], defaultTag: '3-management-alpine' },
309
+ kafka: { requiredEnv: [], defaultPort: '9092', suggestedEnv: ['KAFKA_CFG_LISTENERS=PLAINTEXT://:9092', 'KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092'], defaultTag: '3.7' },
310
+ zookeeper: { requiredEnv: [], defaultPort: '2181', suggestedEnv: ['ALLOW_ANONYMOUS_LOGIN=yes'], defaultTag: '3.9' },
311
+ elasticsearch: { requiredEnv: [], defaultPort: '9200', suggestedEnv: ['discovery.type=single-node', 'xpack.security.enabled=false'], defaultTag: '8.13.0' },
312
+ minio: { requiredEnv: ['MINIO_ROOT_USER', 'MINIO_ROOT_PASSWORD'], defaultPort: '9000', suggestedEnv: ['MINIO_CONSOLE_ADDRESS=:9001'], defaultTag: 'latest' },
313
+ wordpress: { requiredEnv: ['WORDPRESS_DB_HOST', 'WORDPRESS_DB_USER', 'WORDPRESS_DB_PASSWORD', 'WORDPRESS_DB_NAME'], defaultPort: '8080', suggestedEnv: ['WORDPRESS_TABLE_PREFIX=wp_'], defaultTag: '6.5-apache' },
215
314
  };
315
+ &nbsp;
316
+ /**
317
+ * Given an image name (with or without tag), returns the image name with a
318
+ * default tag appended when the image matches a known profile and has no
319
+ * explicit tag. Returns the input unchanged otherwise.
320
+ *
321
+ * @param {string} imageName - e.g. 'postgres', 'postgres:15', 'unknownimage'
322
+ * @param {Record&lt;string, { defaultTag: string }&gt;} profiles
323
+ * @returns {string}
324
+ */
325
+ export function resolveImageTag(imageName, profiles = <span class="branch-0 cbranch-no" title="branch not covered" >IMAGE_PROFILES)</span> {
326
+ if (!imageName) return imageName;
327
+ if (imageName.includes(':')) return imageName;
328
+ const baseName = imageName.trim().toLowerCase().split('/').pop();
329
+ const profile = profiles[baseName];
330
+ if (!profile?.defaultTag) return imageName;
331
+ return `${imageName}:${profile.defaultTag}`;
332
+ }
216
333
  &nbsp;</pre></td></tr></table></pre>
217
334
 
218
335
  <div class='push'></div><!-- for sticky footer -->
@@ -220,7 +337,7 @@ export const IMAGE_PROFILES = {
220
337
  <div class='footer quiet pad2 space-top1 center small'>
221
338
  Code coverage generated by
222
339
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
223
- at 2026-04-25T17:40:28.828Z
340
+ at 2026-04-27T22:13:21.150Z
224
341
  </div>
225
342
  <script src="../prettify.js"></script>
226
343
  <script>
@@ -23,30 +23,30 @@
23
23
  <div class='clearfix'>
24
24
 
25
25
  <div class='fl pad1y space-right2'>
26
- <span class="strong">95.83% </span>
26
+ <span class="strong">54.16% </span>
27
27
  <span class="quiet">Statements</span>
28
- <span class='fraction'>23/24</span>
28
+ <span class='fraction'>13/24</span>
29
29
  </div>
30
30
 
31
31
 
32
32
  <div class='fl pad1y space-right2'>
33
- <span class="strong">94.44% </span>
33
+ <span class="strong">44.44% </span>
34
34
  <span class="quiet">Branches</span>
35
- <span class='fraction'>17/18</span>
35
+ <span class='fraction'>8/18</span>
36
36
  </div>
37
37
 
38
38
 
39
39
  <div class='fl pad1y space-right2'>
40
- <span class="strong">100% </span>
40
+ <span class="strong">75% </span>
41
41
  <span class="quiet">Functions</span>
42
- <span class='fraction'>4/4</span>
42
+ <span class='fraction'>3/4</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">68.42% </span>
48
48
  <span class="quiet">Lines</span>
49
- <span class='fraction'>17/17</span>
49
+ <span class='fraction'>13/19</span>
50
50
  </div>
51
51
 
52
52
 
@@ -61,7 +61,7 @@
61
61
  </div>
62
62
  </template>
63
63
  </div>
64
- <div class='status-line high'></div>
64
+ <div class='status-line medium'></div>
65
65
  <pre><table class="coverage">
66
66
  <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
67
67
  <a name='L2'></a><a href='#L2'>2</a>
@@ -105,7 +105,18 @@
105
105
  <a name='L40'></a><a href='#L40'>40</a>
106
106
  <a name='L41'></a><a href='#L41'>41</a>
107
107
  <a name='L42'></a><a href='#L42'>42</a>
108
- <a name='L43'></a><a href='#L43'>43</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
108
+ <a name='L43'></a><a href='#L43'>43</a>
109
+ <a name='L44'></a><a href='#L44'>44</a>
110
+ <a name='L45'></a><a href='#L45'>45</a>
111
+ <a name='L46'></a><a href='#L46'>46</a>
112
+ <a name='L47'></a><a href='#L47'>47</a>
113
+ <a name='L48'></a><a href='#L48'>48</a>
114
+ <a name='L49'></a><a href='#L49'>49</a>
115
+ <a name='L50'></a><a href='#L50'>50</a>
116
+ <a name='L51'></a><a href='#L51'>51</a>
117
+ <a name='L52'></a><a href='#L52'>52</a>
118
+ <a name='L53'></a><a href='#L53'>53</a>
119
+ <a name='L54'></a><a href='#L54'>54</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
109
120
  <span class="cline-any cline-neutral">&nbsp;</span>
110
121
  <span class="cline-any cline-neutral">&nbsp;</span>
111
122
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -121,31 +132,42 @@
121
132
  <span class="cline-any cline-neutral">&nbsp;</span>
122
133
  <span class="cline-any cline-neutral">&nbsp;</span>
123
134
  <span class="cline-any cline-neutral">&nbsp;</span>
124
- <span class="cline-any cline-yes">10x</span>
125
- <span class="cline-any cline-yes">9x</span>
126
135
  <span class="cline-any cline-neutral">&nbsp;</span>
127
- <span class="cline-any cline-yes">9x</span>
128
- <span class="cline-any cline-yes">9x</span>
129
136
  <span class="cline-any cline-neutral">&nbsp;</span>
130
- <span class="cline-any cline-yes">9x</span>
131
- <span class="cline-any cline-yes">1x</span>
132
- <span class="cline-any cline-yes">1x</span>
133
- <span class="cline-any cline-yes">1x</span>
134
- <span class="cline-any cline-yes">1x</span>
135
- <span class="cline-any cline-yes">1x</span>
136
- <span class="cline-any cline-yes">1x</span>
137
137
  <span class="cline-any cline-neutral">&nbsp;</span>
138
138
  <span class="cline-any cline-neutral">&nbsp;</span>
139
- <span class="cline-any cline-yes">9x</span>
140
139
  <span class="cline-any cline-neutral">&nbsp;</span>
140
+ <span class="cline-any cline-yes">4x</span>
141
141
  <span class="cline-any cline-neutral">&nbsp;</span>
142
+ <span class="cline-any cline-yes">4x</span>
142
143
  <span class="cline-any cline-neutral">&nbsp;</span>
143
- <span class="cline-any cline-yes">9x</span>
144
- <span class="cline-any cline-yes">9x</span>
145
- <span class="cline-any cline-yes">9x</span>
146
- <span class="cline-any cline-yes">9x</span>
144
+ <span class="cline-any cline-yes">4x</span>
147
145
  <span class="cline-any cline-neutral">&nbsp;</span>
148
- <span class="cline-any cline-yes">9x</span>
146
+ <span class="cline-any cline-yes">4x</span>
147
+ <span class="cline-any cline-neutral">&nbsp;</span>
148
+ <span class="cline-any cline-neutral">&nbsp;</span>
149
+ <span class="cline-any cline-yes">4x</span>
150
+ <span class="cline-any cline-yes">4x</span>
151
+ <span class="cline-any cline-neutral">&nbsp;</span>
152
+ <span class="cline-any cline-yes">4x</span>
153
+ <span class="cline-any cline-no">&nbsp;</span>
154
+ <span class="cline-any cline-no">&nbsp;</span>
155
+ <span class="cline-any cline-no">&nbsp;</span>
156
+ <span class="cline-any cline-no">&nbsp;</span>
157
+ <span class="cline-any cline-no">&nbsp;</span>
158
+ <span class="cline-any cline-no">&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">4x</span>
162
+ <span class="cline-any cline-neutral">&nbsp;</span>
163
+ <span class="cline-any cline-neutral">&nbsp;</span>
164
+ <span class="cline-any cline-neutral">&nbsp;</span>
165
+ <span class="cline-any cline-yes">4x</span>
166
+ <span class="cline-any cline-yes">4x</span>
167
+ <span class="cline-any cline-yes">4x</span>
168
+ <span class="cline-any cline-yes">4x</span>
169
+ <span class="cline-any cline-neutral">&nbsp;</span>
170
+ <span class="cline-any cline-yes">4x</span>
149
171
  <span class="cline-any cline-neutral">&nbsp;</span>
150
172
  <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">/**
151
173
  * Builds Docker container creation options from raw user inputs.
@@ -162,30 +184,41 @@
162
184
  * (e.g. "NODE_ENV=production,PORT=3000")
163
185
  * @returns {object} Docker container options ready to pass to createContainer
164
186
  */
165
- export function buildContainerOptions({ imageName: _imageName, containerName, portInput, envInput }) {
166
- const env = (envInput || "").split(",").map(s =&gt; s.trim()).filter(Boolean);
167
- const ports = (portInput || "").split(",").map(s =&gt; s.trim()).filter(Boolean);
187
+ export function buildContainerOptions({
188
+ imageName: _imageName,
189
+ containerName,
190
+ portInput,
191
+ envInput,
192
+ }) {
193
+ const env = (envInput || '')
194
+ .split(',')
195
+ .map((s) =&gt; s.trim())
196
+ .filter(Boolean);
197
+ const ports = (portInput || '')
198
+ .split(',')
199
+ .map((s) =&gt; s.trim())
200
+ .filter(Boolean);
168
201
  &nbsp;
169
202
  const ExposedPorts = {};
170
203
  const PortBindings = {};
171
204
  &nbsp;
172
- ports.forEach(pair =&gt; {
173
- const [host, cont] = pair.split(":");
174
- <span class="missing-if-branch" title="if path not taken" >I</span>if (!host || !cont) <span class="cstat-no" title="statement not covered" >return;</span>
175
- const key = `${cont}/tcp`;
176
- ExposedPorts[key] = {};
177
- PortBindings[key] = PortBindings[key] || [];
178
- PortBindings[key].push({ HostPort: `${host}` });
205
+ ports.forEach(<span class="fstat-no" title="function not covered" >(p</span>air) =&gt; {
206
+ const [host, cont] = <span class="cstat-no" title="statement not covered" >pair.split(':');</span>
207
+ <span class="cstat-no" title="statement not covered" > if (!host || !cont) <span class="cstat-no" title="statement not covered" >return;</span></span>
208
+ const key = <span class="cstat-no" title="statement not covered" >`${cont}/tcp`;</span>
209
+ <span class="cstat-no" title="statement not covered" > ExposedPorts[key] = {};</span>
210
+ <span class="cstat-no" title="statement not covered" > PortBindings[key] = PortBindings[key] || [];</span>
211
+ <span class="cstat-no" title="statement not covered" > PortBindings[key].push({ HostPort: `${host}` });</span>
179
212
  });
180
213
  &nbsp;
181
214
  const options = {
182
215
  Tty: true,
183
216
  };
184
217
  &nbsp;
185
- if (Object.keys(ExposedPorts).length) options.ExposedPorts = ExposedPorts;
186
- if (Object.keys(PortBindings).length) options.HostConfig = { PortBindings };
187
- if (env.length) options.Env = env;
188
- if (containerName) options.name = containerName;
218
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (Object.keys(ExposedPorts).length) <span class="cstat-no" title="statement not covered" >options.ExposedPorts = ExposedPorts;</span>
219
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (Object.keys(PortBindings).length) <span class="cstat-no" title="statement not covered" >options.HostConfig = { PortBindings };</span>
220
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (env.length) <span class="cstat-no" title="statement not covered" >options.Env = env;</span>
221
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (containerName) <span class="cstat-no" title="statement not covered" >options.name = containerName;</span>
189
222
  &nbsp;
190
223
  return options;
191
224
  }
@@ -196,7 +229,7 @@ export function buildContainerOptions({ imageName: _imageName, containerName, po
196
229
  <div class='footer quiet pad2 space-top1 center small'>
197
230
  Code coverage generated by
198
231
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
199
- at 2026-04-25T17:40:28.828Z
232
+ at 2026-04-27T22:13:21.150Z
200
233
  </div>
201
234
  <script src="../prettify.js"></script>
202
235
  <script>