@flowscripter/dynamic-cli-framework 6.0.1 → 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 +2 -2
  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,19 +5,36 @@ import { Stats } from "node:fs";
5
5
  import ConfigCommand from "./command/ConfigCommand.js";
6
6
  import getLogger from "../../util/logger.js";
7
7
  import DumpConfigCommand from "./command/DumpConfigCommand.js";
8
- import { KEY_VALUE_SERVICE_ID, SECRET_SENTINEL_PREFIX, } from "@flowscripter/dynamic-cli-framework-api";
9
- import DefaultKeyValueService from "./DefaultKeyValueService.js";
8
+ import { CONFIGURATION_SERVICE_ID, SECRET_SENTINEL_PREFIX, } from "@flowscripter/dynamic-cli-framework-api";
10
9
  import DefaultSecretService from "./DefaultSecretService.js";
11
10
  import resolveSecrets from "./resolveSecrets.js";
12
11
  import argumentValueMerge from "../../runtime/values/argumentValueMerge.js";
13
12
  import { isGlobalCommand, isGlobalModifierCommand, isSubCommand, } from "../../runtime/command/CommandTypeGuards.js";
14
13
  import { getGlobalCommandValueFromEnvVars, getSubCommandValuesFromEnvVars, } from "../../util/envVarHelper.js";
15
14
  const logger = getLogger("ConfigurationServiceProvider");
15
+ class ConfigurationServiceImpl {
16
+ #provider;
17
+ constructor(provider) {
18
+ this.#provider = provider;
19
+ }
20
+ get configLocation() {
21
+ return this.#provider.configLocation;
22
+ }
23
+ setConfigLocation(location) {
24
+ this.#provider.setConfigLocation(location);
25
+ }
26
+ }
16
27
  /**
17
- * Provides:
28
+ * Provides configuration of default command arguments using a configuration file and/or
29
+ * environment variables.
18
30
  *
19
- * * Configuration of default command arguments using a configuration file and/or environment variables.
20
- * * A generic key-value store for commands and services ({@link KeyValueService}).
31
+ * The same configuration file also backs the raw, per-scope key-value data consumed by
32
+ * {@link KeyValueServiceProvider} (which holds a direct reference to this provider - see
33
+ * {@link getKeyValueData} and {@link flushIfDirty} - rather than looking this provider up via
34
+ * {@link Context}, since only {@link configLocation}/{@link setConfigLocation} are safe to expose
35
+ * generally; {@link getConfigString} is wired directly to {@link DumpConfigCommand} for the same
36
+ * reason - a full config dump would leak every other command's/service's key-value data if it
37
+ * were reachable through a general lookup).
21
38
  *
22
39
  * **Default Command Arguments**
23
40
  *
@@ -62,7 +79,8 @@ const logger = getLogger("ConfigurationServiceProvider");
62
79
  * NOTE: You may store default arguments for secrets by manually configuring secrets in your OS
63
80
  * credential store and referencing them in the config file using the sentinel format
64
81
  * `__SECRET__:<bun_secret_name>`. These values will be resolved from the OS secret store
65
- * before being returned as default argument values. See "Generic Key-Value Service" below.
82
+ * before being returned as default argument values. See {@link KeyValueServiceProvider} for the
83
+ * generic key-value store which uses the same secret sentinel mechanism.
66
84
  *
67
85
  * *Environment Variables*
68
86
  *
@@ -100,67 +118,12 @@ const logger = getLogger("ConfigurationServiceProvider");
100
118
  * * 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`
101
119
  *
102
120
  * NOTE: Any default values from the above configuration sources will be overridden by any arguments provided on the command line.
103
- *
104
- * **Generic Key-Value Service**
105
- *
106
- * The same configuration file above is used to provide storage for the provided {@link KeyValueService}. The values
107
- * are stored under a top level `key-values` property. These are expected to not be modified by the user of the CLI.
108
- *
109
- * The second and third level of properties is used to scope the key-values to specific command names
110
- * (via {@link Command.name} values) and specific service IDs (via {@link ServiceProvider.serviceId} values).
111
- * Keys are strings, but values are arbitrary JSON (see {@link ValueNode}) - not limited to strings -
112
- * and may be deep objects or arrays.
113
- *
114
- * Any node within a value passed to {@link KeyValueService.set} can be wrapped in {@link Secret} -
115
- * at any depth - to have that node, and only that node, stored as an OS-native secret. When a node
116
- * is wrapped in {@link Secret}, its value (of any shape) is JSON-serialized and stored as a single
117
- * OS-native secret via Bun.secrets, with a sentinel value of the format `__SECRET__:<bun_secret_name>`
118
- * substituted in its place in the structure that gets stored in the config file. Everything else in
119
- * the value is stored as plain (unencrypted) config data. The sentinel prefix `__SECRET__:` is
120
- * reserved and must not be used for regular key-value data.
121
- *
122
- * Independently of how a value was written, {@link KeyValueService.get} recursively resolves any
123
- * string leaf - at any depth within the retrieved value - which starts with the sentinel prefix,
124
- * via the OS secret store. This means a secret reference may also be hand-embedded (nested
125
- * arbitrarily deep) directly within a plain, non-secret value in the config file.
126
- *
127
- * Secret support requires `secretServiceEnabled=true` in the constructor (which also requires
128
- * `configEnabled=true`).
129
- *
130
- * As an example:
131
- * ```
132
- * {
133
- * "defaults": {
134
- * ...
135
- * },
136
- * "key-values": {
137
- * "commands": {
138
- * "command1": {
139
- * "foo1": "bar1",
140
- * "foo2": "__SECRET__:command_command1_foo2",
141
- * "foo3": {
142
- * "nested": ["a", "__SECRET__:command_command1_foo3_nested"]
143
- * }
144
- * },
145
- * "command2": {
146
- * "foo1": "bar3"
147
- * }
148
- * },
149
- * "services": {
150
- * "service-id-1": {
151
- * "foo1": "bar"
152
- * }
153
- * }
154
- * }
155
- * }
156
- * ```
157
121
  */
158
122
  export default class ConfigurationServiceProvider {
159
- serviceId = KEY_VALUE_SERVICE_ID;
123
+ serviceId = CONFIGURATION_SERVICE_ID;
160
124
  servicePriority;
161
125
  envVarsEnabled;
162
126
  configEnabled;
163
- keyValueServiceEnabled;
164
127
  secretServiceEnabled;
165
128
  // the location of the currently managed configuration data
166
129
  configLocation;
@@ -168,55 +131,52 @@ export default class ConfigurationServiceProvider {
168
131
  // SingleValueType is for GlobalCommandArgument values, Values is for
169
132
  // SubCommandArgument values.
170
133
  defaultsData = new Map();
171
- // the configuration data to be used by the CLI runner implementation
172
- // when updating command scoped access to key-value data via the key-value service.
134
+ // the underlying per-scope key-value data, read from/written to the configuration file. Exposed
135
+ // (raw, not wrapped in a KeyValueService) only via getKeyValueData(), for KeyValueServiceProvider's
136
+ // exclusive use.
173
137
  #commandKeyValueData = new Map();
174
- // the configuration data to be used by the CLI runner implementation
175
- // when updating service scoped access to key-value data via the key-value service.
176
138
  #serviceKeyValueData = new Map();
177
- // the optional service providing scope limited key-value data to commands and other services
178
- #defaultKeyValueService;
179
- // the optional service providing OS-native secret storage
180
- #defaultSecretService;
181
- #currentCommandNameKeyValueScope;
182
- #currentServiceIdKeyValueScope;
139
+ // secret service used only to resolve secret sentinels embedded in default command argument
140
+ // values (getDefaultArgumentValues()) - unrelated to per-command/per-service KV scoping, so it
141
+ // uses its own fixed, dedicated scope prefix ("defaults"). Its scope is otherwise irrelevant,
142
+ // since only getSecret() (which doesn't consult scope) is ever called on it.
143
+ #defaultsSecretService;
144
+ /**
145
+ * A {@link ConfigurationService} view of this provider, registered in the {@link Context} under
146
+ * {@link CONFIGURATION_SERVICE_ID}. Its `configLocation` getter always reflects this provider's
147
+ * current value.
148
+ */
149
+ #configurationService;
183
150
  /**
184
151
  * Create an instance of the service provider with the specified details.
185
152
  *
186
153
  * @param servicePriority the priority of the service.
187
154
  * @param envVarsEnabled optionally support checking env variables for default argument values.
188
155
  * @param configEnabled optionally enable configuration file support for default argument values.
189
- * @param keyValueServiceEnabled optionally provide a {@link KeyValueService} implementation: `configEnabled` must be true in this case
190
- * @param secretServiceEnabled optionally enable OS-native secret storage via Bun.secrets: `configEnabled` must be true in this case
156
+ * @param secretServiceEnabled optionally enable OS-native secret storage via Bun.secrets for resolving
157
+ * secrets embedded in default argument values: `configEnabled` must be true in this case
191
158
  */
192
- constructor(servicePriority, envVarsEnabled = false, configEnabled = false, keyValueServiceEnabled = false, secretServiceEnabled = false) {
193
- if (!configEnabled && keyValueServiceEnabled) {
194
- throw new Error("configEnabled must be true if keyValueServiceEnabled is true");
195
- }
159
+ constructor(servicePriority, envVarsEnabled = false, configEnabled = false, secretServiceEnabled = false) {
196
160
  if (!configEnabled && secretServiceEnabled) {
197
161
  throw new Error("configEnabled must be true if secretServiceEnabled is true");
198
162
  }
199
163
  this.servicePriority = servicePriority;
200
164
  this.envVarsEnabled = envVarsEnabled;
201
165
  this.configEnabled = configEnabled;
202
- this.keyValueServiceEnabled = keyValueServiceEnabled;
203
166
  this.secretServiceEnabled = secretServiceEnabled;
167
+ this.#configurationService = new ConfigurationServiceImpl(this);
204
168
  }
205
169
  getServiceInfo(cliConfig) {
206
170
  const commands = [];
207
171
  if (this.configEnabled) {
208
- commands.push(new ConfigCommand(this, this.servicePriority));
209
- commands.push(new DumpConfigCommand(this));
172
+ commands.push(new ConfigCommand(this.servicePriority));
173
+ commands.push(new DumpConfigCommand(() => this.getConfigString()));
210
174
  }
211
175
  if (this.secretServiceEnabled) {
212
- this.#defaultSecretService = new DefaultSecretService(cliConfig.name);
213
- }
214
- if (this.keyValueServiceEnabled || this.secretServiceEnabled) {
215
- this.#defaultKeyValueService = new DefaultKeyValueService(this.#defaultSecretService);
176
+ this.#defaultsSecretService = new DefaultSecretService(cliConfig.name, "defaults");
216
177
  }
217
178
  return Promise.resolve({
218
- // this may be undefined if keyValueServiceEnabled and secretServiceEnabled are false
219
- service: this.#defaultKeyValueService,
179
+ service: this.#configurationService,
220
180
  // this may be empty if configEnabled is false
221
181
  commands,
222
182
  });
@@ -266,9 +226,9 @@ export default class ConfigurationServiceProvider {
266
226
  result = argumentValueMerge(envVarValues, configuredValues);
267
227
  }
268
228
  }
269
- if (result !== undefined && this.#defaultSecretService) {
229
+ if (result !== undefined && this.#defaultsSecretService) {
270
230
  result = await resolveSecrets(result, async (bunSecretName) => {
271
- const secretValue = await this.#defaultSecretService.getSecret(bunSecretName);
231
+ const secretValue = await this.#defaultsSecretService.getSecret(bunSecretName);
272
232
  if (secretValue === null) {
273
233
  throw new Error(`Secret not found in OS secret store for sentinel: '${SECRET_SENTINEL_PREFIX}${bunSecretName}'`);
274
234
  }
@@ -278,89 +238,34 @@ export default class ConfigurationServiceProvider {
278
238
  return result;
279
239
  }
280
240
  /**
281
- * Indicate that the contained {@link DefaultKeyValueService} should be set to have a scope of the provided
282
- * command name.
241
+ * Get-or-create the underlying, raw {@link ValueNode} `Map` for the given scope - for
242
+ * {@link KeyValueServiceProvider}'s exclusive use, via its direct reference to this provider.
283
243
  *
284
- * @param commandName the name of the command to scope the key-value data to.
285
- *
286
- * @throws {Error} if an existing scope is set.
244
+ * @param scopeType whether `scopeKey` is a command name or a service/task ID.
245
+ * @param scopeKey the command name or service/task ID to scope the key-value data to.
287
246
  */
288
- setCommandKeyValueScope(commandName) {
289
- if (!this.keyValueServiceEnabled && !this.secretServiceEnabled) {
290
- throw new Error(`Attempt to use KeyValueService/SecretService which is not enabled`);
291
- }
292
- if (this.#currentCommandNameKeyValueScope) {
293
- throw new Error(`Attempt to set CommandNameKeyValueScope to ${commandName} when it is currently set to ${this.#currentCommandNameKeyValueScope}`);
294
- }
295
- if (this.#currentServiceIdKeyValueScope) {
296
- throw new Error(`Attempt to set CommandNameKeyValueScope to ${commandName} when ServiceIdKeyValueScope is currently set to ${this.#currentCommandNameKeyValueScope}`);
297
- }
298
- this.#currentCommandNameKeyValueScope = commandName;
299
- logger.debug("currentCommandNameKeyValueScope: %s", commandName);
300
- if (this.#defaultKeyValueService) {
301
- if (!this.#commandKeyValueData.has(commandName)) {
302
- this.#commandKeyValueData.set(commandName, new Map());
303
- }
304
- this.#defaultKeyValueService.setKeyValueData(this.#commandKeyValueData.get(commandName));
305
- }
306
- if (this.#defaultSecretService) {
307
- this.#defaultSecretService.setScope("command_" + commandName);
308
- }
309
- }
310
- /**
311
- * Indicate that the contained {@link DefaultKeyValueService} should be set to have a scope of the provided
312
- * service ID.
313
- *
314
- * @param serviceId the ID of the service to scope the key-value data to.
315
- *
316
- * @throws {Error} if an existing scope is set.
317
- */
318
- setServiceKeyValueScope(serviceId) {
319
- if (!this.keyValueServiceEnabled && !this.secretServiceEnabled) {
320
- throw new Error(`Attempt to use KeyValueService/SecretService which is not enabled`);
321
- }
322
- if (this.#currentServiceIdKeyValueScope) {
323
- throw new Error(`Attempt to set ServiceIdKeyValueScope to ${serviceId} when it is currently set to ${this.#currentServiceIdKeyValueScope}`);
324
- }
325
- if (this.#currentCommandNameKeyValueScope) {
326
- throw new Error(`Attempt to set ServiceIdKeyValueScope to ${serviceId} when CommandNameKeyValueScope is currently set to ${this.#currentServiceIdKeyValueScope}`);
327
- }
328
- this.#currentServiceIdKeyValueScope = serviceId;
329
- logger.debug("currentServiceIdKeyValueScope: %s", serviceId);
330
- if (this.#defaultKeyValueService) {
331
- if (!this.#serviceKeyValueData.has(serviceId)) {
332
- this.#serviceKeyValueData.set(serviceId, new Map());
333
- }
334
- this.#defaultKeyValueService.setKeyValueData(this.#serviceKeyValueData.get(serviceId));
335
- }
336
- if (this.#defaultSecretService) {
337
- this.#defaultSecretService.setScope("service_" + serviceId);
247
+ getKeyValueData(scopeType, scopeKey) {
248
+ const keyValueData = scopeType === "command" ? this.#commandKeyValueData : this.#serviceKeyValueData;
249
+ if (!keyValueData.has(scopeKey)) {
250
+ keyValueData.set(scopeKey, new Map());
338
251
  }
252
+ return keyValueData.get(scopeKey);
339
253
  }
340
254
  /**
341
- * Indicate that the contained {@link DefaultKeyValueService} should have any current scope cleared.
255
+ * Write the configuration file, if `isDirty` is true - for {@link KeyValueServiceProvider}'s
256
+ * exclusive use, via its direct reference to this provider, called from its shutdown-time
257
+ * flush task.
342
258
  *
343
- * If a scope is currently set and changes have been made to the key-value data, they will be written
344
- * to the current configuration location.
259
+ * @param isDirty whether any scoped KeyValueService has unwritten changes.
345
260
  */
346
- async clearKeyValueScope() {
347
- if (!this.keyValueServiceEnabled && !this.secretServiceEnabled) {
348
- throw new Error(`Attempt to use KeyValueService/SecretService which is not enabled`);
349
- }
350
- if (this.#defaultKeyValueService && this.#defaultKeyValueService.isDirty()) {
351
- if (this.configLocation === undefined) {
352
- throw new Error("Attempt to write updated config with no configLocation set");
353
- }
354
- await this.#writeConfig(this.configLocation);
355
- }
356
- this.#currentCommandNameKeyValueScope = undefined;
357
- this.#currentServiceIdKeyValueScope = undefined;
358
- if (this.#defaultKeyValueService) {
359
- this.#defaultKeyValueService.clearKeyValueData();
261
+ async flushIfDirty(isDirty) {
262
+ if (!isDirty) {
263
+ return;
360
264
  }
361
- if (this.#defaultSecretService) {
362
- this.#defaultSecretService.clearScope();
265
+ if (this.configLocation === undefined) {
266
+ throw new Error("Attempt to write updated config with no configLocation set");
363
267
  }
268
+ await this.#writeConfig(this.configLocation);
364
269
  }
365
270
  async initService(context) {
366
271
  if (!this.configEnabled) {
@@ -1,10 +1,13 @@
1
1
  import type { ValueNode, KeyValueService, SettableValueNode } from "@flowscripter/dynamic-cli-framework-api";
2
2
  import type DefaultSecretService from "./DefaultSecretService.ts";
3
+ /**
4
+ * Bound permanently to a single scope's data at construction - never re-pointed afterward. Create
5
+ * a separate instance per scope (see {@link KeyValueServiceProvider.getScopedKeyValueService}),
6
+ * rather than sharing one instance across scopes.
7
+ */
3
8
  export default class DefaultKeyValueService implements KeyValueService {
4
9
  #private;
5
- constructor(secretService?: DefaultSecretService);
6
- setKeyValueData(keyValueData: Map<string, ValueNode>): void;
7
- clearKeyValueData(): void;
10
+ constructor(keyValueData: Map<string, ValueNode>, secretService?: DefaultSecretService);
8
11
  isDirty(): boolean;
9
12
  get<T extends ValueNode = ValueNode>(key: string): Promise<T>;
10
13
  has(key: string): Promise<boolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"DefaultKeyValueService.d.ts","sourceRoot":"","sources":["../../../../src/service/configuration/DefaultKeyValueService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,EACT,eAAe,EACf,iBAAiB,EAClB,MAAM,yCAAyC,CAAC;AAEjD,OAAO,KAAK,oBAAoB,MAAM,2BAA2B,CAAC;AAGlE,MAAM,CAAC,OAAO,OAAO,sBAAuB,YAAW,eAAe;;IAKpE,YAAY,aAAa,CAAC,EAAE,oBAAoB,EAE/C;IAEM,eAAe,CAAC,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,QAM1D;IAEM,iBAAiB,SAGvB;IAEM,OAAO,IAAI,OAAO,CAExB;IAEY,GAAG,CAAC,CAAC,SAAS,SAAS,GAAG,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAyBzE;IAEM,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAKxC;IAEY,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAMrE;IAsCY,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAU9C;CAsBF"}
1
+ {"version":3,"file":"DefaultKeyValueService.d.ts","sourceRoot":"","sources":["../../../../src/service/configuration/DefaultKeyValueService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,EACT,eAAe,EACf,iBAAiB,EAClB,MAAM,yCAAyC,CAAC;AAEjD,OAAO,KAAK,oBAAoB,MAAM,2BAA2B,CAAC;AAGlE;;;;GAIG;AACH,MAAM,CAAC,OAAO,OAAO,sBAAuB,YAAW,eAAe;;IAKpE,YAAY,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE,aAAa,CAAC,EAAE,oBAAoB,EAGrF;IAEM,OAAO,IAAI,OAAO,CAExB;IAEY,GAAG,CAAC,CAAC,SAAS,SAAS,GAAG,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAsBzE;IAEM,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAExC;IAEY,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAGrE;IAsCY,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAO9C;CAsBF"}
@@ -1,30 +1,22 @@
1
1
  import { Secret, SECRET_SENTINEL_PREFIX } from "@flowscripter/dynamic-cli-framework-api";
2
2
  import resolveSecrets from "./resolveSecrets.js";
3
+ /**
4
+ * Bound permanently to a single scope's data at construction - never re-pointed afterward. Create
5
+ * a separate instance per scope (see {@link KeyValueServiceProvider.getScopedKeyValueService}),
6
+ * rather than sharing one instance across scopes.
7
+ */
3
8
  export default class DefaultKeyValueService {
4
9
  #keyValueData;
5
10
  #dirty = false;
6
11
  #secretService;
7
- constructor(secretService) {
8
- this.#secretService = secretService;
9
- }
10
- setKeyValueData(keyValueData) {
11
- if (this.#keyValueData) {
12
- throw new Error("Attempt to overwrite key-value data, it should be cleared first");
13
- }
12
+ constructor(keyValueData, secretService) {
14
13
  this.#keyValueData = keyValueData;
15
- this.#dirty = false;
16
- }
17
- clearKeyValueData() {
18
- this.#keyValueData = undefined;
19
- this.#dirty = false;
14
+ this.#secretService = secretService;
20
15
  }
21
16
  isDirty() {
22
17
  return this.#dirty;
23
18
  }
24
19
  async get(key) {
25
- if (this.#keyValueData === undefined) {
26
- throw new Error("Attempt to access undefined key-value data");
27
- }
28
20
  const value = this.#keyValueData.get(key);
29
21
  if (value === undefined) {
30
22
  throw new Error("Attempt to access unknown key");
@@ -47,15 +39,9 @@ export default class DefaultKeyValueService {
47
39
  return resolveSecrets(value, resolveSecret);
48
40
  }
49
41
  has(key) {
50
- if (this.#keyValueData === undefined) {
51
- return Promise.reject(new Error("Attempt to access undefined key-value data"));
52
- }
53
42
  return Promise.resolve(this.#keyValueData.has(key));
54
43
  }
55
44
  async set(key, value) {
56
- if (this.#keyValueData === undefined) {
57
- throw new Error("Attempt to access undefined key-value data");
58
- }
59
45
  this.#keyValueData.set(key, await this.#storeSecrets(key, value, []));
60
46
  this.#dirty = true;
61
47
  }
@@ -88,9 +74,6 @@ export default class DefaultKeyValueService {
88
74
  return value;
89
75
  }
90
76
  async delete(key) {
91
- if (this.#keyValueData === undefined) {
92
- throw new Error("Attempt to access undefined key-value data");
93
- }
94
77
  const value = this.#keyValueData.get(key);
95
78
  if (value !== undefined) {
96
79
  await this.#deleteSecrets(value);
@@ -16,9 +16,7 @@ export interface SecretsApi {
16
16
  }
17
17
  export default class DefaultSecretService implements SecretService {
18
18
  #private;
19
- constructor(serviceName: string, secretsApi?: SecretsApi);
20
- setScope(scope: string): void;
21
- clearScope(): void;
19
+ constructor(serviceName: string, scope: string, secretsApi?: SecretsApi);
22
20
  setSecret(key: string, value: string): Promise<string>;
23
21
  getSecret(bunSecretName: string): Promise<string | null>;
24
22
  deleteSecret(bunSecretName: string): Promise<boolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"DefaultSecretService.d.ts","sourceRoot":"","sources":["../../../../src/service/configuration/DefaultSecretService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AAM7E,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACxE,GAAG,CAAC,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9E,MAAM,CAAC,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACtE;AAED,MAAM,CAAC,OAAO,OAAO,oBAAqB,YAAW,aAAa;;IAKhE,YAAY,WAAW,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,UAAU,EAQvD;IAMM,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAEnC;IAEM,UAAU,IAAI,IAAI,CAExB;IAEY,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAiBlE;IAEY,SAAS,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAKpE;IAEY,YAAY,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAKjE;IAEY,SAAS,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAO9D;CACF"}
1
+ {"version":3,"file":"DefaultSecretService.d.ts","sourceRoot":"","sources":["../../../../src/service/configuration/DefaultSecretService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AAM7E,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACxE,GAAG,CAAC,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9E,MAAM,CAAC,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACtE;AAED,MAAM,CAAC,OAAO,OAAO,oBAAqB,YAAW,aAAa;;IAKhE,YAAY,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,UAAU,EAStE;IAMY,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAclE;IAEY,SAAS,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAKpE;IAEY,YAAY,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAKjE;IAEY,SAAS,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAO9D;CACF"}
@@ -5,26 +5,18 @@ export default class DefaultSecretService {
5
5
  #serviceName;
6
6
  #secrets;
7
7
  #scope;
8
- constructor(serviceName, secretsApi) {
8
+ constructor(serviceName, scope, secretsApi) {
9
9
  this.#serviceName = DefaultSecretService.#sanitize(serviceName);
10
10
  if (this.#serviceName.length > MAX_NAME_LENGTH) {
11
11
  throw new Error(`Service name exceeds ${MAX_NAME_LENGTH} characters after sanitization: '${this.#serviceName}'`);
12
12
  }
13
+ this.#scope = DefaultSecretService.#sanitize(scope);
13
14
  this.#secrets = secretsApi ?? bunSecrets;
14
15
  }
15
16
  static #sanitize(value) {
16
17
  return value.replace(/[^a-zA-Z0-9_]/g, "_");
17
18
  }
18
- setScope(scope) {
19
- this.#scope = DefaultSecretService.#sanitize(scope);
20
- }
21
- clearScope() {
22
- this.#scope = undefined;
23
- }
24
19
  async setSecret(key, value) {
25
- if (this.#scope === undefined) {
26
- throw new Error("Attempt to set secret without a scope being set");
27
- }
28
20
  const name = this.#scope + "_" + DefaultSecretService.#sanitize(key);
29
21
  if (name.length > MAX_NAME_LENGTH) {
30
22
  throw new Error(`Secret name exceeds ${MAX_NAME_LENGTH} characters: '${name}'`);
@@ -0,0 +1,105 @@
1
+ import type { ServiceInfo, ServiceProvider } from "@flowscripter/dynamic-cli-framework-api";
2
+ import type { Context } from "@flowscripter/dynamic-cli-framework-api";
3
+ import type { CLIConfig } from "@flowscripter/dynamic-cli-framework-api";
4
+ import type { KeyValueService } from "@flowscripter/dynamic-cli-framework-api";
5
+ import type ConfigurationServiceProvider from "./ConfigurationServiceProvider.ts";
6
+ import type { KeyValueServiceScopeType } from "./ConfigurationServiceProvider.ts";
7
+ /**
8
+ * Provides a generic, per-scope key-value store for commands and services ({@link KeyValueService}).
9
+ *
10
+ * The underlying data is read from/written to the same configuration file managed by
11
+ * {@link ConfigurationServiceProvider} - see {@link ConfigurationServiceProvider.getKeyValueData} and
12
+ * {@link ConfigurationServiceProvider.flushIfDirty}, reached via a direct constructor reference wired
13
+ * up by `BaseCLI` (never via {@link Context}, since that would expose config-file read/write access
14
+ * more broadly than intended).
15
+ *
16
+ * Its `serviceId` is {@link KEY_VALUE_SERVICE_ID} and its {@link ServiceInfo.service} is a
17
+ * placeholder ({@link UnreachableKeyValueService}) - real access always goes through a per-consumer
18
+ * decorated {@link Context} from {@link getContextForScope}, applied by `runner.ts`.
19
+ *
20
+ * Any node within a value passed to {@link KeyValueService.set} can be wrapped in {@link Secret} -
21
+ * at any depth - to have that node, and only that node, stored as an OS-native secret. When a node
22
+ * is wrapped in {@link Secret}, its value (of any shape) is JSON-serialized and stored as a single
23
+ * OS-native secret via Bun.secrets, with a sentinel value of the format `__SECRET__:<bun_secret_name>`
24
+ * substituted in its place in the structure that gets stored in the config file. Everything else in
25
+ * the value is stored as plain (unencrypted) config data. The sentinel prefix `__SECRET__:` is
26
+ * reserved and must not be used for regular key-value data.
27
+ *
28
+ * Independently of how a value was written, {@link KeyValueService.get} recursively resolves any
29
+ * string leaf - at any depth within the retrieved value - which starts with the sentinel prefix,
30
+ * via the OS secret store. This means a secret reference may also be hand-embedded (nested
31
+ * arbitrarily deep) directly within a plain, non-secret value in the config file.
32
+ *
33
+ * Secret support requires `secretServiceEnabled=true` in the constructor (which also requires
34
+ * `keyValueServiceEnabled=true` on the {@link ConfigurationServiceProvider} to have config enabled).
35
+ *
36
+ * As an example of the underlying config file structure:
37
+ * ```
38
+ * {
39
+ * "defaults": {
40
+ * ...
41
+ * },
42
+ * "key-values": {
43
+ * "commands": {
44
+ * "command1": {
45
+ * "foo1": "bar1",
46
+ * "foo2": "__SECRET__:command_command1_foo2",
47
+ * "foo3": {
48
+ * "nested": ["a", "__SECRET__:command_command1_foo3_nested"]
49
+ * }
50
+ * },
51
+ * "command2": {
52
+ * "foo1": "bar3"
53
+ * }
54
+ * },
55
+ * "services": {
56
+ * "service-id-1": {
57
+ * "foo1": "bar"
58
+ * }
59
+ * }
60
+ * }
61
+ * }
62
+ * ```
63
+ */
64
+ export default class KeyValueServiceProvider implements ServiceProvider {
65
+ #private;
66
+ readonly serviceId: string;
67
+ readonly servicePriority: number;
68
+ readonly keyValueServiceEnabled: boolean;
69
+ readonly secretServiceEnabled: boolean;
70
+ /**
71
+ * Create an instance of the service provider with the specified details.
72
+ *
73
+ * @param servicePriority the priority of the service.
74
+ * @param configurationServiceProvider the {@link ConfigurationServiceProvider} whose config file
75
+ * backs this provider's data - an internal wiring reference, never exposed further.
76
+ * @param keyValueServiceEnabled optionally provide a {@link KeyValueService} implementation: `configurationServiceProvider.configEnabled` must be true in this case
77
+ * @param secretServiceEnabled optionally enable OS-native secret storage via Bun.secrets: `configurationServiceProvider.configEnabled` must be true in this case
78
+ */
79
+ constructor(servicePriority: number, configurationServiceProvider: ConfigurationServiceProvider, keyValueServiceEnabled?: boolean, secretServiceEnabled?: boolean);
80
+ getServiceInfo(cliConfig: CLIConfig): Promise<ServiceInfo>;
81
+ /**
82
+ * Return the {@link KeyValueService} bound to the given scope, creating (and caching) it on
83
+ * first request. The returned instance is permanently bound to that scope's data and secret
84
+ * prefix - it is never re-pointed, so it may be held and used for as long as its owning
85
+ * command/service/task is alive without racing any other scope's window.
86
+ *
87
+ * @param scopeType whether `scopeKey` is a command name or a service/task ID.
88
+ * @param scopeKey the command name or service/task ID to scope the key-value data to.
89
+ *
90
+ * @throws {Error} if neither `keyValueServiceEnabled` nor `secretServiceEnabled` is set.
91
+ */
92
+ getScopedKeyValueService(scopeType: KeyValueServiceScopeType, scopeKey: string): KeyValueService;
93
+ /**
94
+ * Return a {@link Context} which delegates every lookup to `context` unchanged, except
95
+ * `getServiceById(KEY_VALUE_SERVICE_ID)`, which resolves to the scope-bound instance from
96
+ * {@link getScopedKeyValueService} for the given scope. Used by `runner.ts` to give each
97
+ * command/service/task a KeyValueService isolated to its own scope, invisibly at the call site.
98
+ *
99
+ * Returns `context` itself, unchanged, if neither `keyValueServiceEnabled` nor
100
+ * `secretServiceEnabled` is set.
101
+ */
102
+ getContextForScope(context: Context, scopeType: KeyValueServiceScopeType, scopeKey: string): Context;
103
+ initService(context: Context): Promise<void>;
104
+ }
105
+ //# sourceMappingURL=KeyValueServiceProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"KeyValueServiceProvider.d.ts","sourceRoot":"","sources":["../../../../src/service/configuration/KeyValueServiceProvider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAC5F,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AACvE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EACV,eAAe,EAGhB,MAAM,yCAAyC,CAAC;AAIjD,OAAO,KAAK,4BAA4B,MAAM,mCAAmC,CAAC;AAClF,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,mCAAmC,CAAC;AA8BlF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AACH,MAAM,CAAC,OAAO,OAAO,uBAAwB,YAAW,eAAe;;IACrE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAwB;IAClD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IAIjC,SAAgB,sBAAsB,EAAE,OAAO,CAAC;IAChD,SAAgB,oBAAoB,EAAE,OAAO,CAAC;IAW9C;;;;;;;;OAQG;IACH,YACE,eAAe,EAAE,MAAM,EACvB,4BAA4B,EAAE,4BAA4B,EAC1D,sBAAsB,UAAQ,EAC9B,oBAAoB,UAAQ,EAY7B;IAEM,cAAc,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC,CAUhE;IAED;;;;;;;;;;OAUG;IACI,wBAAwB,CAC7B,SAAS,EAAE,wBAAwB,EACnC,QAAQ,EAAE,MAAM,GACf,eAAe,CAsBjB;IAED;;;;;;;;OAQG;IACI,kBAAkB,CACvB,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,wBAAwB,EACnC,QAAQ,EAAE,MAAM,GACf,OAAO,CAWT;IAeY,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAWxD;CACF"}