cdd-cli 3.0.1 → 3.1.1

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 (68) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +30 -0
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +19 -0
  3. package/.github/PULL_REQUEST_TEMPLATE/pull_request.md +14 -0
  4. package/.github/workflows/ci.yml +29 -0
  5. package/CHANGELOG.md +34 -0
  6. package/CODE_OF_CONDUCT.md +12 -0
  7. package/CONTRIBUTING.md +22 -0
  8. package/README.md +83 -166
  9. package/SECURITY.md +5 -0
  10. package/babel.config.cjs +6 -0
  11. package/dist/App.js +38 -24
  12. package/dist/cdd.bundle.js +8 -0
  13. package/dist/components/ContainerCreationPrompt.js +48 -0
  14. package/dist/components/ContainerCreator.js +81 -0
  15. package/dist/components/ContainerList.js +24 -4
  16. package/dist/components/ContainerRow.js +66 -13
  17. package/dist/components/ContainerSection.js +14 -0
  18. package/dist/components/Footer.js +10 -0
  19. package/dist/components/PromptField.js +20 -0
  20. package/dist/components/UsageMenu.js +8 -0
  21. package/dist/helpers/actionHelpers.js +0 -1
  22. package/dist/helpers/dockerActions.js +13 -0
  23. package/dist/helpers/dockerService/dockerService.js +5 -0
  24. package/dist/helpers/dockerService/serviceComponents/containerActions.js +163 -0
  25. package/dist/helpers/dockerService/serviceComponents/containerList.js +57 -0
  26. package/dist/helpers/dockerService/serviceComponents/containerLogs.js +24 -0
  27. package/dist/helpers/dockerService/serviceComponents/containerStats.js +50 -0
  28. package/dist/helpers/dockerService/serviceComponents/imageUtils.js +52 -0
  29. package/dist/helpers/dockerService.js +111 -0
  30. package/dist/helpers/validationHelpers.js +26 -0
  31. package/dist/hooks/creation/useContainerActions.js +171 -0
  32. package/dist/hooks/creation/useContainerCreation.js +141 -0
  33. package/dist/hooks/creation/useLogsViewer.js +62 -0
  34. package/dist/hooks/useContainers.js +11 -1
  35. package/dist/hooks/useControls.js +298 -78
  36. package/dist/hooks/useLogsStream.js +1 -1
  37. package/dist/index.js +11 -0
  38. package/esbuild.config.cjs +11 -0
  39. package/jest.config.cjs +6 -0
  40. package/package.json +10 -6
  41. package/src/App.jsx +32 -26
  42. package/src/components/ContainerCreationPrompt.jsx +37 -0
  43. package/src/components/ContainerList.jsx +10 -6
  44. package/src/components/ContainerRow.jsx +36 -19
  45. package/src/components/ContainerSection.jsx +10 -0
  46. package/src/components/Footer.jsx +10 -0
  47. package/src/components/PromptField.jsx +18 -0
  48. package/src/components/UsageMenu.jsx +17 -0
  49. package/src/helpers/actionHelpers.js +0 -1
  50. package/src/helpers/dockerService/dockerService.js +3 -0
  51. package/src/helpers/dockerService/serviceComponents/containerActions.js +52 -0
  52. package/src/helpers/dockerService/serviceComponents/containerList.js +26 -0
  53. package/src/helpers/dockerService/serviceComponents/containerLogs.js +19 -0
  54. package/src/helpers/dockerService/serviceComponents/containerStats.js +34 -0
  55. package/src/helpers/dockerService/serviceComponents/imageUtils.js +21 -0
  56. package/src/helpers/validationHelpers.js +16 -0
  57. package/src/hooks/creation/useContainerActions.js +68 -0
  58. package/src/hooks/creation/useContainerCreation.js +104 -0
  59. package/src/hooks/creation/useLogsViewer.js +50 -0
  60. package/src/hooks/useContainers.js +1 -1
  61. package/src/hooks/useControls.js +176 -55
  62. package/src/hooks/useLogsStream.js +1 -1
  63. package/test/validationHelpers.test.js +28 -0
  64. package/PLAN_KEYBINDINGS_Y_REALTIME.md +0 -260
  65. package/docs/en/README.md +0 -167
  66. package/src/components/StatsViewer.jsx +0 -0
  67. package/src/helpers/dockerActions.js +0 -52
  68. package/src/helpers/dockerService.js +0 -100
@@ -2,78 +2,231 @@ function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArra
2
2
  function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
3
3
  function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
4
4
  function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
5
+ 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 }; })(); }
6
+ 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); }
7
+ 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); }
8
+ 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); }); }; }
5
9
  function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
6
10
  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."); }
7
11
  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; } }
8
12
  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; }
9
13
  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; } }
10
14
  function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
11
- import React, { useState, useRef } from "react";
15
+ import React from "react";
16
+ import { useContainerActions } from "./creation/useContainerActions.js";
17
+ import { useContainerCreation } from "./creation/useContainerCreation.js";
18
+ import { useLogsViewer } from "./creation/useLogsViewer.js";
12
19
  import { useInput } from "ink";
13
- import { startContainer, stopContainer, restartContainer, getLogsStream } from "../helpers/dockerService.js";
14
- import { handleAction } from "../helpers/actionHelpers.js";
15
- import { exitWithMessage } from "../helpers/exitWithMessage.js";
20
+ import { getLogsStream } from "../helpers/dockerService/serviceComponents/containerLogs.js";
21
+ import { createContainer as svcCreateContainer } from "../helpers/dockerService/serviceComponents/containerActions.js";
22
+
23
+ // Principal hook to manage user inputs and control the app state
16
24
  export function useControls() {
17
25
  var containers = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
18
- var _useState = useState(0),
19
- _useState2 = _slicedToArray(_useState, 2),
20
- selected = _useState2[0],
21
- setSelected = _useState2[1];
22
- var _useState3 = useState(""),
23
- _useState4 = _slicedToArray(_useState3, 2),
24
- message = _useState4[0],
25
- setMessage = _useState4[1];
26
- var _useState5 = useState("yellow"),
27
- _useState6 = _slicedToArray(_useState5, 2),
28
- messageColor = _useState6[0],
29
- setMessageColor = _useState6[1];
30
- var _useState7 = useState(false),
31
- _useState8 = _slicedToArray(_useState7, 2),
32
- showLogs = _useState8[0],
33
- setShowLogs = _useState8[1];
34
- var _useState9 = useState([]),
35
- _useState0 = _slicedToArray(_useState9, 2),
36
- logs = _useState0[0],
37
- setLogs = _useState0[1];
38
- var logsStreamRef = useRef(null);
26
+ var _React$useState = React.useState(0),
27
+ _React$useState2 = _slicedToArray(_React$useState, 2),
28
+ selected = _React$useState2[0],
29
+ setSelected = _React$useState2[1];
30
+ var _React$useState3 = React.useState(false),
31
+ _React$useState4 = _slicedToArray(_React$useState3, 2),
32
+ creatingContainer = _React$useState4[0],
33
+ setCreatingContainer = _React$useState4[1];
34
+ var _React$useState5 = React.useState(false),
35
+ _React$useState6 = _slicedToArray(_React$useState5, 2),
36
+ confirmErase = _React$useState6[0],
37
+ setConfirmErase = _React$useState6[1];
39
38
  var total = containers.length;
40
39
 
41
- // Handler para salir de la vista de logs
42
- var exitLogs = function exitLogs() {
43
- setShowLogs(false);
44
- setLogs([]);
45
- if (logsStreamRef.current) {
46
- var _logsStreamRef$curren, _logsStreamRef$curren2;
47
- (_logsStreamRef$curren = (_logsStreamRef$curren2 = logsStreamRef.current).destroy) === null || _logsStreamRef$curren === void 0 || _logsStreamRef$curren.call(_logsStreamRef$curren2);
48
- logsStreamRef.current = null;
49
- }
50
- };
40
+ // Modular hooks
41
+ var actions = useContainerActions({
42
+ containers: containers
43
+ });
44
+ var creation = useContainerCreation({
45
+ onCreate: function () {
46
+ var _onCreate = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(_ref) {
47
+ var imageName, containerName, portInput, envInput, env, ports, ExposedPorts, PortBindings, options, id, _t;
48
+ return _regenerator().w(function (_context) {
49
+ while (1) switch (_context.p = _context.n) {
50
+ case 0:
51
+ imageName = _ref.imageName, containerName = _ref.containerName, portInput = _ref.portInput, envInput = _ref.envInput;
52
+ // Build Docker options
53
+ env = (envInput || "").split(",").map(function (s) {
54
+ return s.trim();
55
+ }).filter(Boolean);
56
+ ports = (portInput || "").split(",").map(function (s) {
57
+ return s.trim();
58
+ }).filter(Boolean);
59
+ ExposedPorts = {};
60
+ PortBindings = {};
61
+ ports.forEach(function (pair) {
62
+ var _pair$split = pair.split(":"),
63
+ _pair$split2 = _slicedToArray(_pair$split, 2),
64
+ host = _pair$split2[0],
65
+ cont = _pair$split2[1];
66
+ if (!host || !cont) return;
67
+ var key = "".concat(cont, "/tcp");
68
+ ExposedPorts[key] = {};
69
+ PortBindings[key] = PortBindings[key] || [];
70
+ PortBindings[key].push({
71
+ HostPort: "".concat(host)
72
+ });
73
+ });
74
+ options = {
75
+ Tty: true
76
+ };
77
+ if (Object.keys(ExposedPorts).length) options.ExposedPorts = ExposedPorts;
78
+ if (Object.keys(PortBindings).length) options.HostConfig = {
79
+ PortBindings: PortBindings
80
+ };
81
+ if (env.length) options.Env = env;
82
+ if (containerName) options.name = containerName;
83
+ actions.setMessage("Creating container ".concat(imageName, "..."));
84
+ actions.setMessageColor("yellow");
85
+ _context.p = 1;
86
+ _context.n = 2;
87
+ return svcCreateContainer(imageName, options);
88
+ case 2:
89
+ id = _context.v;
90
+ actions.setMessage("Created container ".concat(id));
91
+ actions.setMessageColor("green");
92
+ _context.n = 4;
93
+ break;
94
+ case 3:
95
+ _context.p = 3;
96
+ _t = _context.v;
97
+ actions.setMessage("Error creating container: ".concat(_t.message));
98
+ actions.setMessageColor("red");
99
+ case 4:
100
+ _context.p = 4;
101
+ setCreatingContainer(false);
102
+ return _context.f(4);
103
+ case 5:
104
+ return _context.a(2);
105
+ }
106
+ }, _callee, null, [[1, 3, 4, 5]]);
107
+ }));
108
+ function onCreate(_x) {
109
+ return _onCreate.apply(this, arguments);
110
+ }
111
+ return onCreate;
112
+ }(),
113
+ onCancel: function onCancel() {
114
+ return setCreatingContainer(false);
115
+ },
116
+ dbImages: ["mysql", "mariadb", "postgres", "mongo", "mssql", "redis"]
117
+ });
118
+ var logsViewer = useLogsViewer();
119
+
120
+ // Handler to exit logs (delegated to logsViewer)
121
+ var exitLogs = logsViewer.closeLogs;
51
122
  useInput(function (input, key) {
52
- if (showLogs) {
123
+ // Confirmación de borrado
124
+ if (confirmErase) {
125
+ if (input === "y" || input === "Y") {
126
+ actions.handleAction({
127
+ actionFn: function () {
128
+ var _actionFn = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2(id) {
129
+ return _regenerator().w(function (_context2) {
130
+ while (1) switch (_context2.n) {
131
+ case 0:
132
+ _context2.n = 1;
133
+ return actions.removeContainer(id);
134
+ case 1:
135
+ return _context2.a(2, _context2.v);
136
+ }
137
+ }, _callee2);
138
+ }));
139
+ function actionFn(_x2) {
140
+ return _actionFn.apply(this, arguments);
141
+ }
142
+ return actionFn;
143
+ }(),
144
+ actionLabel: "Erasing",
145
+ selected: selected
146
+ });
147
+ setConfirmErase(false);
148
+ actions.setMessageColor("yellow");
149
+ return;
150
+ } else if (input === "n" || input === "N" || key.escape) {
151
+ setConfirmErase(false);
152
+ actions.setMessage("");
153
+ actions.setMessageColor("");
154
+ return;
155
+ } else {
156
+ actions.setMessage("Are you sure you want to erase this container? (y/n)");
157
+ actions.setMessageColor("yellow");
158
+ return;
159
+ }
160
+ }
161
+
162
+ // Logs viewer
163
+ if (logsViewer.showLogs) {
53
164
  if (input === "q" || key.escape) {
54
- exitLogs();
165
+ logsViewer.closeLogs();
166
+ }
167
+ return;
168
+ }
169
+
170
+ // Container creation flow: delegate input to creation hook
171
+ if (creatingContainer) {
172
+ var step = creation.step;
173
+ var appendCharToField = function appendCharToField(setter, value, ch) {
174
+ setter((value || "") + ch);
175
+ };
176
+ // Escape -> cancel
177
+ if (key.escape) {
178
+ creation.cancelCreation();
179
+ setCreatingContainer(false);
180
+ return;
181
+ }
182
+ // Enter -> next step
183
+ if (input === "\r" || input === "\n") {
184
+ creation.nextStep();
185
+ return;
186
+ }
187
+ // Backspace/Delete support
188
+ if (key.backspace || key["delete"]) {
189
+ if (step === 0) creation.setImageName(function (v) {
190
+ return (v || "").slice(0, -1);
191
+ });
192
+ if (step === 1) creation.setContainerName(function (v) {
193
+ return (v || "").slice(0, -1);
194
+ });
195
+ if (step === 2) creation.setPortInput(function (v) {
196
+ return (v || "").slice(0, -1);
197
+ });
198
+ if (step === 3) creation.setEnvInput(function (v) {
199
+ return (v || "").slice(0, -1);
200
+ });
201
+ return;
55
202
  }
203
+ // Printable character input (append)
204
+ if (input && input.length === 1 && !key.ctrl && !key.meta) {
205
+ if (step === 0) appendCharToField(creation.setImageName, creation.imageName, input);
206
+ if (step === 1) appendCharToField(creation.setContainerName, creation.containerName, input);
207
+ if (step === 2) appendCharToField(creation.setPortInput, creation.portInput, input);
208
+ if (step === 3) appendCharToField(creation.setEnvInput, creation.envInput, input);
209
+ return;
210
+ }
211
+ // Otherwise ignore
56
212
  return;
57
213
  }
58
214
 
59
215
  //==========================================================
60
216
  // Menu Navigation
61
217
  //==========================================================
62
- if (key.upArrow && total > 0) {
63
- setSelected(function (i) {
64
- return i === 0 ? total - 1 : i - 1;
65
- });
66
- }
67
- if (key.downArrow && total > 0) {
68
- setSelected(function (i) {
69
- return i === total - 1 ? 0 : i + 1;
70
- });
71
- }
218
+ if (key.upArrow && total > 0) setSelected(function (i) {
219
+ return i === 0 ? total - 1 : i - 1;
220
+ });
221
+ if (key.downArrow && total > 0) setSelected(function (i) {
222
+ return i === total - 1 ? 0 : i + 1;
223
+ });
72
224
  if (input === "q") {
73
- exitWithMessage({
74
- setMessage: setMessage,
75
- setMessageColor: setMessageColor
76
- });
225
+ actions.setMessage("Exiting...");
226
+ actions.setMessageColor("yellow");
227
+ setTimeout(function () {
228
+ return process.exit(0);
229
+ }, 500);
77
230
  return;
78
231
  }
79
232
 
@@ -81,62 +234,129 @@ export function useControls() {
81
234
  // Docker commands
82
235
  //==========================================================
83
236
  if (input === "i") {
84
- handleAction({
85
- containers: containers,
86
- selected: selected,
87
- actionFn: startContainer,
237
+ actions.handleAction({
238
+ actionFn: function () {
239
+ var _actionFn2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3(id) {
240
+ return _regenerator().w(function (_context3) {
241
+ while (1) switch (_context3.n) {
242
+ case 0:
243
+ _context3.n = 1;
244
+ return actions.startContainer(id);
245
+ case 1:
246
+ return _context3.a(2, _context3.v);
247
+ }
248
+ }, _callee3);
249
+ }));
250
+ function actionFn(_x3) {
251
+ return _actionFn2.apply(this, arguments);
252
+ }
253
+ return actionFn;
254
+ }(),
88
255
  actionLabel: "Starting",
89
- setMessage: setMessage,
90
- setMessageColor: setMessageColor,
256
+ selected: selected,
91
257
  stateCheck: function stateCheck(c) {
92
258
  return (c.state === "running" || c.status === "running") && "Container is already running.";
93
259
  }
94
260
  });
95
261
  }
96
262
  if (input === "p") {
97
- handleAction({
98
- containers: containers,
99
- selected: selected,
100
- actionFn: stopContainer,
263
+ actions.handleAction({
264
+ actionFn: function () {
265
+ var _actionFn3 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee4(id) {
266
+ return _regenerator().w(function (_context4) {
267
+ while (1) switch (_context4.n) {
268
+ case 0:
269
+ _context4.n = 1;
270
+ return actions.stopContainer(id);
271
+ case 1:
272
+ return _context4.a(2, _context4.v);
273
+ }
274
+ }, _callee4);
275
+ }));
276
+ function actionFn(_x4) {
277
+ return _actionFn3.apply(this, arguments);
278
+ }
279
+ return actionFn;
280
+ }(),
101
281
  actionLabel: "Stopping",
102
- setMessage: setMessage,
103
- setMessageColor: setMessageColor,
282
+ selected: selected,
104
283
  stateCheck: function stateCheck(c) {
105
284
  return (c.state === "exited" || c.status === "exited" || c.state === "stopped" || c.status === "stopped") && "Container is already stopped.";
106
285
  }
107
286
  });
108
287
  }
109
288
  if (input === "r") {
110
- handleAction({
111
- containers: containers,
112
- selected: selected,
113
- actionFn: restartContainer,
289
+ actions.handleAction({
290
+ actionFn: function () {
291
+ var _actionFn4 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee5(id) {
292
+ return _regenerator().w(function (_context5) {
293
+ while (1) switch (_context5.n) {
294
+ case 0:
295
+ _context5.n = 1;
296
+ return actions.restartContainer(id);
297
+ case 1:
298
+ return _context5.a(2, _context5.v);
299
+ }
300
+ }, _callee5);
301
+ }));
302
+ function actionFn(_x5) {
303
+ return _actionFn4.apply(this, arguments);
304
+ }
305
+ return actionFn;
306
+ }(),
114
307
  actionLabel: "Restarting",
115
- setMessage: setMessage,
116
- setMessageColor: setMessageColor
308
+ selected: selected
117
309
  });
118
310
  }
311
+ if (input === "e" && containers[selected]) {
312
+ setConfirmErase(true);
313
+ actions.setMessage("Are you sure you want to erase this container? (y/n)");
314
+ actions.setMessageColor("yellow");
315
+ return;
316
+ }
119
317
  if (input === "l" && containers[selected]) {
120
- setShowLogs(true);
121
- setLogs([]);
318
+ logsViewer.openLogs();
122
319
  getLogsStream(containers[selected].id, function (data) {
123
- return setLogs(function (prev) {
320
+ return logsViewer.setLogs(function (prev) {
124
321
  return [].concat(_toConsumableArray(prev), _toConsumableArray(data.split("\n").filter(Boolean)));
125
322
  });
126
323
  }, function () {}, function (err) {
127
- return setLogs(function (prev) {
324
+ return logsViewer.setLogs(function (prev) {
128
325
  return [].concat(_toConsumableArray(prev), ["Error: ".concat(err.message)]);
129
326
  });
130
327
  });
328
+ return;
329
+ }
330
+ if (input === "c") {
331
+ setCreatingContainer(true);
332
+ creation.setStep(0);
333
+ creation.setImageName("");
334
+ creation.setContainerName("");
335
+ creation.setPortInput("");
336
+ creation.setEnvInput("");
337
+ creation.setMessage("Insert the name of the image to create: ");
338
+ creation.setMessageColor("yellow");
131
339
  }
132
340
  });
133
341
  return {
134
342
  selected: selected,
135
343
  setSelected: setSelected,
136
- message: message,
137
- messageColor: messageColor,
138
- showLogs: showLogs,
139
- logs: logs,
140
- exitLogs: exitLogs
344
+ // Map messaging to creation when creating, otherwise to actions
345
+ message: creatingContainer ? creation.message : actions.message,
346
+ messageColor: creatingContainer ? creation.messageColor : actions.messageColor,
347
+ showLogs: logsViewer.showLogs,
348
+ logs: logsViewer.logs,
349
+ exitLogs: exitLogs,
350
+ creatingContainer: creatingContainer,
351
+ // Expose creation fields in the shape App expects
352
+ creationStep: creation.step,
353
+ imageNameInput: creation.imageName,
354
+ containerNameInput: creation.containerName,
355
+ portInput: creation.portInput,
356
+ envInput: creation.envInput,
357
+ creation: creation,
358
+ actions: actions,
359
+ logsViewer: logsViewer,
360
+ confirmErase: confirmErase
141
361
  };
142
362
  }
@@ -5,7 +5,7 @@ function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Sym
5
5
  function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
6
6
  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; }
7
7
  import React, { useRef, useCallback } from "react";
8
- import { getLogsStream } from "../helpers/dockerService.js";
8
+ import { getLogsStream } from "../helpers/dockerService/serviceComponents/containerLogs.js";
9
9
  export function useLogsStream() {
10
10
  var logsStreamRef = useRef(null);
11
11
  var openLogs = useCallback(function (containerId, setLogs) {
package/dist/index.js CHANGED
@@ -1,4 +1,15 @@
1
1
  #!/usr/bin/env node
2
+
3
+ /**
4
+ * Entry point for the CDD CLI application.
5
+ * Punto de entrada para la aplicación CLI de CDD.
6
+ *
7
+ * @module index
8
+ * @example
9
+ * // EN: Run the CLI
10
+ * // ES: Ejecutar el CLI
11
+ * node index.js
12
+ */
2
13
  import React from "react";
3
14
  import { render } from "ink";
4
15
  import App from './App.js';
@@ -0,0 +1,11 @@
1
+ const { build } = require('esbuild');
2
+
3
+ build({
4
+ entryPoints: ['src/index.js'],
5
+ bundle: true,
6
+ minify: true,
7
+ platform: 'node',
8
+ outfile: 'dist/cdd.bundle.js',
9
+ sourcemap: true,
10
+ external: ['dockerode', 'ink'],
11
+ }).catch(() => process.exit(1));
@@ -0,0 +1,6 @@
1
+ module.exports = {
2
+ testEnvironment: 'node',
3
+ transform: {
4
+ '^.+\\.[tj]sx?$': 'babel-jest'
5
+ }
6
+ };
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "cdd-cli",
3
- "version": "3.0.1",
3
+ "version": "3.1.1",
4
4
  "description": "CLI Docker Dashboard",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1",
8
- "build": "babel src --out-dir dist --extensions .js,.jsx && node fix-imports.cjs"
7
+ "test": "node --experimental-vm-modules ./node_modules/.bin/jest --runInBand",
8
+ "build": "babel src --out-dir dist --extensions .js,.jsx && node fix-imports.cjs",
9
+ "changes": "npm run build && node dist/index.js"
9
10
  },
10
11
  "keywords": [],
11
12
  "author": "",
@@ -23,8 +24,11 @@
23
24
  "devDependencies": {
24
25
  "@babel/cli": "^7.28.3",
25
26
  "@babel/core": "^7.28.4",
26
- "@babel/preset-env": "^7.28.3",
27
- "@babel/preset-react": "^7.27.1",
28
- "babel-plugin-module-resolver": "^5.0.2"
27
+ "babel-plugin-module-resolver": "^5.0.2",
28
+ "esbuild": "^0.25.11",
29
+ "jest": "^29.6.1",
30
+ "@babel/preset-env": "^7.22.20",
31
+ "@babel/preset-react": "^7.22.5",
32
+ "babel-jest": "^29.6.1"
29
33
  }
30
34
  }
package/src/App.jsx CHANGED
@@ -11,17 +11,33 @@
11
11
  */
12
12
  import React from "react";
13
13
  import { Box, Text, Spacer } from "ink";
14
- import { useContainers } from "./hooks/useContainers";
15
- import { useControls } from "./hooks/useControls";
16
- import ContainerList from "./components/ContainerList";
17
- import MessageFeedback from "./components/MessageFeedback";
18
- import Header from "./components/Header";
19
- import LogViewer from "./components/LogViewer";
14
+ import { useContainers } from "./hooks/useContainers.js";
15
+ import { useControls } from "./hooks/useControls.js";
16
+ import ContainerSection from "./components/ContainerSection.jsx";
17
+ import MessageFeedback from "./components/MessageFeedback.jsx";
18
+ import Header from "./components/Header.jsx";
19
+ import LogViewer from "./components/LogViewer.jsx";
20
+ import ContainerCreationPrompt from "./components/ContainerCreationPrompt.jsx";
21
+ import UsageMenu from "./components/UsageMenu.jsx";
22
+ import Footer from "./components/Footer.jsx";
20
23
 
21
24
  export default function App() {
22
25
  const { containers } = useContainers();
23
- const { selected, message, messageColor, showLogs, logs, exitLogs } =
24
- useControls(containers);
26
+ const controls = useControls(containers);
27
+
28
+ if (controls.creatingContainer) {
29
+ return (
30
+ <ContainerCreationPrompt
31
+ step={controls.creationStep}
32
+ imageName={controls.imageNameInput}
33
+ containerName={controls.containerNameInput}
34
+ portInput={controls.portInput}
35
+ envInput={controls.envInput}
36
+ message={controls.message}
37
+ messageColor={controls.messageColor}
38
+ />
39
+ );
40
+ }
25
41
 
26
42
  return (
27
43
  <>
@@ -33,27 +49,17 @@ export default function App() {
33
49
  >
34
50
  <Header count={containers.length} />
35
51
  <Text> </Text>
36
- {containers.length === 0 ? (
37
- <Text>No containers found</Text>
38
- ) : (
39
- <ContainerList containers={containers} selected={selected} />
40
- )}
52
+ <ContainerSection containers={containers} selected={controls.selected} />
41
53
  <Spacer />
42
- <MessageFeedback message={message} color={messageColor} />
43
- <Text>Use ↑/↓ for navigation</Text>
44
- <Text>•I to initiate selected container</Text>
45
- <Text>•P to stop selected container</Text>
46
- <Text>•L to view logs of selected container</Text>
47
- <Text>•Q to quit</Text>
48
- <Box justifyContent="flex-end" width="100%">
49
- <Text dimColor>Crafted by Carlos Cochero • 2025</Text>
50
- </Box>
54
+ <MessageFeedback message={controls.message} color={controls.messageColor} />
55
+ <UsageMenu />
56
+ <Footer />
51
57
  </Box>
52
- {showLogs && (
58
+ {controls.showLogs && (
53
59
  <LogViewer
54
- logs={logs}
55
- onExit={exitLogs}
56
- container={containers[selected]}
60
+ logs={controls.logs}
61
+ onExit={controls.exitLogs}
62
+ container={containers[controls.selected]}
57
63
  />
58
64
  )}
59
65
  </>
@@ -0,0 +1,37 @@
1
+ import React from "react";
2
+ import { Box, Text } from "ink";
3
+ import { PromptField, PromptMessage } from "./PromptField.jsx";
4
+
5
+ export default function ContainerCreationPrompt(props) {
6
+ const { step, imageName, containerName, portInput, envInput, message, messageColor } = props;
7
+ const prompts = [
8
+ {
9
+ label: "Name of the image to create (e.g., nginx:latest):",
10
+ value: imageName,
11
+ required: true,
12
+ },
13
+ {
14
+ label: "Name of the container (optional):",
15
+ value: containerName,
16
+ required: false,
17
+ },
18
+ {
19
+ label: "Ports (optional, format 8080:80,443:443):",
20
+ value: portInput,
21
+ required: false,
22
+ },
23
+ {
24
+ label: "Environment variables (optional, format VAR1=val1,VAR2=val2):",
25
+ value: envInput,
26
+ required: false,
27
+ },
28
+ ];
29
+ const { label, value, required } = prompts[step] || {};
30
+ return (
31
+ <Box flexDirection="column" borderStyle="round" borderColor="yellow" padding={1}>
32
+ <PromptField label={label} value={value} required={required} />
33
+ <PromptMessage message={message} color={messageColor} />
34
+ <Text dimColor>Press Enter to continue, Escape to cancel</Text>
35
+ </Box>
36
+ );
37
+ }