cdd-cli 4.0.0 → 4.2.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/README.es.md +60 -26
- package/README.md +60 -26
- package/dist/App.js +1 -0
- package/dist/components/ContainerCreationPrompt.js +14 -0
- package/dist/components/PromptField.js +13 -6
- package/dist/components/TextField.js +54 -0
- package/dist/components/UsageMenu.js +1 -1
- package/dist/helpers/appState.js +26 -0
- package/dist/helpers/dockerService/serviceComponents/containerExec.js +211 -0
- package/dist/helpers/textEditing.js +293 -0
- package/dist/hooks/creation/useContainerCreation.js +132 -15
- package/dist/hooks/useContainerCommandRouter.js +12 -2
- package/dist/hooks/useControls.js +21 -26
- package/dist/hooks/useShellMode.js +101 -0
- package/dist/index.js +3 -1
- package/package.json +4 -1
- package/.atl/skill-registry.md +0 -30
- package/.editorconfig +0 -12
- package/.eslintrc.json +0 -30
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -30
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
- package/.github/PULL_REQUEST_TEMPLATE/pull_request.md +0 -14
- package/.github/workflows/ci.yml +0 -32
- package/.github/workflows/docs.yml +0 -42
- package/.prettierignore +0 -4
- package/.prettierrc +0 -8
- package/.~lock.ROADMAP.md# +0 -1
- package/CHANGELOG.md +0 -216
- package/CODE_OF_CONDUCT.md +0 -12
- package/CONTRIBUTING.md +0 -35
- package/ROADMAP.md +0 -647
- package/SECURITY.md +0 -5
- package/__mocks__/ink.cjs +0 -17
- package/_config.yml +0 -13
- package/babel.config.cjs +0 -23
- package/coverage/clover.xml +0 -575
- package/coverage/coverage-final.json +0 -17
- package/coverage/lcov-report/ContainerCreationPrompt.jsx.html +0 -400
- package/coverage/lcov-report/ControlsHUD.jsx.html +0 -256
- package/coverage/lcov-report/base.css +0 -224
- package/coverage/lcov-report/block-navigation.js +0 -87
- package/coverage/lcov-report/components/ContainerCreationPrompt.jsx.html +0 -394
- package/coverage/lcov-report/components/PromptField.jsx.html +0 -229
- package/coverage/lcov-report/components/SuggestionPanel.jsx.html +0 -268
- package/coverage/lcov-report/components/index.html +0 -146
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/helpers/constants.js.html +0 -352
- package/coverage/lcov-report/helpers/containerOptionsBuilder.js.html +0 -244
- package/coverage/lcov-report/helpers/dockerHubService.js.html +0 -310
- package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerActions.js.html +0 -931
- package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerList.js.html +0 -307
- package/coverage/lcov-report/helpers/dockerService/serviceComponents/index.html +0 -131
- package/coverage/lcov-report/helpers/imageNameUtils.js.html +0 -181
- package/coverage/lcov-report/helpers/index.html +0 -206
- package/coverage/lcov-report/helpers/logger.js.html +0 -424
- package/coverage/lcov-report/helpers/safeCall.js.html +0 -139
- package/coverage/lcov-report/helpers/validationHelpers.js.html +0 -346
- package/coverage/lcov-report/hooks/creation/index.html +0 -146
- package/coverage/lcov-report/hooks/creation/useContainerActions.js.html +0 -313
- package/coverage/lcov-report/hooks/creation/useContainerCreation.js.html +0 -982
- package/coverage/lcov-report/hooks/creation/useLogsViewer.js.html +0 -238
- package/coverage/lcov-report/hooks/debug/index.html +0 -116
- package/coverage/lcov-report/hooks/debug/useDebugLogs.js.html +0 -187
- package/coverage/lcov-report/hooks/index.html +0 -161
- package/coverage/lcov-report/hooks/navigation/index.html +0 -116
- package/coverage/lcov-report/hooks/navigation/useContainerSelection.js.html +0 -169
- package/coverage/lcov-report/hooks/useContainerCommandRouter.js.html +0 -454
- package/coverage/lcov-report/hooks/useControls.js.html +0 -868
- package/coverage/lcov-report/hooks/useEraseConfirmation.js.html +0 -202
- package/coverage/lcov-report/hooks/useExitHandler.js.html +0 -292
- package/coverage/lcov-report/index.html +0 -176
- package/coverage/lcov-report/prettify.css +0 -1
- package/coverage/lcov-report/prettify.js +0 -2
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +0 -210
- package/coverage/lcov.info +0 -1163
- package/fix-imports.cjs +0 -54
- package/jest.config.cjs +0 -14
- package/src/App.jsx +0 -102
- package/src/components/ContainerCreationPrompt.jsx +0 -107
- package/src/components/ContainerList.jsx +0 -32
- package/src/components/ContainerRow.jsx +0 -84
- package/src/components/ContainerSection.jsx +0 -24
- package/src/components/ControlsHUD.jsx +0 -66
- package/src/components/Footer.jsx +0 -15
- package/src/components/Header.jsx +0 -41
- package/src/components/LogViewer.jsx +0 -36
- package/src/components/MessageFeedback.jsx +0 -25
- package/src/components/PromptField.jsx +0 -48
- package/src/components/StatsBar.jsx +0 -70
- package/src/components/SuggestionPanel.jsx +0 -61
- package/src/components/UsageMenu.jsx +0 -52
- package/src/helpers/appInfo.js +0 -23
- package/src/helpers/constants.js +0 -89
- package/src/helpers/containerOptionsBuilder.js +0 -53
- package/src/helpers/dockerHubService.js +0 -75
- package/src/helpers/dockerService/dockerService.js +0 -28
- package/src/helpers/dockerService/serviceComponents/containerActions.js +0 -282
- package/src/helpers/dockerService/serviceComponents/containerList.js +0 -74
- package/src/helpers/dockerService/serviceComponents/containerLogs.js +0 -60
- package/src/helpers/dockerService/serviceComponents/containerStats.js +0 -69
- package/src/helpers/dockerService/serviceComponents/imageUtils.js +0 -32
- package/src/helpers/exitWithMessage.js +0 -32
- package/src/helpers/imageNameUtils.js +0 -32
- package/src/helpers/logger.js +0 -112
- package/src/helpers/safeCall.js +0 -18
- package/src/helpers/validationHelpers.js +0 -87
- package/src/hooks/creation/useContainerActions.js +0 -89
- package/src/hooks/creation/useContainerCreation.js +0 -377
- package/src/hooks/creation/useLogsViewer.js +0 -51
- package/src/hooks/debug/useDebugLogs.js +0 -34
- package/src/hooks/navigation/useContainerSelection.js +0 -28
- package/src/hooks/useContainerCommandRouter.js +0 -123
- package/src/hooks/useContainerStats.js +0 -44
- package/src/hooks/useContainers.js +0 -26
- package/src/hooks/useControls.js +0 -263
- package/src/hooks/useEraseConfirmation.js +0 -39
- package/src/hooks/useExitHandler.js +0 -69
- package/src/index.js +0 -20
- package/test/ContainerCreationPrompt.dom.test.js +0 -313
- package/test/ControlsHUD.dom.test.js +0 -125
- package/test/SuggestionPanel.dom.test.js +0 -83
- package/test/constants.test.js +0 -41
- package/test/containerActions.test.js +0 -229
- package/test/containerList.test.js +0 -79
- package/test/containerOptionsBuilder.test.js +0 -36
- package/test/dockerHubService.test.js +0 -213
- package/test/jest.setup.js +0 -7
- package/test/logger.test.js +0 -58
- package/test/resolveImageTag.test.js +0 -26
- package/test/safeCall.test.js +0 -25
- package/test/useContainerCreation.dom.test.js +0 -567
- package/test/useContainerCreationHub.dom.test.js +0 -184
- package/test/useControls.dom.test.js +0 -519
- package/test/validationHelpers.test.js +0 -111
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import { useCallback } from 'react';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Hook responsible solely for routing Docker action keystrokes to the correct handler.
|
|
5
|
-
* Has no domain state of its own — it delegates every side-effect to its dependencies.
|
|
6
|
-
*
|
|
7
|
-
* Handled keys:
|
|
8
|
-
* i — start container
|
|
9
|
-
* p — stop container
|
|
10
|
-
* r — restart container
|
|
11
|
-
* e — request erase confirmation
|
|
12
|
-
* l — open logs viewer and start log stream
|
|
13
|
-
* c — open container creation wizard (reset + activate)
|
|
14
|
-
* d — toggle debug log panel
|
|
15
|
-
*
|
|
16
|
-
* @param {Object} params
|
|
17
|
-
* @param {Object} params.actions - API from useContainerActions
|
|
18
|
-
* @param {Array<Object>} params.containers - Current list of Docker containers
|
|
19
|
-
* @param {number} params.selected - Index of the currently selected container
|
|
20
|
-
* @param {Object} params.creation - API from useContainerCreation (must expose resetCreation)
|
|
21
|
-
* @param {Object} params.logsViewer - API from useLogsViewer
|
|
22
|
-
* @param {Function} params.startLogsStream - Starts the log stream for a container id
|
|
23
|
-
* @param {Function} params.onStartErase - Callback: activates confirmErase state
|
|
24
|
-
* @param {Function} params.onToggleDebug - Callback: toggles showDebugLogs state
|
|
25
|
-
* @param {Function} params.onStartCreate - Callback: sets creatingContainer = true
|
|
26
|
-
* @returns {{ handleDockerCommands: Function }}
|
|
27
|
-
*/
|
|
28
|
-
export function useContainerCommandRouter({
|
|
29
|
-
actions,
|
|
30
|
-
containers,
|
|
31
|
-
selected,
|
|
32
|
-
creation,
|
|
33
|
-
logsViewer,
|
|
34
|
-
startLogsStream,
|
|
35
|
-
onStartErase,
|
|
36
|
-
onToggleDebug,
|
|
37
|
-
onStartCreate,
|
|
38
|
-
}) {
|
|
39
|
-
const handleDockerCommands = useCallback(
|
|
40
|
-
(input) => {
|
|
41
|
-
const container = containers[selected];
|
|
42
|
-
|
|
43
|
-
if (input === 'i') {
|
|
44
|
-
actions.handleAction({
|
|
45
|
-
actionFn: async (id) => await actions.startContainer(id),
|
|
46
|
-
actionLabel: 'Starting',
|
|
47
|
-
selected,
|
|
48
|
-
stateCheck: (c) =>
|
|
49
|
-
(c.state === 'running' || c.status === 'running') &&
|
|
50
|
-
'Container is already running.',
|
|
51
|
-
});
|
|
52
|
-
return true;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (input === 'p') {
|
|
56
|
-
actions.handleAction({
|
|
57
|
-
actionFn: async (id) => await actions.stopContainer(id),
|
|
58
|
-
actionLabel: 'Stopping',
|
|
59
|
-
selected,
|
|
60
|
-
stateCheck: (c) =>
|
|
61
|
-
(c.state === 'exited' ||
|
|
62
|
-
c.status === 'exited' ||
|
|
63
|
-
c.state === 'stopped' ||
|
|
64
|
-
c.status === 'stopped') &&
|
|
65
|
-
'Container is already stopped.',
|
|
66
|
-
});
|
|
67
|
-
return true;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (input === 'r') {
|
|
71
|
-
actions.handleAction({
|
|
72
|
-
actionFn: async (id) => await actions.restartContainer(id),
|
|
73
|
-
actionLabel: 'Restarting',
|
|
74
|
-
selected,
|
|
75
|
-
});
|
|
76
|
-
return true;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (input === 'e') {
|
|
80
|
-
if (!container) {
|
|
81
|
-
return true;
|
|
82
|
-
}
|
|
83
|
-
onStartErase();
|
|
84
|
-
return true;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (input === 'l') {
|
|
88
|
-
if (!container) {
|
|
89
|
-
return true;
|
|
90
|
-
}
|
|
91
|
-
logsViewer.openLogs();
|
|
92
|
-
startLogsStream(container.id);
|
|
93
|
-
return true;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (input === 'c') {
|
|
97
|
-
creation.resetCreation();
|
|
98
|
-
onStartCreate();
|
|
99
|
-
return true;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (input === 'd') {
|
|
103
|
-
onToggleDebug();
|
|
104
|
-
return true;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return false;
|
|
108
|
-
},
|
|
109
|
-
[
|
|
110
|
-
actions,
|
|
111
|
-
containers,
|
|
112
|
-
creation,
|
|
113
|
-
logsViewer,
|
|
114
|
-
onStartCreate,
|
|
115
|
-
onStartErase,
|
|
116
|
-
onToggleDebug,
|
|
117
|
-
selected,
|
|
118
|
-
startLogsStream,
|
|
119
|
-
]
|
|
120
|
-
);
|
|
121
|
-
|
|
122
|
-
return { handleDockerCommands };
|
|
123
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect } from 'react';
|
|
2
|
-
import { getStats } from '../helpers/dockerService/serviceComponents/containerStats.js';
|
|
3
|
-
import { REFRESH_INTERVALS } from '../helpers/constants.js';
|
|
4
|
-
|
|
5
|
-
const DEFAULT_STATS = { cpuPercent: 0, memPercent: 0, netIO: { rx: 0, tx: 0 } };
|
|
6
|
-
|
|
7
|
-
export function useContainerStats(id, state) {
|
|
8
|
-
const [stats, setStats] = useState(DEFAULT_STATS);
|
|
9
|
-
const [statsError, setStatsError] = useState('');
|
|
10
|
-
|
|
11
|
-
useEffect(() => {
|
|
12
|
-
if (state !== 'running') {
|
|
13
|
-
setStats(DEFAULT_STATS);
|
|
14
|
-
setStatsError('');
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
let isMounted = true;
|
|
19
|
-
|
|
20
|
-
const fetchStats = async () => {
|
|
21
|
-
try {
|
|
22
|
-
const s = await getStats(id);
|
|
23
|
-
if (isMounted) {
|
|
24
|
-
setStats(s);
|
|
25
|
-
setStatsError('');
|
|
26
|
-
}
|
|
27
|
-
} catch (err) {
|
|
28
|
-
if (isMounted) {
|
|
29
|
-
setStats(DEFAULT_STATS);
|
|
30
|
-
setStatsError('Error fetching stats');
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
fetchStats();
|
|
36
|
-
const timer = setInterval(fetchStats, REFRESH_INTERVALS.CONTAINER_STATS);
|
|
37
|
-
return () => {
|
|
38
|
-
isMounted = false;
|
|
39
|
-
clearInterval(timer);
|
|
40
|
-
};
|
|
41
|
-
}, [id, state]);
|
|
42
|
-
|
|
43
|
-
return { stats, statsError };
|
|
44
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* React hook to manage Docker containers state.
|
|
3
|
-
*
|
|
4
|
-
* @returns {{containers: Array<Object>}} An object containing the current
|
|
5
|
-
* containers array. The hook starts a periodic refresh and fetches the
|
|
6
|
-
* container list immediately on mount.
|
|
7
|
-
* @example
|
|
8
|
-
* // Use in a component
|
|
9
|
-
* const { containers } = useContainers();
|
|
10
|
-
*/
|
|
11
|
-
import React, { useState, useEffect } from 'react';
|
|
12
|
-
import { getContainers } from '../helpers/dockerService/serviceComponents/containerList';
|
|
13
|
-
import { REFRESH_INTERVALS } from '../helpers/constants.js';
|
|
14
|
-
|
|
15
|
-
export function useContainers() {
|
|
16
|
-
const [containers, setContainers] = useState([]);
|
|
17
|
-
|
|
18
|
-
useEffect(() => {
|
|
19
|
-
const fetch = async () => setContainers(await getContainers());
|
|
20
|
-
fetch();
|
|
21
|
-
const timer = setInterval(fetch, REFRESH_INTERVALS.CONTAINER_LIST);
|
|
22
|
-
return () => clearInterval(timer);
|
|
23
|
-
}, []);
|
|
24
|
-
|
|
25
|
-
return { containers };
|
|
26
|
-
}
|
package/src/hooks/useControls.js
DELETED
|
@@ -1,263 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { useInput } from 'ink';
|
|
3
|
-
import { useContainerActions } from './creation/useContainerActions';
|
|
4
|
-
import { useContainerCreation } from './creation/useContainerCreation';
|
|
5
|
-
import { useLogsViewer } from './creation/useLogsViewer';
|
|
6
|
-
import { useContainerSelection } from './navigation/useContainerSelection';
|
|
7
|
-
import { useDebugLogs } from './debug/useDebugLogs';
|
|
8
|
-
import { useEraseConfirmation } from './useEraseConfirmation';
|
|
9
|
-
import { useExitHandler } from './useExitHandler';
|
|
10
|
-
import { useContainerCommandRouter } from './useContainerCommandRouter';
|
|
11
|
-
import { getLogsStream } from '../helpers/dockerService/serviceComponents/containerLogs.js';
|
|
12
|
-
import { createContainer as svcCreateContainer } from '../helpers/dockerService/serviceComponents/containerActions.js';
|
|
13
|
-
import { buildContainerOptions } from '../helpers/containerOptionsBuilder.js';
|
|
14
|
-
import { DB_IMAGES } from '../helpers/constants.js';
|
|
15
|
-
|
|
16
|
-
// Principal hook to manage user inputs and control the app state
|
|
17
|
-
/**
|
|
18
|
-
* Main hook that wires user input, creation, actions and logs viewing.
|
|
19
|
-
* It coordinates the modular hooks and exposes a compact API consumed by the App.
|
|
20
|
-
*
|
|
21
|
-
* @param {Array<Object>} containers - Current list of Docker containers
|
|
22
|
-
* @returns {Object} controls - API for the App component
|
|
23
|
-
*/
|
|
24
|
-
export function useControls(containers = [], overrides = {}) {
|
|
25
|
-
const [creatingContainer, setCreatingContainer] = React.useState(false);
|
|
26
|
-
|
|
27
|
-
// — Modular hooks —
|
|
28
|
-
const actions = useContainerActions({ containers });
|
|
29
|
-
|
|
30
|
-
const creation = useContainerCreation({
|
|
31
|
-
onCreate: async ({ imageName, containerName, portInput, envInput }) => {
|
|
32
|
-
const options = buildContainerOptions({
|
|
33
|
-
imageName,
|
|
34
|
-
containerName,
|
|
35
|
-
portInput,
|
|
36
|
-
envInput,
|
|
37
|
-
});
|
|
38
|
-
actions.setTimedMessage(`Creating container ${imageName}...`, 'yellow');
|
|
39
|
-
try {
|
|
40
|
-
const { id, ports } = await svcCreateContainer(imageName, options);
|
|
41
|
-
let portMsg = '';
|
|
42
|
-
if (ports && ports.length) {
|
|
43
|
-
portMsg =
|
|
44
|
-
' | Ports: ' +
|
|
45
|
-
ports
|
|
46
|
-
.map((p) => `${p.hostPort}→${p.containerPort}/${p.protocol}`)
|
|
47
|
-
.join(', ');
|
|
48
|
-
}
|
|
49
|
-
actions.setTimedMessage(`Created container ${id}${portMsg}`, 'green');
|
|
50
|
-
} catch (err) {
|
|
51
|
-
actions.setTimedMessage(`Error creating container: ${err.message}`, 'red');
|
|
52
|
-
} finally {
|
|
53
|
-
setCreatingContainer(false);
|
|
54
|
-
}
|
|
55
|
-
},
|
|
56
|
-
onCancel: () => setCreatingContainer(false),
|
|
57
|
-
dbImages: DB_IMAGES,
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
const logsViewer = useLogsViewer();
|
|
61
|
-
const selection = useContainerSelection(containers.length);
|
|
62
|
-
const debugLogs = useDebugLogs();
|
|
63
|
-
|
|
64
|
-
// Allow overrides for testability (e.g. injecting a mock triggerHubSearch)
|
|
65
|
-
const triggerHubSearch = overrides.triggerHubSearch ?? creation.triggerHubSearch;
|
|
66
|
-
const isSearchingHub = overrides.isSearchingHub ?? creation.isSearchingHub;
|
|
67
|
-
const insertNextSuggestedEnv = overrides.insertNextSuggestedEnv !== undefined
|
|
68
|
-
? overrides.insertNextSuggestedEnv
|
|
69
|
-
: creation.insertNextSuggestedEnv;
|
|
70
|
-
|
|
71
|
-
const eraseConfirmation = useEraseConfirmation({
|
|
72
|
-
onConfirm: () => {
|
|
73
|
-
actions.handleAction({
|
|
74
|
-
actionFn: async (id) => await actions.removeContainer(id),
|
|
75
|
-
actionLabel: 'Erasing',
|
|
76
|
-
selected: selection.selected,
|
|
77
|
-
});
|
|
78
|
-
actions.setMessageColor('yellow');
|
|
79
|
-
},
|
|
80
|
-
onCancel: () => {
|
|
81
|
-
actions.setMessage('');
|
|
82
|
-
actions.setMessageColor('');
|
|
83
|
-
},
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Pipe container logs into the viewer while applying a hard limit.
|
|
88
|
-
* @param {string} containerId - Container identifier used by Docker.
|
|
89
|
-
*/
|
|
90
|
-
const startLogsStream = React.useCallback(
|
|
91
|
-
(containerId) => {
|
|
92
|
-
getLogsStream(
|
|
93
|
-
containerId,
|
|
94
|
-
(data) =>
|
|
95
|
-
logsViewer.setLogs((prev) => {
|
|
96
|
-
const newLogs = [...prev, ...data.split('\n').filter(Boolean)];
|
|
97
|
-
return newLogs.slice(-1000);
|
|
98
|
-
}),
|
|
99
|
-
() => {},
|
|
100
|
-
(err) =>
|
|
101
|
-
logsViewer.setLogs((prev) => [...prev, `Error: ${err.message}`])
|
|
102
|
-
);
|
|
103
|
-
},
|
|
104
|
-
[logsViewer]
|
|
105
|
-
);
|
|
106
|
-
|
|
107
|
-
const commandRouter = useContainerCommandRouter({
|
|
108
|
-
actions,
|
|
109
|
-
containers,
|
|
110
|
-
selected: selection.selected,
|
|
111
|
-
creation,
|
|
112
|
-
logsViewer,
|
|
113
|
-
startLogsStream,
|
|
114
|
-
onStartErase: () => {
|
|
115
|
-
eraseConfirmation.startErase();
|
|
116
|
-
actions.setMessage(
|
|
117
|
-
'Are you sure you want to erase this container? (y/n)'
|
|
118
|
-
);
|
|
119
|
-
actions.setMessageColor('yellow');
|
|
120
|
-
},
|
|
121
|
-
onToggleDebug: () => debugLogs.setShowDebugLogs((prev) => !prev),
|
|
122
|
-
onStartCreate: () => setCreatingContainer(true),
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
const exitHandler = useExitHandler({
|
|
126
|
-
onBeforeExit: () => {
|
|
127
|
-
actions.setMessage('Exiting...');
|
|
128
|
-
actions.setMessageColor('yellow');
|
|
129
|
-
logsViewer.closeLogs();
|
|
130
|
-
debugLogs.setShowDebugLogs(false);
|
|
131
|
-
},
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Route keystrokes to the container creation wizard.
|
|
136
|
-
*/
|
|
137
|
-
const processCreationInput = React.useCallback(
|
|
138
|
-
(input, key) => {
|
|
139
|
-
const step = creation.step;
|
|
140
|
-
|
|
141
|
-
const removeLastChar = (setter) =>
|
|
142
|
-
setter((value) => (value || '').slice(0, -1));
|
|
143
|
-
const appendChar = (setter, current, ch) => setter((current || '') + ch);
|
|
144
|
-
|
|
145
|
-
if (key.escape) {
|
|
146
|
-
creation.cancelCreation();
|
|
147
|
-
setCreatingContainer(false);
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (input === '\r' || input === '\n') {
|
|
152
|
-
// If on step 0 with a focused suggestion, apply it instead of advancing
|
|
153
|
-
if (step === 0 && creation.selectedSuggestionIndex >= 0) {
|
|
154
|
-
creation.applyFocusedSuggestion();
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
creation.nextStep();
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Tab on step 0: trigger Docker Hub search (with guards)
|
|
162
|
-
if (key.tab) {
|
|
163
|
-
if (step === 0) {
|
|
164
|
-
if (!isSearchingHub && (creation.imageName || '').trim() !== '') {
|
|
165
|
-
triggerHubSearch();
|
|
166
|
-
}
|
|
167
|
-
} else if (step === 3) {
|
|
168
|
-
insertNextSuggestedEnv?.();
|
|
169
|
-
}
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Arrow keys on step 0: navigate suggestion list
|
|
174
|
-
if (step === 0 && creation.suggestions.length > 0) {
|
|
175
|
-
if (key.upArrow) {
|
|
176
|
-
creation.moveSuggestionSelection(-1);
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
if (key.downArrow) {
|
|
180
|
-
creation.moveSuggestionSelection(1);
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (key.backspace || key.delete) {
|
|
186
|
-
if (step === 0) {
|
|
187
|
-
const newValue = (creation.imageName || '').slice(0, -1);
|
|
188
|
-
creation.updateImageInput(newValue);
|
|
189
|
-
}
|
|
190
|
-
if (step === 1) removeLastChar(creation.setContainerName);
|
|
191
|
-
if (step === 2) removeLastChar(creation.setPortInput);
|
|
192
|
-
if (step === 3) removeLastChar(creation.setEnvInput);
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
if (input && input.length === 1 && !key.ctrl && !key.meta) {
|
|
197
|
-
if (step === 0) {
|
|
198
|
-
creation.updateImageInput((creation.imageName || '') + input);
|
|
199
|
-
}
|
|
200
|
-
if (step === 1)
|
|
201
|
-
appendChar(creation.setContainerName, creation.containerName, input);
|
|
202
|
-
if (step === 2)
|
|
203
|
-
appendChar(creation.setPortInput, creation.portInput, input);
|
|
204
|
-
if (step === 3)
|
|
205
|
-
appendChar(creation.setEnvInput, creation.envInput, input);
|
|
206
|
-
}
|
|
207
|
-
},
|
|
208
|
-
[creation, setCreatingContainer, triggerHubSearch, isSearchingHub, insertNextSuggestedEnv]
|
|
209
|
-
);
|
|
210
|
-
|
|
211
|
-
// Single keyboard entry point
|
|
212
|
-
useInput((input, key) => {
|
|
213
|
-
if (eraseConfirmation.confirmErase) {
|
|
214
|
-
eraseConfirmation.processEraseConfirmation(input, key);
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
if (logsViewer.showLogs) {
|
|
219
|
-
if (input === 'q' || key.escape) {
|
|
220
|
-
logsViewer.closeLogs();
|
|
221
|
-
}
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
if (creatingContainer) {
|
|
226
|
-
processCreationInput(input, key);
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
if (debugLogs.showDebugLogs && key.escape) {
|
|
231
|
-
debugLogs.setShowDebugLogs(false);
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
commandRouter.handleDockerCommands(input);
|
|
236
|
-
exitHandler.handleExitCommand(input);
|
|
237
|
-
selection.handleNavigation(input, key);
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
return {
|
|
241
|
-
selected: selection.selected,
|
|
242
|
-
setSelected: selection.setSelected,
|
|
243
|
-
message: creatingContainer ? creation.message : actions.message,
|
|
244
|
-
messageColor: creatingContainer
|
|
245
|
-
? creation.messageColor
|
|
246
|
-
: actions.messageColor,
|
|
247
|
-
showLogs: logsViewer.showLogs,
|
|
248
|
-
logs: logsViewer.logs,
|
|
249
|
-
exitLogs: logsViewer.closeLogs,
|
|
250
|
-
creatingContainer,
|
|
251
|
-
creationStep: creation.step,
|
|
252
|
-
imageNameInput: creation.imageName,
|
|
253
|
-
containerNameInput: creation.containerName,
|
|
254
|
-
portInput: creation.portInput,
|
|
255
|
-
envInput: creation.envInput,
|
|
256
|
-
creation,
|
|
257
|
-
actions,
|
|
258
|
-
logsViewer,
|
|
259
|
-
confirmErase: eraseConfirmation.confirmErase,
|
|
260
|
-
showDebugLogs: debugLogs.showDebugLogs,
|
|
261
|
-
debugLogs: debugLogs.debugLogs,
|
|
262
|
-
};
|
|
263
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { useState, useCallback } from 'react';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Hook to manage the erase confirmation dialog flow.
|
|
5
|
-
*
|
|
6
|
-
* Encapsulates the `confirmErase` state and the input handler that resolves it.
|
|
7
|
-
* The caller is responsible for what happens on confirm/cancel via the callbacks.
|
|
8
|
-
*
|
|
9
|
-
* @param {Object} params
|
|
10
|
-
* @param {Function} params.onConfirm - Called when the user presses y/Y. Should trigger the erase action.
|
|
11
|
-
* @param {Function} params.onCancel - Called when the user presses n/N or Escape. Should clean up messages.
|
|
12
|
-
* @returns {{ confirmErase: boolean, startErase: Function, processEraseConfirmation: Function }}
|
|
13
|
-
*/
|
|
14
|
-
export function useEraseConfirmation({ onConfirm, onCancel }) {
|
|
15
|
-
const [confirmErase, setConfirmErase] = useState(false);
|
|
16
|
-
|
|
17
|
-
const startErase = useCallback(() => {
|
|
18
|
-
setConfirmErase(true);
|
|
19
|
-
}, []);
|
|
20
|
-
|
|
21
|
-
const processEraseConfirmation = useCallback(
|
|
22
|
-
(input, key) => {
|
|
23
|
-
if (input === 'y' || input === 'Y') {
|
|
24
|
-
onConfirm();
|
|
25
|
-
setConfirmErase(false);
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
if (input === 'n' || input === 'N' || key.escape) {
|
|
30
|
-
setConfirmErase(false);
|
|
31
|
-
onCancel();
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
},
|
|
35
|
-
[onConfirm, onCancel]
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
return { confirmErase, startErase, processEraseConfirmation };
|
|
39
|
-
}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { useCallback } from 'react';
|
|
2
|
-
import { useApp } from 'ink';
|
|
3
|
-
import { EXIT_DELAY } from '../helpers/constants.js';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Hook that encapsulates the application exit sequence triggered by pressing `q`.
|
|
7
|
-
*
|
|
8
|
-
* Responsibilities:
|
|
9
|
-
* - Detect the `q` keypress
|
|
10
|
-
* - Invoke an optional `onBeforeExit` callback for resource cleanup (e.g. closing logs)
|
|
11
|
-
* - Clear the terminal after a short delay
|
|
12
|
-
* - Call Ink's `exit()` and, outside of test environments, `process.exit(0)`
|
|
13
|
-
*
|
|
14
|
-
* @param {Object} [options]
|
|
15
|
-
* @param {Function} [options.onBeforeExit] - Optional callback invoked synchronously
|
|
16
|
-
* before the exit delay starts. Use it to close logs, reset UI state, etc.
|
|
17
|
-
* @returns {{ handleExitCommand: Function }}
|
|
18
|
-
*/
|
|
19
|
-
export function useExitHandler({ onBeforeExit } = {}) {
|
|
20
|
-
const { exit } = useApp();
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Handle the quit command.
|
|
24
|
-
*
|
|
25
|
-
* Mirrors the exact logic of `handleExitCommand` in `useControls.js`:
|
|
26
|
-
* 1. Guard: return false if input is not 'q'
|
|
27
|
-
* 2. Call onBeforeExit (replaces inline logsViewer.closeLogs + setShowDebugLogs(false))
|
|
28
|
-
* 3. After EXIT_DELAY: clear terminal, call exit(), then process.exit(0) outside tests
|
|
29
|
-
*
|
|
30
|
-
* @param {string} input - Raw character input from Ink's useInput
|
|
31
|
-
* @returns {boolean} True when the input was consumed, false otherwise
|
|
32
|
-
*/
|
|
33
|
-
const handleExitCommand = useCallback(
|
|
34
|
-
(input) => {
|
|
35
|
-
if (input !== 'q') {
|
|
36
|
-
return false;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (typeof onBeforeExit === 'function') {
|
|
40
|
-
onBeforeExit();
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const clearTerminal = () => {
|
|
44
|
-
try {
|
|
45
|
-
if (process.stdout && process.stdout.isTTY) {
|
|
46
|
-
process.stdout.write('\u001Bc');
|
|
47
|
-
} else {
|
|
48
|
-
console.clear();
|
|
49
|
-
}
|
|
50
|
-
} catch (err) {
|
|
51
|
-
// Ignore clear errors to avoid blocking exit.
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
setTimeout(() => {
|
|
56
|
-
clearTerminal();
|
|
57
|
-
exit();
|
|
58
|
-
if (process.env.NODE_ENV !== 'test') {
|
|
59
|
-
setTimeout(() => process.exit(0), 50);
|
|
60
|
-
}
|
|
61
|
-
}, EXIT_DELAY);
|
|
62
|
-
|
|
63
|
-
return true;
|
|
64
|
-
},
|
|
65
|
-
[exit, onBeforeExit]
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
return { handleExitCommand };
|
|
69
|
-
}
|
package/src/index.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Entry point for the CDD CLI application.
|
|
4
|
-
*
|
|
5
|
-
* @module index
|
|
6
|
-
* @example
|
|
7
|
-
* // Run the CLI
|
|
8
|
-
* node index.js
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import React from 'react';
|
|
12
|
-
import { render } from 'ink';
|
|
13
|
-
import App from './App.jsx';
|
|
14
|
-
import { logger } from './helpers/logger.js';
|
|
15
|
-
|
|
16
|
-
process.on('uncaughtException', (err) => logger.error('Uncaught exception', err));
|
|
17
|
-
process.on('unhandledRejection', (reason) => logger.error('Unhandled rejection', reason));
|
|
18
|
-
|
|
19
|
-
console.clear();
|
|
20
|
-
render(<App />);
|