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.
- package/.atl/skill-registry.md +30 -0
- package/.editorconfig +12 -0
- package/.eslintrc.json +1 -1
- package/.prettierignore +4 -0
- package/.prettierrc +8 -0
- package/.~lock.ROADMAP.md# +1 -0
- package/CHANGELOG.md +80 -0
- package/README.es.md +198 -0
- package/README.md +81 -23
- package/ROADMAP.md +647 -0
- package/SECURITY.md +5 -0
- package/__mocks__/ink.cjs +17 -0
- package/coverage/clover.xml +471 -500
- package/coverage/coverage-final.json +16 -16
- package/coverage/lcov-report/ContainerCreationPrompt.jsx.html +400 -0
- package/coverage/lcov-report/ControlsHUD.jsx.html +256 -0
- package/coverage/lcov-report/components/ContainerCreationPrompt.jsx.html +394 -0
- package/coverage/lcov-report/components/PromptField.jsx.html +229 -0
- package/coverage/lcov-report/components/SuggestionPanel.jsx.html +268 -0
- package/coverage/lcov-report/components/index.html +146 -0
- package/coverage/lcov-report/helpers/constants.js.html +139 -22
- package/coverage/lcov-report/helpers/containerOptionsBuilder.js.html +75 -42
- package/coverage/lcov-report/helpers/dockerHubService.js.html +310 -0
- package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerActions.js.html +162 -84
- package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerList.js.html +50 -32
- package/coverage/lcov-report/helpers/dockerService/serviceComponents/index.html +21 -21
- package/coverage/lcov-report/helpers/imageNameUtils.js.html +181 -0
- package/coverage/lcov-report/helpers/index.html +84 -54
- package/coverage/lcov-report/helpers/logger.js.html +68 -68
- package/coverage/lcov-report/helpers/safeCall.js.html +15 -15
- package/coverage/lcov-report/helpers/validationHelpers.js.html +66 -114
- package/coverage/lcov-report/hooks/creation/index.html +20 -20
- package/coverage/lcov-report/hooks/creation/useContainerActions.js.html +38 -17
- package/coverage/lcov-report/hooks/creation/useContainerCreation.js.html +593 -89
- package/coverage/lcov-report/hooks/creation/useLogsViewer.js.html +7 -7
- package/coverage/lcov-report/hooks/debug/index.html +5 -5
- package/coverage/lcov-report/hooks/debug/useDebugLogs.js.html +35 -20
- package/coverage/lcov-report/hooks/index.html +40 -40
- package/coverage/lcov-report/hooks/navigation/index.html +19 -19
- package/coverage/lcov-report/hooks/navigation/useContainerSelection.js.html +38 -29
- package/coverage/lcov-report/hooks/useContainerCommandRouter.js.html +119 -80
- package/coverage/lcov-report/hooks/useControls.js.html +306 -123
- package/coverage/lcov-report/hooks/useEraseConfirmation.js.html +8 -8
- package/coverage/lcov-report/hooks/useExitHandler.js.html +50 -41
- package/coverage/lcov-report/index.html +52 -67
- package/coverage/lcov.info +899 -952
- package/dist/App.js +18 -12
- package/dist/components/ContainerCreationPrompt.js +50 -13
- package/dist/components/ContainerList.js +3 -3
- package/dist/components/ContainerRow.js +20 -20
- package/dist/components/ContainerSection.js +3 -3
- package/dist/components/ControlsHUD.js +106 -0
- package/dist/components/Footer.js +2 -2
- package/dist/components/Header.js +7 -7
- package/dist/components/LogViewer.js +3 -3
- package/dist/components/MessageFeedback.js +2 -2
- package/dist/components/PromptField.js +4 -4
- package/dist/components/StatsBar.js +5 -5
- package/dist/components/SuggestionPanel.js +53 -0
- package/dist/components/UsageMenu.js +3 -3
- package/dist/helpers/appInfo.js +7 -7
- package/dist/helpers/constants.js +154 -1
- package/dist/helpers/containerOptionsBuilder.js +3 -3
- package/dist/helpers/dockerHubService.js +111 -0
- package/dist/helpers/dockerService/dockerService.js +1 -1
- package/dist/helpers/dockerService/serviceComponents/containerActions.js +99 -40
- package/dist/helpers/dockerService/serviceComponents/containerList.js +11 -11
- package/dist/helpers/dockerService/serviceComponents/containerLogs.js +9 -9
- package/dist/helpers/dockerService/serviceComponents/containerStats.js +5 -5
- package/dist/helpers/dockerService/serviceComponents/imageUtils.js +1 -1
- package/dist/helpers/exitWithMessage.js +10 -10
- package/dist/helpers/imageNameUtils.js +32 -0
- package/dist/helpers/logger.js +13 -13
- package/dist/helpers/safeCall.js +2 -2
- package/dist/helpers/validationHelpers.js +76 -21
- package/dist/hooks/creation/useContainerActions.js +28 -11
- package/dist/hooks/creation/useContainerCreation.js +452 -62
- package/dist/hooks/creation/useLogsViewer.js +2 -2
- package/dist/hooks/debug/useDebugLogs.js +4 -4
- package/dist/hooks/useContainerCommandRouter.js +14 -14
- package/dist/hooks/useContainers.js +3 -3
- package/dist/hooks/useControls.js +85 -38
- package/dist/hooks/useEraseConfirmation.js +3 -3
- package/dist/index.js +9 -2
- package/jest.config.cjs +7 -0
- package/package.json +7 -3
- package/src/App.jsx +39 -16
- package/src/components/ContainerCreationPrompt.jsx +54 -10
- package/src/components/ContainerList.jsx +3 -3
- package/src/components/ContainerRow.jsx +19 -16
- package/src/components/ContainerSection.jsx +3 -3
- package/src/components/ControlsHUD.jsx +66 -0
- package/src/components/Footer.jsx +2 -2
- package/src/components/Header.jsx +7 -7
- package/src/components/LogViewer.jsx +5 -3
- package/src/components/MessageFeedback.jsx +2 -2
- package/src/components/PromptField.jsx +4 -4
- package/src/components/StatsBar.jsx +11 -7
- package/src/components/SuggestionPanel.jsx +61 -0
- package/src/components/UsageMenu.jsx +18 -13
- package/src/helpers/appInfo.js +7 -7
- package/src/helpers/constants.js +48 -9
- package/src/helpers/containerOptionsBuilder.js +16 -5
- package/src/helpers/dockerHubService.js +75 -0
- package/src/helpers/dockerService/dockerService.js +1 -1
- package/src/helpers/dockerService/serviceComponents/containerActions.js +87 -61
- package/src/helpers/dockerService/serviceComponents/containerList.js +31 -25
- package/src/helpers/dockerService/serviceComponents/containerLogs.js +41 -26
- package/src/helpers/dockerService/serviceComponents/containerStats.js +13 -11
- package/src/helpers/dockerService/serviceComponents/imageUtils.js +5 -4
- package/src/helpers/exitWithMessage.js +13 -7
- package/src/helpers/imageNameUtils.js +32 -0
- package/src/helpers/logger.js +15 -16
- package/src/helpers/safeCall.js +2 -2
- package/src/helpers/validationHelpers.js +13 -29
- package/src/hooks/creation/useContainerActions.js +32 -12
- package/src/hooks/creation/useContainerCreation.js +293 -47
- package/src/hooks/creation/useLogsViewer.js +2 -2
- package/src/hooks/debug/useDebugLogs.js +15 -10
- package/src/hooks/navigation/useContainerSelection.js +17 -14
- package/src/hooks/useContainerCommandRouter.js +71 -58
- package/src/hooks/useContainers.js +4 -4
- package/src/hooks/useControls.js +135 -72
- package/src/hooks/useEraseConfirmation.js +3 -3
- package/src/hooks/useExitHandler.js +29 -26
- package/src/index.js +8 -4
- package/test/ContainerCreationPrompt.dom.test.js +313 -0
- package/test/ControlsHUD.dom.test.js +125 -0
- package/test/SuggestionPanel.dom.test.js +83 -0
- package/test/constants.test.js +41 -0
- package/test/dockerHubService.test.js +213 -0
- package/test/logger.test.js +58 -0
- package/test/resolveImageTag.test.js +26 -0
- package/test/useContainerCreation.dom.test.js +428 -2
- package/test/useContainerCreationHub.dom.test.js +184 -0
- package/test/useControls.dom.test.js +406 -4
|
@@ -2,7 +2,7 @@ function _regenerator() { /*! regenerator-runtime -- Copyright (c) 2014-present,
|
|
|
2
2
|
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); }
|
|
3
3
|
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); }
|
|
4
4
|
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
|
-
import { useCallback } from
|
|
5
|
+
import { useCallback } from 'react';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Hook responsible solely for routing Docker action keystrokes to the correct handler.
|
|
@@ -41,7 +41,7 @@ export function useContainerCommandRouter(_ref) {
|
|
|
41
41
|
onStartCreate = _ref.onStartCreate;
|
|
42
42
|
var handleDockerCommands = useCallback(function (input) {
|
|
43
43
|
var container = containers[selected];
|
|
44
|
-
if (input ===
|
|
44
|
+
if (input === 'i') {
|
|
45
45
|
actions.handleAction({
|
|
46
46
|
actionFn: function () {
|
|
47
47
|
var _actionFn = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(id) {
|
|
@@ -60,15 +60,15 @@ export function useContainerCommandRouter(_ref) {
|
|
|
60
60
|
}
|
|
61
61
|
return actionFn;
|
|
62
62
|
}(),
|
|
63
|
-
actionLabel:
|
|
63
|
+
actionLabel: 'Starting',
|
|
64
64
|
selected: selected,
|
|
65
65
|
stateCheck: function stateCheck(c) {
|
|
66
|
-
return (c.state ===
|
|
66
|
+
return (c.state === 'running' || c.status === 'running') && 'Container is already running.';
|
|
67
67
|
}
|
|
68
68
|
});
|
|
69
69
|
return true;
|
|
70
70
|
}
|
|
71
|
-
if (input ===
|
|
71
|
+
if (input === 'p') {
|
|
72
72
|
actions.handleAction({
|
|
73
73
|
actionFn: function () {
|
|
74
74
|
var _actionFn2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2(id) {
|
|
@@ -87,15 +87,15 @@ export function useContainerCommandRouter(_ref) {
|
|
|
87
87
|
}
|
|
88
88
|
return actionFn;
|
|
89
89
|
}(),
|
|
90
|
-
actionLabel:
|
|
90
|
+
actionLabel: 'Stopping',
|
|
91
91
|
selected: selected,
|
|
92
92
|
stateCheck: function stateCheck(c) {
|
|
93
|
-
return (c.state ===
|
|
93
|
+
return (c.state === 'exited' || c.status === 'exited' || c.state === 'stopped' || c.status === 'stopped') && 'Container is already stopped.';
|
|
94
94
|
}
|
|
95
95
|
});
|
|
96
96
|
return true;
|
|
97
97
|
}
|
|
98
|
-
if (input ===
|
|
98
|
+
if (input === 'r') {
|
|
99
99
|
actions.handleAction({
|
|
100
100
|
actionFn: function () {
|
|
101
101
|
var _actionFn3 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3(id) {
|
|
@@ -114,19 +114,19 @@ export function useContainerCommandRouter(_ref) {
|
|
|
114
114
|
}
|
|
115
115
|
return actionFn;
|
|
116
116
|
}(),
|
|
117
|
-
actionLabel:
|
|
117
|
+
actionLabel: 'Restarting',
|
|
118
118
|
selected: selected
|
|
119
119
|
});
|
|
120
120
|
return true;
|
|
121
121
|
}
|
|
122
|
-
if (input ===
|
|
122
|
+
if (input === 'e') {
|
|
123
123
|
if (!container) {
|
|
124
124
|
return true;
|
|
125
125
|
}
|
|
126
126
|
onStartErase();
|
|
127
127
|
return true;
|
|
128
128
|
}
|
|
129
|
-
if (input ===
|
|
129
|
+
if (input === 'l') {
|
|
130
130
|
if (!container) {
|
|
131
131
|
return true;
|
|
132
132
|
}
|
|
@@ -134,12 +134,12 @@ export function useContainerCommandRouter(_ref) {
|
|
|
134
134
|
startLogsStream(container.id);
|
|
135
135
|
return true;
|
|
136
136
|
}
|
|
137
|
-
if (input ===
|
|
138
|
-
onStartCreate();
|
|
137
|
+
if (input === 'c') {
|
|
139
138
|
creation.resetCreation();
|
|
139
|
+
onStartCreate();
|
|
140
140
|
return true;
|
|
141
141
|
}
|
|
142
|
-
if (input ===
|
|
142
|
+
if (input === 'd') {
|
|
143
143
|
onToggleDebug();
|
|
144
144
|
return true;
|
|
145
145
|
}
|
|
@@ -18,9 +18,9 @@ function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
|
|
|
18
18
|
* // Use in a component
|
|
19
19
|
* const { containers } = useContainers();
|
|
20
20
|
*/
|
|
21
|
-
import React, { useState, useEffect } from
|
|
22
|
-
import { getContainers } from
|
|
23
|
-
import { REFRESH_INTERVALS } from
|
|
21
|
+
import React, { useState, useEffect } from 'react';
|
|
22
|
+
import { getContainers } from '../helpers/dockerService/serviceComponents/containerList.js';
|
|
23
|
+
import { REFRESH_INTERVALS } from '../helpers/constants.js';
|
|
24
24
|
export function useContainers() {
|
|
25
25
|
var _useState = useState([]),
|
|
26
26
|
_useState2 = _slicedToArray(_useState, 2),
|
|
@@ -12,19 +12,20 @@ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r)
|
|
|
12
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; }
|
|
13
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; } }
|
|
14
14
|
function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
|
|
15
|
-
import React from
|
|
16
|
-
import { useInput } from
|
|
17
|
-
import { useContainerActions } from
|
|
18
|
-
import { useContainerCreation } from
|
|
19
|
-
import { useLogsViewer } from
|
|
20
|
-
import { useContainerSelection } from
|
|
21
|
-
import { useDebugLogs } from
|
|
22
|
-
import { useEraseConfirmation } from
|
|
23
|
-
import { useExitHandler } from
|
|
24
|
-
import { useContainerCommandRouter } from
|
|
25
|
-
import { getLogsStream } from
|
|
26
|
-
import { createContainer as svcCreateContainer } from
|
|
27
|
-
import { buildContainerOptions } from
|
|
15
|
+
import React from 'react';
|
|
16
|
+
import { useInput } from 'ink';
|
|
17
|
+
import { useContainerActions } from './creation/useContainerActions.js';
|
|
18
|
+
import { useContainerCreation } from './creation/useContainerCreation.js';
|
|
19
|
+
import { useLogsViewer } from './creation/useLogsViewer.js';
|
|
20
|
+
import { useContainerSelection } from './navigation/useContainerSelection.js';
|
|
21
|
+
import { useDebugLogs } from './debug/useDebugLogs.js';
|
|
22
|
+
import { useEraseConfirmation } from './useEraseConfirmation.js';
|
|
23
|
+
import { useExitHandler } from './useExitHandler.js';
|
|
24
|
+
import { useContainerCommandRouter } from './useContainerCommandRouter.js';
|
|
25
|
+
import { getLogsStream } from '../helpers/dockerService/serviceComponents/containerLogs.js';
|
|
26
|
+
import { createContainer as svcCreateContainer } from '../helpers/dockerService/serviceComponents/containerActions.js';
|
|
27
|
+
import { buildContainerOptions } from '../helpers/containerOptionsBuilder.js';
|
|
28
|
+
import { DB_IMAGES } from '../helpers/constants.js';
|
|
28
29
|
|
|
29
30
|
// Principal hook to manage user inputs and control the app state
|
|
30
31
|
/**
|
|
@@ -35,7 +36,9 @@ import { buildContainerOptions } from "../helpers/containerOptionsBuilder.js";
|
|
|
35
36
|
* @returns {Object} controls - API for the App component
|
|
36
37
|
*/
|
|
37
38
|
export function useControls() {
|
|
39
|
+
var _overrides$triggerHub, _overrides$isSearchin;
|
|
38
40
|
var containers = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
|
|
41
|
+
var overrides = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
39
42
|
var _React$useState = React.useState(false),
|
|
40
43
|
_React$useState2 = _slicedToArray(_React$useState, 2),
|
|
41
44
|
creatingContainer = _React$useState2[0],
|
|
@@ -48,7 +51,7 @@ export function useControls() {
|
|
|
48
51
|
var creation = useContainerCreation({
|
|
49
52
|
onCreate: function () {
|
|
50
53
|
var _onCreate = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(_ref) {
|
|
51
|
-
var imageName, containerName, portInput, envInput, options, id, _t;
|
|
54
|
+
var imageName, containerName, portInput, envInput, options, _yield$svcCreateConta, id, ports, portMsg, _t;
|
|
52
55
|
return _regenerator().w(function (_context) {
|
|
53
56
|
while (1) switch (_context.p = _context.n) {
|
|
54
57
|
case 0:
|
|
@@ -59,22 +62,27 @@ export function useControls() {
|
|
|
59
62
|
portInput: portInput,
|
|
60
63
|
envInput: envInput
|
|
61
64
|
});
|
|
62
|
-
actions.
|
|
63
|
-
actions.setMessageColor("yellow");
|
|
65
|
+
actions.setTimedMessage("Creating container ".concat(imageName, "..."), 'yellow');
|
|
64
66
|
_context.p = 1;
|
|
65
67
|
_context.n = 2;
|
|
66
68
|
return svcCreateContainer(imageName, options);
|
|
67
69
|
case 2:
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
70
|
+
_yield$svcCreateConta = _context.v;
|
|
71
|
+
id = _yield$svcCreateConta.id;
|
|
72
|
+
ports = _yield$svcCreateConta.ports;
|
|
73
|
+
portMsg = '';
|
|
74
|
+
if (ports && ports.length) {
|
|
75
|
+
portMsg = ' | Ports: ' + ports.map(function (p) {
|
|
76
|
+
return "".concat(p.hostPort, "\u2192").concat(p.containerPort, "/").concat(p.protocol);
|
|
77
|
+
}).join(', ');
|
|
78
|
+
}
|
|
79
|
+
actions.setTimedMessage("Created container ".concat(id).concat(portMsg), 'green');
|
|
71
80
|
_context.n = 4;
|
|
72
81
|
break;
|
|
73
82
|
case 3:
|
|
74
83
|
_context.p = 3;
|
|
75
84
|
_t = _context.v;
|
|
76
|
-
actions.
|
|
77
|
-
actions.setMessageColor("red");
|
|
85
|
+
actions.setTimedMessage("Error creating container: ".concat(_t.message), 'red');
|
|
78
86
|
case 4:
|
|
79
87
|
_context.p = 4;
|
|
80
88
|
setCreatingContainer(false);
|
|
@@ -92,11 +100,16 @@ export function useControls() {
|
|
|
92
100
|
onCancel: function onCancel() {
|
|
93
101
|
return setCreatingContainer(false);
|
|
94
102
|
},
|
|
95
|
-
dbImages:
|
|
103
|
+
dbImages: DB_IMAGES
|
|
96
104
|
});
|
|
97
105
|
var logsViewer = useLogsViewer();
|
|
98
106
|
var selection = useContainerSelection(containers.length);
|
|
99
107
|
var debugLogs = useDebugLogs();
|
|
108
|
+
|
|
109
|
+
// Allow overrides for testability (e.g. injecting a mock triggerHubSearch)
|
|
110
|
+
var triggerHubSearch = (_overrides$triggerHub = overrides.triggerHubSearch) !== null && _overrides$triggerHub !== void 0 ? _overrides$triggerHub : creation.triggerHubSearch;
|
|
111
|
+
var isSearchingHub = (_overrides$isSearchin = overrides.isSearchingHub) !== null && _overrides$isSearchin !== void 0 ? _overrides$isSearchin : creation.isSearchingHub;
|
|
112
|
+
var insertNextSuggestedEnv = overrides.insertNextSuggestedEnv !== undefined ? overrides.insertNextSuggestedEnv : creation.insertNextSuggestedEnv;
|
|
100
113
|
var eraseConfirmation = useEraseConfirmation({
|
|
101
114
|
onConfirm: function onConfirm() {
|
|
102
115
|
actions.handleAction({
|
|
@@ -117,14 +130,14 @@ export function useControls() {
|
|
|
117
130
|
}
|
|
118
131
|
return actionFn;
|
|
119
132
|
}(),
|
|
120
|
-
actionLabel:
|
|
133
|
+
actionLabel: 'Erasing',
|
|
121
134
|
selected: selection.selected
|
|
122
135
|
});
|
|
123
|
-
actions.setMessageColor(
|
|
136
|
+
actions.setMessageColor('yellow');
|
|
124
137
|
},
|
|
125
138
|
onCancel: function onCancel() {
|
|
126
|
-
actions.setMessage(
|
|
127
|
-
actions.setMessageColor(
|
|
139
|
+
actions.setMessage('');
|
|
140
|
+
actions.setMessageColor('');
|
|
128
141
|
}
|
|
129
142
|
});
|
|
130
143
|
|
|
@@ -135,7 +148,7 @@ export function useControls() {
|
|
|
135
148
|
var startLogsStream = React.useCallback(function (containerId) {
|
|
136
149
|
getLogsStream(containerId, function (data) {
|
|
137
150
|
return logsViewer.setLogs(function (prev) {
|
|
138
|
-
var newLogs = [].concat(_toConsumableArray(prev), _toConsumableArray(data.split(
|
|
151
|
+
var newLogs = [].concat(_toConsumableArray(prev), _toConsumableArray(data.split('\n').filter(Boolean)));
|
|
139
152
|
return newLogs.slice(-1000);
|
|
140
153
|
});
|
|
141
154
|
}, function () {}, function (err) {
|
|
@@ -153,8 +166,8 @@ export function useControls() {
|
|
|
153
166
|
startLogsStream: startLogsStream,
|
|
154
167
|
onStartErase: function onStartErase() {
|
|
155
168
|
eraseConfirmation.startErase();
|
|
156
|
-
actions.setMessage(
|
|
157
|
-
actions.setMessageColor(
|
|
169
|
+
actions.setMessage('Are you sure you want to erase this container? (y/n)');
|
|
170
|
+
actions.setMessageColor('yellow');
|
|
158
171
|
},
|
|
159
172
|
onToggleDebug: function onToggleDebug() {
|
|
160
173
|
return debugLogs.setShowDebugLogs(function (prev) {
|
|
@@ -167,8 +180,8 @@ export function useControls() {
|
|
|
167
180
|
});
|
|
168
181
|
var exitHandler = useExitHandler({
|
|
169
182
|
onBeforeExit: function onBeforeExit() {
|
|
170
|
-
actions.setMessage(
|
|
171
|
-
actions.setMessageColor(
|
|
183
|
+
actions.setMessage('Exiting...');
|
|
184
|
+
actions.setMessageColor('yellow');
|
|
172
185
|
logsViewer.closeLogs();
|
|
173
186
|
debugLogs.setShowDebugLogs(false);
|
|
174
187
|
}
|
|
@@ -181,35 +194,69 @@ export function useControls() {
|
|
|
181
194
|
var step = creation.step;
|
|
182
195
|
var removeLastChar = function removeLastChar(setter) {
|
|
183
196
|
return setter(function (value) {
|
|
184
|
-
return (value ||
|
|
197
|
+
return (value || '').slice(0, -1);
|
|
185
198
|
});
|
|
186
199
|
};
|
|
187
200
|
var appendChar = function appendChar(setter, current, ch) {
|
|
188
|
-
return setter((current ||
|
|
201
|
+
return setter((current || '') + ch);
|
|
189
202
|
};
|
|
190
203
|
if (key.escape) {
|
|
191
204
|
creation.cancelCreation();
|
|
192
205
|
setCreatingContainer(false);
|
|
193
206
|
return;
|
|
194
207
|
}
|
|
195
|
-
if (input ===
|
|
208
|
+
if (input === '\r' || input === '\n') {
|
|
209
|
+
// If on step 0 with a focused suggestion, apply it instead of advancing
|
|
210
|
+
if (step === 0 && creation.selectedSuggestionIndex >= 0) {
|
|
211
|
+
creation.applyFocusedSuggestion();
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
196
214
|
creation.nextStep();
|
|
197
215
|
return;
|
|
198
216
|
}
|
|
217
|
+
|
|
218
|
+
// Tab on step 0: trigger Docker Hub search (with guards)
|
|
219
|
+
if (key.tab) {
|
|
220
|
+
if (step === 0) {
|
|
221
|
+
if (!isSearchingHub && (creation.imageName || '').trim() !== '') {
|
|
222
|
+
triggerHubSearch();
|
|
223
|
+
}
|
|
224
|
+
} else if (step === 3) {
|
|
225
|
+
insertNextSuggestedEnv === null || insertNextSuggestedEnv === void 0 || insertNextSuggestedEnv();
|
|
226
|
+
}
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Arrow keys on step 0: navigate suggestion list
|
|
231
|
+
if (step === 0 && creation.suggestions.length > 0) {
|
|
232
|
+
if (key.upArrow) {
|
|
233
|
+
creation.moveSuggestionSelection(-1);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (key.downArrow) {
|
|
237
|
+
creation.moveSuggestionSelection(1);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
199
241
|
if (key.backspace || key["delete"]) {
|
|
200
|
-
if (step === 0)
|
|
242
|
+
if (step === 0) {
|
|
243
|
+
var newValue = (creation.imageName || '').slice(0, -1);
|
|
244
|
+
creation.updateImageInput(newValue);
|
|
245
|
+
}
|
|
201
246
|
if (step === 1) removeLastChar(creation.setContainerName);
|
|
202
247
|
if (step === 2) removeLastChar(creation.setPortInput);
|
|
203
248
|
if (step === 3) removeLastChar(creation.setEnvInput);
|
|
204
249
|
return;
|
|
205
250
|
}
|
|
206
251
|
if (input && input.length === 1 && !key.ctrl && !key.meta) {
|
|
207
|
-
if (step === 0)
|
|
252
|
+
if (step === 0) {
|
|
253
|
+
creation.updateImageInput((creation.imageName || '') + input);
|
|
254
|
+
}
|
|
208
255
|
if (step === 1) appendChar(creation.setContainerName, creation.containerName, input);
|
|
209
256
|
if (step === 2) appendChar(creation.setPortInput, creation.portInput, input);
|
|
210
257
|
if (step === 3) appendChar(creation.setEnvInput, creation.envInput, input);
|
|
211
258
|
}
|
|
212
|
-
}, [creation, setCreatingContainer]);
|
|
259
|
+
}, [creation, setCreatingContainer, triggerHubSearch, isSearchingHub, insertNextSuggestedEnv]);
|
|
213
260
|
|
|
214
261
|
// Single keyboard entry point
|
|
215
262
|
useInput(function (input, key) {
|
|
@@ -218,7 +265,7 @@ export function useControls() {
|
|
|
218
265
|
return;
|
|
219
266
|
}
|
|
220
267
|
if (logsViewer.showLogs) {
|
|
221
|
-
if (input ===
|
|
268
|
+
if (input === 'q' || key.escape) {
|
|
222
269
|
logsViewer.closeLogs();
|
|
223
270
|
}
|
|
224
271
|
return;
|
|
@@ -4,7 +4,7 @@ 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, useCallback } from
|
|
7
|
+
import { useState, useCallback } from 'react';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Hook to manage the erase confirmation dialog flow.
|
|
@@ -28,12 +28,12 @@ export function useEraseConfirmation(_ref) {
|
|
|
28
28
|
setConfirmErase(true);
|
|
29
29
|
}, []);
|
|
30
30
|
var processEraseConfirmation = useCallback(function (input, key) {
|
|
31
|
-
if (input ===
|
|
31
|
+
if (input === 'y' || input === 'Y') {
|
|
32
32
|
onConfirm();
|
|
33
33
|
setConfirmErase(false);
|
|
34
34
|
return;
|
|
35
35
|
}
|
|
36
|
-
if (input ===
|
|
36
|
+
if (input === 'n' || input === 'N' || key.escape) {
|
|
37
37
|
setConfirmErase(false);
|
|
38
38
|
onCancel();
|
|
39
39
|
return;
|
package/dist/index.js
CHANGED
|
@@ -7,8 +7,15 @@
|
|
|
7
7
|
* // Run the CLI
|
|
8
8
|
* node index.js
|
|
9
9
|
*/
|
|
10
|
-
import React from
|
|
11
|
-
import { render } from
|
|
10
|
+
import React from 'react';
|
|
11
|
+
import { render } from 'ink';
|
|
12
12
|
import App from './App.js';
|
|
13
|
+
import { logger } from './helpers/logger.js';
|
|
14
|
+
process.on('uncaughtException', function (err) {
|
|
15
|
+
return logger.error('Uncaught exception', err);
|
|
16
|
+
});
|
|
17
|
+
process.on('unhandledRejection', function (reason) {
|
|
18
|
+
return logger.error('Unhandled rejection', reason);
|
|
19
|
+
});
|
|
13
20
|
console.clear();
|
|
14
21
|
render(/*#__PURE__*/React.createElement(App, null));
|
package/jest.config.cjs
CHANGED
|
@@ -3,5 +3,12 @@ module.exports = {
|
|
|
3
3
|
setupFilesAfterEnv: ['./test/jest.setup.js'],
|
|
4
4
|
transform: {
|
|
5
5
|
'^.+\\.[tj]sx?$': 'babel-jest'
|
|
6
|
+
},
|
|
7
|
+
// Allow babel-jest to transform ESM-only packages used by components
|
|
8
|
+
transformIgnorePatterns: [
|
|
9
|
+
'/node_modules/(?!(ink|ink-testing-library|ansi-styles|chalk|cli-cursor|cli-spinners|is-unicode-supported|restore-cursor|signal-exit|slice-ansi|strip-ansi|wrap-ansi|yoga-layout-prebuilt)/)'
|
|
10
|
+
],
|
|
11
|
+
moduleNameMapper: {
|
|
12
|
+
'^ink$': '<rootDir>/__mocks__/ink.cjs'
|
|
6
13
|
}
|
|
7
14
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cdd-cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "CLI Docker Dashboard",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
"build": "NODE_ENV=production babel src --out-dir dist --extensions .js,.jsx && node fix-imports.cjs",
|
|
9
9
|
"changes": "npm run build && node dist/index.js",
|
|
10
10
|
"lint": "eslint src --ext .js,.jsx",
|
|
11
|
-
"docs": "jsdoc -r -d docs src"
|
|
11
|
+
"docs": "jsdoc -r -d docs src",
|
|
12
|
+
"format": "prettier --write \"src/**/*.{js,jsx}\"",
|
|
13
|
+
"format:check": "prettier --check \"src/**/*.{js,jsx}\""
|
|
12
14
|
},
|
|
13
15
|
"keywords": [],
|
|
14
16
|
"author": "",
|
|
@@ -35,9 +37,11 @@
|
|
|
35
37
|
"babel-jest": "^29.6.1",
|
|
36
38
|
"babel-plugin-module-resolver": "^5.0.2",
|
|
37
39
|
"eslint": "^8.50.0",
|
|
40
|
+
"eslint-config-prettier": "^10.1.8",
|
|
38
41
|
"eslint-plugin-react": "^7.37.5",
|
|
39
42
|
"jest": "^29.6.1",
|
|
40
43
|
"jest-environment-jsdom": "^29.7.0",
|
|
41
|
-
"jsdoc": "^4.0.5"
|
|
44
|
+
"jsdoc": "^4.0.5",
|
|
45
|
+
"prettier": "^3.8.3"
|
|
42
46
|
}
|
|
43
47
|
}
|
package/src/App.jsx
CHANGED
|
@@ -9,17 +9,17 @@
|
|
|
9
9
|
* // Render the app
|
|
10
10
|
* <App />
|
|
11
11
|
*/
|
|
12
|
-
import React from
|
|
13
|
-
import { Box, Text, Spacer } from
|
|
14
|
-
import { useContainers } from
|
|
15
|
-
import { useControls } from
|
|
16
|
-
import ContainerSection from
|
|
17
|
-
import MessageFeedback from
|
|
18
|
-
import Header from
|
|
19
|
-
import LogViewer from
|
|
20
|
-
import ContainerCreationPrompt from
|
|
21
|
-
import UsageMenu from
|
|
22
|
-
import Footer from
|
|
12
|
+
import React from 'react';
|
|
13
|
+
import { Box, Text, Spacer } from 'ink';
|
|
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';
|
|
23
23
|
|
|
24
24
|
export default function App() {
|
|
25
25
|
const { containers } = useContainers();
|
|
@@ -35,6 +35,12 @@ export default function App() {
|
|
|
35
35
|
envInput={controls.envInput}
|
|
36
36
|
message={controls.message}
|
|
37
37
|
messageColor={controls.messageColor}
|
|
38
|
+
suggestions={controls.creation.suggestions}
|
|
39
|
+
selectedSuggestionIndex={controls.creation.selectedSuggestionIndex}
|
|
40
|
+
visibleOffset={controls.creation.visibleOffset}
|
|
41
|
+
isSearchingHub={controls.creation.isSearchingHub}
|
|
42
|
+
hubResults={controls.creation.hubResults}
|
|
43
|
+
hasSuggestedEnv={controls.creation.hasSuggestedEnv}
|
|
38
44
|
/>
|
|
39
45
|
);
|
|
40
46
|
}
|
|
@@ -49,18 +55,35 @@ export default function App() {
|
|
|
49
55
|
>
|
|
50
56
|
<Header count={containers.length} />
|
|
51
57
|
<Text> </Text>
|
|
52
|
-
<ContainerSection
|
|
58
|
+
<ContainerSection
|
|
59
|
+
containers={containers}
|
|
60
|
+
selected={controls.selected}
|
|
61
|
+
/>
|
|
53
62
|
<Spacer />
|
|
54
|
-
<MessageFeedback
|
|
63
|
+
<MessageFeedback
|
|
64
|
+
message={controls.message}
|
|
65
|
+
color={controls.messageColor}
|
|
66
|
+
/>
|
|
55
67
|
<UsageMenu />
|
|
56
68
|
{controls.showDebugLogs && (
|
|
57
|
-
<Box
|
|
69
|
+
<Box
|
|
70
|
+
marginTop={1}
|
|
71
|
+
flexDirection="column"
|
|
72
|
+
borderStyle="round"
|
|
73
|
+
borderColor="gray"
|
|
74
|
+
padding={1}
|
|
75
|
+
>
|
|
58
76
|
<Text color="cyan">Debug log — press D or ESC to close</Text>
|
|
59
77
|
{controls.debugLogs.length === 0 ? (
|
|
60
|
-
<Text dimColor>
|
|
78
|
+
<Text dimColor>
|
|
79
|
+
No debug entries yet. Run with CDD_LOG_LEVEL=debug for verbose
|
|
80
|
+
output.
|
|
81
|
+
</Text>
|
|
61
82
|
) : (
|
|
62
83
|
controls.debugLogs.slice(-15).map((line, idx) => (
|
|
63
|
-
<Text key={idx} color="gray">
|
|
84
|
+
<Text key={idx} color="gray">
|
|
85
|
+
{line}
|
|
86
|
+
</Text>
|
|
64
87
|
))
|
|
65
88
|
)}
|
|
66
89
|
</Box>
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import React from
|
|
2
|
-
import { Box
|
|
3
|
-
import { PromptField, PromptMessage } from
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Box } from 'ink';
|
|
3
|
+
import { PromptField, PromptMessage } from './PromptField.jsx';
|
|
4
|
+
import { SuggestionPanel } from './SuggestionPanel.jsx';
|
|
5
|
+
import { ControlsHUD } from './ControlsHUD.jsx';
|
|
4
6
|
import PropTypes from 'prop-types';
|
|
5
7
|
|
|
6
8
|
/**
|
|
@@ -16,38 +18,71 @@ import PropTypes from 'prop-types';
|
|
|
16
18
|
* @param {string} props.envInput - Value for the environment variables field
|
|
17
19
|
* @param {string} props.message - Contextual message to display (errors/hints)
|
|
18
20
|
* @param {string} props.messageColor - Color for the contextual message
|
|
21
|
+
* @param {string[]} [props.suggestions] - Autocomplete suggestions (shown only on step 0)
|
|
22
|
+
* @param {number} [props.selectedSuggestionIndex] - Currently focused suggestion index
|
|
23
|
+
* @param {number} [props.visibleOffset] - First visible suggestion offset
|
|
19
24
|
* @returns {JSX.Element}
|
|
20
25
|
*/
|
|
21
26
|
export default function ContainerCreationPrompt(props) {
|
|
22
|
-
const {
|
|
27
|
+
const {
|
|
28
|
+
step,
|
|
29
|
+
imageName,
|
|
30
|
+
containerName,
|
|
31
|
+
portInput,
|
|
32
|
+
envInput,
|
|
33
|
+
message,
|
|
34
|
+
messageColor,
|
|
35
|
+
suggestions = [],
|
|
36
|
+
selectedSuggestionIndex = -1,
|
|
37
|
+
visibleOffset = 0,
|
|
38
|
+
hubResults = null,
|
|
39
|
+
isSearchingHub = false,
|
|
40
|
+
hasSuggestedEnv = false,
|
|
41
|
+
} = props;
|
|
23
42
|
const prompts = [
|
|
24
43
|
{
|
|
25
|
-
label:
|
|
44
|
+
label: 'Name of the image to create (e.g., nginx:1.27-alpine):',
|
|
26
45
|
value: imageName,
|
|
27
46
|
required: true,
|
|
28
47
|
},
|
|
29
48
|
{
|
|
30
|
-
label:
|
|
49
|
+
label: 'Name of the container (optional):',
|
|
31
50
|
value: containerName,
|
|
32
51
|
required: false,
|
|
33
52
|
},
|
|
34
53
|
{
|
|
35
|
-
label:
|
|
54
|
+
label: 'Ports (optional, format 8080:80,443:443):',
|
|
36
55
|
value: portInput,
|
|
37
56
|
required: false,
|
|
38
57
|
},
|
|
39
58
|
{
|
|
40
|
-
label:
|
|
59
|
+
label: 'Environment variables (optional, format VAR1=val1,VAR2=val2):',
|
|
41
60
|
value: envInput,
|
|
42
61
|
required: false,
|
|
43
62
|
},
|
|
44
63
|
];
|
|
45
64
|
const { label, value, required } = prompts[step] || {};
|
|
65
|
+
const activeItems = hubResults ?? suggestions;
|
|
66
|
+
const showSuggestions = step === 0 && (isSearchingHub || activeItems.length > 0);
|
|
67
|
+
const hasSuggestions = (suggestions?.length > 0) || (hubResults?.length > 0);
|
|
46
68
|
return (
|
|
47
|
-
<Box
|
|
69
|
+
<Box
|
|
70
|
+
flexDirection="column"
|
|
71
|
+
borderStyle="round"
|
|
72
|
+
borderColor="yellow"
|
|
73
|
+
padding={1}
|
|
74
|
+
>
|
|
48
75
|
<PromptField label={label} value={value} required={required} />
|
|
76
|
+
{showSuggestions && (
|
|
77
|
+
<SuggestionPanel
|
|
78
|
+
items={activeItems}
|
|
79
|
+
selectedIndex={selectedSuggestionIndex}
|
|
80
|
+
visibleOffset={visibleOffset}
|
|
81
|
+
isLoading={isSearchingHub}
|
|
82
|
+
/>
|
|
83
|
+
)}
|
|
49
84
|
<PromptMessage message={message} color={messageColor} />
|
|
50
|
-
<
|
|
85
|
+
<ControlsHUD step={step} hasSuggestions={hasSuggestions} isSearchingHub={isSearchingHub} hasSuggestedEnv={hasSuggestedEnv} />
|
|
51
86
|
</Box>
|
|
52
87
|
);
|
|
53
88
|
}
|
|
@@ -60,4 +95,13 @@ ContainerCreationPrompt.propTypes = {
|
|
|
60
95
|
envInput: PropTypes.string,
|
|
61
96
|
message: PropTypes.string,
|
|
62
97
|
messageColor: PropTypes.string,
|
|
98
|
+
suggestions: PropTypes.arrayOf(PropTypes.string),
|
|
99
|
+
selectedSuggestionIndex: PropTypes.number,
|
|
100
|
+
visibleOffset: PropTypes.number,
|
|
101
|
+
hubResults: PropTypes.arrayOf(PropTypes.string),
|
|
102
|
+
isSearchingHub: PropTypes.bool,
|
|
103
|
+
hasSuggestedEnv: PropTypes.bool,
|
|
63
104
|
};
|
|
105
|
+
|
|
106
|
+
// Named export for test compatibility with jest ESM interop
|
|
107
|
+
export { ContainerCreationPrompt };
|
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
* // Render with containers
|
|
10
10
|
* <ContainerList containers={containers} />
|
|
11
11
|
*/
|
|
12
|
-
import React from
|
|
12
|
+
import React from 'react';
|
|
13
13
|
import PropTypes from 'prop-types';
|
|
14
|
-
import { Box } from
|
|
15
|
-
import ContainerRow from
|
|
14
|
+
import { Box } from 'ink';
|
|
15
|
+
import ContainerRow from './ContainerRow.jsx';
|
|
16
16
|
|
|
17
17
|
export default function ContainerList({ containers, selected }) {
|
|
18
18
|
return (
|