@flowscripter/dynamic-cli-framework 11.0.0 → 11.1.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.
@@ -14,7 +14,9 @@ import {
14
14
  ComplexValueTypeName,
15
15
  type PopulatedSingleValueType,
16
16
  type PopulatedValues,
17
+ type Values,
17
18
  } from "@flowscripter/dynamic-cli-framework-api";
19
+ import type { Command } from "@flowscripter/dynamic-cli-framework-api";
18
20
  import type { Argument } from "@flowscripter/dynamic-cli-framework-api";
19
21
  import type { Option } from "@flowscripter/dynamic-cli-framework-api";
20
22
  import type { Positional } from "@flowscripter/dynamic-cli-framework-api";
@@ -23,6 +25,20 @@ import type { SubCommand } from "@flowscripter/dynamic-cli-framework-api";
23
25
  import type { GlobalCommand } from "@flowscripter/dynamic-cli-framework-api";
24
26
  import { isGlobalCommand, isSubCommand } from "../../runtime/command/CommandTypeGuards.ts";
25
27
 
28
+ /**
29
+ * The command and values visible to {@link Argument.validate} functions while prompting.
30
+ */
31
+ interface PromptScope {
32
+ readonly command: Command;
33
+ readonly values: PopulatedValues | PopulatedSingleValueType;
34
+ }
35
+
36
+ /**
37
+ * Used as {@link ValidationContext.command} when prompting via {@link ArgumentPrompterService.promptForArguments},
38
+ * which is not associated with a specific command.
39
+ */
40
+ const NO_COMMAND = { name: "" } as Command;
41
+
26
42
  export default class DefaultArgumentPrompterService implements ArgumentPrompterService {
27
43
  readonly #prompterService: PrompterService;
28
44
 
@@ -63,6 +79,53 @@ export default class DefaultArgumentPrompterService implements ArgumentPrompterS
63
79
  return parseResult;
64
80
  }
65
81
 
82
+ async promptForArguments(
83
+ args: ReadonlyArray<Option | ComplexOption>,
84
+ existing: Values = {},
85
+ ): Promise<Values | undefined> {
86
+ if (!this.#prompterService.promptEnabled) {
87
+ return undefined;
88
+ }
89
+ const prompted: Record<string, Values[string]> = {};
90
+ const merged: Record<string, Values[string]> = { ...existing };
91
+ const scope: PromptScope = { command: NO_COMMAND, values: merged as PopulatedValues };
92
+
93
+ for (const arg of args) {
94
+ if (arg.name in existing) {
95
+ continue;
96
+ }
97
+ const value = (await this.#promptForArgument(arg.name, arg, scope)) as Values[string];
98
+ prompted[arg.name] = value;
99
+ merged[arg.name] = value;
100
+ }
101
+ return prompted as Values;
102
+ }
103
+
104
+ async #promptForArgument(
105
+ name: string,
106
+ arg: Option | Positional | ComplexOption,
107
+ scope: PromptScope,
108
+ ): Promise<PopulatedValues[string]> {
109
+ if ("type" in arg && (arg as ComplexOption).type === ComplexValueTypeName.COMPLEX) {
110
+ return await this.#promptForComplexOption(name, arg as ComplexOption, scope);
111
+ }
112
+ if (DefaultArgumentPrompterService.#isArrayArgument(arg)) {
113
+ return await this.#promptForArrayArgument(name, arg as Option | Positional, scope);
114
+ }
115
+ const prompt = DefaultArgumentPrompterService.#argumentToPrompt(
116
+ name,
117
+ (arg as Option | Positional).description ?? name,
118
+ arg as Argument,
119
+ name,
120
+ scope,
121
+ );
122
+ const result = await this.#prompterService.prompt(prompt);
123
+ return DefaultArgumentPrompterService.#coerceValue(
124
+ result.value as SingleValueType,
125
+ (arg as Argument).type,
126
+ );
127
+ }
128
+
66
129
  async #promptForGlobalCommand(parseResult: ParseResult): Promise<ParseResult> {
67
130
  const command = parseResult.command as GlobalCommand;
68
131
  if (!command.argument) {
@@ -73,6 +136,8 @@ export default class DefaultArgumentPrompterService implements ArgumentPrompterS
73
136
  command.name,
74
137
  command.name,
75
138
  command.argument,
139
+ command.name,
140
+ { command, values: undefined },
76
141
  );
77
142
  const result = await this.#prompterService.prompt(prompt);
78
143
  const value = DefaultArgumentPrompterService.#coerceValue(
@@ -96,6 +161,7 @@ export default class DefaultArgumentPrompterService implements ArgumentPrompterS
96
161
  ...existingValues,
97
162
  };
98
163
  const remainingInvalid: InvalidArgument[] = [];
164
+ const scope: PromptScope = { command, values: newValues as PopulatedValues };
99
165
 
100
166
  for (const invalid of parseResult.invalidArguments) {
101
167
  if (invalid.reason !== InvalidArgumentReason.MISSING_VALUE || !invalid.name) {
@@ -109,30 +175,7 @@ export default class DefaultArgumentPrompterService implements ArgumentPrompterS
109
175
  continue;
110
176
  }
111
177
 
112
- if ("type" in arg && (arg as ComplexOption).type === ComplexValueTypeName.COMPLEX) {
113
- const complexValues = await this.#promptForComplexOption(
114
- invalid.name,
115
- arg as ComplexOption,
116
- );
117
- newValues[invalid.name] = complexValues;
118
- } else if (DefaultArgumentPrompterService.#isArrayArgument(arg)) {
119
- const arrayValues = await this.#promptForArrayArgument(
120
- invalid.name,
121
- arg as Option | Positional,
122
- );
123
- newValues[invalid.name] = arrayValues;
124
- } else {
125
- const prompt = DefaultArgumentPrompterService.#argumentToPrompt(
126
- invalid.name,
127
- (arg as Option | Positional).description ?? invalid.name,
128
- arg as Argument,
129
- );
130
- const result = await this.#prompterService.prompt(prompt);
131
- newValues[invalid.name] = DefaultArgumentPrompterService.#coerceValue(
132
- result.value as SingleValueType,
133
- (arg as Argument).type,
134
- );
135
- }
178
+ newValues[invalid.name] = await this.#promptForArgument(invalid.name, arg, scope);
136
179
  }
137
180
 
138
181
  return {
@@ -147,6 +190,7 @@ export default class DefaultArgumentPrompterService implements ArgumentPrompterS
147
190
  async #promptForComplexOption(
148
191
  name: string,
149
192
  complexOption: ComplexOption,
193
+ scope: PromptScope,
150
194
  ): Promise<PopulatedValues> {
151
195
  const values: Record<string, PopulatedValues[string]> = {};
152
196
 
@@ -155,6 +199,7 @@ export default class DefaultArgumentPrompterService implements ArgumentPrompterS
155
199
  values[(prop as ComplexOption).name] = await this.#promptForComplexOption(
156
200
  `${name}.${(prop as ComplexOption).name}`,
157
201
  prop as ComplexOption,
202
+ scope,
158
203
  );
159
204
  } else {
160
205
  const option = prop as Option;
@@ -165,6 +210,8 @@ export default class DefaultArgumentPrompterService implements ArgumentPrompterS
165
210
  `${name}.${option.name}`,
166
211
  option.description ?? `${name}.${option.name}`,
167
212
  option,
213
+ `${name}.${option.name}`,
214
+ scope,
168
215
  );
169
216
  const result = await this.#prompterService.prompt(prompt);
170
217
  values[option.name] = DefaultArgumentPrompterService.#coerceValue(
@@ -180,6 +227,7 @@ export default class DefaultArgumentPrompterService implements ArgumentPrompterS
180
227
  async #promptForArrayArgument(
181
228
  name: string,
182
229
  arg: Option | Positional,
230
+ scope: PromptScope,
183
231
  ): Promise<Array<SingleValueType>> {
184
232
  const values: SingleValueType[] = [];
185
233
 
@@ -188,6 +236,8 @@ export default class DefaultArgumentPrompterService implements ArgumentPrompterS
188
236
  name,
189
237
  `${arg.description ?? name}${values.length > 0 ? ` [${values.length + 1}]` : ""}`,
190
238
  arg as Argument,
239
+ name,
240
+ scope,
191
241
  );
192
242
  const result = await this.#prompterService.prompt(prompt);
193
243
  values.push(
@@ -212,7 +262,13 @@ export default class DefaultArgumentPrompterService implements ArgumentPrompterS
212
262
  return values;
213
263
  }
214
264
 
215
- static #argumentToPrompt(name: string, description: string, arg: Argument): Prompt {
265
+ static #argumentToPrompt(
266
+ name: string,
267
+ description: string,
268
+ arg: Argument,
269
+ path: string,
270
+ scope: PromptScope,
271
+ ): Prompt {
216
272
  if (arg.type === ValueTypeName.BOOLEAN) {
217
273
  return {
218
274
  name,
@@ -240,7 +296,17 @@ export default class DefaultArgumentPrompterService implements ArgumentPrompterS
240
296
  returnedValue: "",
241
297
  min: arg.minValueInclusive,
242
298
  max: arg.maxValueInclusive,
243
- validate: arg.validate ? (v) => arg.validate!(v) : undefined,
299
+ validate: arg.validate
300
+ ? (v) => {
301
+ const value = DefaultArgumentPrompterService.#coerceValue(v, arg.type);
302
+ return arg.validate!(value, {
303
+ argument: arg,
304
+ path,
305
+ values: scope.values === undefined ? value : scope.values,
306
+ command: scope.command,
307
+ });
308
+ }
309
+ : undefined,
244
310
  };
245
311
 
246
312
  return {
@@ -226,6 +226,7 @@ export default class KeyValueServiceProvider implements ServiceProvider {
226
226
  const scopedKeyValueService = this.getScopedKeyValueService(scopeType, scopeKey);
227
227
  return {
228
228
  cliConfig: context.cliConfig,
229
+ isTty: context.isTty,
229
230
  getServiceById: (id: string): unknown =>
230
231
  id === KEY_VALUE_SERVICE_ID ? scopedKeyValueService : context.getServiceById(id),
231
232
  doesServiceExist: (id: string): boolean => context.doesServiceExist(id),