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.
Files changed (135) hide show
  1. package/README.es.md +60 -26
  2. package/README.md +60 -26
  3. package/dist/App.js +1 -0
  4. package/dist/components/ContainerCreationPrompt.js +14 -0
  5. package/dist/components/PromptField.js +13 -6
  6. package/dist/components/TextField.js +54 -0
  7. package/dist/components/UsageMenu.js +1 -1
  8. package/dist/helpers/appState.js +26 -0
  9. package/dist/helpers/dockerService/serviceComponents/containerExec.js +211 -0
  10. package/dist/helpers/textEditing.js +293 -0
  11. package/dist/hooks/creation/useContainerCreation.js +132 -15
  12. package/dist/hooks/useContainerCommandRouter.js +12 -2
  13. package/dist/hooks/useControls.js +21 -26
  14. package/dist/hooks/useShellMode.js +101 -0
  15. package/dist/index.js +3 -1
  16. package/package.json +4 -1
  17. package/.atl/skill-registry.md +0 -30
  18. package/.editorconfig +0 -12
  19. package/.eslintrc.json +0 -30
  20. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -30
  21. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
  22. package/.github/PULL_REQUEST_TEMPLATE/pull_request.md +0 -14
  23. package/.github/workflows/ci.yml +0 -32
  24. package/.github/workflows/docs.yml +0 -42
  25. package/.prettierignore +0 -4
  26. package/.prettierrc +0 -8
  27. package/.~lock.ROADMAP.md# +0 -1
  28. package/CHANGELOG.md +0 -216
  29. package/CODE_OF_CONDUCT.md +0 -12
  30. package/CONTRIBUTING.md +0 -35
  31. package/ROADMAP.md +0 -647
  32. package/SECURITY.md +0 -5
  33. package/__mocks__/ink.cjs +0 -17
  34. package/_config.yml +0 -13
  35. package/babel.config.cjs +0 -23
  36. package/coverage/clover.xml +0 -575
  37. package/coverage/coverage-final.json +0 -17
  38. package/coverage/lcov-report/ContainerCreationPrompt.jsx.html +0 -400
  39. package/coverage/lcov-report/ControlsHUD.jsx.html +0 -256
  40. package/coverage/lcov-report/base.css +0 -224
  41. package/coverage/lcov-report/block-navigation.js +0 -87
  42. package/coverage/lcov-report/components/ContainerCreationPrompt.jsx.html +0 -394
  43. package/coverage/lcov-report/components/PromptField.jsx.html +0 -229
  44. package/coverage/lcov-report/components/SuggestionPanel.jsx.html +0 -268
  45. package/coverage/lcov-report/components/index.html +0 -146
  46. package/coverage/lcov-report/favicon.png +0 -0
  47. package/coverage/lcov-report/helpers/constants.js.html +0 -352
  48. package/coverage/lcov-report/helpers/containerOptionsBuilder.js.html +0 -244
  49. package/coverage/lcov-report/helpers/dockerHubService.js.html +0 -310
  50. package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerActions.js.html +0 -931
  51. package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerList.js.html +0 -307
  52. package/coverage/lcov-report/helpers/dockerService/serviceComponents/index.html +0 -131
  53. package/coverage/lcov-report/helpers/imageNameUtils.js.html +0 -181
  54. package/coverage/lcov-report/helpers/index.html +0 -206
  55. package/coverage/lcov-report/helpers/logger.js.html +0 -424
  56. package/coverage/lcov-report/helpers/safeCall.js.html +0 -139
  57. package/coverage/lcov-report/helpers/validationHelpers.js.html +0 -346
  58. package/coverage/lcov-report/hooks/creation/index.html +0 -146
  59. package/coverage/lcov-report/hooks/creation/useContainerActions.js.html +0 -313
  60. package/coverage/lcov-report/hooks/creation/useContainerCreation.js.html +0 -982
  61. package/coverage/lcov-report/hooks/creation/useLogsViewer.js.html +0 -238
  62. package/coverage/lcov-report/hooks/debug/index.html +0 -116
  63. package/coverage/lcov-report/hooks/debug/useDebugLogs.js.html +0 -187
  64. package/coverage/lcov-report/hooks/index.html +0 -161
  65. package/coverage/lcov-report/hooks/navigation/index.html +0 -116
  66. package/coverage/lcov-report/hooks/navigation/useContainerSelection.js.html +0 -169
  67. package/coverage/lcov-report/hooks/useContainerCommandRouter.js.html +0 -454
  68. package/coverage/lcov-report/hooks/useControls.js.html +0 -868
  69. package/coverage/lcov-report/hooks/useEraseConfirmation.js.html +0 -202
  70. package/coverage/lcov-report/hooks/useExitHandler.js.html +0 -292
  71. package/coverage/lcov-report/index.html +0 -176
  72. package/coverage/lcov-report/prettify.css +0 -1
  73. package/coverage/lcov-report/prettify.js +0 -2
  74. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  75. package/coverage/lcov-report/sorter.js +0 -210
  76. package/coverage/lcov.info +0 -1163
  77. package/fix-imports.cjs +0 -54
  78. package/jest.config.cjs +0 -14
  79. package/src/App.jsx +0 -102
  80. package/src/components/ContainerCreationPrompt.jsx +0 -107
  81. package/src/components/ContainerList.jsx +0 -32
  82. package/src/components/ContainerRow.jsx +0 -84
  83. package/src/components/ContainerSection.jsx +0 -24
  84. package/src/components/ControlsHUD.jsx +0 -66
  85. package/src/components/Footer.jsx +0 -15
  86. package/src/components/Header.jsx +0 -41
  87. package/src/components/LogViewer.jsx +0 -36
  88. package/src/components/MessageFeedback.jsx +0 -25
  89. package/src/components/PromptField.jsx +0 -48
  90. package/src/components/StatsBar.jsx +0 -70
  91. package/src/components/SuggestionPanel.jsx +0 -61
  92. package/src/components/UsageMenu.jsx +0 -52
  93. package/src/helpers/appInfo.js +0 -23
  94. package/src/helpers/constants.js +0 -89
  95. package/src/helpers/containerOptionsBuilder.js +0 -53
  96. package/src/helpers/dockerHubService.js +0 -75
  97. package/src/helpers/dockerService/dockerService.js +0 -28
  98. package/src/helpers/dockerService/serviceComponents/containerActions.js +0 -282
  99. package/src/helpers/dockerService/serviceComponents/containerList.js +0 -74
  100. package/src/helpers/dockerService/serviceComponents/containerLogs.js +0 -60
  101. package/src/helpers/dockerService/serviceComponents/containerStats.js +0 -69
  102. package/src/helpers/dockerService/serviceComponents/imageUtils.js +0 -32
  103. package/src/helpers/exitWithMessage.js +0 -32
  104. package/src/helpers/imageNameUtils.js +0 -32
  105. package/src/helpers/logger.js +0 -112
  106. package/src/helpers/safeCall.js +0 -18
  107. package/src/helpers/validationHelpers.js +0 -87
  108. package/src/hooks/creation/useContainerActions.js +0 -89
  109. package/src/hooks/creation/useContainerCreation.js +0 -377
  110. package/src/hooks/creation/useLogsViewer.js +0 -51
  111. package/src/hooks/debug/useDebugLogs.js +0 -34
  112. package/src/hooks/navigation/useContainerSelection.js +0 -28
  113. package/src/hooks/useContainerCommandRouter.js +0 -123
  114. package/src/hooks/useContainerStats.js +0 -44
  115. package/src/hooks/useContainers.js +0 -26
  116. package/src/hooks/useControls.js +0 -263
  117. package/src/hooks/useEraseConfirmation.js +0 -39
  118. package/src/hooks/useExitHandler.js +0 -69
  119. package/src/index.js +0 -20
  120. package/test/ContainerCreationPrompt.dom.test.js +0 -313
  121. package/test/ControlsHUD.dom.test.js +0 -125
  122. package/test/SuggestionPanel.dom.test.js +0 -83
  123. package/test/constants.test.js +0 -41
  124. package/test/containerActions.test.js +0 -229
  125. package/test/containerList.test.js +0 -79
  126. package/test/containerOptionsBuilder.test.js +0 -36
  127. package/test/dockerHubService.test.js +0 -213
  128. package/test/jest.setup.js +0 -7
  129. package/test/logger.test.js +0 -58
  130. package/test/resolveImageTag.test.js +0 -26
  131. package/test/safeCall.test.js +0 -25
  132. package/test/useContainerCreation.dom.test.js +0 -567
  133. package/test/useContainerCreationHub.dom.test.js +0 -184
  134. package/test/useControls.dom.test.js +0 -519
  135. 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
- }
@@ -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 />);