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,70 +0,0 @@
1
- import React from 'react';
2
- import { Text } from 'ink';
3
- import chalk from 'chalk';
4
- import PropTypes from 'prop-types';
5
-
6
- const BAR_WIDTH = 8;
7
-
8
- /**
9
- * Small visual bar that shows CPU and memory usage percentages.
10
- *
11
- * @param {Object} props
12
- * @param {number} props.cpu - CPU percentage (values outside 0-100 are clamped)
13
- * @param {number} props.mem - Memory percentage (values outside 0-100 are clamped)
14
- * @returns {JSX.Element}
15
- */
16
- export default function StatsBar({ cpu, mem }) {
17
- const cpuBar = makeBar(cpu, BAR_WIDTH);
18
- const memBar = makeBar(mem, BAR_WIDTH);
19
-
20
- return (
21
- <Text>
22
- CPU:{cpuBar}
23
- {colorize(cpu, ` ${formatPercent(cpu)}%`)} MEM:{memBar}
24
- {colorize(mem, ` ${formatPercent(mem)}%`)}
25
- </Text>
26
- );
27
- }
28
-
29
- StatsBar.propTypes = {
30
- cpu: PropTypes.number.isRequired,
31
- mem: PropTypes.number.isRequired,
32
- };
33
-
34
- /**
35
- * Build a fixed-width bar representation from a numeric percentage.
36
- *
37
- * @param {number} value - Percentage value (values outside 0-100 are clamped)
38
- * @param {number} width - Desired bar width in characters
39
- * @returns {string} Colored bar string
40
- */
41
- function makeBar(value, width) {
42
- const safeWidth = Math.max(1, Math.round(width));
43
- const normalized = Number.isFinite(value)
44
- ? Math.min(Math.max(value, 0), 100)
45
- : 0;
46
- const filled = Math.round((normalized / 100) * safeWidth);
47
- const empty = Math.max(0, safeWidth - filled);
48
- const bar = '█'.repeat(filled) + '░'.repeat(empty);
49
- return colorize(normalized, bar);
50
- }
51
-
52
- function formatPercent(value) {
53
- if (!Number.isFinite(value)) return 0;
54
- const normalized = Math.min(Math.max(value, 0), 100);
55
- if (normalized >= 10) return Math.round(normalized);
56
- return normalized.toFixed(1).replace(/\.0$/, '');
57
- }
58
-
59
- /**
60
- * Colorize a value or text depending on thresholds.
61
- *
62
- * @param {number} value - Numeric value used to pick color; receives clamped percentage
63
- * @param {string} [text] - Text to colorize, defaults to the numeric string
64
- * @returns {string}
65
- */
66
- function colorize(value, text = value.toString()) {
67
- if (value < 50) return chalk.greenBright(text);
68
- if (value < 80) return chalk.yellowBright(text);
69
- return chalk.redBright(text);
70
- }
@@ -1,61 +0,0 @@
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({
18
- items,
19
- selectedIndex,
20
- visibleOffset,
21
- maxVisible = 6,
22
- isLoading = false,
23
- }) {
24
- const hasItems = items && items.length > 0;
25
- if (!hasItems && !isLoading) return null;
26
-
27
- const visible = hasItems ? items.slice(visibleOffset, visibleOffset + maxVisible) : [];
28
-
29
- return (
30
- <Box
31
- flexDirection="column"
32
- borderStyle="single"
33
- borderColor="cyan"
34
- paddingX={1}
35
- data-testid="suggestion-panel"
36
- >
37
- {visible.map((item, localIdx) => {
38
- const globalIdx = visibleOffset + localIdx;
39
- const isFocused = globalIdx === selectedIndex;
40
- return (
41
- <Text key={item} color={isFocused ? 'cyan' : undefined} dimColor={!isFocused}>
42
- {isFocused ? `› ${item}` : ` ${item}`}
43
- </Text>
44
- );
45
- })}
46
- {isLoading && (
47
- <Text dimColor color="gray">
48
- {' [searching Docker Hub...]'}
49
- </Text>
50
- )}
51
- </Box>
52
- );
53
- }
54
-
55
- SuggestionPanel.propTypes = {
56
- items: PropTypes.arrayOf(PropTypes.string).isRequired,
57
- selectedIndex: PropTypes.number.isRequired,
58
- visibleOffset: PropTypes.number.isRequired,
59
- maxVisible: PropTypes.number,
60
- isLoading: PropTypes.bool,
61
- };
@@ -1,52 +0,0 @@
1
- import React from 'react';
2
- import { Box, Text } from 'ink';
3
-
4
- const SHORTCUTS = [
5
- 'Use ↑/↓ for navigation',
6
- 'I to initiate selected container',
7
- 'P to stop selected container',
8
- 'R to restart selected container',
9
- 'C to create a container',
10
- 'L to view logs of selected container',
11
- 'D to toggle debug log panel',
12
- 'E to remove selected container',
13
- 'Q to quit',
14
- ];
15
-
16
- const GRID_COLUMNS = 3;
17
-
18
- /**
19
- * Small usage menu that explains available keybindings to the user.
20
- * Renders entries in a responsive grid to avoid an excessively long column.
21
- *
22
- * @returns {JSX.Element}
23
- */
24
- export default function UsageMenu() {
25
- const rowsPerColumn = Math.ceil(SHORTCUTS.length / GRID_COLUMNS);
26
- const columns = Array.from({ length: GRID_COLUMNS }, (_, columnIndex) =>
27
- SHORTCUTS.slice(
28
- columnIndex * rowsPerColumn,
29
- (columnIndex + 1) * rowsPerColumn
30
- )
31
- ).filter((column) => column.length > 0);
32
-
33
- return (
34
- <Box flexDirection="column" marginTop={1}>
35
- <Box flexDirection="row">
36
- {columns.map((column, columnIndex) => (
37
- <Box
38
- key={`shortcut-column-${columnIndex}`}
39
- flexDirection="column"
40
- marginRight={columnIndex === columns.length - 1 ? 0 : 3}
41
- >
42
- {column.map((shortcut, rowIndex) => (
43
- <Text key={`shortcut-${columnIndex}-${rowIndex}`}>
44
- {shortcut}
45
- </Text>
46
- ))}
47
- </Box>
48
- ))}
49
- </Box>
50
- </Box>
51
- );
52
- }
@@ -1,23 +0,0 @@
1
- import { readFileSync } from 'fs';
2
- import path from 'path';
3
- import { fileURLToPath } from 'url';
4
-
5
- let cachedVersion;
6
-
7
- export function getAppVersion() {
8
- if (cachedVersion) {
9
- return cachedVersion;
10
- }
11
-
12
- try {
13
- const __filename = fileURLToPath(import.meta.url);
14
- const moduleDir = path.dirname(__filename);
15
- const packageJsonPath = path.resolve(moduleDir, '../../package.json');
16
- const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
17
- cachedVersion = packageJson.version ?? 'unknown';
18
- return cachedVersion;
19
- } catch (error) {
20
- cachedVersion = 'unknown';
21
- return cachedVersion;
22
- }
23
- }
@@ -1,89 +0,0 @@
1
- /**
2
- * Time intervals used across the app (milliseconds).
3
- * - CONTAINER_LIST: how often to refresh the list of containers
4
- * - CONTAINER_STATS: how often to fetch stats for running containers
5
- */
6
- export const REFRESH_INTERVALS = {
7
- CONTAINER_LIST: 3000,
8
- CONTAINER_STATS: 1500,
9
- };
10
-
11
- /**
12
- * Timeout values for UI messages before they disappear.
13
- */
14
- export const MESSAGE_TIMEOUTS = {
15
- SHORT: 2000,
16
- DEFAULT: 3000,
17
- };
18
-
19
- /**
20
- * Delay used before exiting the process when showing an exit message.
21
- */
22
- export const EXIT_DELAY = 500;
23
-
24
- /**
25
- * Operational timeouts for Docker operations and image pulls.
26
- */
27
- export const TIMEOUTS = {
28
- CONTAINER_OP: 30000,
29
- PULL_IMAGE: 300000,
30
- };
31
-
32
- /**
33
- * List of well-known database image names (without tags or registry prefixes).
34
- */
35
- export const DB_IMAGES = [
36
- 'mysql',
37
- 'mariadb',
38
- 'postgres',
39
- 'mongo',
40
- 'mssql',
41
- 'redis',
42
- ];
43
-
44
- /**
45
- * Per-image profiles with required env vars, default exposed port, and suggested env vars.
46
- * Keys are normalized image names (no tag, no registry prefix).
47
- *
48
- * @type {Record<string, { requiredEnv: string[], defaultPort: string, suggestedEnv: string[] }>}
49
- */
50
- export const IMAGE_PROFILES = {
51
- nginx: { requiredEnv: [], defaultPort: '80', suggestedEnv: [], defaultTag: '1.27-alpine' },
52
- httpd: { requiredEnv: [], defaultPort: '80', suggestedEnv: [], defaultTag: '2.4-alpine' },
53
- caddy: { requiredEnv: [], defaultPort: '80', suggestedEnv: [], defaultTag: '2-alpine' },
54
- node: { requiredEnv: [], defaultPort: '3000', suggestedEnv: ['NODE_ENV=production'], defaultTag: '22-alpine' },
55
- python: { requiredEnv: [], defaultPort: '8000', suggestedEnv: ['PYTHONUNBUFFERED=1'], defaultTag: '3.12-slim' },
56
- openjdk: { requiredEnv: [], defaultPort: '8080', suggestedEnv: ['JAVA_OPTS=-Xms256m -Xmx512m'], defaultTag: '21-slim' },
57
- golang: { requiredEnv: [], defaultPort: '8080', suggestedEnv: ['GOMAXPROCS=2'], defaultTag: '1.22-alpine' },
58
- redis: { requiredEnv: [], defaultPort: '6379', suggestedEnv: ['REDIS_PASSWORD=change-me'], defaultTag: '7-alpine' },
59
- memcached: { requiredEnv: [], defaultPort: '11211', suggestedEnv: [], defaultTag: '1.6-alpine' },
60
- postgres: { requiredEnv: ['POSTGRES_PASSWORD'], defaultPort: '5432', suggestedEnv: ['POSTGRES_PASSWORD=secret', 'POSTGRES_USER=postgres', 'POSTGRES_DB=app'], defaultTag: '17-alpine' },
61
- mysql: { requiredEnv: ['MYSQL_ROOT_PASSWORD'], defaultPort: '3306', suggestedEnv: ['MYSQL_ROOT_PASSWORD=secret', 'MYSQL_DATABASE=app', 'MYSQL_USER=app', 'MYSQL_PASSWORD=app123'], defaultTag: '8.0' },
62
- mariadb: { requiredEnv: ['MARIADB_ROOT_PASSWORD'], defaultPort: '3306', suggestedEnv: ['MARIADB_ROOT_PASSWORD=secret', 'MARIADB_DATABASE=app', 'MARIADB_USER=app', 'MARIADB_PASSWORD=app123'], defaultTag: '11-alpine' },
63
- mongo: { requiredEnv: [], defaultPort: '27017', suggestedEnv: ['MONGO_INITDB_ROOT_USERNAME=admin', 'MONGO_INITDB_ROOT_PASSWORD=secret'], defaultTag: '7.0' },
64
- mssql: { requiredEnv: ['ACCEPT_EULA', 'SA_PASSWORD'], defaultPort: '1433', suggestedEnv: ['ACCEPT_EULA=Y', 'SA_PASSWORD=Strong!Passw0rd', 'MSSQL_PID=Developer'], defaultTag: '2022-latest' },
65
- rabbitmq: { requiredEnv: [], defaultPort: '5672', suggestedEnv: ['RABBITMQ_DEFAULT_USER=guest', 'RABBITMQ_DEFAULT_PASS=guest'], defaultTag: '3-management-alpine' },
66
- kafka: { requiredEnv: [], defaultPort: '9092', suggestedEnv: ['KAFKA_CFG_LISTENERS=PLAINTEXT://:9092', 'KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092'], defaultTag: '3.7' },
67
- zookeeper: { requiredEnv: [], defaultPort: '2181', suggestedEnv: ['ALLOW_ANONYMOUS_LOGIN=yes'], defaultTag: '3.9' },
68
- elasticsearch: { requiredEnv: [], defaultPort: '9200', suggestedEnv: ['discovery.type=single-node', 'xpack.security.enabled=false'], defaultTag: '8.13.0' },
69
- minio: { requiredEnv: ['MINIO_ROOT_USER', 'MINIO_ROOT_PASSWORD'], defaultPort: '9000', suggestedEnv: ['MINIO_ROOT_USER=admin', 'MINIO_ROOT_PASSWORD=secret', 'MINIO_CONSOLE_ADDRESS=:9001'], defaultTag: 'latest' },
70
- wordpress: { requiredEnv: ['WORDPRESS_DB_HOST', 'WORDPRESS_DB_USER', 'WORDPRESS_DB_PASSWORD', 'WORDPRESS_DB_NAME'], defaultPort: '8080', suggestedEnv: ['WORDPRESS_DB_HOST=db', 'WORDPRESS_DB_USER=wp', 'WORDPRESS_DB_PASSWORD=secret', 'WORDPRESS_DB_NAME=wordpress', 'WORDPRESS_TABLE_PREFIX=wp_'], defaultTag: '6.5-apache' },
71
- };
72
-
73
- /**
74
- * Given an image name (with or without tag), returns the image name with a
75
- * default tag appended when the image matches a known profile and has no
76
- * explicit tag. Returns the input unchanged otherwise.
77
- *
78
- * @param {string} imageName - e.g. 'postgres', 'postgres:15', 'unknownimage'
79
- * @param {Record<string, { defaultTag: string }>} profiles
80
- * @returns {string}
81
- */
82
- export function resolveImageTag(imageName, profiles = IMAGE_PROFILES) {
83
- if (!imageName) return imageName;
84
- if (imageName.includes(':')) return imageName;
85
- const baseName = imageName.trim().toLowerCase().split('/').pop();
86
- const profile = profiles[baseName];
87
- if (!profile?.defaultTag) return imageName;
88
- return `${imageName}:${profile.defaultTag}`;
89
- }
@@ -1,53 +0,0 @@
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({
17
- imageName: _imageName,
18
- containerName,
19
- portInput,
20
- envInput,
21
- }) {
22
- const env = (envInput || '')
23
- .split(',')
24
- .map((s) => s.trim())
25
- .filter(Boolean);
26
- const ports = (portInput || '')
27
- .split(',')
28
- .map((s) => s.trim())
29
- .filter(Boolean);
30
-
31
- const ExposedPorts = {};
32
- const PortBindings = {};
33
-
34
- ports.forEach((pair) => {
35
- const [host, cont] = pair.split(':');
36
- if (!host || !cont) return;
37
- const key = `${cont}/tcp`;
38
- ExposedPorts[key] = {};
39
- PortBindings[key] = PortBindings[key] || [];
40
- PortBindings[key].push({ HostPort: `${host}` });
41
- });
42
-
43
- const options = {
44
- Tty: true,
45
- };
46
-
47
- if (Object.keys(ExposedPorts).length) options.ExposedPorts = ExposedPorts;
48
- if (Object.keys(PortBindings).length) options.HostConfig = { PortBindings };
49
- if (env.length) options.Env = env;
50
- if (containerName) options.name = containerName;
51
-
52
- return options;
53
- }
@@ -1,75 +0,0 @@
1
- /**
2
- * Docker Hub search helper.
3
- * Pure function — no side effects beyond HTTP. All state management
4
- * lives in the calling hook (useContainerCreation).
5
- */
6
-
7
- const DOCKER_HUB_SEARCH_URL =
8
- 'https://hub.docker.com/v2/search/repositories/';
9
-
10
- /**
11
- * Format a Docker Hub search result object into a display string.
12
- *
13
- * Format rules:
14
- * - Official: "name [★ official] [Npulls+]" (if pullCount > 0)
15
- * - Non-official with pulls: "name [Npulls+]"
16
- * - Otherwise: "name"
17
- *
18
- * Pull count suffixes: ≥1B → "1B+", ≥1M → "1M+", ≥1K → "1K+", else raw number
19
- *
20
- * @param {{ name: string, isOfficial: boolean, pullCount: number }} result
21
- * @returns {string}
22
- */
23
- export function formatHubResult(result) {
24
- const { name, isOfficial, pullCount } = result;
25
-
26
- let pullSuffix = '';
27
- if (pullCount >= 1e9) {
28
- pullSuffix = ` [1B+ pulls]`;
29
- } else if (pullCount >= 1e6) {
30
- pullSuffix = ` [${Math.floor(pullCount / 1e6)}M+ pulls]`;
31
- } else if (pullCount >= 1e3) {
32
- pullSuffix = ` [${Math.floor(pullCount / 1e3)}K+ pulls]`;
33
- } else if (pullCount > 0) {
34
- pullSuffix = ` [${pullCount} pulls]`;
35
- }
36
-
37
- if (isOfficial) {
38
- return `${name} [★ official]${pullSuffix}`;
39
- }
40
-
41
- return `${name}${pullSuffix}`;
42
- }
43
-
44
- /**
45
- * Search Docker Hub for images matching the given query.
46
- *
47
- * @param {string} query - Search term (will be trimmed and encoded)
48
- * @param {Object} [options]
49
- * @param {AbortSignal} [options.signal] - Abort signal; defaults to AbortSignal.timeout(5000)
50
- * @returns {Promise<Array<{ name: string, isOfficial: boolean, pullCount: number, description: string }>>}
51
- * @throws {Error} If response.ok is false or the network call rejects
52
- */
53
- export async function searchDockerHub(query, options = {}) {
54
- const q = query.trim();
55
- if (!q) return [];
56
-
57
- const url = `${DOCKER_HUB_SEARCH_URL}?query=${encodeURIComponent(q)}&page_size=10`;
58
- const signal = options.signal ?? AbortSignal.timeout(5000);
59
-
60
- const response = await fetch(url, { signal });
61
-
62
- if (!response.ok) {
63
- throw new Error(`Docker Hub search failed: ${response.status}`);
64
- }
65
-
66
- const data = await response.json();
67
- const results = data.results ?? [];
68
-
69
- return results.map((item) => ({
70
- name: item.repo_name,
71
- isOfficial: item.is_official,
72
- pullCount: item.pull_count,
73
- description: item.short_description,
74
- }));
75
- }
@@ -1,28 +0,0 @@
1
- import Docker from 'dockerode';
2
-
3
- // Use default dockerode configuration which automatically handles:
4
- // - /var/run/docker.sock on Linux/Mac
5
- // - //./pipe/docker_engine on Windows
6
- // - Environment variables DOCKER_HOST, DOCKER_CERT_PATH, etc.
7
-
8
- /**
9
- * Typedef for the docker client used in this project.
10
- * Use a generic object shape so JSDoc can render a meaningful type.
11
- *
12
- * @typedef {Object} DockerClient
13
- * @property {Function} listContainers
14
- * @property {Function} getContainer
15
- * @property {Function} listImages
16
- * @property {Function} createContainer
17
- * @property {Function} pull
18
- */
19
-
20
- /**
21
- * Shared Docker client instance (dockerode). Consumers import { docker }
22
- * and use it to list/create/start/stop containers and images.
23
- *
24
- * @type {DockerClient}
25
- */
26
- const docker = new Docker();
27
-
28
- export { docker };