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
@@ -1,12 +1,52 @@
1
+ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
2
+ function _regenerator() { /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */ var e, t, r = "function" == typeof Symbol ? Symbol : {}, n = r.iterator || "@@iterator", o = r.toStringTag || "@@toStringTag"; function i(r, n, o, i) { var c = n && n.prototype instanceof Generator ? n : Generator, u = Object.create(c.prototype); return _regeneratorDefine2(u, "_invoke", function (r, n, o) { var i, c, u, f = 0, p = o || [], y = !1, G = { p: 0, n: 0, v: e, a: d, f: d.bind(e, 4), d: function d(t, r) { return i = t, c = 0, u = e, G.n = r, a; } }; function d(r, n) { for (c = r, u = n, t = 0; !y && f && !o && t < p.length; t++) { var o, i = p[t], d = G.p, l = i[2]; r > 3 ? (o = l === n) && (u = i[(c = i[4]) ? 5 : (c = 3, 3)], i[4] = i[5] = e) : i[0] <= d && ((o = r < 2 && d < i[1]) ? (c = 0, G.v = n, G.n = i[1]) : d < l && (o = r < 3 || i[0] > n || n > l) && (i[4] = r, i[5] = n, G.n = l, c = 0)); } if (o || r > 1) return a; throw y = !0, n; } return function (o, p, l) { if (f > 1) throw TypeError("Generator is already running"); for (y && 1 === p && d(p, l), c = p, u = l; (t = c < 2 ? e : u) || !y;) { i || (c ? c < 3 ? (c > 1 && (G.n = -1), d(c, u)) : G.n = u : G.v = u); try { if (f = 2, i) { if (c || (o = "next"), t = i[o]) { if (!(t = t.call(i, u))) throw TypeError("iterator result is not an object"); if (!t.done) return t; u = t.value, c < 2 && (c = 0); } else 1 === c && (t = i["return"]) && t.call(i), c < 2 && (u = TypeError("The iterator does not provide a '" + o + "' method"), c = 1); i = e; } else if ((t = (y = G.n < 0) ? u : r.call(n, G)) !== a) break; } catch (t) { i = e, c = 1, u = t; } finally { f = 1; } } return { value: t, done: y }; }; }(r, o, i), !0), u; } var a = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} t = Object.getPrototypeOf; var c = [][n] ? t(t([][n]())) : (_regeneratorDefine2(t = {}, n, function () { return this; }), t), u = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(c); function f(e) { return Object.setPrototypeOf ? Object.setPrototypeOf(e, GeneratorFunctionPrototype) : (e.__proto__ = GeneratorFunctionPrototype, _regeneratorDefine2(e, o, "GeneratorFunction")), e.prototype = Object.create(u), e; } return GeneratorFunction.prototype = GeneratorFunctionPrototype, _regeneratorDefine2(u, "constructor", GeneratorFunctionPrototype), _regeneratorDefine2(GeneratorFunctionPrototype, "constructor", GeneratorFunction), GeneratorFunction.displayName = "GeneratorFunction", _regeneratorDefine2(GeneratorFunctionPrototype, o, "GeneratorFunction"), _regeneratorDefine2(u), _regeneratorDefine2(u, o, "Generator"), _regeneratorDefine2(u, n, function () { return this; }), _regeneratorDefine2(u, "toString", function () { return "[object Generator]"; }), (_regenerator = function _regenerator() { return { w: i, m: f }; })(); }
3
+ function _regeneratorDefine2(e, r, n, t) { var i = Object.defineProperty; try { i({}, "", {}); } catch (e) { i = 0; } _regeneratorDefine2 = function _regeneratorDefine(e, r, n, t) { function o(r, n) { _regeneratorDefine2(e, r, function (e) { return this._invoke(r, n, e); }); } r ? i ? i(e, r, { value: n, enumerable: !t, configurable: !t, writable: !t }) : e[r] = n : (o("next", 0), o("throw", 1), o("return", 2)); }, _regeneratorDefine2(e, r, n, t); }
4
+ function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
5
+ function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
1
6
  function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
2
7
  function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
3
8
  function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
4
9
  function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
5
10
  function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
6
11
  function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
7
- import { useState } from "react";
8
- import { validatePorts } from "../../helpers/validationHelpers.js";
9
- import { safeCall } from "../../helpers/safeCall.js";
12
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
13
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
14
+ function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
15
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
16
+ function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
17
+ import { useReducer, useState, useRef, useEffect } from 'react';
18
+ import { validatePorts, validateEnvVars } from '../../helpers/validationHelpers.js';
19
+ import { IMAGE_PROFILES, resolveImageTag } from '../../helpers/constants.js';
20
+ import { safeCall } from '../../helpers/safeCall.js';
21
+ import { searchDockerHub, formatHubResult } from '../../helpers/dockerHubService.js';
22
+ var MAX_VISIBLE = 6;
23
+ var INITIAL_FORM = {
24
+ step: 0,
25
+ imageName: '',
26
+ containerName: '',
27
+ portInput: '',
28
+ envInput: '',
29
+ message: '',
30
+ messageColor: 'yellow',
31
+ suggestions: [],
32
+ selectedSuggestionIndex: -1,
33
+ visibleOffset: 0
34
+ };
35
+ function formReducer(state, action) {
36
+ switch (action.type) {
37
+ case 'RESET':
38
+ return _objectSpread({}, INITIAL_FORM);
39
+ case 'RESET_WIZARD':
40
+ return _objectSpread(_objectSpread({}, INITIAL_FORM), {}, {
41
+ message: 'Insert the name of the image to create: ',
42
+ messageColor: 'yellow'
43
+ });
44
+ case 'SET':
45
+ return _objectSpread(_objectSpread({}, state), action.payload);
46
+ default:
47
+ return state;
48
+ }
49
+ }
10
50
 
11
51
  /**
12
52
  * Custom hook to manage the container creation flow, step by step.
@@ -15,42 +55,328 @@ import { safeCall } from "../../helpers/safeCall.js";
15
55
  * @param {Object} params
16
56
  * @param {Function} params.onCreate - Callback when creation is confirmed
17
57
  * @param {Function} params.onCancel - Callback when creation is cancelled
18
- * @param {Array<string>} params.dbImages - List of DB image names for env var warning
58
+ * @param {Array<string>} params.dbImages - List of DB image names for env var warning (legacy)
59
+ * @param {Object} [params.imageProfiles] - Image profiles map for contextual validation
19
60
  * @returns {Object} Creation state, setters, and helpers
20
61
  */
21
62
  export function useContainerCreation(_ref) {
63
+ var _imageProfiles$baseNa, _imageProfiles$baseNa2;
22
64
  var onCreate = _ref.onCreate,
23
65
  onCancel = _ref.onCancel,
24
66
  _ref$dbImages = _ref.dbImages,
25
- dbImages = _ref$dbImages === void 0 ? [] : _ref$dbImages;
26
- var _useState = useState(0),
67
+ dbImages = _ref$dbImages === void 0 ? [] : _ref$dbImages,
68
+ _ref$imageProfiles = _ref.imageProfiles,
69
+ imageProfiles = _ref$imageProfiles === void 0 ? IMAGE_PROFILES : _ref$imageProfiles;
70
+ var _useReducer = useReducer(formReducer, INITIAL_FORM),
71
+ _useReducer2 = _slicedToArray(_useReducer, 2),
72
+ form = _useReducer2[0],
73
+ dispatch = _useReducer2[1];
74
+ var step = form.step,
75
+ imageName = form.imageName,
76
+ containerName = form.containerName,
77
+ portInput = form.portInput,
78
+ envInput = form.envInput,
79
+ message = form.message,
80
+ messageColor = form.messageColor,
81
+ suggestions = form.suggestions,
82
+ selectedSuggestionIndex = form.selectedSuggestionIndex,
83
+ visibleOffset = form.visibleOffset;
84
+
85
+ // Convenience setters that mirror the old useState API
86
+ var setStep = function setStep(v) {
87
+ return dispatch({
88
+ type: 'SET',
89
+ payload: {
90
+ step: v
91
+ }
92
+ });
93
+ };
94
+ var setImageName = function setImageName(v) {
95
+ return dispatch({
96
+ type: 'SET',
97
+ payload: {
98
+ imageName: v
99
+ }
100
+ });
101
+ };
102
+ var setContainerName = function setContainerName(v) {
103
+ return dispatch({
104
+ type: 'SET',
105
+ payload: {
106
+ containerName: v
107
+ }
108
+ });
109
+ };
110
+ var setPortInput = function setPortInput(v) {
111
+ return dispatch({
112
+ type: 'SET',
113
+ payload: {
114
+ portInput: v
115
+ }
116
+ });
117
+ };
118
+ var setEnvInput = function setEnvInput(v) {
119
+ return dispatch({
120
+ type: 'SET',
121
+ payload: {
122
+ envInput: typeof v === 'function' ? v(form.envInput) : v
123
+ }
124
+ });
125
+ };
126
+ var setMessage = function setMessage(v) {
127
+ return dispatch({
128
+ type: 'SET',
129
+ payload: {
130
+ message: v
131
+ }
132
+ });
133
+ };
134
+ var setMessageColor = function setMessageColor(v) {
135
+ return dispatch({
136
+ type: 'SET',
137
+ payload: {
138
+ messageColor: v
139
+ }
140
+ });
141
+ };
142
+ var setSuggestions = function setSuggestions(v) {
143
+ return dispatch({
144
+ type: 'SET',
145
+ payload: {
146
+ suggestions: v
147
+ }
148
+ });
149
+ };
150
+ var setSelectedSuggestionIndex = function setSelectedSuggestionIndex(fn) {
151
+ // Support both direct value and updater function
152
+ if (typeof fn === 'function') {
153
+ dispatch({
154
+ type: 'SET',
155
+ payload: {
156
+ selectedSuggestionIndex: fn(form.selectedSuggestionIndex)
157
+ }
158
+ });
159
+ } else {
160
+ dispatch({
161
+ type: 'SET',
162
+ payload: {
163
+ selectedSuggestionIndex: fn
164
+ }
165
+ });
166
+ }
167
+ };
168
+ var setVisibleOffset = function setVisibleOffset(fn) {
169
+ if (typeof fn === 'function') {
170
+ dispatch({
171
+ type: 'SET',
172
+ payload: {
173
+ visibleOffset: fn(form.visibleOffset)
174
+ }
175
+ });
176
+ } else {
177
+ dispatch({
178
+ type: 'SET',
179
+ payload: {
180
+ visibleOffset: fn
181
+ }
182
+ });
183
+ }
184
+ };
185
+
186
+ // Docker Hub search state (kept as useState because they are independent of form)
187
+ var _useState = useState(false),
27
188
  _useState2 = _slicedToArray(_useState, 2),
28
- step = _useState2[0],
29
- setStep = _useState2[1]; // 0: image, 1: name, 2: ports, 3: env
30
- var _useState3 = useState(""),
189
+ isSearchingHub = _useState2[0],
190
+ setIsSearchingHub = _useState2[1];
191
+ var _useState3 = useState(null),
31
192
  _useState4 = _slicedToArray(_useState3, 2),
32
- imageName = _useState4[0],
33
- setImageName = _useState4[1];
34
- var _useState5 = useState(""),
35
- _useState6 = _slicedToArray(_useState5, 2),
36
- containerName = _useState6[0],
37
- setContainerName = _useState6[1];
38
- var _useState7 = useState(""),
39
- _useState8 = _slicedToArray(_useState7, 2),
40
- portInput = _useState8[0],
41
- setPortInput = _useState8[1];
42
- var _useState9 = useState(""),
43
- _useState0 = _slicedToArray(_useState9, 2),
44
- envInput = _useState0[0],
45
- setEnvInput = _useState0[1];
46
- var _useState1 = useState(""),
47
- _useState10 = _slicedToArray(_useState1, 2),
48
- message = _useState10[0],
49
- setMessage = _useState10[1];
50
- var _useState11 = useState("yellow"),
51
- _useState12 = _slicedToArray(_useState11, 2),
52
- messageColor = _useState12[0],
53
- setMessageColor = _useState12[1];
193
+ hubResults = _useState4[0],
194
+ setHubResults = _useState4[1];
195
+ // Tracks active request: { controller: AbortController|null, requestId: number }
196
+ var activeHubRequestRef = useRef({
197
+ controller: null,
198
+ requestId: 0
199
+ });
200
+
201
+ // Timer for auto-clearing messages
202
+ var messageTimerRef = useRef(null);
203
+ function setTimedMessage(msg) {
204
+ var color = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'yellow';
205
+ var ms = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 4000;
206
+ clearTimeout(messageTimerRef.current);
207
+ dispatch({
208
+ type: 'SET',
209
+ payload: {
210
+ message: msg,
211
+ messageColor: color
212
+ }
213
+ });
214
+ if (msg) {
215
+ messageTimerRef.current = setTimeout(function () {
216
+ return dispatch({
217
+ type: 'SET',
218
+ payload: {
219
+ message: ''
220
+ }
221
+ });
222
+ }, ms);
223
+ }
224
+ }
225
+ useEffect(function () {
226
+ return function () {
227
+ return clearTimeout(messageTimerRef.current);
228
+ };
229
+ }, []);
230
+
231
+ /**
232
+ * Updates the image name input and recalculates autocomplete suggestions.
233
+ * Resets selectedSuggestionIndex to -1 on every keystroke.
234
+ * Also aborts any in-flight Hub search and clears Hub results.
235
+ *
236
+ * @param {string} value - New raw image name typed by user
237
+ */
238
+ function updateImageInput(value) {
239
+ var _activeHubRequestRef$;
240
+ // Abort any in-flight Hub search and clear its state
241
+ (_activeHubRequestRef$ = activeHubRequestRef.current.controller) === null || _activeHubRequestRef$ === void 0 || _activeHubRequestRef$.abort();
242
+ activeHubRequestRef.current.requestId += 1;
243
+ setIsSearchingHub(false);
244
+ setHubResults(null);
245
+ var lower = value.toLowerCase();
246
+ var matches = value.trim() ? Object.keys(imageProfiles).filter(function (key) {
247
+ return key.includes(lower);
248
+ }) : [];
249
+ dispatch({
250
+ type: 'SET',
251
+ payload: {
252
+ imageName: value,
253
+ selectedSuggestionIndex: -1,
254
+ visibleOffset: 0,
255
+ suggestions: matches
256
+ }
257
+ });
258
+ }
259
+
260
+ /**
261
+ * Triggers a Docker Hub search for the current imageName.
262
+ * Guards: imageName must be non-empty; no concurrent search allowed.
263
+ * Uses AbortController + requestId to handle race conditions.
264
+ */
265
+ function triggerHubSearch() {
266
+ return _triggerHubSearch.apply(this, arguments);
267
+ }
268
+ /**
269
+ * Moves the suggestion selection up (-1) or down (+1).
270
+ * Clamps index to [-1, suggestions.length - 1].
271
+ * Adjusts visibleOffset to keep selected item in the visible window.
272
+ *
273
+ * @param {number} direction - -1 (up) or 1 (down)
274
+ */
275
+ function _triggerHubSearch() {
276
+ _triggerHubSearch = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {
277
+ var _activeHubRequestRef$2;
278
+ var query, requestId, controller, results, _t;
279
+ return _regenerator().w(function (_context) {
280
+ while (1) switch (_context.p = _context.n) {
281
+ case 0:
282
+ query = imageName.trim();
283
+ if (query) {
284
+ _context.n = 1;
285
+ break;
286
+ }
287
+ return _context.a(2);
288
+ case 1:
289
+ if (!isSearchingHub) {
290
+ _context.n = 2;
291
+ break;
292
+ }
293
+ return _context.a(2);
294
+ case 2:
295
+ // Abort previous request (should already be aborted from updateImageInput,
296
+ // but guard here too)
297
+ (_activeHubRequestRef$2 = activeHubRequestRef.current.controller) === null || _activeHubRequestRef$2 === void 0 || _activeHubRequestRef$2.abort();
298
+ requestId = activeHubRequestRef.current.requestId + 1;
299
+ activeHubRequestRef.current.requestId = requestId;
300
+ controller = new AbortController();
301
+ activeHubRequestRef.current.controller = controller;
302
+ setIsSearchingHub(true);
303
+ dispatch({
304
+ type: 'SET',
305
+ payload: {
306
+ selectedSuggestionIndex: -1,
307
+ visibleOffset: 0
308
+ }
309
+ });
310
+ _context.p = 3;
311
+ _context.n = 4;
312
+ return searchDockerHub(query, {
313
+ signal: controller.signal
314
+ });
315
+ case 4:
316
+ results = _context.v;
317
+ if (!(activeHubRequestRef.current.requestId !== requestId)) {
318
+ _context.n = 5;
319
+ break;
320
+ }
321
+ return _context.a(2);
322
+ case 5:
323
+ setHubResults(results.map(formatHubResult));
324
+ _context.n = 8;
325
+ break;
326
+ case 6:
327
+ _context.p = 6;
328
+ _t = _context.v;
329
+ if (!(activeHubRequestRef.current.requestId !== requestId)) {
330
+ _context.n = 7;
331
+ break;
332
+ }
333
+ return _context.a(2);
334
+ case 7:
335
+ // Silence AbortError; for all other errors fall back to static suggestions
336
+ setHubResults(null);
337
+ case 8:
338
+ _context.p = 8;
339
+ if (activeHubRequestRef.current.requestId === requestId) {
340
+ setIsSearchingHub(false);
341
+ }
342
+ return _context.f(8);
343
+ case 9:
344
+ return _context.a(2);
345
+ }
346
+ }, _callee, null, [[3, 6, 8, 9]]);
347
+ }));
348
+ return _triggerHubSearch.apply(this, arguments);
349
+ }
350
+ function moveSuggestionSelection(direction) {
351
+ var next = Math.max(-1, Math.min(suggestions.length - 1, selectedSuggestionIndex + direction));
352
+ var newOffset = visibleOffset;
353
+ if (next < visibleOffset) newOffset = Math.max(0, next);else if (next >= visibleOffset + MAX_VISIBLE) newOffset = next - MAX_VISIBLE + 1;
354
+ dispatch({
355
+ type: 'SET',
356
+ payload: {
357
+ selectedSuggestionIndex: next,
358
+ visibleOffset: newOffset
359
+ }
360
+ });
361
+ }
362
+
363
+ /**
364
+ * Applies the currently focused suggestion to imageName.
365
+ * Does NOT advance the step. Clears suggestions after applying.
366
+ */
367
+ function applyFocusedSuggestion() {
368
+ if (selectedSuggestionIndex < 0 || selectedSuggestionIndex >= suggestions.length) return;
369
+ var chosen = suggestions[selectedSuggestionIndex];
370
+ dispatch({
371
+ type: 'SET',
372
+ payload: {
373
+ imageName: resolveImageTag(chosen, imageProfiles),
374
+ suggestions: [],
375
+ selectedSuggestionIndex: -1,
376
+ visibleOffset: 0
377
+ }
378
+ });
379
+ }
54
380
 
55
381
  /**
56
382
  * Advances to the next step, with validation and feedback.
@@ -58,42 +384,67 @@ export function useContainerCreation(_ref) {
58
384
  function nextStep() {
59
385
  if (step === 0) {
60
386
  if (!imageName.trim()) {
61
- setMessage("Image name cannot be empty.");
62
- setMessageColor("red");
387
+ setTimedMessage('Image name cannot be empty.', 'red');
63
388
  return;
64
389
  }
65
- setStep(1);
66
- setMessage("Optional: Enter container name or leave empty and press Enter");
67
- setMessageColor("yellow");
390
+ var resolved = resolveImageTag(imageName, imageProfiles);
391
+ dispatch({
392
+ type: 'SET',
393
+ payload: {
394
+ imageName: resolved,
395
+ step: 1
396
+ }
397
+ });
398
+ setTimedMessage('Optional: Enter container name or leave empty and press Enter', 'yellow');
68
399
  return;
69
400
  }
70
401
  if (step === 1) {
71
- setStep(2);
72
- setMessage("Optional: Enter ports (format 8080:80,443:443) or leave empty and press Enter");
73
- setMessageColor("yellow");
402
+ dispatch({
403
+ type: 'SET',
404
+ payload: {
405
+ step: 2
406
+ }
407
+ });
408
+ setTimedMessage('Optional: Enter ports (format 8080:80,443:443) or leave empty and press Enter', 'yellow');
74
409
  return;
75
410
  }
76
411
  if (step === 2) {
77
412
  // Ports are now optional - only validate if provided
78
413
  if (portInput.trim() && !validatePorts(portInput)) {
79
- setMessage("Port format must be host:container and both must be numbers (e.g. 8080:80)");
80
- setMessageColor("red");
414
+ setTimedMessage('Port format must be host:container and both must be numbers (e.g. 8080:80)', 'red');
81
415
  return;
82
416
  }
83
- setStep(3);
417
+ dispatch({
418
+ type: 'SET',
419
+ payload: {
420
+ step: 3
421
+ }
422
+ });
84
423
  var isDb = dbImages.some(function (db) {
85
424
  return imageName.trim().toLowerCase().includes(db);
86
425
  });
87
- if (isDb) {
88
- setMessage("Warning: This image usually requires environment variables (e.g. MYSQL_ROOT_PASSWORD=my-secret-pw for MySQL, POSTGRES_PASSWORD=yourpassword for Postgres). Enter them as VAR=val,VAR2=val2 or leave empty and press Enter.");
89
- setMessageColor("yellow");
426
+ // Check if the image has a profile with required env vars
427
+ var _baseName = imageName.trim().toLowerCase().split(':')[0].split('/').pop();
428
+ var profile = imageProfiles[_baseName];
429
+ if (profile && profile.requiredEnv && profile.requiredEnv.length) {
430
+ var suggestedPart = profile.suggestedEnv && profile.suggestedEnv.length ? " | Suggested: ".concat(profile.suggestedEnv.join(', ')) : '';
431
+ setTimedMessage("Required env vars for ".concat(_baseName, ": ").concat(profile.requiredEnv.join(', '), ". Enter as VAR=val,VAR2=val2").concat(suggestedPart), 'yellow');
432
+ } else if (profile && profile.suggestedEnv && profile.suggestedEnv.length) {
433
+ setTimedMessage("Suggested env vars for ".concat(_baseName, ": ").concat(profile.suggestedEnv.join(', '), ". Enter as VAR=val,VAR2=val2 or leave empty."), 'yellow');
434
+ } else if (isDb) {
435
+ setTimedMessage('Warning: This image usually requires environment variables (e.g. MYSQL_ROOT_PASSWORD=my-secret-pw for MySQL, POSTGRES_PASSWORD=yourpassword for Postgres). Enter them as VAR=val,VAR2=val2 or leave empty and press Enter.', 'yellow');
90
436
  } else {
91
- setMessage("Optional: Enter environment variables (format VAR1=val1,VAR2=val2) or leave empty and press Enter");
92
- setMessageColor("yellow");
437
+ setTimedMessage('Optional: Enter environment variables (format VAR1=val1,VAR2=val2) or leave empty and press Enter', 'yellow');
93
438
  }
94
439
  return;
95
440
  }
96
441
  if (step === 3) {
442
+ // Contextual env validation using image profiles
443
+ var result = validateEnvVars(envInput, imageName, imageProfiles);
444
+ if (!result.valid) {
445
+ setTimedMessage(result.errors.join(' | '), 'red');
446
+ return;
447
+ }
97
448
  // Final step: call onCreate with all data (safely)
98
449
  safeCall(onCreate, {
99
450
  imageName: imageName,
@@ -108,13 +459,9 @@ export function useContainerCreation(_ref) {
108
459
  * Cancels the creation process and resets state.
109
460
  */
110
461
  function cancelCreation() {
111
- setStep(0);
112
- setImageName("");
113
- setContainerName("");
114
- setPortInput("");
115
- setEnvInput("");
116
- setMessage("");
117
- setMessageColor("yellow");
462
+ dispatch({
463
+ type: 'RESET'
464
+ });
118
465
  safeCall(onCancel);
119
466
  }
120
467
 
@@ -123,14 +470,46 @@ export function useContainerCreation(_ref) {
123
470
  * Used by the command router when the user presses 'c' to open the creation wizard.
124
471
  */
125
472
  function resetCreation() {
126
- setStep(0);
127
- setImageName("");
128
- setContainerName("");
129
- setPortInput("");
130
- setEnvInput("");
131
- setMessage("Insert the name of the image to create: ");
132
- setMessageColor("yellow");
473
+ dispatch({
474
+ type: 'RESET_WIZARD'
475
+ });
476
+ }
477
+
478
+ /**
479
+ * Inserts the next pending suggested env var into envInput.
480
+ * Derives "next" by comparing keys already in envInput against suggestedEnv.
481
+ * When all suggestions are already present, sets a feedback message.
482
+ */
483
+ function insertNextSuggestedEnv() {
484
+ var _profile$suggestedEnv;
485
+ var baseName = imageName.trim().toLowerCase().split(':')[0].split('/').pop();
486
+ var profile = imageProfiles[baseName];
487
+ var suggested = (_profile$suggestedEnv = profile === null || profile === void 0 ? void 0 : profile.suggestedEnv) !== null && _profile$suggestedEnv !== void 0 ? _profile$suggestedEnv : [];
488
+ if (!suggested.length) return;
489
+ var addedKeys = envInput.split(',').map(function (s) {
490
+ return s.split('=')[0].trim();
491
+ }).filter(Boolean);
492
+ var next = suggested.find(function (s) {
493
+ return !addedKeys.includes(s.split('=')[0]);
494
+ });
495
+ if (!next) {
496
+ setTimedMessage('All suggested env vars added', 'yellow');
497
+ return;
498
+ }
499
+ var newEnvInput = envInput ? "".concat(envInput, ",").concat(next) : next;
500
+ dispatch({
501
+ type: 'SET',
502
+ payload: {
503
+ envInput: newEnvInput
504
+ }
505
+ });
133
506
  }
507
+
508
+ /**
509
+ * True when the current image profile has at least one suggestedEnv entry.
510
+ */
511
+ var baseName = imageName.trim().toLowerCase().split(':')[0].split('/').pop();
512
+ var hasSuggestedEnv = ((_imageProfiles$baseNa = (_imageProfiles$baseNa2 = imageProfiles[baseName]) === null || _imageProfiles$baseNa2 === void 0 || (_imageProfiles$baseNa2 = _imageProfiles$baseNa2.suggestedEnv) === null || _imageProfiles$baseNa2 === void 0 ? void 0 : _imageProfiles$baseNa2.length) !== null && _imageProfiles$baseNa !== void 0 ? _imageProfiles$baseNa : 0) > 0;
134
513
  return {
135
514
  step: step,
136
515
  setStep: setStep,
@@ -146,8 +525,19 @@ export function useContainerCreation(_ref) {
146
525
  setMessage: setMessage,
147
526
  messageColor: messageColor,
148
527
  setMessageColor: setMessageColor,
528
+ suggestions: suggestions,
529
+ selectedSuggestionIndex: selectedSuggestionIndex,
530
+ visibleOffset: visibleOffset,
531
+ isSearchingHub: isSearchingHub,
532
+ hubResults: hubResults,
533
+ updateImageInput: updateImageInput,
534
+ triggerHubSearch: triggerHubSearch,
535
+ moveSuggestionSelection: moveSuggestionSelection,
536
+ applyFocusedSuggestion: applyFocusedSuggestion,
149
537
  nextStep: nextStep,
150
538
  cancelCreation: cancelCreation,
151
- resetCreation: resetCreation
539
+ resetCreation: resetCreation,
540
+ insertNextSuggestedEnv: insertNextSuggestedEnv,
541
+ hasSuggestedEnv: hasSuggestedEnv
152
542
  };
153
543
  }
@@ -4,8 +4,8 @@ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r)
4
4
  function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
5
5
  function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
6
6
  function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
7
- import { useState, useRef } from "react";
8
- import { safeCall } from "../../helpers/safeCall.js";
7
+ import { useState, useRef } from 'react';
8
+ import { safeCall } from '../../helpers/safeCall.js';
9
9
 
10
10
  /**
11
11
  * Custom hook to manage the logs viewer state and stream reference.
@@ -23,15 +23,15 @@ export function useDebugLogs() {
23
23
  var unsubscribe = logger.subscribe(function (entry) {
24
24
  setDebugLogs(function (prev) {
25
25
  var extras = (entry.args || []).map(function (arg) {
26
- if (typeof arg === "string") return arg;
27
- if (typeof arg === "number" || typeof arg === "boolean") return String(arg);
26
+ if (typeof arg === 'string') return arg;
27
+ if (typeof arg === 'number' || typeof arg === 'boolean') return String(arg);
28
28
  try {
29
29
  return JSON.stringify(arg);
30
30
  } catch (err) {
31
- return "[unserializable]";
31
+ return '[unserializable]';
32
32
  }
33
33
  }).filter(Boolean);
34
- var line = extras.length ? "".concat(entry.formatted, " ").concat(extras.join(" ")) : entry.formatted;
34
+ var line = extras.length ? "".concat(entry.formatted, " ").concat(extras.join(' ')) : entry.formatted;
35
35
  var next = [].concat(_toConsumableArray(prev), [line]);
36
36
  return next.slice(-200);
37
37
  });