@flowscripter/dynamic-cli-framework 10.1.1 → 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.
Files changed (38) hide show
  1. package/dist/src/cli/BaseCLI.d.ts.map +1 -1
  2. package/dist/src/cli/BaseCLI.js +6 -2
  3. package/dist/src/runtime/DefaultContext.d.ts +2 -1
  4. package/dist/src/runtime/DefaultContext.d.ts.map +1 -1
  5. package/dist/src/runtime/DefaultContext.js +3 -1
  6. package/dist/src/runtime/command/CommandValidator.d.ts.map +1 -1
  7. package/dist/src/runtime/command/CommandValidator.js +18 -6
  8. package/dist/src/runtime/parser.d.ts.map +1 -1
  9. package/dist/src/runtime/parser.js +13 -4
  10. package/dist/src/runtime/values/argumentValueValidation.d.ts +26 -4
  11. package/dist/src/runtime/values/argumentValueValidation.d.ts.map +1 -1
  12. package/dist/src/runtime/values/argumentValueValidation.js +53 -50
  13. package/dist/src/service/argumentPrompter/DefaultArgumentPrompterService.d.ts +4 -0
  14. package/dist/src/service/argumentPrompter/DefaultArgumentPrompterService.d.ts.map +1 -1
  15. package/dist/src/service/argumentPrompter/DefaultArgumentPrompterService.js +53 -21
  16. package/dist/src/service/configuration/ConfigurationServiceProvider.d.ts +1 -1
  17. package/dist/src/service/configuration/ConfigurationServiceProvider.d.ts.map +1 -1
  18. package/dist/src/service/configuration/ConfigurationServiceProvider.js +4 -3
  19. package/dist/src/service/configuration/KeyValueServiceProvider.d.ts.map +1 -1
  20. package/dist/src/service/configuration/KeyValueServiceProvider.js +1 -0
  21. package/dist/src/service/configuration/command/{DumpConfigCommand.d.ts → ConfigDumpSubCommand.d.ts} +10 -5
  22. package/dist/src/service/configuration/command/ConfigDumpSubCommand.d.ts.map +1 -0
  23. package/dist/src/service/configuration/command/{DumpConfigCommand.js → ConfigDumpSubCommand.js} +6 -3
  24. package/dist/src/service/configuration/command/ConfigGroupCommand.d.ts +16 -0
  25. package/dist/src/service/configuration/command/ConfigGroupCommand.d.ts.map +1 -0
  26. package/dist/src/service/configuration/command/ConfigGroupCommand.js +14 -0
  27. package/package.json +2 -2
  28. package/src/cli/BaseCLI.ts +6 -2
  29. package/src/runtime/DefaultContext.ts +4 -1
  30. package/src/runtime/command/CommandValidator.ts +35 -5
  31. package/src/runtime/parser.ts +14 -0
  32. package/src/runtime/values/argumentValueValidation.ts +82 -50
  33. package/src/service/argumentPrompter/DefaultArgumentPrompterService.ts +92 -26
  34. package/src/service/configuration/ConfigurationServiceProvider.ts +6 -3
  35. package/src/service/configuration/KeyValueServiceProvider.ts +1 -0
  36. package/src/service/configuration/command/{DumpConfigCommand.ts → ConfigDumpSubCommand.ts} +9 -4
  37. package/src/service/configuration/command/ConfigGroupCommand.ts +20 -0
  38. package/dist/src/service/configuration/command/DumpConfigCommand.d.ts.map +0 -1
@@ -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 {
@@ -14,7 +14,8 @@ import type {
14
14
  import getLogger from "../../util/logger.ts";
15
15
  import type { Context } from "@flowscripter/dynamic-cli-framework-api";
16
16
  import type { GroupCommand } from "@flowscripter/dynamic-cli-framework-api";
17
- import DumpConfigCommand from "./command/DumpConfigCommand.ts";
17
+ import ConfigDumpSubCommand from "./command/ConfigDumpSubCommand.ts";
18
+ import ConfigGroupCommand from "./command/ConfigGroupCommand.ts";
18
19
  import {
19
20
  CONFIGURATION_SERVICE_ID,
20
21
  SECRET_SENTINEL_PREFIX,
@@ -63,7 +64,7 @@ class ConfigurationServiceImpl implements ConfigurationService {
63
64
  * {@link KeyValueServiceProvider} (which holds a direct reference to this provider - see
64
65
  * {@link getKeyValueData} and {@link flushIfDirty} - rather than looking this provider up via
65
66
  * {@link Context}, since only {@link configLocation}/{@link setConfigLocation} are safe to expose
66
- * generally; {@link getConfigString} is wired directly to {@link DumpConfigCommand} for the same
67
+ * generally; {@link getConfigString} is wired directly to {@link ConfigDumpSubCommand} for the same
67
68
  * reason - a full config dump would leak every other command's/service's key-value data if it
68
69
  * were reachable through a general lookup).
69
70
  *
@@ -222,7 +223,9 @@ export default class ConfigurationServiceProvider implements ServiceProvider {
222
223
 
223
224
  if (this.configEnabled) {
224
225
  commands.push(new ConfigCommand(this.servicePriority));
225
- commands.push(new DumpConfigCommand(() => this.getConfigString()));
226
+ commands.push(
227
+ new ConfigGroupCommand([new ConfigDumpSubCommand(() => this.getConfigString())]),
228
+ );
226
229
  }
227
230
  if (this.secretServiceEnabled) {
228
231
  this.#defaultsSecretService = new DefaultSecretService(cliConfig.name, "defaults");
@@ -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),
@@ -1,21 +1,26 @@
1
1
  import type { Context } from "@flowscripter/dynamic-cli-framework-api";
2
- import type { GlobalCommand } from "@flowscripter/dynamic-cli-framework-api";
2
+ import type { Option } from "@flowscripter/dynamic-cli-framework-api";
3
+ import type { Positional } from "@flowscripter/dynamic-cli-framework-api";
4
+ import type { SubCommand } from "@flowscripter/dynamic-cli-framework-api";
3
5
  import type { PrinterService } from "@flowscripter/dynamic-cli-framework-api";
4
6
  import { PRINTER_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
5
7
  import type { SyntaxHighlighterService } from "@flowscripter/dynamic-cli-framework-api";
6
8
  import { SYNTAX_HIGHLIGHTER_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
7
9
 
8
10
  /**
9
- * Command which dumps the configuration loaded by {@link ConfigurationServiceProvider}.
11
+ * Member of the `config` group which dumps the configuration loaded by
12
+ * {@link ConfigurationServiceProvider}.
10
13
  *
11
14
  * Takes a direct reference to {@link ConfigurationServiceProvider.getConfigString}, wired by the
12
15
  * provider that constructs this command, rather than looking it up via {@link Context} -
13
16
  * deliberately, since a full config dump is not something every command/task should be able to
14
17
  * reach through a general lookup (see {@link ConfigurationService}'s doc comment).
15
18
  */
16
- export default class DumpConfigCommand implements GlobalCommand {
17
- readonly name = "dump-config";
19
+ export default class ConfigDumpSubCommand implements SubCommand {
20
+ readonly name = "dump";
18
21
  readonly description = "Dump configuration values";
22
+ readonly options: ReadonlyArray<Option> = [];
23
+ readonly positionals: ReadonlyArray<Positional> = [];
19
24
 
20
25
  readonly #getConfigString: () => string;
21
26
 
@@ -0,0 +1,20 @@
1
+ import type { Context } from "@flowscripter/dynamic-cli-framework-api";
2
+ import type { GroupCommand } from "@flowscripter/dynamic-cli-framework-api";
3
+ import type { SubCommand } from "@flowscripter/dynamic-cli-framework-api";
4
+
5
+ /**
6
+ * Group command named `config` whose members manage the configuration handled by
7
+ * {@link ConfigurationServiceProvider}.
8
+ */
9
+ export default class ConfigGroupCommand implements GroupCommand {
10
+ readonly name = "config";
11
+ readonly description = "Manage configuration";
12
+ readonly enableConfiguration = false;
13
+ readonly memberSubCommands: ReadonlyArray<SubCommand>;
14
+
15
+ public constructor(memberSubCommands: ReadonlyArray<SubCommand>) {
16
+ this.memberSubCommands = memberSubCommands;
17
+ }
18
+
19
+ public async execute(_context: Context): Promise<void> {}
20
+ }
@@ -1 +0,0 @@
1
- {"version":3,"file":"DumpConfigCommand.d.ts","sourceRoot":"","sources":["../../../../../src/service/configuration/command/DumpConfigCommand.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AACvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AAM7E;;;;;;;GAOG;AACH,MAAM,CAAC,OAAO,OAAO,iBAAkB,YAAW,aAAa;;IAC7D,QAAQ,CAAC,IAAI,iBAAiB;IAC9B,QAAQ,CAAC,WAAW,+BAA+B;IAInD,YAAmB,eAAe,EAAE,MAAM,MAAM,EAE/C;IAEY,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CASpD;CACF"}