cdd-cli 3.1.6 → 3.1.9

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 (96) hide show
  1. package/.eslintrc.json +1 -1
  2. package/.github/workflows/ci.yml +3 -0
  3. package/.github/workflows/docs.yml +8 -1
  4. package/CHANGELOG.md +23 -0
  5. package/CONTRIBUTING.md +28 -15
  6. package/README.md +56 -88
  7. package/babel.config.cjs +18 -1
  8. package/dist/components/ContainerRow.js +5 -78
  9. package/dist/components/LogViewer.js +4 -9
  10. package/dist/helpers/containerOptionsBuilder.js +58 -0
  11. package/dist/hooks/creation/useContainerCreation.js +16 -1
  12. package/dist/hooks/debug/useDebugLogs.js +46 -0
  13. package/dist/hooks/navigation/useContainerSelection.js +36 -0
  14. package/dist/hooks/useContainerCommandRouter.js +151 -0
  15. package/dist/{helpers/actionHelpers.js → hooks/useContainerStats.js} +76 -78
  16. package/dist/hooks/useControls.js +83 -342
  17. package/dist/hooks/useEraseConfirmation.js +47 -0
  18. package/dist/hooks/useExitHandler.js +68 -0
  19. package/jest.config.cjs +1 -1
  20. package/package.json +4 -5
  21. package/src/components/ContainerRow.jsx +3 -38
  22. package/src/components/LogViewer.jsx +4 -10
  23. package/src/helpers/containerOptionsBuilder.js +42 -0
  24. package/src/hooks/creation/useContainerCreation.js +15 -0
  25. package/src/hooks/debug/useDebugLogs.js +29 -0
  26. package/src/hooks/navigation/useContainerSelection.js +25 -0
  27. package/src/hooks/useContainerCommandRouter.js +110 -0
  28. package/src/hooks/useContainerStats.js +44 -0
  29. package/src/hooks/useControls.js +68 -272
  30. package/src/hooks/useEraseConfirmation.js +39 -0
  31. package/src/hooks/useExitHandler.js +66 -0
  32. package/test/useContainerCreation.dom.test.js +3 -0
  33. package/.babelrc +0 -6
  34. package/FIXES_APPLIED.md +0 -419
  35. package/SECURITY.md +0 -5
  36. package/dist/hooks/useLogsStream.js +0 -41
  37. package/docs/_config.yml +0 -13
  38. package/docs/components_ContainerCreationPrompt.jsx.html +0 -114
  39. package/docs/components_ContainerRow.jsx.html +0 -151
  40. package/docs/components_ContainerSection.jsx.html +0 -75
  41. package/docs/components_Footer.jsx.html +0 -66
  42. package/docs/components_Header.jsx.html +0 -84
  43. package/docs/components_LogViewer.jsx.html +0 -91
  44. package/docs/components_MessageFeedback.jsx.html +0 -76
  45. package/docs/components_PromptField.jsx.html +0 -99
  46. package/docs/components_StatsBar.jsx.html +0 -105
  47. package/docs/components_UsageMenu.jsx.html +0 -73
  48. package/docs/fonts/OpenSans-Bold-webfont.eot +0 -0
  49. package/docs/fonts/OpenSans-Bold-webfont.svg +0 -1830
  50. package/docs/fonts/OpenSans-Bold-webfont.woff +0 -0
  51. package/docs/fonts/OpenSans-BoldItalic-webfont.eot +0 -0
  52. package/docs/fonts/OpenSans-BoldItalic-webfont.svg +0 -1830
  53. package/docs/fonts/OpenSans-BoldItalic-webfont.woff +0 -0
  54. package/docs/fonts/OpenSans-Italic-webfont.eot +0 -0
  55. package/docs/fonts/OpenSans-Italic-webfont.svg +0 -1830
  56. package/docs/fonts/OpenSans-Italic-webfont.woff +0 -0
  57. package/docs/fonts/OpenSans-Light-webfont.eot +0 -0
  58. package/docs/fonts/OpenSans-Light-webfont.svg +0 -1831
  59. package/docs/fonts/OpenSans-Light-webfont.woff +0 -0
  60. package/docs/fonts/OpenSans-LightItalic-webfont.eot +0 -0
  61. package/docs/fonts/OpenSans-LightItalic-webfont.svg +0 -1835
  62. package/docs/fonts/OpenSans-LightItalic-webfont.woff +0 -0
  63. package/docs/fonts/OpenSans-Regular-webfont.eot +0 -0
  64. package/docs/fonts/OpenSans-Regular-webfont.svg +0 -1831
  65. package/docs/fonts/OpenSans-Regular-webfont.woff +0 -0
  66. package/docs/global.html +0 -6145
  67. package/docs/helpers_actionHelpers.js.html +0 -94
  68. package/docs/helpers_constants.js.html +0 -81
  69. package/docs/helpers_dockerService_dockerService.js.html +0 -79
  70. package/docs/helpers_dockerService_serviceComponents_containerActions.js.html +0 -153
  71. package/docs/helpers_dockerService_serviceComponents_containerList.js.html +0 -81
  72. package/docs/helpers_dockerService_serviceComponents_containerLogs.js.html +0 -85
  73. package/docs/helpers_dockerService_serviceComponents_containerStats.js.html +0 -107
  74. package/docs/helpers_dockerService_serviceComponents_imageUtils.js.html +0 -82
  75. package/docs/helpers_exitWithMessage.js.html +0 -77
  76. package/docs/helpers_safeCall.js.html +0 -68
  77. package/docs/helpers_validationHelpers.js.html +0 -96
  78. package/docs/hooks_creation_useContainerActions.js.html +0 -120
  79. package/docs/hooks_creation_useContainerCreation.js.html +0 -152
  80. package/docs/hooks_creation_useLogsViewer.js.html +0 -102
  81. package/docs/hooks_useControls.js.html +0 -298
  82. package/docs/hooks_useLogsStream.js.html +0 -82
  83. package/docs/index.html +0 -65
  84. package/docs/index.js.html +0 -65
  85. package/docs/module-index.html +0 -171
  86. package/docs/scripts/linenumber.js +0 -25
  87. package/docs/scripts/prettify/Apache-License-2.0.txt +0 -202
  88. package/docs/scripts/prettify/lang-css.js +0 -2
  89. package/docs/scripts/prettify/prettify.js +0 -28
  90. package/docs/styles/jsdoc-default.css +0 -358
  91. package/docs/styles/prettify-jsdoc.css +0 -111
  92. package/docs/styles/prettify-tomorrow.css +0 -132
  93. package/esbuild.config.cjs +0 -11
  94. package/src/helpers/actionHelpers.js +0 -52
  95. package/src/hooks/useLogsStream.js +0 -31
  96. package/test/actionHelpers.test.js +0 -106
@@ -1,9 +1,8 @@
1
- import React, { useState, useEffect } from "react";
1
+ import React from "react";
2
2
  import { Box, Text } from "ink";
3
- import { getStats } from "../helpers/dockerService/serviceComponents/containerStats";
4
- import { REFRESH_INTERVALS } from "../helpers/constants.js";
5
3
  import StatsBar from "./StatsBar.jsx";
6
4
  import PropTypes from 'prop-types';
5
+ import { useContainerStats } from '../hooks/useContainerStats.js';
7
6
 
8
7
  const stateText = (state) => {
9
8
  if (state === "running") return { text: "🟢 RUNNING", color: "green" };
@@ -21,11 +20,7 @@ const stateText = (state) => {
21
20
  */
22
21
  export default function ContainerRow({ container, isSelected = false }) {
23
22
  const { id, name, image, state } = container;
24
- const [stats, setStats] = useState({
25
- cpuPercent: 0,
26
- memPercent: 0,
27
- netIO: { rx: 0, tx: 0 },
28
- });
23
+ const { stats, statsError } = useContainerStats(id, state);
29
24
 
30
25
  // Format ports for display (no leading space to avoid layout shifts)
31
26
  const formatPorts = (ports) => {
@@ -42,36 +37,6 @@ export default function ContainerRow({ container, isSelected = false }) {
42
37
  return s.length > max ? s.slice(0, max - 1) + "…" : s;
43
38
  };
44
39
 
45
- const [statsError, setStatsError] = useState("");
46
- useEffect(() => {
47
- if (state !== "running") return;
48
-
49
- let isMounted = true;
50
-
51
- const fetchStats = async () => {
52
- try {
53
- const s = await getStats(id);
54
- if (isMounted) {
55
- setStats(s);
56
- setStatsError("");
57
- }
58
- } catch (err) {
59
- if (isMounted) {
60
- setStats({ cpuPercent: 0, memPercent: 0, netIO: { rx: 0, tx: 0 } });
61
- setStatsError("Error fetching stats");
62
- }
63
- }
64
- };
65
-
66
- fetchStats();
67
- const timer = setInterval(fetchStats, REFRESH_INTERVALS.CONTAINER_STATS);
68
-
69
- return () => {
70
- isMounted = false;
71
- clearInterval(timer);
72
- };
73
- }, [id, state]);
74
-
75
40
  const stateInfo = stateText(state);
76
41
  return (
77
42
  <Box flexDirection="column" marginBottom={1}>
@@ -1,10 +1,10 @@
1
1
  import React from "react";
2
- import { Text, useInput } from "ink";
3
- import { safeCall } from "../helpers/safeCall";
2
+ import { Text } from "ink";
4
3
  import PropTypes from 'prop-types';
5
4
 
6
5
  /**
7
- * Log viewer overlay component. Shows the most recent lines and closes on ESC.
6
+ * Log viewer overlay component. Shows the most recent lines.
7
+ * Purely presentational — keyboard handling is centralized in useControls.
8
8
  *
9
9
  * @param {Object} props
10
10
  * @param {Array<string>} props.logs - Array of log lines
@@ -12,13 +12,7 @@ import PropTypes from 'prop-types';
12
12
  * @param {Object} [props.container] - Optional container metadata (used for title)
13
13
  * @returns {JSX.Element}
14
14
  */
15
- export default function LogViewer({ logs, onExit, container }) {
16
- useInput((input, key) => {
17
- if (key.escape) {
18
- safeCall(onExit);
19
- }
20
- });
21
-
15
+ export default function LogViewer({ logs, onExit: _onExit, container }) {
22
16
  const visibleLogs = logs.slice(-15);
23
17
 
24
18
  return (
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Builds Docker container creation options from raw user inputs.
3
+ *
4
+ * This is a pure function — it has no side effects and calls no services.
5
+ * It mirrors exactly the option-building logic in useControls.js onCreate callback.
6
+ *
7
+ * @param {object} params
8
+ * @param {string} params.imageName - Docker image name (e.g. "nginx:alpine")
9
+ * @param {string} [params.containerName] - Optional container name
10
+ * @param {string} [params.portInput] - Comma-separated host:container port pairs
11
+ * (e.g. "8080:80,3000:3000")
12
+ * @param {string} [params.envInput] - Comma-separated KEY=VALUE pairs
13
+ * (e.g. "NODE_ENV=production,PORT=3000")
14
+ * @returns {object} Docker container options ready to pass to createContainer
15
+ */
16
+ export function buildContainerOptions({ imageName: _imageName, containerName, portInput, envInput }) {
17
+ const env = (envInput || "").split(",").map(s => s.trim()).filter(Boolean);
18
+ const ports = (portInput || "").split(",").map(s => s.trim()).filter(Boolean);
19
+
20
+ const ExposedPorts = {};
21
+ const PortBindings = {};
22
+
23
+ ports.forEach(pair => {
24
+ const [host, cont] = pair.split(":");
25
+ if (!host || !cont) return;
26
+ const key = `${cont}/tcp`;
27
+ ExposedPorts[key] = {};
28
+ PortBindings[key] = PortBindings[key] || [];
29
+ PortBindings[key].push({ HostPort: `${host}` });
30
+ });
31
+
32
+ const options = {
33
+ Tty: true,
34
+ };
35
+
36
+ if (Object.keys(ExposedPorts).length) options.ExposedPorts = ExposedPorts;
37
+ if (Object.keys(PortBindings).length) options.HostConfig = { PortBindings };
38
+ if (env.length) options.Env = env;
39
+ if (containerName) options.name = containerName;
40
+
41
+ return options;
42
+ }
@@ -80,6 +80,20 @@ export function useContainerCreation({ onCreate, onCancel, dbImages = [] }) {
80
80
  safeCall(onCancel);
81
81
  }
82
82
 
83
+ /**
84
+ * Resets all creation state to initial values without triggering onCancel.
85
+ * Used by the command router when the user presses 'c' to open the creation wizard.
86
+ */
87
+ function resetCreation() {
88
+ setStep(0);
89
+ setImageName("");
90
+ setContainerName("");
91
+ setPortInput("");
92
+ setEnvInput("");
93
+ setMessage("Insert the name of the image to create: ");
94
+ setMessageColor("yellow");
95
+ }
96
+
83
97
  return {
84
98
  step,
85
99
  setStep,
@@ -97,5 +111,6 @@ export function useContainerCreation({ onCreate, onCancel, dbImages = [] }) {
97
111
  setMessageColor,
98
112
  nextStep,
99
113
  cancelCreation,
114
+ resetCreation,
100
115
  };
101
116
  }
@@ -0,0 +1,29 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { logger } from '../../helpers/logger.js';
3
+
4
+ export function useDebugLogs() {
5
+ const [showDebugLogs, setShowDebugLogs] = useState(false);
6
+ const [debugLogs, setDebugLogs] = useState([]);
7
+
8
+ useEffect(() => {
9
+ const unsubscribe = logger.subscribe((entry) => {
10
+ setDebugLogs((prev) => {
11
+ const extras = (entry.args || []).map((arg) => {
12
+ if (typeof arg === "string") return arg;
13
+ if (typeof arg === "number" || typeof arg === "boolean") return String(arg);
14
+ try {
15
+ return JSON.stringify(arg);
16
+ } catch (err) {
17
+ return "[unserializable]";
18
+ }
19
+ }).filter(Boolean);
20
+ const line = extras.length ? `${entry.formatted} ${extras.join(" ")}` : entry.formatted;
21
+ const next = [...prev, line];
22
+ return next.slice(-200);
23
+ });
24
+ });
25
+ return unsubscribe;
26
+ }, []);
27
+
28
+ return { showDebugLogs, setShowDebugLogs, debugLogs };
29
+ }
@@ -0,0 +1,25 @@
1
+ import { useState, useCallback } from 'react';
2
+
3
+ export function useContainerSelection(total) {
4
+ const [selected, setSelected] = useState(0);
5
+
6
+ const handleNavigation = useCallback((input, key) => {
7
+ if (total === 0) {
8
+ return false;
9
+ }
10
+
11
+ if (key.upArrow) {
12
+ setSelected((i) => (i === 0 ? total - 1 : i - 1));
13
+ return true;
14
+ }
15
+
16
+ if (key.downArrow) {
17
+ setSelected((i) => (i === total - 1 ? 0 : i + 1));
18
+ return true;
19
+ }
20
+
21
+ return false;
22
+ }, [total]);
23
+
24
+ return { selected, setSelected, handleNavigation };
25
+ }
@@ -0,0 +1,110 @@
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((input) => {
40
+ const container = containers[selected];
41
+
42
+ if (input === "i") {
43
+ actions.handleAction({
44
+ actionFn: async (id) => await actions.startContainer(id),
45
+ actionLabel: "Starting",
46
+ selected,
47
+ stateCheck: (c) =>
48
+ (c.state === "running" || c.status === "running") &&
49
+ "Container is already running.",
50
+ });
51
+ return true;
52
+ }
53
+
54
+ if (input === "p") {
55
+ actions.handleAction({
56
+ actionFn: async (id) => await actions.stopContainer(id),
57
+ actionLabel: "Stopping",
58
+ selected,
59
+ stateCheck: (c) =>
60
+ (c.state === "exited" ||
61
+ c.status === "exited" ||
62
+ c.state === "stopped" ||
63
+ c.status === "stopped") &&
64
+ "Container is already stopped.",
65
+ });
66
+ return true;
67
+ }
68
+
69
+ if (input === "r") {
70
+ actions.handleAction({
71
+ actionFn: async (id) => await actions.restartContainer(id),
72
+ actionLabel: "Restarting",
73
+ selected,
74
+ });
75
+ return true;
76
+ }
77
+
78
+ if (input === "e") {
79
+ if (!container) {
80
+ return true;
81
+ }
82
+ onStartErase();
83
+ return true;
84
+ }
85
+
86
+ if (input === "l") {
87
+ if (!container) {
88
+ return true;
89
+ }
90
+ logsViewer.openLogs();
91
+ startLogsStream(container.id);
92
+ return true;
93
+ }
94
+
95
+ if (input === "c") {
96
+ onStartCreate();
97
+ creation.resetCreation();
98
+ return true;
99
+ }
100
+
101
+ if (input === "d") {
102
+ onToggleDebug();
103
+ return true;
104
+ }
105
+
106
+ return false;
107
+ }, [actions, containers, creation, logsViewer, onStartCreate, onStartErase, onToggleDebug, selected, startLogsStream]);
108
+
109
+ return { handleDockerCommands };
110
+ }
@@ -0,0 +1,44 @@
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
+ }