cdd-cli 3.2.0 → 3.2.2

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 (120) hide show
  1. package/.atl/skill-registry.md +30 -0
  2. package/.editorconfig +12 -0
  3. package/.eslintrc.json +1 -1
  4. package/.prettierignore +4 -0
  5. package/.prettierrc +8 -0
  6. package/CHANGELOG.md +29 -0
  7. package/SECURITY.md +5 -0
  8. package/__mocks__/ink.cjs +17 -0
  9. package/coverage/clover.xml +526 -436
  10. package/coverage/coverage-final.json +19 -15
  11. package/coverage/lcov-report/components/ContainerCreationPrompt.jsx.html +376 -0
  12. package/coverage/lcov-report/components/PromptField.jsx.html +229 -0
  13. package/coverage/lcov-report/components/SuggestionPanel.jsx.html +244 -0
  14. package/coverage/lcov-report/components/index.html +146 -0
  15. package/coverage/lcov-report/helpers/constants.js.html +80 -17
  16. package/coverage/lcov-report/helpers/containerOptionsBuilder.js.html +41 -8
  17. package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerActions.js.html +162 -84
  18. package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerList.js.html +50 -32
  19. package/coverage/lcov-report/helpers/dockerService/serviceComponents/index.html +21 -21
  20. package/coverage/lcov-report/helpers/imageNameUtils.js.html +181 -0
  21. package/coverage/lcov-report/helpers/index.html +27 -12
  22. package/coverage/lcov-report/helpers/logger.js.html +19 -19
  23. package/coverage/lcov-report/helpers/safeCall.js.html +3 -3
  24. package/coverage/lcov-report/helpers/validationHelpers.js.html +28 -76
  25. package/coverage/lcov-report/hooks/creation/index.html +20 -20
  26. package/coverage/lcov-report/hooks/creation/useContainerActions.js.html +38 -17
  27. package/coverage/lcov-report/hooks/creation/useContainerCreation.js.html +416 -89
  28. package/coverage/lcov-report/hooks/creation/useLogsViewer.js.html +7 -7
  29. package/coverage/lcov-report/hooks/debug/index.html +5 -5
  30. package/coverage/lcov-report/hooks/debug/useDebugLogs.js.html +35 -20
  31. package/coverage/lcov-report/hooks/index.html +40 -40
  32. package/coverage/lcov-report/hooks/navigation/index.html +19 -19
  33. package/coverage/lcov-report/hooks/navigation/useContainerSelection.js.html +38 -29
  34. package/coverage/lcov-report/hooks/useContainerCommandRouter.js.html +119 -80
  35. package/coverage/lcov-report/hooks/useControls.js.html +263 -122
  36. package/coverage/lcov-report/hooks/useEraseConfirmation.js.html +8 -8
  37. package/coverage/lcov-report/hooks/useExitHandler.js.html +50 -41
  38. package/coverage/lcov-report/index.html +67 -52
  39. package/coverage/lcov.info +991 -805
  40. package/dist/App.js +11 -11
  41. package/dist/components/ContainerCreationPrompt.js +31 -10
  42. package/dist/components/ContainerList.js +3 -3
  43. package/dist/components/ContainerRow.js +20 -20
  44. package/dist/components/ContainerSection.js +3 -3
  45. package/dist/components/Footer.js +2 -2
  46. package/dist/components/Header.js +7 -7
  47. package/dist/components/LogViewer.js +3 -3
  48. package/dist/components/MessageFeedback.js +2 -2
  49. package/dist/components/PromptField.js +4 -4
  50. package/dist/components/StatsBar.js +5 -5
  51. package/dist/components/SuggestionPanel.js +44 -0
  52. package/dist/components/UsageMenu.js +3 -3
  53. package/dist/helpers/appInfo.js +7 -7
  54. package/dist/helpers/constants.js +114 -0
  55. package/dist/helpers/containerOptionsBuilder.js +3 -3
  56. package/dist/helpers/dockerService/dockerService.js +1 -1
  57. package/dist/helpers/dockerService/serviceComponents/containerActions.js +99 -40
  58. package/dist/helpers/dockerService/serviceComponents/containerList.js +11 -11
  59. package/dist/helpers/dockerService/serviceComponents/containerLogs.js +9 -9
  60. package/dist/helpers/dockerService/serviceComponents/containerStats.js +5 -5
  61. package/dist/helpers/dockerService/serviceComponents/imageUtils.js +1 -1
  62. package/dist/helpers/exitWithMessage.js +10 -10
  63. package/dist/helpers/imageNameUtils.js +32 -0
  64. package/dist/helpers/logger.js +13 -13
  65. package/dist/helpers/safeCall.js +2 -2
  66. package/dist/helpers/validationHelpers.js +76 -21
  67. package/dist/hooks/creation/useContainerActions.js +9 -9
  68. package/dist/hooks/creation/useContainerCreation.js +138 -36
  69. package/dist/hooks/creation/useLogsViewer.js +2 -2
  70. package/dist/hooks/debug/useDebugLogs.js +4 -4
  71. package/dist/hooks/useContainerCommandRouter.js +13 -13
  72. package/dist/hooks/useContainers.js +3 -3
  73. package/dist/hooks/useControls.js +66 -35
  74. package/dist/hooks/useEraseConfirmation.js +3 -3
  75. package/dist/index.js +2 -2
  76. package/jest.config.cjs +7 -0
  77. package/package.json +7 -3
  78. package/src/App.jsx +33 -16
  79. package/src/components/ContainerCreationPrompt.jsx +43 -9
  80. package/src/components/ContainerList.jsx +3 -3
  81. package/src/components/ContainerRow.jsx +19 -16
  82. package/src/components/ContainerSection.jsx +3 -3
  83. package/src/components/Footer.jsx +2 -2
  84. package/src/components/Header.jsx +7 -7
  85. package/src/components/LogViewer.jsx +5 -3
  86. package/src/components/MessageFeedback.jsx +2 -2
  87. package/src/components/PromptField.jsx +4 -4
  88. package/src/components/StatsBar.jsx +11 -7
  89. package/src/components/SuggestionPanel.jsx +53 -0
  90. package/src/components/UsageMenu.jsx +18 -13
  91. package/src/helpers/appInfo.js +7 -7
  92. package/src/helpers/constants.js +30 -9
  93. package/src/helpers/containerOptionsBuilder.js +16 -5
  94. package/src/helpers/dockerService/dockerService.js +1 -1
  95. package/src/helpers/dockerService/serviceComponents/containerActions.js +87 -61
  96. package/src/helpers/dockerService/serviceComponents/containerList.js +31 -25
  97. package/src/helpers/dockerService/serviceComponents/containerLogs.js +41 -26
  98. package/src/helpers/dockerService/serviceComponents/containerStats.js +13 -11
  99. package/src/helpers/dockerService/serviceComponents/imageUtils.js +5 -4
  100. package/src/helpers/exitWithMessage.js +13 -7
  101. package/src/helpers/imageNameUtils.js +32 -0
  102. package/src/helpers/logger.js +15 -15
  103. package/src/helpers/safeCall.js +2 -2
  104. package/src/helpers/validationHelpers.js +13 -29
  105. package/src/hooks/creation/useContainerActions.js +19 -12
  106. package/src/hooks/creation/useContainerCreation.js +150 -41
  107. package/src/hooks/creation/useLogsViewer.js +2 -2
  108. package/src/hooks/debug/useDebugLogs.js +15 -10
  109. package/src/hooks/navigation/useContainerSelection.js +17 -14
  110. package/src/hooks/useContainerCommandRouter.js +71 -58
  111. package/src/hooks/useContainers.js +4 -4
  112. package/src/hooks/useControls.js +115 -68
  113. package/src/hooks/useEraseConfirmation.js +3 -3
  114. package/src/hooks/useExitHandler.js +29 -26
  115. package/src/index.js +4 -4
  116. package/test/ContainerCreationPrompt.dom.test.js +68 -0
  117. package/test/SuggestionPanel.dom.test.js +53 -0
  118. package/test/constants.test.js +34 -0
  119. package/test/useContainerCreation.dom.test.js +123 -0
  120. package/test/useControls.dom.test.js +198 -2
package/dist/App.js CHANGED
@@ -9,17 +9,17 @@
9
9
  * // Render the app
10
10
  * <App />
11
11
  */
12
- import React from "react";
13
- import { Box, Text, Spacer } from "ink";
14
- import { useContainers } from "./hooks/useContainers.js";
15
- import { useControls } from "./hooks/useControls.js";
16
- import ContainerSection from "./components/ContainerSection.js";
17
- import MessageFeedback from "./components/MessageFeedback.js";
18
- import Header from "./components/Header.js";
19
- import LogViewer from "./components/LogViewer.js";
20
- import ContainerCreationPrompt from "./components/ContainerCreationPrompt.js";
21
- import UsageMenu from "./components/UsageMenu.js";
22
- import Footer from "./components/Footer.js";
12
+ import React from 'react';
13
+ import { Box, Text, Spacer } from 'ink';
14
+ import { useContainers } from './hooks/useContainers.js';
15
+ import { useControls } from './hooks/useControls.js';
16
+ import ContainerSection from './components/ContainerSection.js';
17
+ import MessageFeedback from './components/MessageFeedback.js';
18
+ import Header from './components/Header.js';
19
+ import LogViewer from './components/LogViewer.js';
20
+ import ContainerCreationPrompt from './components/ContainerCreationPrompt.js';
21
+ import UsageMenu from './components/UsageMenu.js';
22
+ import Footer from './components/Footer.js';
23
23
  export default function App() {
24
24
  var _useContainers = useContainers(),
25
25
  containers = _useContainers.containers;
@@ -1,6 +1,7 @@
1
- import React from "react";
2
- import { Box, Text } from "ink";
3
- import { PromptField, PromptMessage } from "./PromptField.js";
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
+ import { PromptField, PromptMessage } from './PromptField.js';
4
+ import { SuggestionPanel } from './SuggestionPanel.js';
4
5
  import PropTypes from 'prop-types';
5
6
 
6
7
  /**
@@ -16,6 +17,9 @@ import PropTypes from 'prop-types';
16
17
  * @param {string} props.envInput - Value for the environment variables field
17
18
  * @param {string} props.message - Contextual message to display (errors/hints)
18
19
  * @param {string} props.messageColor - Color for the contextual message
20
+ * @param {string[]} [props.suggestions] - Autocomplete suggestions (shown only on step 0)
21
+ * @param {number} [props.selectedSuggestionIndex] - Currently focused suggestion index
22
+ * @param {number} [props.visibleOffset] - First visible suggestion offset
19
23
  * @returns {JSX.Element}
20
24
  */
21
25
  export default function ContainerCreationPrompt(props) {
@@ -25,21 +29,27 @@ export default function ContainerCreationPrompt(props) {
25
29
  portInput = props.portInput,
26
30
  envInput = props.envInput,
27
31
  message = props.message,
28
- messageColor = props.messageColor;
32
+ messageColor = props.messageColor,
33
+ _props$suggestions = props.suggestions,
34
+ suggestions = _props$suggestions === void 0 ? [] : _props$suggestions,
35
+ _props$selectedSugges = props.selectedSuggestionIndex,
36
+ selectedSuggestionIndex = _props$selectedSugges === void 0 ? -1 : _props$selectedSugges,
37
+ _props$visibleOffset = props.visibleOffset,
38
+ visibleOffset = _props$visibleOffset === void 0 ? 0 : _props$visibleOffset;
29
39
  var prompts = [{
30
- label: "Name of the image to create (e.g., nginx:latest):",
40
+ label: 'Name of the image to create (e.g., nginx:latest):',
31
41
  value: imageName,
32
42
  required: true
33
43
  }, {
34
- label: "Name of the container (optional):",
44
+ label: 'Name of the container (optional):',
35
45
  value: containerName,
36
46
  required: false
37
47
  }, {
38
- label: "Ports (optional, format 8080:80,443:443):",
48
+ label: 'Ports (optional, format 8080:80,443:443):',
39
49
  value: portInput,
40
50
  required: false
41
51
  }, {
42
- label: "Environment variables (optional, format VAR1=val1,VAR2=val2):",
52
+ label: 'Environment variables (optional, format VAR1=val1,VAR2=val2):',
43
53
  value: envInput,
44
54
  required: false
45
55
  }];
@@ -47,6 +57,7 @@ export default function ContainerCreationPrompt(props) {
47
57
  label = _ref.label,
48
58
  value = _ref.value,
49
59
  required = _ref.required;
60
+ var showSuggestions = step === 0 && suggestions.length > 0;
50
61
  return /*#__PURE__*/React.createElement(Box, {
51
62
  flexDirection: "column",
52
63
  borderStyle: "round",
@@ -56,6 +67,10 @@ export default function ContainerCreationPrompt(props) {
56
67
  label: label,
57
68
  value: value,
58
69
  required: required
70
+ }), showSuggestions && /*#__PURE__*/React.createElement(SuggestionPanel, {
71
+ items: suggestions,
72
+ selectedIndex: selectedSuggestionIndex,
73
+ visibleOffset: visibleOffset
59
74
  }), /*#__PURE__*/React.createElement(PromptMessage, {
60
75
  message: message,
61
76
  color: messageColor
@@ -70,5 +85,11 @@ ContainerCreationPrompt.propTypes = {
70
85
  portInput: PropTypes.string,
71
86
  envInput: PropTypes.string,
72
87
  message: PropTypes.string,
73
- messageColor: PropTypes.string
74
- };
88
+ messageColor: PropTypes.string,
89
+ suggestions: PropTypes.arrayOf(PropTypes.string),
90
+ selectedSuggestionIndex: PropTypes.number,
91
+ visibleOffset: PropTypes.number
92
+ };
93
+
94
+ // Named export for test compatibility with jest ESM interop
95
+ export { ContainerCreationPrompt };
@@ -9,10 +9,10 @@
9
9
  * // Render with containers
10
10
  * <ContainerList containers={containers} />
11
11
  */
12
- import React from "react";
12
+ import React from 'react';
13
13
  import PropTypes from 'prop-types';
14
- import { Box } from "ink";
15
- import ContainerRow from "./ContainerRow.js";
14
+ import { Box } from 'ink';
15
+ import ContainerRow from './ContainerRow.js';
16
16
  export default function ContainerList(_ref) {
17
17
  var containers = _ref.containers,
18
18
  selected = _ref.selected;
@@ -1,24 +1,24 @@
1
- import React from "react";
2
- import { Box, Text } from "ink";
3
- import StatsBar from "./StatsBar.js";
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
+ import StatsBar from './StatsBar.js';
4
4
  import PropTypes from 'prop-types';
5
5
  import { useContainerStats } from '../hooks/useContainerStats.js';
6
6
  var stateText = function stateText(state) {
7
- if (state === "running") return {
8
- text: "🟢 RUNNING",
9
- color: "green"
7
+ if (state === 'running') return {
8
+ text: '🟢 RUNNING',
9
+ color: 'green'
10
10
  };
11
- if (state === "exited") return {
12
- text: "🔴 EXITED",
13
- color: "red"
11
+ if (state === 'exited') return {
12
+ text: '🔴 EXITED',
13
+ color: 'red'
14
14
  };
15
- if (state === "paused") return {
16
- text: "🟠 PAUSED",
17
- color: "yellow"
15
+ if (state === 'paused') return {
16
+ text: '🟠 PAUSED',
17
+ color: 'yellow'
18
18
  };
19
19
  return {
20
20
  text: state.toUpperCase(),
21
- color: "gray"
21
+ color: 'gray'
22
22
  };
23
23
  };
24
24
 
@@ -43,11 +43,11 @@ export default function ContainerRow(_ref) {
43
43
 
44
44
  // Format ports for display (no leading space to avoid layout shifts)
45
45
  var formatPorts = function formatPorts(ports) {
46
- if (!ports || ports.length === 0) return "";
46
+ if (!ports || ports.length === 0) return '';
47
47
  if (Array.isArray(ports)) {
48
48
  return ports.map(function (p, _i) {
49
49
  return "\uD83D\uDD17 ".concat(p);
50
- }).join(" ");
50
+ }).join(' ');
51
51
  }
52
52
  return "\uD83D\uDD17 ".concat(ports);
53
53
  };
@@ -55,8 +55,8 @@ export default function ContainerRow(_ref) {
55
55
  // Helper to truncate long strings to a max length without adding trailing spaces
56
56
  var truncate = function truncate(s) {
57
57
  var max = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 20;
58
- if (!s) return "";
59
- return s.length > max ? s.slice(0, max - 1) + "" : s;
58
+ if (!s) return '';
59
+ return s.length > max ? s.slice(0, max - 1) + '' : s;
60
60
  };
61
61
  var stateInfo = stateText(state);
62
62
  return /*#__PURE__*/React.createElement(Box, {
@@ -71,8 +71,8 @@ export default function ContainerRow(_ref) {
71
71
  justifyContent: "flex-end",
72
72
  paddingRight: 1
73
73
  }, /*#__PURE__*/React.createElement(Text, {
74
- color: isSelected ? "green" : undefined
75
- }, isSelected ? "" : " ")), /*#__PURE__*/React.createElement(Box, {
74
+ color: isSelected ? 'green' : undefined
75
+ }, isSelected ? '' : ' ')), /*#__PURE__*/React.createElement(Box, {
76
76
  width: 18,
77
77
  flexShrink: 1,
78
78
  paddingRight: 1
@@ -100,7 +100,7 @@ export default function ContainerRow(_ref) {
100
100
  }, formatPorts(container.ports))), /*#__PURE__*/React.createElement(Box, {
101
101
  flexShrink: 0,
102
102
  paddingLeft: 1
103
- }, state === "running" ? /*#__PURE__*/React.createElement(StatsBar, {
103
+ }, state === 'running' ? /*#__PURE__*/React.createElement(StatsBar, {
104
104
  cpu: parseFloat(stats.cpuPercent),
105
105
  mem: parseFloat(stats.memPercent)
106
106
  }) : null)), statsError && /*#__PURE__*/React.createElement(Text, {
@@ -1,6 +1,6 @@
1
- import React from "react";
2
- import { Text } from "ink";
3
- import ContainerList from "./ContainerList.js";
1
+ import React from 'react';
2
+ import { Text } from 'ink';
3
+ import ContainerList from './ContainerList.js';
4
4
  import PropTypes from 'prop-types';
5
5
 
6
6
  /**
@@ -1,5 +1,5 @@
1
- import React from "react";
2
- import { Box, Text } from "ink";
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
3
 
4
4
  /**
5
5
  * Simple footer displayed at the bottom of the interface.
@@ -1,8 +1,8 @@
1
- import React from "react";
2
- import { Box, Text } from "ink";
3
- import chalk from "chalk";
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
+ import chalk from 'chalk';
4
4
  import PropTypes from 'prop-types';
5
- import { getAppVersion } from "../helpers/appInfo.js";
5
+ import { getAppVersion } from '../helpers/appInfo.js';
6
6
 
7
7
  /**
8
8
  * Header component displayed at the top of the CLI UI.
@@ -18,12 +18,12 @@ import { getAppVersion } from "../helpers/appInfo.js";
18
18
  var VERSION = getAppVersion();
19
19
  export default function Header(_ref) {
20
20
  var count = _ref.count;
21
- var formattedVersion = VERSION === "unknown" ? "unknown" : "v".concat(VERSION);
21
+ var formattedVersion = VERSION === 'unknown' ? 'unknown' : "v".concat(VERSION);
22
22
  return /*#__PURE__*/React.createElement(Box, {
23
23
  justifyContent: "space-between"
24
24
  }, /*#__PURE__*/React.createElement(Text, {
25
25
  color: "cyanBright"
26
- }, "\uD83D\uDC33 ", chalk.bold("CDD"), /*#__PURE__*/React.createElement(Text, {
26
+ }, "\uD83D\uDC33 ", chalk.bold('CDD'), /*#__PURE__*/React.createElement(Text, {
27
27
  color: "gray"
28
28
  }, " \u2014 CLI Docker Dashboard")), /*#__PURE__*/React.createElement(Box, {
29
29
  flexDirection: "column",
@@ -32,7 +32,7 @@ export default function Header(_ref) {
32
32
  color: "gray"
33
33
  }, formattedVersion), /*#__PURE__*/React.createElement(Text, {
34
34
  color: "gray"
35
- }, count, " container", count === 1 ? "" : "s", " found")));
35
+ }, count, " container", count === 1 ? '' : 's', " found")));
36
36
  }
37
37
  Header.propTypes = {
38
38
  count: PropTypes.number.isRequired
@@ -1,5 +1,5 @@
1
- import React from "react";
2
- import { Text } from "ink";
1
+ import React from 'react';
2
+ import { Text } from 'ink';
3
3
  import PropTypes from 'prop-types';
4
4
 
5
5
  /**
@@ -20,7 +20,7 @@ export default function LogViewer(_ref) {
20
20
  var visibleLogs = logs.slice(-15);
21
21
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Text, {
22
22
  color: "green"
23
- }, (_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, {
23
+ }, (_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, {
24
24
  dimColor: true
25
25
  }, "No logs...") : visibleLogs.map(function (line, idx) {
26
26
  return /*#__PURE__*/React.createElement(Text, {
@@ -1,5 +1,5 @@
1
- import React from "react";
2
- import { Box, Text } from "ink";
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
3
  import PropTypes from 'prop-types';
4
4
 
5
5
  /**
@@ -1,5 +1,5 @@
1
- import React from "react";
2
- import { Text } from "ink";
1
+ import React from 'react';
2
+ import { Text } from 'ink';
3
3
  import PropTypes from 'prop-types';
4
4
 
5
5
  /**
@@ -19,7 +19,7 @@ export function PromptField(_ref) {
19
19
  // If the field is required and empty, show in red
20
20
  var isEmpty = required && !value.trim();
21
21
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Text, null, label), /*#__PURE__*/React.createElement(Text, {
22
- color: isEmpty ? "red" : "cyan"
22
+ color: isEmpty ? 'red' : 'cyan'
23
23
  }, value, "_"));
24
24
  }
25
25
 
@@ -36,7 +36,7 @@ export function PromptMessage(_ref2) {
36
36
  color = _ref2.color;
37
37
  if (!message) return null;
38
38
  return /*#__PURE__*/React.createElement(Text, {
39
- color: color || "yellow"
39
+ color: color || 'yellow'
40
40
  }, message);
41
41
  }
42
42
  PromptField.propTypes = {
@@ -1,6 +1,6 @@
1
- import React from "react";
2
- import { Text } from "ink";
3
- import chalk from "chalk";
1
+ import React from 'react';
2
+ import { Text } from 'ink';
3
+ import chalk from 'chalk';
4
4
  import PropTypes from 'prop-types';
5
5
  var BAR_WIDTH = 8;
6
6
 
@@ -36,14 +36,14 @@ function makeBar(value, width) {
36
36
  var normalized = Number.isFinite(value) ? Math.min(Math.max(value, 0), 100) : 0;
37
37
  var filled = Math.round(normalized / 100 * safeWidth);
38
38
  var empty = Math.max(0, safeWidth - filled);
39
- var bar = "".repeat(filled) + "".repeat(empty);
39
+ var bar = ''.repeat(filled) + ''.repeat(empty);
40
40
  return colorize(normalized, bar);
41
41
  }
42
42
  function formatPercent(value) {
43
43
  if (!Number.isFinite(value)) return 0;
44
44
  var normalized = Math.min(Math.max(value, 0), 100);
45
45
  if (normalized >= 10) return Math.round(normalized);
46
- return normalized.toFixed(1).replace(/\.0$/, "");
46
+ return normalized.toFixed(1).replace(/\.0$/, '');
47
47
  }
48
48
 
49
49
  /**
@@ -0,0 +1,44 @@
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
+ import PropTypes from 'prop-types';
4
+
5
+ /**
6
+ * Renders an autocomplete suggestion panel as a boxed list below the image input.
7
+ * Displays a window of `maxVisible` items starting at `visibleOffset`.
8
+ * The focused row (selectedIndex) is highlighted cyan with a › prefix.
9
+ *
10
+ * @param {Object} props
11
+ * @param {string[]} props.items - Full list of suggestion strings
12
+ * @param {number} props.selectedIndex - Currently focused index (-1 = none)
13
+ * @param {number} props.visibleOffset - First item index of the visible window
14
+ * @param {number} [props.maxVisible=6] - Maximum number of items visible at once
15
+ * @returns {JSX.Element|null}
16
+ */
17
+ export function SuggestionPanel(_ref) {
18
+ var items = _ref.items,
19
+ selectedIndex = _ref.selectedIndex,
20
+ visibleOffset = _ref.visibleOffset,
21
+ _ref$maxVisible = _ref.maxVisible,
22
+ maxVisible = _ref$maxVisible === void 0 ? 6 : _ref$maxVisible;
23
+ if (!items || items.length === 0) return null;
24
+ var visible = items.slice(visibleOffset, visibleOffset + maxVisible);
25
+ return /*#__PURE__*/React.createElement(Box, {
26
+ flexDirection: "column",
27
+ borderStyle: "single",
28
+ borderColor: "cyan",
29
+ paddingX: 1
30
+ }, visible.map(function (item, localIdx) {
31
+ var globalIdx = visibleOffset + localIdx;
32
+ var isFocused = globalIdx === selectedIndex;
33
+ return /*#__PURE__*/React.createElement(Text, {
34
+ key: item,
35
+ color: isFocused ? 'cyan' : undefined
36
+ }, isFocused ? "\u203A ".concat(item) : " ".concat(item));
37
+ }));
38
+ }
39
+ SuggestionPanel.propTypes = {
40
+ items: PropTypes.arrayOf(PropTypes.string).isRequired,
41
+ selectedIndex: PropTypes.number.isRequired,
42
+ visibleOffset: PropTypes.number.isRequired,
43
+ maxVisible: PropTypes.number
44
+ };
@@ -1,6 +1,6 @@
1
- import React from "react";
2
- import { Box, Text } from "ink";
3
- var SHORTCUTS = ["Use ↑/↓ for navigation", "I to initiate selected container", "P to stop selected container", "R to restart selected container", "C to create a container", "L to view logs of selected container", "D to toggle debug log panel", "E to remove selected container", "Q to quit"];
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
+ var SHORTCUTS = ['Use ↑/↓ for navigation', 'I to initiate selected container', 'P to stop selected container', 'R to restart selected container', 'C to create a container', 'L to view logs of selected container', 'D to toggle debug log panel', 'E to remove selected container', 'Q to quit'];
4
4
  var GRID_COLUMNS = 3;
5
5
 
6
6
  /**
@@ -1,6 +1,6 @@
1
- import { readFileSync } from "fs";
2
- import path from "path";
3
- import { fileURLToPath } from "url";
1
+ import { readFileSync } from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
4
  var cachedVersion;
5
5
  export function getAppVersion() {
6
6
  if (cachedVersion) {
@@ -10,12 +10,12 @@ export function getAppVersion() {
10
10
  var _packageJson$version;
11
11
  var __filename = fileURLToPath(import.meta.url);
12
12
  var moduleDir = path.dirname(__filename);
13
- var packageJsonPath = path.resolve(moduleDir, "../../package.json");
14
- var packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
15
- cachedVersion = (_packageJson$version = packageJson.version) !== null && _packageJson$version !== void 0 ? _packageJson$version : "unknown";
13
+ var packageJsonPath = path.resolve(moduleDir, '../../package.json');
14
+ var packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
15
+ cachedVersion = (_packageJson$version = packageJson.version) !== null && _packageJson$version !== void 0 ? _packageJson$version : 'unknown';
16
16
  return cachedVersion;
17
17
  } catch (error) {
18
- cachedVersion = "unknown";
18
+ cachedVersion = 'unknown';
19
19
  return cachedVersion;
20
20
  }
21
21
  }
@@ -27,4 +27,118 @@ export var EXIT_DELAY = 500;
27
27
  export var TIMEOUTS = {
28
28
  CONTAINER_OP: 30000,
29
29
  PULL_IMAGE: 300000
30
+ };
31
+
32
+ /**
33
+ * List of well-known database image names (without tags or registry prefixes).
34
+ */
35
+ export var DB_IMAGES = ['mysql', 'mariadb', 'postgres', 'mongo', 'mssql', 'redis'];
36
+
37
+ /**
38
+ * Per-image profiles with required env vars, default exposed port, and suggested env vars.
39
+ * Keys are normalized image names (no tag, no registry prefix).
40
+ *
41
+ * @type {Record<string, { requiredEnv: string[], defaultPort: string, suggestedEnv: string[] }>}
42
+ */
43
+ export var IMAGE_PROFILES = {
44
+ nginx: {
45
+ requiredEnv: [],
46
+ defaultPort: '80',
47
+ suggestedEnv: []
48
+ },
49
+ httpd: {
50
+ requiredEnv: [],
51
+ defaultPort: '80',
52
+ suggestedEnv: []
53
+ },
54
+ caddy: {
55
+ requiredEnv: [],
56
+ defaultPort: '80',
57
+ suggestedEnv: []
58
+ },
59
+ node: {
60
+ requiredEnv: [],
61
+ defaultPort: '3000',
62
+ suggestedEnv: ['NODE_ENV=production']
63
+ },
64
+ python: {
65
+ requiredEnv: [],
66
+ defaultPort: '8000',
67
+ suggestedEnv: ['PYTHONUNBUFFERED=1']
68
+ },
69
+ openjdk: {
70
+ requiredEnv: [],
71
+ defaultPort: '8080',
72
+ suggestedEnv: ['JAVA_OPTS=-Xms256m -Xmx512m']
73
+ },
74
+ golang: {
75
+ requiredEnv: [],
76
+ defaultPort: '8080',
77
+ suggestedEnv: ['GOMAXPROCS=2']
78
+ },
79
+ redis: {
80
+ requiredEnv: [],
81
+ defaultPort: '6379',
82
+ suggestedEnv: ['REDIS_PASSWORD=change-me']
83
+ },
84
+ memcached: {
85
+ requiredEnv: [],
86
+ defaultPort: '11211',
87
+ suggestedEnv: []
88
+ },
89
+ postgres: {
90
+ requiredEnv: ['POSTGRES_PASSWORD'],
91
+ defaultPort: '5432',
92
+ suggestedEnv: ['POSTGRES_USER=postgres', 'POSTGRES_DB=app']
93
+ },
94
+ mysql: {
95
+ requiredEnv: ['MYSQL_ROOT_PASSWORD'],
96
+ defaultPort: '3306',
97
+ suggestedEnv: ['MYSQL_DATABASE=app', 'MYSQL_USER=app', 'MYSQL_PASSWORD=app123']
98
+ },
99
+ mariadb: {
100
+ requiredEnv: ['MARIADB_ROOT_PASSWORD'],
101
+ defaultPort: '3306',
102
+ suggestedEnv: ['MARIADB_DATABASE=app', 'MARIADB_USER=app', 'MARIADB_PASSWORD=app123']
103
+ },
104
+ mongo: {
105
+ requiredEnv: [],
106
+ defaultPort: '27017',
107
+ suggestedEnv: ['MONGO_INITDB_ROOT_USERNAME=admin', 'MONGO_INITDB_ROOT_PASSWORD=secret']
108
+ },
109
+ mssql: {
110
+ requiredEnv: ['ACCEPT_EULA', 'SA_PASSWORD'],
111
+ defaultPort: '1433',
112
+ suggestedEnv: ['ACCEPT_EULA=Y', 'MSSQL_PID=Developer']
113
+ },
114
+ rabbitmq: {
115
+ requiredEnv: [],
116
+ defaultPort: '5672',
117
+ suggestedEnv: ['RABBITMQ_DEFAULT_USER=guest', 'RABBITMQ_DEFAULT_PASS=guest']
118
+ },
119
+ kafka: {
120
+ requiredEnv: [],
121
+ defaultPort: '9092',
122
+ suggestedEnv: ['KAFKA_CFG_LISTENERS=PLAINTEXT://:9092']
123
+ },
124
+ zookeeper: {
125
+ requiredEnv: [],
126
+ defaultPort: '2181',
127
+ suggestedEnv: ['ALLOW_ANONYMOUS_LOGIN=yes']
128
+ },
129
+ elasticsearch: {
130
+ requiredEnv: [],
131
+ defaultPort: '9200',
132
+ suggestedEnv: ['discovery.type=single-node', 'xpack.security.enabled=false']
133
+ },
134
+ minio: {
135
+ requiredEnv: ['MINIO_ROOT_USER', 'MINIO_ROOT_PASSWORD'],
136
+ defaultPort: '9000',
137
+ suggestedEnv: ['MINIO_CONSOLE_ADDRESS=:9001']
138
+ },
139
+ wordpress: {
140
+ requiredEnv: ['WORDPRESS_DB_HOST', 'WORDPRESS_DB_USER', 'WORDPRESS_DB_PASSWORD', 'WORDPRESS_DB_NAME'],
141
+ defaultPort: '8080',
142
+ suggestedEnv: ['WORDPRESS_TABLE_PREFIX=wp_']
143
+ }
30
144
  };
@@ -24,16 +24,16 @@ export function buildContainerOptions(_ref) {
24
24
  containerName = _ref.containerName,
25
25
  portInput = _ref.portInput,
26
26
  envInput = _ref.envInput;
27
- var env = (envInput || "").split(",").map(function (s) {
27
+ var env = (envInput || '').split(',').map(function (s) {
28
28
  return s.trim();
29
29
  }).filter(Boolean);
30
- var ports = (portInput || "").split(",").map(function (s) {
30
+ var ports = (portInput || '').split(',').map(function (s) {
31
31
  return s.trim();
32
32
  }).filter(Boolean);
33
33
  var ExposedPorts = {};
34
34
  var PortBindings = {};
35
35
  ports.forEach(function (pair) {
36
- var _pair$split = pair.split(":"),
36
+ var _pair$split = pair.split(':'),
37
37
  _pair$split2 = _slicedToArray(_pair$split, 2),
38
38
  host = _pair$split2[0],
39
39
  cont = _pair$split2[1];
@@ -1,4 +1,4 @@
1
- import Docker from "dockerode";
1
+ import Docker from 'dockerode';
2
2
 
3
3
  // Use default dockerode configuration which automatically handles:
4
4
  // - /var/run/docker.sock on Linux/Mac