cdd-cli 3.1.4 → 3.1.5

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 (136) hide show
  1. package/.eslintrc.json +30 -0
  2. package/.github/workflows/docs.yml +35 -0
  3. package/CHANGELOG.md +37 -4
  4. package/README.md +26 -0
  5. package/dist/App.js +20 -3
  6. package/dist/components/ContainerCreationPrompt.js +27 -1
  7. package/dist/components/ContainerList.js +11 -15
  8. package/dist/components/ContainerRow.js +73 -14
  9. package/dist/components/ContainerSection.js +16 -2
  10. package/dist/components/Footer.js +7 -1
  11. package/dist/components/Header.js +17 -1
  12. package/dist/components/LogViewer.js +21 -3
  13. package/dist/components/MessageFeedback.js +15 -1
  14. package/dist/components/PromptField.js +31 -1
  15. package/dist/components/StatsBar.js +46 -8
  16. package/dist/components/UsageMenu.js +30 -1
  17. package/dist/helpers/actionHelpers.js +17 -6
  18. package/dist/helpers/constants.js +30 -0
  19. package/dist/helpers/dockerService/dockerService.js +19 -0
  20. package/dist/helpers/dockerService/serviceComponents/containerActions.js +194 -35
  21. package/dist/helpers/dockerService/serviceComponents/containerList.js +45 -8
  22. package/dist/helpers/dockerService/serviceComponents/containerLogs.js +16 -6
  23. package/dist/helpers/dockerService/serviceComponents/containerStats.js +28 -9
  24. package/dist/helpers/exitWithMessage.js +2 -1
  25. package/dist/helpers/logger.js +112 -0
  26. package/dist/helpers/safeCall.js +21 -0
  27. package/dist/helpers/validationHelpers.js +20 -1
  28. package/dist/hooks/creation/useContainerActions.js +2 -1
  29. package/dist/hooks/creation/useContainerCreation.js +5 -4
  30. package/dist/hooks/creation/useLogsViewer.js +3 -2
  31. package/dist/hooks/useContainers.js +6 -3
  32. package/dist/hooks/useControls.js +253 -122
  33. package/dist/hooks/useLogsStream.js +3 -2
  34. package/dist/index.js +1 -0
  35. package/docs/components_ContainerCreationPrompt.jsx.html +114 -0
  36. package/docs/components_ContainerRow.jsx.html +151 -0
  37. package/docs/components_ContainerSection.jsx.html +75 -0
  38. package/docs/components_Footer.jsx.html +66 -0
  39. package/docs/components_Header.jsx.html +84 -0
  40. package/docs/components_LogViewer.jsx.html +91 -0
  41. package/docs/components_MessageFeedback.jsx.html +76 -0
  42. package/docs/components_PromptField.jsx.html +99 -0
  43. package/docs/components_StatsBar.jsx.html +105 -0
  44. package/docs/components_UsageMenu.jsx.html +73 -0
  45. package/docs/fonts/OpenSans-Bold-webfont.eot +0 -0
  46. package/docs/fonts/OpenSans-Bold-webfont.svg +1830 -0
  47. package/docs/fonts/OpenSans-Bold-webfont.woff +0 -0
  48. package/docs/fonts/OpenSans-BoldItalic-webfont.eot +0 -0
  49. package/docs/fonts/OpenSans-BoldItalic-webfont.svg +1830 -0
  50. package/docs/fonts/OpenSans-BoldItalic-webfont.woff +0 -0
  51. package/docs/fonts/OpenSans-Italic-webfont.eot +0 -0
  52. package/docs/fonts/OpenSans-Italic-webfont.svg +1830 -0
  53. package/docs/fonts/OpenSans-Italic-webfont.woff +0 -0
  54. package/docs/fonts/OpenSans-Light-webfont.eot +0 -0
  55. package/docs/fonts/OpenSans-Light-webfont.svg +1831 -0
  56. package/docs/fonts/OpenSans-Light-webfont.woff +0 -0
  57. package/docs/fonts/OpenSans-LightItalic-webfont.eot +0 -0
  58. package/docs/fonts/OpenSans-LightItalic-webfont.svg +1835 -0
  59. package/docs/fonts/OpenSans-LightItalic-webfont.woff +0 -0
  60. package/docs/fonts/OpenSans-Regular-webfont.eot +0 -0
  61. package/docs/fonts/OpenSans-Regular-webfont.svg +1831 -0
  62. package/docs/fonts/OpenSans-Regular-webfont.woff +0 -0
  63. package/docs/global.html +6145 -0
  64. package/docs/helpers_actionHelpers.js.html +94 -0
  65. package/docs/helpers_constants.js.html +81 -0
  66. package/docs/helpers_dockerService_dockerService.js.html +79 -0
  67. package/docs/helpers_dockerService_serviceComponents_containerActions.js.html +153 -0
  68. package/docs/helpers_dockerService_serviceComponents_containerList.js.html +81 -0
  69. package/docs/helpers_dockerService_serviceComponents_containerLogs.js.html +85 -0
  70. package/docs/helpers_dockerService_serviceComponents_containerStats.js.html +107 -0
  71. package/docs/helpers_dockerService_serviceComponents_imageUtils.js.html +82 -0
  72. package/docs/helpers_exitWithMessage.js.html +77 -0
  73. package/docs/helpers_safeCall.js.html +68 -0
  74. package/docs/helpers_validationHelpers.js.html +96 -0
  75. package/docs/hooks_creation_useContainerActions.js.html +120 -0
  76. package/docs/hooks_creation_useContainerCreation.js.html +152 -0
  77. package/docs/hooks_creation_useLogsViewer.js.html +102 -0
  78. package/docs/hooks_useControls.js.html +298 -0
  79. package/docs/hooks_useLogsStream.js.html +82 -0
  80. package/docs/index.html +65 -0
  81. package/docs/index.js.html +65 -0
  82. package/docs/module-index.html +171 -0
  83. package/docs/scripts/linenumber.js +25 -0
  84. package/docs/scripts/prettify/Apache-License-2.0.txt +202 -0
  85. package/docs/scripts/prettify/lang-css.js +2 -0
  86. package/docs/scripts/prettify/prettify.js +28 -0
  87. package/docs/styles/jsdoc-default.css +358 -0
  88. package/docs/styles/prettify-jsdoc.css +111 -0
  89. package/docs/styles/prettify-tomorrow.css +132 -0
  90. package/fix-imports.cjs +33 -6
  91. package/jest.config.cjs +2 -1
  92. package/package.json +19 -9
  93. package/src/App.jsx +16 -2
  94. package/src/components/ContainerCreationPrompt.jsx +26 -0
  95. package/src/components/ContainerList.jsx +12 -13
  96. package/src/components/ContainerRow.jsx +55 -20
  97. package/src/components/ContainerSection.jsx +15 -1
  98. package/src/components/Footer.jsx +6 -1
  99. package/src/components/Header.jsx +16 -0
  100. package/src/components/LogViewer.jsx +19 -2
  101. package/src/components/MessageFeedback.jsx +14 -0
  102. package/src/components/PromptField.jsx +30 -0
  103. package/src/components/StatsBar.jsx +47 -8
  104. package/src/components/UsageMenu.jsx +38 -8
  105. package/src/helpers/actionHelpers.js +16 -7
  106. package/src/helpers/constants.js +30 -0
  107. package/src/helpers/dockerService/dockerService.js +19 -0
  108. package/src/helpers/dockerService/serviceComponents/containerActions.js +123 -12
  109. package/src/helpers/dockerService/serviceComponents/containerList.js +61 -23
  110. package/src/helpers/dockerService/serviceComponents/containerLogs.js +20 -6
  111. package/src/helpers/dockerService/serviceComponents/containerStats.js +24 -5
  112. package/src/helpers/dockerService/serviceComponents/imageUtils.js +1 -1
  113. package/src/helpers/exitWithMessage.js +2 -1
  114. package/src/helpers/logger.js +113 -0
  115. package/src/helpers/safeCall.js +18 -0
  116. package/src/helpers/validationHelpers.js +21 -3
  117. package/src/hooks/creation/useContainerActions.js +4 -3
  118. package/src/hooks/creation/useContainerCreation.js +5 -4
  119. package/src/hooks/creation/useLogsViewer.js +2 -1
  120. package/src/hooks/useContainers.js +6 -3
  121. package/src/hooks/useControls.js +244 -91
  122. package/src/hooks/useLogsStream.js +2 -1
  123. package/src/index.js +1 -0
  124. package/test/actionHelpers.test.js +106 -0
  125. package/test/containerActions.test.js +138 -0
  126. package/test/containerList.test.js +79 -0
  127. package/test/jest.setup.js +7 -0
  128. package/test/safeCall.test.js +25 -0
  129. package/test/useContainerCreation.dom.test.js +97 -0
  130. package/test/validationHelpers.test.js +2 -2
  131. package/dist/cdd.bundle.js +0 -8
  132. package/dist/components/ContainerCreator.js +0 -81
  133. package/dist/components/StatsViewer.js +0 -0
  134. package/dist/helpers/dockerActions.js +0 -64
  135. package/dist/helpers/dockerService.js +0 -284
  136. package/dist/hooks/useStatsPolling.js +0 -54
package/.eslintrc.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "env": {
3
+ "browser": true,
4
+ "node": true,
5
+ "es2021": true,
6
+ "jest": true
7
+ },
8
+ "extends": ["eslint:recommended", "plugin:react/recommended"],
9
+ "parserOptions": {
10
+ "ecmaVersion": 2021,
11
+ "sourceType": "module",
12
+ "ecmaFeatures": { "jsx": true }
13
+ },
14
+ "rules": {
15
+ "no-unsafe-optional-chaining": "error",
16
+ "react/react-in-jsx-scope": "off",
17
+ "no-console": "off",
18
+ "react/prop-types": "off",
19
+ "no-unused-vars": [
20
+ "warn",
21
+ {
22
+ "argsIgnorePattern": "^_",
23
+ "varsIgnorePattern": "^(?:_|React)$"
24
+ }
25
+ ]
26
+ },
27
+ "settings": {
28
+ "react": { "version": "detect" }
29
+ }
30
+ }
@@ -0,0 +1,35 @@
1
+ name: Build and deploy docs
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+
8
+ jobs:
9
+ build-and-deploy:
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ contents: write
13
+ pages: write
14
+ id-token: write
15
+
16
+ steps:
17
+ - name: Checkout repository
18
+ uses: actions/checkout@v4
19
+
20
+ - name: Setup Node.js
21
+ uses: actions/setup-node@v4
22
+ with:
23
+ node-version: '18'
24
+
25
+ - name: Install dependencies
26
+ run: npm ci
27
+
28
+ - name: Build docs
29
+ run: npm run docs
30
+
31
+ - name: Deploy to GitHub Pages (push to gh-pages)
32
+ uses: peaceiris/actions-gh-pages@v3
33
+ with:
34
+ github_token: ${{ secrets.GITHUB_TOKEN }}
35
+ publish_dir: ./docs
package/CHANGELOG.md CHANGED
@@ -4,6 +4,43 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
6
6
 
7
+ ## [Unreleased]
8
+ - Nothing yet.
9
+
10
+ ## [3.1.5] - 2025-10-18
11
+ _Note: I'm sorry about the issues you've run into; this is a brand-new app and I'm still learning, but I'm moving as quickly as I can to deliver a quality experience._
12
+
13
+ ### Added
14
+
15
+ - `safeCall` utility to safely invoke optional callbacks and avoid uncaught errors from consumer callbacks.
16
+ - PropTypes added to core React components for runtime prop validation (ContainerRow, ContainerList, LogViewer, StatsBar, ContainerCreationPrompt, PromptField, MessageFeedback, Header, ContainerSection).
17
+
18
+ ### Changed
19
+
20
+ - Container creation now inspects image `EXPOSE` declarations and auto-binds ports when the user leaves the ports step empty, reusing the next free host port to avoid conflicts (e.g. second `nginx` gets `81:80`).
21
+ - CLI clears the terminal when starting and again after exiting with `q` for a cleaner shell experience.
22
+ - Replaced direct optional callback invocations and stream destruction calls with `safeCall` in several modules (`containerLogs`, `useLogsStream`, `useLogsViewer`, `useContainerCreation`, `useContainerActions`, `LogViewer`).
23
+ - ESLint config updated to include `jest` env and allow keeping `import React` for build compatibility.
24
+ - Extracted common magic numbers into `src/helpers/constants.js` and replaced hardcoded values across the codebase:
25
+ - `REFRESH_INTERVALS.CONTAINER_LIST` (3000)
26
+ - `REFRESH_INTERVALS.CONTAINER_STATS` (1500)
27
+ - `MESSAGE_TIMEOUTS.SHORT` (2000) and `MESSAGE_TIMEOUTS.DEFAULT` (3000)
28
+ - `EXIT_DELAY` (500)
29
+ - `TIMEOUTS.CONTAINER_OP` (30000) and `TIMEOUTS.PULL_IMAGE` (300000)
30
+ - Files updated to use constants: `useContainers`, `ContainerRow`, `actionHelpers`, `useControls`, `exitWithMessage`, `containerActions`.
31
+
32
+ ### Fixed
33
+
34
+ - Added unit tests for `safeCall` and adjusted several components to silence lint warnings (unused variables).
35
+ - Clamp `StatsBar` CPU/memory percentages to avoid negative repeat errors when streaming container stats (also refreshed component JSDoc to note the clamping).
36
+
37
+ ### Testing
38
+
39
+ - Added additional unit tests for Docker service functions and hooks. All tests pass locally (5 suites, 26 tests).
40
+ - Removed generated `dist/` artifacts and deleted obsolete test files that referenced incompatible test helpers.
41
+
42
+ Incoming fixes and smaller tests / docs updates
43
+
7
44
  ## [3.1.4] - 2025-10-17
8
45
 
9
46
  ### Changed
@@ -51,10 +88,6 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
51
88
 
52
89
  - Various runtime & build issues after modularization (import fixes, exposing container action functions, creation flow wiring).
53
90
 
54
- ## [Unreleased]
55
-
56
- - Incoming fixes and smaller tests / docs updates
57
-
58
91
  ## [3.1.1] - 2025-10-16
59
92
 
60
93
  ### Fixed
package/README.md CHANGED
@@ -6,6 +6,10 @@
6
6
  <a href="https://github.com/caertos/cdd/actions/workflows/ci.yml">
7
7
  <img src="https://github.com/caertos/cdd/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI status" />
8
8
  </a>
9
+ <!-- Documentation published to GitHub Pages -->
10
+ <a href="https://caertos.github.io/cdd/">
11
+ <img src="https://img.shields.io/badge/docs-GitHub%20Pages-blue" alt="Docs" />
12
+ </a>
9
13
  </p>
10
14
 
11
15
  Short, bilingual README with quickstart, development and tests.
@@ -46,6 +50,7 @@ cdd
46
50
  - R: restart selected container
47
51
  - C: create container (interactive prompt)
48
52
  - L: view logs for selected container
53
+ - D: toggle the on-screen debug log panel
49
54
  - E: erase (remove) selected container (confirmation required)
50
55
  - Q: quit
51
56
 
@@ -113,6 +118,27 @@ Tests are located in `test/` and cover utility helpers.
113
118
 
114
119
  ---
115
120
 
121
+ ## Logging
122
+
123
+ - The CLI logs high-level `info`, `warn`, and `error` messages by default so you see meaningful feedback without noise.
124
+ - When you need deeper diagnostics (for example, to troubleshoot why stats or logs are not updating), start the CLI with debug logging enabled:
125
+
126
+ ```bash
127
+ CDD_LOG_LEVEL=debug cdd # if you installed globally
128
+ # or
129
+ CDD_LOG_LEVEL=debug node dist/index.js
130
+ ```
131
+
132
+ - The same effect can be achieved with `LOG_LEVEL=debug`. Set `LOG_LEVEL=warn` if you only want to see warnings and errors.
133
+ - Inside the CLI, press `D` to toggle the live debug log panel; press `D` again or `Esc` to hide it.
134
+ - Redirect output to a file when users report issues so they can share the log easily:
135
+
136
+ ```bash
137
+ CDD_LOG_LEVEL=debug cdd > cdd-debug.log 2>&1
138
+ ```
139
+
140
+ ---
141
+
116
142
  ## License
117
143
 
118
144
  This project is MIT/ISC licensed (see `LICENSE`).
package/dist/App.js CHANGED
@@ -1,10 +1,12 @@
1
1
  /**
2
2
  * Main React component for the CDD CLI UI.
3
+ * Handles container listing, action feedback, optional creation prompt,
4
+ * and renders the live debug log panel (toggle with the D shortcut).
3
5
  *
4
6
  * @component
5
- * @returns {JSX.Element} The rendered app / La app renderizada
7
+ * @returns {JSX.Element} The rendered app
6
8
  * @example
7
- * // EN: Render the app
9
+ * // Render the app
8
10
  * <App />
9
11
  */
10
12
  import React from "react";
@@ -46,7 +48,22 @@ export default function App() {
46
48
  }), /*#__PURE__*/React.createElement(Spacer, null), /*#__PURE__*/React.createElement(MessageFeedback, {
47
49
  message: controls.message,
48
50
  color: controls.messageColor
49
- }), /*#__PURE__*/React.createElement(UsageMenu, null), /*#__PURE__*/React.createElement(Footer, null)), controls.showLogs && /*#__PURE__*/React.createElement(LogViewer, {
51
+ }), /*#__PURE__*/React.createElement(UsageMenu, null), controls.showDebugLogs && /*#__PURE__*/React.createElement(Box, {
52
+ marginTop: 1,
53
+ flexDirection: "column",
54
+ borderStyle: "round",
55
+ borderColor: "gray",
56
+ padding: 1
57
+ }, /*#__PURE__*/React.createElement(Text, {
58
+ color: "cyan"
59
+ }, "Debug log \u2014 press D or ESC to close"), controls.debugLogs.length === 0 ? /*#__PURE__*/React.createElement(Text, {
60
+ dimColor: true
61
+ }, "No debug entries yet. Run with CDD_LOG_LEVEL=debug for verbose output.") : controls.debugLogs.slice(-15).map(function (line, idx) {
62
+ return /*#__PURE__*/React.createElement(Text, {
63
+ key: idx,
64
+ color: "gray"
65
+ }, line);
66
+ })), /*#__PURE__*/React.createElement(Footer, null)), controls.showLogs && /*#__PURE__*/React.createElement(LogViewer, {
50
67
  logs: controls.logs,
51
68
  onExit: controls.exitLogs,
52
69
  container: containers[controls.selected]
@@ -1,6 +1,23 @@
1
1
  import React from "react";
2
2
  import { Box, Text } from "ink";
3
3
  import { PromptField, PromptMessage } from "./PromptField.js";
4
+ import PropTypes from 'prop-types';
5
+
6
+ /**
7
+ * Prompt UI shown when creating a new container.
8
+ * Presents a step-by-step form (image, name, ports, env) and displays
9
+ * a contextual message provided by the creation hook.
10
+ *
11
+ * @param {Object} props
12
+ * @param {number} props.step - Current step index (0..3)
13
+ * @param {string} props.imageName - Value for the image name field
14
+ * @param {string} props.containerName - Value for the container name field
15
+ * @param {string} props.portInput - Value for the ports input field
16
+ * @param {string} props.envInput - Value for the environment variables field
17
+ * @param {string} props.message - Contextual message to display (errors/hints)
18
+ * @param {string} props.messageColor - Color for the contextual message
19
+ * @returns {JSX.Element}
20
+ */
4
21
  export default function ContainerCreationPrompt(props) {
5
22
  var step = props.step,
6
23
  imageName = props.imageName,
@@ -45,4 +62,13 @@ export default function ContainerCreationPrompt(props) {
45
62
  }), /*#__PURE__*/React.createElement(Text, {
46
63
  dimColor: true
47
64
  }, "Press Enter to continue, Escape to cancel"));
48
- }
65
+ }
66
+ ContainerCreationPrompt.propTypes = {
67
+ step: PropTypes.number.isRequired,
68
+ imageName: PropTypes.string,
69
+ containerName: PropTypes.string,
70
+ portInput: PropTypes.string,
71
+ envInput: PropTypes.string,
72
+ message: PropTypes.string,
73
+ messageColor: PropTypes.string
74
+ };
@@ -2,15 +2,16 @@
2
2
  * List component for Docker containers.
3
3
  *
4
4
  * @component
5
- * @param {Object} props - Component props / Props del componente
6
- * @param {Array} props.containers - Containers to display / Contenedores a mostrar
7
- * @returns {JSX.Element} Rendered list / Lista renderizada
5
+ * @param {Object} props - Component props
6
+ * @param {Array} props.containers - Containers to display
7
+ * @returns {JSX.Element} Rendered list
8
8
  * @example
9
9
  * // Render with containers
10
10
  * <ContainerList containers={containers} />
11
11
  */
12
12
  import React from "react";
13
- import { Box, Text } from "ink";
13
+ import PropTypes from 'prop-types';
14
+ import { Box } from "ink";
14
15
  import ContainerRow from "./ContainerRow.js";
15
16
  export default function ContainerList(_ref) {
16
17
  var containers = _ref.containers,
@@ -19,19 +20,14 @@ export default function ContainerList(_ref) {
19
20
  return /*#__PURE__*/React.createElement(Box, {
20
21
  key: container.id,
21
22
  flexDirection: "row",
22
- alignItems: "flex-start"
23
- }, /*#__PURE__*/React.createElement(Box, {
24
- width: 2,
25
- minWidth: 2,
26
- justifyContent: "flex-end"
27
- }, /*#__PURE__*/React.createElement(Text, {
28
- color: i === selected ? "green" : undefined
29
- }, i === selected ? "➤" : " ")), /*#__PURE__*/React.createElement(Box, {
30
- flexGrow: 1,
31
23
  paddingLeft: 1
32
24
  }, /*#__PURE__*/React.createElement(ContainerRow, {
33
25
  container: container,
34
26
  isSelected: i === selected
35
- })));
27
+ }));
36
28
  }));
37
- }
29
+ }
30
+ ContainerList.propTypes = {
31
+ containers: PropTypes.arrayOf(PropTypes.object).isRequired,
32
+ selected: PropTypes.number
33
+ };
@@ -11,7 +11,9 @@ function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
11
11
  import React, { useState, useEffect } from "react";
12
12
  import { Box, Text } from "ink";
13
13
  import { getStats } from "../helpers/dockerService/serviceComponents/containerStats.js";
14
+ import { REFRESH_INTERVALS } from "../helpers/constants.js";
14
15
  import StatsBar from "./StatsBar.js";
16
+ import PropTypes from 'prop-types';
15
17
  var stateText = function stateText(state) {
16
18
  if (state === "running") return {
17
19
  text: "🟢 RUNNING",
@@ -30,8 +32,18 @@ var stateText = function stateText(state) {
30
32
  color: "gray"
31
33
  };
32
34
  };
35
+
36
+ /**
37
+ * Row component that renders information and live stats for a container.
38
+ *
39
+ * @param {Object} props
40
+ * @param {Object} props.container - Container object with id, name, image, state and ports
41
+ * @returns {JSX.Element}
42
+ */
33
43
  export default function ContainerRow(_ref) {
34
- var container = _ref.container;
44
+ var container = _ref.container,
45
+ _ref$isSelected = _ref.isSelected,
46
+ isSelected = _ref$isSelected === void 0 ? false : _ref$isSelected;
35
47
  var id = container.id,
36
48
  name = container.name,
37
49
  image = container.image,
@@ -48,15 +60,22 @@ export default function ContainerRow(_ref) {
48
60
  stats = _useState2[0],
49
61
  setStats = _useState2[1];
50
62
 
51
- // Format ports for display
63
+ // Format ports for display (no leading space to avoid layout shifts)
52
64
  var formatPorts = function formatPorts(ports) {
53
65
  if (!ports || ports.length === 0) return "";
54
66
  if (Array.isArray(ports)) {
55
- return ports.map(function (p, i) {
56
- return " \uD83D\uDD17 ".concat(p);
67
+ return ports.map(function (p, _i) {
68
+ return "\uD83D\uDD17 ".concat(p);
57
69
  }).join(" ");
58
70
  }
59
- return " \uD83D\uDD17 ".concat(ports);
71
+ return "\uD83D\uDD17 ".concat(ports);
72
+ };
73
+
74
+ // Helper to truncate long strings to a max length without adding trailing spaces
75
+ var truncate = function truncate(s) {
76
+ var max = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 20;
77
+ if (!s) return "";
78
+ return s.length > max ? s.slice(0, max - 1) + "…" : s;
60
79
  };
61
80
  var _useState3 = useState(""),
62
81
  _useState4 = _slicedToArray(_useState3, 2),
@@ -106,7 +125,7 @@ export default function ContainerRow(_ref) {
106
125
  };
107
126
  }();
108
127
  fetchStats();
109
- var timer = setInterval(fetchStats, 1500);
128
+ var timer = setInterval(fetchStats, REFRESH_INTERVALS.CONTAINER_STATS);
110
129
  return function () {
111
130
  isMounted = false;
112
131
  clearInterval(timer);
@@ -116,18 +135,58 @@ export default function ContainerRow(_ref) {
116
135
  return /*#__PURE__*/React.createElement(Box, {
117
136
  flexDirection: "column",
118
137
  marginBottom: 1
119
- }, /*#__PURE__*/React.createElement(Box, null, /*#__PURE__*/React.createElement(Text, {
138
+ }, /*#__PURE__*/React.createElement(Box, {
139
+ flexDirection: "row",
140
+ alignItems: "center"
141
+ }, /*#__PURE__*/React.createElement(Box, {
142
+ width: 2,
143
+ minWidth: 2,
144
+ justifyContent: "flex-end",
145
+ paddingRight: 1
146
+ }, /*#__PURE__*/React.createElement(Text, {
147
+ color: isSelected ? "green" : undefined
148
+ }, isSelected ? "➤" : " ")), /*#__PURE__*/React.createElement(Box, {
149
+ width: 18,
150
+ flexShrink: 1,
151
+ paddingRight: 1
152
+ }, /*#__PURE__*/React.createElement(Text, {
120
153
  color: "cyan"
121
- }, name.padEnd(20)), /*#__PURE__*/React.createElement(Text, {
154
+ }, truncate(name, 18))), /*#__PURE__*/React.createElement(Box, {
155
+ width: 18,
156
+ flexShrink: 1,
157
+ paddingRight: 1
158
+ }, /*#__PURE__*/React.createElement(Text, {
122
159
  color: "gray"
123
- }, image.padEnd(20)), /*#__PURE__*/React.createElement(Text, {
160
+ }, truncate(image, 18))), /*#__PURE__*/React.createElement(Box, {
161
+ width: 14,
162
+ minWidth: 12,
163
+ paddingRight: 1
164
+ }, /*#__PURE__*/React.createElement(Text, {
124
165
  color: stateInfo.color
125
- }, stateInfo.text), /*#__PURE__*/React.createElement(Text, {
166
+ }, stateInfo.text)), /*#__PURE__*/React.createElement(Box, {
167
+ flexGrow: 1,
168
+ flexShrink: 1,
169
+ paddingLeft: 0,
170
+ paddingRight: 1
171
+ }, /*#__PURE__*/React.createElement(Text, {
126
172
  color: "yellow"
127
- }, formatPorts(container.ports)), state === "running" && /*#__PURE__*/React.createElement(Text, null, " "), state === "running" && /*#__PURE__*/React.createElement(StatsBar, {
173
+ }, formatPorts(container.ports))), /*#__PURE__*/React.createElement(Box, {
174
+ flexShrink: 0,
175
+ paddingLeft: 1
176
+ }, state === "running" ? /*#__PURE__*/React.createElement(StatsBar, {
128
177
  cpu: parseFloat(stats.cpuPercent),
129
178
  mem: parseFloat(stats.memPercent)
130
- }), state === "running" && statsError && /*#__PURE__*/React.createElement(Text, {
179
+ }) : null)), statsError && /*#__PURE__*/React.createElement(Text, {
131
180
  color: "red"
132
- }, statsError)));
133
- }
181
+ }, statsError));
182
+ }
183
+ ContainerRow.propTypes = {
184
+ container: PropTypes.shape({
185
+ id: PropTypes.string.isRequired,
186
+ name: PropTypes.string.isRequired,
187
+ image: PropTypes.string,
188
+ state: PropTypes.string,
189
+ ports: PropTypes.oneOfType([PropTypes.array, PropTypes.string])
190
+ }).isRequired,
191
+ isSelected: PropTypes.bool
192
+ };
@@ -1,14 +1,28 @@
1
1
  import React from "react";
2
2
  import { Text } from "ink";
3
3
  import ContainerList from "./ContainerList.js";
4
+ import PropTypes from 'prop-types';
5
+
6
+ /**
7
+ * Section that displays the list of containers or a message when none exist.
8
+ *
9
+ * @param {Object} props
10
+ * @param {Array<Object>} props.containers - Array of container objects to display
11
+ * @param {number} [props.selected] - Index of the currently selected container
12
+ * @returns {JSX.Element}
13
+ */
4
14
  export default function ContainerSection(_ref) {
5
15
  var containers = _ref.containers,
6
16
  selected = _ref.selected;
7
- if (containers.length === 0) {
17
+ if (!containers || containers.length === 0) {
8
18
  return /*#__PURE__*/React.createElement(Text, null, "No containers found");
9
19
  }
10
20
  return /*#__PURE__*/React.createElement(ContainerList, {
11
21
  containers: containers,
12
22
  selected: selected
13
23
  });
14
- }
24
+ }
25
+ ContainerSection.propTypes = {
26
+ containers: PropTypes.array.isRequired,
27
+ selected: PropTypes.number
28
+ };
@@ -1,10 +1,16 @@
1
1
  import React from "react";
2
2
  import { Box, Text } from "ink";
3
+
4
+ /**
5
+ * Simple footer displayed at the bottom of the interface.
6
+ *
7
+ * @returns {JSX.Element}
8
+ */
3
9
  export default function Footer() {
4
10
  return /*#__PURE__*/React.createElement(Box, {
5
11
  justifyContent: "flex-end",
6
12
  width: "100%"
7
13
  }, /*#__PURE__*/React.createElement(Text, {
8
14
  dimColor: true
9
- }, "Crafted by Carlos Cochero \u2022 2025"));
15
+ }, "Crafted by Carlos Cochero 2025"));
10
16
  }
@@ -1,6 +1,19 @@
1
1
  import React from "react";
2
2
  import { Box, Text } from "ink";
3
3
  import chalk from "chalk";
4
+ import PropTypes from 'prop-types';
5
+
6
+ /**
7
+ * Header component displayed at the top of the CLI UI.
8
+ *
9
+ * @component
10
+ * @param {Object} props
11
+ * @param {number} props.count - Number of containers currently detected
12
+ * @returns {JSX.Element}
13
+ * @example
14
+ * <Header count={3} />
15
+ */
16
+
4
17
  export default function Header(_ref) {
5
18
  var count = _ref.count;
6
19
  return /*#__PURE__*/React.createElement(Box, {
@@ -12,4 +25,7 @@ export default function Header(_ref) {
12
25
  }, " \u2014 CLI Docker Dashboard")), /*#__PURE__*/React.createElement(Text, {
13
26
  color: "gray"
14
27
  }, count, " container", count === 1 ? "" : "s", " found"));
15
- }
28
+ }
29
+ Header.propTypes = {
30
+ count: PropTypes.number.isRequired
31
+ };
@@ -1,22 +1,40 @@
1
1
  import React from "react";
2
2
  import { Text, useInput } from "ink";
3
+ import { safeCall } from "../helpers/safeCall.js";
4
+ import PropTypes from 'prop-types';
5
+
6
+ /**
7
+ * Log viewer overlay component. Shows the most recent lines and closes on ESC.
8
+ *
9
+ * @param {Object} props
10
+ * @param {Array<string>} props.logs - Array of log lines
11
+ * @param {Function} [props.onExit] - Optional callback when the viewer closes
12
+ * @param {Object} [props.container] - Optional container metadata (used for title)
13
+ * @returns {JSX.Element}
14
+ */
3
15
  export default function LogViewer(_ref) {
16
+ var _container$name;
4
17
  var logs = _ref.logs,
5
18
  onExit = _ref.onExit,
6
19
  container = _ref.container;
7
20
  useInput(function (input, key) {
8
21
  if (key.escape) {
9
- onExit();
22
+ safeCall(onExit);
10
23
  }
11
24
  });
12
25
  var visibleLogs = logs.slice(-15);
13
26
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Text, {
14
27
  color: "green"
15
- }, (container === null || container === void 0 ? void 0 : container.name) || "Container", " logs, press ESC to exit"), visibleLogs.length === 0 ? /*#__PURE__*/React.createElement(Text, {
28
+ }, (_container$name = container === null || container === void 0 ? void 0 : container.name) !== null && _container$name !== void 0 ? _container$name : "Container", " logs, press ESC to exit"), visibleLogs.length === 0 ? /*#__PURE__*/React.createElement(Text, {
16
29
  dimColor: true
17
30
  }, "No logs...") : visibleLogs.map(function (line, idx) {
18
31
  return /*#__PURE__*/React.createElement(Text, {
19
32
  key: idx
20
33
  }, line);
21
34
  }));
22
- }
35
+ }
36
+ LogViewer.propTypes = {
37
+ logs: PropTypes.arrayOf(PropTypes.string).isRequired,
38
+ onExit: PropTypes.func,
39
+ container: PropTypes.object
40
+ };
@@ -1,5 +1,15 @@
1
1
  import React from "react";
2
2
  import { Box, Text } from "ink";
3
+ import PropTypes from 'prop-types';
4
+
5
+ /**
6
+ * Small component that shows a feedback message in the UI.
7
+ *
8
+ * @param {Object} props
9
+ * @param {string} props.message - Message to display. If falsy, component returns null.
10
+ * @param {string} [props.color] - Optional color name for the message text.
11
+ * @returns {JSX.Element|null}
12
+ */
3
13
  export default function MessageFeedback(_ref) {
4
14
  var message = _ref.message,
5
15
  color = _ref.color;
@@ -9,4 +19,8 @@ export default function MessageFeedback(_ref) {
9
19
  }, /*#__PURE__*/React.createElement(Text, {
10
20
  color: color
11
21
  }, message));
12
- }
22
+ }
23
+ MessageFeedback.propTypes = {
24
+ message: PropTypes.string,
25
+ color: PropTypes.string
26
+ };
@@ -1,5 +1,17 @@
1
1
  import React from "react";
2
2
  import { Text } from "ink";
3
+ import PropTypes from 'prop-types';
4
+
5
+ /**
6
+ * PromptField shows a label and the current typed value for an input field.
7
+ * The cursor is represented with a trailing underscore.
8
+ *
9
+ * @param {Object} props
10
+ * @param {string} props.label - Label shown above the input value
11
+ * @param {string} props.value - Current value of the input
12
+ * @param {boolean} [props.required=false] - Whether the field is required
13
+ * @returns {JSX.Element}
14
+ */
3
15
  export function PromptField(_ref) {
4
16
  var label = _ref.label,
5
17
  value = _ref.value,
@@ -10,6 +22,15 @@ export function PromptField(_ref) {
10
22
  color: isEmpty ? "red" : "cyan"
11
23
  }, value, "_"));
12
24
  }
25
+
26
+ /**
27
+ * Small helper to show a message below prompts (errors, hints, etc.).
28
+ *
29
+ * @param {Object} props
30
+ * @param {string} props.message - Message text to display
31
+ * @param {string} [props.color] - Optional color for the message
32
+ * @returns {JSX.Element|null}
33
+ */
13
34
  export function PromptMessage(_ref2) {
14
35
  var message = _ref2.message,
15
36
  color = _ref2.color;
@@ -17,4 +38,13 @@ export function PromptMessage(_ref2) {
17
38
  return /*#__PURE__*/React.createElement(Text, {
18
39
  color: color || "yellow"
19
40
  }, message);
20
- }
41
+ }
42
+ PromptField.propTypes = {
43
+ label: PropTypes.string,
44
+ value: PropTypes.string,
45
+ required: PropTypes.bool
46
+ };
47
+ PromptMessage.propTypes = {
48
+ message: PropTypes.string,
49
+ color: PropTypes.string
50
+ };