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