@pablozaiden/terminatui 0.5.3 → 0.5.4
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/package.json +1 -1
- package/src/tui/adapters/ink/SemanticInkRenderer.tsx +3 -0
- package/src/tui/adapters/opentui/SemanticOpenTuiRenderer.tsx +8 -1
- package/src/tui/controllers/ConfigController.tsx +26 -0
- package/src/tui/driver/types.ts +1 -0
- package/src/tui/semantic/ConfigScreen.tsx +3 -0
- package/src/tui/semantic/types.ts +1 -0
- package/src/tui/utils/schemaToFields.ts +1 -0
- package/src/tui/utils/validateTuiOptions.ts +60 -0
package/package.json
CHANGED
|
@@ -107,6 +107,9 @@ export class SemanticInkRenderer {
|
|
|
107
107
|
/>
|
|
108
108
|
<Box flexDirection="column" paddingTop={1}>
|
|
109
109
|
<Label color="mutedText">CLI: {props.cliCommand}</Label>
|
|
110
|
+
{props.validationError ? (
|
|
111
|
+
<Label color="error" bold>{props.validationError}</Label>
|
|
112
|
+
) : null}
|
|
110
113
|
</Box>
|
|
111
114
|
</Box>
|
|
112
115
|
);
|
|
@@ -98,7 +98,14 @@ export class SemanticOpenTuiRenderer {
|
|
|
98
98
|
}
|
|
99
99
|
/>
|
|
100
100
|
<box flexDirection="column" paddingTop={1}>
|
|
101
|
-
<
|
|
101
|
+
<box>
|
|
102
|
+
<Label color="mutedText">CLI: {props.cliCommand}</Label>
|
|
103
|
+
</box>
|
|
104
|
+
{props.validationError ? (
|
|
105
|
+
<box>
|
|
106
|
+
<Label color="error" bold>{props.validationError}</Label>
|
|
107
|
+
</box>
|
|
108
|
+
) : null}
|
|
102
109
|
</box>
|
|
103
110
|
</box>
|
|
104
111
|
);
|
|
@@ -6,6 +6,7 @@ import { RenderConfigScreen } from "../semantic/render.tsx";
|
|
|
6
6
|
|
|
7
7
|
import { buildCliCommand } from "../utils/buildCliCommand.ts";
|
|
8
8
|
import { loadPersistedParameters, savePersistedParameters } from "../utils/parameterPersistence.ts";
|
|
9
|
+
import { validateTuiOptions } from "../utils/validateTuiOptions.ts";
|
|
9
10
|
|
|
10
11
|
import type { OptionDef, OptionSchema } from "../../types/command.ts";
|
|
11
12
|
import type {
|
|
@@ -112,6 +113,7 @@ export class ConfigController {
|
|
|
112
113
|
values={params.values}
|
|
113
114
|
cliCommand={cliCommand}
|
|
114
115
|
selectedFieldIndex={clampedIndex}
|
|
116
|
+
validationError={params.validationError}
|
|
115
117
|
onSelectionChange={(index) => {
|
|
116
118
|
const maxIndex = params.fieldConfigs.length;
|
|
117
119
|
const nextIndex = Math.max(0, Math.min(index, maxIndex));
|
|
@@ -152,6 +154,30 @@ export class ConfigController {
|
|
|
152
154
|
});
|
|
153
155
|
}}
|
|
154
156
|
onRun={() => {
|
|
157
|
+
const schema = params.command.options as OptionSchema;
|
|
158
|
+
const errors = validateTuiOptions(schema, params.values);
|
|
159
|
+
|
|
160
|
+
if (errors.length > 0) {
|
|
161
|
+
// Show validation error and stay on config screen
|
|
162
|
+
const firstError = errors[0];
|
|
163
|
+
this.navigation.replace("config" satisfies TuiRoute, {
|
|
164
|
+
...params,
|
|
165
|
+
validationError: firstError?.message ?? "Validation error",
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Clear error after 3 seconds
|
|
169
|
+
setTimeout(() => {
|
|
170
|
+
const currentParams = this.navigation.current.params as ConfigRouteParams | undefined;
|
|
171
|
+
if (currentParams?.validationError) {
|
|
172
|
+
this.navigation.replace("config" satisfies TuiRoute, {
|
|
173
|
+
...currentParams,
|
|
174
|
+
validationError: null,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}, 3000);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
155
181
|
void this.run(params);
|
|
156
182
|
}}
|
|
157
183
|
/>
|
package/src/tui/driver/types.ts
CHANGED
|
@@ -15,6 +15,9 @@ export interface ConfigScreenProps {
|
|
|
15
15
|
|
|
16
16
|
onEditField: (fieldId: string) => void;
|
|
17
17
|
onRun: () => void;
|
|
18
|
+
|
|
19
|
+
/** Validation error message to display */
|
|
20
|
+
validationError?: string | null;
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
export function ConfigScreen(_props: ConfigScreenProps) {
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { OptionSchema } from "../../types/command.ts";
|
|
2
|
+
import type { ParseError } from "../../cli/parser.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Validate option values for TUI execution.
|
|
6
|
+
*
|
|
7
|
+
* This differs from CLI validation in that:
|
|
8
|
+
* - Empty strings are treated as missing for required string fields
|
|
9
|
+
* - Empty arrays are treated as missing for required array fields
|
|
10
|
+
*
|
|
11
|
+
* @param schema - The option schema to validate against
|
|
12
|
+
* @param values - The current option values
|
|
13
|
+
* @returns Array of validation errors (empty if valid)
|
|
14
|
+
*/
|
|
15
|
+
export function validateTuiOptions(
|
|
16
|
+
schema: OptionSchema,
|
|
17
|
+
values: Record<string, unknown>
|
|
18
|
+
): ParseError[] {
|
|
19
|
+
const errors: ParseError[] = [];
|
|
20
|
+
|
|
21
|
+
for (const [name, def] of Object.entries(schema)) {
|
|
22
|
+
const value = values[name];
|
|
23
|
+
|
|
24
|
+
// For required fields: check undefined AND empty values
|
|
25
|
+
if (def.required) {
|
|
26
|
+
const isMissing =
|
|
27
|
+
value === undefined ||
|
|
28
|
+
(def.type === "string" && value === "") ||
|
|
29
|
+
(def.type === "array" && Array.isArray(value) && value.length === 0);
|
|
30
|
+
|
|
31
|
+
if (isMissing) {
|
|
32
|
+
errors.push({
|
|
33
|
+
type: "missing_required",
|
|
34
|
+
message: `Missing required option: ${name}`,
|
|
35
|
+
field: name,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Number range validation
|
|
41
|
+
if (def.type === "number" && typeof value === "number") {
|
|
42
|
+
if (def.min !== undefined && value < def.min) {
|
|
43
|
+
errors.push({
|
|
44
|
+
type: "validation",
|
|
45
|
+
message: `Option "${name}" must be at least ${def.min}`,
|
|
46
|
+
field: name,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
if (def.max !== undefined && value > def.max) {
|
|
50
|
+
errors.push({
|
|
51
|
+
type: "validation",
|
|
52
|
+
message: `Option "${name}" must be at most ${def.max}`,
|
|
53
|
+
field: name,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return errors;
|
|
60
|
+
}
|