cdd-cli 3.2.0 → 4.0.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 (136) 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/.~lock.ROADMAP.md# +1 -0
  7. package/CHANGELOG.md +80 -0
  8. package/README.es.md +198 -0
  9. package/README.md +81 -23
  10. package/ROADMAP.md +647 -0
  11. package/SECURITY.md +5 -0
  12. package/__mocks__/ink.cjs +17 -0
  13. package/coverage/clover.xml +471 -500
  14. package/coverage/coverage-final.json +16 -16
  15. package/coverage/lcov-report/ContainerCreationPrompt.jsx.html +400 -0
  16. package/coverage/lcov-report/ControlsHUD.jsx.html +256 -0
  17. package/coverage/lcov-report/components/ContainerCreationPrompt.jsx.html +394 -0
  18. package/coverage/lcov-report/components/PromptField.jsx.html +229 -0
  19. package/coverage/lcov-report/components/SuggestionPanel.jsx.html +268 -0
  20. package/coverage/lcov-report/components/index.html +146 -0
  21. package/coverage/lcov-report/helpers/constants.js.html +139 -22
  22. package/coverage/lcov-report/helpers/containerOptionsBuilder.js.html +75 -42
  23. package/coverage/lcov-report/helpers/dockerHubService.js.html +310 -0
  24. package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerActions.js.html +162 -84
  25. package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerList.js.html +50 -32
  26. package/coverage/lcov-report/helpers/dockerService/serviceComponents/index.html +21 -21
  27. package/coverage/lcov-report/helpers/imageNameUtils.js.html +181 -0
  28. package/coverage/lcov-report/helpers/index.html +84 -54
  29. package/coverage/lcov-report/helpers/logger.js.html +68 -68
  30. package/coverage/lcov-report/helpers/safeCall.js.html +15 -15
  31. package/coverage/lcov-report/helpers/validationHelpers.js.html +66 -114
  32. package/coverage/lcov-report/hooks/creation/index.html +20 -20
  33. package/coverage/lcov-report/hooks/creation/useContainerActions.js.html +38 -17
  34. package/coverage/lcov-report/hooks/creation/useContainerCreation.js.html +593 -89
  35. package/coverage/lcov-report/hooks/creation/useLogsViewer.js.html +7 -7
  36. package/coverage/lcov-report/hooks/debug/index.html +5 -5
  37. package/coverage/lcov-report/hooks/debug/useDebugLogs.js.html +35 -20
  38. package/coverage/lcov-report/hooks/index.html +40 -40
  39. package/coverage/lcov-report/hooks/navigation/index.html +19 -19
  40. package/coverage/lcov-report/hooks/navigation/useContainerSelection.js.html +38 -29
  41. package/coverage/lcov-report/hooks/useContainerCommandRouter.js.html +119 -80
  42. package/coverage/lcov-report/hooks/useControls.js.html +306 -123
  43. package/coverage/lcov-report/hooks/useEraseConfirmation.js.html +8 -8
  44. package/coverage/lcov-report/hooks/useExitHandler.js.html +50 -41
  45. package/coverage/lcov-report/index.html +52 -67
  46. package/coverage/lcov.info +899 -952
  47. package/dist/App.js +18 -12
  48. package/dist/components/ContainerCreationPrompt.js +50 -13
  49. package/dist/components/ContainerList.js +3 -3
  50. package/dist/components/ContainerRow.js +20 -20
  51. package/dist/components/ContainerSection.js +3 -3
  52. package/dist/components/ControlsHUD.js +106 -0
  53. package/dist/components/Footer.js +2 -2
  54. package/dist/components/Header.js +7 -7
  55. package/dist/components/LogViewer.js +3 -3
  56. package/dist/components/MessageFeedback.js +2 -2
  57. package/dist/components/PromptField.js +4 -4
  58. package/dist/components/StatsBar.js +5 -5
  59. package/dist/components/SuggestionPanel.js +53 -0
  60. package/dist/components/UsageMenu.js +3 -3
  61. package/dist/helpers/appInfo.js +7 -7
  62. package/dist/helpers/constants.js +154 -1
  63. package/dist/helpers/containerOptionsBuilder.js +3 -3
  64. package/dist/helpers/dockerHubService.js +111 -0
  65. package/dist/helpers/dockerService/dockerService.js +1 -1
  66. package/dist/helpers/dockerService/serviceComponents/containerActions.js +99 -40
  67. package/dist/helpers/dockerService/serviceComponents/containerList.js +11 -11
  68. package/dist/helpers/dockerService/serviceComponents/containerLogs.js +9 -9
  69. package/dist/helpers/dockerService/serviceComponents/containerStats.js +5 -5
  70. package/dist/helpers/dockerService/serviceComponents/imageUtils.js +1 -1
  71. package/dist/helpers/exitWithMessage.js +10 -10
  72. package/dist/helpers/imageNameUtils.js +32 -0
  73. package/dist/helpers/logger.js +13 -13
  74. package/dist/helpers/safeCall.js +2 -2
  75. package/dist/helpers/validationHelpers.js +76 -21
  76. package/dist/hooks/creation/useContainerActions.js +28 -11
  77. package/dist/hooks/creation/useContainerCreation.js +452 -62
  78. package/dist/hooks/creation/useLogsViewer.js +2 -2
  79. package/dist/hooks/debug/useDebugLogs.js +4 -4
  80. package/dist/hooks/useContainerCommandRouter.js +14 -14
  81. package/dist/hooks/useContainers.js +3 -3
  82. package/dist/hooks/useControls.js +85 -38
  83. package/dist/hooks/useEraseConfirmation.js +3 -3
  84. package/dist/index.js +9 -2
  85. package/jest.config.cjs +7 -0
  86. package/package.json +7 -3
  87. package/src/App.jsx +39 -16
  88. package/src/components/ContainerCreationPrompt.jsx +54 -10
  89. package/src/components/ContainerList.jsx +3 -3
  90. package/src/components/ContainerRow.jsx +19 -16
  91. package/src/components/ContainerSection.jsx +3 -3
  92. package/src/components/ControlsHUD.jsx +66 -0
  93. package/src/components/Footer.jsx +2 -2
  94. package/src/components/Header.jsx +7 -7
  95. package/src/components/LogViewer.jsx +5 -3
  96. package/src/components/MessageFeedback.jsx +2 -2
  97. package/src/components/PromptField.jsx +4 -4
  98. package/src/components/StatsBar.jsx +11 -7
  99. package/src/components/SuggestionPanel.jsx +61 -0
  100. package/src/components/UsageMenu.jsx +18 -13
  101. package/src/helpers/appInfo.js +7 -7
  102. package/src/helpers/constants.js +48 -9
  103. package/src/helpers/containerOptionsBuilder.js +16 -5
  104. package/src/helpers/dockerHubService.js +75 -0
  105. package/src/helpers/dockerService/dockerService.js +1 -1
  106. package/src/helpers/dockerService/serviceComponents/containerActions.js +87 -61
  107. package/src/helpers/dockerService/serviceComponents/containerList.js +31 -25
  108. package/src/helpers/dockerService/serviceComponents/containerLogs.js +41 -26
  109. package/src/helpers/dockerService/serviceComponents/containerStats.js +13 -11
  110. package/src/helpers/dockerService/serviceComponents/imageUtils.js +5 -4
  111. package/src/helpers/exitWithMessage.js +13 -7
  112. package/src/helpers/imageNameUtils.js +32 -0
  113. package/src/helpers/logger.js +15 -16
  114. package/src/helpers/safeCall.js +2 -2
  115. package/src/helpers/validationHelpers.js +13 -29
  116. package/src/hooks/creation/useContainerActions.js +32 -12
  117. package/src/hooks/creation/useContainerCreation.js +293 -47
  118. package/src/hooks/creation/useLogsViewer.js +2 -2
  119. package/src/hooks/debug/useDebugLogs.js +15 -10
  120. package/src/hooks/navigation/useContainerSelection.js +17 -14
  121. package/src/hooks/useContainerCommandRouter.js +71 -58
  122. package/src/hooks/useContainers.js +4 -4
  123. package/src/hooks/useControls.js +135 -72
  124. package/src/hooks/useEraseConfirmation.js +3 -3
  125. package/src/hooks/useExitHandler.js +29 -26
  126. package/src/index.js +8 -4
  127. package/test/ContainerCreationPrompt.dom.test.js +313 -0
  128. package/test/ControlsHUD.dom.test.js +125 -0
  129. package/test/SuggestionPanel.dom.test.js +83 -0
  130. package/test/constants.test.js +41 -0
  131. package/test/dockerHubService.test.js +213 -0
  132. package/test/logger.test.js +58 -0
  133. package/test/resolveImageTag.test.js +26 -0
  134. package/test/useContainerCreation.dom.test.js +428 -2
  135. package/test/useContainerCreationHub.dom.test.js +184 -0
  136. package/test/useControls.dom.test.js +406 -4
@@ -27,4 +27,157 @@ export var EXIT_DELAY = 500;
27
27
  export var TIMEOUTS = {
28
28
  CONTAINER_OP: 30000,
29
29
  PULL_IMAGE: 300000
30
- };
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
+ defaultTag: '1.27-alpine'
49
+ },
50
+ httpd: {
51
+ requiredEnv: [],
52
+ defaultPort: '80',
53
+ suggestedEnv: [],
54
+ defaultTag: '2.4-alpine'
55
+ },
56
+ caddy: {
57
+ requiredEnv: [],
58
+ defaultPort: '80',
59
+ suggestedEnv: [],
60
+ defaultTag: '2-alpine'
61
+ },
62
+ node: {
63
+ requiredEnv: [],
64
+ defaultPort: '3000',
65
+ suggestedEnv: ['NODE_ENV=production'],
66
+ defaultTag: '22-alpine'
67
+ },
68
+ python: {
69
+ requiredEnv: [],
70
+ defaultPort: '8000',
71
+ suggestedEnv: ['PYTHONUNBUFFERED=1'],
72
+ defaultTag: '3.12-slim'
73
+ },
74
+ openjdk: {
75
+ requiredEnv: [],
76
+ defaultPort: '8080',
77
+ suggestedEnv: ['JAVA_OPTS=-Xms256m -Xmx512m'],
78
+ defaultTag: '21-slim'
79
+ },
80
+ golang: {
81
+ requiredEnv: [],
82
+ defaultPort: '8080',
83
+ suggestedEnv: ['GOMAXPROCS=2'],
84
+ defaultTag: '1.22-alpine'
85
+ },
86
+ redis: {
87
+ requiredEnv: [],
88
+ defaultPort: '6379',
89
+ suggestedEnv: ['REDIS_PASSWORD=change-me'],
90
+ defaultTag: '7-alpine'
91
+ },
92
+ memcached: {
93
+ requiredEnv: [],
94
+ defaultPort: '11211',
95
+ suggestedEnv: [],
96
+ defaultTag: '1.6-alpine'
97
+ },
98
+ postgres: {
99
+ requiredEnv: ['POSTGRES_PASSWORD'],
100
+ defaultPort: '5432',
101
+ suggestedEnv: ['POSTGRES_PASSWORD=secret', 'POSTGRES_USER=postgres', 'POSTGRES_DB=app'],
102
+ defaultTag: '17-alpine'
103
+ },
104
+ mysql: {
105
+ requiredEnv: ['MYSQL_ROOT_PASSWORD'],
106
+ defaultPort: '3306',
107
+ suggestedEnv: ['MYSQL_ROOT_PASSWORD=secret', 'MYSQL_DATABASE=app', 'MYSQL_USER=app', 'MYSQL_PASSWORD=app123'],
108
+ defaultTag: '8.0'
109
+ },
110
+ mariadb: {
111
+ requiredEnv: ['MARIADB_ROOT_PASSWORD'],
112
+ defaultPort: '3306',
113
+ suggestedEnv: ['MARIADB_ROOT_PASSWORD=secret', 'MARIADB_DATABASE=app', 'MARIADB_USER=app', 'MARIADB_PASSWORD=app123'],
114
+ defaultTag: '11-alpine'
115
+ },
116
+ mongo: {
117
+ requiredEnv: [],
118
+ defaultPort: '27017',
119
+ suggestedEnv: ['MONGO_INITDB_ROOT_USERNAME=admin', 'MONGO_INITDB_ROOT_PASSWORD=secret'],
120
+ defaultTag: '7.0'
121
+ },
122
+ mssql: {
123
+ requiredEnv: ['ACCEPT_EULA', 'SA_PASSWORD'],
124
+ defaultPort: '1433',
125
+ suggestedEnv: ['ACCEPT_EULA=Y', 'SA_PASSWORD=Strong!Passw0rd', 'MSSQL_PID=Developer'],
126
+ defaultTag: '2022-latest'
127
+ },
128
+ rabbitmq: {
129
+ requiredEnv: [],
130
+ defaultPort: '5672',
131
+ suggestedEnv: ['RABBITMQ_DEFAULT_USER=guest', 'RABBITMQ_DEFAULT_PASS=guest'],
132
+ defaultTag: '3-management-alpine'
133
+ },
134
+ kafka: {
135
+ requiredEnv: [],
136
+ defaultPort: '9092',
137
+ suggestedEnv: ['KAFKA_CFG_LISTENERS=PLAINTEXT://:9092', 'KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092'],
138
+ defaultTag: '3.7'
139
+ },
140
+ zookeeper: {
141
+ requiredEnv: [],
142
+ defaultPort: '2181',
143
+ suggestedEnv: ['ALLOW_ANONYMOUS_LOGIN=yes'],
144
+ defaultTag: '3.9'
145
+ },
146
+ elasticsearch: {
147
+ requiredEnv: [],
148
+ defaultPort: '9200',
149
+ suggestedEnv: ['discovery.type=single-node', 'xpack.security.enabled=false'],
150
+ defaultTag: '8.13.0'
151
+ },
152
+ minio: {
153
+ requiredEnv: ['MINIO_ROOT_USER', 'MINIO_ROOT_PASSWORD'],
154
+ defaultPort: '9000',
155
+ suggestedEnv: ['MINIO_ROOT_USER=admin', 'MINIO_ROOT_PASSWORD=secret', 'MINIO_CONSOLE_ADDRESS=:9001'],
156
+ defaultTag: 'latest'
157
+ },
158
+ wordpress: {
159
+ requiredEnv: ['WORDPRESS_DB_HOST', 'WORDPRESS_DB_USER', 'WORDPRESS_DB_PASSWORD', 'WORDPRESS_DB_NAME'],
160
+ defaultPort: '8080',
161
+ suggestedEnv: ['WORDPRESS_DB_HOST=db', 'WORDPRESS_DB_USER=wp', 'WORDPRESS_DB_PASSWORD=secret', 'WORDPRESS_DB_NAME=wordpress', 'WORDPRESS_TABLE_PREFIX=wp_'],
162
+ defaultTag: '6.5-apache'
163
+ }
164
+ };
165
+
166
+ /**
167
+ * Given an image name (with or without tag), returns the image name with a
168
+ * default tag appended when the image matches a known profile and has no
169
+ * explicit tag. Returns the input unchanged otherwise.
170
+ *
171
+ * @param {string} imageName - e.g. 'postgres', 'postgres:15', 'unknownimage'
172
+ * @param {Record<string, { defaultTag: string }>} profiles
173
+ * @returns {string}
174
+ */
175
+ export function resolveImageTag(imageName) {
176
+ var profiles = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : IMAGE_PROFILES;
177
+ if (!imageName) return imageName;
178
+ if (imageName.includes(':')) return imageName;
179
+ var baseName = imageName.trim().toLowerCase().split('/').pop();
180
+ var profile = profiles[baseName];
181
+ if (!(profile !== null && profile !== void 0 && profile.defaultTag)) return imageName;
182
+ return "".concat(imageName, ":").concat(profile.defaultTag);
183
+ }
@@ -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];
@@ -0,0 +1,111 @@
1
+ function _regenerator() { /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */ var e, t, r = "function" == typeof Symbol ? Symbol : {}, n = r.iterator || "@@iterator", o = r.toStringTag || "@@toStringTag"; function i(r, n, o, i) { var c = n && n.prototype instanceof Generator ? n : Generator, u = Object.create(c.prototype); return _regeneratorDefine2(u, "_invoke", function (r, n, o) { var i, c, u, f = 0, p = o || [], y = !1, G = { p: 0, n: 0, v: e, a: d, f: d.bind(e, 4), d: function d(t, r) { return i = t, c = 0, u = e, G.n = r, a; } }; function d(r, n) { for (c = r, u = n, t = 0; !y && f && !o && t < p.length; t++) { var o, i = p[t], d = G.p, l = i[2]; r > 3 ? (o = l === n) && (u = i[(c = i[4]) ? 5 : (c = 3, 3)], i[4] = i[5] = e) : i[0] <= d && ((o = r < 2 && d < i[1]) ? (c = 0, G.v = n, G.n = i[1]) : d < l && (o = r < 3 || i[0] > n || n > l) && (i[4] = r, i[5] = n, G.n = l, c = 0)); } if (o || r > 1) return a; throw y = !0, n; } return function (o, p, l) { if (f > 1) throw TypeError("Generator is already running"); for (y && 1 === p && d(p, l), c = p, u = l; (t = c < 2 ? e : u) || !y;) { i || (c ? c < 3 ? (c > 1 && (G.n = -1), d(c, u)) : G.n = u : G.v = u); try { if (f = 2, i) { if (c || (o = "next"), t = i[o]) { if (!(t = t.call(i, u))) throw TypeError("iterator result is not an object"); if (!t.done) return t; u = t.value, c < 2 && (c = 0); } else 1 === c && (t = i["return"]) && t.call(i), c < 2 && (u = TypeError("The iterator does not provide a '" + o + "' method"), c = 1); i = e; } else if ((t = (y = G.n < 0) ? u : r.call(n, G)) !== a) break; } catch (t) { i = e, c = 1, u = t; } finally { f = 1; } } return { value: t, done: y }; }; }(r, o, i), !0), u; } var a = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} t = Object.getPrototypeOf; var c = [][n] ? t(t([][n]())) : (_regeneratorDefine2(t = {}, n, function () { return this; }), t), u = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(c); function f(e) { return Object.setPrototypeOf ? Object.setPrototypeOf(e, GeneratorFunctionPrototype) : (e.__proto__ = GeneratorFunctionPrototype, _regeneratorDefine2(e, o, "GeneratorFunction")), e.prototype = Object.create(u), e; } return GeneratorFunction.prototype = GeneratorFunctionPrototype, _regeneratorDefine2(u, "constructor", GeneratorFunctionPrototype), _regeneratorDefine2(GeneratorFunctionPrototype, "constructor", GeneratorFunction), GeneratorFunction.displayName = "GeneratorFunction", _regeneratorDefine2(GeneratorFunctionPrototype, o, "GeneratorFunction"), _regeneratorDefine2(u), _regeneratorDefine2(u, o, "Generator"), _regeneratorDefine2(u, n, function () { return this; }), _regeneratorDefine2(u, "toString", function () { return "[object Generator]"; }), (_regenerator = function _regenerator() { return { w: i, m: f }; })(); }
2
+ function _regeneratorDefine2(e, r, n, t) { var i = Object.defineProperty; try { i({}, "", {}); } catch (e) { i = 0; } _regeneratorDefine2 = function _regeneratorDefine(e, r, n, t) { function o(r, n) { _regeneratorDefine2(e, r, function (e) { return this._invoke(r, n, e); }); } r ? i ? i(e, r, { value: n, enumerable: !t, configurable: !t, writable: !t }) : e[r] = n : (o("next", 0), o("throw", 1), o("return", 2)); }, _regeneratorDefine2(e, r, n, t); }
3
+ function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
4
+ function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
5
+ /**
6
+ * Docker Hub search helper.
7
+ * Pure function — no side effects beyond HTTP. All state management
8
+ * lives in the calling hook (useContainerCreation).
9
+ */
10
+
11
+ var DOCKER_HUB_SEARCH_URL = 'https://hub.docker.com/v2/search/repositories/';
12
+
13
+ /**
14
+ * Format a Docker Hub search result object into a display string.
15
+ *
16
+ * Format rules:
17
+ * - Official: "name [★ official] [Npulls+]" (if pullCount > 0)
18
+ * - Non-official with pulls: "name [Npulls+]"
19
+ * - Otherwise: "name"
20
+ *
21
+ * Pull count suffixes: ≥1B → "1B+", ≥1M → "1M+", ≥1K → "1K+", else raw number
22
+ *
23
+ * @param {{ name: string, isOfficial: boolean, pullCount: number }} result
24
+ * @returns {string}
25
+ */
26
+ export function formatHubResult(result) {
27
+ var name = result.name,
28
+ isOfficial = result.isOfficial,
29
+ pullCount = result.pullCount;
30
+ var pullSuffix = '';
31
+ if (pullCount >= 1e9) {
32
+ pullSuffix = " [1B+ pulls]";
33
+ } else if (pullCount >= 1e6) {
34
+ pullSuffix = " [".concat(Math.floor(pullCount / 1e6), "M+ pulls]");
35
+ } else if (pullCount >= 1e3) {
36
+ pullSuffix = " [".concat(Math.floor(pullCount / 1e3), "K+ pulls]");
37
+ } else if (pullCount > 0) {
38
+ pullSuffix = " [".concat(pullCount, " pulls]");
39
+ }
40
+ if (isOfficial) {
41
+ return "".concat(name, " [\u2605 official]").concat(pullSuffix);
42
+ }
43
+ return "".concat(name).concat(pullSuffix);
44
+ }
45
+
46
+ /**
47
+ * Search Docker Hub for images matching the given query.
48
+ *
49
+ * @param {string} query - Search term (will be trimmed and encoded)
50
+ * @param {Object} [options]
51
+ * @param {AbortSignal} [options.signal] - Abort signal; defaults to AbortSignal.timeout(5000)
52
+ * @returns {Promise<Array<{ name: string, isOfficial: boolean, pullCount: number, description: string }>>}
53
+ * @throws {Error} If response.ok is false or the network call rejects
54
+ */
55
+ export function searchDockerHub(_x) {
56
+ return _searchDockerHub.apply(this, arguments);
57
+ }
58
+ function _searchDockerHub() {
59
+ _searchDockerHub = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(query) {
60
+ var _options$signal, _data$results;
61
+ var options,
62
+ q,
63
+ url,
64
+ signal,
65
+ response,
66
+ data,
67
+ results,
68
+ _args = arguments;
69
+ return _regenerator().w(function (_context) {
70
+ while (1) switch (_context.n) {
71
+ case 0:
72
+ options = _args.length > 1 && _args[1] !== undefined ? _args[1] : {};
73
+ q = query.trim();
74
+ if (q) {
75
+ _context.n = 1;
76
+ break;
77
+ }
78
+ return _context.a(2, []);
79
+ case 1:
80
+ url = "".concat(DOCKER_HUB_SEARCH_URL, "?query=").concat(encodeURIComponent(q), "&page_size=10");
81
+ signal = (_options$signal = options.signal) !== null && _options$signal !== void 0 ? _options$signal : AbortSignal.timeout(5000);
82
+ _context.n = 2;
83
+ return fetch(url, {
84
+ signal: signal
85
+ });
86
+ case 2:
87
+ response = _context.v;
88
+ if (response.ok) {
89
+ _context.n = 3;
90
+ break;
91
+ }
92
+ throw new Error("Docker Hub search failed: ".concat(response.status));
93
+ case 3:
94
+ _context.n = 4;
95
+ return response.json();
96
+ case 4:
97
+ data = _context.v;
98
+ results = (_data$results = data.results) !== null && _data$results !== void 0 ? _data$results : [];
99
+ return _context.a(2, results.map(function (item) {
100
+ return {
101
+ name: item.repo_name,
102
+ isOfficial: item.is_official,
103
+ pullCount: item.pull_count,
104
+ description: item.short_description
105
+ };
106
+ }));
107
+ }
108
+ }, _callee);
109
+ }));
110
+ return _searchDockerHub.apply(this, arguments);
111
+ }
@@ -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
@@ -1,4 +1,10 @@
1
1
  function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
2
+ function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
3
+ function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
4
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
5
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
6
+ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
7
+ function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
2
8
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
3
9
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
4
10
  function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
@@ -8,10 +14,11 @@ function _regenerator() { /*! regenerator-runtime -- Copyright (c) 2014-present,
8
14
  function _regeneratorDefine2(e, r, n, t) { var i = Object.defineProperty; try { i({}, "", {}); } catch (e) { i = 0; } _regeneratorDefine2 = function _regeneratorDefine(e, r, n, t) { function o(r, n) { _regeneratorDefine2(e, r, function (e) { return this._invoke(r, n, e); }); } r ? i ? i(e, r, { value: n, enumerable: !t, configurable: !t, writable: !t }) : e[r] = n : (o("next", 0), o("throw", 1), o("return", 2)); }, _regeneratorDefine2(e, r, n, t); }
9
15
  function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
10
16
  function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
11
- import { docker } from "../dockerService.js";
12
- import { imageExists, pullImage } from "./imageUtils.js";
13
- import { TIMEOUTS } from "../../constants.js";
14
- import { logger } from "../../logger.js";
17
+ import { docker } from '../dockerService.js';
18
+ import { imageExists, pullImage } from './imageUtils.js';
19
+ import { TIMEOUTS, IMAGE_PROFILES } from '../../constants.js';
20
+ import { logger } from '../../logger.js';
21
+ import { normalizeImageName } from '../../imageNameUtils.js';
15
22
 
16
23
  /**
17
24
  * Helper to add timeout to promises. If the provided promise does not settle
@@ -47,7 +54,8 @@ export function removeContainer(_x) {
47
54
  *
48
55
  * @param {string} imageName - Image name (e.g. 'nginx:alpine')
49
56
  * @param {Object} [options] - Docker create options (Env, ExposedPorts, HostConfig, name, etc.)
50
- * @returns {Promise<string>} The created container id
57
+ * @param {Object} [imageProfiles] - Image profiles map for defaultPort fallback
58
+ * @returns {Promise<{id: string, ports: Array<{containerPort: string, hostPort: string, protocol: string, source: string}>}>}
51
59
  * @throws {Error} If image listing/pull or creation fails
52
60
  */
53
61
  function _removeContainer() {
@@ -56,7 +64,7 @@ function _removeContainer() {
56
64
  return _regenerator().w(function (_context) {
57
65
  while (1) switch (_context.p = _context.n) {
58
66
  case 0:
59
- logger.debug("Removing container %s", containerId);
67
+ logger.debug('Removing container %s', containerId);
60
68
  container = docker.getContainer(containerId);
61
69
  _context.p = 1;
62
70
  _context.n = 2;
@@ -64,13 +72,13 @@ function _removeContainer() {
64
72
  force: true
65
73
  }), TIMEOUTS.CONTAINER_OP);
66
74
  case 2:
67
- logger.info("Removed container %s", containerId);
75
+ logger.info('Removed container %s', containerId);
68
76
  _context.n = 4;
69
77
  break;
70
78
  case 3:
71
79
  _context.p = 3;
72
80
  _t = _context.v;
73
- logger.error("Failed to remove container %s", containerId, _t);
81
+ logger.error('Failed to remove container %s', containerId, _t);
74
82
  throw new Error('Error removing container: ' + _t.message);
75
83
  case 4:
76
84
  return _context.a(2);
@@ -91,9 +99,11 @@ export function createContainer(_x2) {
91
99
  function _createContainer() {
92
100
  _createContainer = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2(imageName) {
93
101
  var options,
102
+ imageProfiles,
94
103
  exists,
95
104
  createOpts,
96
105
  hasUserDefinedPorts,
106
+ assignedPorts,
97
107
  image,
98
108
  _inspectData$Config,
99
109
  _inspectData$Containe,
@@ -104,6 +114,11 @@ function _createContainer() {
104
114
  inspectData,
105
115
  exposed,
106
116
  exposedKeys,
117
+ baseName,
118
+ profile,
119
+ fallbackKey,
120
+ _createOpts$HostConfi,
121
+ portBindings,
107
122
  container,
108
123
  containerId,
109
124
  _args2 = arguments,
@@ -116,7 +131,8 @@ function _createContainer() {
116
131
  while (1) switch (_context2.p = _context2.n) {
117
132
  case 0:
118
133
  options = _args2.length > 1 && _args2[1] !== undefined ? _args2[1] : {};
119
- logger.info("Creating container from image %s", imageName);
134
+ imageProfiles = _args2.length > 2 && _args2[2] !== undefined ? _args2[2] : IMAGE_PROFILES;
135
+ logger.info('Creating container from image %s', imageName);
120
136
  _context2.p = 1;
121
137
  _context2.n = 2;
122
138
  return withTimeout(imageExists(imageName), 10000);
@@ -127,25 +143,25 @@ function _createContainer() {
127
143
  case 3:
128
144
  _context2.p = 3;
129
145
  _t2 = _context2.v;
130
- logger.error("Could not check local images for %s", imageName, _t2);
146
+ logger.error('Could not check local images for %s', imageName, _t2);
131
147
  throw new Error('Error listing local images: ' + _t2.message);
132
148
  case 4:
133
149
  if (exists) {
134
150
  _context2.n = 8;
135
151
  break;
136
152
  }
137
- logger.info("Image %s not found locally, pulling", imageName);
153
+ logger.info('Image %s not found locally, pulling', imageName);
138
154
  _context2.p = 5;
139
155
  _context2.n = 6;
140
156
  return withTimeout(pullImage(imageName), TIMEOUTS.PULL_IMAGE);
141
157
  case 6:
142
- logger.info("Pulled image %s", imageName);
158
+ logger.info('Pulled image %s', imageName);
143
159
  _context2.n = 8;
144
160
  break;
145
161
  case 7:
146
162
  _context2.p = 7;
147
163
  _t3 = _context2.v;
148
- logger.error("Failed to pull image %s", imageName, _t3);
164
+ logger.error('Failed to pull image %s', imageName, _t3);
149
165
  throw new Error('Could not pull image: ' + _t3.message);
150
166
  case 8:
151
167
  createOpts = _objectSpread({
@@ -153,18 +169,20 @@ function _createContainer() {
153
169
  Tty: true
154
170
  }, options);
155
171
  hasUserDefinedPorts = Boolean(createOpts.ExposedPorts && Object.keys(createOpts.ExposedPorts).length);
172
+ /** @type {Array<{containerPort: string, hostPort: string, protocol: string, source: string}>} */
173
+ assignedPorts = [];
156
174
  if (hasUserDefinedPorts) {
157
- _context2.n = 17;
175
+ _context2.n = 18;
158
176
  break;
159
177
  }
160
178
  _context2.p = 9;
161
179
  image = docker.getImage(imageName);
162
- if (!(image && typeof image.inspect === "function")) {
180
+ if (!(image && typeof image.inspect === 'function')) {
163
181
  _context2.n = 15;
164
182
  break;
165
183
  }
166
184
  usedHostPorts = new Set();
167
- if (!(typeof docker.listContainers === "function")) {
185
+ if (!(typeof docker.listContainers === 'function')) {
168
186
  _context2.n = 13;
169
187
  break;
170
188
  }
@@ -187,7 +205,7 @@ function _createContainer() {
187
205
  case 12:
188
206
  _context2.p = 12;
189
207
  _t4 = _context2.v;
190
- logger.warn("Could not inspect existing containers to auto-map ports", _t4);
208
+ logger.warn('Could not inspect existing containers to auto-map ports', _t4);
191
209
  case 13:
192
210
  reservePort = function reservePort(port) {
193
211
  var portStr = String(port);
@@ -196,7 +214,6 @@ function _createContainer() {
196
214
  pickNextAvailablePort = function pickNextAvailablePort(base) {
197
215
  var numericBase = Number.parseInt(base, 10);
198
216
  if (Number.isNaN(numericBase)) {
199
- // If the base port is non-numeric, just return the base.
200
217
  if (!usedHostPorts.has(base)) {
201
218
  usedHostPorts.add(base);
202
219
  return base;
@@ -222,7 +239,16 @@ function _createContainer() {
222
239
  case 14:
223
240
  inspectData = _context2.v;
224
241
  exposed = (inspectData === null || inspectData === void 0 || (_inspectData$Config = inspectData.Config) === null || _inspectData$Config === void 0 ? void 0 : _inspectData$Config.ExposedPorts) || (inspectData === null || inspectData === void 0 || (_inspectData$Containe = inspectData.ContainerConfig) === null || _inspectData$Containe === void 0 ? void 0 : _inspectData$Containe.ExposedPorts) || {};
225
- exposedKeys = Object.keys(exposed || {});
242
+ exposedKeys = Object.keys(exposed || {}); // If no ExposedPorts in image, fall back to IMAGE_PROFILES defaultPort
243
+ if (!exposedKeys.length) {
244
+ baseName = normalizeImageName(imageName);
245
+ profile = imageProfiles[baseName];
246
+ if (profile && profile.defaultPort) {
247
+ fallbackKey = "".concat(profile.defaultPort, "/tcp");
248
+ exposed = _defineProperty({}, fallbackKey, {});
249
+ exposedKeys = [fallbackKey];
250
+ }
251
+ }
226
252
  if (exposedKeys.length) {
227
253
  createOpts.ExposedPorts = createOpts.ExposedPorts || {};
228
254
  createOpts.HostConfig = _objectSpread({}, createOpts.HostConfig || {});
@@ -232,11 +258,19 @@ function _createContainer() {
232
258
  createOpts.ExposedPorts[portKey] = exposed[portKey] || {};
233
259
  }
234
260
  if (!createOpts.HostConfig.PortBindings[portKey] || !createOpts.HostConfig.PortBindings[portKey].length) {
235
- var containerPort = portKey.split("/")[0];
261
+ var parts = portKey.split('/');
262
+ var containerPort = parts[0];
263
+ var protocol = parts[1] || 'tcp';
236
264
  var hostPort = pickNextAvailablePort(containerPort);
237
265
  createOpts.HostConfig.PortBindings[portKey] = [{
238
266
  HostPort: hostPort
239
267
  }];
268
+ assignedPorts.push({
269
+ containerPort: containerPort,
270
+ hostPort: hostPort,
271
+ protocol: protocol,
272
+ source: 'auto'
273
+ });
240
274
  }
241
275
  });
242
276
  }
@@ -246,25 +280,50 @@ function _createContainer() {
246
280
  case 16:
247
281
  _context2.p = 16;
248
282
  _t5 = _context2.v;
249
- logger.warn("Could not inspect image %s for default ports", imageName, _t5);
283
+ logger.warn('Could not inspect image %s for default ports', imageName, _t5);
250
284
  case 17:
251
- _context2.p = 17;
252
- _context2.n = 18;
253
- return withTimeout(docker.createContainer(createOpts), TIMEOUTS.CONTAINER_OP);
285
+ _context2.n = 19;
286
+ break;
254
287
  case 18:
255
- container = _context2.v;
256
- containerId = container.id || container.Id;
257
- logger.info("Created container %s from image %s", containerId, imageName);
258
- return _context2.a(2, container.id || container.Id);
288
+ // User-defined ports: collect them with source: "user"
289
+ portBindings = ((_createOpts$HostConfi = createOpts.HostConfig) === null || _createOpts$HostConfi === void 0 ? void 0 : _createOpts$HostConfi.PortBindings) || {};
290
+ Object.entries(portBindings).forEach(function (_ref) {
291
+ var _ref2 = _slicedToArray(_ref, 2),
292
+ portKey = _ref2[0],
293
+ bindings = _ref2[1];
294
+ var parts = portKey.split('/');
295
+ var containerPort = parts[0];
296
+ var protocol = parts[1] || 'tcp';
297
+ (bindings || []).forEach(function (b) {
298
+ assignedPorts.push({
299
+ containerPort: containerPort,
300
+ hostPort: b.HostPort,
301
+ protocol: protocol,
302
+ source: 'user'
303
+ });
304
+ });
305
+ });
259
306
  case 19:
260
307
  _context2.p = 19;
308
+ _context2.n = 20;
309
+ return withTimeout(docker.createContainer(createOpts), TIMEOUTS.CONTAINER_OP);
310
+ case 20:
311
+ container = _context2.v;
312
+ containerId = container.id || container.Id;
313
+ logger.info('Created container %s from image %s', containerId, imageName);
314
+ return _context2.a(2, {
315
+ id: containerId,
316
+ ports: assignedPorts
317
+ });
318
+ case 21:
319
+ _context2.p = 21;
261
320
  _t6 = _context2.v;
262
- logger.error("Failed to create container from image %s", imageName, _t6);
321
+ logger.error('Failed to create container from image %s', imageName, _t6);
263
322
  throw new Error('Error creating container: ' + _t6.message);
264
- case 20:
323
+ case 22:
265
324
  return _context2.a(2);
266
325
  }
267
- }, _callee2, null, [[17, 19], [10, 12], [9, 16], [5, 7], [1, 3]]);
326
+ }, _callee2, null, [[19, 21], [10, 12], [9, 16], [5, 7], [1, 3]]);
268
327
  }));
269
328
  return _createContainer.apply(this, arguments);
270
329
  }
@@ -284,18 +343,18 @@ function _startContainer() {
284
343
  while (1) switch (_context3.p = _context3.n) {
285
344
  case 0:
286
345
  container = docker.getContainer(containerId);
287
- logger.debug("Starting container %s", containerId);
346
+ logger.debug('Starting container %s', containerId);
288
347
  _context3.p = 1;
289
348
  _context3.n = 2;
290
349
  return withTimeout(container.start(), TIMEOUTS.CONTAINER_OP);
291
350
  case 2:
292
- logger.info("Started container %s", containerId);
351
+ logger.info('Started container %s', containerId);
293
352
  _context3.n = 4;
294
353
  break;
295
354
  case 3:
296
355
  _context3.p = 3;
297
356
  _t7 = _context3.v;
298
- logger.error("Failed to start container %s", containerId, _t7);
357
+ logger.error('Failed to start container %s', containerId, _t7);
299
358
  throw _t7;
300
359
  case 4:
301
360
  return _context3.a(2);
@@ -320,18 +379,18 @@ function _stopContainer() {
320
379
  while (1) switch (_context4.p = _context4.n) {
321
380
  case 0:
322
381
  container = docker.getContainer(containerId);
323
- logger.debug("Stopping container %s", containerId);
382
+ logger.debug('Stopping container %s', containerId);
324
383
  _context4.p = 1;
325
384
  _context4.n = 2;
326
385
  return withTimeout(container.stop(), TIMEOUTS.CONTAINER_OP);
327
386
  case 2:
328
- logger.info("Stopped container %s", containerId);
387
+ logger.info('Stopped container %s', containerId);
329
388
  _context4.n = 4;
330
389
  break;
331
390
  case 3:
332
391
  _context4.p = 3;
333
392
  _t8 = _context4.v;
334
- logger.error("Failed to stop container %s", containerId, _t8);
393
+ logger.error('Failed to stop container %s', containerId, _t8);
335
394
  throw _t8;
336
395
  case 4:
337
396
  return _context4.a(2);
@@ -350,18 +409,18 @@ function _restartContainer() {
350
409
  while (1) switch (_context5.p = _context5.n) {
351
410
  case 0:
352
411
  container = docker.getContainer(containerId);
353
- logger.debug("Restarting container %s", containerId);
412
+ logger.debug('Restarting container %s', containerId);
354
413
  _context5.p = 1;
355
414
  _context5.n = 2;
356
415
  return withTimeout(container.restart(), TIMEOUTS.CONTAINER_OP);
357
416
  case 2:
358
- logger.info("Restarted container %s", containerId);
417
+ logger.info('Restarted container %s', containerId);
359
418
  _context5.n = 4;
360
419
  break;
361
420
  case 3:
362
421
  _context5.p = 3;
363
422
  _t9 = _context5.v;
364
- logger.error("Failed to restart container %s", containerId, _t9);
423
+ logger.error('Failed to restart container %s', containerId, _t9);
365
424
  throw _t9;
366
425
  case 4:
367
426
  return _context5.a(2);