@flowscripter/dynamic-cli-framework 1.1.6 → 1.2.1

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 (160) hide show
  1. package/.github/workflows/release-bun-library.yml +1 -0
  2. package/README/core-commands.md +40 -0
  3. package/README/core-service-providers.md +198 -0
  4. package/README/development.md +60 -0
  5. package/README/implementation-details.md +474 -0
  6. package/README/key-concepts.md +751 -0
  7. package/README.md +12 -1413
  8. package/bun.lock +22 -18
  9. package/index.ts +97 -1
  10. package/package.json +8 -7
  11. package/src/api/BaseCLIFeatureOptions.ts +10 -0
  12. package/src/api/CLIConfig.ts +5 -0
  13. package/src/api/RunResult.ts +16 -0
  14. package/src/api/argument/Argument.ts +24 -0
  15. package/src/api/service/ServiceProvider.ts +3 -3
  16. package/src/api/service/core/ArgumentPrompterService.ts +10 -0
  17. package/src/api/service/core/AsciiBannerGeneratorService.ts +44 -7
  18. package/src/api/service/core/CompletionService.ts +36 -0
  19. package/src/api/service/core/DataDumpGeneratorService.ts +27 -0
  20. package/src/api/service/core/ImagePrinterService.ts +26 -0
  21. package/src/api/service/core/KeyValueService.ts +22 -5
  22. package/src/api/service/core/PrinterService.ts +127 -1
  23. package/src/api/service/core/PrompterService.ts +49 -0
  24. package/src/api/service/core/SecretService.ts +27 -0
  25. package/src/api/service/core/ShutdownService.ts +17 -1
  26. package/src/api/service/core/Table.ts +85 -0
  27. package/src/api/service/core/TableGeneratorService.ts +38 -0
  28. package/src/api/service/core/TreePrinterService.ts +11 -0
  29. package/src/cli/BaseCLI.ts +116 -44
  30. package/src/cli/DefaultRuntimeCLI.ts +10 -18
  31. package/src/command/CompletionCommand.ts +179 -0
  32. package/src/command/MultiCommandCliHelpCommand.ts +18 -2
  33. package/src/command/SingleCommandCliHelpCommand.ts +10 -1
  34. package/src/launcher.ts +16 -58
  35. package/src/runtime/command/CommandValidator.ts +3 -3
  36. package/src/runtime/runner.ts +102 -30
  37. package/src/runtime/scanner.ts +18 -15
  38. package/src/runtime/values/argumentValueValidation.ts +49 -5
  39. package/src/runtime/values/globalCommandValuePopulation.ts +1 -1
  40. package/src/runtime/values/subCommandValuePopulation.ts +9 -8
  41. package/src/service/argumentPrompter/ArgumentPrompterServiceProvider.ts +34 -0
  42. package/src/service/argumentPrompter/DefaultArgumentPrompterService.ts +330 -0
  43. package/src/service/asciiBannerGenerator/AsciiBannerGeneratorServiceProvider.ts +1 -1
  44. package/src/service/asciiBannerGenerator/DefaultAsciiBannerGeneratorService.ts +254 -17
  45. package/src/service/banner/BannerServiceProvider.ts +2 -2
  46. package/src/service/chiselAsciiBannerGenerator/ChiselFontAsciiBannerGeneratorService.ts +85 -0
  47. package/src/service/chiselAsciiBannerGenerator/chisel.flf.json +3 -0
  48. package/src/service/completion/CompletionServiceProvider.ts +212 -0
  49. package/src/service/completion/DefaultCompletionService.ts +364 -0
  50. package/src/service/completion/shell/ShellHandler.ts +16 -0
  51. package/src/service/completion/shell/bash.ts +47 -0
  52. package/src/service/completion/shell/fish.ts +43 -0
  53. package/src/service/completion/shell/powershell.ts +66 -0
  54. package/src/service/completion/shell/zsh.ts +50 -0
  55. package/src/service/configuration/ConfigurationServiceProvider.ts +138 -42
  56. package/src/service/configuration/DefaultKeyValueService.ts +53 -7
  57. package/src/service/configuration/DefaultSecretService.ts +85 -0
  58. package/src/service/dataDumpGenerator/DataDumpGeneratorServiceProvider.ts +41 -0
  59. package/src/service/dataDumpGenerator/DefaultDataDumpGeneratorService.ts +80 -0
  60. package/src/service/imagePrinter/DefaultImagePrinterService.ts +23 -0
  61. package/src/service/imagePrinter/ImagePrinterServiceProvider.ts +32 -0
  62. package/src/service/prettyPrinter/PrettyPrinterServiceProvider.ts +1 -1
  63. package/src/service/printer/DefaultPrinterService.ts +147 -27
  64. package/src/service/printer/PrinterServiceProvider.ts +7 -7
  65. package/src/service/printer/command/NoHyperlinksCommand.ts +40 -0
  66. package/src/service/printer/terminal/Progress.ts +21 -4
  67. package/src/service/printer/terminal/Spinner.ts +32 -8
  68. package/src/service/printer/terminal/Theme.ts +1 -1
  69. package/src/service/prompter/DefaultPrompterService.ts +87 -0
  70. package/src/service/prompter/PrompterServiceProvider.ts +36 -0
  71. package/src/service/prompter/command/NoPromptCommand.ts +40 -0
  72. package/src/service/prompter/prompt/PromptContext.ts +47 -0
  73. package/src/service/prompter/prompt/promptAcknowledge.ts +28 -0
  74. package/src/service/prompter/prompt/promptMultiSelect.ts +99 -0
  75. package/src/service/prompter/prompt/promptOpenUrl.ts +97 -0
  76. package/src/service/prompter/prompt/promptSingleSelect.ts +102 -0
  77. package/src/service/prompter/prompt/promptText.ts +107 -0
  78. package/src/service/prompter/prompt/promptToggle.ts +28 -0
  79. package/src/service/shutdown/DefaultShutdownService.ts +13 -0
  80. package/src/service/shutdown/ShutdownServiceProvider.ts +28 -9
  81. package/src/service/shutdown/ShutdownState.ts +5 -0
  82. package/src/service/syntaxHighlighter/SyntaxHighlighterServiceProvider.ts +1 -1
  83. package/src/service/tableGenerator/DefaultTableGeneratorService.ts +415 -0
  84. package/src/service/tableGenerator/TableGeneratorServiceProvider.ts +43 -0
  85. package/src/service/treePrinter/DefaultTreePrinterService.ts +49 -0
  86. package/src/service/treePrinter/TreePrinterServiceProvider.ts +40 -0
  87. package/src/terminal/Ansi.ts +27 -0
  88. package/src/terminal/ImageRenderer.ts +199 -0
  89. package/src/terminal/KeyReader.ts +23 -0
  90. package/src/terminal/PngDecoder.ts +143 -0
  91. package/src/{service/printer/terminal → terminal}/Styler.ts +3 -0
  92. package/src/{service/printer/terminal → terminal}/Terminal.ts +1 -0
  93. package/src/terminal/TtyKeyReader.ts +83 -0
  94. package/src/terminal/TtyStyler.ts +78 -0
  95. package/src/{service/printer/terminal → terminal}/TtyTerminal.ts +9 -1
  96. package/src/terminal/supportsHyperlinks.ts +110 -0
  97. package/src/util/configHelper.ts +2 -2
  98. package/src/util/envVarHelper.ts +2 -2
  99. package/src/util/helpHelper.ts +66 -122
  100. package/src/util/logger.ts +16 -14
  101. package/src/util/runnerHelper.ts +1 -1
  102. package/tests/cli/BaseCLI.test.ts +50 -54
  103. package/tests/command/CompletionCommand.test.ts +68 -0
  104. package/tests/command/UsageCommand.test.ts +3 -2
  105. package/tests/command/VersionCommand.test.ts +3 -2
  106. package/tests/fixtures/Context.ts +9 -2
  107. package/tests/fixtures/util.ts +1 -1
  108. package/tests/runtime/parser.test.ts +9 -7
  109. package/tests/runtime/registry/DefaultServiceProviderRegistry.test.ts +3 -3
  110. package/tests/runtime/values/argumentValueValidation.test.ts +193 -0
  111. package/tests/service/argumentPrompter/ArgumentPrompterServiceProvider.test.ts +47 -0
  112. package/tests/service/argumentPrompter/DefaultArgumentPrompterService.test.ts +754 -0
  113. package/tests/service/asciiBannerGenerator/AsciiBannerGeneratorServiceProvider.test.ts +5 -4
  114. package/tests/service/asciiBannerGenerator/DefaultAsciiBannerGeneratorService.test.ts +163 -3
  115. package/tests/service/asciiBannerGenerator/__snapshots__/DefaultAsciiBannerGeneratorService.test.ts.snap +1 -1
  116. package/tests/service/banner/BannerServiceProvider.test.ts +8 -4
  117. package/tests/service/chiselAsciiBannerGenerator/ChiselFontAsciiBannerGeneratorService.test.ts +52 -0
  118. package/tests/service/completion/CompletionServiceProvider.test.ts +404 -0
  119. package/tests/service/completion/DefaultCompletionService.test.ts +278 -0
  120. package/tests/service/completion/shell/bash.test.ts +73 -0
  121. package/tests/service/completion/shell/fish.test.ts +62 -0
  122. package/tests/service/completion/shell/powershell.test.ts +59 -0
  123. package/tests/service/completion/shell/zsh.test.ts +60 -0
  124. package/tests/service/configuration/ConfigurationServiceProvider.test.ts +24 -12
  125. package/tests/service/configuration/DefaultKeyValueService.test.ts +118 -16
  126. package/tests/service/configuration/DefaultSecretService.test.ts +148 -0
  127. package/tests/service/configuration/command/ConfigCommand.test.ts +29 -0
  128. package/tests/service/configuration/command/DumpConfigCommand.test.ts +60 -0
  129. package/tests/service/dataDumpGenerator/DataDumpGeneratorServiceProvider.test.ts +57 -0
  130. package/tests/service/dataDumpGenerator/DefaultDataDumpGeneratorService.test.ts +217 -0
  131. package/tests/service/imagePrinter/DefaultImagePrinterService.test.ts +96 -0
  132. package/tests/service/imagePrinter/ImagePrinterServiceProvider.test.ts +17 -0
  133. package/tests/service/prettyPrinter/DefaultPrettyPrinterService.test.ts +2 -2
  134. package/tests/service/prettyPrinter/PrettyPrinterServiceProvider.test.ts +5 -4
  135. package/tests/service/printer/DefaultPrinterService.test.ts +233 -2
  136. package/tests/service/printer/PrinterServiceProvider.test.ts +6 -5
  137. package/tests/service/printer/command/DarkModeCommand.test.ts +83 -0
  138. package/tests/service/printer/command/LogLevelCommand.test.ts +7 -5
  139. package/tests/service/printer/command/NoColorCommand.test.ts +83 -0
  140. package/tests/service/printer/command/NoHyperlinksCommand.test.ts +89 -0
  141. package/tests/service/printer/terminal/Progress.test.ts +64 -4
  142. package/tests/service/printer/terminal/Spinner.test.ts +44 -3
  143. package/tests/service/prompter/DefaultPrompterService.test.ts +615 -0
  144. package/tests/service/prompter/PrompterServiceProvider.test.ts +47 -0
  145. package/tests/service/prompter/command/NoPromptCommand.test.ts +58 -0
  146. package/tests/service/shutdown/ShutdownServiceProvider.test.ts +4 -2
  147. package/tests/service/syntaxHighlighter/DefaultSyntaxHighlighterService.test.ts +3 -2
  148. package/tests/service/syntaxHighlighter/SyntaxHighlighterServiceProvider.test.ts +5 -4
  149. package/tests/service/tableGenerator/DefaultTableGeneratorService.test.ts +363 -0
  150. package/tests/service/tableGenerator/TableGeneratorServiceProvider.test.ts +47 -0
  151. package/tests/service/treePrinter/DefaultTreePrinterService.test.ts +144 -0
  152. package/tests/service/treePrinter/TreePrinterServiceProvider.test.ts +55 -0
  153. package/tests/terminal/ImageRenderer.test.ts +161 -0
  154. package/tests/terminal/PngDecoder.test.ts +223 -0
  155. package/tests/terminal/supportsHyperlinks.test.ts +149 -0
  156. package/tests/util/configHelper.test.ts +4 -4
  157. package/tests/util/logger.test.ts +23 -1
  158. package/tsconfig.json +31 -0
  159. package/src/service/printer/terminal/Ansi.ts +0 -10
  160. package/src/service/printer/terminal/TtyStyler.ts +0 -39
package/README.md CHANGED
@@ -45,8 +45,11 @@ So it isn't really dynamic at the moment! 😜
45
45
  - Support (optional) for persisted configuration and environment variables to
46
46
  specify command argument defaults.
47
47
  - Core (but optional) commands for help, logging level and version management.
48
- - Core (but optional) services for color output, syntax highlighting and pretty
49
- printing to stdout/stderr and configuration management.
48
+ - Core (but optional) services for color output (foreground and background),
49
+ syntax highlighting, pretty printing, table and tree structure rendering,
50
+ configuration management, and user input prompting.
51
+ - Graceful signal handling (SIGINT/SIGTERM) with shutdown hooks, cooperative
52
+ cancellation for long-running commands, and triple-interrupt force exit.
50
53
  - Core (but optional) support for dynamic discovery and installation of commands
51
54
  and services using
52
55
  [dynamic-plugin-framework](https://github.com/flowscripter/dynamic-plugin-framework)
@@ -69,1418 +72,14 @@ The following example projects are available:
69
72
  [//]: # (- [example-cli-plugin](https://github.com/flowscripter/example-cli-plugin) is an)
70
73
  [//]: # ( example command and service plugin based on this framework.)
71
74
 
72
- ## Key Concepts
75
+ ## Further Details
73
76
 
74
- The key concepts are:
75
-
76
- - Specific command functionality is implemented within one or more `Command`
77
- instances.
78
- - Generic support functionality is implemented within or or more `Service`
79
- instances.
80
- - The `CLI` is responsible for:
81
- - maintaining a `CommandRegistry` and ensuring the `Command` instances it has
82
- registered are available when scanning and parsing arguments and executing
83
- specified commands.
84
- - maintaining a `ServiceProviderRegistry` and ensuring that services provided
85
- by each of the registered `ServiceProvider` instances are available for use
86
- by any invoked `Command` via a a `Context`.
87
- - Dynamic plugins (enabled by
88
- [dynamic-plugin-framework](https://github.com/flowscripter/dynamic-plugin-framework))
89
- allow:
90
- - dynamic load and import of one or more `CommandFactory` implementations
91
- providing one or more `Command` implementations.
92
- - dynamic load and import of one or more `ServiceProviderFactory`
93
- implementations providing one or more `ServiceProvider` implementations.
94
-
95
- The following high-level conceptual diagram illustrates these relationships:
96
-
97
- ```mermaid
98
- classDiagram
99
- class CLI {
100
- <<interface>>
101
- }
102
-
103
- class CLIConfig {
104
- }
105
-
106
- class Command {
107
- <<interface>>
108
- }
109
-
110
- class ServiceProvider {
111
- <<interface>>
112
- serviceId
113
- servicePriority
114
- }
115
-
116
- class Context {
117
- <<interface>>
118
- }
119
-
120
- class CommandRegistry {
121
- <<interface>>
122
- }
123
-
124
- class ServiceProviderRegistry {
125
- <<interface>>
126
- }
127
-
128
- class Service {
129
- <<interface>>
130
- }
131
-
132
- CLI --> CLIConfig
133
-
134
- CLI --> Context
135
-
136
- CLI --> CommandRegistry
137
-
138
- CommandRegistry --> "*" Command : registers
139
-
140
- ServiceProviderRegistry --> "*" ServiceProvider : registers
141
-
142
- CLI --> "1..*" Command : executes
143
-
144
- Command --> Context : executed within
145
-
146
- Context --> CLIConfig
147
-
148
- Context --> "*" Service : access to
149
-
150
- ServiceProvider --> "*" Service : provides
151
-
152
- class PluginManager {
153
- }
154
-
155
- class Plugin {
156
- <<interface>>
157
- }
158
-
159
- CLI --> PluginManager
160
-
161
- class CommandFactory {
162
- <<interface>>
163
- }
164
-
165
- class ServiceProviderFactory {
166
- <<interface>>
167
- }
168
-
169
- CLI-->ServiceProviderRegistry
170
-
171
- PluginManager --> "*" Plugin : registers
172
-
173
- Plugin --> "*" CommandFactory: implements
174
-
175
- Plugin --> "*" ServiceProviderFactory: implements
176
-
177
- CommandFactory --> "*" Command : provides
178
-
179
- ServiceProviderFactory --> "*" ServiceProvider : provides
180
- ```
181
-
182
- ### Commands
183
-
184
- A `Command` declares:
185
-
186
- - a `name` which is to be used in command line arguments to invoke it.
187
- - a function `execute()` which is invoked if the command is specified. This
188
- function is invoked with the parsed arguments and a context.
189
- - an optional declaration of arguments it supports.
190
-
191
- The sub-types of command are: `GlobalCommand`, `GlobalModifierCommand`,
192
- `SubCommand` and `GroupCommand`.
193
-
194
- The following high-level conceptual diagram illustrates the Command API:
195
-
196
- ```mermaid
197
- classDiagram
198
- class Command {
199
- <<interface>>
200
- name
201
- description
202
- enableConfiguration
203
- }
204
-
205
- class SubCommand {
206
- <<interface>>
207
- execute()
208
- }
209
-
210
- class GroupCommand {
211
- <<interface>>
212
- execute()
213
- }
214
-
215
- class GlobalCommand {
216
- <<interface>>
217
- shortAlias
218
- execute()
219
- }
220
-
221
- class GlobalModifierCommand {
222
- <<interface>>
223
- executePriority
224
- }
225
-
226
- class Argument {
227
- <<interface>>
228
- type
229
- allowableValues
230
- configurationKey
231
- }
232
-
233
- class SubCommandArgument {
234
- name
235
- description
236
- <<interface>>
237
- }
238
-
239
- class Option {
240
- <<interface>>
241
- shortAlias
242
- defaultValue
243
- isOptional
244
- isArray
245
- }
246
-
247
- class Positional {
248
- <<interface>>
249
- isVarArgMultiple
250
- isVarArgOptional
251
- }
252
-
253
- class ComplexOption {
254
- <<interface>>
255
- }
256
-
257
- class GlobalCommandArgument {
258
- <<interface>>
259
- defaultValue
260
- isOptional
261
- }
262
-
263
- class UsageExample {
264
- description
265
- exampleArguments
266
- output
267
- }
268
-
269
- Argument <|-- SubCommandArgument
270
-
271
- Argument <|-- GlobalCommandArgument
272
-
273
- SubCommandArgument <|-- Option
274
-
275
- SubCommandArgument <|-- Positional
276
-
277
- Option <|-- ComplexOption
278
-
279
- Option "1..*" <-- ComplexOption : properties
280
-
281
- Command <|-- GroupCommand
282
-
283
- Option "0..*" <-- SubCommand
284
-
285
- Positional "0..*" <-- SubCommand
286
-
287
- GlobalCommand --> "0..1" GlobalCommandArgument
288
-
289
- Command <|-- SubCommand
290
-
291
- GlobalCommand <|-- GlobalModifierCommand
292
-
293
- Command <|-- GlobalCommand
294
-
295
- GroupCommand --> "1..*" SubCommand : memberSubCommands
296
-
297
- SubCommand --> "0..*" UsageExample
298
- ```
299
-
300
- #### Global Command
301
-
302
- A `GlobalCommand` provides the ability to invoke functionality via a global
303
- argument and one optional value:
304
-
305
- executable --<global_command>[=<value>]
306
-
307
- A concrete example:
308
-
309
- myNetworkApp --help=connect
310
-
311
- A `GlobalCommand` also supports a short character alias:
312
-
313
- executable -<global_command_short_alias>[=<value>]
314
-
315
- A concrete example:
316
-
317
- myNetworkApp -h=connect
318
-
319
- #### Global Modifier Commands
320
-
321
- Any number of `GlobalModifierCommand` instances can be specified as long as they
322
- are accompanied by a `GlobalCommand` or a `SubCommand`:
323
-
324
- executable --<global_modifier_command_1>[=<global_modifier_command_1_argument>] \
325
- --<global_modifier_command_2>[=<global_modifier_command_2_argument>] \
326
- <global_command>[=<value>]
327
-
328
- Each `GlobalModifierCommand` will be executed before the single specified
329
- `GlobalCommand` or `SubCommand` is executed. This behaviour allows
330
- `GlobalModifierCommands` to modify the context in which later commands execute.
331
-
332
- A `GlobalModifierCommand` defines an "execution priority" which is used to
333
- determine the order of execution when multiple `GlobalModifierCommands` are
334
- specified.
335
-
336
- A concrete example:
337
-
338
- myNetworkApp --log-level=DEBUG --config=./config.json --help
339
-
340
- where:
341
-
342
- - `log-level` is a `GlobalModifierCommand` with a value of `DEBUG`
343
- - `config` is a `GlobalModifierCommand` with a value of `./config.json`
344
- - `help` is a `GlobalCommand`.
345
-
346
- #### Sub-Command
347
-
348
- A `SubCommand` provides the ability to invoke functionality via specifying the
349
- command name followed by any number of option and positional arguments.
350
-
351
- A sub-command is invoked as follows:
352
-
353
- executable <sub_command> [sub_command_arguments]
354
-
355
- A concrete example:
356
-
357
- myNetworkApp serve --host=localhost
358
-
359
- #### Group Command
360
-
361
- A `GroupCommand` allows multiple member `SubCommand` instances to be grouped
362
- under a single named group. The name of the `GroupCommand` is specified before
363
- the desired member `SubCommand` in one of two ways:
364
-
365
- executable <group_command> <member_sub_command> [member_sub_command_arguments]
366
- executable <group_command>:<member_sub_command> [member_sub_command_arguments]
367
-
368
- Some concrete examples:
369
-
370
- myNetworkApp utils ping --host=localhost
371
- myNetworkApp utils:ping --host=localhost
372
-
373
- **NOTE**: A `GroupCommand` also provides for it's own logic to be invoked BEFORE
374
- the specified sub-command. However, a `GroupCommand` does not support any
375
- arguments itself.
376
-
377
- ### Arguments
378
-
379
- #### Global Command and Global Modifier Command Arguments
380
-
381
- `GlobalCommand` and `GlobalModifierCommand` instances support the definition of
382
- a single `GlobalCommandArgument` consisting of:
383
-
384
- - a type: `NUMBER`, `INTEGER`, `BOOLEAN`, `STRING` or `SECRET`.
385
- - an optional set of allowable values.
386
- - an optional default value.
387
- - whether the value is mandatory.
388
-
389
- There are four ways in which these argument values can be specified:
390
-
391
- executable --<global_command>=<value>
392
- executable --<global_command> <value>
393
- executable -<global_command_short_alias>=<value>
394
- executable -<global_command_short_alias> <value>
395
-
396
- Some concrete examples:
397
-
398
- myNetworkApp --help=connect
399
- myNetworkApp --help connect
400
- myNetworkApp -h=connect
401
- myNetworkApp -h connect
402
-
403
- ###### Boolean Values
404
-
405
- For boolean options, specifying the value as `true` is not required. All of the
406
- following set the value to `true`:
407
-
408
- executable --<global_command>=true
409
- executable --<global_command> true
410
- executable --<global_command>
411
- executable -<global_command_short_alias>=true
412
- executable -<global_command_short_alias> true
413
- executable -<global_command_short_alias>
414
-
415
- #### Sub-Command Arguments
416
-
417
- Arguments for a `SubCommand` can take two forms: `Option` or `Positional`.
418
-
419
- Common to both are the following features:
420
-
421
- - a name which must consist of alphanumeric non-whitespace ASCII characters or
422
- `_` and `-` characters. It cannot start with `-`.
423
- - a type: `NUMBER`, `INTEGER`, `BOOLEAN`, `STRING` or `SECRET`.
424
- - an optional set of valid value choices.
425
-
426
- ##### Options
427
-
428
- An `option` argument also provides for:
429
-
430
- - a short character alias for the option.
431
- - whether the option is mandatory.
432
- - an optional default value.
433
- - whether the option can be specified more than once (i.e. it is an array value)
434
- - an additional type: `COMPLEX` which allows for nested arguments
435
-
436
- There are four ways in which options can be specified:
437
-
438
- executable <sub_command> --<option_name>=<value>
439
- executable <sub_command> --<option_name> <value>
440
- executable <sub_command> -<option_short_alias>=<value>
441
- executable <sub_command> -<option_short_alias> <value>
442
-
443
- ###### Boolean Values
444
-
445
- For boolean options, specifying the value as `true` is not required. All of the
446
- following set the value to `true`:
447
-
448
- executable <sub_command> --<boolean_option_name>=true
449
- executable <sub_command> --<boolean_option_name> true
450
- executable <sub_command> --<boolean_option_name>
451
- executable <sub_command> -<boolean_option_short_alias>=true
452
- executable <sub_command> -<boolean_option_short_alias> true
453
- executable <sub_command> -<boolean_option_short_alias>
454
-
455
- ###### Array Options
456
-
457
- For array options, multiple values can be specified using explicit indices:
458
-
459
- executable <sub_command> --<array_option_name>[index]=value --<array_option_name>[index]=value
460
-
461
- or with implicit indices:
462
-
463
- executable <sub_command> --<array_option_name>=value --<array_option_name>=value
464
-
465
- Some concrete examples:
466
-
467
- myNetworkApp --bind --interface[0]=eth0 --interface[1]=eth1
468
- myNetworkApp --bind --interface=eth0 --interface=eth1
469
-
470
- **NOTE**: When using explicit indices, the indices may be specified in any
471
- order, however specifying a sparse array is not supported:
472
-
473
- myNetworkApp --bind --interface[2]=eth0 --interface[1]=eth1 --interface[0]=eth2
474
- myNetworkApp --bind --interface[1]=eth0 --interface[2]=eth1 // invalid as index 0 is not specified
475
-
476
- **NOTE**: Arrays of arrays are not supported:
477
-
478
- myNetworkApp --bind --interfaces[0][0]=eth0 // invalid as multi-dimensional arrays are not supported
479
-
480
- ###### Complex Options
481
-
482
- For complex options, the path to the desired property is specified using a `.`
483
- separator:
484
-
485
- executable <sub_command> --<parent_option_name>.<property_name>=<value> --<parent_option_name>.<property_name>.<sub-property-name>=<value>
486
- executable <sub_command> --<parent_option_name>.<property_short_alias>=<value> --<parent_option_short_alias>.<property_name>.<sub-property-name>=<value>
487
-
488
- Mixed use of option names and short aliases is supported when specifying nested
489
- complex option properties. If the property specification starts with the root
490
- options's name then `--` must be used; whereas if it starts with the root
491
- option's short alias then `-` must be used. The following are all equivalent:
492
-
493
- --alpha.beta.gamma=1
494
- --alpha.b.gamma=1
495
- --alpha.b.g=1
496
- -a.beta.gamma=1
497
- -a.beta.g=1
498
- -a.b.g=1
499
-
500
- Implicit and explicit array indexing is supported when specifying nested complex
501
- option properties. However the implicit indexing is only applied to the last
502
- nested property reference. The following are equivalent:
503
-
504
- --foo.bar=1 --foo.bar=2
505
- --foo.bar[0]=1 --foo.bar[1]=2
506
-
507
- If arrays of complex options need to be referenced then explicit indexing is
508
- required. As an example:
509
-
510
- --foo[0].bar=1 --foo[1].bar=2
511
-
512
- Some concrete examples:
513
-
514
- myNetworkApp --connect --address.host=127.0.0.1 --address.port=8080
515
- myNetworkApp --poll --address[0].host=127.0.0.1 --address[0].port=8080 --address[1].host=10.0.10.1 --address[1].port=443
516
-
517
- ##### Positionals
518
-
519
- A `positional` argument is specified by a value which appears at the correct
520
- position in the list of `SubCommand` arguments:
521
-
522
- executable <sub_command_name> <positional_1_value> <positional_2_value>
523
-
524
- A concrete example:
525
-
526
- myHelloWorldApp say hello
527
-
528
- where:
529
-
530
- - `say` is a sub-command.
531
- - `hello` is the value for the first positional argument.
532
-
533
- ###### Varargs Positionals
534
-
535
- A `positional` argument also provides for "varargs" support (both optional and
536
- multiple) which allows for zero, one or more entries:
537
-
538
- **NOTE**: Only one "varargs" `positional` can be defined and it must be the last
539
- positional expected for the command.
540
-
541
- If "varargs" optional is set for `positional_1`, these are both valid:
542
-
543
- executable <sub_command>
544
- executable <sub_command> <positional_1_value_1>
545
-
546
- If "varargs" multiple is set for `positional_1`, these are both valid:
547
-
548
- executable <sub_command> <positional_1_value_1>
549
- executable <sub_command> <positional_1_value_1> <positional_1_value_2>
550
-
551
- If "varargs" optional AND multiple is set for `positional_1`, these are all
552
- valid:
553
-
554
- executable <sub_command>
555
- executable <sub_command> <positional_1_value_1>
556
- executable <sub_command> <positional_1_value_1> <positional_1_value_2> <positional_1_value_3>
557
-
558
- ### Argument Values
559
-
560
- #### Value Types
561
-
562
- The supported value types which can be specified for an `Option`, a `Positional`
563
- or `GlobalCommandArgument` are:
564
-
565
- - `STRING` - a string value where whitespace can be included by using double
566
- quotes e.g. `myHelloWorldApp say "hello world"`
567
- - `NUMBER` - any number value such as `0.01` or `-10`
568
- - `INTEGER` - any positive or negative integer value. Note that these values
569
- will still be stored as a JavaScript number and specification as `INTEGER` is
570
- only used for validation when parsing arguments.
571
- - `BOOLEAN` - string values of `true`, `TRUE`, `false` and `FALSE` are converted
572
- to a JavaScript boolean value. Specifying just the argument name is also
573
- sufficient to indicate a value of `true`.
574
- - `SECRET` - a string value which may be specified as an argument, as a default
575
- value in a configuration file or (ideally) sourced from an environment
576
- variable.
577
-
578
- [//]: # (TODO: remove when PromptService is implemented: https://github.com/flowscripter/dynamic-cli-framework/issues/10)
579
-
580
- **NOTE**: `SECRET` will be more useful as a type when a `PromptService` is
581
- implemented.
582
-
583
- #### Default Values
584
-
585
- Support for reading default argument values from a configuration file and/or
586
- environment variables is provided by `ConfigurationServiceProvider`.
587
-
588
- Usage of configured default values for each `Command` is only enabled if
589
- `Command.enableConfiguration` is `true`.
590
-
591
- ##### Configuration File
592
-
593
- Default values can be stored in a JSON file which contains a top level
594
- `defaults` property. The second level of properties under `defaults` is used to
595
- refer to each `Command` by `Command.name` and the contained values are treated
596
- as command argument values. As an example:
597
-
598
- ```
599
- {
600
- "defaults": {
601
- "subCommand1": {
602
- "arg1": [
603
- 1,
604
- 2
605
- ],
606
- "arg2": {
607
- "arg3": "foo"
608
- }
609
- },
610
- "subCommand2": {
611
- "arg4": true
612
- },
613
- "globalCommand": "globalArgumentValue"
614
- }
615
- }
616
- ```
617
-
618
- The default location of the configuration file is
619
- `$HOME/.<application_name>.json`. If `$HOME` is not defined then no default
620
- configuration will be used. The location of the configuration file can be
621
- modified via the `ConfigCommand` global modifier command.
622
-
623
- As a concrete example, the following command line:
624
-
625
- `myNetworkApp --connect --address.host=127.0.0.1 --address.port=8080`
626
-
627
- is equivalent to specifying default values for the `address.host` and
628
- `address.port` arguments in the configuration file `$HOME/.myNetworkApp.json`:
629
-
630
- ```
631
- {
632
- "defaults": {
633
- "connect": {
634
- "address.host": "127.0.0.1",
635
- "address.port": 8080
636
- }
637
- }
638
- }
639
- ```
640
-
641
- and using the command line:
642
-
643
- `myNetworkApp --connect`
644
-
645
- These defaults can then be overridden on the command line:
646
-
647
- `myNetworkApp --connect --address.host=192.168.1.1`
648
-
649
- ##### Environment Variables
650
-
651
- Default values are also sourced from environment variable values. Any values set
652
- by environment variables will override those sourced from a configuration file.
653
- Any values set on the command line override environment variable and
654
- configuration file values.
655
-
656
- The optional `Argument.configurationKey` value is a configuration key to use for
657
- the argument. It must consist of alphanumeric non-whitespace uppercase ASCII or
658
- `_` characters and must not start with a digit.
659
-
660
- If not specified a default configuration key is determined as follows:
661
-
662
- The `Argument.name` is capitalized and any `-` characters are replaced with `_`
663
- characters. If the result starts with a digit, it is prefixed with `_`. Some
664
- examples:
665
-
666
- - name: `FooBar` => default configuration key: `FOOBAR`
667
- - name: `Hello-World-` => default configuration key: `HELLO_WORLD_`
668
- - name: `3` => default configuration key: `_3`
669
-
670
- **NOTE**: Regardless of whether a `configurationKey` is specified, or the
671
- default is relied upon, it will only be used if the parent `Command` has
672
- `Command.enableConfiguration` specified as `true`.
673
-
674
- The full key for an argument (or a nested option in a complex option) is
675
- determined as follows:
676
-
677
- - Nested argument configuration keys are concatenated with a `_` separator.
678
- - Any arguments which support array values must by suffixed with `_` and an
679
- explicit array index.
680
- - If the root argument in the path does not use a custom
681
- `Argument.configurationKey` then the key path is additionally suffixed with
682
- the `CLIConfig.name` and the `Command.name` with `_` separators.
683
-
684
- This is best explained with examples...
685
-
686
- Examples for no custom configuration key:
687
-
688
- - executable: `MyCLI`, command: `command1`, simple argument: `arg1` =>
689
- environment variable: `MYCLI_COMMAND1_ARG1`
690
- - executable: `MyCLI`, command: `command1`, array argument referring to the 1st
691
- element: `arg2[0]` => environment variable: `MYCLI_COMMAND1_ARG2_0`
692
- - executable: `MyCLI`, command: `command1`, argument name: `3` (this is a digit
693
- so it is by default suffixed with `_`) => environment variable:
694
- `MYCLI_COMMAND1__3`
695
- - executable: `MyCLI`, command: `command1`, nested option: `arg1.arg2` =>
696
- environment variable: `MYCLI_COMMAND1_ARG1_ARG2`
697
- - executable: `MyCLI`, command: `command1`, nested option with both levels being
698
- arrays and referring to the 2nd element of each: `arg1[1].arg2[1]` =>
699
- environment variable: `MYCLI_COMMAND1_ARG1_1_ARG2_1`
700
-
701
- Examples for custom configuration key at the root level (and therefore not
702
- prefixed with CLI and command names):
703
-
704
- - executable: `MyCLI`, command: `command1`, simple argument: `arg1` with
705
- configuration key: `FOO` => environment variable: `FOO`
706
- - executable: `MyCLI`, command: `command1`, array argument referring to the 1st
707
- element: `arg2[0]` and with configuration key: `BAR` => environment variable:
708
- `BAR_0`
709
-
710
- Examples for custom configuration key NOT at the root level (and therefore
711
- prefixed with CLI and command names) examples:
712
-
713
- - executable: `MyCLI`, command: `command1`, nested option: `arg1.arg2` with
714
- `arg2` configuration key: `FOO` => environment variable:
715
- `MYCLI_COMMAND1_ARG1_FOO`
716
- - executable: `MyCLI`, command: `command1`, nested option with both levels being
717
- arrays and referring to the 2nd element of each: `arg1[1].arg2[1]` and with
718
- arg2 configuration key: `BAR` => environment variable:
719
- `MYCLI_COMMAND1_ARG1_1_BAR_1`
720
-
721
- As a concrete example, the following command line:
722
-
723
- `myNetworkApp --connect --address.host=127.0.0.1 --address.port=8080`
724
-
725
- is equivalent to defining the following environment variables:
726
-
727
- - `MYNETWORKAPP_CONNECT_ADDRESS_HOST=127.0.0.1`
728
- - `MYNETWORKAPP_CONNECT_ADDRESS_PORT=8080`
729
-
730
- and using the command line:
731
-
732
- `myNetworkApp --connect`
733
-
734
- **NOTE**: For boolean values, defining the environment variable with ANY value
735
- is equivalent to setting it to `true`. All of the following set the value to
736
- `true`:
737
-
738
- NO_COLOR=1
739
- NO_COLOR=true
740
- NO_COLOR=false // sets the value to true as the environment variable has a value set!
741
-
742
- To specify the value as `false`, set the environment variable to be an empty
743
- string e.g.
744
-
745
- NO_COLOR= // sets the value to false
746
-
747
- #### Configured Value Merging
748
-
749
- Any configured values are merged with parsed values before being validated based
750
- on their associated argument definitions.
751
-
752
- The following logic is applied during merging of configured and parsed values:
753
-
754
- **Configured primitive values are used unless overridden by parsed values**
755
-
756
- Example:
757
-
758
- configured: { "foo": "bar" }
759
- parsed: undefined
760
- result: { "foo": "bar1" }
761
-
762
- Example:
763
-
764
- configured: { "foo": "bar" }
765
- parsed: { "foo": "bar1" }
766
- result: { "foo": "bar1" }
767
-
768
- **Configured complex values are merged (union of unique property names) or
769
- overridden (replacement of duplicate property names) by parsed values**
770
-
771
- Example:
772
-
773
- configured: { "foo": { "a": 1, "b": 2 } }
774
- parsed: { "foo": { "a": 3, "c": 4 } }
775
- result: { "foo": { "a": 1, "b": 2, "c": 4 } }
776
-
777
- **Configured array values are merged (for unique indices) or overridden
778
- (replacement of duplicate indices) by parsed values**
779
-
780
- Example:
781
-
782
- configured: { "foo": [ 0, 1, 2, 3 ] }
783
- parsed: { "foo": [ 0, 4, 2 ] }
784
- result: { "foo": [ 0, 4, 2, 3 ] }
785
-
786
- Example:
787
-
788
- configured: { "foo": [ 0, 1, 2, 3 ] }
789
- parsed: { "foo": [ 0, undefined, 2 ] }
790
- result: { "foo": [ 0, 4, 2, 3 ] }
791
-
792
- Example:
793
-
794
- configured: { "foo": [ { "a": 1 }, { "a": 2 }, { "a": 3 }, { "a": 4 } ] }
795
- parsed: { "foo": [ { "a": 5 }, undefined, { "a": 6, "b": 7 } ] }
796
- result: { "foo": [ { "a": 5 }, { "a": 2 }, { "a": 6, "b": 7 }, { "a": 4 } ] }
797
-
798
- ####
799
-
800
- ### Value Validation
801
-
802
- After parsing of specified arguments, merging with configured defaults and
803
- assigning values to relevant command arguments, values are validated based on
804
- the argument definitions.
805
-
806
- The following scenarios produce validation errors:
807
-
808
- - **Missing Value**: If an argument is not optional and no value has been
809
- provided.
810
- - **Incorrect Value Type**: The value specified was not the correct type for the
811
- argument. e.g. `foo` cannot be provided for a boolean argument, nor for a
812
- complex object argument.
813
- - **Illegal Multiple Values**: The argument does not support multiple values but
814
- multiple values have been provided.
815
- - **Illegal Value**: If the argument defines possible allowable values and the
816
- value provided is not one of these.
817
- - **Illegal Sparse Array**: If multiple values were specified using array
818
- indices and this resulted in empty entries in the array of values.
819
- - **Unknown Property**: If the value provided is for a property which is not
820
- defined on a complex object argument.
821
- - **Nesting Depth Exceeded**: If there is an attempt specify a complex option
822
- property at a nesting depth more than the maximum (10).
823
- - **Array Size Exceeded**: If there is an attempt set more than the maximum
824
- (255) number of values for an array option.
825
- - **Option Is Complex**: If the argument attempts to set a value on a complex
826
- option rather than on a primitive option or a primitive property of a complex
827
- option.
828
-
829
- ## Implementation Details
830
-
831
- ### `launcher`
832
-
833
- The standard way to make use of the framework is to import `launcher` and use
834
- one of the two helper functions it provides:
835
-
836
- - `launchSingleCommandCLI`
837
- - `launchMultiCommandCLI`
838
-
839
- These allow a CLI implementor to specify `Command` instances to use together
840
- with basic CLI details such as name and description.
841
-
842
- ### `CLI`
843
-
844
- The `CLI` interface has the simple responsibility of taking a `CLIConfig` and a
845
- list of user specified command line arguments which it should then parse and
846
- execute any valid specified `Command` it discovers.
847
-
848
- #### `BaseCLI`
849
-
850
- The `BaseCLI` class provides a base implementation of the `CLI` interface which
851
- supports both single command and multiple sub-command CLI scenarios. It provides
852
- the ability to add any number of `Command` and `ServiceProvider` instances.
853
-
854
- If only one command is provided, `BaseCLI` will operate as a single command CLI
855
- and the provided command will be set as a default command. If more than one
856
- command is added, `BaseCLI` will operate as a multi-command CLI. In this case
857
- the default command will be set to a help command. In the case of no command
858
- being specified or a parse error occurring, appropriate help will be displayed.
859
-
860
- By default the `BaseCLI` adds the following `ServiceProvider` implementations
861
- (these are documented in further detail below):
862
-
863
- - `ShutdownServiceProvider` allowing CLI shutdown hooks to be registered.
864
- - `ConfigurationServiceProvider` allowing argument value defaults to be loaded
865
- from a configuration file or environment variables. This also provides a
866
- key-value store service.
867
- - `PrinterServiceProvider` allowing CLI output to stdout and stderr writable
868
- streams.
869
-
870
- By default the `BaseCLI` adds the following `Command` implementations (these are
871
- documented in further detail below):
872
-
873
- - Appropriate help commands depending on whether the CLI is configured with a
874
- single command or multiple sub-command.
875
- - commands provided by the `ConfigurationServiceProvider`.
876
- - commands provided by the `PrinterServiceProvider`.
877
- - `VersionCommand`
878
-
879
- #### `DefaultRuntimeCLI`
880
-
881
- `DefaultRuntimeCLI` is a simple extension to `BaseCLI` which uses NodeJS
882
- specific APIs to access the command line arguments, stdout and stderr streams
883
- and to exit the process.
884
-
885
- ### `runner`
886
-
887
- Core CLI behaviour is provided by a `runner` implementation which is responsible
888
- for parsing arguments, determining which `Command` instances to execute and then
889
- executing them.
890
-
891
- The `runner` implementation supports specification of a default command which
892
- should be executed if no command names are parsed on the command line. In this
893
- scenario, any arguments provided will also be parsed as possible arguments for
894
- the default command.
895
-
896
- The logic for the `runner` is somewhat complex as it allows for the prioritised
897
- execution of `GlobalModifierCommand` instances and the prioritised
898
- initialisation of `ServiceProvider` instances. One reason for this is to allow
899
- the `ConfigurationServiceProvider` to be initialised first and for the resulting
900
- configuration to be available to other `ServiceProvider` instances which are yet
901
- to be initialised.
902
-
903
- The following activity diagram illustrates the `runner` logic:
904
-
905
- ```mermaid
906
- flowchart TD
907
- A([start])
908
-
909
- subgraph 1 [for each ServiceProvider in servicePriority order:]
910
-
911
- B([scan args for provided\nGlobalModifierCommand clauses])
912
-
913
- subgraph 2 [for each discovered clause:]
914
- C([set default\narg values])
915
- D([parse args])
916
- E([add to list of\nGlobalModifierCommands\nclauses to execute])
917
- end
918
-
919
- F([scan default arg values for provided\nGlobalModifierCommand clauses])
920
-
921
- subgraph 3 [for each discovered clause:]
922
- G([parse args])
923
- H([add to list of\nGlobalModifierCommands\nclauses to execute])
924
- end
925
-
926
- I([order GlobalModifierCommands\nclauses by executePriority])
927
-
928
- subgraph 4 [for each discovered clause:]
929
- J([execute GlobalModifierCommand])
930
- end
931
-
932
- K([init service provided by ServiceProvider])
933
- end
934
-
935
- L([scan args for non-ServiceProvider\nGlobalModifierCommand clauses])
936
-
937
- subgraph 5 [for each discovered clause:]
938
- M([set default\narg values])
939
- N([parse args])
940
- O([add to list of\nGlobalModifierCommands\nclauses to execute])
941
- end
942
-
943
- P([scan default arg values for non-ServiceProvider\nGlobalModifierCommand clauses])
944
-
945
- subgraph 6 [for each discovered clause:]
946
- Q([parse args])
947
- R([add to list of\nGlobalModifierCommands\nclauses to execute])
948
- end
949
-
950
- S([order GlobalModifierCommands\nclauses by executePriority])
951
-
952
- subgraph 7 [for each discovered clause:]
953
- T([execute GlobalModifierCommand])
954
- end
955
-
956
- U([scan args for non-modifier Command clause])
957
-
958
- V{clause found?}
959
- V1([set default\narg values])
960
- V2([parse args])
961
- V3([execute\nnon-modifier Command])
962
- W{default\nnon-modifier Command\nprovided?}
963
- W1([scan args for\nnon-modifier Command clause])
964
- W2{clause\nfound?}
965
- W21([set default\narg values])
966
- W22([parse args])
967
- W23{valid?}
968
- W24([execute\nnon-modifier Command])
969
- W3([set default\narg values])
970
- W31{valid?}
971
- W32([execute\nnon-modifier Command])
972
-
973
- Z([end])
974
-
975
- C --> D
976
- D --> E
977
-
978
- G --> H
979
-
980
- M --> N
981
- N --> O
982
-
983
- Q --> R
984
-
985
- A --> 1
986
- B --> 2
987
- 2 --> F
988
- F --> 3
989
- 3 --> I
990
- I --> 4
991
- 4 --> K
992
- 1 --> L
993
- L --> 5
994
- 5 --> P
995
- P --> 6
996
- 6 --> S
997
- S --> 7
998
- 7 --> U
999
- U --> V
1000
- V --> |yes|V1
1001
- V --> |no|W
1002
- W --> |yes|W1
1003
- W --> |no|Z
1004
-
1005
- V1 --> V2
1006
- V2 --> V3
1007
- V3 --> Z
1008
-
1009
- W1 --> W2
1010
- W2 --> |yes|W21
1011
- W2 --> |no|W3
1012
-
1013
- W21 --> W22
1014
- W22 --> W23
1015
-
1016
- W23 --> |yes|W24
1017
- W23 --> |no|Z
1018
-
1019
- W24 --> Z
1020
-
1021
- W3 --> W31
1022
- W31 --> |yes|W32
1023
- W31 --> |no|Z
1024
-
1025
- W32 --> Z
1026
- ```
1027
-
1028
- ### `scanner`
1029
-
1030
- The `runner` defers to a `scanner` implementation which scans arguments for
1031
- potential `CommandClause` instances e.g. a `Command.name` followed by potential
1032
- arguments for that command..
1033
-
1034
- ### `parser`
1035
-
1036
- The `runner` defers to a `parser` implementation which performs the actual
1037
- argument parsing based on the `CommandClause` instances returned from the
1038
- `scanner`.
1039
-
1040
- The following parsing rules apply:
1041
-
1042
- **Arguments Must Follow Command**
1043
-
1044
- All arguments for a command are expected to FOLLOW the command i.e. this is
1045
- **NOT** valid:
1046
-
1047
- executable <sub_command_argument> <sub_command> // not valid
1048
-
1049
- **Arbitrary Option Order**
1050
-
1051
- The order of options for a particular command is not important i.e. these are
1052
- equivalent:
1053
-
1054
- executable <sub_command> --<option_1_name> <option_1_value> --<option_2_name> <option_2_value>
1055
- executable <sub_command> --<option_2_name> <option_2_value> --<option_1_name> <option_1_value>
1056
-
1057
- **Arbitrary Command Order**
1058
-
1059
- The order of commands is not important i.e. these are equivalent:
1060
-
1061
- executable <sub_command> [sub_command_arguments] --<modifier_command_1> [modifier_command_1_arguments] \
1062
- --<modifier_command_2> [modifier_command_2_arguments]
1063
- executable --<modifier_command_1> [modifier_command_1_arguments] <sub_command> [sub_command_arguments] \
1064
- --<modifier_command_2> [modifier_command_2_arguments]
1065
-
1066
- **No Command Interleaving**
1067
-
1068
- Arguments for commands cannot be interleaved with other commands i.e. this is
1069
- **NOT** valid:
1070
-
1071
- executable --<modifier_command> <sub_command> [sub_command_arguments] [modifier_command_arguments] // not valid
1072
-
1073
- **Single Command**
1074
-
1075
- Apart from global modifier commands, there is expected to be only one command
1076
- specified i.e. these will **NOT** work as intended:
1077
-
1078
- // not valid - sub-command 2 and arguments will be treated as trailing arguments of sub-command 1.
1079
- executable <sub_command_1> [sub_command_1_arguments] <sub_command_2> [sub_command_2_arguments]
1080
-
1081
- // not valid - sub-command and arguments will be treated as trailing arguments of global command.
1082
- executable --<global_command> <sub_command> [sub_command_arguments]
1083
-
1084
- **Group Command**
1085
-
1086
- A group command name must always be following immediately by a container
1087
- sub-command name i.e. these are **NOT** valid:
1088
-
1089
- executable <member_sub_command> <group_command> // not valid
1090
- executable <group_command> <global_command> <member_sub_command> // not valid
1091
-
1092
- **Unused Leading and Trailing Arguments**
1093
-
1094
- Any leading arguments which appear BEFORE an identified command name are
1095
- retained. Any trailing arguments which appear after an identified name which are
1096
- not consumed when parsing the command arguments are also retained.
1097
-
1098
- Once a command has been identified and parsed any retained arguments are
1099
- considered unused and a warning is output.
1100
-
1101
- If a command is NOT identified, any retained arguments are considered potential
1102
- arguments for a default command if it has been configured. This behaviour means
1103
- the following are equivalent:
1104
-
1105
- executable <default_command_argument> --<modifier_command_name> <modifier_command_argument>
1106
- executable --<modifier_command_name> <modifier_command_argument> <default_command_argument>
1107
-
1108
- ### Command Execution
1109
-
1110
- A `Command` is executed via the implemented function:
1111
-
1112
- execute(argumentValues: ArgumentValues, context: Context): Promise<void>;
1113
-
1114
- The `Context` instance allow access to the `CLIConfig` and the ability to access
1115
- services by registered service IDs.
1116
-
1117
- The `ArgumentValues` instance provides access to the populated and validated
1118
- arguments for the command. These values are provided either:
1119
-
1120
- - as a single key-value pair in the form `commandName: globalArgumentValue` for
1121
- a `GlobalCommand` or a `GlobalModifierCommand`.
1122
- - as a complex nested key-value structure mirroring the defined `Option` and
1123
- `Positional` instances of a `SubCommand`
1124
-
1125
- As an example, if a `GlobalModifierCommand` is defined as follows:
1126
-
1127
- ```
1128
- const globalModifierCommand: GlobalModifierCommand = {
1129
- name: "log-level",
1130
- argument: {
1131
- name: "level",
1132
- type: ArgumentValueTypeName.STRING,
1133
- },
1134
- executePriority: 1,
1135
- execute: (argumentValues: ArgumentValues, context: Context) => Promise.resolve()
1136
- };
1137
- ```
1138
-
1139
- and a `SubCommand` is defined as follows:
1140
-
1141
- ```
1142
- const subCommand: SubCommand = {
1143
- name: "connect",
1144
- options: [
1145
- {
1146
- name: "address",
1147
- type: ComplexValueTypeName.COMPLEX,
1148
- properties: [
1149
- {
1150
- name: "host",
1151
- type: ArgumentValueTypeName.STRING
1152
- },
1153
- {
1154
- name: "port",
1155
- type: ArgumentValueTypeName.NUMBER
1156
- }
1157
- ]
1158
- }
1159
- ],
1160
- positionals: [
1161
- {
1162
- name: "retryOnError",
1163
- type: ArgumentValueTypeName.BOOLEAN
1164
- }
1165
- ],
1166
- execute: (argumentValues: ArgumentValues, context: Context) => Promise.resolve()
1167
- };
1168
- ```
1169
-
1170
- when the following command line arguments are specified:
1171
-
1172
- `myNetworkApp --connect --address.host=127.0.0.1 --address.port=8080 true --log-level=DEBUG`
1173
-
1174
- then the `ArgumentValues` passed to the `globalModifierCommand.execute(...)`
1175
- function would be:
1176
-
1177
- ```
1178
- {
1179
- "log-level": "DEBUG"
1180
- }
1181
- ```
1182
-
1183
- and the `ArgumentValues` passed to the `subCommand.execute(...)` function would
1184
- be:
1185
-
1186
- ```
1187
- {
1188
- address: {
1189
- host: "127.0.0.1",
1190
- port: 8080
1191
- },
1192
- retryOnError: true
1193
- }
1194
- ```
1195
-
1196
- ### Core Service Providers
1197
-
1198
- The core `ServiceProvider` implementations (and the service and `Command`
1199
- implementations they provide) built into the framework are:
1200
-
1201
- #### `BannerServiceProvider`
1202
-
1203
- On initialisation this uses the `PrinterService` to output the CLI name in ASCII
1204
- banner text together with the CLI description and version.
1205
-
1206
- Provides:
1207
-
1208
- - `NoBannerCommand` allowing banner printing to be disabled via the argument
1209
- `--no-banner` or the env var `NO_BANNER`.
1210
-
1211
- #### `ConfigurationServiceProvider`
1212
-
1213
- Provides:
1214
-
1215
- - `KeyValueService` allowing the storage and retrieval of key value pairs scoped
1216
- to the current `Command` or service being executed. The values are persisted
1217
- to the CLI configuration file.
1218
- - `ConfigCommand` allowing the default location of the configuration file to be
1219
- overridden via the argument `--config` or the env var `CONFIG_LOCATION`.
1220
- - `DumpConfigCommand` a global command which dumps the full CLI configuration to
1221
- stdout via `--dump-config`.
1222
-
1223
- #### `ShutdownServiceProvider`
1224
-
1225
- Provides:
1226
-
1227
- - `ShutdownService` allowing registration of callbacks for CLI shutdown.
1228
-
1229
- #### `PrinterServiceProvider`
1230
-
1231
- Provides:
1232
-
1233
- - `DefaultPrinterService` allowing color output to stdout, stderr, management of
1234
- log levels and widgets such as a spinner and progress bars.
1235
- - `DarkModeCommand` which allows dark or light mode to be enabled via the
1236
- argument `--dark-mode` or the env var `DARK_MODE`.
1237
- - `NoColorCommand` which allows color output to be disabled via the argument
1238
- `--no-color` or the env var `NO_COLOR`.
1239
- - `LogLevelCommand` which allows the log level to be set via the argument
1240
- `--log-level` or the env var `LOG_LEVEL`.
1241
-
1242
- #### `PrettyPrinterServiceProvider`
1243
-
1244
- Provides:
1245
-
1246
- - `PrettyPrinterService` allowing pretty printing of structured data and source
1247
- code. JSON pretty printing is provided by default and other data or language
1248
- formats can be added on demand by commands.
1249
-
1250
- #### `SyntaxHighlighterServiceProvider`
1251
-
1252
- Provides:
1253
-
1254
- - `SyntaxHighlighterService` allowing ANSI based color highlighting of
1255
- structured data and source code. JSON highlighting is provided by default and
1256
- other data or language formats can be added on demand by commands.
1257
-
1258
- Note that the `SyntaxHighlighterService` has no effect if the
1259
- `DefaultPrinterService` is configured to disable color output.
1260
-
1261
- #### `AsciiBannerGeneratorServiceProvider`
1262
-
1263
- Provides:
1264
-
1265
- - `AsciiBannerGeneratorService` allowing messages to be rendered using ASCII
1266
- banner [FIGlet](http://www.figlet.org) fonts. The FIGlet "standard" font is
1267
- provided by default and other fonts can be added on demand by commands.
1268
-
1269
- ### Core Commands
1270
-
1271
- The core `Command` implementations provided with the framework are:
1272
-
1273
- #### `MultiCommandCliHelpGlobalCommand`
1274
-
1275
- Implementation of multi-command CLI help. Some examples:
1276
-
1277
- - `myCli --help`
1278
- - `myCli -h`
1279
- - `myCli --help <command>`
1280
- - `myCli -h <command>`
1281
-
1282
- #### `MultiCommandCliHelpSubCommand`
1283
-
1284
- Implementation of multi-command CLI help. Some examples:
1285
-
1286
- - `myCli help`
1287
- - `myCli help <command>`
1288
-
1289
- #### `SingleCommandCliHelpGlobalCommand`
1290
-
1291
- Implementation of single (default) command CLI help. Some examples:
1292
-
1293
- - `myCli --help`
1294
- - `myCli -h`
1295
-
1296
- #### `SingleCommandCliHelpSubCommand`
1297
-
1298
- Implementation of single (default) command CLI help. An example:
1299
-
1300
- - `myCli help`
1301
-
1302
- #### `UsageCommand`
1303
-
1304
- Implementation which prints basic CLI usage instructions.
1305
-
1306
- #### `VersionCommand`
1307
-
1308
- Implementation which prints the version of the CLI.
1309
-
1310
- ## API
1311
-
1312
- API docs for the library:
1313
-
1314
- [API Documentation](https://jsr.io/@flowscripter/dynamic-cli-framework/doc)
1315
-
1316
- ## Development
1317
-
1318
- Install dependencies:
1319
-
1320
- `bun install`
1321
-
1322
- Test:
1323
-
1324
- `bun test`
1325
-
1326
- **NOTE**: The following tasks use Deno as it excels at these and Bun does not
1327
- currently provide such functionality:
1328
-
1329
- Format:
1330
-
1331
- `deno fmt`
1332
-
1333
- Lint:
1334
-
1335
- `deno lint index.ts src/ tests/`
1336
-
1337
- Generate HTML API Documentation:
1338
-
1339
- `deno doc --html --name=template-bun-library index.ts`
1340
-
1341
- The following diagram provides an overview of the main internal modules and
1342
- classes:
1343
-
1344
- ```mermaid
1345
- classDiagram
1346
-
1347
- class parser {
1348
- }
1349
-
1350
- class scanner {
1351
- }
1352
-
1353
- class Context {
1354
- <<interface>>
1355
- }
1356
-
1357
- class CLI {
1358
- <<interface>>
1359
- }
1360
-
1361
- class runner {
1362
- }
1363
-
1364
- class BaseCLI {
1365
- }
1366
-
1367
- class Command {
1368
- <<interface>>
1369
- }
1370
-
1371
- class DefaultCommandRegistry {
1372
- }
1373
-
1374
- class launcher {
1375
- }
1376
-
1377
- class DefaultServiceProviderRegistry {
1378
- }
1379
-
1380
- class ServiceProvider {
1381
- <<interface>>
1382
- }
1383
-
1384
- class DefaultContext {
1385
- }
1386
-
1387
- class CommandRegistry {
1388
- <<interface>>
1389
- }
1390
-
1391
- class ServiceProviderRegistry {
1392
- <<interface>>
1393
- }
1394
-
1395
- class DefaultRuntimeCLI {
1396
- }
1397
-
1398
- class CLIConfig {
1399
- }
1400
-
1401
- class ServiceXYZ {
1402
- }
1403
-
1404
- runner --> scanner
1405
-
1406
- runner --> parser
1407
-
1408
- CLI <|.. BaseCLI
1409
-
1410
- BaseCLI <|-- DefaultRuntimeCLI
1411
-
1412
- runner --> Context
1413
-
1414
- runner --> CommandRegistry
1415
-
1416
- runner --> ServiceProviderRegistry
1417
-
1418
- ServiceProviderRegistry <|.. DefaultServiceProviderRegistry
1419
-
1420
- ServiceProviderRegistry --> "0..*" ServiceProvider : registers
1421
-
1422
- BaseCLI --> DefaultCommandRegistry
1423
-
1424
- CommandRegistry <|.. DefaultCommandRegistry
1425
-
1426
- CommandRegistry --> "0..*" Command : registers
1427
-
1428
- runner --> "0..1" Command : defaultCommand
1429
-
1430
- Context <|.. DefaultContext
1431
-
1432
- BaseCLI --> DefaultContext
1433
-
1434
- BaseCLI --> DefaultServiceProviderRegistry
1435
-
1436
- Context --> CLIConfig
1437
-
1438
- BaseCLI --> CLIConfig
1439
-
1440
- Context --> ServiceProviderRegistry
1441
-
1442
- ServiceProvider --> ServiceXYZ : provides
1443
-
1444
- Context --> ServiceXYZ : access to
1445
-
1446
- DefaultRuntimeCLI <-- launcher
1447
- ```
1448
-
1449
- ### Debug Logging
1450
-
1451
- Internal framework logging can be enabled by setting the
1452
- `DYNAMIC_CLI_FRAMEWORK_DEBUG` environment variable.
1453
-
1454
- The logging implementation will look for an object conforming to the `Logger`
1455
- interface and use it if found. If not found, a simple logging implementation
1456
- using the `console` object will be used.
1457
-
1458
- ### Command and Service Validation
1459
-
1460
- By default, commands and services that are built into the CLI or provided by
1461
- already installed plugins are not validated as they are loaded. The only
1462
- validation that takes place is for commands or services provided by plugins
1463
- BEFORE they are installed.
1464
-
1465
- When using `launcher.ts` runtime validation of all commands and services can be
1466
- forced by defining the `DYNAMIC_CLI_FRAMEWORK_VALIDATE_ALL` environment
1467
- variable.
1468
-
1469
- Command validation includes:
1470
-
1471
- - ensuring multiple commands in the CLI do not have duplicate names or short
1472
- aliases.
1473
- - command options for each command do not include duplicate names or short
1474
- aliases.
1475
- - default values for options have the type specified by the option.
1476
- - default values for options match an allowable value if specified by the
1477
- option.
1478
- - default array values for options are specified only for array options.
1479
- - command options for each command do not include duplicate names or short
1480
- aliases.
1481
- - only the last positional for a command is defined as a vararg.
1482
- - default values for complex options match the nested property hierarchy.
1483
- - paths to nested properties of complex options are unique.
77
+ - [Key Concepts](./README/key-concepts.md)
78
+ - [Implementation Details](./README/implementation-details.md)
79
+ - [Core Service Providers](./README/core-service-providers.md)
80
+ - [Core Commands](./README/core-commands.md)
81
+ - [Development](./README/development.md)
82
+ - [API Documentation](https://flowscripter.github.io/dynamic-cli-framework/index.html)
1484
83
 
1485
84
  ## License
1486
85