@nmakarov/cli-toolkit 0.7.1 → 0.7.2
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/dist/index.cjs +1695 -1638
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1669 -1618
- package/dist/index.js.map +1 -1
- package/dist/init.cjs +1100 -0
- package/dist/init.cjs.map +1 -1
- package/dist/init.js +1104 -0
- package/dist/init.js.map +1 -1
- package/dist/screen.cjs +2 -2
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
|
|
3
|
+
var __esmCache = {};
|
|
4
|
+
var __loadESMSync = function(moduleName) {
|
|
5
|
+
if (!__esmCache[moduleName]) {
|
|
6
|
+
throw new Error(`ESM module "${moduleName}" not loaded. Please call the load() function first: const toolkit = require("@nmakarov/cli-toolkit"); await toolkit.load();`);
|
|
7
|
+
}
|
|
8
|
+
return __esmCache[moduleName];
|
|
9
|
+
};
|
|
2
10
|
var __create = Object.create;
|
|
3
11
|
var __defProp = Object.defineProperty;
|
|
4
12
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
13
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
14
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
15
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
16
|
+
var __esm = (fn, res) => function __init() {
|
|
17
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
18
|
+
};
|
|
8
19
|
var __export = (target, all) => {
|
|
9
20
|
for (var name in all)
|
|
10
21
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -27,1788 +38,1834 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
27
38
|
));
|
|
28
39
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
40
|
|
|
30
|
-
// src/
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
var Args = class {
|
|
89
|
-
args = {};
|
|
90
|
-
flags = {};
|
|
91
|
-
options = {};
|
|
92
|
-
commands = [];
|
|
93
|
-
usedKeys = /* @__PURE__ */ new Set();
|
|
94
|
-
aliases = {};
|
|
95
|
-
overrides = {};
|
|
96
|
-
defaults = {};
|
|
97
|
-
prefixes = [];
|
|
98
|
-
nots = [];
|
|
99
|
-
configValues = {};
|
|
100
|
-
configsLoaded = [];
|
|
101
|
-
env = "local";
|
|
102
|
-
constructor(config2 = {}) {
|
|
103
|
-
this.aliases = config2.aliases || {};
|
|
104
|
-
this.overrides = config2.overrides || {};
|
|
105
|
-
this.defaults = config2.defaults || {};
|
|
106
|
-
this.prefixes = config2.prefixes || ["not", "no"];
|
|
107
|
-
const args = config2.args || process.argv.slice(2);
|
|
108
|
-
this.parseArgs(args);
|
|
109
|
-
this.env = this.get("env")?.toLowerCase() || "local";
|
|
110
|
-
this.loadDotEnv();
|
|
111
|
-
this.loadConfigFiles();
|
|
112
|
-
this.checkConflicts();
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Parse command line arguments
|
|
116
|
-
*/
|
|
117
|
-
parseArgs(args) {
|
|
118
|
-
let i = 0;
|
|
119
|
-
while (i < args.length) {
|
|
120
|
-
const arg = args[i];
|
|
121
|
-
if (arg.startsWith("--")) {
|
|
122
|
-
const [key, value] = this.parseLongOption(arg);
|
|
123
|
-
this.setValue(key, value);
|
|
124
|
-
i++;
|
|
125
|
-
} else if (arg.startsWith("-")) {
|
|
126
|
-
const result = this.parseShortOption(arg, args, i);
|
|
127
|
-
if (result.consumed > 0) {
|
|
128
|
-
i += result.consumed;
|
|
41
|
+
// src/screen/components.ts
|
|
42
|
+
function getScreenWidth(maxWidth = null) {
|
|
43
|
+
const terminalWidth = process.stdout.columns || 80;
|
|
44
|
+
const availableWidth = Math.max(20, terminalWidth - 4);
|
|
45
|
+
return maxWidth ? Math.min(availableWidth, maxWidth) : availableWidth;
|
|
46
|
+
}
|
|
47
|
+
function ScreenContainer({ children }) {
|
|
48
|
+
const width = getScreenWidth();
|
|
49
|
+
return (0, import_react.createElement)(import_ink.Box, {
|
|
50
|
+
flexDirection: "column",
|
|
51
|
+
marginTop: 1,
|
|
52
|
+
borderStyle: "single",
|
|
53
|
+
borderColor: "cyan",
|
|
54
|
+
paddingX: 1,
|
|
55
|
+
width
|
|
56
|
+
// Use the calculated width directly
|
|
57
|
+
}, children);
|
|
58
|
+
}
|
|
59
|
+
function ScreenRow({ children }) {
|
|
60
|
+
return (0, import_react.createElement)(import_ink.Box, { flexDirection: "column" }, children);
|
|
61
|
+
}
|
|
62
|
+
function ScreenTitle({ text }) {
|
|
63
|
+
return (0, import_react.createElement)(
|
|
64
|
+
ScreenRow,
|
|
65
|
+
{},
|
|
66
|
+
(0, import_react.createElement)(import_ink.Text, { bold: true, color: "cyan" }, text)
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
function ScreenDivider({ width }) {
|
|
70
|
+
const dividerWidth = width || getScreenWidth() - 4;
|
|
71
|
+
return (0, import_react.createElement)(import_ink.Text, { color: "cyan", dimColor: true }, "\u2500".repeat(dividerWidth));
|
|
72
|
+
}
|
|
73
|
+
function ScreenBody({ children, alignItems = "flex-start" }) {
|
|
74
|
+
return (0, import_react.createElement)(import_ink.Box, { flexDirection: "column", alignItems }, children);
|
|
75
|
+
}
|
|
76
|
+
function ScreenFooter({ lines, textStyle }) {
|
|
77
|
+
const defaultTextStyle = {
|
|
78
|
+
dimColor: true,
|
|
79
|
+
color: "white"
|
|
80
|
+
};
|
|
81
|
+
const finalTextStyle = { ...defaultTextStyle, ...textStyle };
|
|
82
|
+
const flattenAndWrap = (items, keyPrefix = "") => {
|
|
83
|
+
const result = [];
|
|
84
|
+
let keyIndex = 0;
|
|
85
|
+
items.forEach((item, index) => {
|
|
86
|
+
if (Array.isArray(item)) {
|
|
87
|
+
const nested = flattenAndWrap(item, `${keyPrefix}-${index}`);
|
|
88
|
+
result.push(...nested);
|
|
89
|
+
} else if (typeof item === "string") {
|
|
90
|
+
result.push(
|
|
91
|
+
(0, import_react.createElement)(import_ink.Text, { key: `${keyPrefix}-${keyIndex++}`, ...finalTextStyle }, item)
|
|
92
|
+
);
|
|
93
|
+
} else {
|
|
94
|
+
const element = item;
|
|
95
|
+
if (element.key === null || element.key === void 0) {
|
|
96
|
+
result.push(
|
|
97
|
+
(0, import_react.createElement)(import_ink.Text, { key: `${keyPrefix}-${keyIndex++}`, ...finalTextStyle }, element)
|
|
98
|
+
);
|
|
129
99
|
} else {
|
|
130
|
-
|
|
100
|
+
result.push(element);
|
|
131
101
|
}
|
|
132
|
-
} else {
|
|
133
|
-
this.commands.push(arg);
|
|
134
|
-
i++;
|
|
135
102
|
}
|
|
136
|
-
}
|
|
103
|
+
});
|
|
104
|
+
return result;
|
|
105
|
+
};
|
|
106
|
+
const wrappedItems = flattenAndWrap(lines);
|
|
107
|
+
return (0, import_react.createElement)(
|
|
108
|
+
import_ink.Box,
|
|
109
|
+
{ flexDirection: "column" },
|
|
110
|
+
(0, import_react.createElement)(import_ink.Box, { flexDirection: "row" }, ...wrappedItems)
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
var import_react, import_ink;
|
|
114
|
+
var init_components = __esm({
|
|
115
|
+
"src/screen/components.ts"() {
|
|
116
|
+
"use strict";
|
|
117
|
+
import_react = require("react");
|
|
118
|
+
import_ink = require("ink");
|
|
137
119
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// src/screen/list-components.ts
|
|
123
|
+
function MultiColumnListComponent({ items, ctx, selectedIndexRef }) {
|
|
124
|
+
const [, forceUpdate] = (0, import_react2.useState)({});
|
|
125
|
+
const termWidth = (process.stdout.columns || 80) - 8;
|
|
126
|
+
const maxItemLength = Math.max(...items.map((w) => w.length));
|
|
127
|
+
const columnWidth = maxItemLength + 3;
|
|
128
|
+
const columns = Math.max(1, Math.floor(termWidth / columnWidth));
|
|
129
|
+
const itemsPerColumn = Math.ceil(items.length / columns);
|
|
130
|
+
(0, import_react2.useEffect)(() => {
|
|
131
|
+
ctx.setAction("moveUp", () => {
|
|
132
|
+
selectedIndexRef.current = Math.max(0, selectedIndexRef.current - 1);
|
|
133
|
+
forceUpdate({});
|
|
134
|
+
});
|
|
135
|
+
ctx.setAction("moveDown", () => {
|
|
136
|
+
selectedIndexRef.current = Math.min(items.length - 1, selectedIndexRef.current + 1);
|
|
137
|
+
forceUpdate({});
|
|
138
|
+
});
|
|
139
|
+
ctx.setAction("moveLeft", () => {
|
|
140
|
+
if (selectedIndexRef.current === 0) {
|
|
141
|
+
ctx.goBack();
|
|
142
|
+
} else {
|
|
143
|
+
selectedIndexRef.current = Math.max(0, selectedIndexRef.current - itemsPerColumn);
|
|
144
|
+
forceUpdate({});
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
ctx.setAction("moveRight", () => {
|
|
148
|
+
selectedIndexRef.current = Math.min(items.length - 1, selectedIndexRef.current + itemsPerColumn);
|
|
149
|
+
forceUpdate({});
|
|
150
|
+
});
|
|
151
|
+
ctx.setKeyBinding([
|
|
152
|
+
{ key: "leftArrow", caption: "navigate", action: "moveLeft", order: 0 },
|
|
153
|
+
{ key: "rightArrow", caption: "navigate", action: "moveRight", order: 0 },
|
|
154
|
+
{ key: "upArrow", caption: "navigate", action: "moveUp", order: 0 },
|
|
155
|
+
{ key: "downArrow", caption: "navigate", action: "moveDown", order: 0 }
|
|
156
|
+
]);
|
|
157
|
+
ctx.addFooter(`Total: ${items.length} items`);
|
|
158
|
+
}, []);
|
|
159
|
+
const selectedIndex = selectedIndexRef.current;
|
|
160
|
+
const rows = [];
|
|
161
|
+
for (let row = 0; row < itemsPerColumn; row++) {
|
|
162
|
+
const cols = [];
|
|
163
|
+
for (let col = 0; col < columns; col++) {
|
|
164
|
+
const index = col * itemsPerColumn + row;
|
|
165
|
+
if (index < items.length) {
|
|
166
|
+
const isSelected = index === selectedIndex;
|
|
167
|
+
cols.push(
|
|
168
|
+
h2(
|
|
169
|
+
import_ink2.Box,
|
|
170
|
+
{ key: index, width: columnWidth },
|
|
171
|
+
h2(import_ink2.Text, {
|
|
172
|
+
color: isSelected ? "black" : "white",
|
|
173
|
+
backgroundColor: isSelected ? "cyan" : void 0,
|
|
174
|
+
bold: isSelected
|
|
175
|
+
}, items[index].padEnd(maxItemLength))
|
|
176
|
+
)
|
|
177
|
+
);
|
|
148
178
|
}
|
|
149
|
-
this.nots.push(key);
|
|
150
|
-
return [strippedKey, false];
|
|
151
|
-
}
|
|
152
|
-
if (key.includes("=")) {
|
|
153
|
-
const eqIndex = key.indexOf("=");
|
|
154
|
-
const optionKey = key.slice(0, eqIndex);
|
|
155
|
-
const value = key.slice(eqIndex + 1);
|
|
156
|
-
return [optionKey, this.parseValue(value)];
|
|
157
|
-
} else {
|
|
158
|
-
return [key, true];
|
|
159
179
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
180
|
+
rows.push(
|
|
181
|
+
h2(ScreenRow, { key: row, children: h2(import_ink2.Box, { flexDirection: "row" }, ...cols) })
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
return h2(import_ink2.Box, { flexDirection: "column" }, ...rows);
|
|
185
|
+
}
|
|
186
|
+
function MultiColumnListWithPreviewComponent({
|
|
187
|
+
items,
|
|
188
|
+
getPreviewContent,
|
|
189
|
+
ctx,
|
|
190
|
+
selectedIndexRef
|
|
191
|
+
}) {
|
|
192
|
+
const [, forceUpdate] = (0, import_react2.useState)({});
|
|
193
|
+
const termWidth = (process.stdout.columns || 80) - 8;
|
|
194
|
+
const maxItemLength = Math.max(...items.map((w) => w.length));
|
|
195
|
+
const columnWidth = maxItemLength + 3;
|
|
196
|
+
const columns = Math.max(1, Math.floor(termWidth / columnWidth));
|
|
197
|
+
const itemsPerColumn = Math.ceil(items.length / columns);
|
|
198
|
+
(0, import_react2.useEffect)(() => {
|
|
199
|
+
ctx.setAction("moveUp", () => {
|
|
200
|
+
selectedIndexRef.current = Math.max(0, selectedIndexRef.current - 1);
|
|
201
|
+
forceUpdate({});
|
|
202
|
+
});
|
|
203
|
+
ctx.setAction("moveDown", () => {
|
|
204
|
+
selectedIndexRef.current = Math.min(items.length - 1, selectedIndexRef.current + 1);
|
|
205
|
+
forceUpdate({});
|
|
206
|
+
});
|
|
207
|
+
ctx.setAction("moveLeft", () => {
|
|
208
|
+
if (selectedIndexRef.current === 0) {
|
|
209
|
+
ctx.goBack();
|
|
210
|
+
} else {
|
|
211
|
+
selectedIndexRef.current = Math.max(0, selectedIndexRef.current - itemsPerColumn);
|
|
212
|
+
forceUpdate({});
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
ctx.setAction("moveRight", () => {
|
|
216
|
+
selectedIndexRef.current = Math.min(items.length - 1, selectedIndexRef.current + itemsPerColumn);
|
|
217
|
+
forceUpdate({});
|
|
218
|
+
});
|
|
219
|
+
ctx.setKeyBinding([
|
|
220
|
+
{ key: "leftArrow", caption: "navigate", action: "moveLeft", order: 0 },
|
|
221
|
+
{ key: "rightArrow", caption: "navigate", action: "moveRight", order: 0 },
|
|
222
|
+
{ key: "upArrow", caption: "navigate", action: "moveUp", order: 0 },
|
|
223
|
+
{ key: "downArrow", caption: "navigate", action: "moveDown", order: 0 }
|
|
224
|
+
]);
|
|
225
|
+
ctx.addFooter(`Total: ${items.length} items`);
|
|
226
|
+
}, []);
|
|
227
|
+
const selectedIndex = selectedIndexRef.current;
|
|
228
|
+
const selectedItem = items[selectedIndex];
|
|
229
|
+
const rows = [];
|
|
230
|
+
for (let row = 0; row < itemsPerColumn; row++) {
|
|
231
|
+
const cols = [];
|
|
232
|
+
for (let col = 0; col < columns; col++) {
|
|
233
|
+
const index = col * itemsPerColumn + row;
|
|
234
|
+
if (index < items.length) {
|
|
235
|
+
const isSelected = index === selectedIndex;
|
|
236
|
+
cols.push(
|
|
237
|
+
h2(
|
|
238
|
+
import_ink2.Box,
|
|
239
|
+
{ key: index, width: columnWidth },
|
|
240
|
+
h2(import_ink2.Text, {
|
|
241
|
+
color: isSelected ? "black" : "white",
|
|
242
|
+
backgroundColor: isSelected ? "cyan" : void 0,
|
|
243
|
+
bold: isSelected
|
|
244
|
+
}, items[index].padEnd(maxItemLength))
|
|
245
|
+
)
|
|
246
|
+
);
|
|
247
|
+
}
|
|
170
248
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
249
|
+
rows.push(
|
|
250
|
+
h2(ScreenRow, { key: row, children: h2(import_ink2.Box, { flexDirection: "row" }, ...cols) })
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
const previewContent = getPreviewContent ? getPreviewContent(selectedItem) : selectedItem;
|
|
254
|
+
const previewRows = [];
|
|
255
|
+
if (typeof previewContent === "string") {
|
|
256
|
+
previewRows.push(h2(ScreenRow, { key: "preview-string", children: h2(import_ink2.Text, { bold: true }, previewContent) }));
|
|
257
|
+
} else if (typeof previewContent === "object" && !import_react2.default.isValidElement(previewContent) && previewContent !== null) {
|
|
258
|
+
Object.entries(previewContent).forEach(([key, value], idx) => {
|
|
259
|
+
previewRows.push(h2(ScreenRow, { key: `preview-${key}-${idx}`, children: h2(import_ink2.Text, {}, `${key}: ${value}`) }));
|
|
260
|
+
});
|
|
261
|
+
} else if (import_react2.default.isValidElement(previewContent)) {
|
|
262
|
+
previewRows.push(h2(ScreenRow, { key: "preview-element", children: previewContent }));
|
|
263
|
+
}
|
|
264
|
+
return h2(
|
|
265
|
+
import_ink2.Box,
|
|
266
|
+
{ flexDirection: "column" },
|
|
267
|
+
...rows,
|
|
268
|
+
h2(ScreenRow, { key: "spacer-1", children: h2(import_ink2.Text, {}, " ") }),
|
|
269
|
+
h2(ScreenDivider, { key: "divider" }),
|
|
270
|
+
h2(ScreenRow, { key: "spacer-2", children: h2(import_ink2.Text, {}, " ") }),
|
|
271
|
+
...previewRows
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sortable = false, maxHeight, sortHighlightStyle, selectionMarker = " " }) {
|
|
275
|
+
const [, forceUpdate] = (0, import_react2.useState)({});
|
|
276
|
+
const [sortOrder, setSortOrder] = (0, import_react2.useState)("none");
|
|
277
|
+
const [scrollOffset, setScrollOffset] = (0, import_react2.useState)(0);
|
|
278
|
+
const scrollStateRef = (0, import_react2.useRef)({ scrollOffset: 0, maxHeight: 0, totalItems: 0 });
|
|
279
|
+
const defaultGetTitle = (item) => {
|
|
280
|
+
return getTitle ? getTitle(item) : typeof item.value === "string" ? item.value : item.value?.title || item.name;
|
|
281
|
+
};
|
|
282
|
+
const titleGetter = getTitle || defaultGetTitle;
|
|
283
|
+
const displayItems = sortable && sortOrder !== "none" ? [...items].sort((a, b) => {
|
|
284
|
+
const titleA = titleGetter(a).toLowerCase();
|
|
285
|
+
const titleB = titleGetter(b).toLowerCase();
|
|
286
|
+
if (sortOrder === "asc") {
|
|
287
|
+
return titleA < titleB ? -1 : titleA > titleB ? 1 : 0;
|
|
288
|
+
} else {
|
|
289
|
+
return titleA > titleB ? -1 : titleA < titleB ? 1 : 0;
|
|
290
|
+
}
|
|
291
|
+
}) : items;
|
|
292
|
+
const effectiveMaxHeight = maxHeight || displayItems.length;
|
|
293
|
+
const canScroll = displayItems.length > effectiveMaxHeight;
|
|
294
|
+
const maxScrollOffset = Math.max(0, displayItems.length - effectiveMaxHeight);
|
|
295
|
+
const clampedScrollOffset = Math.min(Math.max(0, scrollOffset), maxScrollOffset);
|
|
296
|
+
const visibleItems = displayItems.slice(clampedScrollOffset, clampedScrollOffset + effectiveMaxHeight);
|
|
297
|
+
const canScrollUp = clampedScrollOffset > 0;
|
|
298
|
+
const canScrollDown = clampedScrollOffset < maxScrollOffset;
|
|
299
|
+
scrollStateRef.current = { scrollOffset, maxHeight: effectiveMaxHeight, totalItems: displayItems.length };
|
|
300
|
+
(0, import_react2.useEffect)(() => {
|
|
301
|
+
ctx.setAction("moveUp", () => {
|
|
302
|
+
const newIndex = Math.max(0, selectedIndexRef.current - 1);
|
|
303
|
+
selectedIndexRef.current = newIndex;
|
|
304
|
+
const { scrollOffset: currentScrollOffset, maxHeight: currentMaxHeight, totalItems } = scrollStateRef.current;
|
|
305
|
+
const currentMaxScrollOffset = Math.max(0, totalItems - currentMaxHeight);
|
|
306
|
+
const currentClampedScrollOffset = Math.min(Math.max(0, currentScrollOffset), currentMaxScrollOffset);
|
|
307
|
+
if (newIndex < currentClampedScrollOffset) {
|
|
308
|
+
setScrollOffset(newIndex);
|
|
309
|
+
}
|
|
310
|
+
forceUpdate({});
|
|
311
|
+
});
|
|
312
|
+
ctx.setAction("moveDown", () => {
|
|
313
|
+
const currentItems = sortable && sortOrder !== "none" ? [...items].sort((a, b) => {
|
|
314
|
+
const titleA = titleGetter(a).toLowerCase();
|
|
315
|
+
const titleB = titleGetter(b).toLowerCase();
|
|
316
|
+
if (sortOrder === "asc") {
|
|
317
|
+
return titleA < titleB ? -1 : titleA > titleB ? 1 : 0;
|
|
176
318
|
} else {
|
|
177
|
-
|
|
319
|
+
return titleA > titleB ? -1 : titleA < titleB ? 1 : 0;
|
|
178
320
|
}
|
|
321
|
+
}) : items;
|
|
322
|
+
const maxIndex = currentItems.length - 1;
|
|
323
|
+
const newIndex = Math.min(maxIndex, selectedIndexRef.current + 1);
|
|
324
|
+
selectedIndexRef.current = newIndex;
|
|
325
|
+
const { scrollOffset: currentScrollOffset, maxHeight: currentMaxHeight, totalItems } = scrollStateRef.current;
|
|
326
|
+
const currentMaxScrollOffset = Math.max(0, totalItems - currentMaxHeight);
|
|
327
|
+
const currentClampedScrollOffset = Math.min(Math.max(0, currentScrollOffset), currentMaxScrollOffset);
|
|
328
|
+
if (newIndex >= currentClampedScrollOffset + currentMaxHeight) {
|
|
329
|
+
setScrollOffset(newIndex - currentMaxHeight + 1);
|
|
179
330
|
}
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const
|
|
184
|
-
const
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
331
|
+
forceUpdate({});
|
|
332
|
+
});
|
|
333
|
+
ctx.setAction("scrollUp", () => {
|
|
334
|
+
const { scrollOffset: currentScrollOffset, maxHeight: currentMaxHeight, totalItems } = scrollStateRef.current;
|
|
335
|
+
const currentMaxScrollOffset = Math.max(0, totalItems - currentMaxHeight);
|
|
336
|
+
const newScrollOffset = Math.max(0, currentScrollOffset - 1);
|
|
337
|
+
setScrollOffset(newScrollOffset);
|
|
338
|
+
forceUpdate({});
|
|
339
|
+
});
|
|
340
|
+
ctx.setAction("scrollDown", () => {
|
|
341
|
+
const { scrollOffset: currentScrollOffset, maxHeight: currentMaxHeight, totalItems } = scrollStateRef.current;
|
|
342
|
+
const currentMaxScrollOffset = Math.max(0, totalItems - currentMaxHeight);
|
|
343
|
+
const newScrollOffset = Math.min(currentMaxScrollOffset, currentScrollOffset + 1);
|
|
344
|
+
setScrollOffset(newScrollOffset);
|
|
345
|
+
forceUpdate({});
|
|
346
|
+
});
|
|
347
|
+
if (sortable) {
|
|
348
|
+
ctx.setAction("toggleSort", () => {
|
|
349
|
+
const nextSort = sortOrder === "none" ? "asc" : sortOrder === "asc" ? "desc" : "none";
|
|
350
|
+
const currentSelectedItem = displayItems[selectedIndexRef.current];
|
|
351
|
+
setSortOrder(nextSort);
|
|
352
|
+
const newSortedItems = nextSort !== "none" ? [...items].sort((a, b) => {
|
|
353
|
+
const titleA = titleGetter(a).toLowerCase();
|
|
354
|
+
const titleB = titleGetter(b).toLowerCase();
|
|
355
|
+
if (nextSort === "asc") {
|
|
356
|
+
return titleA < titleB ? -1 : titleA > titleB ? 1 : 0;
|
|
191
357
|
} else {
|
|
192
|
-
|
|
358
|
+
return titleA > titleB ? -1 : titleA < titleB ? 1 : 0;
|
|
193
359
|
}
|
|
360
|
+
}) : items;
|
|
361
|
+
const newIndex = newSortedItems.findIndex((item) => item === currentSelectedItem);
|
|
362
|
+
if (newIndex !== -1) {
|
|
363
|
+
selectedIndexRef.current = newIndex;
|
|
364
|
+
setScrollOffset(newIndex);
|
|
365
|
+
} else {
|
|
366
|
+
selectedIndexRef.current = 0;
|
|
367
|
+
setScrollOffset(0);
|
|
194
368
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
369
|
+
forceUpdate({});
|
|
370
|
+
});
|
|
371
|
+
const defaultHighlightStyle = {
|
|
372
|
+
color: "black",
|
|
373
|
+
backgroundColor: "green",
|
|
374
|
+
bold: true
|
|
375
|
+
};
|
|
376
|
+
const highlightStyle = { ...defaultHighlightStyle, ...sortHighlightStyle };
|
|
377
|
+
const sortCaption = () => {
|
|
378
|
+
if (sortOrder === "none") {
|
|
379
|
+
return h2(import_ink2.Text, {}, "s to toggle sort");
|
|
198
380
|
} else {
|
|
199
|
-
|
|
381
|
+
const sortLabel = sortOrder === "asc" ? "ASC" : "DESC";
|
|
382
|
+
return h2(
|
|
383
|
+
import_ink2.Text,
|
|
384
|
+
{},
|
|
385
|
+
"s to toggle ",
|
|
386
|
+
h2(import_ink2.Text, { color: "white", bold: true }, "sort"),
|
|
387
|
+
" ",
|
|
388
|
+
h2(import_ink2.Text, highlightStyle, ` ${sortLabel} `)
|
|
389
|
+
);
|
|
200
390
|
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
391
|
+
};
|
|
392
|
+
ctx.setKeyBinding([
|
|
393
|
+
{ key: "upArrow", caption: "navigate", action: "moveUp", order: 0 },
|
|
394
|
+
{ key: "downArrow", caption: "navigate", action: "moveDown", order: 0 },
|
|
395
|
+
{
|
|
396
|
+
key: "s",
|
|
397
|
+
caption: sortCaption,
|
|
398
|
+
action: "toggleSort",
|
|
399
|
+
order: 5
|
|
400
|
+
}
|
|
401
|
+
]);
|
|
402
|
+
ctx.update();
|
|
205
403
|
} else {
|
|
206
|
-
|
|
207
|
-
|
|
404
|
+
ctx.setKeyBinding([
|
|
405
|
+
{ key: "upArrow", caption: "navigate", action: "moveUp", order: 0 },
|
|
406
|
+
{ key: "downArrow", caption: "navigate", action: "moveDown", order: 0 }
|
|
407
|
+
]);
|
|
208
408
|
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
409
|
+
}, [sortOrder, sortable]);
|
|
410
|
+
const selectedIndex = selectedIndexRef.current;
|
|
411
|
+
const defaultRenderItem = (item, isSelected, displayIndex, actualIndex) => {
|
|
412
|
+
const isFirstVisible = displayIndex === 0;
|
|
413
|
+
const isLastVisible = displayIndex === visibleItems.length - 1;
|
|
414
|
+
let arrowPrefix = "";
|
|
415
|
+
let selectionPrefix = "";
|
|
416
|
+
if (isFirstVisible && canScrollUp) {
|
|
417
|
+
arrowPrefix = "\u2191 ";
|
|
418
|
+
} else if (isLastVisible && canScrollDown) {
|
|
419
|
+
arrowPrefix = "\u2193 ";
|
|
420
|
+
} else {
|
|
421
|
+
arrowPrefix = " ";
|
|
216
422
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Set a value with proper categorization
|
|
221
|
-
*/
|
|
222
|
-
setValue(key, value) {
|
|
223
|
-
const resolvedKey = this.aliases[key] || key;
|
|
224
|
-
if (typeof value === "boolean") {
|
|
225
|
-
this.flags[resolvedKey] = value;
|
|
423
|
+
if (isSelected) {
|
|
424
|
+
selectionPrefix = selectionMarker;
|
|
226
425
|
} else {
|
|
227
|
-
|
|
426
|
+
selectionPrefix = " ".repeat(selectionMarker.length);
|
|
228
427
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
428
|
+
return h2(
|
|
429
|
+
import_ink2.Box,
|
|
430
|
+
{ flexDirection: "row" },
|
|
431
|
+
// Arrow (clickable if functional, not highlighted)
|
|
432
|
+
h2(import_ink2.Text, {
|
|
433
|
+
key: `arrow-${actualIndex}`,
|
|
434
|
+
color: "white"
|
|
435
|
+
}, arrowPrefix),
|
|
436
|
+
// Selection marker space (always same width, not highlighted)
|
|
437
|
+
h2(import_ink2.Text, { key: `marker-${actualIndex}`, color: "white" }, selectionPrefix),
|
|
438
|
+
// Item name (highlighted if selected)
|
|
439
|
+
h2(import_ink2.Text, {
|
|
440
|
+
key: `name-${actualIndex}`,
|
|
441
|
+
color: isSelected ? "black" : "white",
|
|
442
|
+
backgroundColor: isSelected ? "cyan" : void 0,
|
|
443
|
+
bold: isSelected
|
|
444
|
+
}, item.name)
|
|
445
|
+
);
|
|
446
|
+
};
|
|
447
|
+
const itemRenderer = renderItem || defaultRenderItem;
|
|
448
|
+
return h2(
|
|
449
|
+
import_ink2.Box,
|
|
450
|
+
{ flexDirection: "column" },
|
|
451
|
+
...visibleItems.map((item, displayIndex) => {
|
|
452
|
+
const actualIndex = clampedScrollOffset + displayIndex;
|
|
453
|
+
const isSelected = actualIndex === selectedIndex;
|
|
454
|
+
if (renderItem) {
|
|
455
|
+
const isFirstVisible = displayIndex === 0;
|
|
456
|
+
const isLastVisible = displayIndex === visibleItems.length - 1;
|
|
457
|
+
let arrowPrefix = "";
|
|
458
|
+
let selectionPrefix = "";
|
|
459
|
+
if (isFirstVisible && canScrollUp) {
|
|
460
|
+
arrowPrefix = "\u2191 ";
|
|
461
|
+
} else if (isLastVisible && canScrollDown) {
|
|
462
|
+
arrowPrefix = "\u2193 ";
|
|
463
|
+
} else {
|
|
464
|
+
arrowPrefix = " ";
|
|
465
|
+
}
|
|
466
|
+
if (isSelected) {
|
|
467
|
+
selectionPrefix = selectionMarker;
|
|
468
|
+
} else {
|
|
469
|
+
selectionPrefix = " ".repeat(selectionMarker.length);
|
|
470
|
+
}
|
|
471
|
+
return h2(ScreenRow, {
|
|
472
|
+
key: `item-${actualIndex}`,
|
|
473
|
+
children: h2(
|
|
474
|
+
import_ink2.Box,
|
|
475
|
+
{ flexDirection: "row" },
|
|
476
|
+
// Arrow (clickable if functional, not highlighted)
|
|
477
|
+
h2(import_ink2.Text, {
|
|
478
|
+
key: `arrow-${actualIndex}`,
|
|
479
|
+
color: "white"
|
|
480
|
+
}, arrowPrefix),
|
|
481
|
+
// Selection marker space (always same width, not highlighted)
|
|
482
|
+
h2(import_ink2.Text, { key: `marker-${actualIndex}`, color: "white" }, selectionPrefix),
|
|
483
|
+
// Custom rendered content
|
|
484
|
+
renderItem(item, isSelected, displayIndex)
|
|
485
|
+
)
|
|
486
|
+
});
|
|
487
|
+
} else {
|
|
488
|
+
return h2(ScreenRow, {
|
|
489
|
+
key: `item-${actualIndex}`,
|
|
490
|
+
children: itemRenderer(item, isSelected, displayIndex, actualIndex)
|
|
491
|
+
});
|
|
241
492
|
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
493
|
+
})
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
var import_react2, import_ink2, h2;
|
|
497
|
+
var init_list_components = __esm({
|
|
498
|
+
"src/screen/list-components.ts"() {
|
|
499
|
+
"use strict";
|
|
500
|
+
import_react2 = __toESM(require("react"), 1);
|
|
501
|
+
import_ink2 = require("ink");
|
|
502
|
+
init_components();
|
|
503
|
+
h2 = import_react2.createElement;
|
|
246
504
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
return this.args[lcKey];
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
// src/screen/screens.ts
|
|
508
|
+
function groupKeyBindings(bindings) {
|
|
509
|
+
const groups = {};
|
|
510
|
+
const enabledBindings = bindings.filter((b) => b.enabled !== false);
|
|
511
|
+
enabledBindings.forEach((binding) => {
|
|
512
|
+
const caption = typeof binding.caption === "string" ? binding.caption : "";
|
|
513
|
+
if (!groups[caption]) {
|
|
514
|
+
groups[caption] = {
|
|
515
|
+
keys: [],
|
|
516
|
+
caption,
|
|
517
|
+
order: binding.order || 999
|
|
518
|
+
};
|
|
262
519
|
}
|
|
263
|
-
|
|
264
|
-
|
|
520
|
+
groups[caption].keys.push(binding.key);
|
|
521
|
+
});
|
|
522
|
+
return Object.values(groups);
|
|
523
|
+
}
|
|
524
|
+
function formatKeyBindings(bindings, mode = "long") {
|
|
525
|
+
const resolvedBindings = bindings.map((binding) => {
|
|
526
|
+
let resolvedCaption = binding.caption;
|
|
527
|
+
if (typeof binding.caption === "function") {
|
|
528
|
+
resolvedCaption = binding.caption();
|
|
265
529
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
530
|
+
return {
|
|
531
|
+
...binding,
|
|
532
|
+
resolvedCaption
|
|
533
|
+
};
|
|
534
|
+
});
|
|
535
|
+
const groups = groupKeyBindings(resolvedBindings.map((b) => ({
|
|
536
|
+
...b,
|
|
537
|
+
caption: typeof b.resolvedCaption === "string" ? b.resolvedCaption : ""
|
|
538
|
+
})));
|
|
539
|
+
groups.sort((a, b) => a.order - b.order);
|
|
540
|
+
const items = [];
|
|
541
|
+
groups.forEach((group) => {
|
|
542
|
+
const bindingWithCustom = resolvedBindings.find(
|
|
543
|
+
(b) => group.keys.includes(b.key) && typeof b.resolvedCaption !== "string"
|
|
270
544
|
);
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
}
|
|
280
|
-
if (resolvedKey === "env" && process.env.NODE_ENV !== void 0) {
|
|
281
|
-
return process.env.NODE_ENV;
|
|
282
|
-
}
|
|
283
|
-
return void 0;
|
|
284
|
-
}
|
|
285
|
-
/**
|
|
286
|
-
* Set a value (for testing/internal use)
|
|
287
|
-
*/
|
|
288
|
-
set(key, value) {
|
|
289
|
-
this.args[key] = value;
|
|
290
|
-
}
|
|
291
|
-
/**
|
|
292
|
-
* Check if a command exists (case-insensitive)
|
|
293
|
-
*/
|
|
294
|
-
hasCommand(cmd) {
|
|
295
|
-
return this.commands.some((command) => command.toLowerCase() === cmd.toLowerCase());
|
|
296
|
-
}
|
|
297
|
-
/**
|
|
298
|
-
* Get all commands
|
|
299
|
-
*/
|
|
300
|
-
getCommands() {
|
|
301
|
-
return [...this.commands];
|
|
302
|
-
}
|
|
303
|
-
/**
|
|
304
|
-
* Get used keys (as array)
|
|
305
|
-
*/
|
|
306
|
-
getUsed() {
|
|
307
|
-
return Array.from(this.usedKeys);
|
|
308
|
-
}
|
|
309
|
-
/**
|
|
310
|
-
* Get unused keys (as array)
|
|
311
|
-
*/
|
|
312
|
-
getUnused() {
|
|
313
|
-
const unused = [];
|
|
314
|
-
for (const key of Object.keys(this.args)) {
|
|
315
|
-
if (!this.usedKeys.has(key) && !this.nots.includes(key)) {
|
|
316
|
-
unused.push(key);
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
return unused;
|
|
320
|
-
}
|
|
321
|
-
/**
|
|
322
|
-
* Convert key to environment variable format
|
|
323
|
-
*/
|
|
324
|
-
toEnvKey(key) {
|
|
325
|
-
return key.replace(
|
|
326
|
-
/[A-Z0-9]/g,
|
|
327
|
-
(match, offset) => offset === 0 ? match : "_" + match.toLowerCase()
|
|
328
|
-
).toUpperCase();
|
|
329
|
-
}
|
|
330
|
-
/**
|
|
331
|
-
* Load .env file
|
|
332
|
-
*/
|
|
333
|
-
loadDotEnv() {
|
|
334
|
-
const dotEnvPath = this.get("dotEnvPath") || process.cwd();
|
|
335
|
-
const dotEnvFile = this.get("dotEnvFile") || ".env";
|
|
336
|
-
if (this.get("dotEnvFile")) {
|
|
337
|
-
const customPath = (0, import_path.resolve)(dotEnvPath, dotEnvFile);
|
|
338
|
-
if ((0, import_fs.existsSync)(customPath)) {
|
|
339
|
-
(0, import_dotenv.config)({ path: customPath, quiet: true });
|
|
545
|
+
if (bindingWithCustom && bindingWithCustom.resolvedCaption) {
|
|
546
|
+
items.push(bindingWithCustom.resolvedCaption);
|
|
547
|
+
} else {
|
|
548
|
+
const keyStr = formatKeys(group.keys);
|
|
549
|
+
if (mode === "long") {
|
|
550
|
+
items.push(`${keyStr} to ${group.caption}`);
|
|
551
|
+
} else {
|
|
552
|
+
items.push(keyStr);
|
|
340
553
|
}
|
|
341
|
-
return;
|
|
342
554
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
555
|
+
});
|
|
556
|
+
return items;
|
|
557
|
+
}
|
|
558
|
+
function formatKeys(keys) {
|
|
559
|
+
const keyMap = {
|
|
560
|
+
"escape": "esc",
|
|
561
|
+
"leftArrow": "\u2190",
|
|
562
|
+
"rightArrow": "\u2192",
|
|
563
|
+
"upArrow": "\u2191",
|
|
564
|
+
"downArrow": "\u2193",
|
|
565
|
+
"return": "enter"
|
|
566
|
+
};
|
|
567
|
+
return keys.map((k) => keyMap[k] || k).join("/");
|
|
568
|
+
}
|
|
569
|
+
async function showScreen(config2) {
|
|
570
|
+
const {
|
|
571
|
+
title,
|
|
572
|
+
onRender,
|
|
573
|
+
parentData = {}
|
|
574
|
+
} = config2;
|
|
575
|
+
return new Promise((resolve2) => {
|
|
576
|
+
let instance2;
|
|
577
|
+
const keyBindings = [];
|
|
578
|
+
const actions = {};
|
|
579
|
+
const customFooterItems = [];
|
|
580
|
+
let renderResult = null;
|
|
581
|
+
let initialized = false;
|
|
582
|
+
const Screen = () => {
|
|
583
|
+
const [updateCounter, setUpdateCounter] = (0, import_react3.useState)(0);
|
|
584
|
+
if (!initialized) {
|
|
585
|
+
const defaultBindings = [
|
|
586
|
+
{ key: "escape", caption: "go back", action: "back", protected: true, order: 1 },
|
|
587
|
+
{ key: "leftArrow", caption: "go back", action: "back", protected: false, order: 1 }
|
|
588
|
+
// Note: 'select' is not a default - components add it if needed
|
|
589
|
+
];
|
|
590
|
+
defaultBindings.forEach((binding) => {
|
|
591
|
+
keyBindings.push(binding);
|
|
592
|
+
});
|
|
593
|
+
actions.back = () => {
|
|
594
|
+
cleanup(null);
|
|
595
|
+
};
|
|
596
|
+
initialized = true;
|
|
354
597
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
598
|
+
const context = {
|
|
599
|
+
setAction: (actionName, handlerFn) => {
|
|
600
|
+
actions[actionName] = handlerFn;
|
|
601
|
+
},
|
|
602
|
+
setKeyBinding: (bindingOrBindings) => {
|
|
603
|
+
const bindingsToSet = Array.isArray(bindingOrBindings) ? bindingOrBindings : [bindingOrBindings];
|
|
604
|
+
bindingsToSet.forEach((binding) => {
|
|
605
|
+
const existingIndex = keyBindings.findIndex((b) => b.key === binding.key);
|
|
606
|
+
if (existingIndex >= 0) {
|
|
607
|
+
const existing = keyBindings[existingIndex];
|
|
608
|
+
if (existing.protected) {
|
|
609
|
+
console.warn(`Cannot override protected key: ${binding.key}`);
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
keyBindings[existingIndex] = {
|
|
613
|
+
...existing,
|
|
614
|
+
...binding,
|
|
615
|
+
order: binding.order !== void 0 ? binding.order : existing.order,
|
|
616
|
+
enabled: binding.enabled !== void 0 ? binding.enabled : existing.enabled !== void 0 ? existing.enabled : true
|
|
617
|
+
};
|
|
618
|
+
} else {
|
|
619
|
+
keyBindings.push({
|
|
620
|
+
protected: false,
|
|
621
|
+
order: 999,
|
|
622
|
+
enabled: true,
|
|
623
|
+
...binding
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
});
|
|
627
|
+
},
|
|
628
|
+
updateKeyBinding: (keyName, updates) => {
|
|
629
|
+
const index = keyBindings.findIndex((b) => b.key === keyName);
|
|
630
|
+
if (index >= 0) {
|
|
631
|
+
keyBindings[index] = {
|
|
632
|
+
...keyBindings[index],
|
|
633
|
+
...updates
|
|
634
|
+
};
|
|
366
635
|
}
|
|
367
|
-
}
|
|
636
|
+
},
|
|
637
|
+
removeKeyBinding: (keyName) => {
|
|
638
|
+
const index = keyBindings.findIndex((b) => b.key === keyName);
|
|
639
|
+
if (index >= 0) {
|
|
640
|
+
if (keyBindings[index].protected) {
|
|
641
|
+
console.warn(`Cannot remove protected key: ${keyName}`);
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
keyBindings.splice(index, 1);
|
|
645
|
+
}
|
|
646
|
+
},
|
|
647
|
+
addFooter: (item) => {
|
|
648
|
+
customFooterItems.push(item);
|
|
649
|
+
},
|
|
650
|
+
clearFooter: () => {
|
|
651
|
+
customFooterItems.length = 0;
|
|
652
|
+
},
|
|
653
|
+
setFooter: (items) => {
|
|
654
|
+
customFooterItems.length = 0;
|
|
655
|
+
const itemsArray = Array.isArray(items) ? items : [items];
|
|
656
|
+
customFooterItems.push(...itemsArray);
|
|
657
|
+
},
|
|
658
|
+
update: () => {
|
|
659
|
+
setUpdateCounter((c) => c + 1);
|
|
660
|
+
},
|
|
661
|
+
goBack: () => {
|
|
662
|
+
if (actions.back) {
|
|
663
|
+
actions.back();
|
|
664
|
+
}
|
|
665
|
+
},
|
|
666
|
+
close: (result) => {
|
|
667
|
+
cleanup(result);
|
|
668
|
+
},
|
|
669
|
+
parentData
|
|
670
|
+
};
|
|
671
|
+
if (!renderResult) {
|
|
672
|
+
renderResult = onRender(context);
|
|
368
673
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
/**
|
|
375
|
-
* Load configuration files
|
|
376
|
-
*/
|
|
377
|
-
loadConfigFiles() {
|
|
378
|
-
this.configsLoaded = [];
|
|
379
|
-
this.configValues = {};
|
|
380
|
-
const _defaultConfigExtension = this.get("defaultConfigExtension") || "js";
|
|
381
|
-
const optConfigFiles = this.get("config") || this.get("configs") || "";
|
|
382
|
-
const configFiles = optConfigFiles ? optConfigFiles.split(/,\s*/) : [];
|
|
383
|
-
const optConfigFilePath = this.get("configPath");
|
|
384
|
-
if (configFiles.length > 0) {
|
|
385
|
-
for (const cfgFile of configFiles) {
|
|
386
|
-
let notLoaded = false;
|
|
387
|
-
let notLoadedEnvSpecific = false;
|
|
388
|
-
const cfgFileWithPath = this.resolveFileWithPath(optConfigFilePath, cfgFile);
|
|
389
|
-
try {
|
|
390
|
-
const cfgContents = this.requireConfigFile(cfgFileWithPath);
|
|
391
|
-
this.configValues = { ...this.configValues, ...cfgContents };
|
|
392
|
-
this.configsLoaded.push(cfgFileWithPath);
|
|
393
|
-
} catch {
|
|
394
|
-
notLoaded = true;
|
|
674
|
+
(0, import_ink3.useInput)((input, key) => {
|
|
675
|
+
if (key.ctrl && input === "c") {
|
|
676
|
+
cleanup(null);
|
|
677
|
+
process.exit(0);
|
|
678
|
+
return;
|
|
395
679
|
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
680
|
+
let matchedBinding = null;
|
|
681
|
+
for (const binding of keyBindings) {
|
|
682
|
+
let keyMatches = false;
|
|
683
|
+
if (key[binding.key]) {
|
|
684
|
+
keyMatches = true;
|
|
685
|
+
} else if (input === binding.key) {
|
|
686
|
+
keyMatches = true;
|
|
687
|
+
}
|
|
688
|
+
if (keyMatches) {
|
|
689
|
+
if (binding.enabled === false) {
|
|
690
|
+
continue;
|
|
691
|
+
}
|
|
692
|
+
if (binding.condition && !binding.condition(context)) {
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
matchedBinding = binding;
|
|
696
|
+
break;
|
|
408
697
|
}
|
|
409
|
-
} else {
|
|
410
|
-
notLoadedEnvSpecific = true;
|
|
411
698
|
}
|
|
412
|
-
if (
|
|
413
|
-
|
|
699
|
+
if (matchedBinding && actions[matchedBinding.action]) {
|
|
700
|
+
const actionResult = actions[matchedBinding.action]({
|
|
701
|
+
input,
|
|
702
|
+
key,
|
|
703
|
+
binding: matchedBinding
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
});
|
|
707
|
+
const footerLines = [];
|
|
708
|
+
const bindingItems = formatKeyBindings(keyBindings, "long");
|
|
709
|
+
if (bindingItems.length > 0) {
|
|
710
|
+
const bindingsLine = [];
|
|
711
|
+
bindingItems.forEach((item, idx) => {
|
|
712
|
+
if (idx > 0) {
|
|
713
|
+
bindingsLine.push(", ");
|
|
714
|
+
}
|
|
715
|
+
bindingsLine.push(item);
|
|
716
|
+
});
|
|
717
|
+
const allStrings = bindingItems.every((item) => typeof item === "string");
|
|
718
|
+
if (allStrings) {
|
|
719
|
+
footerLines.push(bindingsLine.join(""));
|
|
720
|
+
} else {
|
|
721
|
+
const wrappedBindingsLine = bindingsLine.map(
|
|
722
|
+
(item) => typeof item === "string" ? (0, import_react3.createElement)(import_ink3.Text, {}, item) : item
|
|
723
|
+
);
|
|
724
|
+
footerLines.push(wrappedBindingsLine);
|
|
414
725
|
}
|
|
415
726
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
const
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
const
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
727
|
+
customFooterItems.forEach((item) => {
|
|
728
|
+
if (typeof item === "string") {
|
|
729
|
+
footerLines.push(item);
|
|
730
|
+
} else {
|
|
731
|
+
footerLines.push(item);
|
|
732
|
+
}
|
|
733
|
+
});
|
|
734
|
+
return (0, import_react3.createElement)(
|
|
735
|
+
ScreenContainer,
|
|
736
|
+
{},
|
|
737
|
+
(0, import_react3.createElement)(ScreenTitle, { text: title }),
|
|
738
|
+
(0, import_react3.createElement)(ScreenDivider),
|
|
739
|
+
(0, import_react3.createElement)(ScreenRow, {}, (0, import_react3.createElement)(import_ink3.Text, {}, " ")),
|
|
740
|
+
renderResult,
|
|
741
|
+
(0, import_react3.createElement)(ScreenRow, {}, (0, import_react3.createElement)(import_ink3.Text, {}, " ")),
|
|
742
|
+
(0, import_react3.createElement)(ScreenDivider),
|
|
743
|
+
(0, import_react3.createElement)(ScreenFooter, { lines: footerLines })
|
|
744
|
+
);
|
|
745
|
+
};
|
|
746
|
+
const cleanup = (result) => {
|
|
747
|
+
if (instance2) instance2.unmount();
|
|
748
|
+
setTimeout(() => resolve2(result), 50);
|
|
749
|
+
};
|
|
750
|
+
instance2 = (0, import_ink3.render)((0, import_react3.createElement)(Screen));
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
async function showListScreen(config2) {
|
|
754
|
+
const { title, items, onSelect, onEscape, parentData, initialSelectedIndex = 0, renderItem, getTitle, sortable, maxHeight, sortHighlightStyle, selectionMarker } = config2;
|
|
755
|
+
return showScreen({
|
|
756
|
+
title,
|
|
757
|
+
parentData,
|
|
758
|
+
onRender: (ctx) => {
|
|
759
|
+
const selectedIndexRef = { current: initialSelectedIndex };
|
|
760
|
+
ctx.setAction("select", () => {
|
|
761
|
+
const selected = items[selectedIndexRef.current];
|
|
762
|
+
if (onSelect) {
|
|
763
|
+
const result = onSelect(selected.value, selectedIndexRef.current);
|
|
764
|
+
ctx.close(result);
|
|
765
|
+
}
|
|
766
|
+
});
|
|
767
|
+
if (onEscape) {
|
|
768
|
+
ctx.setAction("back", () => {
|
|
769
|
+
const result = onEscape(selectedIndexRef.current);
|
|
770
|
+
ctx.close(result);
|
|
771
|
+
});
|
|
456
772
|
}
|
|
457
|
-
|
|
458
|
-
|
|
773
|
+
ctx.setKeyBinding({ key: "return", caption: "select", action: "select", order: 2 });
|
|
774
|
+
return (0, import_react3.createElement)(ListComponent, { items, ctx, selectedIndexRef, renderItem, getTitle, sortable, maxHeight, sortHighlightStyle, selectionMarker });
|
|
775
|
+
}
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
async function showMultiColumnListScreen(config2) {
|
|
779
|
+
const { title, items, onSelect, onEscape, parentData, initialSelectedIndex = 0 } = config2;
|
|
780
|
+
return showScreen({
|
|
781
|
+
title,
|
|
782
|
+
parentData,
|
|
783
|
+
onRender: (ctx) => {
|
|
784
|
+
const selectedIndexRef = { current: initialSelectedIndex };
|
|
785
|
+
ctx.setAction("select", () => {
|
|
786
|
+
const selected = items[selectedIndexRef.current];
|
|
787
|
+
if (onSelect) {
|
|
788
|
+
const result = onSelect(selected, selectedIndexRef.current);
|
|
789
|
+
ctx.close(result);
|
|
790
|
+
}
|
|
791
|
+
});
|
|
792
|
+
if (onEscape) {
|
|
793
|
+
ctx.setAction("back", () => {
|
|
794
|
+
const result = onEscape(selectedIndexRef.current);
|
|
795
|
+
ctx.close(result);
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
ctx.setKeyBinding({ key: "return", caption: "select", action: "select", order: 2 });
|
|
799
|
+
return (0, import_react3.createElement)(MultiColumnListComponent, { items, ctx, selectedIndexRef });
|
|
800
|
+
}
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
async function showMultiColumnListWithPreviewScreen(config2) {
|
|
804
|
+
const { title, items, getPreviewContent, onSelect, onEscape, parentData, initialSelectedIndex = 0 } = config2;
|
|
805
|
+
return showScreen({
|
|
806
|
+
title,
|
|
807
|
+
parentData,
|
|
808
|
+
onRender: (ctx) => {
|
|
809
|
+
const selectedIndexRef = { current: initialSelectedIndex };
|
|
810
|
+
ctx.setAction("select", () => {
|
|
811
|
+
const selected = items[selectedIndexRef.current];
|
|
812
|
+
if (onSelect) {
|
|
813
|
+
const result = onSelect(selected, selectedIndexRef.current);
|
|
814
|
+
ctx.close(result);
|
|
815
|
+
}
|
|
816
|
+
});
|
|
817
|
+
if (onEscape) {
|
|
818
|
+
ctx.setAction("back", () => {
|
|
819
|
+
const result = onEscape(selectedIndexRef.current);
|
|
820
|
+
ctx.close(result);
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
ctx.setKeyBinding({ key: "return", caption: "select", action: "select", order: 2 });
|
|
824
|
+
return (0, import_react3.createElement)(MultiColumnListWithPreviewComponent, { items, getPreviewContent, ctx, selectedIndexRef });
|
|
459
825
|
}
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
var import_react3, import_ink3, showMenuScreen, showWordGridScreen;
|
|
829
|
+
var init_screens = __esm({
|
|
830
|
+
"src/screen/screens.ts"() {
|
|
831
|
+
"use strict";
|
|
832
|
+
import_react3 = require("react");
|
|
833
|
+
import_ink3 = require("ink");
|
|
834
|
+
init_components();
|
|
835
|
+
init_list_components();
|
|
836
|
+
showMenuScreen = showListScreen;
|
|
837
|
+
showWordGridScreen = showMultiColumnListScreen;
|
|
460
838
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
839
|
+
});
|
|
840
|
+
|
|
841
|
+
// src/screen/ui-elements.ts
|
|
842
|
+
function ListItem({
|
|
843
|
+
children,
|
|
844
|
+
isSelected = false,
|
|
845
|
+
color = "white",
|
|
846
|
+
backgroundColor,
|
|
847
|
+
bold = false,
|
|
848
|
+
dimColor = false
|
|
849
|
+
}) {
|
|
850
|
+
return (0, import_react4.createElement)(
|
|
851
|
+
import_ink4.Box,
|
|
852
|
+
{},
|
|
853
|
+
(0, import_react4.createElement)(import_ink4.Text, {
|
|
854
|
+
color: isSelected ? backgroundColor || "green" : color,
|
|
855
|
+
backgroundColor: isSelected ? color : backgroundColor,
|
|
856
|
+
bold: isSelected || bold,
|
|
857
|
+
dimColor: !isSelected && dimColor
|
|
858
|
+
}, children)
|
|
859
|
+
);
|
|
860
|
+
}
|
|
861
|
+
function TextBlock({
|
|
862
|
+
text,
|
|
863
|
+
color = "white",
|
|
864
|
+
dimmed = false,
|
|
865
|
+
bold = false,
|
|
866
|
+
maxWidth
|
|
867
|
+
}) {
|
|
868
|
+
return (0, import_react4.createElement)(
|
|
869
|
+
import_ink4.Box,
|
|
870
|
+
{},
|
|
871
|
+
(0, import_react4.createElement)(import_ink4.Text, {
|
|
872
|
+
color,
|
|
873
|
+
dimColor: dimmed,
|
|
874
|
+
bold
|
|
875
|
+
}, text)
|
|
876
|
+
);
|
|
877
|
+
}
|
|
878
|
+
function Divider({ character = "\u2500", width = 80 }) {
|
|
879
|
+
return (0, import_react4.createElement)(
|
|
880
|
+
import_ink4.Box,
|
|
881
|
+
{ marginY: 1 },
|
|
882
|
+
(0, import_react4.createElement)(import_ink4.Text, { dimColor: true }, character.repeat(width))
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
function GridCell({
|
|
886
|
+
children,
|
|
887
|
+
width,
|
|
888
|
+
color = "white",
|
|
889
|
+
backgroundColor,
|
|
890
|
+
bold = false,
|
|
891
|
+
dimColor = false,
|
|
892
|
+
align = "left"
|
|
893
|
+
}) {
|
|
894
|
+
return (0, import_react4.createElement)(
|
|
895
|
+
import_ink4.Box,
|
|
896
|
+
{ width },
|
|
897
|
+
(0, import_react4.createElement)(import_ink4.Text, {
|
|
898
|
+
color,
|
|
899
|
+
backgroundColor,
|
|
900
|
+
bold,
|
|
901
|
+
dimColor,
|
|
902
|
+
textAlign: align
|
|
903
|
+
}, children)
|
|
904
|
+
);
|
|
905
|
+
}
|
|
906
|
+
function InputField({ prompt, value, onChange, onSubmit }) {
|
|
907
|
+
return (0, import_react4.createElement)(
|
|
908
|
+
import_ink4.Box,
|
|
909
|
+
{ flexDirection: "column" },
|
|
910
|
+
(0, import_react4.createElement)(import_ink4.Text, {}, prompt),
|
|
911
|
+
(0, import_react4.createElement)(
|
|
912
|
+
import_ink4.Box,
|
|
913
|
+
{ marginTop: 1 },
|
|
914
|
+
(0, import_react4.createElement)(import_ink4.Text, { color: "cyan" }, " > ", value, "_")
|
|
915
|
+
)
|
|
916
|
+
);
|
|
917
|
+
}
|
|
918
|
+
var import_react4, import_ink4;
|
|
919
|
+
var init_ui_elements = __esm({
|
|
920
|
+
"src/screen/ui-elements.ts"() {
|
|
921
|
+
"use strict";
|
|
922
|
+
import_react4 = require("react");
|
|
923
|
+
import_ink4 = require("ink");
|
|
471
924
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
// src/screen/utils.ts
|
|
928
|
+
function buildBreadcrumb(parts) {
|
|
929
|
+
if (parts.length === 0) return "";
|
|
930
|
+
if (parts.length === 1) return parts[0];
|
|
931
|
+
return parts.slice(1).map((part) => `\u2190 ${part}`).join(" ");
|
|
932
|
+
}
|
|
933
|
+
function buildDetailBreadcrumb(path4, suffix = "") {
|
|
934
|
+
if (path4.length <= 1) {
|
|
935
|
+
return suffix ? `\u2190 ${suffix}` : path4[0] || "";
|
|
483
936
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
function getArgsInstance() {
|
|
487
|
-
return instance;
|
|
937
|
+
const breadcrumb = buildBreadcrumb(path4);
|
|
938
|
+
return suffix ? `${breadcrumb} ${suffix}` : breadcrumb;
|
|
488
939
|
}
|
|
940
|
+
var init_utils = __esm({
|
|
941
|
+
"src/screen/utils.ts"() {
|
|
942
|
+
"use strict";
|
|
943
|
+
}
|
|
944
|
+
});
|
|
489
945
|
|
|
490
|
-
// src/
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
946
|
+
// src/screen/footer-builder.ts
|
|
947
|
+
function buildFooter(config2 = {}) {
|
|
948
|
+
const {
|
|
949
|
+
navigation = null,
|
|
950
|
+
actions = null,
|
|
951
|
+
info = null,
|
|
952
|
+
escape = "Esc to go back",
|
|
953
|
+
custom = null
|
|
954
|
+
} = config2;
|
|
955
|
+
const lines = [];
|
|
956
|
+
const mainParts = [];
|
|
957
|
+
if (navigation) {
|
|
958
|
+
mainParts.push(navigation);
|
|
498
959
|
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
constructor(message) {
|
|
502
|
-
super(message);
|
|
503
|
-
this.name = "ParamError";
|
|
960
|
+
if (actions) {
|
|
961
|
+
mainParts.push(actions);
|
|
504
962
|
}
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
// src/params/custom-types.ts
|
|
508
|
-
var joiEdateType = (value, helpers) => {
|
|
509
|
-
if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z$/.test(value)) {
|
|
510
|
-
const testDate = new Date(value);
|
|
511
|
-
if (!isNaN(testDate.getTime())) {
|
|
512
|
-
return value;
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
if (value instanceof Date) {
|
|
516
|
-
return value.toISOString();
|
|
517
|
-
}
|
|
518
|
-
if (typeof value !== "string") {
|
|
519
|
-
value = String(value);
|
|
520
|
-
}
|
|
521
|
-
if (value.toLowerCase() === "now") {
|
|
522
|
-
return (/* @__PURE__ */ new Date()).toISOString();
|
|
523
|
-
}
|
|
524
|
-
const referenceRegex = /^@(\w+)([+-]\d+[smhdwy])$/i;
|
|
525
|
-
const referenceMatch = value.match(referenceRegex);
|
|
526
|
-
if (referenceMatch) {
|
|
527
|
-
const [, paramName, relativeExpr] = referenceMatch;
|
|
528
|
-
const context = helpers.prefs?.context;
|
|
529
|
-
if (!context || !context.params) {
|
|
530
|
-
throw new ParamError(`Cannot resolve cross-parameter reference @${paramName}: context not available. Ensure parameters are processed with proper context.`);
|
|
531
|
-
}
|
|
532
|
-
const referencedValue = context.params[paramName];
|
|
533
|
-
if (referencedValue === void 0 || referencedValue === null) {
|
|
534
|
-
throw new ParamError(`Cannot resolve @${paramName}: parameter "${paramName}" is not defined or has no value. Parameters are evaluated left-to-right.`);
|
|
535
|
-
}
|
|
536
|
-
let referenceDate;
|
|
537
|
-
if (referencedValue instanceof Date) {
|
|
538
|
-
referenceDate = referencedValue;
|
|
539
|
-
} else if (typeof referencedValue === "string") {
|
|
540
|
-
referenceDate = new Date(referencedValue);
|
|
541
|
-
if (isNaN(referenceDate.getTime())) {
|
|
542
|
-
throw new ParamError(`Referenced parameter @${paramName} has invalid date value: ${referencedValue}`);
|
|
543
|
-
}
|
|
544
|
-
} else {
|
|
545
|
-
throw new ParamError(`Referenced parameter @${paramName} is not a valid date type (found: ${typeof referencedValue})`);
|
|
546
|
-
}
|
|
547
|
-
const relativeMatch2 = relativeExpr.match(/^([+-])(\d+)([smhdwy])$/i);
|
|
548
|
-
if (!relativeMatch2) {
|
|
549
|
-
throw new ParamError(`Invalid relative time expression in @${paramName}${relativeExpr}`);
|
|
550
|
-
}
|
|
551
|
-
const [, sign, amount, unit] = relativeMatch2;
|
|
552
|
-
const offset = calculateTimeOffset(parseInt(amount, 10), unit, sign);
|
|
553
|
-
const resultDate = new Date(referenceDate.getTime() + offset);
|
|
554
|
-
return resultDate.toISOString();
|
|
963
|
+
if (escape) {
|
|
964
|
+
mainParts.push(escape);
|
|
555
965
|
}
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
if (relativeMatch) {
|
|
559
|
-
const [, sign, amount, unit] = relativeMatch;
|
|
560
|
-
const numAmount = parseInt(amount, 10);
|
|
561
|
-
if (isNaN(numAmount)) {
|
|
562
|
-
throw new ParamError(`Invalid relative time amount: ${amount}`);
|
|
563
|
-
}
|
|
564
|
-
const offset = calculateTimeOffset(numAmount, unit, sign);
|
|
565
|
-
const resultDate = new Date(Date.now() + offset);
|
|
566
|
-
return resultDate.toISOString();
|
|
966
|
+
if (mainParts.length > 0) {
|
|
967
|
+
lines.push(mainParts.join(", "));
|
|
567
968
|
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
969
|
+
if (info) {
|
|
970
|
+
const infoLines = Array.isArray(info) ? info : [info];
|
|
971
|
+
lines.push(...infoLines);
|
|
571
972
|
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
let multiplier = 1;
|
|
576
|
-
switch (unit.toLowerCase()) {
|
|
577
|
-
case "s":
|
|
578
|
-
multiplier = 1e3;
|
|
579
|
-
break;
|
|
580
|
-
case "m":
|
|
581
|
-
multiplier = 60 * 1e3;
|
|
582
|
-
break;
|
|
583
|
-
case "h":
|
|
584
|
-
multiplier = 60 * 60 * 1e3;
|
|
585
|
-
break;
|
|
586
|
-
case "d":
|
|
587
|
-
multiplier = 24 * 60 * 60 * 1e3;
|
|
588
|
-
break;
|
|
589
|
-
case "w":
|
|
590
|
-
multiplier = 7 * 24 * 60 * 60 * 1e3;
|
|
591
|
-
break;
|
|
592
|
-
case "y":
|
|
593
|
-
multiplier = 365 * 24 * 60 * 60 * 1e3;
|
|
594
|
-
break;
|
|
595
|
-
default:
|
|
596
|
-
throw new ParamError(`Invalid time unit: ${unit}. Supported units: s, m, h, d, w, y`);
|
|
973
|
+
if (custom) {
|
|
974
|
+
const customLines = Array.isArray(custom) ? custom : [custom];
|
|
975
|
+
lines.push(...customLines);
|
|
597
976
|
}
|
|
598
|
-
return
|
|
977
|
+
return lines;
|
|
599
978
|
}
|
|
600
|
-
|
|
601
|
-
if (
|
|
602
|
-
return [];
|
|
979
|
+
function organizeFooterMessages(messages) {
|
|
980
|
+
if (!messages || messages.length === 0) {
|
|
981
|
+
return ["Esc to go back"];
|
|
603
982
|
}
|
|
604
|
-
const
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
983
|
+
const navigation = messages.filter((m) => m.includes("\u2191") || m.includes("\u2193") || m.includes("\u2190") || m.includes("\u2192"));
|
|
984
|
+
const actions = messages.filter((m) => m.includes("Enter") || m.includes("select") || m.includes("submit"));
|
|
985
|
+
const escape = messages.filter((m) => m.includes("Esc"));
|
|
986
|
+
const others = messages.filter(
|
|
987
|
+
(m) => !navigation.includes(m) && !actions.includes(m) && !escape.includes(m)
|
|
988
|
+
);
|
|
989
|
+
const lines = [];
|
|
990
|
+
const mainLine = [...navigation, ...actions, ...escape].join(", ");
|
|
991
|
+
if (mainLine) lines.push(mainLine);
|
|
992
|
+
lines.push(...others);
|
|
993
|
+
return lines;
|
|
994
|
+
}
|
|
995
|
+
var FooterPresets;
|
|
996
|
+
var init_footer_builder = __esm({
|
|
997
|
+
"src/screen/footer-builder.ts"() {
|
|
998
|
+
"use strict";
|
|
999
|
+
FooterPresets = {
|
|
1000
|
+
/**
|
|
1001
|
+
* Menu screen footer
|
|
1002
|
+
*/
|
|
1003
|
+
menu: (customInfo = null) => buildFooter({
|
|
1004
|
+
navigation: "\u2191/\u2193 to navigate",
|
|
1005
|
+
actions: "Enter to select",
|
|
1006
|
+
escape: "Esc to go back",
|
|
1007
|
+
info: customInfo
|
|
1008
|
+
}),
|
|
1009
|
+
/**
|
|
1010
|
+
* Word grid footer
|
|
1011
|
+
*/
|
|
1012
|
+
wordGrid: (totalWords) => buildFooter({
|
|
1013
|
+
navigation: "\u2191\u2193\u2190\u2192 to navigate",
|
|
1014
|
+
actions: "Enter to select",
|
|
1015
|
+
escape: "Esc to go back",
|
|
1016
|
+
info: `Total: ${totalWords} words`
|
|
1017
|
+
}),
|
|
1018
|
+
/**
|
|
1019
|
+
* Text input footer
|
|
1020
|
+
*/
|
|
1021
|
+
textInput: () => buildFooter({
|
|
1022
|
+
actions: "Type and press Enter to submit",
|
|
1023
|
+
escape: "Esc to cancel"
|
|
1024
|
+
}),
|
|
1025
|
+
/**
|
|
1026
|
+
* Info/static screen footer
|
|
1027
|
+
*/
|
|
1028
|
+
info: () => buildFooter({
|
|
1029
|
+
escape: "Esc to continue"
|
|
1030
|
+
}),
|
|
1031
|
+
/**
|
|
1032
|
+
* Main menu footer (escape exits)
|
|
1033
|
+
*/
|
|
1034
|
+
mainMenu: () => buildFooter({
|
|
1035
|
+
navigation: "\u2191/\u2193 to navigate",
|
|
1036
|
+
actions: "Enter to select",
|
|
1037
|
+
escape: "Esc to exit"
|
|
1038
|
+
}),
|
|
1039
|
+
/**
|
|
1040
|
+
* Action menu footer (for word cards, etc.)
|
|
1041
|
+
*/
|
|
1042
|
+
actionMenu: (hasAudio = false) => {
|
|
1043
|
+
const parts = buildFooter({
|
|
1044
|
+
navigation: "\u2191/\u2193 to navigate",
|
|
1045
|
+
actions: "Enter to select",
|
|
1046
|
+
escape: "Esc to go back"
|
|
1047
|
+
});
|
|
1048
|
+
if (hasAudio) {
|
|
1049
|
+
parts.push("Audio available");
|
|
1050
|
+
}
|
|
1051
|
+
return parts;
|
|
615
1052
|
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
} else {
|
|
620
|
-
throw new ParamError(`unknown type "${type}" for array elements`);
|
|
621
|
-
}
|
|
622
|
-
});
|
|
623
|
-
return arr;
|
|
624
|
-
};
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
});
|
|
625
1056
|
|
|
626
|
-
// src/
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
1057
|
+
// src/screen/index.ts
|
|
1058
|
+
async function load() {
|
|
1059
|
+
if (loadPromise) return loadPromise;
|
|
1060
|
+
|
|
1061
|
+
loadPromise = Promise.resolve();
|
|
1062
|
+
return loadPromise;
|
|
1063
|
+
}
|
|
1064
|
+
var import_react5, import_ink5, loadPromise;
|
|
1065
|
+
var init_screen = __esm({
|
|
1066
|
+
"src/screen/index.ts"() {
|
|
1067
|
+
"use strict";
|
|
1068
|
+
import_react5 = __toESM(require("react"), 1);
|
|
1069
|
+
import_ink5 = require("ink");
|
|
1070
|
+
init_screens();
|
|
1071
|
+
init_list_components();
|
|
1072
|
+
init_components();
|
|
1073
|
+
init_ui_elements();
|
|
1074
|
+
init_utils();
|
|
1075
|
+
init_footer_builder();
|
|
1076
|
+
loadPromise = null;
|
|
1077
|
+
if (typeof window === "undefined") {
|
|
1078
|
+
load().catch(() => {
|
|
1079
|
+
});
|
|
637
1080
|
}
|
|
638
1081
|
}
|
|
1082
|
+
});
|
|
1083
|
+
|
|
1084
|
+
// src/index.ts
|
|
1085
|
+
var src_exports = {};
|
|
1086
|
+
__export(src_exports, {
|
|
1087
|
+
Args: () => Args,
|
|
1088
|
+
Box: () => import_ink5.Box,
|
|
1089
|
+
Db: () => Db,
|
|
1090
|
+
Divider: () => Divider,
|
|
1091
|
+
FileDatabase: () => FileDatabase,
|
|
1092
|
+
FileDatabaseError: () => FileDatabaseError,
|
|
1093
|
+
FooterPresets: () => FooterPresets,
|
|
1094
|
+
GridCell: () => GridCell,
|
|
1095
|
+
InputField: () => InputField,
|
|
1096
|
+
ListComponent: () => ListComponent,
|
|
1097
|
+
ListItem: () => ListItem,
|
|
1098
|
+
MultiColumnListComponent: () => MultiColumnListComponent,
|
|
1099
|
+
MultiColumnListWithPreviewComponent: () => MultiColumnListWithPreviewComponent,
|
|
1100
|
+
Params: () => Params,
|
|
1101
|
+
React: () => import_react5.default,
|
|
1102
|
+
ScreenBody: () => ScreenBody,
|
|
1103
|
+
ScreenContainer: () => ScreenContainer,
|
|
1104
|
+
ScreenDivider: () => ScreenDivider,
|
|
1105
|
+
ScreenFooter: () => ScreenFooter,
|
|
1106
|
+
ScreenRow: () => ScreenRow,
|
|
1107
|
+
ScreenTitle: () => ScreenTitle,
|
|
1108
|
+
Text: () => import_ink5.Text,
|
|
1109
|
+
TextBlock: () => TextBlock,
|
|
1110
|
+
buildBreadcrumb: () => buildBreadcrumb,
|
|
1111
|
+
buildDetailBreadcrumb: () => buildDetailBreadcrumb,
|
|
1112
|
+
buildFooter: () => buildFooter,
|
|
1113
|
+
defaultFileSynopsisFunction: () => defaultFileSynopsisFunction,
|
|
1114
|
+
defaultVersionSynopsisFunction: () => defaultVersionSynopsisFunction,
|
|
1115
|
+
getArgsInstance: () => getArgsInstance,
|
|
1116
|
+
getParamsInstance: () => getParamsInstance,
|
|
1117
|
+
h: () => import_react5.createElement,
|
|
1118
|
+
joiEdateType: () => joiEdateType,
|
|
1119
|
+
joiStringArrayType: () => joiStringArrayType,
|
|
1120
|
+
load: () => load,
|
|
1121
|
+
organizeFooterMessages: () => organizeFooterMessages,
|
|
1122
|
+
setupContext: () => setupContext,
|
|
1123
|
+
showListScreen: () => showListScreen,
|
|
1124
|
+
showMenuScreen: () => showMenuScreen,
|
|
1125
|
+
showMultiColumnListScreen: () => showMultiColumnListScreen,
|
|
1126
|
+
showMultiColumnListWithPreviewScreen: () => showMultiColumnListWithPreviewScreen,
|
|
1127
|
+
showScreen: () => showScreen,
|
|
1128
|
+
showWordGridScreen: () => showWordGridScreen,
|
|
1129
|
+
useCallback: () => import_react5.useCallback,
|
|
1130
|
+
useEffect: () => import_react5.useEffect,
|
|
1131
|
+
useInput: () => import_ink5.useInput,
|
|
1132
|
+
useMemo: () => import_react5.useMemo,
|
|
1133
|
+
useRef: () => import_react5.useRef,
|
|
1134
|
+
useState: () => import_react5.useState
|
|
1135
|
+
});
|
|
1136
|
+
module.exports = __toCommonJS(src_exports);
|
|
1137
|
+
|
|
1138
|
+
// src/args/index.ts
|
|
1139
|
+
var import_fs = require("fs");
|
|
1140
|
+
var import_path = require("path");
|
|
1141
|
+
var import_dotenv = require("dotenv");
|
|
1142
|
+
var Args = class {
|
|
1143
|
+
args = {};
|
|
1144
|
+
flags = {};
|
|
1145
|
+
options = {};
|
|
1146
|
+
commands = [];
|
|
1147
|
+
usedKeys = /* @__PURE__ */ new Set();
|
|
1148
|
+
aliases = {};
|
|
1149
|
+
overrides = {};
|
|
1150
|
+
defaults = {};
|
|
1151
|
+
prefixes = [];
|
|
1152
|
+
nots = [];
|
|
1153
|
+
configValues = {};
|
|
1154
|
+
configsLoaded = [];
|
|
1155
|
+
env = "local";
|
|
1156
|
+
constructor(config2 = {}) {
|
|
1157
|
+
this.aliases = config2.aliases || {};
|
|
1158
|
+
this.overrides = config2.overrides || {};
|
|
1159
|
+
this.defaults = config2.defaults || {};
|
|
1160
|
+
this.prefixes = config2.prefixes || ["not", "no"];
|
|
1161
|
+
const args = config2.args || process.argv.slice(2);
|
|
1162
|
+
this.parseArgs(args);
|
|
1163
|
+
this.env = this.get("env")?.toLowerCase() || "local";
|
|
1164
|
+
this.loadDotEnv();
|
|
1165
|
+
this.loadConfigFiles();
|
|
1166
|
+
this.checkConflicts();
|
|
1167
|
+
}
|
|
639
1168
|
/**
|
|
640
|
-
*
|
|
1169
|
+
* Parse command line arguments
|
|
641
1170
|
*/
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
type = import_joi.default.string();
|
|
661
|
-
}
|
|
662
|
-
if (!this.definitions[key]) {
|
|
663
|
-
this.definitions[key] = {};
|
|
664
|
-
}
|
|
665
|
-
this.definitions[key].type = type;
|
|
666
|
-
if (definition && definition.values) {
|
|
667
|
-
if (Array.isArray(definition.values)) {
|
|
668
|
-
this.definitions[key].values = definition.values;
|
|
1171
|
+
parseArgs(args) {
|
|
1172
|
+
let i = 0;
|
|
1173
|
+
while (i < args.length) {
|
|
1174
|
+
const arg = args[i];
|
|
1175
|
+
if (arg.startsWith("--")) {
|
|
1176
|
+
const [key, value] = this.parseLongOption(arg);
|
|
1177
|
+
this.setValue(key, value);
|
|
1178
|
+
i++;
|
|
1179
|
+
} else if (arg.startsWith("-")) {
|
|
1180
|
+
const result = this.parseShortOption(arg, args, i);
|
|
1181
|
+
if (result.consumed > 0) {
|
|
1182
|
+
i += result.consumed;
|
|
1183
|
+
} else {
|
|
1184
|
+
i++;
|
|
1185
|
+
}
|
|
1186
|
+
} else {
|
|
1187
|
+
this.commands.push(arg);
|
|
1188
|
+
i++;
|
|
669
1189
|
}
|
|
670
1190
|
}
|
|
671
|
-
return this.definitions[key];
|
|
672
1191
|
}
|
|
673
1192
|
/**
|
|
674
|
-
*
|
|
1193
|
+
* Parse long option (--key=value or --key)
|
|
675
1194
|
*/
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
type = import_joi.default.boolean();
|
|
684
|
-
} else if (str.match(/^date/i)) {
|
|
685
|
-
type = import_joi.default.custom(joiEdateType);
|
|
686
|
-
} else if (str.match(/^duration/i)) {
|
|
687
|
-
type = import_joi.default.string().isoDuration();
|
|
688
|
-
} else if (str.match(/^array/i)) {
|
|
689
|
-
let elementTypes = "string";
|
|
690
|
-
const tmp = str.match(/\((.*)\)/);
|
|
691
|
-
if (tmp && tmp[1].match(/string/i)) {
|
|
692
|
-
elementTypes = "string";
|
|
693
|
-
} else if (tmp && tmp[1].match(/number|integer|int/i)) {
|
|
694
|
-
elementTypes = "number";
|
|
695
|
-
} else if (tmp && tmp[1].match(/boolean|bool/i)) {
|
|
696
|
-
elementTypes = "boolean";
|
|
1195
|
+
parseLongOption(arg) {
|
|
1196
|
+
const key = arg.slice(2);
|
|
1197
|
+
const prefix = this.prefixes.find((p) => key.startsWith(p));
|
|
1198
|
+
if (prefix) {
|
|
1199
|
+
let strippedKey = key.slice(prefix.length);
|
|
1200
|
+
if (strippedKey.startsWith("-")) {
|
|
1201
|
+
strippedKey = strippedKey.slice(1);
|
|
697
1202
|
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
type = import_joi.default.string();
|
|
1203
|
+
this.nots.push(key);
|
|
1204
|
+
return [strippedKey, false];
|
|
701
1205
|
}
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
const
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
type = type.default(defValObj.value);
|
|
710
|
-
} else if (str.match(/required/)) {
|
|
711
|
-
type = type.required();
|
|
1206
|
+
if (key.includes("=")) {
|
|
1207
|
+
const eqIndex = key.indexOf("=");
|
|
1208
|
+
const optionKey = key.slice(0, eqIndex);
|
|
1209
|
+
const value = key.slice(eqIndex + 1);
|
|
1210
|
+
return [optionKey, this.parseValue(value)];
|
|
1211
|
+
} else {
|
|
1212
|
+
return [key, true];
|
|
712
1213
|
}
|
|
713
|
-
return type;
|
|
714
1214
|
}
|
|
715
1215
|
/**
|
|
716
|
-
*
|
|
1216
|
+
* Parse short option (-k=value, -k, or bundled -vsd)
|
|
717
1217
|
*/
|
|
718
|
-
|
|
719
|
-
const
|
|
720
|
-
if (
|
|
721
|
-
const
|
|
722
|
-
|
|
1218
|
+
parseShortOption(arg, args, index) {
|
|
1219
|
+
const key = arg.slice(1);
|
|
1220
|
+
if (key.length === 1 && index + 1 < args.length && !args[index + 1].startsWith("-")) {
|
|
1221
|
+
const value = args[index + 1];
|
|
1222
|
+
this.setValue(key, this.parseValue(value));
|
|
1223
|
+
return { consumed: 2 };
|
|
723
1224
|
}
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
1225
|
+
if (key.length > 1 && !key.includes("=")) {
|
|
1226
|
+
for (let i = 0; i < key.length; i++) {
|
|
1227
|
+
const shortKey = key[i];
|
|
1228
|
+
if (shortKey in this.aliases) {
|
|
1229
|
+
this.setValue(shortKey, true);
|
|
1230
|
+
} else {
|
|
1231
|
+
this.args[shortKey] = true;
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
return { consumed: 1 };
|
|
734
1235
|
}
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
1236
|
+
if (key.includes("=")) {
|
|
1237
|
+
const eqIndex = key.indexOf("=");
|
|
1238
|
+
const optionKey = key.slice(0, eqIndex);
|
|
1239
|
+
const value = key.slice(eqIndex + 1);
|
|
1240
|
+
if (optionKey.length > 1) {
|
|
1241
|
+
for (let i = 0; i < optionKey.length - 1; i++) {
|
|
1242
|
+
const shortKey = optionKey[i];
|
|
1243
|
+
if (shortKey in this.aliases) {
|
|
1244
|
+
this.setValue(shortKey, true);
|
|
1245
|
+
} else {
|
|
1246
|
+
this.args[shortKey] = true;
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
const lastKey = optionKey[optionKey.length - 1];
|
|
1250
|
+
if (lastKey in this.aliases) {
|
|
1251
|
+
this.setValue(lastKey, this.parseValue(value));
|
|
1252
|
+
} else {
|
|
1253
|
+
this.args[lastKey] = this.parseValue(value);
|
|
1254
|
+
}
|
|
1255
|
+
} else {
|
|
1256
|
+
this.setValue(optionKey, this.parseValue(value));
|
|
1257
|
+
}
|
|
1258
|
+
return { consumed: 1 };
|
|
1259
|
+
} else {
|
|
1260
|
+
this.setValue(key, true);
|
|
1261
|
+
return { consumed: 1 };
|
|
740
1262
|
}
|
|
741
|
-
return res;
|
|
742
1263
|
}
|
|
743
1264
|
/**
|
|
744
|
-
*
|
|
1265
|
+
* Parse value (handle quotes)
|
|
745
1266
|
*/
|
|
746
|
-
|
|
747
|
-
if (
|
|
748
|
-
|
|
749
|
-
val = val.value;
|
|
750
|
-
}
|
|
751
|
-
const def = this.assignDefinition(key, definition);
|
|
752
|
-
if (!this.runAllRegisteredSetters(key, val)) {
|
|
753
|
-
this.params[key] = val;
|
|
1267
|
+
parseValue(value) {
|
|
1268
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
1269
|
+
return value.slice(1, -1);
|
|
754
1270
|
}
|
|
1271
|
+
return value;
|
|
755
1272
|
}
|
|
756
1273
|
/**
|
|
757
|
-
*
|
|
758
|
-
* Processes parameters left-to-right to support cross-parameter references
|
|
1274
|
+
* Set a value with proper categorization
|
|
759
1275
|
*/
|
|
760
|
-
|
|
761
|
-
const
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
this.params[k] = value;
|
|
767
|
-
}
|
|
1276
|
+
setValue(key, value) {
|
|
1277
|
+
const resolvedKey = this.aliases[key] || key;
|
|
1278
|
+
if (typeof value === "boolean") {
|
|
1279
|
+
this.flags[resolvedKey] = value;
|
|
1280
|
+
} else {
|
|
1281
|
+
this.options[resolvedKey] = value;
|
|
768
1282
|
}
|
|
769
|
-
|
|
1283
|
+
this.args[resolvedKey.toLowerCase()] = value;
|
|
770
1284
|
}
|
|
771
1285
|
/**
|
|
772
|
-
*
|
|
1286
|
+
* Check for conflicts (short + long form of same option)
|
|
773
1287
|
*/
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
for (const
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
1288
|
+
checkConflicts() {
|
|
1289
|
+
const conflicts = [];
|
|
1290
|
+
for (const [shortKey, longKey] of Object.entries(this.aliases)) {
|
|
1291
|
+
const hasShort = this.args[shortKey] !== void 0;
|
|
1292
|
+
const hasLong = this.args[longKey] !== void 0;
|
|
1293
|
+
if (hasShort && hasLong) {
|
|
1294
|
+
conflicts.push(`Both -${shortKey} and --${longKey} specified`);
|
|
780
1295
|
}
|
|
781
1296
|
}
|
|
782
|
-
|
|
1297
|
+
if (conflicts.length > 0) {
|
|
1298
|
+
throw new Error(`Argument conflicts: ${conflicts.join(", ")}`);
|
|
1299
|
+
}
|
|
783
1300
|
}
|
|
784
1301
|
/**
|
|
785
|
-
*
|
|
1302
|
+
* Get a value with precedence order
|
|
786
1303
|
*/
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
break;
|
|
793
|
-
}
|
|
1304
|
+
get(key) {
|
|
1305
|
+
const resolvedKey = this.aliases[key] || key;
|
|
1306
|
+
this.usedKeys.add(resolvedKey);
|
|
1307
|
+
if (this.overrides[resolvedKey] !== void 0) {
|
|
1308
|
+
return this.overrides[resolvedKey];
|
|
794
1309
|
}
|
|
795
|
-
|
|
1310
|
+
const lcKey = resolvedKey.toLowerCase();
|
|
1311
|
+
const lcKeyWithEnv = `${lcKey}${this.env ? `_${this.env.toLowerCase()}` : ""}`;
|
|
1312
|
+
if (this.env && this.args[lcKeyWithEnv] !== void 0) {
|
|
1313
|
+
return this.args[lcKeyWithEnv];
|
|
1314
|
+
} else if (this.args[lcKey] !== void 0) {
|
|
1315
|
+
return this.args[lcKey];
|
|
1316
|
+
}
|
|
1317
|
+
if (this.configValues[resolvedKey] !== void 0) {
|
|
1318
|
+
return this.configValues[resolvedKey];
|
|
1319
|
+
}
|
|
1320
|
+
const envKey = this.toEnvKey(resolvedKey);
|
|
1321
|
+
const envKeyWithEnv = `${envKey}${this.env ? `_${this.env.toUpperCase()}` : ""}`;
|
|
1322
|
+
const envSpecificKey = Object.keys(process.env).find(
|
|
1323
|
+
(k) => this.env && k.toUpperCase() === envKeyWithEnv
|
|
1324
|
+
);
|
|
1325
|
+
const envKeyFound = Object.keys(process.env).find((k) => k.toUpperCase() === envKey);
|
|
1326
|
+
if (envSpecificKey) {
|
|
1327
|
+
return process.env[envSpecificKey];
|
|
1328
|
+
} else if (envKeyFound) {
|
|
1329
|
+
return process.env[envKeyFound];
|
|
1330
|
+
}
|
|
1331
|
+
if (this.defaults[resolvedKey] !== void 0) {
|
|
1332
|
+
return this.defaults[resolvedKey];
|
|
1333
|
+
}
|
|
1334
|
+
if (resolvedKey === "env" && process.env.NODE_ENV !== void 0) {
|
|
1335
|
+
return process.env.NODE_ENV;
|
|
1336
|
+
}
|
|
1337
|
+
return void 0;
|
|
796
1338
|
}
|
|
797
1339
|
/**
|
|
798
|
-
*
|
|
1340
|
+
* Set a value (for testing/internal use)
|
|
799
1341
|
*/
|
|
800
|
-
|
|
801
|
-
this.
|
|
1342
|
+
set(key, value) {
|
|
1343
|
+
this.args[key] = value;
|
|
802
1344
|
}
|
|
803
1345
|
/**
|
|
804
|
-
*
|
|
1346
|
+
* Check if a command exists (case-insensitive)
|
|
805
1347
|
*/
|
|
806
|
-
|
|
807
|
-
this.
|
|
1348
|
+
hasCommand(cmd) {
|
|
1349
|
+
return this.commands.some((command) => command.toLowerCase() === cmd.toLowerCase());
|
|
808
1350
|
}
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
var import_react5 = __toESM(require("react"), 1);
|
|
815
|
-
var import_ink5 = require("ink");
|
|
816
|
-
|
|
817
|
-
// src/screen/screens.ts
|
|
818
|
-
var import_react3 = require("react");
|
|
819
|
-
var import_ink3 = require("ink");
|
|
820
|
-
|
|
821
|
-
// src/screen/components.ts
|
|
822
|
-
var import_react = require("react");
|
|
823
|
-
var import_ink = require("ink");
|
|
824
|
-
function getScreenWidth(maxWidth = null) {
|
|
825
|
-
const terminalWidth = process.stdout.columns || 80;
|
|
826
|
-
const availableWidth = Math.max(20, terminalWidth - 4);
|
|
827
|
-
return maxWidth ? Math.min(availableWidth, maxWidth) : availableWidth;
|
|
828
|
-
}
|
|
829
|
-
function ScreenContainer({ children }) {
|
|
830
|
-
const width = getScreenWidth();
|
|
831
|
-
return (0, import_react.createElement)(import_ink.Box, {
|
|
832
|
-
flexDirection: "column",
|
|
833
|
-
marginTop: 1,
|
|
834
|
-
borderStyle: "single",
|
|
835
|
-
borderColor: "cyan",
|
|
836
|
-
paddingX: 1,
|
|
837
|
-
width
|
|
838
|
-
// Use the calculated width directly
|
|
839
|
-
}, children);
|
|
840
|
-
}
|
|
841
|
-
function ScreenRow({ children }) {
|
|
842
|
-
return (0, import_react.createElement)(import_ink.Box, { flexDirection: "column" }, children);
|
|
843
|
-
}
|
|
844
|
-
function ScreenTitle({ text }) {
|
|
845
|
-
return (0, import_react.createElement)(
|
|
846
|
-
ScreenRow,
|
|
847
|
-
{},
|
|
848
|
-
(0, import_react.createElement)(import_ink.Text, { bold: true, color: "cyan" }, text)
|
|
849
|
-
);
|
|
850
|
-
}
|
|
851
|
-
function ScreenDivider({ width }) {
|
|
852
|
-
const dividerWidth = width || getScreenWidth() - 4;
|
|
853
|
-
return (0, import_react.createElement)(import_ink.Text, { color: "cyan", dimColor: true }, "\u2500".repeat(dividerWidth));
|
|
854
|
-
}
|
|
855
|
-
function ScreenBody({ children, alignItems = "flex-start" }) {
|
|
856
|
-
return (0, import_react.createElement)(import_ink.Box, { flexDirection: "column", alignItems }, children);
|
|
857
|
-
}
|
|
858
|
-
function ScreenFooter({ lines, textStyle }) {
|
|
859
|
-
const defaultTextStyle = {
|
|
860
|
-
dimColor: true,
|
|
861
|
-
color: "white"
|
|
862
|
-
};
|
|
863
|
-
const finalTextStyle = { ...defaultTextStyle, ...textStyle };
|
|
864
|
-
const flattenAndWrap = (items, keyPrefix = "") => {
|
|
865
|
-
const result = [];
|
|
866
|
-
let keyIndex = 0;
|
|
867
|
-
items.forEach((item, index) => {
|
|
868
|
-
if (Array.isArray(item)) {
|
|
869
|
-
const nested = flattenAndWrap(item, `${keyPrefix}-${index}`);
|
|
870
|
-
result.push(...nested);
|
|
871
|
-
} else if (typeof item === "string") {
|
|
872
|
-
result.push(
|
|
873
|
-
(0, import_react.createElement)(import_ink.Text, { key: `${keyPrefix}-${keyIndex++}`, ...finalTextStyle }, item)
|
|
874
|
-
);
|
|
875
|
-
} else {
|
|
876
|
-
const element = item;
|
|
877
|
-
if (element.key === null || element.key === void 0) {
|
|
878
|
-
result.push(
|
|
879
|
-
(0, import_react.createElement)(import_ink.Text, { key: `${keyPrefix}-${keyIndex++}`, ...finalTextStyle }, element)
|
|
880
|
-
);
|
|
881
|
-
} else {
|
|
882
|
-
result.push(element);
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
});
|
|
886
|
-
return result;
|
|
887
|
-
};
|
|
888
|
-
const wrappedItems = flattenAndWrap(lines);
|
|
889
|
-
return (0, import_react.createElement)(
|
|
890
|
-
import_ink.Box,
|
|
891
|
-
{ flexDirection: "column" },
|
|
892
|
-
(0, import_react.createElement)(import_ink.Box, { flexDirection: "row" }, ...wrappedItems)
|
|
893
|
-
);
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
// src/screen/list-components.ts
|
|
897
|
-
var import_react2 = __toESM(require("react"), 1);
|
|
898
|
-
var import_ink2 = require("ink");
|
|
899
|
-
var h2 = import_react2.createElement;
|
|
900
|
-
function MultiColumnListComponent({ items, ctx, selectedIndexRef }) {
|
|
901
|
-
const [, forceUpdate] = (0, import_react2.useState)({});
|
|
902
|
-
const termWidth = (process.stdout.columns || 80) - 8;
|
|
903
|
-
const maxItemLength = Math.max(...items.map((w) => w.length));
|
|
904
|
-
const columnWidth = maxItemLength + 3;
|
|
905
|
-
const columns = Math.max(1, Math.floor(termWidth / columnWidth));
|
|
906
|
-
const itemsPerColumn = Math.ceil(items.length / columns);
|
|
907
|
-
(0, import_react2.useEffect)(() => {
|
|
908
|
-
ctx.setAction("moveUp", () => {
|
|
909
|
-
selectedIndexRef.current = Math.max(0, selectedIndexRef.current - 1);
|
|
910
|
-
forceUpdate({});
|
|
911
|
-
});
|
|
912
|
-
ctx.setAction("moveDown", () => {
|
|
913
|
-
selectedIndexRef.current = Math.min(items.length - 1, selectedIndexRef.current + 1);
|
|
914
|
-
forceUpdate({});
|
|
915
|
-
});
|
|
916
|
-
ctx.setAction("moveLeft", () => {
|
|
917
|
-
if (selectedIndexRef.current === 0) {
|
|
918
|
-
ctx.goBack();
|
|
919
|
-
} else {
|
|
920
|
-
selectedIndexRef.current = Math.max(0, selectedIndexRef.current - itemsPerColumn);
|
|
921
|
-
forceUpdate({});
|
|
922
|
-
}
|
|
923
|
-
});
|
|
924
|
-
ctx.setAction("moveRight", () => {
|
|
925
|
-
selectedIndexRef.current = Math.min(items.length - 1, selectedIndexRef.current + itemsPerColumn);
|
|
926
|
-
forceUpdate({});
|
|
927
|
-
});
|
|
928
|
-
ctx.setKeyBinding([
|
|
929
|
-
{ key: "leftArrow", caption: "navigate", action: "moveLeft", order: 0 },
|
|
930
|
-
{ key: "rightArrow", caption: "navigate", action: "moveRight", order: 0 },
|
|
931
|
-
{ key: "upArrow", caption: "navigate", action: "moveUp", order: 0 },
|
|
932
|
-
{ key: "downArrow", caption: "navigate", action: "moveDown", order: 0 }
|
|
933
|
-
]);
|
|
934
|
-
ctx.addFooter(`Total: ${items.length} items`);
|
|
935
|
-
}, []);
|
|
936
|
-
const selectedIndex = selectedIndexRef.current;
|
|
937
|
-
const rows = [];
|
|
938
|
-
for (let row = 0; row < itemsPerColumn; row++) {
|
|
939
|
-
const cols = [];
|
|
940
|
-
for (let col = 0; col < columns; col++) {
|
|
941
|
-
const index = col * itemsPerColumn + row;
|
|
942
|
-
if (index < items.length) {
|
|
943
|
-
const isSelected = index === selectedIndex;
|
|
944
|
-
cols.push(
|
|
945
|
-
h2(
|
|
946
|
-
import_ink2.Box,
|
|
947
|
-
{ key: index, width: columnWidth },
|
|
948
|
-
h2(import_ink2.Text, {
|
|
949
|
-
color: isSelected ? "black" : "white",
|
|
950
|
-
backgroundColor: isSelected ? "cyan" : void 0,
|
|
951
|
-
bold: isSelected
|
|
952
|
-
}, items[index].padEnd(maxItemLength))
|
|
953
|
-
)
|
|
954
|
-
);
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
rows.push(
|
|
958
|
-
h2(ScreenRow, { key: row, children: h2(import_ink2.Box, { flexDirection: "row" }, ...cols) })
|
|
959
|
-
);
|
|
1351
|
+
/**
|
|
1352
|
+
* Get all commands
|
|
1353
|
+
*/
|
|
1354
|
+
getCommands() {
|
|
1355
|
+
return [...this.commands];
|
|
960
1356
|
}
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
(0, import_react2.useEffect)(() => {
|
|
976
|
-
ctx.setAction("moveUp", () => {
|
|
977
|
-
selectedIndexRef.current = Math.max(0, selectedIndexRef.current - 1);
|
|
978
|
-
forceUpdate({});
|
|
979
|
-
});
|
|
980
|
-
ctx.setAction("moveDown", () => {
|
|
981
|
-
selectedIndexRef.current = Math.min(items.length - 1, selectedIndexRef.current + 1);
|
|
982
|
-
forceUpdate({});
|
|
983
|
-
});
|
|
984
|
-
ctx.setAction("moveLeft", () => {
|
|
985
|
-
if (selectedIndexRef.current === 0) {
|
|
986
|
-
ctx.goBack();
|
|
987
|
-
} else {
|
|
988
|
-
selectedIndexRef.current = Math.max(0, selectedIndexRef.current - itemsPerColumn);
|
|
989
|
-
forceUpdate({});
|
|
990
|
-
}
|
|
991
|
-
});
|
|
992
|
-
ctx.setAction("moveRight", () => {
|
|
993
|
-
selectedIndexRef.current = Math.min(items.length - 1, selectedIndexRef.current + itemsPerColumn);
|
|
994
|
-
forceUpdate({});
|
|
995
|
-
});
|
|
996
|
-
ctx.setKeyBinding([
|
|
997
|
-
{ key: "leftArrow", caption: "navigate", action: "moveLeft", order: 0 },
|
|
998
|
-
{ key: "rightArrow", caption: "navigate", action: "moveRight", order: 0 },
|
|
999
|
-
{ key: "upArrow", caption: "navigate", action: "moveUp", order: 0 },
|
|
1000
|
-
{ key: "downArrow", caption: "navigate", action: "moveDown", order: 0 }
|
|
1001
|
-
]);
|
|
1002
|
-
ctx.addFooter(`Total: ${items.length} items`);
|
|
1003
|
-
}, []);
|
|
1004
|
-
const selectedIndex = selectedIndexRef.current;
|
|
1005
|
-
const selectedItem = items[selectedIndex];
|
|
1006
|
-
const rows = [];
|
|
1007
|
-
for (let row = 0; row < itemsPerColumn; row++) {
|
|
1008
|
-
const cols = [];
|
|
1009
|
-
for (let col = 0; col < columns; col++) {
|
|
1010
|
-
const index = col * itemsPerColumn + row;
|
|
1011
|
-
if (index < items.length) {
|
|
1012
|
-
const isSelected = index === selectedIndex;
|
|
1013
|
-
cols.push(
|
|
1014
|
-
h2(
|
|
1015
|
-
import_ink2.Box,
|
|
1016
|
-
{ key: index, width: columnWidth },
|
|
1017
|
-
h2(import_ink2.Text, {
|
|
1018
|
-
color: isSelected ? "black" : "white",
|
|
1019
|
-
backgroundColor: isSelected ? "cyan" : void 0,
|
|
1020
|
-
bold: isSelected
|
|
1021
|
-
}, items[index].padEnd(maxItemLength))
|
|
1022
|
-
)
|
|
1023
|
-
);
|
|
1357
|
+
/**
|
|
1358
|
+
* Get used keys (as array)
|
|
1359
|
+
*/
|
|
1360
|
+
getUsed() {
|
|
1361
|
+
return Array.from(this.usedKeys);
|
|
1362
|
+
}
|
|
1363
|
+
/**
|
|
1364
|
+
* Get unused keys (as array)
|
|
1365
|
+
*/
|
|
1366
|
+
getUnused() {
|
|
1367
|
+
const unused = [];
|
|
1368
|
+
for (const key of Object.keys(this.args)) {
|
|
1369
|
+
if (!this.usedKeys.has(key) && !this.nots.includes(key)) {
|
|
1370
|
+
unused.push(key);
|
|
1024
1371
|
}
|
|
1025
1372
|
}
|
|
1026
|
-
|
|
1027
|
-
h2(ScreenRow, { key: row, children: h2(import_ink2.Box, { flexDirection: "row" }, ...cols) })
|
|
1028
|
-
);
|
|
1373
|
+
return unused;
|
|
1029
1374
|
}
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
} else if (import_react2.default.isValidElement(previewContent)) {
|
|
1039
|
-
previewRows.push(h2(ScreenRow, { key: "preview-element", children: previewContent }));
|
|
1375
|
+
/**
|
|
1376
|
+
* Convert key to environment variable format
|
|
1377
|
+
*/
|
|
1378
|
+
toEnvKey(key) {
|
|
1379
|
+
return key.replace(
|
|
1380
|
+
/[A-Z0-9]/g,
|
|
1381
|
+
(match, offset) => offset === 0 ? match : "_" + match.toLowerCase()
|
|
1382
|
+
).toUpperCase();
|
|
1040
1383
|
}
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
}
|
|
1051
|
-
function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sortable = false, maxHeight, sortHighlightStyle, selectionMarker = " " }) {
|
|
1052
|
-
const [, forceUpdate] = (0, import_react2.useState)({});
|
|
1053
|
-
const [sortOrder, setSortOrder] = (0, import_react2.useState)("none");
|
|
1054
|
-
const [scrollOffset, setScrollOffset] = (0, import_react2.useState)(0);
|
|
1055
|
-
const scrollStateRef = (0, import_react2.useRef)({ scrollOffset: 0, maxHeight: 0, totalItems: 0 });
|
|
1056
|
-
const defaultGetTitle = (item) => {
|
|
1057
|
-
return getTitle ? getTitle(item) : typeof item.value === "string" ? item.value : item.value?.title || item.name;
|
|
1058
|
-
};
|
|
1059
|
-
const titleGetter = getTitle || defaultGetTitle;
|
|
1060
|
-
const displayItems = sortable && sortOrder !== "none" ? [...items].sort((a, b) => {
|
|
1061
|
-
const titleA = titleGetter(a).toLowerCase();
|
|
1062
|
-
const titleB = titleGetter(b).toLowerCase();
|
|
1063
|
-
if (sortOrder === "asc") {
|
|
1064
|
-
return titleA < titleB ? -1 : titleA > titleB ? 1 : 0;
|
|
1065
|
-
} else {
|
|
1066
|
-
return titleA > titleB ? -1 : titleA < titleB ? 1 : 0;
|
|
1067
|
-
}
|
|
1068
|
-
}) : items;
|
|
1069
|
-
const effectiveMaxHeight = maxHeight || displayItems.length;
|
|
1070
|
-
const canScroll = displayItems.length > effectiveMaxHeight;
|
|
1071
|
-
const maxScrollOffset = Math.max(0, displayItems.length - effectiveMaxHeight);
|
|
1072
|
-
const clampedScrollOffset = Math.min(Math.max(0, scrollOffset), maxScrollOffset);
|
|
1073
|
-
const visibleItems = displayItems.slice(clampedScrollOffset, clampedScrollOffset + effectiveMaxHeight);
|
|
1074
|
-
const canScrollUp = clampedScrollOffset > 0;
|
|
1075
|
-
const canScrollDown = clampedScrollOffset < maxScrollOffset;
|
|
1076
|
-
scrollStateRef.current = { scrollOffset, maxHeight: effectiveMaxHeight, totalItems: displayItems.length };
|
|
1077
|
-
(0, import_react2.useEffect)(() => {
|
|
1078
|
-
ctx.setAction("moveUp", () => {
|
|
1079
|
-
const newIndex = Math.max(0, selectedIndexRef.current - 1);
|
|
1080
|
-
selectedIndexRef.current = newIndex;
|
|
1081
|
-
const { scrollOffset: currentScrollOffset, maxHeight: currentMaxHeight, totalItems } = scrollStateRef.current;
|
|
1082
|
-
const currentMaxScrollOffset = Math.max(0, totalItems - currentMaxHeight);
|
|
1083
|
-
const currentClampedScrollOffset = Math.min(Math.max(0, currentScrollOffset), currentMaxScrollOffset);
|
|
1084
|
-
if (newIndex < currentClampedScrollOffset) {
|
|
1085
|
-
setScrollOffset(newIndex);
|
|
1384
|
+
/**
|
|
1385
|
+
* Load .env file
|
|
1386
|
+
*/
|
|
1387
|
+
loadDotEnv() {
|
|
1388
|
+
const dotEnvPath = this.get("dotEnvPath") || process.cwd();
|
|
1389
|
+
const dotEnvFile = this.get("dotEnvFile") || ".env";
|
|
1390
|
+
if (this.get("dotEnvFile")) {
|
|
1391
|
+
const customPath = (0, import_path.resolve)(dotEnvPath, dotEnvFile);
|
|
1392
|
+
if ((0, import_fs.existsSync)(customPath)) {
|
|
1393
|
+
(0, import_dotenv.config)({ path: customPath, quiet: true });
|
|
1086
1394
|
}
|
|
1087
|
-
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
const newIndex = Math.min(maxIndex, selectedIndexRef.current + 1);
|
|
1101
|
-
selectedIndexRef.current = newIndex;
|
|
1102
|
-
const { scrollOffset: currentScrollOffset, maxHeight: currentMaxHeight, totalItems } = scrollStateRef.current;
|
|
1103
|
-
const currentMaxScrollOffset = Math.max(0, totalItems - currentMaxHeight);
|
|
1104
|
-
const currentClampedScrollOffset = Math.min(Math.max(0, currentScrollOffset), currentMaxScrollOffset);
|
|
1105
|
-
if (newIndex >= currentClampedScrollOffset + currentMaxHeight) {
|
|
1106
|
-
setScrollOffset(newIndex - currentMaxHeight + 1);
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1397
|
+
let dotEnvPathFile = null;
|
|
1398
|
+
const envSpecificFile = `.env.${this.env}`;
|
|
1399
|
+
const envSpecificPath = (0, import_path.resolve)(dotEnvPath, envSpecificFile);
|
|
1400
|
+
if ((0, import_fs.existsSync)(envSpecificPath)) {
|
|
1401
|
+
dotEnvPathFile = envSpecificPath;
|
|
1402
|
+
}
|
|
1403
|
+
if (!dotEnvPathFile && !this.get("dotEnvPath")) {
|
|
1404
|
+
const examplesPath = (0, import_path.resolve)(dotEnvPath, "examples");
|
|
1405
|
+
const examplesEnvSpecificPath = (0, import_path.resolve)(examplesPath, envSpecificFile);
|
|
1406
|
+
if ((0, import_fs.existsSync)(examplesEnvSpecificPath)) {
|
|
1407
|
+
dotEnvPathFile = examplesEnvSpecificPath;
|
|
1107
1408
|
}
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
ctx.setAction("scrollDown", () => {
|
|
1118
|
-
const { scrollOffset: currentScrollOffset, maxHeight: currentMaxHeight, totalItems } = scrollStateRef.current;
|
|
1119
|
-
const currentMaxScrollOffset = Math.max(0, totalItems - currentMaxHeight);
|
|
1120
|
-
const newScrollOffset = Math.min(currentMaxScrollOffset, currentScrollOffset + 1);
|
|
1121
|
-
setScrollOffset(newScrollOffset);
|
|
1122
|
-
forceUpdate({});
|
|
1123
|
-
});
|
|
1124
|
-
if (sortable) {
|
|
1125
|
-
ctx.setAction("toggleSort", () => {
|
|
1126
|
-
const nextSort = sortOrder === "none" ? "asc" : sortOrder === "asc" ? "desc" : "none";
|
|
1127
|
-
const currentSelectedItem = displayItems[selectedIndexRef.current];
|
|
1128
|
-
setSortOrder(nextSort);
|
|
1129
|
-
const newSortedItems = nextSort !== "none" ? [...items].sort((a, b) => {
|
|
1130
|
-
const titleA = titleGetter(a).toLowerCase();
|
|
1131
|
-
const titleB = titleGetter(b).toLowerCase();
|
|
1132
|
-
if (nextSort === "asc") {
|
|
1133
|
-
return titleA < titleB ? -1 : titleA > titleB ? 1 : 0;
|
|
1409
|
+
}
|
|
1410
|
+
if (!dotEnvPathFile) {
|
|
1411
|
+
dotEnvPathFile = (0, import_path.resolve)(dotEnvPath, dotEnvFile);
|
|
1412
|
+
if (!(0, import_fs.existsSync)(dotEnvPathFile)) {
|
|
1413
|
+
if (!this.get("dotEnvPath")) {
|
|
1414
|
+
const examplesPath = (0, import_path.resolve)(dotEnvPath, "examples");
|
|
1415
|
+
const examplesEnvFile = (0, import_path.resolve)(examplesPath, dotEnvFile);
|
|
1416
|
+
if ((0, import_fs.existsSync)(examplesEnvFile)) {
|
|
1417
|
+
dotEnvPathFile = examplesEnvFile;
|
|
1134
1418
|
} else {
|
|
1135
|
-
|
|
1419
|
+
dotEnvPathFile = (0, import_path.resolve)(dotEnvPath, "..", dotEnvFile);
|
|
1136
1420
|
}
|
|
1137
|
-
}) : items;
|
|
1138
|
-
const newIndex = newSortedItems.findIndex((item) => item === currentSelectedItem);
|
|
1139
|
-
if (newIndex !== -1) {
|
|
1140
|
-
selectedIndexRef.current = newIndex;
|
|
1141
|
-
setScrollOffset(newIndex);
|
|
1142
|
-
} else {
|
|
1143
|
-
selectedIndexRef.current = 0;
|
|
1144
|
-
setScrollOffset(0);
|
|
1145
1421
|
}
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
if (dotEnvPathFile && (0, import_fs.existsSync)(dotEnvPathFile)) {
|
|
1425
|
+
(0, import_dotenv.config)({ path: dotEnvPathFile, quiet: true });
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
/**
|
|
1429
|
+
* Load configuration files
|
|
1430
|
+
*/
|
|
1431
|
+
loadConfigFiles() {
|
|
1432
|
+
this.configsLoaded = [];
|
|
1433
|
+
this.configValues = {};
|
|
1434
|
+
const _defaultConfigExtension = this.get("defaultConfigExtension") || "js";
|
|
1435
|
+
const optConfigFiles = this.get("config") || this.get("configs") || "";
|
|
1436
|
+
const configFiles = optConfigFiles ? optConfigFiles.split(/,\s*/) : [];
|
|
1437
|
+
const optConfigFilePath = this.get("configPath");
|
|
1438
|
+
if (configFiles.length > 0) {
|
|
1439
|
+
for (const cfgFile of configFiles) {
|
|
1440
|
+
let notLoaded = false;
|
|
1441
|
+
let notLoadedEnvSpecific = false;
|
|
1442
|
+
const cfgFileWithPath = this.resolveFileWithPath(optConfigFilePath, cfgFile);
|
|
1443
|
+
try {
|
|
1444
|
+
const cfgContents = this.requireConfigFile(cfgFileWithPath);
|
|
1445
|
+
this.configValues = { ...this.configValues, ...cfgContents };
|
|
1446
|
+
this.configsLoaded.push(cfgFileWithPath);
|
|
1447
|
+
} catch {
|
|
1448
|
+
notLoaded = true;
|
|
1449
|
+
}
|
|
1450
|
+
const cfgEnvFileWithPath = this.resolveFileWithPath(
|
|
1451
|
+
optConfigFilePath,
|
|
1452
|
+
cfgFile,
|
|
1453
|
+
this.env
|
|
1454
|
+
);
|
|
1455
|
+
if (cfgEnvFileWithPath !== cfgFileWithPath) {
|
|
1456
|
+
try {
|
|
1457
|
+
const cfgContents = this.requireConfigFile(cfgEnvFileWithPath);
|
|
1458
|
+
this.configValues = { ...this.configValues, ...cfgContents };
|
|
1459
|
+
this.configsLoaded.push(cfgEnvFileWithPath);
|
|
1460
|
+
} catch {
|
|
1461
|
+
notLoadedEnvSpecific = true;
|
|
1462
|
+
}
|
|
1157
1463
|
} else {
|
|
1158
|
-
|
|
1159
|
-
return h2(
|
|
1160
|
-
import_ink2.Text,
|
|
1161
|
-
{},
|
|
1162
|
-
"s to toggle ",
|
|
1163
|
-
h2(import_ink2.Text, { color: "white", bold: true }, "sort"),
|
|
1164
|
-
" ",
|
|
1165
|
-
h2(import_ink2.Text, highlightStyle, ` ${sortLabel} `)
|
|
1166
|
-
);
|
|
1464
|
+
notLoadedEnvSpecific = true;
|
|
1167
1465
|
}
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
{ key: "upArrow", caption: "navigate", action: "moveUp", order: 0 },
|
|
1171
|
-
{ key: "downArrow", caption: "navigate", action: "moveDown", order: 0 },
|
|
1172
|
-
{
|
|
1173
|
-
key: "s",
|
|
1174
|
-
caption: sortCaption,
|
|
1175
|
-
action: "toggleSort",
|
|
1176
|
-
order: 5
|
|
1466
|
+
if (notLoaded && notLoadedEnvSpecific) {
|
|
1467
|
+
throw new Error(`can't load config file "${cfgFileWithPath}"`);
|
|
1177
1468
|
}
|
|
1178
|
-
|
|
1179
|
-
ctx.update();
|
|
1180
|
-
} else {
|
|
1181
|
-
ctx.setKeyBinding([
|
|
1182
|
-
{ key: "upArrow", caption: "navigate", action: "moveUp", order: 0 },
|
|
1183
|
-
{ key: "downArrow", caption: "navigate", action: "moveDown", order: 0 }
|
|
1184
|
-
]);
|
|
1469
|
+
}
|
|
1185
1470
|
}
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
let
|
|
1192
|
-
|
|
1193
|
-
if (
|
|
1194
|
-
|
|
1195
|
-
} else if (isLastVisible && canScrollDown) {
|
|
1196
|
-
arrowPrefix = "\u2193 ";
|
|
1471
|
+
}
|
|
1472
|
+
/**
|
|
1473
|
+
* Resolve file path with environment-specific naming
|
|
1474
|
+
*/
|
|
1475
|
+
resolveFileWithPath(optConfigFilePath, cfgFile, env) {
|
|
1476
|
+
let cfgFileWithPath = optConfigFilePath ? (0, import_path.isAbsolute)(optConfigFilePath) ? (0, import_path.resolve)(optConfigFilePath, cfgFile) : (0, import_path.resolve)(process.cwd(), optConfigFilePath, cfgFile) : (0, import_path.isAbsolute)(cfgFile) ? cfgFile : (0, import_path.resolve)(process.cwd(), cfgFile);
|
|
1477
|
+
const { basePathWithName, extension } = this.splitPath(cfgFileWithPath);
|
|
1478
|
+
if (env) {
|
|
1479
|
+
cfgFileWithPath = `${basePathWithName}.${env}.${extension || "js"}`;
|
|
1197
1480
|
} else {
|
|
1198
|
-
|
|
1481
|
+
cfgFileWithPath = `${basePathWithName}.${extension || "js"}`;
|
|
1199
1482
|
}
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1483
|
+
return cfgFileWithPath;
|
|
1484
|
+
}
|
|
1485
|
+
/**
|
|
1486
|
+
* Split file path into base path and extension
|
|
1487
|
+
*/
|
|
1488
|
+
splitPath(filePath) {
|
|
1489
|
+
const basePathWithName = (0, import_path.join)((0, import_path.dirname)(filePath), (0, import_path.basename)(filePath, (0, import_path.extname)(filePath)));
|
|
1490
|
+
const extension = (0, import_path.extname)(filePath).slice(1);
|
|
1491
|
+
return { basePathWithName, extension };
|
|
1492
|
+
}
|
|
1493
|
+
/**
|
|
1494
|
+
* Require a configuration file (supports .js and .json)
|
|
1495
|
+
*/
|
|
1496
|
+
requireConfigFile(filePath) {
|
|
1497
|
+
if (!(0, import_fs.existsSync)(filePath)) {
|
|
1498
|
+
throw new Error(`Config file not found: ${filePath}`);
|
|
1204
1499
|
}
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
// Item name (highlighted if selected)
|
|
1216
|
-
h2(import_ink2.Text, {
|
|
1217
|
-
key: `name-${actualIndex}`,
|
|
1218
|
-
color: isSelected ? "black" : "white",
|
|
1219
|
-
backgroundColor: isSelected ? "cyan" : void 0,
|
|
1220
|
-
bold: isSelected
|
|
1221
|
-
}, item.name)
|
|
1222
|
-
);
|
|
1223
|
-
};
|
|
1224
|
-
const itemRenderer = renderItem || defaultRenderItem;
|
|
1225
|
-
return h2(
|
|
1226
|
-
import_ink2.Box,
|
|
1227
|
-
{ flexDirection: "column" },
|
|
1228
|
-
...visibleItems.map((item, displayIndex) => {
|
|
1229
|
-
const actualIndex = clampedScrollOffset + displayIndex;
|
|
1230
|
-
const isSelected = actualIndex === selectedIndex;
|
|
1231
|
-
if (renderItem) {
|
|
1232
|
-
const isFirstVisible = displayIndex === 0;
|
|
1233
|
-
const isLastVisible = displayIndex === visibleItems.length - 1;
|
|
1234
|
-
let arrowPrefix = "";
|
|
1235
|
-
let selectionPrefix = "";
|
|
1236
|
-
if (isFirstVisible && canScrollUp) {
|
|
1237
|
-
arrowPrefix = "\u2191 ";
|
|
1238
|
-
} else if (isLastVisible && canScrollDown) {
|
|
1239
|
-
arrowPrefix = "\u2193 ";
|
|
1240
|
-
} else {
|
|
1241
|
-
arrowPrefix = " ";
|
|
1242
|
-
}
|
|
1243
|
-
if (isSelected) {
|
|
1244
|
-
selectionPrefix = selectionMarker;
|
|
1245
|
-
} else {
|
|
1246
|
-
selectionPrefix = " ".repeat(selectionMarker.length);
|
|
1247
|
-
}
|
|
1248
|
-
return h2(ScreenRow, {
|
|
1249
|
-
key: `item-${actualIndex}`,
|
|
1250
|
-
children: h2(
|
|
1251
|
-
import_ink2.Box,
|
|
1252
|
-
{ flexDirection: "row" },
|
|
1253
|
-
// Arrow (clickable if functional, not highlighted)
|
|
1254
|
-
h2(import_ink2.Text, {
|
|
1255
|
-
key: `arrow-${actualIndex}`,
|
|
1256
|
-
color: "white"
|
|
1257
|
-
}, arrowPrefix),
|
|
1258
|
-
// Selection marker space (always same width, not highlighted)
|
|
1259
|
-
h2(import_ink2.Text, { key: `marker-${actualIndex}`, color: "white" }, selectionPrefix),
|
|
1260
|
-
// Custom rendered content
|
|
1261
|
-
renderItem(item, isSelected, displayIndex)
|
|
1262
|
-
)
|
|
1263
|
-
});
|
|
1264
|
-
} else {
|
|
1265
|
-
return h2(ScreenRow, {
|
|
1266
|
-
key: `item-${actualIndex}`,
|
|
1267
|
-
children: itemRenderer(item, isSelected, displayIndex, actualIndex)
|
|
1268
|
-
});
|
|
1500
|
+
const ext = (0, import_path.extname)(filePath).toLowerCase();
|
|
1501
|
+
if (ext === ".json") {
|
|
1502
|
+
const content = (0, import_fs.readFileSync)(filePath, "utf8");
|
|
1503
|
+
return JSON.parse(content);
|
|
1504
|
+
} else if (ext === ".js") {
|
|
1505
|
+
try {
|
|
1506
|
+
delete require.cache[require.resolve(filePath)];
|
|
1507
|
+
return require(filePath);
|
|
1508
|
+
} catch (error) {
|
|
1509
|
+
throw new Error(`Failed to load JS config file: ${error instanceof Error ? error.message : String(error)}`);
|
|
1269
1510
|
}
|
|
1270
|
-
}
|
|
1271
|
-
|
|
1272
|
-
}
|
|
1273
|
-
|
|
1274
|
-
// src/screen/screens.ts
|
|
1275
|
-
function groupKeyBindings(bindings) {
|
|
1276
|
-
const groups = {};
|
|
1277
|
-
const enabledBindings = bindings.filter((b) => b.enabled !== false);
|
|
1278
|
-
enabledBindings.forEach((binding) => {
|
|
1279
|
-
const caption = typeof binding.caption === "string" ? binding.caption : "";
|
|
1280
|
-
if (!groups[caption]) {
|
|
1281
|
-
groups[caption] = {
|
|
1282
|
-
keys: [],
|
|
1283
|
-
caption,
|
|
1284
|
-
order: binding.order || 999
|
|
1285
|
-
};
|
|
1286
|
-
}
|
|
1287
|
-
groups[caption].keys.push(binding.key);
|
|
1288
|
-
});
|
|
1289
|
-
return Object.values(groups);
|
|
1290
|
-
}
|
|
1291
|
-
function formatKeyBindings(bindings, mode = "long") {
|
|
1292
|
-
const resolvedBindings = bindings.map((binding) => {
|
|
1293
|
-
let resolvedCaption = binding.caption;
|
|
1294
|
-
if (typeof binding.caption === "function") {
|
|
1295
|
-
resolvedCaption = binding.caption();
|
|
1511
|
+
} else {
|
|
1512
|
+
throw new Error(`Unsupported file extension: ${ext}`);
|
|
1296
1513
|
}
|
|
1514
|
+
}
|
|
1515
|
+
/**
|
|
1516
|
+
* Get all parsed data
|
|
1517
|
+
*/
|
|
1518
|
+
getParsed() {
|
|
1297
1519
|
return {
|
|
1298
|
-
|
|
1299
|
-
|
|
1520
|
+
command: this.commands[0] || "",
|
|
1521
|
+
flags: { ...this.flags },
|
|
1522
|
+
options: { ...this.options },
|
|
1523
|
+
usedKeys: Array.from(this.usedKeys)
|
|
1300
1524
|
};
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
const bindingWithCustom = resolvedBindings.find(
|
|
1310
|
-
(b) => group.keys.includes(b.key) && typeof b.resolvedCaption !== "string"
|
|
1525
|
+
}
|
|
1526
|
+
/**
|
|
1527
|
+
* Set prefixes dynamically and re-parse arguments (like legacy)
|
|
1528
|
+
*/
|
|
1529
|
+
setPrefixes(prefixes) {
|
|
1530
|
+
const arr = Array.isArray(prefixes) ? prefixes : prefixes.split(/,\s*/);
|
|
1531
|
+
const sortedArr = arr.sort(
|
|
1532
|
+
(a, b) => a.length < b.length ? 1 : a.length > b.length ? -1 : 0
|
|
1311
1533
|
);
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1322
|
-
});
|
|
1323
|
-
return items;
|
|
1324
|
-
}
|
|
1325
|
-
function formatKeys(keys) {
|
|
1326
|
-
const keyMap = {
|
|
1327
|
-
"escape": "esc",
|
|
1328
|
-
"leftArrow": "\u2190",
|
|
1329
|
-
"rightArrow": "\u2192",
|
|
1330
|
-
"upArrow": "\u2191",
|
|
1331
|
-
"downArrow": "\u2193",
|
|
1332
|
-
"return": "enter"
|
|
1333
|
-
};
|
|
1334
|
-
return keys.map((k) => keyMap[k] || k).join("/");
|
|
1534
|
+
this.prefixes = sortedArr.map((el) => el.toLowerCase());
|
|
1535
|
+
const args = process.argv.slice(2);
|
|
1536
|
+
this.parseArgs(args);
|
|
1537
|
+
}
|
|
1538
|
+
};
|
|
1539
|
+
var instance = null;
|
|
1540
|
+
function getArgsInstance() {
|
|
1541
|
+
return instance;
|
|
1335
1542
|
}
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1543
|
+
|
|
1544
|
+
// src/params/index.ts
|
|
1545
|
+
var import_joi = __toESM(require("joi"), 1);
|
|
1546
|
+
|
|
1547
|
+
// src/errors.ts
|
|
1548
|
+
var FrameworkError = class extends Error {
|
|
1549
|
+
constructor(message) {
|
|
1550
|
+
super(message);
|
|
1551
|
+
this.name = "FrameworkError";
|
|
1552
|
+
}
|
|
1553
|
+
};
|
|
1554
|
+
var ParamError = class extends FrameworkError {
|
|
1555
|
+
constructor(message) {
|
|
1556
|
+
super(message);
|
|
1557
|
+
this.name = "ParamError";
|
|
1558
|
+
}
|
|
1559
|
+
};
|
|
1560
|
+
|
|
1561
|
+
// src/params/custom-types.ts
|
|
1562
|
+
var joiEdateType = (value, helpers) => {
|
|
1563
|
+
if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z$/.test(value)) {
|
|
1564
|
+
const testDate = new Date(value);
|
|
1565
|
+
if (!isNaN(testDate.getTime())) {
|
|
1566
|
+
return value;
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
if (value instanceof Date) {
|
|
1570
|
+
return value.toISOString();
|
|
1571
|
+
}
|
|
1572
|
+
if (typeof value !== "string") {
|
|
1573
|
+
value = String(value);
|
|
1574
|
+
}
|
|
1575
|
+
if (value.toLowerCase() === "now") {
|
|
1576
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
1577
|
+
}
|
|
1578
|
+
const referenceRegex = /^@(\w+)([+-]\d+[smhdwy])$/i;
|
|
1579
|
+
const referenceMatch = value.match(referenceRegex);
|
|
1580
|
+
if (referenceMatch) {
|
|
1581
|
+
const [, paramName, relativeExpr] = referenceMatch;
|
|
1582
|
+
const context = helpers.prefs?.context;
|
|
1583
|
+
if (!context || !context.params) {
|
|
1584
|
+
throw new ParamError(`Cannot resolve cross-parameter reference @${paramName}: context not available. Ensure parameters are processed with proper context.`);
|
|
1585
|
+
}
|
|
1586
|
+
const referencedValue = context.params[paramName];
|
|
1587
|
+
if (referencedValue === void 0 || referencedValue === null) {
|
|
1588
|
+
throw new ParamError(`Cannot resolve @${paramName}: parameter "${paramName}" is not defined or has no value. Parameters are evaluated left-to-right.`);
|
|
1589
|
+
}
|
|
1590
|
+
let referenceDate;
|
|
1591
|
+
if (referencedValue instanceof Date) {
|
|
1592
|
+
referenceDate = referencedValue;
|
|
1593
|
+
} else if (typeof referencedValue === "string") {
|
|
1594
|
+
referenceDate = new Date(referencedValue);
|
|
1595
|
+
if (isNaN(referenceDate.getTime())) {
|
|
1596
|
+
throw new ParamError(`Referenced parameter @${paramName} has invalid date value: ${referencedValue}`);
|
|
1364
1597
|
}
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
actions[actionName] = handlerFn;
|
|
1368
|
-
},
|
|
1369
|
-
setKeyBinding: (bindingOrBindings) => {
|
|
1370
|
-
const bindingsToSet = Array.isArray(bindingOrBindings) ? bindingOrBindings : [bindingOrBindings];
|
|
1371
|
-
bindingsToSet.forEach((binding) => {
|
|
1372
|
-
const existingIndex = keyBindings.findIndex((b) => b.key === binding.key);
|
|
1373
|
-
if (existingIndex >= 0) {
|
|
1374
|
-
const existing = keyBindings[existingIndex];
|
|
1375
|
-
if (existing.protected) {
|
|
1376
|
-
console.warn(`Cannot override protected key: ${binding.key}`);
|
|
1377
|
-
return;
|
|
1378
|
-
}
|
|
1379
|
-
keyBindings[existingIndex] = {
|
|
1380
|
-
...existing,
|
|
1381
|
-
...binding,
|
|
1382
|
-
order: binding.order !== void 0 ? binding.order : existing.order,
|
|
1383
|
-
enabled: binding.enabled !== void 0 ? binding.enabled : existing.enabled !== void 0 ? existing.enabled : true
|
|
1384
|
-
};
|
|
1385
|
-
} else {
|
|
1386
|
-
keyBindings.push({
|
|
1387
|
-
protected: false,
|
|
1388
|
-
order: 999,
|
|
1389
|
-
enabled: true,
|
|
1390
|
-
...binding
|
|
1391
|
-
});
|
|
1392
|
-
}
|
|
1393
|
-
});
|
|
1394
|
-
},
|
|
1395
|
-
updateKeyBinding: (keyName, updates) => {
|
|
1396
|
-
const index = keyBindings.findIndex((b) => b.key === keyName);
|
|
1397
|
-
if (index >= 0) {
|
|
1398
|
-
keyBindings[index] = {
|
|
1399
|
-
...keyBindings[index],
|
|
1400
|
-
...updates
|
|
1401
|
-
};
|
|
1402
|
-
}
|
|
1403
|
-
},
|
|
1404
|
-
removeKeyBinding: (keyName) => {
|
|
1405
|
-
const index = keyBindings.findIndex((b) => b.key === keyName);
|
|
1406
|
-
if (index >= 0) {
|
|
1407
|
-
if (keyBindings[index].protected) {
|
|
1408
|
-
console.warn(`Cannot remove protected key: ${keyName}`);
|
|
1409
|
-
return;
|
|
1410
|
-
}
|
|
1411
|
-
keyBindings.splice(index, 1);
|
|
1412
|
-
}
|
|
1413
|
-
},
|
|
1414
|
-
addFooter: (item) => {
|
|
1415
|
-
customFooterItems.push(item);
|
|
1416
|
-
},
|
|
1417
|
-
clearFooter: () => {
|
|
1418
|
-
customFooterItems.length = 0;
|
|
1419
|
-
},
|
|
1420
|
-
setFooter: (items) => {
|
|
1421
|
-
customFooterItems.length = 0;
|
|
1422
|
-
const itemsArray = Array.isArray(items) ? items : [items];
|
|
1423
|
-
customFooterItems.push(...itemsArray);
|
|
1424
|
-
},
|
|
1425
|
-
update: () => {
|
|
1426
|
-
setUpdateCounter((c) => c + 1);
|
|
1427
|
-
},
|
|
1428
|
-
goBack: () => {
|
|
1429
|
-
if (actions.back) {
|
|
1430
|
-
actions.back();
|
|
1431
|
-
}
|
|
1432
|
-
},
|
|
1433
|
-
close: (result) => {
|
|
1434
|
-
cleanup(result);
|
|
1435
|
-
},
|
|
1436
|
-
parentData
|
|
1437
|
-
};
|
|
1438
|
-
if (!renderResult) {
|
|
1439
|
-
renderResult = onRender(context);
|
|
1440
|
-
}
|
|
1441
|
-
(0, import_ink3.useInput)((input, key) => {
|
|
1442
|
-
if (key.ctrl && input === "c") {
|
|
1443
|
-
cleanup(null);
|
|
1444
|
-
process.exit(0);
|
|
1445
|
-
return;
|
|
1446
|
-
}
|
|
1447
|
-
let matchedBinding = null;
|
|
1448
|
-
for (const binding of keyBindings) {
|
|
1449
|
-
let keyMatches = false;
|
|
1450
|
-
if (key[binding.key]) {
|
|
1451
|
-
keyMatches = true;
|
|
1452
|
-
} else if (input === binding.key) {
|
|
1453
|
-
keyMatches = true;
|
|
1454
|
-
}
|
|
1455
|
-
if (keyMatches) {
|
|
1456
|
-
if (binding.enabled === false) {
|
|
1457
|
-
continue;
|
|
1458
|
-
}
|
|
1459
|
-
if (binding.condition && !binding.condition(context)) {
|
|
1460
|
-
continue;
|
|
1461
|
-
}
|
|
1462
|
-
matchedBinding = binding;
|
|
1463
|
-
break;
|
|
1464
|
-
}
|
|
1465
|
-
}
|
|
1466
|
-
if (matchedBinding && actions[matchedBinding.action]) {
|
|
1467
|
-
const actionResult = actions[matchedBinding.action]({
|
|
1468
|
-
input,
|
|
1469
|
-
key,
|
|
1470
|
-
binding: matchedBinding
|
|
1471
|
-
});
|
|
1472
|
-
}
|
|
1473
|
-
});
|
|
1474
|
-
const footerLines = [];
|
|
1475
|
-
const bindingItems = formatKeyBindings(keyBindings, "long");
|
|
1476
|
-
if (bindingItems.length > 0) {
|
|
1477
|
-
const bindingsLine = [];
|
|
1478
|
-
bindingItems.forEach((item, idx) => {
|
|
1479
|
-
if (idx > 0) {
|
|
1480
|
-
bindingsLine.push(", ");
|
|
1481
|
-
}
|
|
1482
|
-
bindingsLine.push(item);
|
|
1483
|
-
});
|
|
1484
|
-
const allStrings = bindingItems.every((item) => typeof item === "string");
|
|
1485
|
-
if (allStrings) {
|
|
1486
|
-
footerLines.push(bindingsLine.join(""));
|
|
1487
|
-
} else {
|
|
1488
|
-
const wrappedBindingsLine = bindingsLine.map(
|
|
1489
|
-
(item) => typeof item === "string" ? (0, import_react3.createElement)(import_ink3.Text, {}, item) : item
|
|
1490
|
-
);
|
|
1491
|
-
footerLines.push(wrappedBindingsLine);
|
|
1492
|
-
}
|
|
1493
|
-
}
|
|
1494
|
-
customFooterItems.forEach((item) => {
|
|
1495
|
-
if (typeof item === "string") {
|
|
1496
|
-
footerLines.push(item);
|
|
1497
|
-
} else {
|
|
1498
|
-
footerLines.push(item);
|
|
1499
|
-
}
|
|
1500
|
-
});
|
|
1501
|
-
return (0, import_react3.createElement)(
|
|
1502
|
-
ScreenContainer,
|
|
1503
|
-
{},
|
|
1504
|
-
(0, import_react3.createElement)(ScreenTitle, { text: title }),
|
|
1505
|
-
(0, import_react3.createElement)(ScreenDivider),
|
|
1506
|
-
(0, import_react3.createElement)(ScreenRow, {}, (0, import_react3.createElement)(import_ink3.Text, {}, " ")),
|
|
1507
|
-
renderResult,
|
|
1508
|
-
(0, import_react3.createElement)(ScreenRow, {}, (0, import_react3.createElement)(import_ink3.Text, {}, " ")),
|
|
1509
|
-
(0, import_react3.createElement)(ScreenDivider),
|
|
1510
|
-
(0, import_react3.createElement)(ScreenFooter, { lines: footerLines })
|
|
1511
|
-
);
|
|
1512
|
-
};
|
|
1513
|
-
const cleanup = (result) => {
|
|
1514
|
-
if (instance2) instance2.unmount();
|
|
1515
|
-
setTimeout(() => resolve2(result), 50);
|
|
1516
|
-
};
|
|
1517
|
-
instance2 = (0, import_ink3.render)((0, import_react3.createElement)(Screen));
|
|
1518
|
-
});
|
|
1519
|
-
}
|
|
1520
|
-
async function showListScreen(config2) {
|
|
1521
|
-
const { title, items, onSelect, onEscape, parentData, initialSelectedIndex = 0, renderItem, getTitle, sortable, maxHeight, sortHighlightStyle, selectionMarker } = config2;
|
|
1522
|
-
return showScreen({
|
|
1523
|
-
title,
|
|
1524
|
-
parentData,
|
|
1525
|
-
onRender: (ctx) => {
|
|
1526
|
-
const selectedIndexRef = { current: initialSelectedIndex };
|
|
1527
|
-
ctx.setAction("select", () => {
|
|
1528
|
-
const selected = items[selectedIndexRef.current];
|
|
1529
|
-
if (onSelect) {
|
|
1530
|
-
const result = onSelect(selected.value, selectedIndexRef.current);
|
|
1531
|
-
ctx.close(result);
|
|
1532
|
-
}
|
|
1533
|
-
});
|
|
1534
|
-
if (onEscape) {
|
|
1535
|
-
ctx.setAction("back", () => {
|
|
1536
|
-
const result = onEscape(selectedIndexRef.current);
|
|
1537
|
-
ctx.close(result);
|
|
1538
|
-
});
|
|
1539
|
-
}
|
|
1540
|
-
ctx.setKeyBinding({ key: "return", caption: "select", action: "select", order: 2 });
|
|
1541
|
-
return (0, import_react3.createElement)(ListComponent, { items, ctx, selectedIndexRef, renderItem, getTitle, sortable, maxHeight, sortHighlightStyle, selectionMarker });
|
|
1598
|
+
} else {
|
|
1599
|
+
throw new ParamError(`Referenced parameter @${paramName} is not a valid date type (found: ${typeof referencedValue})`);
|
|
1542
1600
|
}
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
const { title, items, onSelect, onEscape, parentData, initialSelectedIndex = 0 } = config2;
|
|
1547
|
-
return showScreen({
|
|
1548
|
-
title,
|
|
1549
|
-
parentData,
|
|
1550
|
-
onRender: (ctx) => {
|
|
1551
|
-
const selectedIndexRef = { current: initialSelectedIndex };
|
|
1552
|
-
ctx.setAction("select", () => {
|
|
1553
|
-
const selected = items[selectedIndexRef.current];
|
|
1554
|
-
if (onSelect) {
|
|
1555
|
-
const result = onSelect(selected, selectedIndexRef.current);
|
|
1556
|
-
ctx.close(result);
|
|
1557
|
-
}
|
|
1558
|
-
});
|
|
1559
|
-
if (onEscape) {
|
|
1560
|
-
ctx.setAction("back", () => {
|
|
1561
|
-
const result = onEscape(selectedIndexRef.current);
|
|
1562
|
-
ctx.close(result);
|
|
1563
|
-
});
|
|
1564
|
-
}
|
|
1565
|
-
ctx.setKeyBinding({ key: "return", caption: "select", action: "select", order: 2 });
|
|
1566
|
-
return (0, import_react3.createElement)(MultiColumnListComponent, { items, ctx, selectedIndexRef });
|
|
1601
|
+
const relativeMatch2 = relativeExpr.match(/^([+-])(\d+)([smhdwy])$/i);
|
|
1602
|
+
if (!relativeMatch2) {
|
|
1603
|
+
throw new ParamError(`Invalid relative time expression in @${paramName}${relativeExpr}`);
|
|
1567
1604
|
}
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
const result = onSelect(selected, selectedIndexRef.current);
|
|
1581
|
-
ctx.close(result);
|
|
1582
|
-
}
|
|
1583
|
-
});
|
|
1584
|
-
if (onEscape) {
|
|
1585
|
-
ctx.setAction("back", () => {
|
|
1586
|
-
const result = onEscape(selectedIndexRef.current);
|
|
1587
|
-
ctx.close(result);
|
|
1588
|
-
});
|
|
1589
|
-
}
|
|
1590
|
-
ctx.setKeyBinding({ key: "return", caption: "select", action: "select", order: 2 });
|
|
1591
|
-
return (0, import_react3.createElement)(MultiColumnListWithPreviewComponent, { items, getPreviewContent, ctx, selectedIndexRef });
|
|
1605
|
+
const [, sign, amount, unit] = relativeMatch2;
|
|
1606
|
+
const offset = calculateTimeOffset(parseInt(amount, 10), unit, sign);
|
|
1607
|
+
const resultDate = new Date(referenceDate.getTime() + offset);
|
|
1608
|
+
return resultDate.toISOString();
|
|
1609
|
+
}
|
|
1610
|
+
const relativeTimeRegex = /^([+-])(\d+)([smhdwy])$/i;
|
|
1611
|
+
const relativeMatch = value.match(relativeTimeRegex);
|
|
1612
|
+
if (relativeMatch) {
|
|
1613
|
+
const [, sign, amount, unit] = relativeMatch;
|
|
1614
|
+
const numAmount = parseInt(amount, 10);
|
|
1615
|
+
if (isNaN(numAmount)) {
|
|
1616
|
+
throw new ParamError(`Invalid relative time amount: ${amount}`);
|
|
1592
1617
|
}
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
}) {
|
|
1627
|
-
return (0, import_react4.createElement)(
|
|
1628
|
-
import_ink4.Box,
|
|
1629
|
-
{},
|
|
1630
|
-
(0, import_react4.createElement)(import_ink4.Text, {
|
|
1631
|
-
color,
|
|
1632
|
-
dimColor: dimmed,
|
|
1633
|
-
bold
|
|
1634
|
-
}, text)
|
|
1635
|
-
);
|
|
1636
|
-
}
|
|
1637
|
-
function Divider({ character = "\u2500", width = 80 }) {
|
|
1638
|
-
return (0, import_react4.createElement)(
|
|
1639
|
-
import_ink4.Box,
|
|
1640
|
-
{ marginY: 1 },
|
|
1641
|
-
(0, import_react4.createElement)(import_ink4.Text, { dimColor: true }, character.repeat(width))
|
|
1642
|
-
);
|
|
1643
|
-
}
|
|
1644
|
-
function GridCell({
|
|
1645
|
-
children,
|
|
1646
|
-
width,
|
|
1647
|
-
color = "white",
|
|
1648
|
-
backgroundColor,
|
|
1649
|
-
bold = false,
|
|
1650
|
-
dimColor = false,
|
|
1651
|
-
align = "left"
|
|
1652
|
-
}) {
|
|
1653
|
-
return (0, import_react4.createElement)(
|
|
1654
|
-
import_ink4.Box,
|
|
1655
|
-
{ width },
|
|
1656
|
-
(0, import_react4.createElement)(import_ink4.Text, {
|
|
1657
|
-
color,
|
|
1658
|
-
backgroundColor,
|
|
1659
|
-
bold,
|
|
1660
|
-
dimColor,
|
|
1661
|
-
textAlign: align
|
|
1662
|
-
}, children)
|
|
1663
|
-
);
|
|
1664
|
-
}
|
|
1665
|
-
function InputField({ prompt, value, onChange, onSubmit }) {
|
|
1666
|
-
return (0, import_react4.createElement)(
|
|
1667
|
-
import_ink4.Box,
|
|
1668
|
-
{ flexDirection: "column" },
|
|
1669
|
-
(0, import_react4.createElement)(import_ink4.Text, {}, prompt),
|
|
1670
|
-
(0, import_react4.createElement)(
|
|
1671
|
-
import_ink4.Box,
|
|
1672
|
-
{ marginTop: 1 },
|
|
1673
|
-
(0, import_react4.createElement)(import_ink4.Text, { color: "cyan" }, " > ", value, "_")
|
|
1674
|
-
)
|
|
1675
|
-
);
|
|
1676
|
-
}
|
|
1677
|
-
|
|
1678
|
-
// src/screen/utils.ts
|
|
1679
|
-
function buildBreadcrumb(parts) {
|
|
1680
|
-
if (parts.length === 0) return "";
|
|
1681
|
-
if (parts.length === 1) return parts[0];
|
|
1682
|
-
return parts.slice(1).map((part) => `\u2190 ${part}`).join(" ");
|
|
1683
|
-
}
|
|
1684
|
-
function buildDetailBreadcrumb(path4, suffix = "") {
|
|
1685
|
-
if (path4.length <= 1) {
|
|
1686
|
-
return suffix ? `\u2190 ${suffix}` : path4[0] || "";
|
|
1618
|
+
const offset = calculateTimeOffset(numAmount, unit, sign);
|
|
1619
|
+
const resultDate = new Date(Date.now() + offset);
|
|
1620
|
+
return resultDate.toISOString();
|
|
1621
|
+
}
|
|
1622
|
+
const parsedDate = new Date(value);
|
|
1623
|
+
if (isNaN(parsedDate.getTime())) {
|
|
1624
|
+
throw new ParamError(`Invalid date format: ${value}. Expected a valid date string, "now", relative time expression (e.g., "-2h", "+1d"), or cross-parameter reference (e.g., "@startTime+2h")`);
|
|
1625
|
+
}
|
|
1626
|
+
return parsedDate.toISOString();
|
|
1627
|
+
};
|
|
1628
|
+
function calculateTimeOffset(amount, unit, sign) {
|
|
1629
|
+
let multiplier = 1;
|
|
1630
|
+
switch (unit.toLowerCase()) {
|
|
1631
|
+
case "s":
|
|
1632
|
+
multiplier = 1e3;
|
|
1633
|
+
break;
|
|
1634
|
+
case "m":
|
|
1635
|
+
multiplier = 60 * 1e3;
|
|
1636
|
+
break;
|
|
1637
|
+
case "h":
|
|
1638
|
+
multiplier = 60 * 60 * 1e3;
|
|
1639
|
+
break;
|
|
1640
|
+
case "d":
|
|
1641
|
+
multiplier = 24 * 60 * 60 * 1e3;
|
|
1642
|
+
break;
|
|
1643
|
+
case "w":
|
|
1644
|
+
multiplier = 7 * 24 * 60 * 60 * 1e3;
|
|
1645
|
+
break;
|
|
1646
|
+
case "y":
|
|
1647
|
+
multiplier = 365 * 24 * 60 * 60 * 1e3;
|
|
1648
|
+
break;
|
|
1649
|
+
default:
|
|
1650
|
+
throw new ParamError(`Invalid time unit: ${unit}. Supported units: s, m, h, d, w, y`);
|
|
1687
1651
|
}
|
|
1688
|
-
|
|
1689
|
-
return suffix ? `${breadcrumb} ${suffix}` : breadcrumb;
|
|
1652
|
+
return sign === "+" ? amount * multiplier : -amount * multiplier;
|
|
1690
1653
|
}
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
const {
|
|
1695
|
-
navigation = null,
|
|
1696
|
-
actions = null,
|
|
1697
|
-
info = null,
|
|
1698
|
-
escape = "Esc to go back",
|
|
1699
|
-
custom = null
|
|
1700
|
-
} = config2;
|
|
1701
|
-
const lines = [];
|
|
1702
|
-
const mainParts = [];
|
|
1703
|
-
if (navigation) {
|
|
1704
|
-
mainParts.push(navigation);
|
|
1654
|
+
var joiStringArrayType = (type) => (value, helpers) => {
|
|
1655
|
+
if (value === void 0 || typeof value === "function") {
|
|
1656
|
+
return [];
|
|
1705
1657
|
}
|
|
1706
|
-
|
|
1707
|
-
|
|
1658
|
+
const arr = value.split(/,\s*/).map((el) => {
|
|
1659
|
+
if (type === "number") {
|
|
1660
|
+
const v = parseInt(el, 10);
|
|
1661
|
+
if (isNaN(v)) {
|
|
1662
|
+
throw new ParamError(`array element "${el}" should be numeric`);
|
|
1663
|
+
}
|
|
1664
|
+
return v;
|
|
1665
|
+
} else if (type === "boolean") {
|
|
1666
|
+
const v = el.match(/true|t|yes|1/i) ? true : el.match(/false|f|no|0/i) ? false : null;
|
|
1667
|
+
if (v === null) {
|
|
1668
|
+
throw new ParamError(`array element "${el}" should be boolean`);
|
|
1669
|
+
}
|
|
1670
|
+
return v;
|
|
1671
|
+
} else if (type === "string") {
|
|
1672
|
+
return el;
|
|
1673
|
+
} else {
|
|
1674
|
+
throw new ParamError(`unknown type "${type}" for array elements`);
|
|
1675
|
+
}
|
|
1676
|
+
});
|
|
1677
|
+
return arr;
|
|
1678
|
+
};
|
|
1679
|
+
|
|
1680
|
+
// src/params/index.ts
|
|
1681
|
+
var Params = class {
|
|
1682
|
+
params = {};
|
|
1683
|
+
definitions = {};
|
|
1684
|
+
args;
|
|
1685
|
+
paramSetters = [];
|
|
1686
|
+
paramGetters = [];
|
|
1687
|
+
constructor({ args }, opts = {}) {
|
|
1688
|
+
this.args = args;
|
|
1689
|
+
for (const [k, v] of Object.entries(opts)) {
|
|
1690
|
+
this.params[k] = v;
|
|
1691
|
+
}
|
|
1708
1692
|
}
|
|
1709
|
-
|
|
1710
|
-
|
|
1693
|
+
/**
|
|
1694
|
+
* Assign a parameter definition
|
|
1695
|
+
*/
|
|
1696
|
+
assignDefinition(key, definition) {
|
|
1697
|
+
if (this.definitions[key] && !definition) {
|
|
1698
|
+
return this.definitions[key];
|
|
1699
|
+
}
|
|
1700
|
+
let type;
|
|
1701
|
+
if (!definition) {
|
|
1702
|
+
type = import_joi.default.string();
|
|
1703
|
+
} else if (import_joi.default.isSchema(definition)) {
|
|
1704
|
+
type = definition;
|
|
1705
|
+
} else if (import_joi.default.isSchema(definition.type)) {
|
|
1706
|
+
type = definition.type;
|
|
1707
|
+
} else if (typeof definition === "string") {
|
|
1708
|
+
type = this.toJoi(definition);
|
|
1709
|
+
} else if (typeof definition.type === "string") {
|
|
1710
|
+
type = this.toJoi(definition.type);
|
|
1711
|
+
} else if (!definition.type) {
|
|
1712
|
+
type = import_joi.default.string();
|
|
1713
|
+
} else {
|
|
1714
|
+
type = import_joi.default.string();
|
|
1715
|
+
}
|
|
1716
|
+
if (!this.definitions[key]) {
|
|
1717
|
+
this.definitions[key] = {};
|
|
1718
|
+
}
|
|
1719
|
+
this.definitions[key].type = type;
|
|
1720
|
+
if (definition && definition.values) {
|
|
1721
|
+
if (Array.isArray(definition.values)) {
|
|
1722
|
+
this.definitions[key].values = definition.values;
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
return this.definitions[key];
|
|
1711
1726
|
}
|
|
1712
|
-
|
|
1713
|
-
|
|
1727
|
+
/**
|
|
1728
|
+
* Convert string definition to Joi schema
|
|
1729
|
+
*/
|
|
1730
|
+
toJoi(str) {
|
|
1731
|
+
let type;
|
|
1732
|
+
if (str.match(/^string|^text/i)) {
|
|
1733
|
+
type = import_joi.default.string();
|
|
1734
|
+
} else if (str.match(/^number|^integer|^int/i)) {
|
|
1735
|
+
type = import_joi.default.number();
|
|
1736
|
+
} else if (str.match(/^boolean|^bool/i)) {
|
|
1737
|
+
type = import_joi.default.boolean();
|
|
1738
|
+
} else if (str.match(/^date/i)) {
|
|
1739
|
+
type = import_joi.default.custom(joiEdateType);
|
|
1740
|
+
} else if (str.match(/^duration/i)) {
|
|
1741
|
+
type = import_joi.default.string().isoDuration();
|
|
1742
|
+
} else if (str.match(/^array/i)) {
|
|
1743
|
+
let elementTypes = "string";
|
|
1744
|
+
const tmp = str.match(/\((.*)\)/);
|
|
1745
|
+
if (tmp && tmp[1].match(/string/i)) {
|
|
1746
|
+
elementTypes = "string";
|
|
1747
|
+
} else if (tmp && tmp[1].match(/number|integer|int/i)) {
|
|
1748
|
+
elementTypes = "number";
|
|
1749
|
+
} else if (tmp && tmp[1].match(/boolean|bool/i)) {
|
|
1750
|
+
elementTypes = "boolean";
|
|
1751
|
+
}
|
|
1752
|
+
type = import_joi.default.custom(joiStringArrayType(elementTypes));
|
|
1753
|
+
} else {
|
|
1754
|
+
type = import_joi.default.string();
|
|
1755
|
+
}
|
|
1756
|
+
const regexForDefault = /\bdefault\s+([^\s]+)/;
|
|
1757
|
+
const matchForDefault = str.match(regexForDefault);
|
|
1758
|
+
if (matchForDefault) {
|
|
1759
|
+
const defValObj = type.validate(matchForDefault[1]);
|
|
1760
|
+
if (defValObj.error) {
|
|
1761
|
+
throw new ParamError(`default value "${defValObj.value}" type mismatch`);
|
|
1762
|
+
}
|
|
1763
|
+
type = type.default(defValObj.value);
|
|
1764
|
+
} else if (str.match(/required/)) {
|
|
1765
|
+
type = type.required();
|
|
1766
|
+
}
|
|
1767
|
+
return type;
|
|
1714
1768
|
}
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1769
|
+
/**
|
|
1770
|
+
* Validate a value against a definition
|
|
1771
|
+
*/
|
|
1772
|
+
validate(key, val, def) {
|
|
1773
|
+
const { value, error } = def.type.validate(val, { context: { params: this.params } });
|
|
1774
|
+
if (error) {
|
|
1775
|
+
const errs = error.details.map((el) => el.message).join(", ");
|
|
1776
|
+
throw new ParamError(`"${key}" validation error: ${errs}`);
|
|
1777
|
+
}
|
|
1778
|
+
return value;
|
|
1718
1779
|
}
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1780
|
+
/**
|
|
1781
|
+
* Get a parameter value with validation
|
|
1782
|
+
*/
|
|
1783
|
+
get(key, definition) {
|
|
1784
|
+
const def = this.assignDefinition(key, definition);
|
|
1785
|
+
let valFromGetters = void 0;
|
|
1786
|
+
if (def.volatile || true) {
|
|
1787
|
+
valFromGetters = this.runAllRegisteredGetters(key);
|
|
1788
|
+
}
|
|
1789
|
+
const valFromArgs = this.args.get(key);
|
|
1790
|
+
const valFromParams = this.params[key];
|
|
1791
|
+
const res = valFromGetters ? this.validate(key, valFromGetters, def) : valFromArgs ? this.validate(key, valFromArgs, def) : this.validate(key, valFromParams, def);
|
|
1792
|
+
if (res !== void 0 && def.values && !def.values.includes(res)) {
|
|
1793
|
+
throw new ParamError(`key ${key} should be one of ${def.values}`);
|
|
1794
|
+
}
|
|
1795
|
+
return res;
|
|
1722
1796
|
}
|
|
1723
|
-
return lines;
|
|
1724
|
-
}
|
|
1725
|
-
var FooterPresets = {
|
|
1726
1797
|
/**
|
|
1727
|
-
*
|
|
1798
|
+
* Set a parameter value with validation
|
|
1728
1799
|
*/
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1800
|
+
set(key, val, definition) {
|
|
1801
|
+
if (val && val.type && val.value) {
|
|
1802
|
+
definition = val;
|
|
1803
|
+
val = val.value;
|
|
1804
|
+
}
|
|
1805
|
+
const def = this.assignDefinition(key, definition);
|
|
1806
|
+
if (!this.runAllRegisteredSetters(key, val)) {
|
|
1807
|
+
this.params[key] = val;
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1735
1810
|
/**
|
|
1736
|
-
*
|
|
1811
|
+
* Get all parameters from definitions
|
|
1812
|
+
* Processes parameters left-to-right to support cross-parameter references
|
|
1737
1813
|
*/
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1814
|
+
getAll(defs) {
|
|
1815
|
+
const res = {};
|
|
1816
|
+
for (const [k, def] of Object.entries(defs)) {
|
|
1817
|
+
const value = this.get(k, def);
|
|
1818
|
+
res[k] = value;
|
|
1819
|
+
if (value !== void 0) {
|
|
1820
|
+
this.params[k] = value;
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
return res;
|
|
1824
|
+
}
|
|
1744
1825
|
/**
|
|
1745
|
-
*
|
|
1826
|
+
* Run all registered getters for a key
|
|
1746
1827
|
*/
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1828
|
+
runAllRegisteredGetters(key) {
|
|
1829
|
+
let val = null;
|
|
1830
|
+
for (const getter of this.paramGetters) {
|
|
1831
|
+
val = getter(key, this.definitions[key]);
|
|
1832
|
+
if (val !== void 0) {
|
|
1833
|
+
break;
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
return val;
|
|
1837
|
+
}
|
|
1751
1838
|
/**
|
|
1752
|
-
*
|
|
1839
|
+
* Run all registered setters for a key
|
|
1753
1840
|
*/
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1841
|
+
runAllRegisteredSetters(key, value) {
|
|
1842
|
+
let setterUsed = false;
|
|
1843
|
+
for (const setter of this.paramSetters) {
|
|
1844
|
+
setterUsed = setter(key, value);
|
|
1845
|
+
if (setterUsed) {
|
|
1846
|
+
break;
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
return setterUsed;
|
|
1850
|
+
}
|
|
1757
1851
|
/**
|
|
1758
|
-
*
|
|
1852
|
+
* Register a parameter getter
|
|
1759
1853
|
*/
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
escape: "Esc to exit"
|
|
1764
|
-
}),
|
|
1854
|
+
registerParamGetter(fn) {
|
|
1855
|
+
this.paramGetters.push(fn);
|
|
1856
|
+
}
|
|
1765
1857
|
/**
|
|
1766
|
-
*
|
|
1858
|
+
* Register a parameter setter
|
|
1767
1859
|
*/
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
navigation: "\u2191/\u2193 to navigate",
|
|
1771
|
-
actions: "Enter to select",
|
|
1772
|
-
escape: "Esc to go back"
|
|
1773
|
-
});
|
|
1774
|
-
if (hasAudio) {
|
|
1775
|
-
parts.push("Audio available");
|
|
1776
|
-
}
|
|
1777
|
-
return parts;
|
|
1860
|
+
registerParamSetter(fn) {
|
|
1861
|
+
this.paramSetters.push(fn);
|
|
1778
1862
|
}
|
|
1779
1863
|
};
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
return ["Esc to go back"];
|
|
1783
|
-
}
|
|
1784
|
-
const navigation = messages.filter((m) => m.includes("\u2191") || m.includes("\u2193") || m.includes("\u2190") || m.includes("\u2192"));
|
|
1785
|
-
const actions = messages.filter((m) => m.includes("Enter") || m.includes("select") || m.includes("submit"));
|
|
1786
|
-
const escape = messages.filter((m) => m.includes("Esc"));
|
|
1787
|
-
const others = messages.filter(
|
|
1788
|
-
(m) => !navigation.includes(m) && !actions.includes(m) && !escape.includes(m)
|
|
1789
|
-
);
|
|
1790
|
-
const lines = [];
|
|
1791
|
-
const mainLine = [...navigation, ...actions, ...escape].join(", ");
|
|
1792
|
-
if (mainLine) lines.push(mainLine);
|
|
1793
|
-
lines.push(...others);
|
|
1794
|
-
return lines;
|
|
1795
|
-
}
|
|
1864
|
+
var paramsInstance = null;
|
|
1865
|
+
var getParamsInstance = () => paramsInstance;
|
|
1796
1866
|
|
|
1797
|
-
// src/screen
|
|
1798
|
-
|
|
1799
|
-
async function load() {
|
|
1800
|
-
if (loadPromise) return loadPromise;
|
|
1801
|
-
loadPromise = Promise.all([
|
|
1802
|
-
import("react"),
|
|
1803
|
-
import("ink")
|
|
1804
|
-
]).then(() => {
|
|
1805
|
-
});
|
|
1806
|
-
return loadPromise;
|
|
1807
|
-
}
|
|
1808
|
-
if (typeof window === "undefined") {
|
|
1809
|
-
load().catch(() => {
|
|
1810
|
-
});
|
|
1811
|
-
}
|
|
1867
|
+
// src/screen.ts
|
|
1868
|
+
init_screen();
|
|
1812
1869
|
|
|
1813
1870
|
// src/filedatabase/index.ts
|
|
1814
1871
|
var import_fs4 = __toESM(require("fs"), 1);
|