@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pablozaiden/terminatui",
3
- "version": "0.5.3",
3
+ "version": "0.5.4",
4
4
  "description": "Terminal UI and Command Line Application Framework",
5
5
  "repository": {
6
6
  "url": "https://github.com/PabloZaiden/terminatui"
@@ -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
- <Label color="mutedText">CLI: {props.cliCommand}</Label>
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
  />
@@ -25,6 +25,7 @@ export type ConfigRouteParams = {
25
25
  values: Record<string, unknown>;
26
26
  fieldConfigs: ReturnType<typeof schemaToFieldConfigs>;
27
27
  selectedFieldIndex?: number;
28
+ validationError?: string | null;
28
29
  };
29
30
 
30
31
  export type CommandBrowserRouteParams = {
@@ -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) {
@@ -125,4 +125,5 @@ export interface FieldConfig {
125
125
  label: string;
126
126
  type: FieldType;
127
127
  options?: FieldOption[];
128
+ required?: boolean;
128
129
  }
@@ -75,6 +75,7 @@ export function schemaToFieldConfigs(schema: OptionSchema): FieldConfig[] {
75
75
  label: def.label ?? keyToLabel(key),
76
76
  type: optionTypeToFieldType(def),
77
77
  options: createFieldOptions(def),
78
+ required: def.required,
78
79
  };
79
80
 
80
81
  fields.push(fieldConfig);
@@ -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
+ }