cdd-cli 3.2.2 → 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 (126) hide show
  1. package/README.es.md +222 -0
  2. package/README.md +108 -26
  3. package/dist/App.js +7 -1
  4. package/dist/components/ContainerCreationPrompt.js +26 -10
  5. package/dist/components/ControlsHUD.js +106 -0
  6. package/dist/components/SuggestionPanel.js +16 -7
  7. package/dist/components/UsageMenu.js +1 -1
  8. package/dist/helpers/appState.js +26 -0
  9. package/dist/helpers/constants.js +60 -21
  10. package/dist/helpers/dockerHubService.js +111 -0
  11. package/dist/helpers/dockerService/serviceComponents/containerExec.js +211 -0
  12. package/dist/helpers/logger.js +1 -1
  13. package/dist/hooks/creation/useContainerActions.js +22 -5
  14. package/dist/hooks/creation/useContainerCreation.js +414 -107
  15. package/dist/hooks/useContainerCommandRouter.js +13 -3
  16. package/dist/hooks/useControls.js +28 -7
  17. package/dist/hooks/useShellMode.js +101 -0
  18. package/dist/index.js +10 -1
  19. package/package.json +4 -1
  20. package/.atl/skill-registry.md +0 -30
  21. package/.editorconfig +0 -12
  22. package/.eslintrc.json +0 -30
  23. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -30
  24. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
  25. package/.github/PULL_REQUEST_TEMPLATE/pull_request.md +0 -14
  26. package/.github/workflows/ci.yml +0 -32
  27. package/.github/workflows/docs.yml +0 -42
  28. package/.prettierignore +0 -4
  29. package/.prettierrc +0 -8
  30. package/CHANGELOG.md +0 -165
  31. package/CODE_OF_CONDUCT.md +0 -12
  32. package/CONTRIBUTING.md +0 -35
  33. package/SECURITY.md +0 -5
  34. package/__mocks__/ink.cjs +0 -17
  35. package/_config.yml +0 -13
  36. package/babel.config.cjs +0 -23
  37. package/coverage/clover.xml +0 -694
  38. package/coverage/coverage-final.json +0 -21
  39. package/coverage/lcov-report/base.css +0 -224
  40. package/coverage/lcov-report/block-navigation.js +0 -87
  41. package/coverage/lcov-report/components/ContainerCreationPrompt.jsx.html +0 -376
  42. package/coverage/lcov-report/components/PromptField.jsx.html +0 -229
  43. package/coverage/lcov-report/components/SuggestionPanel.jsx.html +0 -244
  44. package/coverage/lcov-report/components/index.html +0 -146
  45. package/coverage/lcov-report/favicon.png +0 -0
  46. package/coverage/lcov-report/helpers/constants.js.html +0 -298
  47. package/coverage/lcov-report/helpers/containerOptionsBuilder.js.html +0 -244
  48. package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerActions.js.html +0 -931
  49. package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerList.js.html +0 -307
  50. package/coverage/lcov-report/helpers/dockerService/serviceComponents/index.html +0 -131
  51. package/coverage/lcov-report/helpers/imageNameUtils.js.html +0 -181
  52. package/coverage/lcov-report/helpers/index.html +0 -191
  53. package/coverage/lcov-report/helpers/logger.js.html +0 -424
  54. package/coverage/lcov-report/helpers/safeCall.js.html +0 -139
  55. package/coverage/lcov-report/helpers/validationHelpers.js.html +0 -346
  56. package/coverage/lcov-report/hooks/creation/index.html +0 -146
  57. package/coverage/lcov-report/hooks/creation/useContainerActions.js.html +0 -313
  58. package/coverage/lcov-report/hooks/creation/useContainerCreation.js.html +0 -805
  59. package/coverage/lcov-report/hooks/creation/useLogsViewer.js.html +0 -238
  60. package/coverage/lcov-report/hooks/debug/index.html +0 -116
  61. package/coverage/lcov-report/hooks/debug/useDebugLogs.js.html +0 -187
  62. package/coverage/lcov-report/hooks/index.html +0 -161
  63. package/coverage/lcov-report/hooks/navigation/index.html +0 -116
  64. package/coverage/lcov-report/hooks/navigation/useContainerSelection.js.html +0 -169
  65. package/coverage/lcov-report/hooks/useContainerCommandRouter.js.html +0 -454
  66. package/coverage/lcov-report/hooks/useControls.js.html +0 -826
  67. package/coverage/lcov-report/hooks/useEraseConfirmation.js.html +0 -202
  68. package/coverage/lcov-report/hooks/useExitHandler.js.html +0 -292
  69. package/coverage/lcov-report/index.html +0 -206
  70. package/coverage/lcov-report/prettify.css +0 -1
  71. package/coverage/lcov-report/prettify.js +0 -2
  72. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  73. package/coverage/lcov-report/sorter.js +0 -210
  74. package/coverage/lcov.info +0 -1402
  75. package/fix-imports.cjs +0 -54
  76. package/jest.config.cjs +0 -14
  77. package/src/App.jsx +0 -96
  78. package/src/components/ContainerCreationPrompt.jsx +0 -97
  79. package/src/components/ContainerList.jsx +0 -32
  80. package/src/components/ContainerRow.jsx +0 -84
  81. package/src/components/ContainerSection.jsx +0 -24
  82. package/src/components/Footer.jsx +0 -15
  83. package/src/components/Header.jsx +0 -41
  84. package/src/components/LogViewer.jsx +0 -36
  85. package/src/components/MessageFeedback.jsx +0 -25
  86. package/src/components/PromptField.jsx +0 -48
  87. package/src/components/StatsBar.jsx +0 -70
  88. package/src/components/SuggestionPanel.jsx +0 -53
  89. package/src/components/UsageMenu.jsx +0 -52
  90. package/src/helpers/appInfo.js +0 -23
  91. package/src/helpers/constants.js +0 -71
  92. package/src/helpers/containerOptionsBuilder.js +0 -53
  93. package/src/helpers/dockerService/dockerService.js +0 -28
  94. package/src/helpers/dockerService/serviceComponents/containerActions.js +0 -282
  95. package/src/helpers/dockerService/serviceComponents/containerList.js +0 -74
  96. package/src/helpers/dockerService/serviceComponents/containerLogs.js +0 -60
  97. package/src/helpers/dockerService/serviceComponents/containerStats.js +0 -69
  98. package/src/helpers/dockerService/serviceComponents/imageUtils.js +0 -32
  99. package/src/helpers/exitWithMessage.js +0 -32
  100. package/src/helpers/imageNameUtils.js +0 -32
  101. package/src/helpers/logger.js +0 -113
  102. package/src/helpers/safeCall.js +0 -18
  103. package/src/helpers/validationHelpers.js +0 -87
  104. package/src/hooks/creation/useContainerActions.js +0 -76
  105. package/src/hooks/creation/useContainerCreation.js +0 -240
  106. package/src/hooks/creation/useLogsViewer.js +0 -51
  107. package/src/hooks/debug/useDebugLogs.js +0 -34
  108. package/src/hooks/navigation/useContainerSelection.js +0 -28
  109. package/src/hooks/useContainerCommandRouter.js +0 -123
  110. package/src/hooks/useContainerStats.js +0 -44
  111. package/src/hooks/useContainers.js +0 -26
  112. package/src/hooks/useControls.js +0 -247
  113. package/src/hooks/useEraseConfirmation.js +0 -39
  114. package/src/hooks/useExitHandler.js +0 -69
  115. package/src/index.js +0 -16
  116. package/test/ContainerCreationPrompt.dom.test.js +0 -68
  117. package/test/SuggestionPanel.dom.test.js +0 -53
  118. package/test/constants.test.js +0 -34
  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/jest.setup.js +0 -7
  123. package/test/safeCall.test.js +0 -25
  124. package/test/useContainerCreation.dom.test.js +0 -264
  125. package/test/useControls.dom.test.js +0 -313
  126. package/test/validationHelpers.test.js +0 -111
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Shared reference to the Ink render instance.
3
+ * Set once in index.js after render(<App />) and read by useShellMode
4
+ * to unmount/remount the UI around interactive shell sessions.
5
+ *
6
+ * @module appState
7
+ */
8
+
9
+ /** @type {import('ink').RenderReturnType | null} */
10
+ var inkApp = null;
11
+
12
+ /**
13
+ * Store the Ink render return value.
14
+ * @param {import('ink').RenderReturnType} app
15
+ */
16
+ export function setInkApp(app) {
17
+ inkApp = app;
18
+ }
19
+
20
+ /**
21
+ * Get the Ink render return value.
22
+ * @returns {import('ink').RenderReturnType | null}
23
+ */
24
+ export function getInkApp() {
25
+ return inkApp;
26
+ }
@@ -44,101 +44,140 @@ export var IMAGE_PROFILES = {
44
44
  nginx: {
45
45
  requiredEnv: [],
46
46
  defaultPort: '80',
47
- suggestedEnv: []
47
+ suggestedEnv: [],
48
+ defaultTag: '1.27-alpine'
48
49
  },
49
50
  httpd: {
50
51
  requiredEnv: [],
51
52
  defaultPort: '80',
52
- suggestedEnv: []
53
+ suggestedEnv: [],
54
+ defaultTag: '2.4-alpine'
53
55
  },
54
56
  caddy: {
55
57
  requiredEnv: [],
56
58
  defaultPort: '80',
57
- suggestedEnv: []
59
+ suggestedEnv: [],
60
+ defaultTag: '2-alpine'
58
61
  },
59
62
  node: {
60
63
  requiredEnv: [],
61
64
  defaultPort: '3000',
62
- suggestedEnv: ['NODE_ENV=production']
65
+ suggestedEnv: ['NODE_ENV=production'],
66
+ defaultTag: '22-alpine'
63
67
  },
64
68
  python: {
65
69
  requiredEnv: [],
66
70
  defaultPort: '8000',
67
- suggestedEnv: ['PYTHONUNBUFFERED=1']
71
+ suggestedEnv: ['PYTHONUNBUFFERED=1'],
72
+ defaultTag: '3.12-slim'
68
73
  },
69
74
  openjdk: {
70
75
  requiredEnv: [],
71
76
  defaultPort: '8080',
72
- suggestedEnv: ['JAVA_OPTS=-Xms256m -Xmx512m']
77
+ suggestedEnv: ['JAVA_OPTS=-Xms256m -Xmx512m'],
78
+ defaultTag: '21-slim'
73
79
  },
74
80
  golang: {
75
81
  requiredEnv: [],
76
82
  defaultPort: '8080',
77
- suggestedEnv: ['GOMAXPROCS=2']
83
+ suggestedEnv: ['GOMAXPROCS=2'],
84
+ defaultTag: '1.22-alpine'
78
85
  },
79
86
  redis: {
80
87
  requiredEnv: [],
81
88
  defaultPort: '6379',
82
- suggestedEnv: ['REDIS_PASSWORD=change-me']
89
+ suggestedEnv: ['REDIS_PASSWORD=change-me'],
90
+ defaultTag: '7-alpine'
83
91
  },
84
92
  memcached: {
85
93
  requiredEnv: [],
86
94
  defaultPort: '11211',
87
- suggestedEnv: []
95
+ suggestedEnv: [],
96
+ defaultTag: '1.6-alpine'
88
97
  },
89
98
  postgres: {
90
99
  requiredEnv: ['POSTGRES_PASSWORD'],
91
100
  defaultPort: '5432',
92
- suggestedEnv: ['POSTGRES_USER=postgres', 'POSTGRES_DB=app']
101
+ suggestedEnv: ['POSTGRES_PASSWORD=secret', 'POSTGRES_USER=postgres', 'POSTGRES_DB=app'],
102
+ defaultTag: '17-alpine'
93
103
  },
94
104
  mysql: {
95
105
  requiredEnv: ['MYSQL_ROOT_PASSWORD'],
96
106
  defaultPort: '3306',
97
- suggestedEnv: ['MYSQL_DATABASE=app', 'MYSQL_USER=app', 'MYSQL_PASSWORD=app123']
107
+ suggestedEnv: ['MYSQL_ROOT_PASSWORD=secret', 'MYSQL_DATABASE=app', 'MYSQL_USER=app', 'MYSQL_PASSWORD=app123'],
108
+ defaultTag: '8.0'
98
109
  },
99
110
  mariadb: {
100
111
  requiredEnv: ['MARIADB_ROOT_PASSWORD'],
101
112
  defaultPort: '3306',
102
- suggestedEnv: ['MARIADB_DATABASE=app', 'MARIADB_USER=app', 'MARIADB_PASSWORD=app123']
113
+ suggestedEnv: ['MARIADB_ROOT_PASSWORD=secret', 'MARIADB_DATABASE=app', 'MARIADB_USER=app', 'MARIADB_PASSWORD=app123'],
114
+ defaultTag: '11-alpine'
103
115
  },
104
116
  mongo: {
105
117
  requiredEnv: [],
106
118
  defaultPort: '27017',
107
- suggestedEnv: ['MONGO_INITDB_ROOT_USERNAME=admin', 'MONGO_INITDB_ROOT_PASSWORD=secret']
119
+ suggestedEnv: ['MONGO_INITDB_ROOT_USERNAME=admin', 'MONGO_INITDB_ROOT_PASSWORD=secret'],
120
+ defaultTag: '7.0'
108
121
  },
109
122
  mssql: {
110
123
  requiredEnv: ['ACCEPT_EULA', 'SA_PASSWORD'],
111
124
  defaultPort: '1433',
112
- suggestedEnv: ['ACCEPT_EULA=Y', 'MSSQL_PID=Developer']
125
+ suggestedEnv: ['ACCEPT_EULA=Y', 'SA_PASSWORD=Strong!Passw0rd', 'MSSQL_PID=Developer'],
126
+ defaultTag: '2022-latest'
113
127
  },
114
128
  rabbitmq: {
115
129
  requiredEnv: [],
116
130
  defaultPort: '5672',
117
- suggestedEnv: ['RABBITMQ_DEFAULT_USER=guest', 'RABBITMQ_DEFAULT_PASS=guest']
131
+ suggestedEnv: ['RABBITMQ_DEFAULT_USER=guest', 'RABBITMQ_DEFAULT_PASS=guest'],
132
+ defaultTag: '3-management-alpine'
118
133
  },
119
134
  kafka: {
120
135
  requiredEnv: [],
121
136
  defaultPort: '9092',
122
- suggestedEnv: ['KAFKA_CFG_LISTENERS=PLAINTEXT://:9092']
137
+ suggestedEnv: ['KAFKA_CFG_LISTENERS=PLAINTEXT://:9092', 'KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092'],
138
+ defaultTag: '3.7'
123
139
  },
124
140
  zookeeper: {
125
141
  requiredEnv: [],
126
142
  defaultPort: '2181',
127
- suggestedEnv: ['ALLOW_ANONYMOUS_LOGIN=yes']
143
+ suggestedEnv: ['ALLOW_ANONYMOUS_LOGIN=yes'],
144
+ defaultTag: '3.9'
128
145
  },
129
146
  elasticsearch: {
130
147
  requiredEnv: [],
131
148
  defaultPort: '9200',
132
- suggestedEnv: ['discovery.type=single-node', 'xpack.security.enabled=false']
149
+ suggestedEnv: ['discovery.type=single-node', 'xpack.security.enabled=false'],
150
+ defaultTag: '8.13.0'
133
151
  },
134
152
  minio: {
135
153
  requiredEnv: ['MINIO_ROOT_USER', 'MINIO_ROOT_PASSWORD'],
136
154
  defaultPort: '9000',
137
- suggestedEnv: ['MINIO_CONSOLE_ADDRESS=:9001']
155
+ suggestedEnv: ['MINIO_ROOT_USER=admin', 'MINIO_ROOT_PASSWORD=secret', 'MINIO_CONSOLE_ADDRESS=:9001'],
156
+ defaultTag: 'latest'
138
157
  },
139
158
  wordpress: {
140
159
  requiredEnv: ['WORDPRESS_DB_HOST', 'WORDPRESS_DB_USER', 'WORDPRESS_DB_PASSWORD', 'WORDPRESS_DB_NAME'],
141
160
  defaultPort: '8080',
142
- suggestedEnv: ['WORDPRESS_TABLE_PREFIX=wp_']
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'
143
163
  }
144
- };
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
+ }
@@ -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
+ }
@@ -0,0 +1,211 @@
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
+ import { spawn } from 'child_process';
6
+ import { docker } from '../dockerService.js';
7
+ import { logger } from '../../logger.js';
8
+ import { TIMEOUTS } from '../../constants.js';
9
+
10
+ /**
11
+ * Detect the default shell available inside a running container.
12
+ * Tries `bash` first, falls back to `sh`.
13
+ *
14
+ * @param {string} containerId - Docker container id
15
+ * @returns {Promise<string>} The shell command (e.g. 'bash' or 'sh')
16
+ */
17
+ export function detectShell(_x) {
18
+ return _detectShell.apply(this, arguments);
19
+ }
20
+
21
+ /**
22
+ * Spawn an interactive shell inside a running container using `docker exec -it`.
23
+ * Returns a promise that resolves when the user exits the shell.
24
+ *
25
+ * @param {string} containerId - Docker container id
26
+ * @param {Object} [options]
27
+ * @param {string} [options.shell] - Shell to use (auto-detected if omitted)
28
+ * @param {string} [options.workingDir] - Working directory inside the container
29
+ * @returns {Promise<{code: number|null, signal: string|null}>} Exit info
30
+ */
31
+ function _detectShell() {
32
+ _detectShell = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(containerId) {
33
+ var container, tryShell, hasBash, _t;
34
+ return _regenerator().w(function (_context) {
35
+ while (1) switch (_context.p = _context.n) {
36
+ case 0:
37
+ container = docker.getContainer(containerId);
38
+ tryShell = function tryShell(shell) {
39
+ return new Promise(function (resolve) {
40
+ container.exec({
41
+ Cmd: ['which', shell],
42
+ AttachStdout: true,
43
+ AttachStderr: false,
44
+ Tty: false
45
+ }, function (err, exec) {
46
+ if (err) {
47
+ resolve(false);
48
+ return;
49
+ }
50
+ exec.start({
51
+ Tty: false
52
+ }, function (startErr, stream) {
53
+ if (startErr) {
54
+ resolve(false);
55
+ return;
56
+ }
57
+ var output = '';
58
+ stream.on('data', function (chunk) {
59
+ output += chunk.toString();
60
+ });
61
+ stream.on('end', function () {
62
+ resolve(output.trim().length > 0);
63
+ });
64
+ stream.on('error', function () {
65
+ return resolve(false);
66
+ });
67
+ });
68
+ });
69
+ });
70
+ };
71
+ _context.p = 1;
72
+ _context.n = 2;
73
+ return Promise.race([tryShell('bash'), new Promise(function (r) {
74
+ return setTimeout(function () {
75
+ return r(false);
76
+ }, TIMEOUTS.CONTAINER_OP);
77
+ })]);
78
+ case 2:
79
+ hasBash = _context.v;
80
+ if (!hasBash) {
81
+ _context.n = 3;
82
+ break;
83
+ }
84
+ logger.debug('Detected bash in container %s', containerId);
85
+ return _context.a(2, 'bash');
86
+ case 3:
87
+ _context.n = 5;
88
+ break;
89
+ case 4:
90
+ _context.p = 4;
91
+ _t = _context.v;
92
+ case 5:
93
+ logger.debug('Falling back to sh in container %s', containerId);
94
+ return _context.a(2, 'sh');
95
+ }
96
+ }, _callee, null, [[1, 4]]);
97
+ }));
98
+ return _detectShell.apply(this, arguments);
99
+ }
100
+ export function execInteractive(_x2) {
101
+ return _execInteractive.apply(this, arguments);
102
+ }
103
+
104
+ /**
105
+ * Execute a non-interactive command inside a running container.
106
+ * Returns stdout and stderr as strings.
107
+ *
108
+ * @param {string} containerId - Docker container id
109
+ * @param {string[]} cmd - Command to execute (e.g. ['ls', '-la'])
110
+ * @param {Object} [options]
111
+ * @param {number} [options.timeout] - Timeout in ms
112
+ * @returns {Promise<{stdout: string, stderr: string, exitCode: number}>}
113
+ */
114
+ function _execInteractive() {
115
+ _execInteractive = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2(containerId) {
116
+ var options,
117
+ shell,
118
+ args,
119
+ _args2 = arguments,
120
+ _t2;
121
+ return _regenerator().w(function (_context2) {
122
+ while (1) switch (_context2.n) {
123
+ case 0:
124
+ options = _args2.length > 1 && _args2[1] !== undefined ? _args2[1] : {};
125
+ _t2 = options.shell;
126
+ if (_t2) {
127
+ _context2.n = 2;
128
+ break;
129
+ }
130
+ _context2.n = 1;
131
+ return detectShell(containerId);
132
+ case 1:
133
+ _t2 = _context2.v;
134
+ case 2:
135
+ shell = _t2;
136
+ args = ['exec', '-it', containerId, shell];
137
+ if (options.workingDir) {
138
+ args.splice(3, 0, '-w', options.workingDir);
139
+ }
140
+ logger.info('Opening interactive shell in container %s (shell=%s)', containerId, shell);
141
+ return _context2.a(2, new Promise(function (resolve, reject) {
142
+ var child = spawn('docker', args, {
143
+ stdio: 'inherit',
144
+ shell: false
145
+ });
146
+ child.on('error', function (err) {
147
+ logger.error('Failed to spawn docker exec', err);
148
+ reject(new Error("Failed to open shell: ".concat(err.message)));
149
+ });
150
+ child.on('close', function (code, signal) {
151
+ logger.info('Interactive shell exited (code=%s, signal=%s)', code, signal);
152
+ resolve({
153
+ code: code,
154
+ signal: signal
155
+ });
156
+ });
157
+ }));
158
+ }
159
+ }, _callee2);
160
+ }));
161
+ return _execInteractive.apply(this, arguments);
162
+ }
163
+ export function execCommand(containerId, cmd) {
164
+ var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
165
+ return new Promise(function (resolve, reject) {
166
+ var container = docker.getContainer(containerId);
167
+ container.exec({
168
+ Cmd: cmd,
169
+ AttachStdout: true,
170
+ AttachStderr: true,
171
+ Tty: false
172
+ }, function (err, exec) {
173
+ if (err) {
174
+ return reject(new Error("Exec create failed: ".concat(err.message)));
175
+ }
176
+ exec.start({
177
+ Tty: false
178
+ }, function (startErr, stream) {
179
+ if (startErr) {
180
+ return reject(new Error("Exec start failed: ".concat(startErr.message)));
181
+ }
182
+ var stdout = '';
183
+ var stderr = '';
184
+ stream.on('data', function (chunk) {
185
+ var str = chunk.toString();
186
+ // Docker multiplexed streams use 8-byte headers.
187
+ // For Tty=false, output is multiplexed; for simplicity we accumulate.
188
+ stdout += str;
189
+ });
190
+ stream.on('end', function () {
191
+ exec.inspect(function (inspectErr, info) {
192
+ var _info$ExitCode;
193
+ resolve({
194
+ stdout: stdout.trim(),
195
+ stderr: stderr.trim(),
196
+ exitCode: (_info$ExitCode = info === null || info === void 0 ? void 0 : info.ExitCode) !== null && _info$ExitCode !== void 0 ? _info$ExitCode : -1
197
+ });
198
+ });
199
+ });
200
+ stream.on('error', function (streamErr) {
201
+ reject(new Error("Stream error: ".concat(streamErr.message)));
202
+ });
203
+ if (options.timeout) {
204
+ setTimeout(function () {
205
+ reject(new Error('Command timed out'));
206
+ }, options.timeout);
207
+ }
208
+ });
209
+ });
210
+ });
211
+ }
@@ -58,7 +58,7 @@ function log(level, message) {
58
58
  timestamp: timestamp,
59
59
  formatted: formatted
60
60
  };
61
- var shouldWriteToConsole = listeners.size === 0 || level === 'error' || level === 'warn';
61
+ var shouldWriteToConsole = listeners.size === 0;
62
62
  if (shouldWriteToConsole) {
63
63
  var method = level === 'debug' ? 'log' : level;
64
64
  if (typeof console[method] === 'function') {
@@ -8,7 +8,7 @@ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r)
8
8
  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; }
9
9
  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; } }
10
10
  function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
11
- import { useState } from 'react';
11
+ import { useState, useRef, useEffect } from 'react';
12
12
  import { safeCall } from '../../helpers/safeCall.js';
13
13
  import { startContainer as svcStartContainer, stopContainer as svcStopContainer, restartContainer as svcRestartContainer, removeContainer as svcRemoveContainer } from '../../helpers/dockerService/serviceComponents/containerActions.js';
14
14
 
@@ -32,6 +32,24 @@ export function useContainerActions(_ref) {
32
32
  _useState4 = _slicedToArray(_useState3, 2),
33
33
  messageColor = _useState4[0],
34
34
  setMessageColor = _useState4[1];
35
+ var messageTimerRef = useRef(null);
36
+ function setTimedMessage(msg) {
37
+ var color = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'yellow';
38
+ var ms = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 4000;
39
+ clearTimeout(messageTimerRef.current);
40
+ setMessage(msg);
41
+ if (color !== undefined) setMessageColor(color);
42
+ if (msg) {
43
+ messageTimerRef.current = setTimeout(function () {
44
+ return setMessage('');
45
+ }, ms);
46
+ }
47
+ }
48
+ useEffect(function () {
49
+ return function () {
50
+ return clearTimeout(messageTimerRef.current);
51
+ };
52
+ }, []);
35
53
 
36
54
  /**
37
55
  * Handles a generic container action and sets feedback.
@@ -76,16 +94,14 @@ export function useContainerActions(_ref) {
76
94
  _context5.n = 4;
77
95
  return actionFn(container.id);
78
96
  case 4:
79
- setMessage("".concat(actionLabel, " container successful."));
80
- setMessageColor('green');
97
+ setTimedMessage("".concat(actionLabel, " container successful."), 'green');
81
98
  safeCall(onAction);
82
99
  _context5.n = 6;
83
100
  break;
84
101
  case 5:
85
102
  _context5.p = 5;
86
103
  _t = _context5.v;
87
- setMessage("Failed to ".concat(actionLabel.toLowerCase(), " container: ").concat(_t.message));
88
- setMessageColor('red');
104
+ setTimedMessage("Failed to ".concat(actionLabel.toLowerCase(), " container: ").concat(_t.message), 'red');
89
105
  case 6:
90
106
  return _context5.a(2);
91
107
  }
@@ -96,6 +112,7 @@ export function useContainerActions(_ref) {
96
112
  return {
97
113
  message: message,
98
114
  setMessage: setMessage,
115
+ setTimedMessage: setTimedMessage,
99
116
  messageColor: messageColor,
100
117
  setMessageColor: setMessageColor,
101
118
  handleAction: handleAction,