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.
- package/README.es.md +60 -26
- package/README.md +60 -26
- package/dist/App.js +1 -0
- package/dist/components/ContainerCreationPrompt.js +14 -0
- package/dist/components/PromptField.js +13 -6
- package/dist/components/TextField.js +54 -0
- package/dist/components/UsageMenu.js +1 -1
- package/dist/helpers/appState.js +26 -0
- package/dist/helpers/dockerService/serviceComponents/containerExec.js +211 -0
- package/dist/helpers/textEditing.js +293 -0
- package/dist/hooks/creation/useContainerCreation.js +132 -15
- package/dist/hooks/useContainerCommandRouter.js +12 -2
- package/dist/hooks/useControls.js +21 -26
- package/dist/hooks/useShellMode.js +101 -0
- package/dist/index.js +3 -1
- package/package.json +4 -1
- package/.atl/skill-registry.md +0 -30
- package/.editorconfig +0 -12
- package/.eslintrc.json +0 -30
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -30
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
- package/.github/PULL_REQUEST_TEMPLATE/pull_request.md +0 -14
- package/.github/workflows/ci.yml +0 -32
- package/.github/workflows/docs.yml +0 -42
- package/.prettierignore +0 -4
- package/.prettierrc +0 -8
- package/.~lock.ROADMAP.md# +0 -1
- package/CHANGELOG.md +0 -216
- package/CODE_OF_CONDUCT.md +0 -12
- package/CONTRIBUTING.md +0 -35
- package/ROADMAP.md +0 -647
- package/SECURITY.md +0 -5
- package/__mocks__/ink.cjs +0 -17
- package/_config.yml +0 -13
- package/babel.config.cjs +0 -23
- package/coverage/clover.xml +0 -575
- package/coverage/coverage-final.json +0 -17
- package/coverage/lcov-report/ContainerCreationPrompt.jsx.html +0 -400
- package/coverage/lcov-report/ControlsHUD.jsx.html +0 -256
- package/coverage/lcov-report/base.css +0 -224
- package/coverage/lcov-report/block-navigation.js +0 -87
- package/coverage/lcov-report/components/ContainerCreationPrompt.jsx.html +0 -394
- package/coverage/lcov-report/components/PromptField.jsx.html +0 -229
- package/coverage/lcov-report/components/SuggestionPanel.jsx.html +0 -268
- package/coverage/lcov-report/components/index.html +0 -146
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/helpers/constants.js.html +0 -352
- package/coverage/lcov-report/helpers/containerOptionsBuilder.js.html +0 -244
- package/coverage/lcov-report/helpers/dockerHubService.js.html +0 -310
- package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerActions.js.html +0 -931
- package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerList.js.html +0 -307
- package/coverage/lcov-report/helpers/dockerService/serviceComponents/index.html +0 -131
- package/coverage/lcov-report/helpers/imageNameUtils.js.html +0 -181
- package/coverage/lcov-report/helpers/index.html +0 -206
- package/coverage/lcov-report/helpers/logger.js.html +0 -424
- package/coverage/lcov-report/helpers/safeCall.js.html +0 -139
- package/coverage/lcov-report/helpers/validationHelpers.js.html +0 -346
- package/coverage/lcov-report/hooks/creation/index.html +0 -146
- package/coverage/lcov-report/hooks/creation/useContainerActions.js.html +0 -313
- package/coverage/lcov-report/hooks/creation/useContainerCreation.js.html +0 -982
- package/coverage/lcov-report/hooks/creation/useLogsViewer.js.html +0 -238
- package/coverage/lcov-report/hooks/debug/index.html +0 -116
- package/coverage/lcov-report/hooks/debug/useDebugLogs.js.html +0 -187
- package/coverage/lcov-report/hooks/index.html +0 -161
- package/coverage/lcov-report/hooks/navigation/index.html +0 -116
- package/coverage/lcov-report/hooks/navigation/useContainerSelection.js.html +0 -169
- package/coverage/lcov-report/hooks/useContainerCommandRouter.js.html +0 -454
- package/coverage/lcov-report/hooks/useControls.js.html +0 -868
- package/coverage/lcov-report/hooks/useEraseConfirmation.js.html +0 -202
- package/coverage/lcov-report/hooks/useExitHandler.js.html +0 -292
- package/coverage/lcov-report/index.html +0 -176
- package/coverage/lcov-report/prettify.css +0 -1
- package/coverage/lcov-report/prettify.js +0 -2
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +0 -210
- package/coverage/lcov.info +0 -1163
- package/fix-imports.cjs +0 -54
- package/jest.config.cjs +0 -14
- package/src/App.jsx +0 -102
- package/src/components/ContainerCreationPrompt.jsx +0 -107
- package/src/components/ContainerList.jsx +0 -32
- package/src/components/ContainerRow.jsx +0 -84
- package/src/components/ContainerSection.jsx +0 -24
- package/src/components/ControlsHUD.jsx +0 -66
- package/src/components/Footer.jsx +0 -15
- package/src/components/Header.jsx +0 -41
- package/src/components/LogViewer.jsx +0 -36
- package/src/components/MessageFeedback.jsx +0 -25
- package/src/components/PromptField.jsx +0 -48
- package/src/components/StatsBar.jsx +0 -70
- package/src/components/SuggestionPanel.jsx +0 -61
- package/src/components/UsageMenu.jsx +0 -52
- package/src/helpers/appInfo.js +0 -23
- package/src/helpers/constants.js +0 -89
- package/src/helpers/containerOptionsBuilder.js +0 -53
- package/src/helpers/dockerHubService.js +0 -75
- package/src/helpers/dockerService/dockerService.js +0 -28
- package/src/helpers/dockerService/serviceComponents/containerActions.js +0 -282
- package/src/helpers/dockerService/serviceComponents/containerList.js +0 -74
- package/src/helpers/dockerService/serviceComponents/containerLogs.js +0 -60
- package/src/helpers/dockerService/serviceComponents/containerStats.js +0 -69
- package/src/helpers/dockerService/serviceComponents/imageUtils.js +0 -32
- package/src/helpers/exitWithMessage.js +0 -32
- package/src/helpers/imageNameUtils.js +0 -32
- package/src/helpers/logger.js +0 -112
- package/src/helpers/safeCall.js +0 -18
- package/src/helpers/validationHelpers.js +0 -87
- package/src/hooks/creation/useContainerActions.js +0 -89
- package/src/hooks/creation/useContainerCreation.js +0 -377
- package/src/hooks/creation/useLogsViewer.js +0 -51
- package/src/hooks/debug/useDebugLogs.js +0 -34
- package/src/hooks/navigation/useContainerSelection.js +0 -28
- package/src/hooks/useContainerCommandRouter.js +0 -123
- package/src/hooks/useContainerStats.js +0 -44
- package/src/hooks/useContainers.js +0 -26
- package/src/hooks/useControls.js +0 -263
- package/src/hooks/useEraseConfirmation.js +0 -39
- package/src/hooks/useExitHandler.js +0 -69
- package/src/index.js +0 -20
- package/test/ContainerCreationPrompt.dom.test.js +0 -313
- package/test/ControlsHUD.dom.test.js +0 -125
- package/test/SuggestionPanel.dom.test.js +0 -83
- package/test/constants.test.js +0 -41
- package/test/containerActions.test.js +0 -229
- package/test/containerList.test.js +0 -79
- package/test/containerOptionsBuilder.test.js +0 -36
- package/test/dockerHubService.test.js +0 -213
- package/test/jest.setup.js +0 -7
- package/test/logger.test.js +0 -58
- package/test/resolveImageTag.test.js +0 -26
- package/test/safeCall.test.js +0 -25
- package/test/useContainerCreation.dom.test.js +0 -567
- package/test/useContainerCreationHub.dom.test.js +0 -184
- package/test/useControls.dom.test.js +0 -519
- 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
|
-
}
|
package/src/helpers/appInfo.js
DELETED
|
@@ -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
|
-
}
|
package/src/helpers/constants.js
DELETED
|
@@ -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 };
|