@flowscripter/dynamic-cli-framework 6.0.0 → 7.0.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 (124) hide show
  1. package/README.md +2 -0
  2. package/dist/index.d.ts +10 -3
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +7 -2
  5. package/dist/src/cli/BaseCLI.d.ts +8 -0
  6. package/dist/src/cli/BaseCLI.d.ts.map +1 -1
  7. package/dist/src/cli/BaseCLI.js +37 -4
  8. package/dist/src/launcher.d.ts +3 -3
  9. package/dist/src/launcher.d.ts.map +1 -1
  10. package/dist/src/launcher.js +6 -4
  11. package/dist/src/runtime/lifecycle/PriorityTaskList.d.ts +22 -0
  12. package/dist/src/runtime/lifecycle/PriorityTaskList.d.ts.map +1 -0
  13. package/dist/src/runtime/lifecycle/PriorityTaskList.js +23 -0
  14. package/dist/src/runtime/runner.d.ts +15 -7
  15. package/dist/src/runtime/runner.d.ts.map +1 -1
  16. package/dist/src/runtime/runner.js +82 -53
  17. package/dist/src/service/configuration/ConfigurationServiceProvider.d.ts +26 -82
  18. package/dist/src/service/configuration/ConfigurationServiceProvider.d.ts.map +1 -1
  19. package/dist/src/service/configuration/ConfigurationServiceProvider.js +68 -163
  20. package/dist/src/service/configuration/DefaultKeyValueService.d.ts +6 -3
  21. package/dist/src/service/configuration/DefaultKeyValueService.d.ts.map +1 -1
  22. package/dist/src/service/configuration/DefaultKeyValueService.js +7 -24
  23. package/dist/src/service/configuration/DefaultSecretService.d.ts +1 -3
  24. package/dist/src/service/configuration/DefaultSecretService.d.ts.map +1 -1
  25. package/dist/src/service/configuration/DefaultSecretService.js +2 -10
  26. package/dist/src/service/configuration/KeyValueServiceProvider.d.ts +105 -0
  27. package/dist/src/service/configuration/KeyValueServiceProvider.d.ts.map +1 -0
  28. package/dist/src/service/configuration/KeyValueServiceProvider.js +201 -0
  29. package/dist/src/service/configuration/command/ConfigCommand.d.ts +4 -5
  30. package/dist/src/service/configuration/command/ConfigCommand.d.ts.map +1 -1
  31. package/dist/src/service/configuration/command/ConfigCommand.js +7 -6
  32. package/dist/src/service/configuration/command/DumpConfigCommand.d.ts +7 -3
  33. package/dist/src/service/configuration/command/DumpConfigCommand.d.ts.map +1 -1
  34. package/dist/src/service/configuration/command/DumpConfigCommand.js +10 -5
  35. package/dist/src/service/fetch/DefaultFetchService.d.ts +2 -2
  36. package/dist/src/service/fetch/DefaultFetchService.d.ts.map +1 -1
  37. package/dist/src/service/fetch/DefaultFetchService.js +17 -12
  38. package/dist/src/service/fetch/FetchServiceProvider.d.ts.map +1 -1
  39. package/dist/src/service/fetch/FetchServiceProvider.js +2 -3
  40. package/dist/src/service/printer/PrinterServiceProvider.d.ts.map +1 -1
  41. package/dist/src/service/printer/PrinterServiceProvider.js +11 -7
  42. package/dist/src/service/printer/command/DarkModeCommand.d.ts +3 -5
  43. package/dist/src/service/printer/command/DarkModeCommand.d.ts.map +1 -1
  44. package/dist/src/service/printer/command/DarkModeCommand.js +6 -6
  45. package/dist/src/service/printer/command/LogLevelCommand.d.ts +3 -5
  46. package/dist/src/service/printer/command/LogLevelCommand.d.ts.map +1 -1
  47. package/dist/src/service/printer/command/LogLevelCommand.js +9 -9
  48. package/dist/src/service/printer/command/NoColorCommand.d.ts +3 -5
  49. package/dist/src/service/printer/command/NoColorCommand.d.ts.map +1 -1
  50. package/dist/src/service/printer/command/NoColorCommand.js +6 -6
  51. package/dist/src/service/printer/command/NoHyperlinksCommand.d.ts +2 -4
  52. package/dist/src/service/printer/command/NoHyperlinksCommand.d.ts.map +1 -1
  53. package/dist/src/service/printer/command/NoHyperlinksCommand.js +5 -5
  54. package/dist/src/service/prompter/PrompterServiceProvider.js +1 -1
  55. package/dist/src/service/prompter/command/NoPromptCommand.d.ts +2 -4
  56. package/dist/src/service/prompter/command/NoPromptCommand.d.ts.map +1 -1
  57. package/dist/src/service/prompter/command/NoPromptCommand.js +5 -5
  58. package/dist/src/service/shutdown/DefaultShutdownService.d.ts +4 -3
  59. package/dist/src/service/shutdown/DefaultShutdownService.d.ts.map +1 -1
  60. package/dist/src/service/shutdown/DefaultShutdownService.js +4 -3
  61. package/dist/src/service/shutdown/ShutdownServiceProvider.js +2 -2
  62. package/dist/src/service/spawn/DefaultSpawnService.d.ts +2 -2
  63. package/dist/src/service/spawn/DefaultSpawnService.d.ts.map +1 -1
  64. package/dist/src/service/spawn/DefaultSpawnService.js +17 -14
  65. package/dist/src/service/spawn/SpawnServiceProvider.d.ts.map +1 -1
  66. package/dist/src/service/spawn/SpawnServiceProvider.js +1 -4
  67. package/dist/src/service/startup/DefaultStartupService.d.ts +10 -0
  68. package/dist/src/service/startup/DefaultStartupService.d.ts.map +1 -0
  69. package/dist/src/service/startup/DefaultStartupService.js +13 -0
  70. package/dist/src/service/startup/StartupServiceProvider.d.ts +17 -0
  71. package/dist/src/service/startup/StartupServiceProvider.d.ts.map +1 -0
  72. package/dist/src/service/startup/StartupServiceProvider.js +26 -0
  73. package/dist/src/service/upgrade/DefaultUpgradeService.d.ts +10 -4
  74. package/dist/src/service/upgrade/DefaultUpgradeService.d.ts.map +1 -1
  75. package/dist/src/service/upgrade/DefaultUpgradeService.js +156 -37
  76. package/dist/src/service/upgrade/UpgradeServiceProvider.d.ts +10 -0
  77. package/dist/src/service/upgrade/UpgradeServiceProvider.d.ts.map +1 -1
  78. package/dist/src/service/upgrade/UpgradeServiceProvider.js +25 -6
  79. package/dist/src/service/upgrade/command/UpgradeSubCommand.d.ts +0 -3
  80. package/dist/src/service/upgrade/command/UpgradeSubCommand.d.ts.map +1 -1
  81. package/dist/src/service/upgrade/command/UpgradeSubCommand.js +5 -13
  82. package/dist/src/startup/banner/bannerStartupTask.d.ts +17 -0
  83. package/dist/src/startup/banner/bannerStartupTask.d.ts.map +1 -0
  84. package/dist/src/startup/banner/bannerStartupTask.js +66 -0
  85. package/dist/src/{service → startup}/banner/command/NoBannerCommand.d.ts +8 -2
  86. package/dist/src/startup/banner/command/NoBannerCommand.d.ts.map +1 -0
  87. package/dist/src/{service → startup}/banner/command/NoBannerCommand.js +4 -4
  88. package/index.ts +19 -3
  89. package/package.json +7 -7
  90. package/src/cli/BaseCLI.ts +49 -3
  91. package/src/launcher.ts +7 -2
  92. package/src/runtime/lifecycle/PriorityTaskList.ts +34 -0
  93. package/src/runtime/runner.ts +106 -67
  94. package/src/service/configuration/ConfigurationServiceProvider.ts +80 -180
  95. package/src/service/configuration/DefaultKeyValueService.ts +8 -27
  96. package/src/service/configuration/DefaultSecretService.ts +3 -13
  97. package/src/service/configuration/KeyValueServiceProvider.ts +246 -0
  98. package/src/service/configuration/command/ConfigCommand.ts +12 -11
  99. package/src/service/configuration/command/DumpConfigCommand.ts +10 -9
  100. package/src/service/fetch/DefaultFetchService.ts +18 -12
  101. package/src/service/fetch/FetchServiceProvider.ts +2 -4
  102. package/src/service/printer/PrinterServiceProvider.ts +11 -7
  103. package/src/service/printer/command/DarkModeCommand.ts +7 -8
  104. package/src/service/printer/command/LogLevelCommand.ts +10 -11
  105. package/src/service/printer/command/NoColorCommand.ts +7 -8
  106. package/src/service/printer/command/NoHyperlinksCommand.ts +6 -7
  107. package/src/service/prompter/PrompterServiceProvider.ts +1 -1
  108. package/src/service/prompter/command/NoPromptCommand.ts +6 -7
  109. package/src/service/shutdown/DefaultShutdownService.ts +5 -4
  110. package/src/service/shutdown/ShutdownServiceProvider.ts +2 -2
  111. package/src/service/spawn/DefaultSpawnService.ts +18 -14
  112. package/src/service/spawn/SpawnServiceProvider.ts +1 -5
  113. package/src/service/startup/DefaultStartupService.ts +17 -0
  114. package/src/service/startup/StartupServiceProvider.ts +36 -0
  115. package/src/service/upgrade/DefaultUpgradeService.ts +202 -42
  116. package/src/service/upgrade/UpgradeServiceProvider.ts +31 -8
  117. package/src/service/upgrade/command/UpgradeSubCommand.ts +5 -15
  118. package/src/startup/banner/bannerStartupTask.ts +96 -0
  119. package/src/{service → startup}/banner/command/NoBannerCommand.ts +12 -5
  120. package/dist/src/service/banner/BannerServiceProvider.d.ts +0 -29
  121. package/dist/src/service/banner/BannerServiceProvider.d.ts.map +0 -1
  122. package/dist/src/service/banner/BannerServiceProvider.js +0 -69
  123. package/dist/src/service/banner/command/NoBannerCommand.d.ts.map +0 -1
  124. package/src/service/banner/BannerServiceProvider.ts +0 -89
@@ -5,7 +5,25 @@ import { printCommandExecutionError, printNoCommandRecognisedError, printNoComma
5
5
  import { scanForGlobalModifierCommandClauses, scanForNonModifierCommandClause } from "./scanner.js";
6
6
  import { parseGlobalCommandClause, parseSubCommandClause } from "./parser.js";
7
7
  import { ARGUMENT_PROMPTER_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
8
+ import DefaultStartupService from "../service/startup/DefaultStartupService.js";
9
+ import { sortByPriority } from "./lifecycle/PriorityTaskList.js";
8
10
  const logger = getLogger("runner");
11
+ /**
12
+ * Wrap a {@link ServiceProvider}'s `initService()` call as a {@link StartupTask} so it can be
13
+ * merged with directly-registered {@link StartupTask}s (e.g. the banner task) into a single
14
+ * priority-ordered sequence.
15
+ */
16
+ function toProviderStartupTask(serviceProvider, commandRegistry) {
17
+ return {
18
+ id: serviceProvider.serviceId,
19
+ priority: serviceProvider.servicePriority,
20
+ mode: "blocking",
21
+ modifierCommands: Array.from(commandRegistry
22
+ .getGlobalModifierCommandsByNameProvidedByService(serviceProvider.serviceId)
23
+ .values()),
24
+ run: (context) => serviceProvider.initService(context),
25
+ };
26
+ }
9
27
  async function attemptPromptForMissingArguments(parseResult, context) {
10
28
  if (!context.doesServiceExist(ARGUMENT_PROMPTER_SERVICE_ID)) {
11
29
  return undefined;
@@ -30,37 +48,27 @@ async function attemptPromptForMissingArguments(parseResult, context) {
30
48
  *
31
49
  * @param parseResult the {@link ParseResult} to execute.
32
50
  * @param context the {@link Context} to use.
33
- * @param configurationServiceProvider optional {@link ConfigurationServiceProvider} to use to get default argument values.
51
+ * @param keyValueServiceProvider optional {@link KeyValueServiceProvider} to use to get a scope-decorated {@link Context}.
34
52
  * @param isDefaultCommand whether the command is the default command for the CLI.
35
53
  */
36
- async function executeParsedCommand(parseResult, context, configurationServiceProvider, isDefaultCommand = false) {
54
+ async function executeParsedCommand(parseResult, context, keyValueServiceProvider, isDefaultCommand = false) {
37
55
  try {
38
56
  if (parseResult.groupCommand !== undefined) {
39
57
  logger.debug("Executing group command with name: %s", parseResult.groupCommand.name);
40
- if (configurationServiceProvider?.keyValueServiceEnabled ||
41
- configurationServiceProvider?.secretServiceEnabled) {
42
- configurationServiceProvider.setCommandKeyValueScope(parseResult.groupCommand.name);
43
- }
44
- await parseResult.groupCommand.execute(context);
45
- if (configurationServiceProvider?.keyValueServiceEnabled ||
46
- configurationServiceProvider?.secretServiceEnabled) {
47
- await configurationServiceProvider.clearKeyValueScope();
48
- }
58
+ const groupContext = keyValueServiceProvider
59
+ ? keyValueServiceProvider.getContextForScope(context, "command", parseResult.groupCommand.name)
60
+ : context;
61
+ await parseResult.groupCommand.execute(groupContext);
49
62
  }
50
63
  logger.debug("Executing command with name: %s and args: %O", parseResult.command.name, parseResult.populatedArgumentValues);
51
- if (configurationServiceProvider?.keyValueServiceEnabled ||
52
- configurationServiceProvider?.secretServiceEnabled) {
53
- configurationServiceProvider.setCommandKeyValueScope(parseResult.command.name);
54
- }
64
+ const commandContext = keyValueServiceProvider
65
+ ? keyValueServiceProvider.getContextForScope(context, "command", parseResult.command.name)
66
+ : context;
55
67
  if (isSubCommand(parseResult.command)) {
56
- await parseResult.command.execute(context, parseResult.populatedArgumentValues);
68
+ await parseResult.command.execute(commandContext, parseResult.populatedArgumentValues);
57
69
  }
58
70
  else {
59
- await parseResult.command.execute(context, parseResult.populatedArgumentValues);
60
- }
61
- if (configurationServiceProvider?.keyValueServiceEnabled ||
62
- configurationServiceProvider?.secretServiceEnabled) {
63
- await configurationServiceProvider.clearKeyValueScope();
71
+ await parseResult.command.execute(commandContext, parseResult.populatedArgumentValues);
64
72
  }
65
73
  }
66
74
  catch (err) {
@@ -86,9 +94,10 @@ async function executeParsedCommand(parseResult, context, configurationServicePr
86
94
  * @param globalModifierCommandsByName a map of non-modifier {@link Command} instances by name to parse for.
87
95
  * @param globalModifierCommandsByShortAlias a map of {@link GlobalModifierCommand} instances by short alias to use when scanning.
88
96
  * @param configurationServiceProvider optional {@link ConfigurationServiceProvider} to use to get default argument values.
97
+ * @param keyValueServiceProvider optional {@link KeyValueServiceProvider} to use to get a scope-decorated {@link Context}.
89
98
  * @param context the {@link Context} to use.
90
99
  */
91
- async function findAndExecuteGlobalModifierCommands(availableArgSequences, unusedArgSequences, globalModifierCommandsByName, globalModifierCommandsByShortAlias, configurationServiceProvider, context) {
100
+ async function findAndExecuteGlobalModifierCommands(availableArgSequences, unusedArgSequences, globalModifierCommandsByName, globalModifierCommandsByShortAlias, configurationServiceProvider, keyValueServiceProvider, context) {
92
101
  // build a list of GlobalModifierCommands to execute
93
102
  const globalModifierCommandParseResults = [];
94
103
  // maintain a list of remaining GlobalModifierCommands to scan
@@ -151,7 +160,7 @@ async function findAndExecuteGlobalModifierCommands(availableArgSequences, unuse
151
160
  a.command.executePriority);
152
161
  for (const parseResult of globalModifierCommandParseResults) {
153
162
  // execute and fast fail on error
154
- const runResult = await executeParsedCommand(parseResult, context, configurationServiceProvider);
163
+ const runResult = await executeParsedCommand(parseResult, context, keyValueServiceProvider);
155
164
  if (runResult.runState !== RunState.SUCCESS) {
156
165
  return runResult;
157
166
  }
@@ -166,9 +175,10 @@ async function findAndExecuteGlobalModifierCommands(availableArgSequences, unuse
166
175
  * @param globalCommandsByShortAlias the map of {@link GlobalCommand} instances by short alias to use when scanning.
167
176
  * @param groupAndMemberCommandsByJoinedName optional map of {@link GroupCommand} and member {@link SubCommand} instances to use when scanning.
168
177
  * @param configurationServiceProvider optional {@link ConfigurationServiceProvider} to use to get default argument values.
178
+ * @param keyValueServiceProvider optional {@link KeyValueServiceProvider} to use to get a scope-decorated {@link Context}.
169
179
  * @param context the {@link Context} to use.
170
180
  */
171
- async function findAndExecuteNonModifierCommand(availableArgs, unusedArgs, nonModifierCommandsByName, globalCommandsByShortAlias, groupAndMemberCommandsByJoinedName, configurationServiceProvider, context) {
181
+ async function findAndExecuteNonModifierCommand(availableArgs, unusedArgs, nonModifierCommandsByName, globalCommandsByShortAlias, groupAndMemberCommandsByJoinedName, configurationServiceProvider, keyValueServiceProvider, context) {
172
182
  let parseResult;
173
183
  const scanResult = scanForNonModifierCommandClause(availableArgs, nonModifierCommandsByName, globalCommandsByShortAlias, groupAndMemberCommandsByJoinedName);
174
184
  // add any unused args back to the list of still available args for next scan operation
@@ -248,7 +258,7 @@ async function findAndExecuteNonModifierCommand(availableArgs, unusedArgs, nonMo
248
258
  if (unusedArgs.length > 0) {
249
259
  await printUnusedArgsWarning(context, unusedArgs.flat());
250
260
  }
251
- return executeParsedCommand(parseResult, context, configurationServiceProvider);
261
+ return executeParsedCommand(parseResult, context, keyValueServiceProvider);
252
262
  }
253
263
  /**
254
264
  * Assume provided command in use and parse arguments and execute with specified argument values if valid.
@@ -257,9 +267,10 @@ async function findAndExecuteNonModifierCommand(availableArgs, unusedArgs, nonMo
257
267
  * @param unusedArgs an array to which any unused argument sequences will be added.
258
268
  * @param defaultNonModifierCommand the default non-modifier {@link Command} to use.
259
269
  * @param configurationServiceProvider optional {@link ConfigurationServiceProvider} to use to get default argument values.
270
+ * @param keyValueServiceProvider optional {@link KeyValueServiceProvider} to use to get a scope-decorated {@link Context}.
260
271
  * @param context the {@link Context} to use.
261
272
  */
262
- async function findAndExecuteDefaultNonModifierCommand(availableArgs, unusedArgs, defaultNonModifierCommand, configurationServiceProvider, context) {
273
+ async function findAndExecuteDefaultNonModifierCommand(availableArgs, unusedArgs, defaultNonModifierCommand, configurationServiceProvider, keyValueServiceProvider, context) {
263
274
  const defaultArgumentValues = configurationServiceProvider
264
275
  ? await configurationServiceProvider.getDefaultArgumentValues(context.cliConfig, defaultNonModifierCommand)
265
276
  : undefined;
@@ -348,7 +359,7 @@ async function findAndExecuteDefaultNonModifierCommand(availableArgs, unusedArgs
348
359
  if (unusedArgs.length > 0) {
349
360
  await printUnusedArgsWarning(context, unusedArgs.flat());
350
361
  }
351
- return executeParsedCommand(parseResult, context, configurationServiceProvider, true);
362
+ return executeParsedCommand(parseResult, context, keyValueServiceProvider, true);
352
363
  }
353
364
  /**
354
365
  * Scan arguments and execute the parsed {@link Command} clauses with provided argument values
@@ -356,14 +367,15 @@ async function findAndExecuteDefaultNonModifierCommand(availableArgs, unusedArgs
356
367
  *
357
368
  * An overview of the logic (assuming the optional ConfigurationServiceProvider is provided):
358
369
  *
359
- * 1. For each ServiceProvider in servicePriority order:
360
- * - Scan arguments for GlobalModifierCommand clauses provided by the ServiceProvider
370
+ * 1. For each StartupTask in priority order (whether backed by a ServiceProvider's
371
+ * initService() or registered directly, e.g. the banner task):
372
+ * - Scan arguments for GlobalModifierCommand clauses provided by the task
361
373
  * - For each discovered clause:
362
374
  * - Set any argument defaults from ConfigurationServiceProvider
363
375
  * - Parse the arguments for the GlobalModifierCommand
364
376
  * - Return on error
365
377
  * - Add to list of GlobalModifierCommands to execute
366
- * - Scan ConfigurationServiceProvider for GlobalModifierCommand clauses provided by the ServiceProvider not already found in arguments
378
+ * - Scan ConfigurationServiceProvider for GlobalModifierCommand clauses provided by the task not already found in arguments
367
379
  * - For each configured clause:
368
380
  * - Parse the arguments for the GlobalModifierCommand
369
381
  * - Return on error
@@ -372,14 +384,18 @@ async function findAndExecuteDefaultNonModifierCommand(availableArgs, unusedArgs
372
384
  * - For each GlobalModifierCommand clause:
373
385
  * - Execute the GlobalModifierCommand
374
386
  * - Return on error
375
- * - Init the ServiceProvider's service.
376
- * 2. Scan arguments for any GlobalModifierCommand clauses not provided by ServiceProviders
387
+ * - Give the task a Context whose KeyValueService is bound permanently to its own scope
388
+ * (keyed by task.id), via KeyValueServiceProvider if provided.
389
+ * - Run the task: awaited before continuing if its mode is "blocking" (the default, and how
390
+ * every ServiceProvider's initService() behaves); fired without awaiting if "background"
391
+ * (errors logged, not propagated).
392
+ * 2. Scan arguments for any GlobalModifierCommand clauses not provided by a StartupTask
377
393
  * - For each discovered clause:
378
394
  * - Set any argument defaults from ConfigurationServiceProvider
379
395
  * - Parse the arguments for the GlobalModifierCommand
380
396
  * - Return on error
381
397
  * - Add to list of GlobalModifierCommands to execute
382
- * 3. Scan ConfigurationServiceProvider for GlobalModifierCommand clauses not provided by the ServiceProvider and not already found in arguments
398
+ * 3. Scan ConfigurationServiceProvider for GlobalModifierCommand clauses not provided by a StartupTask and not already found in arguments
383
399
  * - For each discovered clause:
384
400
  * - Parse the arguments for the GlobalModifierCommand
385
401
  * - Return on error
@@ -414,11 +430,12 @@ async function findAndExecuteDefaultNonModifierCommand(availableArgs, unusedArgs
414
430
  * @param commandRegistry the {@link CommandRegistry} to use when scanning and parsing.
415
431
  * @param serviceProviderRegistry the {@link ServiceProviderRegistry} to use when scanning and parsing.
416
432
  * @param configurationServiceProvider optional {@link ConfigurationServiceProvider} to use for accessing argument defaults.
433
+ * @param keyValueServiceProvider optional {@link KeyValueServiceProvider} to use to get a scope-decorated {@link Context}.
417
434
  * @param context the {@link Context} in which to execute specified {@link Command} instances.
418
435
  * @param defaultCommand optional {@link SubCommand} or {@link GlobalCommand} implementation to attempt to parse arguments for and execute if
419
436
  * no non-modifier {@link Command} name is identified in the provided arguments.
420
437
  */
421
- export async function run(args, commandRegistry, serviceProviderRegistry, configurationServiceProvider, context, defaultCommand) {
438
+ export async function run(args, commandRegistry, serviceProviderRegistry, configurationServiceProvider, keyValueServiceProvider, context, defaultCommand, startupService) {
422
439
  if (defaultCommand && !isSubCommand(defaultCommand) && !isGlobalCommand(defaultCommand)) {
423
440
  throw new Error("If a default command is provided, if must be a global command or sub-command!");
424
441
  }
@@ -427,15 +444,24 @@ export async function run(args, commandRegistry, serviceProviderRegistry, config
427
444
  // this is to prevent incorrectly parsing arguments as if they were interleaved across multiple commands which is not supported
428
445
  let availableArgs = [args];
429
446
  let unusedArgs = [];
430
- // for each ServiceProvider (they are returned in servicePriority order)
431
- for (const serviceProvider of serviceProviderRegistry.getServiceProviders()) {
432
- // using the GlobalModifierCommands instances provided by the ServiceProvider...
433
- const globalModifierCommandsByName = commandRegistry.getGlobalModifierCommandsByNameProvidedByService(serviceProvider.serviceId);
434
- const globalModifierCommandsByShortAlias = commandRegistry.getGlobalModifierCommandsByShortAliasProvidedByService(serviceProvider.serviceId);
435
- // if there are GlobalModifierCommands provided by the ServiceProvider...
447
+ // merge every ServiceProvider's initService() into a StartupTask, then combine with any
448
+ // directly-registered StartupTasks (e.g. the banner task) into one priority-ordered sequence
449
+ const startupTasks = sortByPriority([
450
+ ...serviceProviderRegistry
451
+ .getServiceProviders()
452
+ .map((serviceProvider) => toProviderStartupTask(serviceProvider, commandRegistry)),
453
+ ...(startupService ? startupService.getTasks() : []),
454
+ ]);
455
+ for (const task of startupTasks) {
456
+ // using the GlobalModifierCommands instances provided by the task...
457
+ const globalModifierCommandsByName = new Map((task.modifierCommands ?? []).map((command) => [command.name, command]));
458
+ const globalModifierCommandsByShortAlias = new Map((task.modifierCommands ?? [])
459
+ .filter((command) => Boolean(command.shortAlias))
460
+ .map((command) => [command.shortAlias, command]));
461
+ // if there are GlobalModifierCommands provided by the task...
436
462
  if (globalModifierCommandsByName.size > 0) {
437
463
  // ...scan arguments, parse any resulting clauses and execute any resulting parsed GlobalModifierCommands
438
- const runResult = await findAndExecuteGlobalModifierCommands(availableArgs, unusedArgs, globalModifierCommandsByName, globalModifierCommandsByShortAlias, configurationServiceProvider, context);
464
+ const runResult = await findAndExecuteGlobalModifierCommands(availableArgs, unusedArgs, globalModifierCommandsByName, globalModifierCommandsByShortAlias, configurationServiceProvider, keyValueServiceProvider, context);
439
465
  // nothing is returned unless a command was found and there was an error, in which case return the error
440
466
  if (runResult) {
441
467
  return runResult;
@@ -444,16 +470,19 @@ export async function run(args, commandRegistry, serviceProviderRegistry, config
444
470
  availableArgs = unusedArgs;
445
471
  unusedArgs = [];
446
472
  }
447
- // init the service provider's service
448
- logger.debug("Initialising service with ID: %s", serviceProvider.serviceId);
449
- if (configurationServiceProvider?.keyValueServiceEnabled ||
450
- configurationServiceProvider?.secretServiceEnabled) {
451
- configurationServiceProvider.setServiceKeyValueScope(serviceProvider.serviceId);
473
+ logger.debug("Running startup task with ID: %s", task.id);
474
+ // each task gets a Context whose KeyValueService is bound permanently to its own scope (keyed
475
+ // by task.id)
476
+ const taskContext = keyValueServiceProvider
477
+ ? keyValueServiceProvider.getContextForScope(context, "service", task.id)
478
+ : context;
479
+ if ((task.mode ?? "blocking") === "blocking") {
480
+ await task.run(taskContext);
452
481
  }
453
- await serviceProvider.initService(context);
454
- if (configurationServiceProvider?.keyValueServiceEnabled ||
455
- configurationServiceProvider?.secretServiceEnabled) {
456
- await configurationServiceProvider.clearKeyValueScope();
482
+ else {
483
+ void task.run(taskContext).catch((error) => {
484
+ logger.debug(() => `Background startup task '${task.id}' failed: ${error}`);
485
+ });
457
486
  }
458
487
  }
459
488
  // using the GlobalModifierCommands instances which are NOT provided by a ServiceProvider...
@@ -462,7 +491,7 @@ export async function run(args, commandRegistry, serviceProviderRegistry, config
462
491
  // if there are GlobalModifierCommands NOT provided by the ServiceProvider...
463
492
  if (globalModifierCommandsByName.size > 0) {
464
493
  // ...scan arguments, parse any resulting clauses and execute any resulting parsed GlobalModifierCommands
465
- const runResult = await findAndExecuteGlobalModifierCommands(availableArgs, unusedArgs, globalModifierCommandsByName, globalModifierCommandsByShortAlias, configurationServiceProvider, context);
494
+ const runResult = await findAndExecuteGlobalModifierCommands(availableArgs, unusedArgs, globalModifierCommandsByName, globalModifierCommandsByShortAlias, configurationServiceProvider, keyValueServiceProvider, context);
466
495
  // nothing is returned unless a command was found and there was an error, in which case return the error
467
496
  if (runResult) {
468
497
  return runResult;
@@ -477,7 +506,7 @@ export async function run(args, commandRegistry, serviceProviderRegistry, config
477
506
  // and any group/member instance combinations...
478
507
  const groupAndMemberCommandByJoinedName = commandRegistry.getGroupAndMemberCommandsByJoinedName();
479
508
  // ...scan arguments, parse a resulting clause and execute a resulting parsed non-modifier Command
480
- let runResult = await findAndExecuteNonModifierCommand(availableArgs, unusedArgs, nonModifierCommandsByName, globalCommandsByShortAlias, groupAndMemberCommandByJoinedName, configurationServiceProvider, context);
509
+ let runResult = await findAndExecuteNonModifierCommand(availableArgs, unusedArgs, nonModifierCommandsByName, globalCommandsByShortAlias, groupAndMemberCommandByJoinedName, configurationServiceProvider, keyValueServiceProvider, context);
481
510
  // nothing is returned if no command was discovered (otherwise a run result is returned)
482
511
  if (runResult) {
483
512
  return runResult;
@@ -488,7 +517,7 @@ export async function run(args, commandRegistry, serviceProviderRegistry, config
488
517
  // if default non-modifier Command provided...
489
518
  if (defaultCommand) {
490
519
  // ...assume default command name and parse resulting clauses and execute a resulting parsed non-modifier Command if found
491
- runResult = await findAndExecuteDefaultNonModifierCommand(availableArgs, unusedArgs, defaultCommand, configurationServiceProvider, context);
520
+ runResult = await findAndExecuteDefaultNonModifierCommand(availableArgs, unusedArgs, defaultCommand, configurationServiceProvider, keyValueServiceProvider, context);
492
521
  // nothing is returned if no command was discovered (otherwise a run result is returned)
493
522
  if (runResult) {
494
523
  return runResult;
@@ -1,13 +1,20 @@
1
1
  import type { ServiceInfo, ServiceProvider } from "@flowscripter/dynamic-cli-framework-api";
2
- import type { SingleValueType, Values, PopulatedValues, PopulatedValueType } from "@flowscripter/dynamic-cli-framework-api";
2
+ import type { SingleValueType, Values, ValueNode, PopulatedValues, PopulatedValueType } from "@flowscripter/dynamic-cli-framework-api";
3
3
  import type { Context } from "@flowscripter/dynamic-cli-framework-api";
4
4
  import type { Command } from "@flowscripter/dynamic-cli-framework-api";
5
5
  import type { CLIConfig } from "@flowscripter/dynamic-cli-framework-api";
6
+ export type KeyValueServiceScopeType = "command" | "service";
6
7
  /**
7
- * Provides:
8
+ * Provides configuration of default command arguments using a configuration file and/or
9
+ * environment variables.
8
10
  *
9
- * * Configuration of default command arguments using a configuration file and/or environment variables.
10
- * * A generic key-value store for commands and services ({@link KeyValueService}).
11
+ * The same configuration file also backs the raw, per-scope key-value data consumed by
12
+ * {@link KeyValueServiceProvider} (which holds a direct reference to this provider - see
13
+ * {@link getKeyValueData} and {@link flushIfDirty} - rather than looking this provider up via
14
+ * {@link Context}, since only {@link configLocation}/{@link setConfigLocation} are safe to expose
15
+ * generally; {@link getConfigString} is wired directly to {@link DumpConfigCommand} for the same
16
+ * reason - a full config dump would leak every other command's/service's key-value data if it
17
+ * were reachable through a general lookup).
11
18
  *
12
19
  * **Default Command Arguments**
13
20
  *
@@ -52,7 +59,8 @@ import type { CLIConfig } from "@flowscripter/dynamic-cli-framework-api";
52
59
  * NOTE: You may store default arguments for secrets by manually configuring secrets in your OS
53
60
  * credential store and referencing them in the config file using the sentinel format
54
61
  * `__SECRET__:<bun_secret_name>`. These values will be resolved from the OS secret store
55
- * before being returned as default argument values. See "Generic Key-Value Service" below.
62
+ * before being returned as default argument values. See {@link KeyValueServiceProvider} for the
63
+ * generic key-value store which uses the same secret sentinel mechanism.
56
64
  *
57
65
  * *Environment Variables*
58
66
  *
@@ -90,60 +98,6 @@ import type { CLIConfig } from "@flowscripter/dynamic-cli-framework-api";
90
98
  * * executable: `MyCLI`, command: `command1`, nested sub-argument with both levels being arrays and referring to the 2nd element of each: `arg1[1].arg2[1]`, arg2 configuration key: `BAR` => environment variable: `MYCLI_COMMAND1_ARG1_1_BAR_2`
91
99
  *
92
100
  * NOTE: Any default values from the above configuration sources will be overridden by any arguments provided on the command line.
93
- *
94
- * **Generic Key-Value Service**
95
- *
96
- * The same configuration file above is used to provide storage for the provided {@link KeyValueService}. The values
97
- * are stored under a top level `key-values` property. These are expected to not be modified by the user of the CLI.
98
- *
99
- * The second and third level of properties is used to scope the key-values to specific command names
100
- * (via {@link Command.name} values) and specific service IDs (via {@link ServiceProvider.serviceId} values).
101
- * Keys are strings, but values are arbitrary JSON (see {@link ValueNode}) - not limited to strings -
102
- * and may be deep objects or arrays.
103
- *
104
- * Any node within a value passed to {@link KeyValueService.set} can be wrapped in {@link Secret} -
105
- * at any depth - to have that node, and only that node, stored as an OS-native secret. When a node
106
- * is wrapped in {@link Secret}, its value (of any shape) is JSON-serialized and stored as a single
107
- * OS-native secret via Bun.secrets, with a sentinel value of the format `__SECRET__:<bun_secret_name>`
108
- * substituted in its place in the structure that gets stored in the config file. Everything else in
109
- * the value is stored as plain (unencrypted) config data. The sentinel prefix `__SECRET__:` is
110
- * reserved and must not be used for regular key-value data.
111
- *
112
- * Independently of how a value was written, {@link KeyValueService.get} recursively resolves any
113
- * string leaf - at any depth within the retrieved value - which starts with the sentinel prefix,
114
- * via the OS secret store. This means a secret reference may also be hand-embedded (nested
115
- * arbitrarily deep) directly within a plain, non-secret value in the config file.
116
- *
117
- * Secret support requires `secretServiceEnabled=true` in the constructor (which also requires
118
- * `configEnabled=true`).
119
- *
120
- * As an example:
121
- * ```
122
- * {
123
- * "defaults": {
124
- * ...
125
- * },
126
- * "key-values": {
127
- * "commands": {
128
- * "command1": {
129
- * "foo1": "bar1",
130
- * "foo2": "__SECRET__:command_command1_foo2",
131
- * "foo3": {
132
- * "nested": ["a", "__SECRET__:command_command1_foo3_nested"]
133
- * }
134
- * },
135
- * "command2": {
136
- * "foo1": "bar3"
137
- * }
138
- * },
139
- * "services": {
140
- * "service-id-1": {
141
- * "foo1": "bar"
142
- * }
143
- * }
144
- * }
145
- * }
146
- * ```
147
101
  */
148
102
  export default class ConfigurationServiceProvider implements ServiceProvider {
149
103
  #private;
@@ -151,7 +105,6 @@ export default class ConfigurationServiceProvider implements ServiceProvider {
151
105
  readonly servicePriority: number;
152
106
  readonly envVarsEnabled: boolean;
153
107
  readonly configEnabled: boolean;
154
- readonly keyValueServiceEnabled: boolean;
155
108
  readonly secretServiceEnabled: boolean;
156
109
  configLocation: string | undefined;
157
110
  defaultsData: Map<string, Values | SingleValueType>;
@@ -161,10 +114,10 @@ export default class ConfigurationServiceProvider implements ServiceProvider {
161
114
  * @param servicePriority the priority of the service.
162
115
  * @param envVarsEnabled optionally support checking env variables for default argument values.
163
116
  * @param configEnabled optionally enable configuration file support for default argument values.
164
- * @param keyValueServiceEnabled optionally provide a {@link KeyValueService} implementation: `configEnabled` must be true in this case
165
- * @param secretServiceEnabled optionally enable OS-native secret storage via Bun.secrets: `configEnabled` must be true in this case
117
+ * @param secretServiceEnabled optionally enable OS-native secret storage via Bun.secrets for resolving
118
+ * secrets embedded in default argument values: `configEnabled` must be true in this case
166
119
  */
167
- constructor(servicePriority: number, envVarsEnabled?: boolean, configEnabled?: boolean, keyValueServiceEnabled?: boolean, secretServiceEnabled?: boolean);
120
+ constructor(servicePriority: number, envVarsEnabled?: boolean, configEnabled?: boolean, secretServiceEnabled?: boolean);
168
121
  getServiceInfo(cliConfig: CLIConfig): Promise<ServiceInfo>;
169
122
  setConfigLocation(configLocation: string): void;
170
123
  /**
@@ -177,30 +130,21 @@ export default class ConfigurationServiceProvider implements ServiceProvider {
177
130
  */
178
131
  getDefaultArgumentValues(cliConfig: CLIConfig, command: Command): Promise<PopulatedValues | PopulatedValueType | undefined>;
179
132
  /**
180
- * Indicate that the contained {@link DefaultKeyValueService} should be set to have a scope of the provided
181
- * command name.
182
- *
183
- * @param commandName the name of the command to scope the key-value data to.
184
- *
185
- * @throws {Error} if an existing scope is set.
186
- */
187
- setCommandKeyValueScope(commandName: string): void;
188
- /**
189
- * Indicate that the contained {@link DefaultKeyValueService} should be set to have a scope of the provided
190
- * service ID.
191
- *
192
- * @param serviceId the ID of the service to scope the key-value data to.
133
+ * Get-or-create the underlying, raw {@link ValueNode} `Map` for the given scope - for
134
+ * {@link KeyValueServiceProvider}'s exclusive use, via its direct reference to this provider.
193
135
  *
194
- * @throws {Error} if an existing scope is set.
136
+ * @param scopeType whether `scopeKey` is a command name or a service/task ID.
137
+ * @param scopeKey the command name or service/task ID to scope the key-value data to.
195
138
  */
196
- setServiceKeyValueScope(serviceId: string): void;
139
+ getKeyValueData(scopeType: KeyValueServiceScopeType, scopeKey: string): Map<string, ValueNode>;
197
140
  /**
198
- * Indicate that the contained {@link DefaultKeyValueService} should have any current scope cleared.
141
+ * Write the configuration file, if `isDirty` is true - for {@link KeyValueServiceProvider}'s
142
+ * exclusive use, via its direct reference to this provider, called from its shutdown-time
143
+ * flush task.
199
144
  *
200
- * If a scope is currently set and changes have been made to the key-value data, they will be written
201
- * to the current configuration location.
145
+ * @param isDirty whether any scoped KeyValueService has unwritten changes.
202
146
  */
203
- clearKeyValueScope(): Promise<void>;
147
+ flushIfDirty(isDirty: boolean): Promise<void>;
204
148
  initService(context: Context): Promise<void>;
205
149
  getConfigString(): string;
206
150
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ConfigurationServiceProvider.d.ts","sourceRoot":"","sources":["../../../../src/service/configuration/ConfigurationServiceProvider.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAE5F,OAAO,KAAK,EACV,eAAe,EACf,MAAM,EAEN,eAAe,EACf,kBAAkB,EACnB,MAAM,yCAAyC,CAAC;AAEjD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AASvE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AAOvE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAQzE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6IG;AACH,MAAM,CAAC,OAAO,OAAO,4BAA6B,YAAW,eAAe;;IAC1E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAwB;IAClD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IAEjC,SAAgB,cAAc,EAAE,OAAO,CAAC;IACxC,SAAgB,aAAa,EAAE,OAAO,CAAC;IACvC,SAAgB,sBAAsB,EAAE,OAAO,CAAC;IAChD,SAAgB,oBAAoB,EAAE,OAAO,CAAC;IAGvC,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;IAKnC,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,CAAC,CAAa;IAmBvE;;;;;;;;OAQG;IACH,YACE,eAAe,EAAE,MAAM,EACvB,cAAc,UAAQ,EACtB,aAAa,UAAQ,EACrB,sBAAsB,UAAQ,EAC9B,oBAAoB,UAAQ,EAa7B;IAEM,cAAc,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC,CAmBhE;IAEM,iBAAiB,CAAC,cAAc,EAAE,MAAM,QAE9C;IAED;;;;;;;OAOG;IACU,wBAAwB,CACnC,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,eAAe,GAAG,kBAAkB,GAAG,SAAS,CAAC,CAoD3D;IAED;;;;;;;OAOG;IACI,uBAAuB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CA2BxD;IAED;;;;;;;OAOG;IACI,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CA2BtD;IAED;;;;;OAKG;IACU,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAmB/C;IAEY,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CA0CxD;IA4DM,eAAe,IAAI,MAAM,CAgC/B;CACF"}
1
+ {"version":3,"file":"ConfigurationServiceProvider.d.ts","sourceRoot":"","sources":["../../../../src/service/configuration/ConfigurationServiceProvider.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAE5F,OAAO,KAAK,EACV,eAAe,EACf,MAAM,EACN,SAAS,EACT,eAAe,EACf,kBAAkB,EACnB,MAAM,yCAAyC,CAAC;AAEjD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AASvE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AAOvE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAQzE,MAAM,MAAM,wBAAwB,GAAG,SAAS,GAAG,SAAS,CAAC;AAkB7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8FG;AACH,MAAM,CAAC,OAAO,OAAO,4BAA6B,YAAW,eAAe;;IAC1E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAA4B;IACtD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IAEjC,SAAgB,cAAc,EAAE,OAAO,CAAC;IACxC,SAAgB,aAAa,EAAE,OAAO,CAAC;IACvC,SAAgB,oBAAoB,EAAE,OAAO,CAAC;IAGvC,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;IAKnC,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,CAAC,CAAa;IAqBvE;;;;;;;;OAQG;IACH,YACE,eAAe,EAAE,MAAM,EACvB,cAAc,UAAQ,EACtB,aAAa,UAAQ,EACrB,oBAAoB,UAAQ,EAW7B;IAEM,cAAc,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC,CAehE;IAEM,iBAAiB,CAAC,cAAc,EAAE,MAAM,QAE9C;IAED;;;;;;;OAOG;IACU,wBAAwB,CACnC,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,eAAe,GAAG,kBAAkB,GAAG,SAAS,CAAC,CAoD3D;IAED;;;;;;OAMG;IACI,eAAe,CACpB,SAAS,EAAE,wBAAwB,EACnC,QAAQ,EAAE,MAAM,GACf,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAOxB;IAED;;;;;;OAMG;IACU,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAQzD;IAEY,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CA0CxD;IA4DM,eAAe,IAAI,MAAM,CAgC/B;CACF"}