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