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.
- package/.atl/skill-registry.md +30 -0
- package/.editorconfig +12 -0
- package/.eslintrc.json +1 -1
- package/.prettierignore +4 -0
- package/.prettierrc +8 -0
- package/.~lock.ROADMAP.md# +1 -0
- package/CHANGELOG.md +80 -0
- package/README.es.md +198 -0
- package/README.md +81 -23
- package/ROADMAP.md +647 -0
- package/SECURITY.md +5 -0
- package/__mocks__/ink.cjs +17 -0
- package/coverage/clover.xml +471 -500
- package/coverage/coverage-final.json +16 -16
- package/coverage/lcov-report/ContainerCreationPrompt.jsx.html +400 -0
- package/coverage/lcov-report/ControlsHUD.jsx.html +256 -0
- package/coverage/lcov-report/components/ContainerCreationPrompt.jsx.html +394 -0
- package/coverage/lcov-report/components/PromptField.jsx.html +229 -0
- package/coverage/lcov-report/components/SuggestionPanel.jsx.html +268 -0
- package/coverage/lcov-report/components/index.html +146 -0
- package/coverage/lcov-report/helpers/constants.js.html +139 -22
- package/coverage/lcov-report/helpers/containerOptionsBuilder.js.html +75 -42
- package/coverage/lcov-report/helpers/dockerHubService.js.html +310 -0
- package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerActions.js.html +162 -84
- package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerList.js.html +50 -32
- package/coverage/lcov-report/helpers/dockerService/serviceComponents/index.html +21 -21
- package/coverage/lcov-report/helpers/imageNameUtils.js.html +181 -0
- package/coverage/lcov-report/helpers/index.html +84 -54
- package/coverage/lcov-report/helpers/logger.js.html +68 -68
- package/coverage/lcov-report/helpers/safeCall.js.html +15 -15
- package/coverage/lcov-report/helpers/validationHelpers.js.html +66 -114
- package/coverage/lcov-report/hooks/creation/index.html +20 -20
- package/coverage/lcov-report/hooks/creation/useContainerActions.js.html +38 -17
- package/coverage/lcov-report/hooks/creation/useContainerCreation.js.html +593 -89
- package/coverage/lcov-report/hooks/creation/useLogsViewer.js.html +7 -7
- package/coverage/lcov-report/hooks/debug/index.html +5 -5
- package/coverage/lcov-report/hooks/debug/useDebugLogs.js.html +35 -20
- package/coverage/lcov-report/hooks/index.html +40 -40
- package/coverage/lcov-report/hooks/navigation/index.html +19 -19
- package/coverage/lcov-report/hooks/navigation/useContainerSelection.js.html +38 -29
- package/coverage/lcov-report/hooks/useContainerCommandRouter.js.html +119 -80
- package/coverage/lcov-report/hooks/useControls.js.html +306 -123
- package/coverage/lcov-report/hooks/useEraseConfirmation.js.html +8 -8
- package/coverage/lcov-report/hooks/useExitHandler.js.html +50 -41
- package/coverage/lcov-report/index.html +52 -67
- package/coverage/lcov.info +899 -952
- package/dist/App.js +18 -12
- package/dist/components/ContainerCreationPrompt.js +50 -13
- package/dist/components/ContainerList.js +3 -3
- package/dist/components/ContainerRow.js +20 -20
- package/dist/components/ContainerSection.js +3 -3
- package/dist/components/ControlsHUD.js +106 -0
- package/dist/components/Footer.js +2 -2
- package/dist/components/Header.js +7 -7
- package/dist/components/LogViewer.js +3 -3
- package/dist/components/MessageFeedback.js +2 -2
- package/dist/components/PromptField.js +4 -4
- package/dist/components/StatsBar.js +5 -5
- package/dist/components/SuggestionPanel.js +53 -0
- package/dist/components/UsageMenu.js +3 -3
- package/dist/helpers/appInfo.js +7 -7
- package/dist/helpers/constants.js +154 -1
- package/dist/helpers/containerOptionsBuilder.js +3 -3
- package/dist/helpers/dockerHubService.js +111 -0
- package/dist/helpers/dockerService/dockerService.js +1 -1
- package/dist/helpers/dockerService/serviceComponents/containerActions.js +99 -40
- package/dist/helpers/dockerService/serviceComponents/containerList.js +11 -11
- package/dist/helpers/dockerService/serviceComponents/containerLogs.js +9 -9
- package/dist/helpers/dockerService/serviceComponents/containerStats.js +5 -5
- package/dist/helpers/dockerService/serviceComponents/imageUtils.js +1 -1
- package/dist/helpers/exitWithMessage.js +10 -10
- package/dist/helpers/imageNameUtils.js +32 -0
- package/dist/helpers/logger.js +13 -13
- package/dist/helpers/safeCall.js +2 -2
- package/dist/helpers/validationHelpers.js +76 -21
- package/dist/hooks/creation/useContainerActions.js +28 -11
- package/dist/hooks/creation/useContainerCreation.js +452 -62
- package/dist/hooks/creation/useLogsViewer.js +2 -2
- package/dist/hooks/debug/useDebugLogs.js +4 -4
- package/dist/hooks/useContainerCommandRouter.js +14 -14
- package/dist/hooks/useContainers.js +3 -3
- package/dist/hooks/useControls.js +85 -38
- package/dist/hooks/useEraseConfirmation.js +3 -3
- package/dist/index.js +9 -2
- package/jest.config.cjs +7 -0
- package/package.json +7 -3
- package/src/App.jsx +39 -16
- package/src/components/ContainerCreationPrompt.jsx +54 -10
- package/src/components/ContainerList.jsx +3 -3
- package/src/components/ContainerRow.jsx +19 -16
- package/src/components/ContainerSection.jsx +3 -3
- package/src/components/ControlsHUD.jsx +66 -0
- package/src/components/Footer.jsx +2 -2
- package/src/components/Header.jsx +7 -7
- package/src/components/LogViewer.jsx +5 -3
- package/src/components/MessageFeedback.jsx +2 -2
- package/src/components/PromptField.jsx +4 -4
- package/src/components/StatsBar.jsx +11 -7
- package/src/components/SuggestionPanel.jsx +61 -0
- package/src/components/UsageMenu.jsx +18 -13
- package/src/helpers/appInfo.js +7 -7
- package/src/helpers/constants.js +48 -9
- package/src/helpers/containerOptionsBuilder.js +16 -5
- package/src/helpers/dockerHubService.js +75 -0
- package/src/helpers/dockerService/dockerService.js +1 -1
- package/src/helpers/dockerService/serviceComponents/containerActions.js +87 -61
- package/src/helpers/dockerService/serviceComponents/containerList.js +31 -25
- package/src/helpers/dockerService/serviceComponents/containerLogs.js +41 -26
- package/src/helpers/dockerService/serviceComponents/containerStats.js +13 -11
- package/src/helpers/dockerService/serviceComponents/imageUtils.js +5 -4
- package/src/helpers/exitWithMessage.js +13 -7
- package/src/helpers/imageNameUtils.js +32 -0
- package/src/helpers/logger.js +15 -16
- package/src/helpers/safeCall.js +2 -2
- package/src/helpers/validationHelpers.js +13 -29
- package/src/hooks/creation/useContainerActions.js +32 -12
- package/src/hooks/creation/useContainerCreation.js +293 -47
- package/src/hooks/creation/useLogsViewer.js +2 -2
- package/src/hooks/debug/useDebugLogs.js +15 -10
- package/src/hooks/navigation/useContainerSelection.js +17 -14
- package/src/hooks/useContainerCommandRouter.js +71 -58
- package/src/hooks/useContainers.js +4 -4
- package/src/hooks/useControls.js +135 -72
- package/src/hooks/useEraseConfirmation.js +3 -3
- package/src/hooks/useExitHandler.js +29 -26
- package/src/index.js +8 -4
- package/test/ContainerCreationPrompt.dom.test.js +313 -0
- package/test/ControlsHUD.dom.test.js +125 -0
- package/test/SuggestionPanel.dom.test.js +83 -0
- package/test/constants.test.js +41 -0
- package/test/dockerHubService.test.js +213 -0
- package/test/logger.test.js +58 -0
- package/test/resolveImageTag.test.js +26 -0
- package/test/useContainerCreation.dom.test.js +428 -2
- package/test/useContainerCreationHub.dom.test.js +184 -0
- package/test/useControls.dom.test.js +406 -4
|
@@ -1,12 +1,52 @@
|
|
|
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 _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 }; })(); }
|
|
3
|
+
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); }
|
|
4
|
+
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); }
|
|
5
|
+
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); }); }; }
|
|
1
6
|
function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
|
|
2
7
|
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."); }
|
|
3
8
|
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; } }
|
|
4
9
|
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; }
|
|
5
10
|
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; } }
|
|
6
11
|
function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
12
|
+
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; }
|
|
13
|
+
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; }
|
|
14
|
+
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; }
|
|
15
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
|
|
16
|
+
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
17
|
+
import { useReducer, useState, useRef, useEffect } from 'react';
|
|
18
|
+
import { validatePorts, validateEnvVars } from '../../helpers/validationHelpers.js';
|
|
19
|
+
import { IMAGE_PROFILES, resolveImageTag } from '../../helpers/constants.js';
|
|
20
|
+
import { safeCall } from '../../helpers/safeCall.js';
|
|
21
|
+
import { searchDockerHub, formatHubResult } from '../../helpers/dockerHubService.js';
|
|
22
|
+
var MAX_VISIBLE = 6;
|
|
23
|
+
var INITIAL_FORM = {
|
|
24
|
+
step: 0,
|
|
25
|
+
imageName: '',
|
|
26
|
+
containerName: '',
|
|
27
|
+
portInput: '',
|
|
28
|
+
envInput: '',
|
|
29
|
+
message: '',
|
|
30
|
+
messageColor: 'yellow',
|
|
31
|
+
suggestions: [],
|
|
32
|
+
selectedSuggestionIndex: -1,
|
|
33
|
+
visibleOffset: 0
|
|
34
|
+
};
|
|
35
|
+
function formReducer(state, action) {
|
|
36
|
+
switch (action.type) {
|
|
37
|
+
case 'RESET':
|
|
38
|
+
return _objectSpread({}, INITIAL_FORM);
|
|
39
|
+
case 'RESET_WIZARD':
|
|
40
|
+
return _objectSpread(_objectSpread({}, INITIAL_FORM), {}, {
|
|
41
|
+
message: 'Insert the name of the image to create: ',
|
|
42
|
+
messageColor: 'yellow'
|
|
43
|
+
});
|
|
44
|
+
case 'SET':
|
|
45
|
+
return _objectSpread(_objectSpread({}, state), action.payload);
|
|
46
|
+
default:
|
|
47
|
+
return state;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
10
50
|
|
|
11
51
|
/**
|
|
12
52
|
* Custom hook to manage the container creation flow, step by step.
|
|
@@ -15,42 +55,328 @@ import { safeCall } from "../../helpers/safeCall.js";
|
|
|
15
55
|
* @param {Object} params
|
|
16
56
|
* @param {Function} params.onCreate - Callback when creation is confirmed
|
|
17
57
|
* @param {Function} params.onCancel - Callback when creation is cancelled
|
|
18
|
-
* @param {Array<string>} params.dbImages - List of DB image names for env var warning
|
|
58
|
+
* @param {Array<string>} params.dbImages - List of DB image names for env var warning (legacy)
|
|
59
|
+
* @param {Object} [params.imageProfiles] - Image profiles map for contextual validation
|
|
19
60
|
* @returns {Object} Creation state, setters, and helpers
|
|
20
61
|
*/
|
|
21
62
|
export function useContainerCreation(_ref) {
|
|
63
|
+
var _imageProfiles$baseNa, _imageProfiles$baseNa2;
|
|
22
64
|
var onCreate = _ref.onCreate,
|
|
23
65
|
onCancel = _ref.onCancel,
|
|
24
66
|
_ref$dbImages = _ref.dbImages,
|
|
25
|
-
dbImages = _ref$dbImages === void 0 ? [] : _ref$dbImages
|
|
26
|
-
|
|
67
|
+
dbImages = _ref$dbImages === void 0 ? [] : _ref$dbImages,
|
|
68
|
+
_ref$imageProfiles = _ref.imageProfiles,
|
|
69
|
+
imageProfiles = _ref$imageProfiles === void 0 ? IMAGE_PROFILES : _ref$imageProfiles;
|
|
70
|
+
var _useReducer = useReducer(formReducer, INITIAL_FORM),
|
|
71
|
+
_useReducer2 = _slicedToArray(_useReducer, 2),
|
|
72
|
+
form = _useReducer2[0],
|
|
73
|
+
dispatch = _useReducer2[1];
|
|
74
|
+
var step = form.step,
|
|
75
|
+
imageName = form.imageName,
|
|
76
|
+
containerName = form.containerName,
|
|
77
|
+
portInput = form.portInput,
|
|
78
|
+
envInput = form.envInput,
|
|
79
|
+
message = form.message,
|
|
80
|
+
messageColor = form.messageColor,
|
|
81
|
+
suggestions = form.suggestions,
|
|
82
|
+
selectedSuggestionIndex = form.selectedSuggestionIndex,
|
|
83
|
+
visibleOffset = form.visibleOffset;
|
|
84
|
+
|
|
85
|
+
// Convenience setters that mirror the old useState API
|
|
86
|
+
var setStep = function setStep(v) {
|
|
87
|
+
return dispatch({
|
|
88
|
+
type: 'SET',
|
|
89
|
+
payload: {
|
|
90
|
+
step: v
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
var setImageName = function setImageName(v) {
|
|
95
|
+
return dispatch({
|
|
96
|
+
type: 'SET',
|
|
97
|
+
payload: {
|
|
98
|
+
imageName: v
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
var setContainerName = function setContainerName(v) {
|
|
103
|
+
return dispatch({
|
|
104
|
+
type: 'SET',
|
|
105
|
+
payload: {
|
|
106
|
+
containerName: v
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
var setPortInput = function setPortInput(v) {
|
|
111
|
+
return dispatch({
|
|
112
|
+
type: 'SET',
|
|
113
|
+
payload: {
|
|
114
|
+
portInput: v
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
};
|
|
118
|
+
var setEnvInput = function setEnvInput(v) {
|
|
119
|
+
return dispatch({
|
|
120
|
+
type: 'SET',
|
|
121
|
+
payload: {
|
|
122
|
+
envInput: typeof v === 'function' ? v(form.envInput) : v
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
var setMessage = function setMessage(v) {
|
|
127
|
+
return dispatch({
|
|
128
|
+
type: 'SET',
|
|
129
|
+
payload: {
|
|
130
|
+
message: v
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
var setMessageColor = function setMessageColor(v) {
|
|
135
|
+
return dispatch({
|
|
136
|
+
type: 'SET',
|
|
137
|
+
payload: {
|
|
138
|
+
messageColor: v
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
};
|
|
142
|
+
var setSuggestions = function setSuggestions(v) {
|
|
143
|
+
return dispatch({
|
|
144
|
+
type: 'SET',
|
|
145
|
+
payload: {
|
|
146
|
+
suggestions: v
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
};
|
|
150
|
+
var setSelectedSuggestionIndex = function setSelectedSuggestionIndex(fn) {
|
|
151
|
+
// Support both direct value and updater function
|
|
152
|
+
if (typeof fn === 'function') {
|
|
153
|
+
dispatch({
|
|
154
|
+
type: 'SET',
|
|
155
|
+
payload: {
|
|
156
|
+
selectedSuggestionIndex: fn(form.selectedSuggestionIndex)
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
} else {
|
|
160
|
+
dispatch({
|
|
161
|
+
type: 'SET',
|
|
162
|
+
payload: {
|
|
163
|
+
selectedSuggestionIndex: fn
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
var setVisibleOffset = function setVisibleOffset(fn) {
|
|
169
|
+
if (typeof fn === 'function') {
|
|
170
|
+
dispatch({
|
|
171
|
+
type: 'SET',
|
|
172
|
+
payload: {
|
|
173
|
+
visibleOffset: fn(form.visibleOffset)
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
} else {
|
|
177
|
+
dispatch({
|
|
178
|
+
type: 'SET',
|
|
179
|
+
payload: {
|
|
180
|
+
visibleOffset: fn
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// Docker Hub search state (kept as useState because they are independent of form)
|
|
187
|
+
var _useState = useState(false),
|
|
27
188
|
_useState2 = _slicedToArray(_useState, 2),
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
var _useState3 = useState(
|
|
189
|
+
isSearchingHub = _useState2[0],
|
|
190
|
+
setIsSearchingHub = _useState2[1];
|
|
191
|
+
var _useState3 = useState(null),
|
|
31
192
|
_useState4 = _slicedToArray(_useState3, 2),
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
193
|
+
hubResults = _useState4[0],
|
|
194
|
+
setHubResults = _useState4[1];
|
|
195
|
+
// Tracks active request: { controller: AbortController|null, requestId: number }
|
|
196
|
+
var activeHubRequestRef = useRef({
|
|
197
|
+
controller: null,
|
|
198
|
+
requestId: 0
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// Timer for auto-clearing messages
|
|
202
|
+
var messageTimerRef = useRef(null);
|
|
203
|
+
function setTimedMessage(msg) {
|
|
204
|
+
var color = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'yellow';
|
|
205
|
+
var ms = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 4000;
|
|
206
|
+
clearTimeout(messageTimerRef.current);
|
|
207
|
+
dispatch({
|
|
208
|
+
type: 'SET',
|
|
209
|
+
payload: {
|
|
210
|
+
message: msg,
|
|
211
|
+
messageColor: color
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
if (msg) {
|
|
215
|
+
messageTimerRef.current = setTimeout(function () {
|
|
216
|
+
return dispatch({
|
|
217
|
+
type: 'SET',
|
|
218
|
+
payload: {
|
|
219
|
+
message: ''
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
}, ms);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
useEffect(function () {
|
|
226
|
+
return function () {
|
|
227
|
+
return clearTimeout(messageTimerRef.current);
|
|
228
|
+
};
|
|
229
|
+
}, []);
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Updates the image name input and recalculates autocomplete suggestions.
|
|
233
|
+
* Resets selectedSuggestionIndex to -1 on every keystroke.
|
|
234
|
+
* Also aborts any in-flight Hub search and clears Hub results.
|
|
235
|
+
*
|
|
236
|
+
* @param {string} value - New raw image name typed by user
|
|
237
|
+
*/
|
|
238
|
+
function updateImageInput(value) {
|
|
239
|
+
var _activeHubRequestRef$;
|
|
240
|
+
// Abort any in-flight Hub search and clear its state
|
|
241
|
+
(_activeHubRequestRef$ = activeHubRequestRef.current.controller) === null || _activeHubRequestRef$ === void 0 || _activeHubRequestRef$.abort();
|
|
242
|
+
activeHubRequestRef.current.requestId += 1;
|
|
243
|
+
setIsSearchingHub(false);
|
|
244
|
+
setHubResults(null);
|
|
245
|
+
var lower = value.toLowerCase();
|
|
246
|
+
var matches = value.trim() ? Object.keys(imageProfiles).filter(function (key) {
|
|
247
|
+
return key.includes(lower);
|
|
248
|
+
}) : [];
|
|
249
|
+
dispatch({
|
|
250
|
+
type: 'SET',
|
|
251
|
+
payload: {
|
|
252
|
+
imageName: value,
|
|
253
|
+
selectedSuggestionIndex: -1,
|
|
254
|
+
visibleOffset: 0,
|
|
255
|
+
suggestions: matches
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Triggers a Docker Hub search for the current imageName.
|
|
262
|
+
* Guards: imageName must be non-empty; no concurrent search allowed.
|
|
263
|
+
* Uses AbortController + requestId to handle race conditions.
|
|
264
|
+
*/
|
|
265
|
+
function triggerHubSearch() {
|
|
266
|
+
return _triggerHubSearch.apply(this, arguments);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Moves the suggestion selection up (-1) or down (+1).
|
|
270
|
+
* Clamps index to [-1, suggestions.length - 1].
|
|
271
|
+
* Adjusts visibleOffset to keep selected item in the visible window.
|
|
272
|
+
*
|
|
273
|
+
* @param {number} direction - -1 (up) or 1 (down)
|
|
274
|
+
*/
|
|
275
|
+
function _triggerHubSearch() {
|
|
276
|
+
_triggerHubSearch = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {
|
|
277
|
+
var _activeHubRequestRef$2;
|
|
278
|
+
var query, requestId, controller, results, _t;
|
|
279
|
+
return _regenerator().w(function (_context) {
|
|
280
|
+
while (1) switch (_context.p = _context.n) {
|
|
281
|
+
case 0:
|
|
282
|
+
query = imageName.trim();
|
|
283
|
+
if (query) {
|
|
284
|
+
_context.n = 1;
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
return _context.a(2);
|
|
288
|
+
case 1:
|
|
289
|
+
if (!isSearchingHub) {
|
|
290
|
+
_context.n = 2;
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
return _context.a(2);
|
|
294
|
+
case 2:
|
|
295
|
+
// Abort previous request (should already be aborted from updateImageInput,
|
|
296
|
+
// but guard here too)
|
|
297
|
+
(_activeHubRequestRef$2 = activeHubRequestRef.current.controller) === null || _activeHubRequestRef$2 === void 0 || _activeHubRequestRef$2.abort();
|
|
298
|
+
requestId = activeHubRequestRef.current.requestId + 1;
|
|
299
|
+
activeHubRequestRef.current.requestId = requestId;
|
|
300
|
+
controller = new AbortController();
|
|
301
|
+
activeHubRequestRef.current.controller = controller;
|
|
302
|
+
setIsSearchingHub(true);
|
|
303
|
+
dispatch({
|
|
304
|
+
type: 'SET',
|
|
305
|
+
payload: {
|
|
306
|
+
selectedSuggestionIndex: -1,
|
|
307
|
+
visibleOffset: 0
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
_context.p = 3;
|
|
311
|
+
_context.n = 4;
|
|
312
|
+
return searchDockerHub(query, {
|
|
313
|
+
signal: controller.signal
|
|
314
|
+
});
|
|
315
|
+
case 4:
|
|
316
|
+
results = _context.v;
|
|
317
|
+
if (!(activeHubRequestRef.current.requestId !== requestId)) {
|
|
318
|
+
_context.n = 5;
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
321
|
+
return _context.a(2);
|
|
322
|
+
case 5:
|
|
323
|
+
setHubResults(results.map(formatHubResult));
|
|
324
|
+
_context.n = 8;
|
|
325
|
+
break;
|
|
326
|
+
case 6:
|
|
327
|
+
_context.p = 6;
|
|
328
|
+
_t = _context.v;
|
|
329
|
+
if (!(activeHubRequestRef.current.requestId !== requestId)) {
|
|
330
|
+
_context.n = 7;
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
333
|
+
return _context.a(2);
|
|
334
|
+
case 7:
|
|
335
|
+
// Silence AbortError; for all other errors fall back to static suggestions
|
|
336
|
+
setHubResults(null);
|
|
337
|
+
case 8:
|
|
338
|
+
_context.p = 8;
|
|
339
|
+
if (activeHubRequestRef.current.requestId === requestId) {
|
|
340
|
+
setIsSearchingHub(false);
|
|
341
|
+
}
|
|
342
|
+
return _context.f(8);
|
|
343
|
+
case 9:
|
|
344
|
+
return _context.a(2);
|
|
345
|
+
}
|
|
346
|
+
}, _callee, null, [[3, 6, 8, 9]]);
|
|
347
|
+
}));
|
|
348
|
+
return _triggerHubSearch.apply(this, arguments);
|
|
349
|
+
}
|
|
350
|
+
function moveSuggestionSelection(direction) {
|
|
351
|
+
var next = Math.max(-1, Math.min(suggestions.length - 1, selectedSuggestionIndex + direction));
|
|
352
|
+
var newOffset = visibleOffset;
|
|
353
|
+
if (next < visibleOffset) newOffset = Math.max(0, next);else if (next >= visibleOffset + MAX_VISIBLE) newOffset = next - MAX_VISIBLE + 1;
|
|
354
|
+
dispatch({
|
|
355
|
+
type: 'SET',
|
|
356
|
+
payload: {
|
|
357
|
+
selectedSuggestionIndex: next,
|
|
358
|
+
visibleOffset: newOffset
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Applies the currently focused suggestion to imageName.
|
|
365
|
+
* Does NOT advance the step. Clears suggestions after applying.
|
|
366
|
+
*/
|
|
367
|
+
function applyFocusedSuggestion() {
|
|
368
|
+
if (selectedSuggestionIndex < 0 || selectedSuggestionIndex >= suggestions.length) return;
|
|
369
|
+
var chosen = suggestions[selectedSuggestionIndex];
|
|
370
|
+
dispatch({
|
|
371
|
+
type: 'SET',
|
|
372
|
+
payload: {
|
|
373
|
+
imageName: resolveImageTag(chosen, imageProfiles),
|
|
374
|
+
suggestions: [],
|
|
375
|
+
selectedSuggestionIndex: -1,
|
|
376
|
+
visibleOffset: 0
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
}
|
|
54
380
|
|
|
55
381
|
/**
|
|
56
382
|
* Advances to the next step, with validation and feedback.
|
|
@@ -58,42 +384,67 @@ export function useContainerCreation(_ref) {
|
|
|
58
384
|
function nextStep() {
|
|
59
385
|
if (step === 0) {
|
|
60
386
|
if (!imageName.trim()) {
|
|
61
|
-
|
|
62
|
-
setMessageColor("red");
|
|
387
|
+
setTimedMessage('Image name cannot be empty.', 'red');
|
|
63
388
|
return;
|
|
64
389
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
390
|
+
var resolved = resolveImageTag(imageName, imageProfiles);
|
|
391
|
+
dispatch({
|
|
392
|
+
type: 'SET',
|
|
393
|
+
payload: {
|
|
394
|
+
imageName: resolved,
|
|
395
|
+
step: 1
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
setTimedMessage('Optional: Enter container name or leave empty and press Enter', 'yellow');
|
|
68
399
|
return;
|
|
69
400
|
}
|
|
70
401
|
if (step === 1) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
402
|
+
dispatch({
|
|
403
|
+
type: 'SET',
|
|
404
|
+
payload: {
|
|
405
|
+
step: 2
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
setTimedMessage('Optional: Enter ports (format 8080:80,443:443) or leave empty and press Enter', 'yellow');
|
|
74
409
|
return;
|
|
75
410
|
}
|
|
76
411
|
if (step === 2) {
|
|
77
412
|
// Ports are now optional - only validate if provided
|
|
78
413
|
if (portInput.trim() && !validatePorts(portInput)) {
|
|
79
|
-
|
|
80
|
-
setMessageColor("red");
|
|
414
|
+
setTimedMessage('Port format must be host:container and both must be numbers (e.g. 8080:80)', 'red');
|
|
81
415
|
return;
|
|
82
416
|
}
|
|
83
|
-
|
|
417
|
+
dispatch({
|
|
418
|
+
type: 'SET',
|
|
419
|
+
payload: {
|
|
420
|
+
step: 3
|
|
421
|
+
}
|
|
422
|
+
});
|
|
84
423
|
var isDb = dbImages.some(function (db) {
|
|
85
424
|
return imageName.trim().toLowerCase().includes(db);
|
|
86
425
|
});
|
|
87
|
-
if
|
|
88
|
-
|
|
89
|
-
|
|
426
|
+
// Check if the image has a profile with required env vars
|
|
427
|
+
var _baseName = imageName.trim().toLowerCase().split(':')[0].split('/').pop();
|
|
428
|
+
var profile = imageProfiles[_baseName];
|
|
429
|
+
if (profile && profile.requiredEnv && profile.requiredEnv.length) {
|
|
430
|
+
var suggestedPart = profile.suggestedEnv && profile.suggestedEnv.length ? " | Suggested: ".concat(profile.suggestedEnv.join(', ')) : '';
|
|
431
|
+
setTimedMessage("Required env vars for ".concat(_baseName, ": ").concat(profile.requiredEnv.join(', '), ". Enter as VAR=val,VAR2=val2").concat(suggestedPart), 'yellow');
|
|
432
|
+
} else if (profile && profile.suggestedEnv && profile.suggestedEnv.length) {
|
|
433
|
+
setTimedMessage("Suggested env vars for ".concat(_baseName, ": ").concat(profile.suggestedEnv.join(', '), ". Enter as VAR=val,VAR2=val2 or leave empty."), 'yellow');
|
|
434
|
+
} else if (isDb) {
|
|
435
|
+
setTimedMessage('Warning: This image usually requires environment variables (e.g. MYSQL_ROOT_PASSWORD=my-secret-pw for MySQL, POSTGRES_PASSWORD=yourpassword for Postgres). Enter them as VAR=val,VAR2=val2 or leave empty and press Enter.', 'yellow');
|
|
90
436
|
} else {
|
|
91
|
-
|
|
92
|
-
setMessageColor("yellow");
|
|
437
|
+
setTimedMessage('Optional: Enter environment variables (format VAR1=val1,VAR2=val2) or leave empty and press Enter', 'yellow');
|
|
93
438
|
}
|
|
94
439
|
return;
|
|
95
440
|
}
|
|
96
441
|
if (step === 3) {
|
|
442
|
+
// Contextual env validation using image profiles
|
|
443
|
+
var result = validateEnvVars(envInput, imageName, imageProfiles);
|
|
444
|
+
if (!result.valid) {
|
|
445
|
+
setTimedMessage(result.errors.join(' | '), 'red');
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
97
448
|
// Final step: call onCreate with all data (safely)
|
|
98
449
|
safeCall(onCreate, {
|
|
99
450
|
imageName: imageName,
|
|
@@ -108,13 +459,9 @@ export function useContainerCreation(_ref) {
|
|
|
108
459
|
* Cancels the creation process and resets state.
|
|
109
460
|
*/
|
|
110
461
|
function cancelCreation() {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
setPortInput("");
|
|
115
|
-
setEnvInput("");
|
|
116
|
-
setMessage("");
|
|
117
|
-
setMessageColor("yellow");
|
|
462
|
+
dispatch({
|
|
463
|
+
type: 'RESET'
|
|
464
|
+
});
|
|
118
465
|
safeCall(onCancel);
|
|
119
466
|
}
|
|
120
467
|
|
|
@@ -123,14 +470,46 @@ export function useContainerCreation(_ref) {
|
|
|
123
470
|
* Used by the command router when the user presses 'c' to open the creation wizard.
|
|
124
471
|
*/
|
|
125
472
|
function resetCreation() {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
473
|
+
dispatch({
|
|
474
|
+
type: 'RESET_WIZARD'
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Inserts the next pending suggested env var into envInput.
|
|
480
|
+
* Derives "next" by comparing keys already in envInput against suggestedEnv.
|
|
481
|
+
* When all suggestions are already present, sets a feedback message.
|
|
482
|
+
*/
|
|
483
|
+
function insertNextSuggestedEnv() {
|
|
484
|
+
var _profile$suggestedEnv;
|
|
485
|
+
var baseName = imageName.trim().toLowerCase().split(':')[0].split('/').pop();
|
|
486
|
+
var profile = imageProfiles[baseName];
|
|
487
|
+
var suggested = (_profile$suggestedEnv = profile === null || profile === void 0 ? void 0 : profile.suggestedEnv) !== null && _profile$suggestedEnv !== void 0 ? _profile$suggestedEnv : [];
|
|
488
|
+
if (!suggested.length) return;
|
|
489
|
+
var addedKeys = envInput.split(',').map(function (s) {
|
|
490
|
+
return s.split('=')[0].trim();
|
|
491
|
+
}).filter(Boolean);
|
|
492
|
+
var next = suggested.find(function (s) {
|
|
493
|
+
return !addedKeys.includes(s.split('=')[0]);
|
|
494
|
+
});
|
|
495
|
+
if (!next) {
|
|
496
|
+
setTimedMessage('All suggested env vars added', 'yellow');
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
var newEnvInput = envInput ? "".concat(envInput, ",").concat(next) : next;
|
|
500
|
+
dispatch({
|
|
501
|
+
type: 'SET',
|
|
502
|
+
payload: {
|
|
503
|
+
envInput: newEnvInput
|
|
504
|
+
}
|
|
505
|
+
});
|
|
133
506
|
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* True when the current image profile has at least one suggestedEnv entry.
|
|
510
|
+
*/
|
|
511
|
+
var baseName = imageName.trim().toLowerCase().split(':')[0].split('/').pop();
|
|
512
|
+
var hasSuggestedEnv = ((_imageProfiles$baseNa = (_imageProfiles$baseNa2 = imageProfiles[baseName]) === null || _imageProfiles$baseNa2 === void 0 || (_imageProfiles$baseNa2 = _imageProfiles$baseNa2.suggestedEnv) === null || _imageProfiles$baseNa2 === void 0 ? void 0 : _imageProfiles$baseNa2.length) !== null && _imageProfiles$baseNa !== void 0 ? _imageProfiles$baseNa : 0) > 0;
|
|
134
513
|
return {
|
|
135
514
|
step: step,
|
|
136
515
|
setStep: setStep,
|
|
@@ -146,8 +525,19 @@ export function useContainerCreation(_ref) {
|
|
|
146
525
|
setMessage: setMessage,
|
|
147
526
|
messageColor: messageColor,
|
|
148
527
|
setMessageColor: setMessageColor,
|
|
528
|
+
suggestions: suggestions,
|
|
529
|
+
selectedSuggestionIndex: selectedSuggestionIndex,
|
|
530
|
+
visibleOffset: visibleOffset,
|
|
531
|
+
isSearchingHub: isSearchingHub,
|
|
532
|
+
hubResults: hubResults,
|
|
533
|
+
updateImageInput: updateImageInput,
|
|
534
|
+
triggerHubSearch: triggerHubSearch,
|
|
535
|
+
moveSuggestionSelection: moveSuggestionSelection,
|
|
536
|
+
applyFocusedSuggestion: applyFocusedSuggestion,
|
|
149
537
|
nextStep: nextStep,
|
|
150
538
|
cancelCreation: cancelCreation,
|
|
151
|
-
resetCreation: resetCreation
|
|
539
|
+
resetCreation: resetCreation,
|
|
540
|
+
insertNextSuggestedEnv: insertNextSuggestedEnv,
|
|
541
|
+
hasSuggestedEnv: hasSuggestedEnv
|
|
152
542
|
};
|
|
153
543
|
}
|
|
@@ -4,8 +4,8 @@ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r)
|
|
|
4
4
|
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; }
|
|
5
5
|
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; } }
|
|
6
6
|
function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
|
|
7
|
-
import { useState, useRef } from
|
|
8
|
-
import { safeCall } from
|
|
7
|
+
import { useState, useRef } from 'react';
|
|
8
|
+
import { safeCall } from '../../helpers/safeCall.js';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Custom hook to manage the logs viewer state and stream reference.
|
|
@@ -23,15 +23,15 @@ export function useDebugLogs() {
|
|
|
23
23
|
var unsubscribe = logger.subscribe(function (entry) {
|
|
24
24
|
setDebugLogs(function (prev) {
|
|
25
25
|
var extras = (entry.args || []).map(function (arg) {
|
|
26
|
-
if (typeof arg ===
|
|
27
|
-
if (typeof arg ===
|
|
26
|
+
if (typeof arg === 'string') return arg;
|
|
27
|
+
if (typeof arg === 'number' || typeof arg === 'boolean') return String(arg);
|
|
28
28
|
try {
|
|
29
29
|
return JSON.stringify(arg);
|
|
30
30
|
} catch (err) {
|
|
31
|
-
return
|
|
31
|
+
return '[unserializable]';
|
|
32
32
|
}
|
|
33
33
|
}).filter(Boolean);
|
|
34
|
-
var line = extras.length ? "".concat(entry.formatted, " ").concat(extras.join(
|
|
34
|
+
var line = extras.length ? "".concat(entry.formatted, " ").concat(extras.join(' ')) : entry.formatted;
|
|
35
35
|
var next = [].concat(_toConsumableArray(prev), [line]);
|
|
36
36
|
return next.slice(-200);
|
|
37
37
|
});
|