@flowscripter/dynamic-cli-framework 9.1.3 → 10.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 (47) hide show
  1. package/dist/src/command/MultiCommandCliHelpCommand.d.ts.map +1 -1
  2. package/dist/src/command/MultiCommandCliHelpCommand.js +1 -1
  3. package/dist/src/runtime/command/CommandValidator.d.ts.map +1 -1
  4. package/dist/src/runtime/command/CommandValidator.js +9 -2
  5. package/dist/src/runtime/parser.d.ts +8 -2
  6. package/dist/src/runtime/parser.d.ts.map +1 -1
  7. package/dist/src/runtime/parser.js +11 -6
  8. package/dist/src/runtime/runner.d.ts.map +1 -1
  9. package/dist/src/runtime/runner.js +5 -2
  10. package/dist/src/runtime/values/ValuePopulationResult.d.ts +4 -0
  11. package/dist/src/runtime/values/ValuePopulationResult.d.ts.map +1 -1
  12. package/dist/src/runtime/values/globalCommandValuePopulation.d.ts.map +1 -1
  13. package/dist/src/runtime/values/globalCommandValuePopulation.js +11 -3
  14. package/dist/src/runtime/values/subCommandValuePopulation.d.ts.map +1 -1
  15. package/dist/src/runtime/values/subCommandValuePopulation.js +39 -9
  16. package/dist/src/service/configuration/ConfigurationServiceProvider.d.ts +12 -3
  17. package/dist/src/service/configuration/ConfigurationServiceProvider.d.ts.map +1 -1
  18. package/dist/src/service/configuration/ConfigurationServiceProvider.js +16 -7
  19. package/dist/src/util/configHelper.d.ts +4 -1
  20. package/dist/src/util/configHelper.d.ts.map +1 -1
  21. package/dist/src/util/configHelper.js +6 -1
  22. package/dist/src/util/envVarHelper.d.ts +4 -1
  23. package/dist/src/util/envVarHelper.d.ts.map +1 -1
  24. package/dist/src/util/envVarHelper.js +8 -5
  25. package/dist/src/util/helpHelper.d.ts +1 -1
  26. package/dist/src/util/helpHelper.d.ts.map +1 -1
  27. package/dist/src/util/helpHelper.js +8 -16
  28. package/dist/src/util/runnerHelper.d.ts +2 -0
  29. package/dist/src/util/runnerHelper.d.ts.map +1 -1
  30. package/dist/src/util/runnerHelper.js +10 -0
  31. package/dist/src/util/secretRedaction.d.ts +11 -0
  32. package/dist/src/util/secretRedaction.d.ts.map +1 -0
  33. package/dist/src/util/secretRedaction.js +45 -0
  34. package/package.json +1 -1
  35. package/src/command/MultiCommandCliHelpCommand.ts +7 -1
  36. package/src/runtime/command/CommandValidator.ts +8 -1
  37. package/src/runtime/parser.ts +22 -16
  38. package/src/runtime/runner.ts +5 -0
  39. package/src/runtime/values/ValuePopulationResult.ts +5 -0
  40. package/src/runtime/values/globalCommandValuePopulation.ts +12 -5
  41. package/src/runtime/values/subCommandValuePopulation.ts +47 -12
  42. package/src/service/configuration/ConfigurationServiceProvider.ts +18 -6
  43. package/src/util/configHelper.ts +7 -0
  44. package/src/util/envVarHelper.ts +19 -3
  45. package/src/util/helpHelper.ts +34 -13
  46. package/src/util/runnerHelper.ts +18 -0
  47. package/src/util/secretRedaction.ts +81 -0
@@ -8,11 +8,12 @@ function getKeySegment(segment) {
8
8
  }
9
9
  return keySegment;
10
10
  }
11
- function getSubCommandArgumentKeyPrefix(cliConfig, command, subCommandArgument) {
11
+ function getSubCommandArgumentKeyPrefix(cliConfig, command, subCommandArgument, groupCommand) {
12
12
  if (subCommandArgument.configurationKey) {
13
13
  return subCommandArgument.configurationKey;
14
14
  }
15
- return `${getKeySegment(cliConfig.name)}_${getKeySegment(command.name)}_${getKeySegment(subCommandArgument.name)}`;
15
+ const groupSegment = groupCommand ? `${getKeySegment(groupCommand.name)}_` : "";
16
+ return `${getKeySegment(cliConfig.name)}_${groupSegment}${getKeySegment(command.name)}_${getKeySegment(subCommandArgument.name)}`;
16
17
  }
17
18
  function getGlobalArgumentKeyPrefix(cliConfig, command, globalCommandArgument) {
18
19
  if (globalCommandArgument.configurationKey) {
@@ -146,13 +147,15 @@ function getOptionValuesFromEnvVars(envVarValues, potentialEnvVarNames, env, opt
146
147
  * @param cliConfig the {@link CLIConfig} to use for determining the default configuration key.
147
148
  * @param command the {@link SubCommand} to use for determining the default configuration key and containing
148
149
  * {@link Positional} and {@link Option} instances to populate values for.
150
+ * @param groupCommand the {@link GroupCommand} if the command is a group member, in which case the group name
151
+ * is an additional env var name segment.
149
152
  */
150
- export function getSubCommandValuesFromEnvVars(cliConfig, command) {
153
+ export function getSubCommandValuesFromEnvVars(cliConfig, command, groupCommand) {
151
154
  const envVarValues = Object.create(null);
152
155
  if (command.options) {
153
156
  for (const option of command.options) {
154
157
  const potentialEnvVarNames = Object.keys(process.env);
155
- const optionPrefix = getSubCommandArgumentKeyPrefix(cliConfig, command, option);
158
+ const optionPrefix = getSubCommandArgumentKeyPrefix(cliConfig, command, option, groupCommand);
156
159
  getOptionValuesFromEnvVars(envVarValues, potentialEnvVarNames, process.env ?? {}, option, optionPrefix);
157
160
  }
158
161
  }
@@ -163,7 +166,7 @@ export function getSubCommandValuesFromEnvVars(cliConfig, command) {
163
166
  positional.name === "prototype") {
164
167
  throw new Error(`Unsafe key name in use: ${positional.name}, CodeQL Rule ID: js/prototype-polluting-assignment`);
165
168
  }
166
- let envVarNamePrefix = getSubCommandArgumentKeyPrefix(cliConfig, command, positional);
169
+ let envVarNamePrefix = getSubCommandArgumentKeyPrefix(cliConfig, command, positional, groupCommand);
167
170
  if (positional.isVarargMultiple) {
168
171
  envVarNamePrefix = `${envVarNamePrefix}_`;
169
172
  const matches = Object.keys(process.env).filter((envVarName) => envVarName.startsWith(envVarNamePrefix));
@@ -19,7 +19,7 @@ export interface HelpSection {
19
19
  export declare function printHelpSections(printerService: PrinterService, tableGeneratorService: TableGeneratorService, sections: Array<HelpSection>): Promise<void>;
20
20
  export declare function getGlobalArgumentHelpEntry(cliConfig: CLIConfig, includeEnvVars: boolean, globalCommand: GlobalCommand): HelpEntry;
21
21
  export declare function getSubCommandArgumentsSyntax(subCommand: SubCommand): string;
22
- export declare function getCommandArgsHelpSections(cliConfig: CLIConfig, includeEnvVars: boolean, subCommand: SubCommand, isSingleCommandApp: boolean): Array<HelpSection>;
22
+ export declare function getCommandArgsHelpSections(cliConfig: CLIConfig, includeEnvVars: boolean, subCommand: SubCommand, isSingleCommandApp: boolean, groupCommand?: GroupCommand): Array<HelpSection>;
23
23
  export declare function getCommandExamplesHelpSections(printerService: PrinterService, context: Context, subCommand: SubCommand): Array<HelpSection>;
24
24
  export declare function getMultiCommandAppSyntax(context: Context, globalModifierCommands: ReadonlyArray<GlobalModifierCommand>, globalCommands: ReadonlyArray<GlobalCommand>, groupCommands: ReadonlyArray<GroupCommand>, subCommands: ReadonlyArray<SubCommand>): string;
25
25
  export declare function findPossibleCommandNames(commandName: string, groupCommands: ReadonlyArray<GroupCommand>, subCommands: ReadonlyArray<SubCommand>): string[];
@@ -1 +1 @@
1
- {"version":3,"file":"helpHelper.d.ts","sourceRoot":"","sources":["../../../src/util/helpHelper.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AAG7E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yCAAyC,CAAC;AAC1E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AAIvE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yCAAyC,CAAC;AACrF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yCAAyC,CAAC;AAM5E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAC;AAG9E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yCAAyC,CAAC;AAiCrF,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,cAAc,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;CAC5C;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;CACxC;AAsDD,wBAAsB,iBAAiB,CACrC,cAAc,EAAE,cAAc,EAC9B,qBAAqB,EAAE,qBAAqB,EAC5C,QAAQ,EAAE,KAAK,CAAC,WAAW,CAAC,GAC3B,OAAO,CAAC,IAAI,CAAC,CA2Cf;AA2BD,wBAAgB,0BAA0B,CACxC,SAAS,EAAE,SAAS,EACpB,cAAc,EAAE,OAAO,EACvB,aAAa,EAAE,aAAa,GAC3B,SAAS,CA6DX;AAED,wBAAgB,4BAA4B,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CA+C3E;AAkLD,wBAAgB,0BAA0B,CACxC,SAAS,EAAE,SAAS,EACpB,cAAc,EAAE,OAAO,EACvB,UAAU,EAAE,UAAU,EACtB,kBAAkB,EAAE,OAAO,GAC1B,KAAK,CAAC,WAAW,CAAC,CAoBpB;AACD,wBAAgB,8BAA8B,CAC5C,cAAc,EAAE,cAAc,EAC9B,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,UAAU,GACrB,KAAK,CAAC,WAAW,CAAC,CAgCpB;AAED,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,OAAO,EAChB,sBAAsB,EAAE,aAAa,CAAC,qBAAqB,CAAC,EAC5D,cAAc,EAAE,aAAa,CAAC,aAAa,CAAC,EAC5C,aAAa,EAAE,aAAa,CAAC,YAAY,CAAC,EAC1C,WAAW,EAAE,aAAa,CAAC,UAAU,CAAC,GACrC,MAAM,CA8IR;AAED,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,aAAa,CAAC,YAAY,CAAC,EAC1C,WAAW,EAAE,aAAa,CAAC,UAAU,CAAC,GACrC,MAAM,EAAE,CAkBV"}
1
+ {"version":3,"file":"helpHelper.d.ts","sourceRoot":"","sources":["../../../src/util/helpHelper.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AAG7E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yCAAyC,CAAC;AAC1E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AAIvE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yCAAyC,CAAC;AACrF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yCAAyC,CAAC;AAM5E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAC;AAG9E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yCAAyC,CAAC;AAiCrF,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,cAAc,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;CAC5C;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;CACxC;AAsDD,wBAAsB,iBAAiB,CACrC,cAAc,EAAE,cAAc,EAC9B,qBAAqB,EAAE,qBAAqB,EAC5C,QAAQ,EAAE,KAAK,CAAC,WAAW,CAAC,GAC3B,OAAO,CAAC,IAAI,CAAC,CA2Cf;AA2BD,wBAAgB,0BAA0B,CACxC,SAAS,EAAE,SAAS,EACpB,cAAc,EAAE,OAAO,EACvB,aAAa,EAAE,aAAa,GAC3B,SAAS,CA6DX;AAED,wBAAgB,4BAA4B,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CA+C3E;AA8LD,wBAAgB,0BAA0B,CACxC,SAAS,EAAE,SAAS,EACpB,cAAc,EAAE,OAAO,EACvB,UAAU,EAAE,UAAU,EACtB,kBAAkB,EAAE,OAAO,EAC3B,YAAY,CAAC,EAAE,YAAY,GAC1B,KAAK,CAAC,WAAW,CAAC,CA4BpB;AACD,wBAAgB,8BAA8B,CAC5C,cAAc,EAAE,cAAc,EAC9B,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,UAAU,GACrB,KAAK,CAAC,WAAW,CAAC,CAgCpB;AAED,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,OAAO,EAChB,sBAAsB,EAAE,aAAa,CAAC,qBAAqB,CAAC,EAC5D,cAAc,EAAE,aAAa,CAAC,aAAa,CAAC,EAC5C,aAAa,EAAE,aAAa,CAAC,YAAY,CAAC,EAC1C,WAAW,EAAE,aAAa,CAAC,UAAU,CAAC,GACrC,MAAM,CA8IR;AAED,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,aAAa,CAAC,YAAY,CAAC,EAC1C,WAAW,EAAE,aAAa,CAAC,UAAU,CAAC,GACrC,MAAM,EAAE,CAkBV"}
@@ -216,7 +216,7 @@ export function getSubCommandArgumentsSyntax(subCommand) {
216
216
  });
217
217
  return syntax;
218
218
  }
219
- function getOptionHelpEntry(cliConfig, includeEnvVars, command, option, subEntryIndent = 0, optionAncestry = []) {
219
+ function getOptionHelpEntry(cliConfig, includeEnvVars, command, option, subEntryIndent = 0, optionAncestry = [], groupCommand) {
220
220
  let subEntries;
221
221
  let optionCharacters = "--";
222
222
  let aliasCharacter = "-";
@@ -230,10 +230,7 @@ function getOptionHelpEntry(cliConfig, includeEnvVars, command, option, subEntry
230
230
  if (option.type === ComplexValueTypeName.COMPLEX) {
231
231
  subEntries = [];
232
232
  option.properties.forEach((property) => {
233
- subEntries.push(getOptionHelpEntry(cliConfig, includeEnvVars, command, property, newSubEntryIndent, [
234
- ...optionAncestry,
235
- option,
236
- ]));
233
+ subEntries.push(getOptionHelpEntry(cliConfig, includeEnvVars, command, property, newSubEntryIndent, [...optionAncestry, option], groupCommand));
237
234
  });
238
235
  }
239
236
  else {
@@ -279,10 +276,7 @@ function getOptionHelpEntry(cliConfig, includeEnvVars, command, option, subEntry
279
276
  notesItems.push("array");
280
277
  }
281
278
  if (includeEnvVars && option.type !== ComplexValueTypeName.COMPLEX) {
282
- const configurationKey = getSubCommandArgumentConfigurationKey(cliConfig, command, [
283
- ...optionAncestry,
284
- option,
285
- ]);
279
+ const configurationKey = getSubCommandArgumentConfigurationKey(cliConfig, command, [...optionAncestry, option], groupCommand);
286
280
  if (configurationKey !== undefined) {
287
281
  notesItems.push(`env var: ${configurationKey}`);
288
282
  }
@@ -303,7 +297,7 @@ function getOptionHelpEntry(cliConfig, includeEnvVars, command, option, subEntry
303
297
  helpSubEntries: subEntries,
304
298
  };
305
299
  }
306
- function getPositionalHelpEntry(cliConfig, includeEnvVars, command, positional) {
300
+ function getPositionalHelpEntry(cliConfig, includeEnvVars, command, positional, groupCommand) {
307
301
  const notesItems = [];
308
302
  if (positional.allowableValues !== undefined && positional.allowableValues.length > 0) {
309
303
  notesItems.push(`valid values: ${userInputList(positional.allowableValues, "|")}`);
@@ -341,9 +335,7 @@ function getPositionalHelpEntry(cliConfig, includeEnvVars, command, positional)
341
335
  notesItems.push("array");
342
336
  }
343
337
  if (includeEnvVars) {
344
- const configurationKey = getSubCommandArgumentConfigurationKey(cliConfig, command, [
345
- positional,
346
- ]);
338
+ const configurationKey = getSubCommandArgumentConfigurationKey(cliConfig, command, [positional], groupCommand);
347
339
  if (configurationKey !== undefined) {
348
340
  notesItems.push(`env var: ${configurationKey}`);
349
341
  }
@@ -362,7 +354,7 @@ function getPositionalHelpEntry(cliConfig, includeEnvVars, command, positional)
362
354
  description,
363
355
  };
364
356
  }
365
- export function getCommandArgsHelpSections(cliConfig, includeEnvVars, subCommand, isSingleCommandApp) {
357
+ export function getCommandArgsHelpSections(cliConfig, includeEnvVars, subCommand, isSingleCommandApp, groupCommand) {
366
358
  const helpSections = [];
367
359
  if (subCommand.options.length > 0 || subCommand.positionals.length > 0) {
368
360
  const argumentsSection = {
@@ -370,10 +362,10 @@ export function getCommandArgsHelpSections(cliConfig, includeEnvVars, subCommand
370
362
  helpEntries: [],
371
363
  };
372
364
  subCommand.options.forEach((option) => {
373
- argumentsSection.helpEntries.push(getOptionHelpEntry(cliConfig, includeEnvVars, subCommand, option));
365
+ argumentsSection.helpEntries.push(getOptionHelpEntry(cliConfig, includeEnvVars, subCommand, option, 0, [], groupCommand));
374
366
  });
375
367
  subCommand.positionals.forEach((positional) => {
376
- argumentsSection.helpEntries.push(getPositionalHelpEntry(cliConfig, includeEnvVars, subCommand, positional));
368
+ argumentsSection.helpEntries.push(getPositionalHelpEntry(cliConfig, includeEnvVars, subCommand, positional, groupCommand));
377
369
  });
378
370
  helpSections.push(argumentsSection);
379
371
  }
@@ -1,3 +1,4 @@
1
+ import type { SecretAwareParseResult } from "../runtime/parser.ts";
1
2
  import type { ParseResult } from "../runtime/parser.ts";
2
3
  import type { Context } from "@flowscripter/dynamic-cli-framework-api";
3
4
  import type { CommandRegistry } from "@flowscripter/dynamic-cli-framework-api";
@@ -7,4 +8,5 @@ export declare function printRuntimeError(context: Context, err: Error): Promise
7
8
  export declare function printNoCommandSpecifiedError(context: Context): Promise<void>;
8
9
  export declare function printNoCommandRecognisedError(context: Context, commandRegistry: CommandRegistry, availableArgs: ReadonlyArray<string>): Promise<void>;
9
10
  export declare function printUnusedArgsWarning(context: Context, overallUnusedArgs: ReadonlyArray<string>): Promise<void>;
11
+ export declare function printSecretArgsWarning(context: Context, parseResult: SecretAwareParseResult): Promise<void>;
10
12
  //# sourceMappingURL=runnerHelper.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"runnerHelper.d.ts","sourceRoot":"","sources":["../../../src/util/runnerHelper.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AAEvE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAY/E,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,WAAW,EACxB,gBAAgB,UAAQ,iBAiBzB;AAED,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,WAAW,EACxB,GAAG,EAAE,KAAK,EACV,gBAAgB,UAAQ,iBAiBzB;AAED,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,iBAMnE;AAED,wBAAsB,4BAA4B,CAAC,OAAO,EAAE,OAAO,iBAGlE;AAED,wBAAsB,6BAA6B,CACjD,OAAO,EAAE,OAAO,EAChB,eAAe,EAAE,eAAe,EAChC,aAAa,EAAE,aAAa,CAAC,MAAM,CAAC,iBAgCrC;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,OAAO,EAChB,iBAAiB,EAAE,aAAa,CAAC,MAAM,CAAC,iBAQzC"}
1
+ {"version":3,"file":"runnerHelper.d.ts","sourceRoot":"","sources":["../../../src/util/runnerHelper.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AAEvE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAY/E,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,WAAW,EACxB,gBAAgB,UAAQ,iBAiBzB;AAED,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,WAAW,EACxB,GAAG,EAAE,KAAK,EACV,gBAAgB,UAAQ,iBAiBzB;AAED,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,iBAMnE;AAED,wBAAsB,4BAA4B,CAAC,OAAO,EAAE,OAAO,iBAGlE;AAED,wBAAsB,6BAA6B,CACjD,OAAO,EAAE,OAAO,EAChB,eAAe,EAAE,eAAe,EAChC,aAAa,EAAE,aAAa,CAAC,MAAM,CAAC,iBAgCrC;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,OAAO,EAChB,iBAAiB,EAAE,aAAa,CAAC,MAAM,CAAC,iBAQzC;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,sBAAsB,iBAapC"}
@@ -78,3 +78,13 @@ export async function printUnusedArgsWarning(context, overallUnusedArgs) {
78
78
  await printerService.warn(`Unused args: ${overallUnusedArgs.join(" ")}\n\n`, Icon.ALERT);
79
79
  }
80
80
  }
81
+ export async function printSecretArgsWarning(context, parseResult) {
82
+ const secretArgumentNames = parseResult.secretArgumentNames;
83
+ if (!secretArgumentNames || secretArgumentNames.length === 0) {
84
+ return;
85
+ }
86
+ const printerService = context.getServiceById(PRINTER_SERVICE_ID);
87
+ await printerService.warn(`SECRET value passed as a plain argument: ${secretArgumentNames.join(", ")}. ` +
88
+ "This is not recommended as it may be exposed via shell history and process listings. " +
89
+ "Use an environment variable, a prompt or a configuration file default instead.\n\n", Icon.ALERT);
90
+ }
@@ -0,0 +1,11 @@
1
+ import { type GlobalCommand, type PopulatedSingleValueType, type PopulatedValues, type SubCommand } from "@flowscripter/dynamic-cli-framework-api";
2
+ export declare const REDACTED = "***";
3
+ /**
4
+ * Copy of the provided values suitable for logging, with the values of any SECRET options or positionals replaced.
5
+ */
6
+ export declare function redactSubCommandValues<T extends PopulatedValues | undefined>(subCommand: SubCommand, values: T): T;
7
+ /**
8
+ * Copy of the provided value suitable for logging, with the value replaced if the global command argument is a SECRET.
9
+ */
10
+ export declare function redactGlobalCommandValue(globalCommand: GlobalCommand, value: PopulatedSingleValueType): PopulatedSingleValueType;
11
+ //# sourceMappingURL=secretRedaction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secretRedaction.d.ts","sourceRoot":"","sources":["../../../src/util/secretRedaction.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,aAAa,EAElB,KAAK,wBAAwB,EAC7B,KAAK,eAAe,EAEpB,KAAK,UAAU,EAChB,MAAM,yCAAyC,CAAC;AAGjD,eAAO,MAAM,QAAQ,QAAQ,CAAC;AA4C9B;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,eAAe,GAAG,SAAS,EAC1E,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,CAAC,GACR,CAAC,CAKH;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,aAAa,EAAE,aAAa,EAC5B,KAAK,EAAE,wBAAwB,GAC9B,wBAAwB,CAK1B"}
@@ -0,0 +1,45 @@
1
+ import { ValueTypeName, } from "@flowscripter/dynamic-cli-framework-api";
2
+ import { isComplexOption } from "../runtime/argument/ArgumentTypeGuards.js";
3
+ export const REDACTED = "***";
4
+ function redactValue(definition, value) {
5
+ if (value === undefined || value === null) {
6
+ return value;
7
+ }
8
+ if (isComplexOption(definition)) {
9
+ const properties = definition.properties;
10
+ if (Array.isArray(value)) {
11
+ return value.map((v) => redactValue(definition, v));
12
+ }
13
+ return redactDefinedValues(properties, [], value);
14
+ }
15
+ if (definition.type === ValueTypeName.SECRET) {
16
+ return Array.isArray(value) ? value.map(() => REDACTED) : REDACTED;
17
+ }
18
+ return value;
19
+ }
20
+ function redactDefinedValues(options, positionals, values) {
21
+ const redacted = {};
22
+ for (const [name, value] of Object.entries(values)) {
23
+ const definition = options.find((o) => o.name === name) ?? positionals.find((p) => p.name === name);
24
+ redacted[name] = definition ? redactValue(definition, value) : value;
25
+ }
26
+ return redacted;
27
+ }
28
+ /**
29
+ * Copy of the provided values suitable for logging, with the values of any SECRET options or positionals replaced.
30
+ */
31
+ export function redactSubCommandValues(subCommand, values) {
32
+ if (values === undefined) {
33
+ return values;
34
+ }
35
+ return redactDefinedValues(subCommand.options, subCommand.positionals, values);
36
+ }
37
+ /**
38
+ * Copy of the provided value suitable for logging, with the value replaced if the global command argument is a SECRET.
39
+ */
40
+ export function redactGlobalCommandValue(globalCommand, value) {
41
+ if (value !== undefined && globalCommand.argument?.type === ValueTypeName.SECRET) {
42
+ return REDACTED;
43
+ }
44
+ return value;
45
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowscripter/dynamic-cli-framework",
3
- "version": "9.1.3",
3
+ "version": "10.1.0",
4
4
  "description": "A framework for developing CLI applications which supports dynamic discovery and installation of new commands",
5
5
  "keywords": [
6
6
  "bun",
@@ -288,7 +288,13 @@ abstract class MultiCommandCliAbstractHelpCommand {
288
288
  });
289
289
 
290
290
  helpSections.push(
291
- ...getCommandArgsHelpSections(context.cliConfig, this.#includeEnvVars, subCommand, false),
291
+ ...getCommandArgsHelpSections(
292
+ context.cliConfig,
293
+ this.#includeEnvVars,
294
+ subCommand,
295
+ false,
296
+ groupCommand,
297
+ ),
292
298
  );
293
299
 
294
300
  helpSections.push(...getCommandExamplesHelpSections(printerService, context, subCommand));
@@ -165,6 +165,7 @@ function validateSubCommandArgument(subCommandArgument: SubCommandArgument): voi
165
165
  export default class CommandValidator {
166
166
  readonly #cliConfig: CLIConfig;
167
167
  readonly #configurationKeys: Array<string> = [];
168
+ #currentGroupCommand: GroupCommand | undefined;
168
169
 
169
170
  public constructor(cliConfig: CLIConfig) {
170
171
  this.#cliConfig = cliConfig;
@@ -265,7 +266,12 @@ export default class CommandValidator {
265
266
  );
266
267
  }
267
268
  subCommandNames.push(subCommand.name);
268
- this.#validateSubCommand(subCommand);
269
+ this.#currentGroupCommand = groupCommand;
270
+ try {
271
+ this.#validateSubCommand(subCommand);
272
+ } finally {
273
+ this.#currentGroupCommand = undefined;
274
+ }
269
275
  });
270
276
  }
271
277
 
@@ -365,6 +371,7 @@ export default class CommandValidator {
365
371
  this.#cliConfig,
366
372
  command,
367
373
  argumentAncestry,
374
+ this.#currentGroupCommand,
368
375
  );
369
376
 
370
377
  if (configurationKey !== undefined) {
@@ -13,6 +13,7 @@ import {
13
13
  validatePositionalValue,
14
14
  } from "./values/argumentValueValidation.ts";
15
15
  import getLogger from "../util/logger.ts";
16
+ import { redactGlobalCommandValue, redactSubCommandValues } from "../util/secretRedaction.ts";
16
17
  import populateSubCommandValues from "./values/subCommandValuePopulation.ts";
17
18
  import type { GlobalCommand } from "@flowscripter/dynamic-cli-framework-api";
18
19
  import type { SubCommand } from "@flowscripter/dynamic-cli-framework-api";
@@ -21,6 +22,13 @@ const logger = getLogger("parser");
21
22
 
22
23
  export type { ParseResult } from "@flowscripter/dynamic-cli-framework-api";
23
24
 
25
+ /**
26
+ * A {@link ParseResult} which also reports any SECRET arguments supplied as plain command line arguments.
27
+ */
28
+ export type SecretAwareParseResult = ParseResult & {
29
+ readonly secretArgumentNames?: ReadonlyArray<string>;
30
+ };
31
+
24
32
  /**
25
33
  * Parse the arguments for the specified {@link CommandClause} assuming it contains a {@link SubCommand}.
26
34
  *
@@ -30,7 +38,7 @@ export type { ParseResult } from "@flowscripter/dynamic-cli-framework-api";
30
38
  export function parseSubCommandClause(
31
39
  commandClause: CommandClause,
32
40
  defaultValues?: PopulatedValues,
33
- ): ParseResult {
41
+ ): SecretAwareParseResult {
34
42
  const command = commandClause.command as SubCommand;
35
43
  const potentialArgs = commandClause.potentialArgs;
36
44
  const groupCommand = commandClause.groupCommand;
@@ -48,11 +56,8 @@ export function parseSubCommandClause(
48
56
  };
49
57
  }
50
58
 
51
- const { populatedArgumentValues, unusedArgs, invalidArgument } = populateSubCommandValues(
52
- command,
53
- potentialArgs,
54
- defaultValues,
55
- );
59
+ const { populatedArgumentValues, unusedArgs, invalidArgument, secretArgumentNames } =
60
+ populateSubCommandValues(command, potentialArgs, defaultValues);
56
61
 
57
62
  if (invalidArgument) {
58
63
  invalidArguments.push(invalidArgument);
@@ -73,7 +78,7 @@ export function parseSubCommandClause(
73
78
 
74
79
  logger.debug(
75
80
  "Command arguments after options validated: %O with invalid args: %O",
76
- populatedArgumentValues,
81
+ redactSubCommandValues(command, populatedArgumentValues),
77
82
  invalidArguments,
78
83
  );
79
84
 
@@ -91,11 +96,12 @@ export function parseSubCommandClause(
91
96
  });
92
97
  logger.debug(
93
98
  "Command arguments after positionals validated: %O with invalid args: %O",
94
- populatedArgumentValues,
99
+ redactSubCommandValues(command, populatedArgumentValues),
95
100
  invalidArguments,
96
101
  );
97
102
  }
98
103
  }
104
+ const secrets = secretArgumentNames ? { secretArgumentNames } : {};
99
105
  if (groupCommand) {
100
106
  return {
101
107
  command,
@@ -103,6 +109,7 @@ export function parseSubCommandClause(
103
109
  groupCommand,
104
110
  invalidArguments,
105
111
  unusedArgs,
112
+ ...secrets,
106
113
  };
107
114
  }
108
115
  return {
@@ -110,6 +117,7 @@ export function parseSubCommandClause(
110
117
  populatedArgumentValues,
111
118
  invalidArguments,
112
119
  unusedArgs: unusedArgs,
120
+ ...secrets,
113
121
  };
114
122
  }
115
123
 
@@ -122,7 +130,7 @@ export function parseSubCommandClause(
122
130
  export function parseGlobalCommandClause(
123
131
  commandClause: CommandClause,
124
132
  defaultValue?: PopulatedSingleValueType,
125
- ): ParseResult {
133
+ ): SecretAwareParseResult {
126
134
  const command = commandClause.command as GlobalCommand;
127
135
  const potentialArgs = commandClause.potentialArgs;
128
136
  const invalidArguments: Array<InvalidArgument> = [];
@@ -137,11 +145,8 @@ export function parseGlobalCommandClause(
137
145
  };
138
146
  }
139
147
 
140
- let { populatedArgumentValue, unusedArgs, invalidArgument } = populateGlobalCommandValue(
141
- command,
142
- potentialArgs,
143
- defaultValue,
144
- );
148
+ let { populatedArgumentValue, unusedArgs, invalidArgument, secretArgumentNames } =
149
+ populateGlobalCommandValue(command, potentialArgs, defaultValue);
145
150
 
146
151
  if (invalidArgument) {
147
152
  invalidArguments.push(invalidArgument);
@@ -150,7 +155,7 @@ export function parseGlobalCommandClause(
150
155
  logger.debug(
151
156
  "Command arguments for command: %s after value population: %O with invalid args: %O and unused args: %O",
152
157
  command.name,
153
- populatedArgumentValue,
158
+ redactGlobalCommandValue(command, populatedArgumentValue),
154
159
  invalidArguments,
155
160
  unusedArgs,
156
161
  );
@@ -170,7 +175,7 @@ export function parseGlobalCommandClause(
170
175
  logger.debug(
171
176
  "Command arguments for command: %s after value validation: %O with invalid args: %O",
172
177
  command.name,
173
- populatedArgumentValue,
178
+ redactGlobalCommandValue(command, populatedArgumentValue),
174
179
  invalidArguments,
175
180
  );
176
181
 
@@ -179,5 +184,6 @@ export function parseGlobalCommandClause(
179
184
  populatedArgumentValues: populatedArgumentValue,
180
185
  invalidArguments,
181
186
  unusedArgs,
187
+ ...(secretArgumentNames && { secretArgumentNames }),
182
188
  };
183
189
  }
@@ -15,6 +15,7 @@ import {
15
15
  printNoCommandRecognisedError,
16
16
  printNoCommandSpecifiedError,
17
17
  printParseResultError,
18
+ printSecretArgsWarning,
18
19
  printUnusedArgsWarning,
19
20
  } from "../util/runnerHelper.ts";
20
21
  import { scanForGlobalModifierCommandClauses, scanForNonModifierCommandClause } from "./scanner.ts";
@@ -198,6 +199,7 @@ async function findAndExecuteGlobalModifierCommands(
198
199
  globalModifierCommandClause,
199
200
  defaultArgumentValues as PopulatedSingleValueType,
200
201
  );
202
+ await printSecretArgsWarning(context, parseResult);
201
203
 
202
204
  if (parseResult.invalidArguments.length > 0) {
203
205
  await printParseResultError(context, parseResult);
@@ -314,6 +316,7 @@ async function findAndExecuteNonModifierCommand(
314
316
  ? await configurationServiceProvider.getDefaultArgumentValues(
315
317
  context.cliConfig,
316
318
  scanResult.nonModifierCommandClause.command,
319
+ scanResult.nonModifierCommandClause.groupCommand,
317
320
  )
318
321
  : undefined;
319
322
 
@@ -328,6 +331,7 @@ async function findAndExecuteNonModifierCommand(
328
331
  defaultArgumentValues as PopulatedValues,
329
332
  );
330
333
  }
334
+ await printSecretArgsWarning(context, parseResult);
331
335
 
332
336
  if (parseResult.invalidArguments.length > 0) {
333
337
  const prompted = await attemptPromptForMissingArguments(parseResult, context);
@@ -463,6 +467,7 @@ async function findAndExecuteDefaultNonModifierCommand(
463
467
  defaultArgumentValues as PopulatedValues,
464
468
  );
465
469
  }
470
+ await printSecretArgsWarning(context, parseResult);
466
471
 
467
472
  // add any unused args back to the list of still unused args
468
473
  if (parseResult.unusedArgs.length > 0) {
@@ -17,6 +17,11 @@ export interface ValuePopulationResult {
17
17
  * Any invalid argument discovered while populating values.
18
18
  */
19
19
  readonly invalidArgument?: InvalidArgument;
20
+
21
+ /**
22
+ * Names of any SECRET arguments whose values were supplied as plain command line arguments.
23
+ */
24
+ readonly secretArgumentNames?: ReadonlyArray<string>;
20
25
  }
21
26
 
22
27
  /**
@@ -3,6 +3,7 @@ import type { GlobalCommandValuePopulationResult } from "./ValuePopulationResult
3
3
  import { ValueTypeName } from "@flowscripter/dynamic-cli-framework-api";
4
4
  import type { PopulatedSingleValueType } from "@flowscripter/dynamic-cli-framework-api";
5
5
  import getLogger from "../../util/logger.ts";
6
+ import { REDACTED } from "../../util/secretRedaction.ts";
6
7
  import { InvalidArgumentReason } from "@flowscripter/dynamic-cli-framework-api";
7
8
 
8
9
  const logger = getLogger("globalCommandValuePopulation");
@@ -19,16 +20,21 @@ export default function populateGlobalCommandValue(
19
20
  potentialArgs: ReadonlyArray<string>,
20
21
  defaultValue: PopulatedSingleValueType,
21
22
  ): GlobalCommandValuePopulationResult {
23
+ const argument = globalCommand.argument!;
24
+ const isSecret = argument.type === ValueTypeName.SECRET;
22
25
  logger.debug(() => {
23
- const message = `Populating value for global command: '${globalCommand.name}' using potential args: ${potentialArgs.join(
24
- " ",
25
- )}`;
26
+ const message = `Populating value for global command: '${globalCommand.name}' using potential args: ${(isSecret
27
+ ? potentialArgs
28
+ .slice(0, 1)
29
+ .map(() => REDACTED)
30
+ .concat(potentialArgs.slice(1))
31
+ : potentialArgs
32
+ ).join(" ")}`;
26
33
  if (defaultValue !== undefined) {
27
- return `${message} and configured value: '${defaultValue}'`;
34
+ return `${message} and configured value: '${isSecret ? REDACTED : defaultValue}'`;
28
35
  }
29
36
  return message;
30
37
  });
31
- const argument = globalCommand.argument!;
32
38
 
33
39
  // default to defined argument default value
34
40
  let populatedArgumentValue = argument.defaultValue;
@@ -83,5 +89,6 @@ export default function populateGlobalCommandValue(
83
89
  return {
84
90
  populatedArgumentValue,
85
91
  unusedArgs,
92
+ ...(isSecret && potentialArgs.length > 0 && { secretArgumentNames: [globalCommand.name] }),
86
93
  };
87
94
  }
@@ -8,6 +8,7 @@ import {
8
8
  type PopulatedValueType,
9
9
  } from "@flowscripter/dynamic-cli-framework-api";
10
10
  import getLogger from "../../util/logger.ts";
11
+ import { REDACTED, redactSubCommandValues } from "../../util/secretRedaction.ts";
11
12
  import argumentValueMerge from "./argumentValueMerge.ts";
12
13
  import type { SubCommandValuePopulationResult } from "./ValuePopulationResult.ts";
13
14
  import type { ComplexOption } from "@flowscripter/dynamic-cli-framework-api";
@@ -87,6 +88,20 @@ class ParseContext {
87
88
  */
88
89
  currentPositionalIndex;
89
90
 
91
+ /**
92
+ * Set when the most recent argument supplied a value for a SECRET option or positional.
93
+ */
94
+ secretArgument?: { name: string; inline: boolean };
95
+
96
+ /**
97
+ * Return and clear the {@link secretArgument} recorded by the most recent {@link parse}.
98
+ */
99
+ takeSecretArgument(): { name: string; inline: boolean } | undefined {
100
+ const secretArgument = this.secretArgument;
101
+ this.secretArgument = undefined;
102
+ return secretArgument;
103
+ }
104
+
90
105
  /**
91
106
  * Use to store lazily created map of option paths e.g. --alpha.b.g and -a.b.gamma for {@link ComplexOption} or {@link Option} instances.
92
107
  */
@@ -351,7 +366,10 @@ class ParseContext {
351
366
  * @return `false` if there is a problem populating the value, in which case the {@link state} will be set to
352
367
  * {@link ParseState.ERROR} and {@link invalidArgument} will be populated.
353
368
  */
354
- setOptionValue(value: SingleValueType): boolean {
369
+ setOptionValue(value: SingleValueType, inline = false): boolean {
370
+ if (this.currentOption!.type === ValueTypeName.SECRET) {
371
+ this.secretArgument = { name: this.currentOption!.name, inline };
372
+ }
355
373
  let optionValue: Array<PopulatedValues> | PopulatedValues | PopulatedValueType =
356
374
  this.populatedArgumentValues;
357
375
  const optionPath = this.currentOptionPath as Array<string | number>;
@@ -439,6 +457,10 @@ class ParseContext {
439
457
  const positional = this.subCommand.positionals[this.currentPositionalIndex]!;
440
458
  const currentValue = this.populatedArgumentValues[positional.name];
441
459
 
460
+ if (positional.type === ValueTypeName.SECRET) {
461
+ this.secretArgument = { name: positional.name, inline: false };
462
+ }
463
+
442
464
  // if we haven't populated the argument value before...
443
465
  if (currentValue === undefined) {
444
466
  if (positional.isVarargMultiple) {
@@ -546,7 +568,7 @@ class ParseContext {
546
568
  // try to set the value if we have one
547
569
  if (potentialOptionValue !== undefined) {
548
570
  // set the value
549
- this.setOptionValue(potentialOptionValue);
571
+ this.setOptionValue(potentialOptionValue, true);
550
572
 
551
573
  // return regardless
552
574
  return;
@@ -587,24 +609,23 @@ export default function populateSubCommandValues(
587
609
  potentialArgs: ReadonlyArray<string>,
588
610
  defaultValues: PopulatedValues | undefined,
589
611
  ): SubCommandValuePopulationResult {
590
- logger.debug(() => {
591
- const message = `Populating values for sub-command: '${subCommand.name}' using potential args: ${potentialArgs.join(
592
- " ",
593
- )}`;
594
- if (defaultValues !== undefined) {
595
- return `${message} and configured values: ${JSON.stringify(defaultValues)}`;
596
- }
597
- return message;
598
- });
599
-
600
612
  const parseContext = new ParseContext(subCommand);
601
613
  const unusedArgs: Array<string> = [];
614
+ const loggedArgs = [...potentialArgs];
615
+ const secretArgumentNames = new Set<string>();
602
616
 
603
617
  for (let i = 0; i < potentialArgs.length; i++) {
604
618
  const potentialArg = potentialArgs[i]!;
605
619
 
606
620
  // parse the next arg
607
621
  parseContext.parse(potentialArg);
622
+ const secretArgument = parseContext.takeSecretArgument();
623
+ if (secretArgument) {
624
+ secretArgumentNames.add(secretArgument.name);
625
+ loggedArgs[i] = secretArgument.inline
626
+ ? `${potentialArg.slice(0, potentialArg.indexOf("=") + 1)}${REDACTED}`
627
+ : REDACTED;
628
+ }
608
629
 
609
630
  // check if we had a parsing error, record the error, flush the remaining args and stop parsing
610
631
  if (parseContext.state === ParseState.ERROR) {
@@ -620,6 +641,18 @@ export default function populateSubCommandValues(
620
641
  }
621
642
  }
622
643
 
644
+ logger.debug(() => {
645
+ const message = `Populated values for sub-command: '${subCommand.name}' using potential args: ${loggedArgs.join(
646
+ " ",
647
+ )}`;
648
+ if (defaultValues !== undefined) {
649
+ return `${message} and configured values: ${JSON.stringify(
650
+ redactSubCommandValues(subCommand, defaultValues),
651
+ )}`;
652
+ }
653
+ return message;
654
+ });
655
+
623
656
  // Check if the last arg specified an option but no value
624
657
  if (parseContext.state === ParseState.OPTION_VALUE_EXPECTED) {
625
658
  // special case if the last arg was for a boolean option where the value was implicitly `true`
@@ -639,6 +672,7 @@ export default function populateSubCommandValues(
639
672
  populatedArgumentValues: parseContext.populatedArgumentValues,
640
673
  unusedArgs: unusedArgs,
641
674
  invalidArgument: parseContext.invalidArgument,
675
+ ...(secretArgumentNames.size > 0 && { secretArgumentNames: [...secretArgumentNames] }),
642
676
  };
643
677
  }
644
678
 
@@ -651,5 +685,6 @@ export default function populateSubCommandValues(
651
685
  return {
652
686
  populatedArgumentValues,
653
687
  unusedArgs: unusedArgs,
688
+ ...(secretArgumentNames.size > 0 && { secretArgumentNames: [...secretArgumentNames] }),
654
689
  };
655
690
  }