@nmakarov/cli-toolkit 0.7.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/args.cjs +35 -5
- package/dist/args.cjs.map +1 -1
- package/dist/args.js +35 -5
- package/dist/args.js.map +1 -1
- package/dist/index.cjs +1938 -1690
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1888 -1645
- package/dist/index.js.map +1 -1
- package/dist/init.cjs +1380 -57
- package/dist/init.cjs.map +1 -1
- package/dist/init.js +1384 -57
- package/dist/init.js.map +1 -1
- package/dist/logger.cjs +98 -22
- package/dist/logger.cjs.map +1 -1
- package/dist/logger.js +97 -21
- package/dist/logger.js.map +1 -1
- package/dist/params.cjs +90 -20
- package/dist/params.cjs.map +1 -1
- package/dist/params.js +90 -18
- package/dist/params.js.map +1 -1
- package/dist/screen.cjs +2 -2
- package/package.json +1 -1
package/dist/init.cjs
CHANGED
|
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __esm = (fn, res) => function __init() {
|
|
9
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
+
};
|
|
8
11
|
var __export = (target, all) => {
|
|
9
12
|
for (var name in all)
|
|
10
13
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -27,6 +30,1091 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
27
30
|
));
|
|
28
31
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
32
|
|
|
33
|
+
// src/screen/components.ts
|
|
34
|
+
function getScreenWidth(maxWidth = null) {
|
|
35
|
+
const terminalWidth = process.stdout.columns || 80;
|
|
36
|
+
const availableWidth = Math.max(20, terminalWidth - 4);
|
|
37
|
+
return maxWidth ? Math.min(availableWidth, maxWidth) : availableWidth;
|
|
38
|
+
}
|
|
39
|
+
function ScreenContainer({ children }) {
|
|
40
|
+
const width = getScreenWidth();
|
|
41
|
+
return (0, import_react.createElement)(import_ink.Box, {
|
|
42
|
+
flexDirection: "column",
|
|
43
|
+
marginTop: 1,
|
|
44
|
+
borderStyle: "single",
|
|
45
|
+
borderColor: "cyan",
|
|
46
|
+
paddingX: 1,
|
|
47
|
+
width
|
|
48
|
+
// Use the calculated width directly
|
|
49
|
+
}, children);
|
|
50
|
+
}
|
|
51
|
+
function ScreenRow({ children }) {
|
|
52
|
+
return (0, import_react.createElement)(import_ink.Box, { flexDirection: "column" }, children);
|
|
53
|
+
}
|
|
54
|
+
function ScreenTitle({ text }) {
|
|
55
|
+
return (0, import_react.createElement)(
|
|
56
|
+
ScreenRow,
|
|
57
|
+
{},
|
|
58
|
+
(0, import_react.createElement)(import_ink.Text, { bold: true, color: "cyan" }, text)
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
function ScreenDivider({ width }) {
|
|
62
|
+
const dividerWidth = width || getScreenWidth() - 4;
|
|
63
|
+
return (0, import_react.createElement)(import_ink.Text, { color: "cyan", dimColor: true }, "\u2500".repeat(dividerWidth));
|
|
64
|
+
}
|
|
65
|
+
function ScreenBody({ children, alignItems = "flex-start" }) {
|
|
66
|
+
return (0, import_react.createElement)(import_ink.Box, { flexDirection: "column", alignItems }, children);
|
|
67
|
+
}
|
|
68
|
+
function ScreenFooter({ lines, textStyle }) {
|
|
69
|
+
const defaultTextStyle = {
|
|
70
|
+
dimColor: true,
|
|
71
|
+
color: "white"
|
|
72
|
+
};
|
|
73
|
+
const finalTextStyle = { ...defaultTextStyle, ...textStyle };
|
|
74
|
+
const flattenAndWrap = (items, keyPrefix = "") => {
|
|
75
|
+
const result = [];
|
|
76
|
+
let keyIndex = 0;
|
|
77
|
+
items.forEach((item, index) => {
|
|
78
|
+
if (Array.isArray(item)) {
|
|
79
|
+
const nested = flattenAndWrap(item, `${keyPrefix}-${index}`);
|
|
80
|
+
result.push(...nested);
|
|
81
|
+
} else if (typeof item === "string") {
|
|
82
|
+
result.push(
|
|
83
|
+
(0, import_react.createElement)(import_ink.Text, { key: `${keyPrefix}-${keyIndex++}`, ...finalTextStyle }, item)
|
|
84
|
+
);
|
|
85
|
+
} else {
|
|
86
|
+
const element = item;
|
|
87
|
+
if (element.key === null || element.key === void 0) {
|
|
88
|
+
result.push(
|
|
89
|
+
(0, import_react.createElement)(import_ink.Text, { key: `${keyPrefix}-${keyIndex++}`, ...finalTextStyle }, element)
|
|
90
|
+
);
|
|
91
|
+
} else {
|
|
92
|
+
result.push(element);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
return result;
|
|
97
|
+
};
|
|
98
|
+
const wrappedItems = flattenAndWrap(lines);
|
|
99
|
+
return (0, import_react.createElement)(
|
|
100
|
+
import_ink.Box,
|
|
101
|
+
{ flexDirection: "column" },
|
|
102
|
+
(0, import_react.createElement)(import_ink.Box, { flexDirection: "row" }, ...wrappedItems)
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
var import_react, import_ink;
|
|
106
|
+
var init_components = __esm({
|
|
107
|
+
"src/screen/components.ts"() {
|
|
108
|
+
"use strict";
|
|
109
|
+
import_react = require("react");
|
|
110
|
+
import_ink = require("ink");
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// src/screen/list-components.ts
|
|
115
|
+
function MultiColumnListComponent({ items, ctx, selectedIndexRef }) {
|
|
116
|
+
const [, forceUpdate] = (0, import_react2.useState)({});
|
|
117
|
+
const termWidth = (process.stdout.columns || 80) - 8;
|
|
118
|
+
const maxItemLength = Math.max(...items.map((w) => w.length));
|
|
119
|
+
const columnWidth = maxItemLength + 3;
|
|
120
|
+
const columns = Math.max(1, Math.floor(termWidth / columnWidth));
|
|
121
|
+
const itemsPerColumn = Math.ceil(items.length / columns);
|
|
122
|
+
(0, import_react2.useEffect)(() => {
|
|
123
|
+
ctx.setAction("moveUp", () => {
|
|
124
|
+
selectedIndexRef.current = Math.max(0, selectedIndexRef.current - 1);
|
|
125
|
+
forceUpdate({});
|
|
126
|
+
});
|
|
127
|
+
ctx.setAction("moveDown", () => {
|
|
128
|
+
selectedIndexRef.current = Math.min(items.length - 1, selectedIndexRef.current + 1);
|
|
129
|
+
forceUpdate({});
|
|
130
|
+
});
|
|
131
|
+
ctx.setAction("moveLeft", () => {
|
|
132
|
+
if (selectedIndexRef.current === 0) {
|
|
133
|
+
ctx.goBack();
|
|
134
|
+
} else {
|
|
135
|
+
selectedIndexRef.current = Math.max(0, selectedIndexRef.current - itemsPerColumn);
|
|
136
|
+
forceUpdate({});
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
ctx.setAction("moveRight", () => {
|
|
140
|
+
selectedIndexRef.current = Math.min(items.length - 1, selectedIndexRef.current + itemsPerColumn);
|
|
141
|
+
forceUpdate({});
|
|
142
|
+
});
|
|
143
|
+
ctx.setKeyBinding([
|
|
144
|
+
{ key: "leftArrow", caption: "navigate", action: "moveLeft", order: 0 },
|
|
145
|
+
{ key: "rightArrow", caption: "navigate", action: "moveRight", order: 0 },
|
|
146
|
+
{ key: "upArrow", caption: "navigate", action: "moveUp", order: 0 },
|
|
147
|
+
{ key: "downArrow", caption: "navigate", action: "moveDown", order: 0 }
|
|
148
|
+
]);
|
|
149
|
+
ctx.addFooter(`Total: ${items.length} items`);
|
|
150
|
+
}, []);
|
|
151
|
+
const selectedIndex = selectedIndexRef.current;
|
|
152
|
+
const rows = [];
|
|
153
|
+
for (let row = 0; row < itemsPerColumn; row++) {
|
|
154
|
+
const cols = [];
|
|
155
|
+
for (let col = 0; col < columns; col++) {
|
|
156
|
+
const index = col * itemsPerColumn + row;
|
|
157
|
+
if (index < items.length) {
|
|
158
|
+
const isSelected = index === selectedIndex;
|
|
159
|
+
cols.push(
|
|
160
|
+
h2(
|
|
161
|
+
import_ink2.Box,
|
|
162
|
+
{ key: index, width: columnWidth },
|
|
163
|
+
h2(import_ink2.Text, {
|
|
164
|
+
color: isSelected ? "black" : "white",
|
|
165
|
+
backgroundColor: isSelected ? "cyan" : void 0,
|
|
166
|
+
bold: isSelected
|
|
167
|
+
}, items[index].padEnd(maxItemLength))
|
|
168
|
+
)
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
rows.push(
|
|
173
|
+
h2(ScreenRow, { key: row, children: h2(import_ink2.Box, { flexDirection: "row" }, ...cols) })
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
return h2(import_ink2.Box, { flexDirection: "column" }, ...rows);
|
|
177
|
+
}
|
|
178
|
+
function MultiColumnListWithPreviewComponent({
|
|
179
|
+
items,
|
|
180
|
+
getPreviewContent,
|
|
181
|
+
ctx,
|
|
182
|
+
selectedIndexRef
|
|
183
|
+
}) {
|
|
184
|
+
const [, forceUpdate] = (0, import_react2.useState)({});
|
|
185
|
+
const termWidth = (process.stdout.columns || 80) - 8;
|
|
186
|
+
const maxItemLength = Math.max(...items.map((w) => w.length));
|
|
187
|
+
const columnWidth = maxItemLength + 3;
|
|
188
|
+
const columns = Math.max(1, Math.floor(termWidth / columnWidth));
|
|
189
|
+
const itemsPerColumn = Math.ceil(items.length / columns);
|
|
190
|
+
(0, import_react2.useEffect)(() => {
|
|
191
|
+
ctx.setAction("moveUp", () => {
|
|
192
|
+
selectedIndexRef.current = Math.max(0, selectedIndexRef.current - 1);
|
|
193
|
+
forceUpdate({});
|
|
194
|
+
});
|
|
195
|
+
ctx.setAction("moveDown", () => {
|
|
196
|
+
selectedIndexRef.current = Math.min(items.length - 1, selectedIndexRef.current + 1);
|
|
197
|
+
forceUpdate({});
|
|
198
|
+
});
|
|
199
|
+
ctx.setAction("moveLeft", () => {
|
|
200
|
+
if (selectedIndexRef.current === 0) {
|
|
201
|
+
ctx.goBack();
|
|
202
|
+
} else {
|
|
203
|
+
selectedIndexRef.current = Math.max(0, selectedIndexRef.current - itemsPerColumn);
|
|
204
|
+
forceUpdate({});
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
ctx.setAction("moveRight", () => {
|
|
208
|
+
selectedIndexRef.current = Math.min(items.length - 1, selectedIndexRef.current + itemsPerColumn);
|
|
209
|
+
forceUpdate({});
|
|
210
|
+
});
|
|
211
|
+
ctx.setKeyBinding([
|
|
212
|
+
{ key: "leftArrow", caption: "navigate", action: "moveLeft", order: 0 },
|
|
213
|
+
{ key: "rightArrow", caption: "navigate", action: "moveRight", order: 0 },
|
|
214
|
+
{ key: "upArrow", caption: "navigate", action: "moveUp", order: 0 },
|
|
215
|
+
{ key: "downArrow", caption: "navigate", action: "moveDown", order: 0 }
|
|
216
|
+
]);
|
|
217
|
+
ctx.addFooter(`Total: ${items.length} items`);
|
|
218
|
+
}, []);
|
|
219
|
+
const selectedIndex = selectedIndexRef.current;
|
|
220
|
+
const selectedItem = items[selectedIndex];
|
|
221
|
+
const rows = [];
|
|
222
|
+
for (let row = 0; row < itemsPerColumn; row++) {
|
|
223
|
+
const cols = [];
|
|
224
|
+
for (let col = 0; col < columns; col++) {
|
|
225
|
+
const index = col * itemsPerColumn + row;
|
|
226
|
+
if (index < items.length) {
|
|
227
|
+
const isSelected = index === selectedIndex;
|
|
228
|
+
cols.push(
|
|
229
|
+
h2(
|
|
230
|
+
import_ink2.Box,
|
|
231
|
+
{ key: index, width: columnWidth },
|
|
232
|
+
h2(import_ink2.Text, {
|
|
233
|
+
color: isSelected ? "black" : "white",
|
|
234
|
+
backgroundColor: isSelected ? "cyan" : void 0,
|
|
235
|
+
bold: isSelected
|
|
236
|
+
}, items[index].padEnd(maxItemLength))
|
|
237
|
+
)
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
rows.push(
|
|
242
|
+
h2(ScreenRow, { key: row, children: h2(import_ink2.Box, { flexDirection: "row" }, ...cols) })
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
const previewContent = getPreviewContent ? getPreviewContent(selectedItem) : selectedItem;
|
|
246
|
+
const previewRows = [];
|
|
247
|
+
if (typeof previewContent === "string") {
|
|
248
|
+
previewRows.push(h2(ScreenRow, { key: "preview-string", children: h2(import_ink2.Text, { bold: true }, previewContent) }));
|
|
249
|
+
} else if (typeof previewContent === "object" && !import_react2.default.isValidElement(previewContent) && previewContent !== null) {
|
|
250
|
+
Object.entries(previewContent).forEach(([key, value], idx) => {
|
|
251
|
+
previewRows.push(h2(ScreenRow, { key: `preview-${key}-${idx}`, children: h2(import_ink2.Text, {}, `${key}: ${value}`) }));
|
|
252
|
+
});
|
|
253
|
+
} else if (import_react2.default.isValidElement(previewContent)) {
|
|
254
|
+
previewRows.push(h2(ScreenRow, { key: "preview-element", children: previewContent }));
|
|
255
|
+
}
|
|
256
|
+
return h2(
|
|
257
|
+
import_ink2.Box,
|
|
258
|
+
{ flexDirection: "column" },
|
|
259
|
+
...rows,
|
|
260
|
+
h2(ScreenRow, { key: "spacer-1", children: h2(import_ink2.Text, {}, " ") }),
|
|
261
|
+
h2(ScreenDivider, { key: "divider" }),
|
|
262
|
+
h2(ScreenRow, { key: "spacer-2", children: h2(import_ink2.Text, {}, " ") }),
|
|
263
|
+
...previewRows
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sortable = false, maxHeight, sortHighlightStyle, selectionMarker = " " }) {
|
|
267
|
+
const [, forceUpdate] = (0, import_react2.useState)({});
|
|
268
|
+
const [sortOrder, setSortOrder] = (0, import_react2.useState)("none");
|
|
269
|
+
const [scrollOffset, setScrollOffset] = (0, import_react2.useState)(0);
|
|
270
|
+
const scrollStateRef = (0, import_react2.useRef)({ scrollOffset: 0, maxHeight: 0, totalItems: 0 });
|
|
271
|
+
const defaultGetTitle = (item) => {
|
|
272
|
+
return getTitle ? getTitle(item) : typeof item.value === "string" ? item.value : item.value?.title || item.name;
|
|
273
|
+
};
|
|
274
|
+
const titleGetter = getTitle || defaultGetTitle;
|
|
275
|
+
const displayItems = sortable && sortOrder !== "none" ? [...items].sort((a, b) => {
|
|
276
|
+
const titleA = titleGetter(a).toLowerCase();
|
|
277
|
+
const titleB = titleGetter(b).toLowerCase();
|
|
278
|
+
if (sortOrder === "asc") {
|
|
279
|
+
return titleA < titleB ? -1 : titleA > titleB ? 1 : 0;
|
|
280
|
+
} else {
|
|
281
|
+
return titleA > titleB ? -1 : titleA < titleB ? 1 : 0;
|
|
282
|
+
}
|
|
283
|
+
}) : items;
|
|
284
|
+
const effectiveMaxHeight = maxHeight || displayItems.length;
|
|
285
|
+
const canScroll = displayItems.length > effectiveMaxHeight;
|
|
286
|
+
const maxScrollOffset = Math.max(0, displayItems.length - effectiveMaxHeight);
|
|
287
|
+
const clampedScrollOffset = Math.min(Math.max(0, scrollOffset), maxScrollOffset);
|
|
288
|
+
const visibleItems = displayItems.slice(clampedScrollOffset, clampedScrollOffset + effectiveMaxHeight);
|
|
289
|
+
const canScrollUp = clampedScrollOffset > 0;
|
|
290
|
+
const canScrollDown = clampedScrollOffset < maxScrollOffset;
|
|
291
|
+
scrollStateRef.current = { scrollOffset, maxHeight: effectiveMaxHeight, totalItems: displayItems.length };
|
|
292
|
+
(0, import_react2.useEffect)(() => {
|
|
293
|
+
ctx.setAction("moveUp", () => {
|
|
294
|
+
const newIndex = Math.max(0, selectedIndexRef.current - 1);
|
|
295
|
+
selectedIndexRef.current = newIndex;
|
|
296
|
+
const { scrollOffset: currentScrollOffset, maxHeight: currentMaxHeight, totalItems } = scrollStateRef.current;
|
|
297
|
+
const currentMaxScrollOffset = Math.max(0, totalItems - currentMaxHeight);
|
|
298
|
+
const currentClampedScrollOffset = Math.min(Math.max(0, currentScrollOffset), currentMaxScrollOffset);
|
|
299
|
+
if (newIndex < currentClampedScrollOffset) {
|
|
300
|
+
setScrollOffset(newIndex);
|
|
301
|
+
}
|
|
302
|
+
forceUpdate({});
|
|
303
|
+
});
|
|
304
|
+
ctx.setAction("moveDown", () => {
|
|
305
|
+
const currentItems = sortable && sortOrder !== "none" ? [...items].sort((a, b) => {
|
|
306
|
+
const titleA = titleGetter(a).toLowerCase();
|
|
307
|
+
const titleB = titleGetter(b).toLowerCase();
|
|
308
|
+
if (sortOrder === "asc") {
|
|
309
|
+
return titleA < titleB ? -1 : titleA > titleB ? 1 : 0;
|
|
310
|
+
} else {
|
|
311
|
+
return titleA > titleB ? -1 : titleA < titleB ? 1 : 0;
|
|
312
|
+
}
|
|
313
|
+
}) : items;
|
|
314
|
+
const maxIndex = currentItems.length - 1;
|
|
315
|
+
const newIndex = Math.min(maxIndex, selectedIndexRef.current + 1);
|
|
316
|
+
selectedIndexRef.current = newIndex;
|
|
317
|
+
const { scrollOffset: currentScrollOffset, maxHeight: currentMaxHeight, totalItems } = scrollStateRef.current;
|
|
318
|
+
const currentMaxScrollOffset = Math.max(0, totalItems - currentMaxHeight);
|
|
319
|
+
const currentClampedScrollOffset = Math.min(Math.max(0, currentScrollOffset), currentMaxScrollOffset);
|
|
320
|
+
if (newIndex >= currentClampedScrollOffset + currentMaxHeight) {
|
|
321
|
+
setScrollOffset(newIndex - currentMaxHeight + 1);
|
|
322
|
+
}
|
|
323
|
+
forceUpdate({});
|
|
324
|
+
});
|
|
325
|
+
ctx.setAction("scrollUp", () => {
|
|
326
|
+
const { scrollOffset: currentScrollOffset, maxHeight: currentMaxHeight, totalItems } = scrollStateRef.current;
|
|
327
|
+
const currentMaxScrollOffset = Math.max(0, totalItems - currentMaxHeight);
|
|
328
|
+
const newScrollOffset = Math.max(0, currentScrollOffset - 1);
|
|
329
|
+
setScrollOffset(newScrollOffset);
|
|
330
|
+
forceUpdate({});
|
|
331
|
+
});
|
|
332
|
+
ctx.setAction("scrollDown", () => {
|
|
333
|
+
const { scrollOffset: currentScrollOffset, maxHeight: currentMaxHeight, totalItems } = scrollStateRef.current;
|
|
334
|
+
const currentMaxScrollOffset = Math.max(0, totalItems - currentMaxHeight);
|
|
335
|
+
const newScrollOffset = Math.min(currentMaxScrollOffset, currentScrollOffset + 1);
|
|
336
|
+
setScrollOffset(newScrollOffset);
|
|
337
|
+
forceUpdate({});
|
|
338
|
+
});
|
|
339
|
+
if (sortable) {
|
|
340
|
+
ctx.setAction("toggleSort", () => {
|
|
341
|
+
const nextSort = sortOrder === "none" ? "asc" : sortOrder === "asc" ? "desc" : "none";
|
|
342
|
+
const currentSelectedItem = displayItems[selectedIndexRef.current];
|
|
343
|
+
setSortOrder(nextSort);
|
|
344
|
+
const newSortedItems = nextSort !== "none" ? [...items].sort((a, b) => {
|
|
345
|
+
const titleA = titleGetter(a).toLowerCase();
|
|
346
|
+
const titleB = titleGetter(b).toLowerCase();
|
|
347
|
+
if (nextSort === "asc") {
|
|
348
|
+
return titleA < titleB ? -1 : titleA > titleB ? 1 : 0;
|
|
349
|
+
} else {
|
|
350
|
+
return titleA > titleB ? -1 : titleA < titleB ? 1 : 0;
|
|
351
|
+
}
|
|
352
|
+
}) : items;
|
|
353
|
+
const newIndex = newSortedItems.findIndex((item) => item === currentSelectedItem);
|
|
354
|
+
if (newIndex !== -1) {
|
|
355
|
+
selectedIndexRef.current = newIndex;
|
|
356
|
+
setScrollOffset(newIndex);
|
|
357
|
+
} else {
|
|
358
|
+
selectedIndexRef.current = 0;
|
|
359
|
+
setScrollOffset(0);
|
|
360
|
+
}
|
|
361
|
+
forceUpdate({});
|
|
362
|
+
});
|
|
363
|
+
const defaultHighlightStyle = {
|
|
364
|
+
color: "black",
|
|
365
|
+
backgroundColor: "green",
|
|
366
|
+
bold: true
|
|
367
|
+
};
|
|
368
|
+
const highlightStyle = { ...defaultHighlightStyle, ...sortHighlightStyle };
|
|
369
|
+
const sortCaption = () => {
|
|
370
|
+
if (sortOrder === "none") {
|
|
371
|
+
return h2(import_ink2.Text, {}, "s to toggle sort");
|
|
372
|
+
} else {
|
|
373
|
+
const sortLabel = sortOrder === "asc" ? "ASC" : "DESC";
|
|
374
|
+
return h2(
|
|
375
|
+
import_ink2.Text,
|
|
376
|
+
{},
|
|
377
|
+
"s to toggle ",
|
|
378
|
+
h2(import_ink2.Text, { color: "white", bold: true }, "sort"),
|
|
379
|
+
" ",
|
|
380
|
+
h2(import_ink2.Text, highlightStyle, ` ${sortLabel} `)
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
ctx.setKeyBinding([
|
|
385
|
+
{ key: "upArrow", caption: "navigate", action: "moveUp", order: 0 },
|
|
386
|
+
{ key: "downArrow", caption: "navigate", action: "moveDown", order: 0 },
|
|
387
|
+
{
|
|
388
|
+
key: "s",
|
|
389
|
+
caption: sortCaption,
|
|
390
|
+
action: "toggleSort",
|
|
391
|
+
order: 5
|
|
392
|
+
}
|
|
393
|
+
]);
|
|
394
|
+
ctx.update();
|
|
395
|
+
} else {
|
|
396
|
+
ctx.setKeyBinding([
|
|
397
|
+
{ key: "upArrow", caption: "navigate", action: "moveUp", order: 0 },
|
|
398
|
+
{ key: "downArrow", caption: "navigate", action: "moveDown", order: 0 }
|
|
399
|
+
]);
|
|
400
|
+
}
|
|
401
|
+
}, [sortOrder, sortable]);
|
|
402
|
+
const selectedIndex = selectedIndexRef.current;
|
|
403
|
+
const defaultRenderItem = (item, isSelected, displayIndex, actualIndex) => {
|
|
404
|
+
const isFirstVisible = displayIndex === 0;
|
|
405
|
+
const isLastVisible = displayIndex === visibleItems.length - 1;
|
|
406
|
+
let arrowPrefix = "";
|
|
407
|
+
let selectionPrefix = "";
|
|
408
|
+
if (isFirstVisible && canScrollUp) {
|
|
409
|
+
arrowPrefix = "\u2191 ";
|
|
410
|
+
} else if (isLastVisible && canScrollDown) {
|
|
411
|
+
arrowPrefix = "\u2193 ";
|
|
412
|
+
} else {
|
|
413
|
+
arrowPrefix = " ";
|
|
414
|
+
}
|
|
415
|
+
if (isSelected) {
|
|
416
|
+
selectionPrefix = selectionMarker;
|
|
417
|
+
} else {
|
|
418
|
+
selectionPrefix = " ".repeat(selectionMarker.length);
|
|
419
|
+
}
|
|
420
|
+
return h2(
|
|
421
|
+
import_ink2.Box,
|
|
422
|
+
{ flexDirection: "row" },
|
|
423
|
+
// Arrow (clickable if functional, not highlighted)
|
|
424
|
+
h2(import_ink2.Text, {
|
|
425
|
+
key: `arrow-${actualIndex}`,
|
|
426
|
+
color: "white"
|
|
427
|
+
}, arrowPrefix),
|
|
428
|
+
// Selection marker space (always same width, not highlighted)
|
|
429
|
+
h2(import_ink2.Text, { key: `marker-${actualIndex}`, color: "white" }, selectionPrefix),
|
|
430
|
+
// Item name (highlighted if selected)
|
|
431
|
+
h2(import_ink2.Text, {
|
|
432
|
+
key: `name-${actualIndex}`,
|
|
433
|
+
color: isSelected ? "black" : "white",
|
|
434
|
+
backgroundColor: isSelected ? "cyan" : void 0,
|
|
435
|
+
bold: isSelected
|
|
436
|
+
}, item.name)
|
|
437
|
+
);
|
|
438
|
+
};
|
|
439
|
+
const itemRenderer = renderItem || defaultRenderItem;
|
|
440
|
+
return h2(
|
|
441
|
+
import_ink2.Box,
|
|
442
|
+
{ flexDirection: "column" },
|
|
443
|
+
...visibleItems.map((item, displayIndex) => {
|
|
444
|
+
const actualIndex = clampedScrollOffset + displayIndex;
|
|
445
|
+
const isSelected = actualIndex === selectedIndex;
|
|
446
|
+
if (renderItem) {
|
|
447
|
+
const isFirstVisible = displayIndex === 0;
|
|
448
|
+
const isLastVisible = displayIndex === visibleItems.length - 1;
|
|
449
|
+
let arrowPrefix = "";
|
|
450
|
+
let selectionPrefix = "";
|
|
451
|
+
if (isFirstVisible && canScrollUp) {
|
|
452
|
+
arrowPrefix = "\u2191 ";
|
|
453
|
+
} else if (isLastVisible && canScrollDown) {
|
|
454
|
+
arrowPrefix = "\u2193 ";
|
|
455
|
+
} else {
|
|
456
|
+
arrowPrefix = " ";
|
|
457
|
+
}
|
|
458
|
+
if (isSelected) {
|
|
459
|
+
selectionPrefix = selectionMarker;
|
|
460
|
+
} else {
|
|
461
|
+
selectionPrefix = " ".repeat(selectionMarker.length);
|
|
462
|
+
}
|
|
463
|
+
return h2(ScreenRow, {
|
|
464
|
+
key: `item-${actualIndex}`,
|
|
465
|
+
children: h2(
|
|
466
|
+
import_ink2.Box,
|
|
467
|
+
{ flexDirection: "row" },
|
|
468
|
+
// Arrow (clickable if functional, not highlighted)
|
|
469
|
+
h2(import_ink2.Text, {
|
|
470
|
+
key: `arrow-${actualIndex}`,
|
|
471
|
+
color: "white"
|
|
472
|
+
}, arrowPrefix),
|
|
473
|
+
// Selection marker space (always same width, not highlighted)
|
|
474
|
+
h2(import_ink2.Text, { key: `marker-${actualIndex}`, color: "white" }, selectionPrefix),
|
|
475
|
+
// Custom rendered content
|
|
476
|
+
renderItem(item, isSelected, displayIndex)
|
|
477
|
+
)
|
|
478
|
+
});
|
|
479
|
+
} else {
|
|
480
|
+
return h2(ScreenRow, {
|
|
481
|
+
key: `item-${actualIndex}`,
|
|
482
|
+
children: itemRenderer(item, isSelected, displayIndex, actualIndex)
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
})
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
var import_react2, import_ink2, h2;
|
|
489
|
+
var init_list_components = __esm({
|
|
490
|
+
"src/screen/list-components.ts"() {
|
|
491
|
+
"use strict";
|
|
492
|
+
import_react2 = __toESM(require("react"), 1);
|
|
493
|
+
import_ink2 = require("ink");
|
|
494
|
+
init_components();
|
|
495
|
+
h2 = import_react2.createElement;
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
// src/screen/screens.ts
|
|
500
|
+
function groupKeyBindings(bindings) {
|
|
501
|
+
const groups = {};
|
|
502
|
+
const enabledBindings = bindings.filter((b) => b.enabled !== false);
|
|
503
|
+
enabledBindings.forEach((binding) => {
|
|
504
|
+
const caption = typeof binding.caption === "string" ? binding.caption : "";
|
|
505
|
+
if (!groups[caption]) {
|
|
506
|
+
groups[caption] = {
|
|
507
|
+
keys: [],
|
|
508
|
+
caption,
|
|
509
|
+
order: binding.order || 999
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
groups[caption].keys.push(binding.key);
|
|
513
|
+
});
|
|
514
|
+
return Object.values(groups);
|
|
515
|
+
}
|
|
516
|
+
function formatKeyBindings(bindings, mode = "long") {
|
|
517
|
+
const resolvedBindings = bindings.map((binding) => {
|
|
518
|
+
let resolvedCaption = binding.caption;
|
|
519
|
+
if (typeof binding.caption === "function") {
|
|
520
|
+
resolvedCaption = binding.caption();
|
|
521
|
+
}
|
|
522
|
+
return {
|
|
523
|
+
...binding,
|
|
524
|
+
resolvedCaption
|
|
525
|
+
};
|
|
526
|
+
});
|
|
527
|
+
const groups = groupKeyBindings(resolvedBindings.map((b) => ({
|
|
528
|
+
...b,
|
|
529
|
+
caption: typeof b.resolvedCaption === "string" ? b.resolvedCaption : ""
|
|
530
|
+
})));
|
|
531
|
+
groups.sort((a, b) => a.order - b.order);
|
|
532
|
+
const items = [];
|
|
533
|
+
groups.forEach((group) => {
|
|
534
|
+
const bindingWithCustom = resolvedBindings.find(
|
|
535
|
+
(b) => group.keys.includes(b.key) && typeof b.resolvedCaption !== "string"
|
|
536
|
+
);
|
|
537
|
+
if (bindingWithCustom && bindingWithCustom.resolvedCaption) {
|
|
538
|
+
items.push(bindingWithCustom.resolvedCaption);
|
|
539
|
+
} else {
|
|
540
|
+
const keyStr = formatKeys(group.keys);
|
|
541
|
+
if (mode === "long") {
|
|
542
|
+
items.push(`${keyStr} to ${group.caption}`);
|
|
543
|
+
} else {
|
|
544
|
+
items.push(keyStr);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
return items;
|
|
549
|
+
}
|
|
550
|
+
function formatKeys(keys) {
|
|
551
|
+
const keyMap = {
|
|
552
|
+
"escape": "esc",
|
|
553
|
+
"leftArrow": "\u2190",
|
|
554
|
+
"rightArrow": "\u2192",
|
|
555
|
+
"upArrow": "\u2191",
|
|
556
|
+
"downArrow": "\u2193",
|
|
557
|
+
"return": "enter"
|
|
558
|
+
};
|
|
559
|
+
return keys.map((k) => keyMap[k] || k).join("/");
|
|
560
|
+
}
|
|
561
|
+
async function showScreen(config2) {
|
|
562
|
+
const {
|
|
563
|
+
title,
|
|
564
|
+
onRender,
|
|
565
|
+
parentData = {}
|
|
566
|
+
} = config2;
|
|
567
|
+
return new Promise((resolve2) => {
|
|
568
|
+
let instance;
|
|
569
|
+
const keyBindings = [];
|
|
570
|
+
const actions = {};
|
|
571
|
+
const customFooterItems = [];
|
|
572
|
+
let renderResult = null;
|
|
573
|
+
let initialized = false;
|
|
574
|
+
const Screen = () => {
|
|
575
|
+
const [updateCounter, setUpdateCounter] = (0, import_react3.useState)(0);
|
|
576
|
+
if (!initialized) {
|
|
577
|
+
const defaultBindings = [
|
|
578
|
+
{ key: "escape", caption: "go back", action: "back", protected: true, order: 1 },
|
|
579
|
+
{ key: "leftArrow", caption: "go back", action: "back", protected: false, order: 1 }
|
|
580
|
+
// Note: 'select' is not a default - components add it if needed
|
|
581
|
+
];
|
|
582
|
+
defaultBindings.forEach((binding) => {
|
|
583
|
+
keyBindings.push(binding);
|
|
584
|
+
});
|
|
585
|
+
actions.back = () => {
|
|
586
|
+
cleanup(null);
|
|
587
|
+
};
|
|
588
|
+
initialized = true;
|
|
589
|
+
}
|
|
590
|
+
const context = {
|
|
591
|
+
setAction: (actionName, handlerFn) => {
|
|
592
|
+
actions[actionName] = handlerFn;
|
|
593
|
+
},
|
|
594
|
+
setKeyBinding: (bindingOrBindings) => {
|
|
595
|
+
const bindingsToSet = Array.isArray(bindingOrBindings) ? bindingOrBindings : [bindingOrBindings];
|
|
596
|
+
bindingsToSet.forEach((binding) => {
|
|
597
|
+
const existingIndex = keyBindings.findIndex((b) => b.key === binding.key);
|
|
598
|
+
if (existingIndex >= 0) {
|
|
599
|
+
const existing = keyBindings[existingIndex];
|
|
600
|
+
if (existing.protected) {
|
|
601
|
+
console.warn(`Cannot override protected key: ${binding.key}`);
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
keyBindings[existingIndex] = {
|
|
605
|
+
...existing,
|
|
606
|
+
...binding,
|
|
607
|
+
order: binding.order !== void 0 ? binding.order : existing.order,
|
|
608
|
+
enabled: binding.enabled !== void 0 ? binding.enabled : existing.enabled !== void 0 ? existing.enabled : true
|
|
609
|
+
};
|
|
610
|
+
} else {
|
|
611
|
+
keyBindings.push({
|
|
612
|
+
protected: false,
|
|
613
|
+
order: 999,
|
|
614
|
+
enabled: true,
|
|
615
|
+
...binding
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
});
|
|
619
|
+
},
|
|
620
|
+
updateKeyBinding: (keyName, updates) => {
|
|
621
|
+
const index = keyBindings.findIndex((b) => b.key === keyName);
|
|
622
|
+
if (index >= 0) {
|
|
623
|
+
keyBindings[index] = {
|
|
624
|
+
...keyBindings[index],
|
|
625
|
+
...updates
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
},
|
|
629
|
+
removeKeyBinding: (keyName) => {
|
|
630
|
+
const index = keyBindings.findIndex((b) => b.key === keyName);
|
|
631
|
+
if (index >= 0) {
|
|
632
|
+
if (keyBindings[index].protected) {
|
|
633
|
+
console.warn(`Cannot remove protected key: ${keyName}`);
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
keyBindings.splice(index, 1);
|
|
637
|
+
}
|
|
638
|
+
},
|
|
639
|
+
addFooter: (item) => {
|
|
640
|
+
customFooterItems.push(item);
|
|
641
|
+
},
|
|
642
|
+
clearFooter: () => {
|
|
643
|
+
customFooterItems.length = 0;
|
|
644
|
+
},
|
|
645
|
+
setFooter: (items) => {
|
|
646
|
+
customFooterItems.length = 0;
|
|
647
|
+
const itemsArray = Array.isArray(items) ? items : [items];
|
|
648
|
+
customFooterItems.push(...itemsArray);
|
|
649
|
+
},
|
|
650
|
+
update: () => {
|
|
651
|
+
setUpdateCounter((c) => c + 1);
|
|
652
|
+
},
|
|
653
|
+
goBack: () => {
|
|
654
|
+
if (actions.back) {
|
|
655
|
+
actions.back();
|
|
656
|
+
}
|
|
657
|
+
},
|
|
658
|
+
close: (result) => {
|
|
659
|
+
cleanup(result);
|
|
660
|
+
},
|
|
661
|
+
parentData
|
|
662
|
+
};
|
|
663
|
+
if (!renderResult) {
|
|
664
|
+
renderResult = onRender(context);
|
|
665
|
+
}
|
|
666
|
+
(0, import_ink3.useInput)((input, key) => {
|
|
667
|
+
if (key.ctrl && input === "c") {
|
|
668
|
+
cleanup(null);
|
|
669
|
+
process.exit(0);
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
let matchedBinding = null;
|
|
673
|
+
for (const binding of keyBindings) {
|
|
674
|
+
let keyMatches = false;
|
|
675
|
+
if (key[binding.key]) {
|
|
676
|
+
keyMatches = true;
|
|
677
|
+
} else if (input === binding.key) {
|
|
678
|
+
keyMatches = true;
|
|
679
|
+
}
|
|
680
|
+
if (keyMatches) {
|
|
681
|
+
if (binding.enabled === false) {
|
|
682
|
+
continue;
|
|
683
|
+
}
|
|
684
|
+
if (binding.condition && !binding.condition(context)) {
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
687
|
+
matchedBinding = binding;
|
|
688
|
+
break;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
if (matchedBinding && actions[matchedBinding.action]) {
|
|
692
|
+
const actionResult = actions[matchedBinding.action]({
|
|
693
|
+
input,
|
|
694
|
+
key,
|
|
695
|
+
binding: matchedBinding
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
});
|
|
699
|
+
const footerLines = [];
|
|
700
|
+
const bindingItems = formatKeyBindings(keyBindings, "long");
|
|
701
|
+
if (bindingItems.length > 0) {
|
|
702
|
+
const bindingsLine = [];
|
|
703
|
+
bindingItems.forEach((item, idx) => {
|
|
704
|
+
if (idx > 0) {
|
|
705
|
+
bindingsLine.push(", ");
|
|
706
|
+
}
|
|
707
|
+
bindingsLine.push(item);
|
|
708
|
+
});
|
|
709
|
+
const allStrings = bindingItems.every((item) => typeof item === "string");
|
|
710
|
+
if (allStrings) {
|
|
711
|
+
footerLines.push(bindingsLine.join(""));
|
|
712
|
+
} else {
|
|
713
|
+
const wrappedBindingsLine = bindingsLine.map(
|
|
714
|
+
(item) => typeof item === "string" ? (0, import_react3.createElement)(import_ink3.Text, {}, item) : item
|
|
715
|
+
);
|
|
716
|
+
footerLines.push(wrappedBindingsLine);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
customFooterItems.forEach((item) => {
|
|
720
|
+
if (typeof item === "string") {
|
|
721
|
+
footerLines.push(item);
|
|
722
|
+
} else {
|
|
723
|
+
footerLines.push(item);
|
|
724
|
+
}
|
|
725
|
+
});
|
|
726
|
+
return (0, import_react3.createElement)(
|
|
727
|
+
ScreenContainer,
|
|
728
|
+
{},
|
|
729
|
+
(0, import_react3.createElement)(ScreenTitle, { text: title }),
|
|
730
|
+
(0, import_react3.createElement)(ScreenDivider),
|
|
731
|
+
(0, import_react3.createElement)(ScreenRow, {}, (0, import_react3.createElement)(import_ink3.Text, {}, " ")),
|
|
732
|
+
renderResult,
|
|
733
|
+
(0, import_react3.createElement)(ScreenRow, {}, (0, import_react3.createElement)(import_ink3.Text, {}, " ")),
|
|
734
|
+
(0, import_react3.createElement)(ScreenDivider),
|
|
735
|
+
(0, import_react3.createElement)(ScreenFooter, { lines: footerLines })
|
|
736
|
+
);
|
|
737
|
+
};
|
|
738
|
+
const cleanup = (result) => {
|
|
739
|
+
if (instance) instance.unmount();
|
|
740
|
+
setTimeout(() => resolve2(result), 50);
|
|
741
|
+
};
|
|
742
|
+
instance = (0, import_ink3.render)((0, import_react3.createElement)(Screen));
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
async function showListScreen(config2) {
|
|
746
|
+
const { title, items, onSelect, onEscape, parentData, initialSelectedIndex = 0, renderItem, getTitle, sortable, maxHeight, sortHighlightStyle, selectionMarker } = config2;
|
|
747
|
+
return showScreen({
|
|
748
|
+
title,
|
|
749
|
+
parentData,
|
|
750
|
+
onRender: (ctx) => {
|
|
751
|
+
const selectedIndexRef = { current: initialSelectedIndex };
|
|
752
|
+
ctx.setAction("select", () => {
|
|
753
|
+
const selected = items[selectedIndexRef.current];
|
|
754
|
+
if (onSelect) {
|
|
755
|
+
const result = onSelect(selected.value, selectedIndexRef.current);
|
|
756
|
+
ctx.close(result);
|
|
757
|
+
}
|
|
758
|
+
});
|
|
759
|
+
if (onEscape) {
|
|
760
|
+
ctx.setAction("back", () => {
|
|
761
|
+
const result = onEscape(selectedIndexRef.current);
|
|
762
|
+
ctx.close(result);
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
ctx.setKeyBinding({ key: "return", caption: "select", action: "select", order: 2 });
|
|
766
|
+
return (0, import_react3.createElement)(ListComponent, { items, ctx, selectedIndexRef, renderItem, getTitle, sortable, maxHeight, sortHighlightStyle, selectionMarker });
|
|
767
|
+
}
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
async function showMultiColumnListScreen(config2) {
|
|
771
|
+
const { title, items, onSelect, onEscape, parentData, initialSelectedIndex = 0 } = config2;
|
|
772
|
+
return showScreen({
|
|
773
|
+
title,
|
|
774
|
+
parentData,
|
|
775
|
+
onRender: (ctx) => {
|
|
776
|
+
const selectedIndexRef = { current: initialSelectedIndex };
|
|
777
|
+
ctx.setAction("select", () => {
|
|
778
|
+
const selected = items[selectedIndexRef.current];
|
|
779
|
+
if (onSelect) {
|
|
780
|
+
const result = onSelect(selected, selectedIndexRef.current);
|
|
781
|
+
ctx.close(result);
|
|
782
|
+
}
|
|
783
|
+
});
|
|
784
|
+
if (onEscape) {
|
|
785
|
+
ctx.setAction("back", () => {
|
|
786
|
+
const result = onEscape(selectedIndexRef.current);
|
|
787
|
+
ctx.close(result);
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
ctx.setKeyBinding({ key: "return", caption: "select", action: "select", order: 2 });
|
|
791
|
+
return (0, import_react3.createElement)(MultiColumnListComponent, { items, ctx, selectedIndexRef });
|
|
792
|
+
}
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
async function showMultiColumnListWithPreviewScreen(config2) {
|
|
796
|
+
const { title, items, getPreviewContent, onSelect, onEscape, parentData, initialSelectedIndex = 0 } = config2;
|
|
797
|
+
return showScreen({
|
|
798
|
+
title,
|
|
799
|
+
parentData,
|
|
800
|
+
onRender: (ctx) => {
|
|
801
|
+
const selectedIndexRef = { current: initialSelectedIndex };
|
|
802
|
+
ctx.setAction("select", () => {
|
|
803
|
+
const selected = items[selectedIndexRef.current];
|
|
804
|
+
if (onSelect) {
|
|
805
|
+
const result = onSelect(selected, selectedIndexRef.current);
|
|
806
|
+
ctx.close(result);
|
|
807
|
+
}
|
|
808
|
+
});
|
|
809
|
+
if (onEscape) {
|
|
810
|
+
ctx.setAction("back", () => {
|
|
811
|
+
const result = onEscape(selectedIndexRef.current);
|
|
812
|
+
ctx.close(result);
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
ctx.setKeyBinding({ key: "return", caption: "select", action: "select", order: 2 });
|
|
816
|
+
return (0, import_react3.createElement)(MultiColumnListWithPreviewComponent, { items, getPreviewContent, ctx, selectedIndexRef });
|
|
817
|
+
}
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
var import_react3, import_ink3, showMenuScreen, showWordGridScreen;
|
|
821
|
+
var init_screens = __esm({
|
|
822
|
+
"src/screen/screens.ts"() {
|
|
823
|
+
"use strict";
|
|
824
|
+
import_react3 = require("react");
|
|
825
|
+
import_ink3 = require("ink");
|
|
826
|
+
init_components();
|
|
827
|
+
init_list_components();
|
|
828
|
+
showMenuScreen = showListScreen;
|
|
829
|
+
showWordGridScreen = showMultiColumnListScreen;
|
|
830
|
+
}
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
// src/screen/ui-elements.ts
|
|
834
|
+
function ListItem({
|
|
835
|
+
children,
|
|
836
|
+
isSelected = false,
|
|
837
|
+
color = "white",
|
|
838
|
+
backgroundColor,
|
|
839
|
+
bold = false,
|
|
840
|
+
dimColor = false
|
|
841
|
+
}) {
|
|
842
|
+
return (0, import_react4.createElement)(
|
|
843
|
+
import_ink4.Box,
|
|
844
|
+
{},
|
|
845
|
+
(0, import_react4.createElement)(import_ink4.Text, {
|
|
846
|
+
color: isSelected ? backgroundColor || "green" : color,
|
|
847
|
+
backgroundColor: isSelected ? color : backgroundColor,
|
|
848
|
+
bold: isSelected || bold,
|
|
849
|
+
dimColor: !isSelected && dimColor
|
|
850
|
+
}, children)
|
|
851
|
+
);
|
|
852
|
+
}
|
|
853
|
+
function TextBlock({
|
|
854
|
+
text,
|
|
855
|
+
color = "white",
|
|
856
|
+
dimmed = false,
|
|
857
|
+
bold = false,
|
|
858
|
+
maxWidth
|
|
859
|
+
}) {
|
|
860
|
+
return (0, import_react4.createElement)(
|
|
861
|
+
import_ink4.Box,
|
|
862
|
+
{},
|
|
863
|
+
(0, import_react4.createElement)(import_ink4.Text, {
|
|
864
|
+
color,
|
|
865
|
+
dimColor: dimmed,
|
|
866
|
+
bold
|
|
867
|
+
}, text)
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
function Divider({ character = "\u2500", width = 80 }) {
|
|
871
|
+
return (0, import_react4.createElement)(
|
|
872
|
+
import_ink4.Box,
|
|
873
|
+
{ marginY: 1 },
|
|
874
|
+
(0, import_react4.createElement)(import_ink4.Text, { dimColor: true }, character.repeat(width))
|
|
875
|
+
);
|
|
876
|
+
}
|
|
877
|
+
function GridCell({
|
|
878
|
+
children,
|
|
879
|
+
width,
|
|
880
|
+
color = "white",
|
|
881
|
+
backgroundColor,
|
|
882
|
+
bold = false,
|
|
883
|
+
dimColor = false,
|
|
884
|
+
align = "left"
|
|
885
|
+
}) {
|
|
886
|
+
return (0, import_react4.createElement)(
|
|
887
|
+
import_ink4.Box,
|
|
888
|
+
{ width },
|
|
889
|
+
(0, import_react4.createElement)(import_ink4.Text, {
|
|
890
|
+
color,
|
|
891
|
+
backgroundColor,
|
|
892
|
+
bold,
|
|
893
|
+
dimColor,
|
|
894
|
+
textAlign: align
|
|
895
|
+
}, children)
|
|
896
|
+
);
|
|
897
|
+
}
|
|
898
|
+
function InputField({ prompt, value, onChange, onSubmit }) {
|
|
899
|
+
return (0, import_react4.createElement)(
|
|
900
|
+
import_ink4.Box,
|
|
901
|
+
{ flexDirection: "column" },
|
|
902
|
+
(0, import_react4.createElement)(import_ink4.Text, {}, prompt),
|
|
903
|
+
(0, import_react4.createElement)(
|
|
904
|
+
import_ink4.Box,
|
|
905
|
+
{ marginTop: 1 },
|
|
906
|
+
(0, import_react4.createElement)(import_ink4.Text, { color: "cyan" }, " > ", value, "_")
|
|
907
|
+
)
|
|
908
|
+
);
|
|
909
|
+
}
|
|
910
|
+
var import_react4, import_ink4;
|
|
911
|
+
var init_ui_elements = __esm({
|
|
912
|
+
"src/screen/ui-elements.ts"() {
|
|
913
|
+
"use strict";
|
|
914
|
+
import_react4 = require("react");
|
|
915
|
+
import_ink4 = require("ink");
|
|
916
|
+
}
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
// src/screen/utils.ts
|
|
920
|
+
function buildBreadcrumb(parts) {
|
|
921
|
+
if (parts.length === 0) return "";
|
|
922
|
+
if (parts.length === 1) return parts[0];
|
|
923
|
+
return parts.slice(1).map((part) => `\u2190 ${part}`).join(" ");
|
|
924
|
+
}
|
|
925
|
+
function buildDetailBreadcrumb(path, suffix = "") {
|
|
926
|
+
if (path.length <= 1) {
|
|
927
|
+
return suffix ? `\u2190 ${suffix}` : path[0] || "";
|
|
928
|
+
}
|
|
929
|
+
const breadcrumb = buildBreadcrumb(path);
|
|
930
|
+
return suffix ? `${breadcrumb} ${suffix}` : breadcrumb;
|
|
931
|
+
}
|
|
932
|
+
var init_utils = __esm({
|
|
933
|
+
"src/screen/utils.ts"() {
|
|
934
|
+
"use strict";
|
|
935
|
+
}
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
// src/screen/footer-builder.ts
|
|
939
|
+
function buildFooter(config2 = {}) {
|
|
940
|
+
const {
|
|
941
|
+
navigation = null,
|
|
942
|
+
actions = null,
|
|
943
|
+
info = null,
|
|
944
|
+
escape = "Esc to go back",
|
|
945
|
+
custom = null
|
|
946
|
+
} = config2;
|
|
947
|
+
const lines = [];
|
|
948
|
+
const mainParts = [];
|
|
949
|
+
if (navigation) {
|
|
950
|
+
mainParts.push(navigation);
|
|
951
|
+
}
|
|
952
|
+
if (actions) {
|
|
953
|
+
mainParts.push(actions);
|
|
954
|
+
}
|
|
955
|
+
if (escape) {
|
|
956
|
+
mainParts.push(escape);
|
|
957
|
+
}
|
|
958
|
+
if (mainParts.length > 0) {
|
|
959
|
+
lines.push(mainParts.join(", "));
|
|
960
|
+
}
|
|
961
|
+
if (info) {
|
|
962
|
+
const infoLines = Array.isArray(info) ? info : [info];
|
|
963
|
+
lines.push(...infoLines);
|
|
964
|
+
}
|
|
965
|
+
if (custom) {
|
|
966
|
+
const customLines = Array.isArray(custom) ? custom : [custom];
|
|
967
|
+
lines.push(...customLines);
|
|
968
|
+
}
|
|
969
|
+
return lines;
|
|
970
|
+
}
|
|
971
|
+
function organizeFooterMessages(messages) {
|
|
972
|
+
if (!messages || messages.length === 0) {
|
|
973
|
+
return ["Esc to go back"];
|
|
974
|
+
}
|
|
975
|
+
const navigation = messages.filter((m) => m.includes("\u2191") || m.includes("\u2193") || m.includes("\u2190") || m.includes("\u2192"));
|
|
976
|
+
const actions = messages.filter((m) => m.includes("Enter") || m.includes("select") || m.includes("submit"));
|
|
977
|
+
const escape = messages.filter((m) => m.includes("Esc"));
|
|
978
|
+
const others = messages.filter(
|
|
979
|
+
(m) => !navigation.includes(m) && !actions.includes(m) && !escape.includes(m)
|
|
980
|
+
);
|
|
981
|
+
const lines = [];
|
|
982
|
+
const mainLine = [...navigation, ...actions, ...escape].join(", ");
|
|
983
|
+
if (mainLine) lines.push(mainLine);
|
|
984
|
+
lines.push(...others);
|
|
985
|
+
return lines;
|
|
986
|
+
}
|
|
987
|
+
var FooterPresets;
|
|
988
|
+
var init_footer_builder = __esm({
|
|
989
|
+
"src/screen/footer-builder.ts"() {
|
|
990
|
+
"use strict";
|
|
991
|
+
FooterPresets = {
|
|
992
|
+
/**
|
|
993
|
+
* Menu screen footer
|
|
994
|
+
*/
|
|
995
|
+
menu: (customInfo = null) => buildFooter({
|
|
996
|
+
navigation: "\u2191/\u2193 to navigate",
|
|
997
|
+
actions: "Enter to select",
|
|
998
|
+
escape: "Esc to go back",
|
|
999
|
+
info: customInfo
|
|
1000
|
+
}),
|
|
1001
|
+
/**
|
|
1002
|
+
* Word grid footer
|
|
1003
|
+
*/
|
|
1004
|
+
wordGrid: (totalWords) => buildFooter({
|
|
1005
|
+
navigation: "\u2191\u2193\u2190\u2192 to navigate",
|
|
1006
|
+
actions: "Enter to select",
|
|
1007
|
+
escape: "Esc to go back",
|
|
1008
|
+
info: `Total: ${totalWords} words`
|
|
1009
|
+
}),
|
|
1010
|
+
/**
|
|
1011
|
+
* Text input footer
|
|
1012
|
+
*/
|
|
1013
|
+
textInput: () => buildFooter({
|
|
1014
|
+
actions: "Type and press Enter to submit",
|
|
1015
|
+
escape: "Esc to cancel"
|
|
1016
|
+
}),
|
|
1017
|
+
/**
|
|
1018
|
+
* Info/static screen footer
|
|
1019
|
+
*/
|
|
1020
|
+
info: () => buildFooter({
|
|
1021
|
+
escape: "Esc to continue"
|
|
1022
|
+
}),
|
|
1023
|
+
/**
|
|
1024
|
+
* Main menu footer (escape exits)
|
|
1025
|
+
*/
|
|
1026
|
+
mainMenu: () => buildFooter({
|
|
1027
|
+
navigation: "\u2191/\u2193 to navigate",
|
|
1028
|
+
actions: "Enter to select",
|
|
1029
|
+
escape: "Esc to exit"
|
|
1030
|
+
}),
|
|
1031
|
+
/**
|
|
1032
|
+
* Action menu footer (for word cards, etc.)
|
|
1033
|
+
*/
|
|
1034
|
+
actionMenu: (hasAudio = false) => {
|
|
1035
|
+
const parts = buildFooter({
|
|
1036
|
+
navigation: "\u2191/\u2193 to navigate",
|
|
1037
|
+
actions: "Enter to select",
|
|
1038
|
+
escape: "Esc to go back"
|
|
1039
|
+
});
|
|
1040
|
+
if (hasAudio) {
|
|
1041
|
+
parts.push("Audio available");
|
|
1042
|
+
}
|
|
1043
|
+
return parts;
|
|
1044
|
+
}
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
});
|
|
1048
|
+
|
|
1049
|
+
// src/screen/index.ts
|
|
1050
|
+
var screen_exports = {};
|
|
1051
|
+
__export(screen_exports, {
|
|
1052
|
+
Box: () => import_ink5.Box,
|
|
1053
|
+
Divider: () => Divider,
|
|
1054
|
+
FooterPresets: () => FooterPresets,
|
|
1055
|
+
GridCell: () => GridCell,
|
|
1056
|
+
InputField: () => InputField,
|
|
1057
|
+
ListComponent: () => ListComponent,
|
|
1058
|
+
ListItem: () => ListItem,
|
|
1059
|
+
MultiColumnListComponent: () => MultiColumnListComponent,
|
|
1060
|
+
MultiColumnListWithPreviewComponent: () => MultiColumnListWithPreviewComponent,
|
|
1061
|
+
React: () => import_react5.default,
|
|
1062
|
+
ScreenBody: () => ScreenBody,
|
|
1063
|
+
ScreenContainer: () => ScreenContainer,
|
|
1064
|
+
ScreenDivider: () => ScreenDivider,
|
|
1065
|
+
ScreenFooter: () => ScreenFooter,
|
|
1066
|
+
ScreenRow: () => ScreenRow,
|
|
1067
|
+
ScreenTitle: () => ScreenTitle,
|
|
1068
|
+
Text: () => import_ink5.Text,
|
|
1069
|
+
TextBlock: () => TextBlock,
|
|
1070
|
+
buildBreadcrumb: () => buildBreadcrumb,
|
|
1071
|
+
buildDetailBreadcrumb: () => buildDetailBreadcrumb,
|
|
1072
|
+
buildFooter: () => buildFooter,
|
|
1073
|
+
h: () => import_react5.createElement,
|
|
1074
|
+
load: () => load,
|
|
1075
|
+
organizeFooterMessages: () => organizeFooterMessages,
|
|
1076
|
+
showListScreen: () => showListScreen,
|
|
1077
|
+
showMenuScreen: () => showMenuScreen,
|
|
1078
|
+
showMultiColumnListScreen: () => showMultiColumnListScreen,
|
|
1079
|
+
showMultiColumnListWithPreviewScreen: () => showMultiColumnListWithPreviewScreen,
|
|
1080
|
+
showScreen: () => showScreen,
|
|
1081
|
+
showWordGridScreen: () => showWordGridScreen,
|
|
1082
|
+
useCallback: () => import_react5.useCallback,
|
|
1083
|
+
useEffect: () => import_react5.useEffect,
|
|
1084
|
+
useInput: () => import_ink5.useInput,
|
|
1085
|
+
useMemo: () => import_react5.useMemo,
|
|
1086
|
+
useRef: () => import_react5.useRef,
|
|
1087
|
+
useState: () => import_react5.useState
|
|
1088
|
+
});
|
|
1089
|
+
async function load() {
|
|
1090
|
+
if (loadPromise) return loadPromise;
|
|
1091
|
+
loadPromise = Promise.all([
|
|
1092
|
+
import("react"),
|
|
1093
|
+
import("ink")
|
|
1094
|
+
]).then(() => {
|
|
1095
|
+
});
|
|
1096
|
+
return loadPromise;
|
|
1097
|
+
}
|
|
1098
|
+
var import_react5, import_ink5, loadPromise;
|
|
1099
|
+
var init_screen = __esm({
|
|
1100
|
+
"src/screen/index.ts"() {
|
|
1101
|
+
"use strict";
|
|
1102
|
+
import_react5 = __toESM(require("react"), 1);
|
|
1103
|
+
import_ink5 = require("ink");
|
|
1104
|
+
init_screens();
|
|
1105
|
+
init_list_components();
|
|
1106
|
+
init_components();
|
|
1107
|
+
init_ui_elements();
|
|
1108
|
+
init_utils();
|
|
1109
|
+
init_footer_builder();
|
|
1110
|
+
loadPromise = null;
|
|
1111
|
+
if (typeof window === "undefined") {
|
|
1112
|
+
load().catch(() => {
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
});
|
|
1117
|
+
|
|
30
1118
|
// src/init.ts
|
|
31
1119
|
var init_exports = {};
|
|
32
1120
|
__export(init_exports, {
|
|
@@ -39,7 +1127,7 @@ module.exports = __toCommonJS(init_exports);
|
|
|
39
1127
|
var import_fs = require("fs");
|
|
40
1128
|
var import_path = require("path");
|
|
41
1129
|
var import_dotenv = require("dotenv");
|
|
42
|
-
var Args = class {
|
|
1130
|
+
var Args = class _Args {
|
|
43
1131
|
args = {};
|
|
44
1132
|
flags = {};
|
|
45
1133
|
options = {};
|
|
@@ -54,10 +1142,13 @@ var Args = class {
|
|
|
54
1142
|
configsLoaded = [];
|
|
55
1143
|
env = "local";
|
|
56
1144
|
constructor(config2 = {}) {
|
|
57
|
-
this.aliases =
|
|
58
|
-
this.overrides =
|
|
59
|
-
this.defaults =
|
|
60
|
-
this.prefixes =
|
|
1145
|
+
this.aliases = {};
|
|
1146
|
+
this.overrides = {};
|
|
1147
|
+
this.defaults = {};
|
|
1148
|
+
this.prefixes = ["not", "no"];
|
|
1149
|
+
if (Object.keys(config2).length > 0) {
|
|
1150
|
+
this.configure(config2);
|
|
1151
|
+
}
|
|
61
1152
|
const args = config2.args || process.argv.slice(2);
|
|
62
1153
|
this.parseArgs(args);
|
|
63
1154
|
this.env = this.get("env")?.toLowerCase() || "local";
|
|
@@ -65,6 +1156,33 @@ var Args = class {
|
|
|
65
1156
|
this.loadConfigFiles();
|
|
66
1157
|
this.checkConflicts();
|
|
67
1158
|
}
|
|
1159
|
+
/**
|
|
1160
|
+
* Configure Args options
|
|
1161
|
+
* Only parameters present in config are updated
|
|
1162
|
+
* Note: Args is special - it's initialized first, so it can't take context
|
|
1163
|
+
*/
|
|
1164
|
+
configure(config2) {
|
|
1165
|
+
if (config2.aliases !== void 0) {
|
|
1166
|
+
this.aliases = config2.aliases;
|
|
1167
|
+
}
|
|
1168
|
+
if (config2.overrides !== void 0) {
|
|
1169
|
+
this.overrides = config2.overrides;
|
|
1170
|
+
}
|
|
1171
|
+
if (config2.defaults !== void 0) {
|
|
1172
|
+
this.defaults = config2.defaults;
|
|
1173
|
+
}
|
|
1174
|
+
if (config2.prefixes !== void 0) {
|
|
1175
|
+
this.prefixes = config2.prefixes;
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
/**
|
|
1179
|
+
* Initialize Args instance
|
|
1180
|
+
* Note: Args is special - it's initialized first, so it can't take context
|
|
1181
|
+
* This static method is for consistency with other components
|
|
1182
|
+
*/
|
|
1183
|
+
static init(config2 = {}) {
|
|
1184
|
+
return new _Args(config2);
|
|
1185
|
+
}
|
|
68
1186
|
/**
|
|
69
1187
|
* Parse command line arguments
|
|
70
1188
|
*/
|
|
@@ -580,18 +1698,76 @@ var joiStringArrayType = (type) => (value, helpers) => {
|
|
|
580
1698
|
};
|
|
581
1699
|
|
|
582
1700
|
// src/params/index.ts
|
|
583
|
-
var Params = class {
|
|
1701
|
+
var Params = class _Params {
|
|
1702
|
+
context;
|
|
1703
|
+
// Partial context during initialization
|
|
584
1704
|
params = {};
|
|
585
1705
|
definitions = {};
|
|
586
1706
|
args;
|
|
587
1707
|
paramSetters = [];
|
|
588
1708
|
paramGetters = [];
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
1709
|
+
trackedParams = [];
|
|
1710
|
+
constructor(context, options = {}) {
|
|
1711
|
+
this.context = context;
|
|
1712
|
+
this.args = context.args;
|
|
1713
|
+
if (Object.keys(options).length > 0) {
|
|
1714
|
+
this.configure(options);
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
/**
|
|
1718
|
+
* Configure parameters
|
|
1719
|
+
* Only parameters present in options are updated
|
|
1720
|
+
*/
|
|
1721
|
+
configure(options) {
|
|
1722
|
+
for (const [k, v] of Object.entries(options)) {
|
|
592
1723
|
this.params[k] = v;
|
|
593
1724
|
}
|
|
594
1725
|
}
|
|
1726
|
+
/**
|
|
1727
|
+
* Initialize Params from context and CLI parameters
|
|
1728
|
+
* Note: Params is special - it's initialized early with partial context
|
|
1729
|
+
*/
|
|
1730
|
+
static init(context, options) {
|
|
1731
|
+
return new _Params(context, options || {});
|
|
1732
|
+
}
|
|
1733
|
+
/**
|
|
1734
|
+
* Track a parameter request for --stopAfter=init feature
|
|
1735
|
+
*/
|
|
1736
|
+
trackParam(key, definition, value, source) {
|
|
1737
|
+
this.trackedParams.push({
|
|
1738
|
+
key,
|
|
1739
|
+
definition,
|
|
1740
|
+
value,
|
|
1741
|
+
source
|
|
1742
|
+
});
|
|
1743
|
+
}
|
|
1744
|
+
/**
|
|
1745
|
+
* Get all tracked parameters (for --stopAfter=init)
|
|
1746
|
+
*/
|
|
1747
|
+
getTrackedParams() {
|
|
1748
|
+
return [...this.trackedParams];
|
|
1749
|
+
}
|
|
1750
|
+
/**
|
|
1751
|
+
* Get all figured parameters as a record
|
|
1752
|
+
* Returns all parameters that were collected during initialization,
|
|
1753
|
+
* whether from CLI args, options, or defaults
|
|
1754
|
+
*/
|
|
1755
|
+
getAllFigured() {
|
|
1756
|
+
const result = {};
|
|
1757
|
+
for (const param of this.trackedParams) {
|
|
1758
|
+
result[param.key] = {
|
|
1759
|
+
value: param.value,
|
|
1760
|
+
source: param.source
|
|
1761
|
+
};
|
|
1762
|
+
}
|
|
1763
|
+
return result;
|
|
1764
|
+
}
|
|
1765
|
+
/**
|
|
1766
|
+
* Clear tracked parameters
|
|
1767
|
+
*/
|
|
1768
|
+
clearTrackedParams() {
|
|
1769
|
+
this.trackedParams = [];
|
|
1770
|
+
}
|
|
595
1771
|
/**
|
|
596
1772
|
* Assign a parameter definition
|
|
597
1773
|
*/
|
|
@@ -665,6 +1841,8 @@ var Params = class {
|
|
|
665
1841
|
type = type.default(defValObj.value);
|
|
666
1842
|
} else if (str.match(/required/)) {
|
|
667
1843
|
type = type.required();
|
|
1844
|
+
} else {
|
|
1845
|
+
type = type.optional();
|
|
668
1846
|
}
|
|
669
1847
|
return type;
|
|
670
1848
|
}
|
|
@@ -672,7 +1850,12 @@ var Params = class {
|
|
|
672
1850
|
* Validate a value against a definition
|
|
673
1851
|
*/
|
|
674
1852
|
validate(key, val, def) {
|
|
675
|
-
const
|
|
1853
|
+
const normalizedVal = val === null ? void 0 : val;
|
|
1854
|
+
const { value, error } = def.type.validate(normalizedVal, {
|
|
1855
|
+
context: { params: this.params },
|
|
1856
|
+
abortEarly: false,
|
|
1857
|
+
allowUnknown: false
|
|
1858
|
+
});
|
|
676
1859
|
if (error) {
|
|
677
1860
|
const errs = error.details.map((el) => el.message).join(", ");
|
|
678
1861
|
throw new ParamError(`"${key}" validation error: ${errs}`);
|
|
@@ -690,11 +1873,26 @@ var Params = class {
|
|
|
690
1873
|
}
|
|
691
1874
|
const valFromArgs = this.args.get(key);
|
|
692
1875
|
const valFromParams = this.params[key];
|
|
693
|
-
|
|
694
|
-
|
|
1876
|
+
let source = "default";
|
|
1877
|
+
let value;
|
|
1878
|
+
if (valFromGetters !== void 0 && valFromGetters !== null) {
|
|
1879
|
+
value = this.validate(key, valFromGetters, def);
|
|
1880
|
+
source = "options";
|
|
1881
|
+
} else if (valFromArgs !== void 0 && valFromArgs !== null) {
|
|
1882
|
+
value = this.validate(key, valFromArgs, def);
|
|
1883
|
+
source = "cli";
|
|
1884
|
+
} else if (valFromParams !== void 0 && valFromParams !== null) {
|
|
1885
|
+
value = this.validate(key, valFromParams, def);
|
|
1886
|
+
source = "options";
|
|
1887
|
+
} else {
|
|
1888
|
+
value = this.validate(key, void 0, def);
|
|
1889
|
+
source = "default";
|
|
1890
|
+
}
|
|
1891
|
+
this.trackParam(key, definition || "string", value, source);
|
|
1892
|
+
if (value !== void 0 && def.values && !def.values.includes(value)) {
|
|
695
1893
|
throw new ParamError(`key ${key} should be one of ${def.values}`);
|
|
696
1894
|
}
|
|
697
|
-
return
|
|
1895
|
+
return value;
|
|
698
1896
|
}
|
|
699
1897
|
/**
|
|
700
1898
|
* Set a parameter value with validation
|
|
@@ -728,10 +1926,10 @@ var Params = class {
|
|
|
728
1926
|
* Run all registered getters for a key
|
|
729
1927
|
*/
|
|
730
1928
|
runAllRegisteredGetters(key) {
|
|
731
|
-
let val =
|
|
1929
|
+
let val = void 0;
|
|
732
1930
|
for (const getter of this.paramGetters) {
|
|
733
1931
|
val = getter(key, this.definitions[key]);
|
|
734
|
-
if (val !== void 0) {
|
|
1932
|
+
if (val !== void 0 && val !== null) {
|
|
735
1933
|
break;
|
|
736
1934
|
}
|
|
737
1935
|
}
|
|
@@ -802,6 +2000,7 @@ var ALL_LEVELS = [
|
|
|
802
2000
|
"response",
|
|
803
2001
|
"progress"
|
|
804
2002
|
];
|
|
2003
|
+
var MAX_LEVEL_LENGTH = Math.max(...ALL_LEVELS.map((level) => level.toUpperCase().length));
|
|
805
2004
|
var LEVEL_COLORS = {
|
|
806
2005
|
error: import_chalk.default.red.bold,
|
|
807
2006
|
warn: import_chalk.default.rgb(255, 165, 0),
|
|
@@ -815,13 +2014,104 @@ var LEVEL_COLORS = {
|
|
|
815
2014
|
progress: import_chalk.default.green,
|
|
816
2015
|
results: import_chalk.default.magenta
|
|
817
2016
|
};
|
|
818
|
-
var
|
|
2017
|
+
var Logger = class _Logger {
|
|
2018
|
+
context;
|
|
2019
|
+
// Partial context during initialization
|
|
819
2020
|
options;
|
|
820
2021
|
transport;
|
|
821
2022
|
startTimes = {};
|
|
822
2023
|
lastProgressTimes = {};
|
|
823
|
-
constructor(options = {}) {
|
|
824
|
-
this.
|
|
2024
|
+
constructor(context, options = {}) {
|
|
2025
|
+
this.context = context;
|
|
2026
|
+
this.options = this.getDefaultOptions();
|
|
2027
|
+
if (options) {
|
|
2028
|
+
this.configure(options);
|
|
2029
|
+
}
|
|
2030
|
+
this.updateTransport();
|
|
2031
|
+
}
|
|
2032
|
+
/**
|
|
2033
|
+
* Configure logger options
|
|
2034
|
+
* Only parameters present in options are updated
|
|
2035
|
+
*/
|
|
2036
|
+
configure(options) {
|
|
2037
|
+
if (options.mode !== void 0) {
|
|
2038
|
+
this.options.mode = this.isValidMode(options.mode) ? options.mode : "text";
|
|
2039
|
+
}
|
|
2040
|
+
if (options.route !== void 0) {
|
|
2041
|
+
this.options.route = options.route;
|
|
2042
|
+
this.updateTransport();
|
|
2043
|
+
}
|
|
2044
|
+
if (options.prefix !== void 0) {
|
|
2045
|
+
this.options.prefix = options.prefix;
|
|
2046
|
+
}
|
|
2047
|
+
if (options.silent !== void 0) {
|
|
2048
|
+
this.options.silent = options.silent;
|
|
2049
|
+
}
|
|
2050
|
+
if (options.showLevel !== void 0) {
|
|
2051
|
+
this.options.showLevel = options.showLevel;
|
|
2052
|
+
}
|
|
2053
|
+
if (options.timestamp !== void 0) {
|
|
2054
|
+
this.options.timestamp = options.timestamp;
|
|
2055
|
+
}
|
|
2056
|
+
if (options.levels !== void 0) {
|
|
2057
|
+
this.options.levels = this.normalizeLevels(options.levels);
|
|
2058
|
+
}
|
|
2059
|
+
if (options.progress !== void 0) {
|
|
2060
|
+
if (options.progress.withTimes !== void 0) {
|
|
2061
|
+
this.options.progressTimes = options.progress.withTimes;
|
|
2062
|
+
}
|
|
2063
|
+
if (options.progress.throttleMs !== void 0) {
|
|
2064
|
+
this.options.progressThrottle = options.progress.throttleMs;
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
/**
|
|
2069
|
+
* Initialize logger from context and CLI parameters
|
|
2070
|
+
*/
|
|
2071
|
+
static init(context, options) {
|
|
2072
|
+
const paramDefs = {
|
|
2073
|
+
mode: "string default text",
|
|
2074
|
+
route: "string default console",
|
|
2075
|
+
prefix: "string",
|
|
2076
|
+
silent: "boolean default false",
|
|
2077
|
+
showLevel: "boolean default true",
|
|
2078
|
+
timestamp: "boolean default false",
|
|
2079
|
+
levels: "string",
|
|
2080
|
+
progressWithTimes: "boolean default false",
|
|
2081
|
+
progressThrottleMs: "number"
|
|
2082
|
+
};
|
|
2083
|
+
const cliParams = context.params.getAll(paramDefs);
|
|
2084
|
+
const config2 = {
|
|
2085
|
+
mode: options?.mode ?? cliParams.mode,
|
|
2086
|
+
route: options?.route ?? cliParams.route,
|
|
2087
|
+
prefix: options?.prefix ?? cliParams.prefix,
|
|
2088
|
+
silent: options?.silent ?? cliParams.silent,
|
|
2089
|
+
showLevel: options?.showLevel ?? cliParams.showLevel,
|
|
2090
|
+
timestamp: options?.timestamp ?? cliParams.timestamp,
|
|
2091
|
+
levels: options?.levels ?? (cliParams.levels ? cliParams.levels.split(",") : void 0),
|
|
2092
|
+
progress: options?.progress ?? {
|
|
2093
|
+
withTimes: cliParams.progressWithTimes,
|
|
2094
|
+
throttleMs: cliParams.progressThrottleMs
|
|
2095
|
+
}
|
|
2096
|
+
};
|
|
2097
|
+
const logger = new _Logger(context, config2);
|
|
2098
|
+
context.logger = logger;
|
|
2099
|
+
return logger;
|
|
2100
|
+
}
|
|
2101
|
+
getDefaultOptions() {
|
|
2102
|
+
return {
|
|
2103
|
+
mode: "text",
|
|
2104
|
+
route: this.shouldUseIpcRoute() ? "ipc" : "console",
|
|
2105
|
+
prefix: void 0,
|
|
2106
|
+
silent: false,
|
|
2107
|
+
showLevel: true,
|
|
2108
|
+
timestamp: false,
|
|
2109
|
+
levels: ALL_LEVELS,
|
|
2110
|
+
progressTimes: false,
|
|
2111
|
+
progressThrottle: void 0
|
|
2112
|
+
};
|
|
2113
|
+
}
|
|
2114
|
+
updateTransport() {
|
|
825
2115
|
this.transport = this.options.route === "ipc" ? new ParentProcessTransport() : new ConsoleTransport();
|
|
826
2116
|
}
|
|
827
2117
|
setMode(mode) {
|
|
@@ -930,7 +2220,7 @@ var CliToolkitLogger = class {
|
|
|
930
2220
|
parts.push(now.toISOString());
|
|
931
2221
|
}
|
|
932
2222
|
if (this.options.showLevel) {
|
|
933
|
-
parts.push(struct.level.toUpperCase());
|
|
2223
|
+
parts.push(struct.level.toUpperCase().padEnd(MAX_LEVEL_LENGTH));
|
|
934
2224
|
}
|
|
935
2225
|
if (struct.level === "progress") {
|
|
936
2226
|
if (struct.prefix) {
|
|
@@ -964,22 +2254,6 @@ var CliToolkitLogger = class {
|
|
|
964
2254
|
inspectChunks(chunks) {
|
|
965
2255
|
return chunks.map((chunk) => import_util.default.inspect(chunk, { colors: true, depth: null })).join(" ");
|
|
966
2256
|
}
|
|
967
|
-
normalizeOptions(options) {
|
|
968
|
-
const { route, mode, prefix, silent, showLevel, timestamp, levels, progress } = options;
|
|
969
|
-
const shouldUseIpc = this.shouldUseIpcRoute();
|
|
970
|
-
const normalized = {
|
|
971
|
-
mode: this.isValidMode(mode) ? mode : "text",
|
|
972
|
-
route: route ?? (shouldUseIpc ? "ipc" : "console"),
|
|
973
|
-
prefix,
|
|
974
|
-
silent: silent ?? false,
|
|
975
|
-
showLevel: showLevel ?? true,
|
|
976
|
-
timestamp: timestamp ?? false,
|
|
977
|
-
levels: this.normalizeLevels(levels),
|
|
978
|
-
progressTimes: progress?.withTimes ?? false,
|
|
979
|
-
progressThrottle: progress?.throttleMs
|
|
980
|
-
};
|
|
981
|
-
return normalized;
|
|
982
|
-
}
|
|
983
2257
|
shouldUseIpcRoute() {
|
|
984
2258
|
if (process.env.VITEST || process.env.NODE_ENV === "test") {
|
|
985
2259
|
return false;
|
|
@@ -1010,35 +2284,44 @@ var CliToolkitLogger = class {
|
|
|
1010
2284
|
|
|
1011
2285
|
// src/init/index.ts
|
|
1012
2286
|
var import_events = require("events");
|
|
2287
|
+
function extractComponentOptions(opts, componentName) {
|
|
2288
|
+
const reservedKeys = ["overrides", "defaults", "modules"];
|
|
2289
|
+
const componentOptions = {};
|
|
2290
|
+
for (const [key, value] of Object.entries(opts)) {
|
|
2291
|
+
if (!reservedKeys.includes(key)) {
|
|
2292
|
+
componentOptions[key] = value;
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2295
|
+
return componentOptions;
|
|
2296
|
+
}
|
|
1013
2297
|
function setup(opts = {}) {
|
|
1014
|
-
const args =
|
|
2298
|
+
const args = Args.init({
|
|
1015
2299
|
overrides: opts.overrides || {},
|
|
1016
2300
|
defaults: opts.defaults || {}
|
|
1017
2301
|
});
|
|
1018
|
-
const
|
|
1019
|
-
const loggerOptions = opts.logger || {};
|
|
1020
|
-
const logger = new CliToolkitLogger({
|
|
1021
|
-
mode: loggerOptions.mode || "text",
|
|
1022
|
-
route: loggerOptions.route || "console",
|
|
1023
|
-
prefix: loggerOptions.prefix,
|
|
1024
|
-
silent: loggerOptions.silent,
|
|
1025
|
-
showLevel: loggerOptions.showLevel,
|
|
1026
|
-
timestamp: loggerOptions.timestamp,
|
|
1027
|
-
levels: loggerOptions.levels
|
|
1028
|
-
});
|
|
1029
|
-
const cleanupFunctions = [];
|
|
1030
|
-
const context = {
|
|
2302
|
+
const partialContext = {
|
|
1031
2303
|
args,
|
|
1032
|
-
params,
|
|
1033
|
-
logger,
|
|
1034
2304
|
emitter: new import_events.EventEmitter(),
|
|
1035
2305
|
isStop: () => false,
|
|
1036
|
-
|
|
1037
|
-
cleanupFunctions,
|
|
2306
|
+
cleanupFunctions: [],
|
|
1038
2307
|
registerCleanup: (fn) => {
|
|
1039
|
-
cleanupFunctions.push(fn);
|
|
2308
|
+
partialContext.cleanupFunctions.push(fn);
|
|
1040
2309
|
}
|
|
1041
2310
|
};
|
|
2311
|
+
const params = Params.init(partialContext, opts.overrides || {});
|
|
2312
|
+
partialContext.params = params;
|
|
2313
|
+
const loggerOptions = extractComponentOptions(opts, "logger");
|
|
2314
|
+
const logger = Logger.init(partialContext, loggerOptions);
|
|
2315
|
+
partialContext.logger = logger;
|
|
2316
|
+
const context = {
|
|
2317
|
+
args,
|
|
2318
|
+
params,
|
|
2319
|
+
logger,
|
|
2320
|
+
emitter: partialContext.emitter,
|
|
2321
|
+
isStop: partialContext.isStop,
|
|
2322
|
+
cleanupFunctions: partialContext.cleanupFunctions,
|
|
2323
|
+
registerCleanup: partialContext.registerCleanup
|
|
2324
|
+
};
|
|
1042
2325
|
logger.debug("[setup] completed successfully");
|
|
1043
2326
|
return context;
|
|
1044
2327
|
}
|
|
@@ -1049,13 +2332,46 @@ async function setupModules(context, opts = {}) {
|
|
|
1049
2332
|
context.logger.debug("[setupModules] completed successfully");
|
|
1050
2333
|
return context;
|
|
1051
2334
|
}
|
|
2335
|
+
function printAllParameters(context) {
|
|
2336
|
+
const trackedParams = context.params.getTrackedParams();
|
|
2337
|
+
console.log("\n=== All Figured Parameters ===");
|
|
2338
|
+
console.log("\nComponent: Logger");
|
|
2339
|
+
const loggerParams = trackedParams.filter(
|
|
2340
|
+
(p) => ["mode", "route", "prefix", "silent", "showLevel", "timestamp", "levels"].includes(p.key)
|
|
2341
|
+
);
|
|
2342
|
+
if (loggerParams.length > 0) {
|
|
2343
|
+
loggerParams.forEach((p) => {
|
|
2344
|
+
console.log(` ${p.key}: ${JSON.stringify(p.value)} (from ${p.source})`);
|
|
2345
|
+
});
|
|
2346
|
+
} else {
|
|
2347
|
+
console.log(" (no parameters requested)");
|
|
2348
|
+
}
|
|
2349
|
+
console.log("\n=== End Parameters ===\n");
|
|
2350
|
+
}
|
|
1052
2351
|
async function init(flow, opts = {}) {
|
|
1053
2352
|
let stop = false;
|
|
1054
2353
|
let context = null;
|
|
1055
2354
|
try {
|
|
2355
|
+
try {
|
|
2356
|
+
const screenModule = await Promise.resolve().then(() => (init_screen(), screen_exports));
|
|
2357
|
+
if (screenModule && typeof screenModule.load === "function") {
|
|
2358
|
+
await screenModule.load();
|
|
2359
|
+
}
|
|
2360
|
+
} catch {
|
|
2361
|
+
if (typeof require !== "undefined") {
|
|
2362
|
+
try {
|
|
2363
|
+
} catch {
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
1056
2367
|
context = setup(opts);
|
|
1057
2368
|
context.isStop = () => stop;
|
|
1058
2369
|
context = await setupModules(context, opts);
|
|
2370
|
+
const stopAfter = context.args.get("stopAfter");
|
|
2371
|
+
if (stopAfter === "init") {
|
|
2372
|
+
printAllParameters(context);
|
|
2373
|
+
process.exit(0);
|
|
2374
|
+
}
|
|
1059
2375
|
process.on("SIGINT", async () => {
|
|
1060
2376
|
if (stop) {
|
|
1061
2377
|
context.logger.warn("[process] killed");
|
|
@@ -1073,14 +2389,21 @@ async function init(flow, opts = {}) {
|
|
|
1073
2389
|
await flow(context);
|
|
1074
2390
|
} catch (error) {
|
|
1075
2391
|
const errorLocation = error instanceof Error && error.stack ? error.stack.split("\n")[1]?.trim() || "Unknown location" : "Unknown location";
|
|
2392
|
+
const logError = (msg, ...args) => {
|
|
2393
|
+
if (context?.logger) {
|
|
2394
|
+
context.logger.error(msg, ...args);
|
|
2395
|
+
} else {
|
|
2396
|
+
console.error(msg, ...args);
|
|
2397
|
+
}
|
|
2398
|
+
};
|
|
1076
2399
|
if (error instanceof ParamError) {
|
|
1077
|
-
|
|
2400
|
+
logError(`[params]: ${error.message} (${errorLocation})`);
|
|
1078
2401
|
process.exitCode = 3;
|
|
1079
2402
|
} else if (error instanceof InitError) {
|
|
1080
|
-
|
|
2403
|
+
logError(`[init]: ${error.message} (${errorLocation})`);
|
|
1081
2404
|
process.exitCode = 4;
|
|
1082
2405
|
} else {
|
|
1083
|
-
|
|
2406
|
+
logError(`[other] error:`, error, errorLocation);
|
|
1084
2407
|
process.exitCode = 5;
|
|
1085
2408
|
}
|
|
1086
2409
|
} finally {
|