cdd-cli 3.2.2 → 4.1.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 (126) hide show
  1. package/README.es.md +222 -0
  2. package/README.md +108 -26
  3. package/dist/App.js +7 -1
  4. package/dist/components/ContainerCreationPrompt.js +26 -10
  5. package/dist/components/ControlsHUD.js +106 -0
  6. package/dist/components/SuggestionPanel.js +16 -7
  7. package/dist/components/UsageMenu.js +1 -1
  8. package/dist/helpers/appState.js +26 -0
  9. package/dist/helpers/constants.js +60 -21
  10. package/dist/helpers/dockerHubService.js +111 -0
  11. package/dist/helpers/dockerService/serviceComponents/containerExec.js +211 -0
  12. package/dist/helpers/logger.js +1 -1
  13. package/dist/hooks/creation/useContainerActions.js +22 -5
  14. package/dist/hooks/creation/useContainerCreation.js +414 -107
  15. package/dist/hooks/useContainerCommandRouter.js +13 -3
  16. package/dist/hooks/useControls.js +28 -7
  17. package/dist/hooks/useShellMode.js +101 -0
  18. package/dist/index.js +10 -1
  19. package/package.json +4 -1
  20. package/.atl/skill-registry.md +0 -30
  21. package/.editorconfig +0 -12
  22. package/.eslintrc.json +0 -30
  23. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -30
  24. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
  25. package/.github/PULL_REQUEST_TEMPLATE/pull_request.md +0 -14
  26. package/.github/workflows/ci.yml +0 -32
  27. package/.github/workflows/docs.yml +0 -42
  28. package/.prettierignore +0 -4
  29. package/.prettierrc +0 -8
  30. package/CHANGELOG.md +0 -165
  31. package/CODE_OF_CONDUCT.md +0 -12
  32. package/CONTRIBUTING.md +0 -35
  33. package/SECURITY.md +0 -5
  34. package/__mocks__/ink.cjs +0 -17
  35. package/_config.yml +0 -13
  36. package/babel.config.cjs +0 -23
  37. package/coverage/clover.xml +0 -694
  38. package/coverage/coverage-final.json +0 -21
  39. package/coverage/lcov-report/base.css +0 -224
  40. package/coverage/lcov-report/block-navigation.js +0 -87
  41. package/coverage/lcov-report/components/ContainerCreationPrompt.jsx.html +0 -376
  42. package/coverage/lcov-report/components/PromptField.jsx.html +0 -229
  43. package/coverage/lcov-report/components/SuggestionPanel.jsx.html +0 -244
  44. package/coverage/lcov-report/components/index.html +0 -146
  45. package/coverage/lcov-report/favicon.png +0 -0
  46. package/coverage/lcov-report/helpers/constants.js.html +0 -298
  47. package/coverage/lcov-report/helpers/containerOptionsBuilder.js.html +0 -244
  48. package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerActions.js.html +0 -931
  49. package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerList.js.html +0 -307
  50. package/coverage/lcov-report/helpers/dockerService/serviceComponents/index.html +0 -131
  51. package/coverage/lcov-report/helpers/imageNameUtils.js.html +0 -181
  52. package/coverage/lcov-report/helpers/index.html +0 -191
  53. package/coverage/lcov-report/helpers/logger.js.html +0 -424
  54. package/coverage/lcov-report/helpers/safeCall.js.html +0 -139
  55. package/coverage/lcov-report/helpers/validationHelpers.js.html +0 -346
  56. package/coverage/lcov-report/hooks/creation/index.html +0 -146
  57. package/coverage/lcov-report/hooks/creation/useContainerActions.js.html +0 -313
  58. package/coverage/lcov-report/hooks/creation/useContainerCreation.js.html +0 -805
  59. package/coverage/lcov-report/hooks/creation/useLogsViewer.js.html +0 -238
  60. package/coverage/lcov-report/hooks/debug/index.html +0 -116
  61. package/coverage/lcov-report/hooks/debug/useDebugLogs.js.html +0 -187
  62. package/coverage/lcov-report/hooks/index.html +0 -161
  63. package/coverage/lcov-report/hooks/navigation/index.html +0 -116
  64. package/coverage/lcov-report/hooks/navigation/useContainerSelection.js.html +0 -169
  65. package/coverage/lcov-report/hooks/useContainerCommandRouter.js.html +0 -454
  66. package/coverage/lcov-report/hooks/useControls.js.html +0 -826
  67. package/coverage/lcov-report/hooks/useEraseConfirmation.js.html +0 -202
  68. package/coverage/lcov-report/hooks/useExitHandler.js.html +0 -292
  69. package/coverage/lcov-report/index.html +0 -206
  70. package/coverage/lcov-report/prettify.css +0 -1
  71. package/coverage/lcov-report/prettify.js +0 -2
  72. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  73. package/coverage/lcov-report/sorter.js +0 -210
  74. package/coverage/lcov.info +0 -1402
  75. package/fix-imports.cjs +0 -54
  76. package/jest.config.cjs +0 -14
  77. package/src/App.jsx +0 -96
  78. package/src/components/ContainerCreationPrompt.jsx +0 -97
  79. package/src/components/ContainerList.jsx +0 -32
  80. package/src/components/ContainerRow.jsx +0 -84
  81. package/src/components/ContainerSection.jsx +0 -24
  82. package/src/components/Footer.jsx +0 -15
  83. package/src/components/Header.jsx +0 -41
  84. package/src/components/LogViewer.jsx +0 -36
  85. package/src/components/MessageFeedback.jsx +0 -25
  86. package/src/components/PromptField.jsx +0 -48
  87. package/src/components/StatsBar.jsx +0 -70
  88. package/src/components/SuggestionPanel.jsx +0 -53
  89. package/src/components/UsageMenu.jsx +0 -52
  90. package/src/helpers/appInfo.js +0 -23
  91. package/src/helpers/constants.js +0 -71
  92. package/src/helpers/containerOptionsBuilder.js +0 -53
  93. package/src/helpers/dockerService/dockerService.js +0 -28
  94. package/src/helpers/dockerService/serviceComponents/containerActions.js +0 -282
  95. package/src/helpers/dockerService/serviceComponents/containerList.js +0 -74
  96. package/src/helpers/dockerService/serviceComponents/containerLogs.js +0 -60
  97. package/src/helpers/dockerService/serviceComponents/containerStats.js +0 -69
  98. package/src/helpers/dockerService/serviceComponents/imageUtils.js +0 -32
  99. package/src/helpers/exitWithMessage.js +0 -32
  100. package/src/helpers/imageNameUtils.js +0 -32
  101. package/src/helpers/logger.js +0 -113
  102. package/src/helpers/safeCall.js +0 -18
  103. package/src/helpers/validationHelpers.js +0 -87
  104. package/src/hooks/creation/useContainerActions.js +0 -76
  105. package/src/hooks/creation/useContainerCreation.js +0 -240
  106. package/src/hooks/creation/useLogsViewer.js +0 -51
  107. package/src/hooks/debug/useDebugLogs.js +0 -34
  108. package/src/hooks/navigation/useContainerSelection.js +0 -28
  109. package/src/hooks/useContainerCommandRouter.js +0 -123
  110. package/src/hooks/useContainerStats.js +0 -44
  111. package/src/hooks/useContainers.js +0 -26
  112. package/src/hooks/useControls.js +0 -247
  113. package/src/hooks/useEraseConfirmation.js +0 -39
  114. package/src/hooks/useExitHandler.js +0 -69
  115. package/src/index.js +0 -16
  116. package/test/ContainerCreationPrompt.dom.test.js +0 -68
  117. package/test/SuggestionPanel.dom.test.js +0 -53
  118. package/test/constants.test.js +0 -34
  119. package/test/containerActions.test.js +0 -229
  120. package/test/containerList.test.js +0 -79
  121. package/test/containerOptionsBuilder.test.js +0 -36
  122. package/test/jest.setup.js +0 -7
  123. package/test/safeCall.test.js +0 -25
  124. package/test/useContainerCreation.dom.test.js +0 -264
  125. package/test/useControls.dom.test.js +0 -313
  126. package/test/validationHelpers.test.js +0 -111
@@ -1,14 +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';
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';
8
18
  import { validatePorts, validateEnvVars } from '../../helpers/validationHelpers.js';
9
- import { IMAGE_PROFILES } from '../../helpers/constants.js';
19
+ import { IMAGE_PROFILES, resolveImageTag } from '../../helpers/constants.js';
10
20
  import { safeCall } from '../../helpers/safeCall.js';
21
+ import { searchDockerHub, formatHubResult } from '../../helpers/dockerHubService.js';
11
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
+ }
12
50
 
13
51
  /**
14
52
  * Custom hook to manage the container creation flow, step by step.
@@ -22,76 +60,230 @@ var MAX_VISIBLE = 6;
22
60
  * @returns {Object} Creation state, setters, and helpers
23
61
  */
24
62
  export function useContainerCreation(_ref) {
63
+ var _imageProfiles$baseNa, _imageProfiles$baseNa2;
25
64
  var onCreate = _ref.onCreate,
26
65
  onCancel = _ref.onCancel,
27
66
  _ref$dbImages = _ref.dbImages,
28
67
  dbImages = _ref$dbImages === void 0 ? [] : _ref$dbImages,
29
68
  _ref$imageProfiles = _ref.imageProfiles,
30
69
  imageProfiles = _ref$imageProfiles === void 0 ? IMAGE_PROFILES : _ref$imageProfiles;
31
- var _useState = useState(0),
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),
32
188
  _useState2 = _slicedToArray(_useState, 2),
33
- step = _useState2[0],
34
- setStep = _useState2[1]; // 0: image, 1: name, 2: ports, 3: env
35
- var _useState3 = useState(''),
189
+ isSearchingHub = _useState2[0],
190
+ setIsSearchingHub = _useState2[1];
191
+ var _useState3 = useState(null),
36
192
  _useState4 = _slicedToArray(_useState3, 2),
37
- imageName = _useState4[0],
38
- setImageName = _useState4[1];
39
- var _useState5 = useState(''),
40
- _useState6 = _slicedToArray(_useState5, 2),
41
- containerName = _useState6[0],
42
- setContainerName = _useState6[1];
43
- var _useState7 = useState(''),
44
- _useState8 = _slicedToArray(_useState7, 2),
45
- portInput = _useState8[0],
46
- setPortInput = _useState8[1];
47
- var _useState9 = useState(''),
48
- _useState0 = _slicedToArray(_useState9, 2),
49
- envInput = _useState0[0],
50
- setEnvInput = _useState0[1];
51
- var _useState1 = useState(''),
52
- _useState10 = _slicedToArray(_useState1, 2),
53
- message = _useState10[0],
54
- setMessage = _useState10[1];
55
- var _useState11 = useState('yellow'),
56
- _useState12 = _slicedToArray(_useState11, 2),
57
- messageColor = _useState12[0],
58
- 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
+ });
59
200
 
60
- // Autocomplete state
61
- var _useState13 = useState([]),
62
- _useState14 = _slicedToArray(_useState13, 2),
63
- suggestions = _useState14[0],
64
- setSuggestions = _useState14[1];
65
- var _useState15 = useState(-1),
66
- _useState16 = _slicedToArray(_useState15, 2),
67
- selectedSuggestionIndex = _useState16[0],
68
- setSelectedSuggestionIndex = _useState16[1];
69
- var _useState17 = useState(0),
70
- _useState18 = _slicedToArray(_useState17, 2),
71
- visibleOffset = _useState18[0],
72
- setVisibleOffset = _useState18[1];
201
+ // Timer for auto-clearing messages
202
+ var messageTimerRef = useRef(null);
203
+
204
+ /**
205
+ * Sets a permanent message (e.g. step instructions). No auto-clear.
206
+ */
207
+ function setStepMessage(msg) {
208
+ var color = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'yellow';
209
+ clearTimeout(messageTimerRef.current);
210
+ dispatch({
211
+ type: 'SET',
212
+ payload: {
213
+ message: msg,
214
+ messageColor: color
215
+ }
216
+ });
217
+ }
218
+
219
+ /**
220
+ * Sets a transient feedback message (e.g. validation errors). Auto-clears after `ms`.
221
+ */
222
+ function setTimedMessage(msg) {
223
+ var color = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'yellow';
224
+ var ms = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 4000;
225
+ clearTimeout(messageTimerRef.current);
226
+ dispatch({
227
+ type: 'SET',
228
+ payload: {
229
+ message: msg,
230
+ messageColor: color
231
+ }
232
+ });
233
+ if (msg) {
234
+ messageTimerRef.current = setTimeout(function () {
235
+ return dispatch({
236
+ type: 'SET',
237
+ payload: {
238
+ message: ''
239
+ }
240
+ });
241
+ }, ms);
242
+ }
243
+ }
244
+ useEffect(function () {
245
+ return function () {
246
+ return clearTimeout(messageTimerRef.current);
247
+ };
248
+ }, []);
73
249
 
74
250
  /**
75
251
  * Updates the image name input and recalculates autocomplete suggestions.
76
252
  * Resets selectedSuggestionIndex to -1 on every keystroke.
253
+ * Also aborts any in-flight Hub search and clears Hub results.
77
254
  *
78
255
  * @param {string} value - New raw image name typed by user
79
256
  */
80
257
  function updateImageInput(value) {
81
- setImageName(value);
82
- setSelectedSuggestionIndex(-1);
83
- setVisibleOffset(0);
84
- if (!value.trim()) {
85
- setSuggestions([]);
86
- return;
87
- }
258
+ var _activeHubRequestRef$;
259
+ // Abort any in-flight Hub search and clear its state
260
+ (_activeHubRequestRef$ = activeHubRequestRef.current.controller) === null || _activeHubRequestRef$ === void 0 || _activeHubRequestRef$.abort();
261
+ activeHubRequestRef.current.requestId += 1;
262
+ setIsSearchingHub(false);
263
+ setHubResults(null);
88
264
  var lower = value.toLowerCase();
89
- var matches = Object.keys(imageProfiles).filter(function (key) {
265
+ var matches = value.trim() ? Object.keys(imageProfiles).filter(function (key) {
90
266
  return key.includes(lower);
267
+ }) : [];
268
+ dispatch({
269
+ type: 'SET',
270
+ payload: {
271
+ imageName: value,
272
+ selectedSuggestionIndex: -1,
273
+ visibleOffset: 0,
274
+ suggestions: matches
275
+ }
91
276
  });
92
- setSuggestions(matches);
93
277
  }
94
278
 
279
+ /**
280
+ * Triggers a Docker Hub search for the current imageName.
281
+ * Guards: imageName must be non-empty; no concurrent search allowed.
282
+ * Uses AbortController + requestId to handle race conditions.
283
+ */
284
+ function triggerHubSearch() {
285
+ return _triggerHubSearch.apply(this, arguments);
286
+ }
95
287
  /**
96
288
  * Moves the suggestion selection up (-1) or down (+1).
97
289
  * Clamps index to [-1, suggestions.length - 1].
@@ -99,16 +291,91 @@ export function useContainerCreation(_ref) {
99
291
  *
100
292
  * @param {number} direction - -1 (up) or 1 (down)
101
293
  */
294
+ function _triggerHubSearch() {
295
+ _triggerHubSearch = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {
296
+ var _activeHubRequestRef$2;
297
+ var query, requestId, controller, results, _t;
298
+ return _regenerator().w(function (_context) {
299
+ while (1) switch (_context.p = _context.n) {
300
+ case 0:
301
+ query = imageName.trim();
302
+ if (query) {
303
+ _context.n = 1;
304
+ break;
305
+ }
306
+ return _context.a(2);
307
+ case 1:
308
+ if (!isSearchingHub) {
309
+ _context.n = 2;
310
+ break;
311
+ }
312
+ return _context.a(2);
313
+ case 2:
314
+ // Abort previous request (should already be aborted from updateImageInput,
315
+ // but guard here too)
316
+ (_activeHubRequestRef$2 = activeHubRequestRef.current.controller) === null || _activeHubRequestRef$2 === void 0 || _activeHubRequestRef$2.abort();
317
+ requestId = activeHubRequestRef.current.requestId + 1;
318
+ activeHubRequestRef.current.requestId = requestId;
319
+ controller = new AbortController();
320
+ activeHubRequestRef.current.controller = controller;
321
+ setIsSearchingHub(true);
322
+ dispatch({
323
+ type: 'SET',
324
+ payload: {
325
+ selectedSuggestionIndex: -1,
326
+ visibleOffset: 0
327
+ }
328
+ });
329
+ _context.p = 3;
330
+ _context.n = 4;
331
+ return searchDockerHub(query, {
332
+ signal: controller.signal
333
+ });
334
+ case 4:
335
+ results = _context.v;
336
+ if (!(activeHubRequestRef.current.requestId !== requestId)) {
337
+ _context.n = 5;
338
+ break;
339
+ }
340
+ return _context.a(2);
341
+ case 5:
342
+ setHubResults(results.map(formatHubResult));
343
+ _context.n = 8;
344
+ break;
345
+ case 6:
346
+ _context.p = 6;
347
+ _t = _context.v;
348
+ if (!(activeHubRequestRef.current.requestId !== requestId)) {
349
+ _context.n = 7;
350
+ break;
351
+ }
352
+ return _context.a(2);
353
+ case 7:
354
+ // Silence AbortError; for all other errors fall back to static suggestions
355
+ setHubResults(null);
356
+ case 8:
357
+ _context.p = 8;
358
+ if (activeHubRequestRef.current.requestId === requestId) {
359
+ setIsSearchingHub(false);
360
+ }
361
+ return _context.f(8);
362
+ case 9:
363
+ return _context.a(2);
364
+ }
365
+ }, _callee, null, [[3, 6, 8, 9]]);
366
+ }));
367
+ return _triggerHubSearch.apply(this, arguments);
368
+ }
102
369
  function moveSuggestionSelection(direction) {
103
- setSelectedSuggestionIndex(function (prev) {
104
- var next = Math.max(-1, Math.min(suggestions.length - 1, prev + direction));
105
- // Adjust visibleOffset to keep selected in view
106
- setVisibleOffset(function (offset) {
107
- if (next < offset) return Math.max(0, next);
108
- if (next >= offset + MAX_VISIBLE) return next - MAX_VISIBLE + 1;
109
- return offset;
110
- });
111
- return next;
370
+ var next = Math.max(-1, Math.min(suggestions.length - 1, selectedSuggestionIndex + direction));
371
+ var newOffset = visibleOffset;
372
+ if (next < visibleOffset) newOffset = Math.max(0, next);else if (next >= visibleOffset + MAX_VISIBLE) newOffset = next - MAX_VISIBLE + 1;
373
+ dispatch({
374
+ type: 'SET',
375
+ payload: {
376
+ selectedSuggestionIndex: next,
377
+ visibleOffset: newOffset
378
+ }
112
379
  });
113
380
  }
114
381
 
@@ -119,10 +386,15 @@ export function useContainerCreation(_ref) {
119
386
  function applyFocusedSuggestion() {
120
387
  if (selectedSuggestionIndex < 0 || selectedSuggestionIndex >= suggestions.length) return;
121
388
  var chosen = suggestions[selectedSuggestionIndex];
122
- setImageName(chosen);
123
- setSuggestions([]);
124
- setSelectedSuggestionIndex(-1);
125
- setVisibleOffset(0);
389
+ dispatch({
390
+ type: 'SET',
391
+ payload: {
392
+ imageName: resolveImageTag(chosen, imageProfiles),
393
+ suggestions: [],
394
+ selectedSuggestionIndex: -1,
395
+ visibleOffset: 0
396
+ }
397
+ });
126
398
  }
127
399
 
128
400
  /**
@@ -131,48 +403,57 @@ export function useContainerCreation(_ref) {
131
403
  function nextStep() {
132
404
  if (step === 0) {
133
405
  if (!imageName.trim()) {
134
- setMessage('Image name cannot be empty.');
135
- setMessageColor('red');
406
+ setTimedMessage('Image name cannot be empty.', 'red');
136
407
  return;
137
408
  }
138
- setStep(1);
139
- setMessage('Optional: Enter container name or leave empty and press Enter');
140
- setMessageColor('yellow');
409
+ var resolved = resolveImageTag(imageName, imageProfiles);
410
+ dispatch({
411
+ type: 'SET',
412
+ payload: {
413
+ imageName: resolved,
414
+ step: 1
415
+ }
416
+ });
417
+ setStepMessage('Optional: Enter container name or leave empty and press Enter', 'yellow');
141
418
  return;
142
419
  }
143
420
  if (step === 1) {
144
- setStep(2);
145
- setMessage('Optional: Enter ports (format 8080:80,443:443) or leave empty and press Enter');
146
- setMessageColor('yellow');
421
+ dispatch({
422
+ type: 'SET',
423
+ payload: {
424
+ step: 2
425
+ }
426
+ });
427
+ setStepMessage('Optional: Enter ports (format 8080:80,443:443) or leave empty and press Enter', 'yellow');
147
428
  return;
148
429
  }
149
430
  if (step === 2) {
150
431
  // Ports are now optional - only validate if provided
151
432
  if (portInput.trim() && !validatePorts(portInput)) {
152
- setMessage('Port format must be host:container and both must be numbers (e.g. 8080:80)');
153
- setMessageColor('red');
433
+ setTimedMessage('Port format must be host:container and both must be numbers (e.g. 8080:80)', 'red');
154
434
  return;
155
435
  }
156
- setStep(3);
436
+ dispatch({
437
+ type: 'SET',
438
+ payload: {
439
+ step: 3
440
+ }
441
+ });
157
442
  var isDb = dbImages.some(function (db) {
158
443
  return imageName.trim().toLowerCase().includes(db);
159
444
  });
160
445
  // Check if the image has a profile with required env vars
161
- var baseName = imageName.trim().toLowerCase().split(':')[0].split('/').pop();
162
- var profile = imageProfiles[baseName];
446
+ var _baseName = imageName.trim().toLowerCase().split(':')[0].split('/').pop();
447
+ var profile = imageProfiles[_baseName];
163
448
  if (profile && profile.requiredEnv && profile.requiredEnv.length) {
164
449
  var suggestedPart = profile.suggestedEnv && profile.suggestedEnv.length ? " | Suggested: ".concat(profile.suggestedEnv.join(', ')) : '';
165
- setMessage("Required env vars for ".concat(baseName, ": ").concat(profile.requiredEnv.join(', '), ". Enter as VAR=val,VAR2=val2").concat(suggestedPart));
166
- setMessageColor('yellow');
450
+ setStepMessage("Required env vars for ".concat(_baseName, ": ").concat(profile.requiredEnv.join(', '), ". Enter as VAR=val,VAR2=val2").concat(suggestedPart), 'yellow');
167
451
  } else if (profile && profile.suggestedEnv && profile.suggestedEnv.length) {
168
- setMessage("Suggested env vars for ".concat(baseName, ": ").concat(profile.suggestedEnv.join(', '), ". Enter as VAR=val,VAR2=val2 or leave empty."));
169
- setMessageColor('yellow');
452
+ setStepMessage("Suggested env vars for ".concat(_baseName, ": ").concat(profile.suggestedEnv.join(', '), ". Enter as VAR=val,VAR2=val2 or leave empty."), 'yellow');
170
453
  } else if (isDb) {
171
- 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.');
172
- setMessageColor('yellow');
454
+ setStepMessage('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');
173
455
  } else {
174
- setMessage('Optional: Enter environment variables (format VAR1=val1,VAR2=val2) or leave empty and press Enter');
175
- setMessageColor('yellow');
456
+ setStepMessage('Optional: Enter environment variables (format VAR1=val1,VAR2=val2) or leave empty and press Enter', 'yellow');
176
457
  }
177
458
  return;
178
459
  }
@@ -180,8 +461,7 @@ export function useContainerCreation(_ref) {
180
461
  // Contextual env validation using image profiles
181
462
  var result = validateEnvVars(envInput, imageName, imageProfiles);
182
463
  if (!result.valid) {
183
- setMessage(result.errors.join(' | '));
184
- setMessageColor('red');
464
+ setTimedMessage(result.errors.join(' | '), 'red');
185
465
  return;
186
466
  }
187
467
  // Final step: call onCreate with all data (safely)
@@ -198,16 +478,9 @@ export function useContainerCreation(_ref) {
198
478
  * Cancels the creation process and resets state.
199
479
  */
200
480
  function cancelCreation() {
201
- setStep(0);
202
- setImageName('');
203
- setContainerName('');
204
- setPortInput('');
205
- setEnvInput('');
206
- setMessage('');
207
- setMessageColor('yellow');
208
- setSuggestions([]);
209
- setSelectedSuggestionIndex(-1);
210
- setVisibleOffset(0);
481
+ dispatch({
482
+ type: 'RESET'
483
+ });
211
484
  safeCall(onCancel);
212
485
  }
213
486
 
@@ -216,17 +489,46 @@ export function useContainerCreation(_ref) {
216
489
  * Used by the command router when the user presses 'c' to open the creation wizard.
217
490
  */
218
491
  function resetCreation() {
219
- setStep(0);
220
- setImageName('');
221
- setContainerName('');
222
- setPortInput('');
223
- setEnvInput('');
224
- setMessage('Insert the name of the image to create: ');
225
- setMessageColor('yellow');
226
- setSuggestions([]);
227
- setSelectedSuggestionIndex(-1);
228
- setVisibleOffset(0);
492
+ dispatch({
493
+ type: 'RESET_WIZARD'
494
+ });
229
495
  }
496
+
497
+ /**
498
+ * Inserts the next pending suggested env var into envInput.
499
+ * Derives "next" by comparing keys already in envInput against suggestedEnv.
500
+ * When all suggestions are already present, sets a feedback message.
501
+ */
502
+ function insertNextSuggestedEnv() {
503
+ var _profile$suggestedEnv;
504
+ var baseName = imageName.trim().toLowerCase().split(':')[0].split('/').pop();
505
+ var profile = imageProfiles[baseName];
506
+ var suggested = (_profile$suggestedEnv = profile === null || profile === void 0 ? void 0 : profile.suggestedEnv) !== null && _profile$suggestedEnv !== void 0 ? _profile$suggestedEnv : [];
507
+ if (!suggested.length) return;
508
+ var addedKeys = envInput.split(',').map(function (s) {
509
+ return s.split('=')[0].trim();
510
+ }).filter(Boolean);
511
+ var next = suggested.find(function (s) {
512
+ return !addedKeys.includes(s.split('=')[0]);
513
+ });
514
+ if (!next) {
515
+ setTimedMessage('All suggested env vars added', 'yellow');
516
+ return;
517
+ }
518
+ var newEnvInput = envInput ? "".concat(envInput, ",").concat(next) : next;
519
+ dispatch({
520
+ type: 'SET',
521
+ payload: {
522
+ envInput: newEnvInput
523
+ }
524
+ });
525
+ }
526
+
527
+ /**
528
+ * True when the current image profile has at least one suggestedEnv entry.
529
+ */
530
+ var baseName = imageName.trim().toLowerCase().split(':')[0].split('/').pop();
531
+ 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;
230
532
  return {
231
533
  step: step,
232
534
  setStep: setStep,
@@ -245,11 +547,16 @@ export function useContainerCreation(_ref) {
245
547
  suggestions: suggestions,
246
548
  selectedSuggestionIndex: selectedSuggestionIndex,
247
549
  visibleOffset: visibleOffset,
550
+ isSearchingHub: isSearchingHub,
551
+ hubResults: hubResults,
248
552
  updateImageInput: updateImageInput,
553
+ triggerHubSearch: triggerHubSearch,
249
554
  moveSuggestionSelection: moveSuggestionSelection,
250
555
  applyFocusedSuggestion: applyFocusedSuggestion,
251
556
  nextStep: nextStep,
252
557
  cancelCreation: cancelCreation,
253
- resetCreation: resetCreation
558
+ resetCreation: resetCreation,
559
+ insertNextSuggestedEnv: insertNextSuggestedEnv,
560
+ hasSuggestedEnv: hasSuggestedEnv
254
561
  };
255
562
  }