@flowscripter/dynamic-cli-framework 6.0.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/index.d.ts +10 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -2
- package/dist/src/cli/BaseCLI.d.ts +8 -0
- package/dist/src/cli/BaseCLI.d.ts.map +1 -1
- package/dist/src/cli/BaseCLI.js +37 -4
- package/dist/src/launcher.d.ts +3 -3
- package/dist/src/launcher.d.ts.map +1 -1
- package/dist/src/launcher.js +6 -4
- package/dist/src/runtime/lifecycle/PriorityTaskList.d.ts +22 -0
- package/dist/src/runtime/lifecycle/PriorityTaskList.d.ts.map +1 -0
- package/dist/src/runtime/lifecycle/PriorityTaskList.js +23 -0
- package/dist/src/runtime/runner.d.ts +15 -7
- package/dist/src/runtime/runner.d.ts.map +1 -1
- package/dist/src/runtime/runner.js +82 -53
- package/dist/src/service/configuration/ConfigurationServiceProvider.d.ts +26 -82
- package/dist/src/service/configuration/ConfigurationServiceProvider.d.ts.map +1 -1
- package/dist/src/service/configuration/ConfigurationServiceProvider.js +68 -163
- package/dist/src/service/configuration/DefaultKeyValueService.d.ts +6 -3
- package/dist/src/service/configuration/DefaultKeyValueService.d.ts.map +1 -1
- package/dist/src/service/configuration/DefaultKeyValueService.js +7 -24
- package/dist/src/service/configuration/DefaultSecretService.d.ts +1 -3
- package/dist/src/service/configuration/DefaultSecretService.d.ts.map +1 -1
- package/dist/src/service/configuration/DefaultSecretService.js +2 -10
- package/dist/src/service/configuration/KeyValueServiceProvider.d.ts +105 -0
- package/dist/src/service/configuration/KeyValueServiceProvider.d.ts.map +1 -0
- package/dist/src/service/configuration/KeyValueServiceProvider.js +201 -0
- package/dist/src/service/configuration/command/ConfigCommand.d.ts +4 -5
- package/dist/src/service/configuration/command/ConfigCommand.d.ts.map +1 -1
- package/dist/src/service/configuration/command/ConfigCommand.js +7 -6
- package/dist/src/service/configuration/command/DumpConfigCommand.d.ts +7 -3
- package/dist/src/service/configuration/command/DumpConfigCommand.d.ts.map +1 -1
- package/dist/src/service/configuration/command/DumpConfigCommand.js +10 -5
- package/dist/src/service/fetch/DefaultFetchService.d.ts +2 -2
- package/dist/src/service/fetch/DefaultFetchService.d.ts.map +1 -1
- package/dist/src/service/fetch/DefaultFetchService.js +17 -12
- package/dist/src/service/fetch/FetchServiceProvider.d.ts.map +1 -1
- package/dist/src/service/fetch/FetchServiceProvider.js +2 -3
- package/dist/src/service/printer/PrinterServiceProvider.d.ts.map +1 -1
- package/dist/src/service/printer/PrinterServiceProvider.js +11 -7
- package/dist/src/service/printer/command/DarkModeCommand.d.ts +3 -5
- package/dist/src/service/printer/command/DarkModeCommand.d.ts.map +1 -1
- package/dist/src/service/printer/command/DarkModeCommand.js +6 -6
- package/dist/src/service/printer/command/LogLevelCommand.d.ts +3 -5
- package/dist/src/service/printer/command/LogLevelCommand.d.ts.map +1 -1
- package/dist/src/service/printer/command/LogLevelCommand.js +9 -9
- package/dist/src/service/printer/command/NoColorCommand.d.ts +3 -5
- package/dist/src/service/printer/command/NoColorCommand.d.ts.map +1 -1
- package/dist/src/service/printer/command/NoColorCommand.js +6 -6
- package/dist/src/service/printer/command/NoHyperlinksCommand.d.ts +2 -4
- package/dist/src/service/printer/command/NoHyperlinksCommand.d.ts.map +1 -1
- package/dist/src/service/printer/command/NoHyperlinksCommand.js +5 -5
- package/dist/src/service/prompter/PrompterServiceProvider.js +1 -1
- package/dist/src/service/prompter/command/NoPromptCommand.d.ts +2 -4
- package/dist/src/service/prompter/command/NoPromptCommand.d.ts.map +1 -1
- package/dist/src/service/prompter/command/NoPromptCommand.js +5 -5
- package/dist/src/service/shutdown/DefaultShutdownService.d.ts +4 -3
- package/dist/src/service/shutdown/DefaultShutdownService.d.ts.map +1 -1
- package/dist/src/service/shutdown/DefaultShutdownService.js +4 -3
- package/dist/src/service/shutdown/ShutdownServiceProvider.js +2 -2
- package/dist/src/service/spawn/DefaultSpawnService.d.ts +2 -2
- package/dist/src/service/spawn/DefaultSpawnService.d.ts.map +1 -1
- package/dist/src/service/spawn/DefaultSpawnService.js +17 -14
- package/dist/src/service/spawn/SpawnServiceProvider.d.ts.map +1 -1
- package/dist/src/service/spawn/SpawnServiceProvider.js +1 -4
- package/dist/src/service/startup/DefaultStartupService.d.ts +10 -0
- package/dist/src/service/startup/DefaultStartupService.d.ts.map +1 -0
- package/dist/src/service/startup/DefaultStartupService.js +13 -0
- package/dist/src/service/startup/StartupServiceProvider.d.ts +17 -0
- package/dist/src/service/startup/StartupServiceProvider.d.ts.map +1 -0
- package/dist/src/service/startup/StartupServiceProvider.js +26 -0
- package/dist/src/service/upgrade/DefaultUpgradeService.d.ts +10 -4
- package/dist/src/service/upgrade/DefaultUpgradeService.d.ts.map +1 -1
- package/dist/src/service/upgrade/DefaultUpgradeService.js +156 -37
- package/dist/src/service/upgrade/UpgradeServiceProvider.d.ts +10 -0
- package/dist/src/service/upgrade/UpgradeServiceProvider.d.ts.map +1 -1
- package/dist/src/service/upgrade/UpgradeServiceProvider.js +25 -6
- package/dist/src/service/upgrade/command/UpgradeSubCommand.d.ts +0 -3
- package/dist/src/service/upgrade/command/UpgradeSubCommand.d.ts.map +1 -1
- package/dist/src/service/upgrade/command/UpgradeSubCommand.js +5 -13
- package/dist/src/startup/banner/bannerStartupTask.d.ts +17 -0
- package/dist/src/startup/banner/bannerStartupTask.d.ts.map +1 -0
- package/dist/src/startup/banner/bannerStartupTask.js +66 -0
- package/dist/src/{service → startup}/banner/command/NoBannerCommand.d.ts +8 -2
- package/dist/src/startup/banner/command/NoBannerCommand.d.ts.map +1 -0
- package/dist/src/{service → startup}/banner/command/NoBannerCommand.js +4 -4
- package/index.ts +19 -3
- package/package.json +7 -7
- package/src/cli/BaseCLI.ts +49 -3
- package/src/launcher.ts +7 -2
- package/src/runtime/lifecycle/PriorityTaskList.ts +34 -0
- package/src/runtime/runner.ts +106 -67
- package/src/service/configuration/ConfigurationServiceProvider.ts +80 -180
- package/src/service/configuration/DefaultKeyValueService.ts +8 -27
- package/src/service/configuration/DefaultSecretService.ts +3 -13
- package/src/service/configuration/KeyValueServiceProvider.ts +246 -0
- package/src/service/configuration/command/ConfigCommand.ts +12 -11
- package/src/service/configuration/command/DumpConfigCommand.ts +10 -9
- package/src/service/fetch/DefaultFetchService.ts +18 -12
- package/src/service/fetch/FetchServiceProvider.ts +2 -4
- package/src/service/printer/PrinterServiceProvider.ts +11 -7
- package/src/service/printer/command/DarkModeCommand.ts +7 -8
- package/src/service/printer/command/LogLevelCommand.ts +10 -11
- package/src/service/printer/command/NoColorCommand.ts +7 -8
- package/src/service/printer/command/NoHyperlinksCommand.ts +6 -7
- package/src/service/prompter/PrompterServiceProvider.ts +1 -1
- package/src/service/prompter/command/NoPromptCommand.ts +6 -7
- package/src/service/shutdown/DefaultShutdownService.ts +5 -4
- package/src/service/shutdown/ShutdownServiceProvider.ts +2 -2
- package/src/service/spawn/DefaultSpawnService.ts +18 -14
- package/src/service/spawn/SpawnServiceProvider.ts +1 -5
- package/src/service/startup/DefaultStartupService.ts +17 -0
- package/src/service/startup/StartupServiceProvider.ts +36 -0
- package/src/service/upgrade/DefaultUpgradeService.ts +202 -42
- package/src/service/upgrade/UpgradeServiceProvider.ts +31 -8
- package/src/service/upgrade/command/UpgradeSubCommand.ts +5 -15
- package/src/startup/banner/bannerStartupTask.ts +96 -0
- package/src/{service → startup}/banner/command/NoBannerCommand.ts +12 -5
- package/dist/src/service/banner/BannerServiceProvider.d.ts +0 -29
- package/dist/src/service/banner/BannerServiceProvider.d.ts.map +0 -1
- package/dist/src/service/banner/BannerServiceProvider.js +0 -69
- package/dist/src/service/banner/command/NoBannerCommand.d.ts.map +0 -1
- package/src/service/banner/BannerServiceProvider.ts +0 -89
|
@@ -15,10 +15,10 @@ import getLogger from "../../util/logger.ts";
|
|
|
15
15
|
import type { Context } from "@flowscripter/dynamic-cli-framework-api";
|
|
16
16
|
import DumpConfigCommand from "./command/DumpConfigCommand.ts";
|
|
17
17
|
import {
|
|
18
|
-
|
|
18
|
+
CONFIGURATION_SERVICE_ID,
|
|
19
19
|
SECRET_SENTINEL_PREFIX,
|
|
20
20
|
} from "@flowscripter/dynamic-cli-framework-api";
|
|
21
|
-
import
|
|
21
|
+
import type { ConfigurationService } from "@flowscripter/dynamic-cli-framework-api";
|
|
22
22
|
import DefaultSecretService from "./DefaultSecretService.ts";
|
|
23
23
|
import resolveSecrets from "./resolveSecrets.ts";
|
|
24
24
|
import type { Command } from "@flowscripter/dynamic-cli-framework-api";
|
|
@@ -36,11 +36,35 @@ import {
|
|
|
36
36
|
|
|
37
37
|
const logger = getLogger("ConfigurationServiceProvider");
|
|
38
38
|
|
|
39
|
+
export type KeyValueServiceScopeType = "command" | "service";
|
|
40
|
+
|
|
41
|
+
class ConfigurationServiceImpl implements ConfigurationService {
|
|
42
|
+
readonly #provider: ConfigurationServiceProvider;
|
|
43
|
+
|
|
44
|
+
public constructor(provider: ConfigurationServiceProvider) {
|
|
45
|
+
this.#provider = provider;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public get configLocation(): string | undefined {
|
|
49
|
+
return this.#provider.configLocation;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public setConfigLocation(location: string): void {
|
|
53
|
+
this.#provider.setConfigLocation(location);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
39
57
|
/**
|
|
40
|
-
* Provides
|
|
58
|
+
* Provides configuration of default command arguments using a configuration file and/or
|
|
59
|
+
* environment variables.
|
|
41
60
|
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
61
|
+
* The same configuration file also backs the raw, per-scope key-value data consumed by
|
|
62
|
+
* {@link KeyValueServiceProvider} (which holds a direct reference to this provider - see
|
|
63
|
+
* {@link getKeyValueData} and {@link flushIfDirty} - rather than looking this provider up via
|
|
64
|
+
* {@link Context}, since only {@link configLocation}/{@link setConfigLocation} are safe to expose
|
|
65
|
+
* generally; {@link getConfigString} is wired directly to {@link DumpConfigCommand} for the same
|
|
66
|
+
* reason - a full config dump would leak every other command's/service's key-value data if it
|
|
67
|
+
* were reachable through a general lookup).
|
|
44
68
|
*
|
|
45
69
|
* **Default Command Arguments**
|
|
46
70
|
*
|
|
@@ -85,7 +109,8 @@ const logger = getLogger("ConfigurationServiceProvider");
|
|
|
85
109
|
* NOTE: You may store default arguments for secrets by manually configuring secrets in your OS
|
|
86
110
|
* credential store and referencing them in the config file using the sentinel format
|
|
87
111
|
* `__SECRET__:<bun_secret_name>`. These values will be resolved from the OS secret store
|
|
88
|
-
* before being returned as default argument values. See
|
|
112
|
+
* before being returned as default argument values. See {@link KeyValueServiceProvider} for the
|
|
113
|
+
* generic key-value store which uses the same secret sentinel mechanism.
|
|
89
114
|
*
|
|
90
115
|
* *Environment Variables*
|
|
91
116
|
*
|
|
@@ -123,68 +148,13 @@ const logger = getLogger("ConfigurationServiceProvider");
|
|
|
123
148
|
* * 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`
|
|
124
149
|
*
|
|
125
150
|
* NOTE: Any default values from the above configuration sources will be overridden by any arguments provided on the command line.
|
|
126
|
-
*
|
|
127
|
-
* **Generic Key-Value Service**
|
|
128
|
-
*
|
|
129
|
-
* The same configuration file above is used to provide storage for the provided {@link KeyValueService}. The values
|
|
130
|
-
* are stored under a top level `key-values` property. These are expected to not be modified by the user of the CLI.
|
|
131
|
-
*
|
|
132
|
-
* The second and third level of properties is used to scope the key-values to specific command names
|
|
133
|
-
* (via {@link Command.name} values) and specific service IDs (via {@link ServiceProvider.serviceId} values).
|
|
134
|
-
* Keys are strings, but values are arbitrary JSON (see {@link ValueNode}) - not limited to strings -
|
|
135
|
-
* and may be deep objects or arrays.
|
|
136
|
-
*
|
|
137
|
-
* Any node within a value passed to {@link KeyValueService.set} can be wrapped in {@link Secret} -
|
|
138
|
-
* at any depth - to have that node, and only that node, stored as an OS-native secret. When a node
|
|
139
|
-
* is wrapped in {@link Secret}, its value (of any shape) is JSON-serialized and stored as a single
|
|
140
|
-
* OS-native secret via Bun.secrets, with a sentinel value of the format `__SECRET__:<bun_secret_name>`
|
|
141
|
-
* substituted in its place in the structure that gets stored in the config file. Everything else in
|
|
142
|
-
* the value is stored as plain (unencrypted) config data. The sentinel prefix `__SECRET__:` is
|
|
143
|
-
* reserved and must not be used for regular key-value data.
|
|
144
|
-
*
|
|
145
|
-
* Independently of how a value was written, {@link KeyValueService.get} recursively resolves any
|
|
146
|
-
* string leaf - at any depth within the retrieved value - which starts with the sentinel prefix,
|
|
147
|
-
* via the OS secret store. This means a secret reference may also be hand-embedded (nested
|
|
148
|
-
* arbitrarily deep) directly within a plain, non-secret value in the config file.
|
|
149
|
-
*
|
|
150
|
-
* Secret support requires `secretServiceEnabled=true` in the constructor (which also requires
|
|
151
|
-
* `configEnabled=true`).
|
|
152
|
-
*
|
|
153
|
-
* As an example:
|
|
154
|
-
* ```
|
|
155
|
-
* {
|
|
156
|
-
* "defaults": {
|
|
157
|
-
* ...
|
|
158
|
-
* },
|
|
159
|
-
* "key-values": {
|
|
160
|
-
* "commands": {
|
|
161
|
-
* "command1": {
|
|
162
|
-
* "foo1": "bar1",
|
|
163
|
-
* "foo2": "__SECRET__:command_command1_foo2",
|
|
164
|
-
* "foo3": {
|
|
165
|
-
* "nested": ["a", "__SECRET__:command_command1_foo3_nested"]
|
|
166
|
-
* }
|
|
167
|
-
* },
|
|
168
|
-
* "command2": {
|
|
169
|
-
* "foo1": "bar3"
|
|
170
|
-
* }
|
|
171
|
-
* },
|
|
172
|
-
* "services": {
|
|
173
|
-
* "service-id-1": {
|
|
174
|
-
* "foo1": "bar"
|
|
175
|
-
* }
|
|
176
|
-
* }
|
|
177
|
-
* }
|
|
178
|
-
* }
|
|
179
|
-
* ```
|
|
180
151
|
*/
|
|
181
152
|
export default class ConfigurationServiceProvider implements ServiceProvider {
|
|
182
|
-
readonly serviceId: string =
|
|
153
|
+
readonly serviceId: string = CONFIGURATION_SERVICE_ID;
|
|
183
154
|
readonly servicePriority: number;
|
|
184
155
|
|
|
185
156
|
public readonly envVarsEnabled: boolean;
|
|
186
157
|
public readonly configEnabled: boolean;
|
|
187
|
-
public readonly keyValueServiceEnabled: boolean;
|
|
188
158
|
public readonly secretServiceEnabled: boolean;
|
|
189
159
|
|
|
190
160
|
// the location of the currently managed configuration data
|
|
@@ -195,22 +165,24 @@ export default class ConfigurationServiceProvider implements ServiceProvider {
|
|
|
195
165
|
// SubCommandArgument values.
|
|
196
166
|
public defaultsData: Map<string, Values | SingleValueType> = new Map();
|
|
197
167
|
|
|
198
|
-
// the
|
|
199
|
-
//
|
|
168
|
+
// the underlying per-scope key-value data, read from/written to the configuration file. Exposed
|
|
169
|
+
// (raw, not wrapped in a KeyValueService) only via getKeyValueData(), for KeyValueServiceProvider's
|
|
170
|
+
// exclusive use.
|
|
200
171
|
#commandKeyValueData = new Map<string, Map<string, ValueNode>>();
|
|
201
|
-
|
|
202
|
-
// the configuration data to be used by the CLI runner implementation
|
|
203
|
-
// when updating service scoped access to key-value data via the key-value service.
|
|
204
172
|
#serviceKeyValueData = new Map<string, Map<string, ValueNode>>();
|
|
205
173
|
|
|
206
|
-
//
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
//
|
|
210
|
-
#
|
|
174
|
+
// secret service used only to resolve secret sentinels embedded in default command argument
|
|
175
|
+
// values (getDefaultArgumentValues()) - unrelated to per-command/per-service KV scoping, so it
|
|
176
|
+
// uses its own fixed, dedicated scope prefix ("defaults"). Its scope is otherwise irrelevant,
|
|
177
|
+
// since only getSecret() (which doesn't consult scope) is ever called on it.
|
|
178
|
+
#defaultsSecretService: DefaultSecretService | undefined;
|
|
211
179
|
|
|
212
|
-
|
|
213
|
-
|
|
180
|
+
/**
|
|
181
|
+
* A {@link ConfigurationService} view of this provider, registered in the {@link Context} under
|
|
182
|
+
* {@link CONFIGURATION_SERVICE_ID}. Its `configLocation` getter always reflects this provider's
|
|
183
|
+
* current value.
|
|
184
|
+
*/
|
|
185
|
+
readonly #configurationService: ConfigurationService;
|
|
214
186
|
|
|
215
187
|
/**
|
|
216
188
|
* Create an instance of the service provider with the specified details.
|
|
@@ -218,45 +190,38 @@ export default class ConfigurationServiceProvider implements ServiceProvider {
|
|
|
218
190
|
* @param servicePriority the priority of the service.
|
|
219
191
|
* @param envVarsEnabled optionally support checking env variables for default argument values.
|
|
220
192
|
* @param configEnabled optionally enable configuration file support for default argument values.
|
|
221
|
-
* @param
|
|
222
|
-
*
|
|
193
|
+
* @param secretServiceEnabled optionally enable OS-native secret storage via Bun.secrets for resolving
|
|
194
|
+
* secrets embedded in default argument values: `configEnabled` must be true in this case
|
|
223
195
|
*/
|
|
224
196
|
public constructor(
|
|
225
197
|
servicePriority: number,
|
|
226
198
|
envVarsEnabled = false,
|
|
227
199
|
configEnabled = false,
|
|
228
|
-
keyValueServiceEnabled = false,
|
|
229
200
|
secretServiceEnabled = false,
|
|
230
201
|
) {
|
|
231
|
-
if (!configEnabled && keyValueServiceEnabled) {
|
|
232
|
-
throw new Error("configEnabled must be true if keyValueServiceEnabled is true");
|
|
233
|
-
}
|
|
234
202
|
if (!configEnabled && secretServiceEnabled) {
|
|
235
203
|
throw new Error("configEnabled must be true if secretServiceEnabled is true");
|
|
236
204
|
}
|
|
237
205
|
this.servicePriority = servicePriority;
|
|
238
206
|
this.envVarsEnabled = envVarsEnabled;
|
|
239
207
|
this.configEnabled = configEnabled;
|
|
240
|
-
this.keyValueServiceEnabled = keyValueServiceEnabled;
|
|
241
208
|
this.secretServiceEnabled = secretServiceEnabled;
|
|
209
|
+
|
|
210
|
+
this.#configurationService = new ConfigurationServiceImpl(this);
|
|
242
211
|
}
|
|
243
212
|
|
|
244
213
|
public getServiceInfo(cliConfig: CLIConfig): Promise<ServiceInfo> {
|
|
245
214
|
const commands: Array<Command> = [];
|
|
246
215
|
|
|
247
216
|
if (this.configEnabled) {
|
|
248
|
-
commands.push(new ConfigCommand(this
|
|
249
|
-
commands.push(new DumpConfigCommand(this));
|
|
217
|
+
commands.push(new ConfigCommand(this.servicePriority));
|
|
218
|
+
commands.push(new DumpConfigCommand(() => this.getConfigString()));
|
|
250
219
|
}
|
|
251
220
|
if (this.secretServiceEnabled) {
|
|
252
|
-
this.#
|
|
253
|
-
}
|
|
254
|
-
if (this.keyValueServiceEnabled || this.secretServiceEnabled) {
|
|
255
|
-
this.#defaultKeyValueService = new DefaultKeyValueService(this.#defaultSecretService);
|
|
221
|
+
this.#defaultsSecretService = new DefaultSecretService(cliConfig.name, "defaults");
|
|
256
222
|
}
|
|
257
223
|
return Promise.resolve({
|
|
258
|
-
|
|
259
|
-
service: this.#defaultKeyValueService,
|
|
224
|
+
service: this.#configurationService,
|
|
260
225
|
// this may be empty if configEnabled is false
|
|
261
226
|
commands,
|
|
262
227
|
});
|
|
@@ -313,11 +278,11 @@ export default class ConfigurationServiceProvider implements ServiceProvider {
|
|
|
313
278
|
}
|
|
314
279
|
}
|
|
315
280
|
|
|
316
|
-
if (result !== undefined && this.#
|
|
281
|
+
if (result !== undefined && this.#defaultsSecretService) {
|
|
317
282
|
result = await resolveSecrets<PopulatedValues | PopulatedValueType>(
|
|
318
283
|
result,
|
|
319
284
|
async (bunSecretName) => {
|
|
320
|
-
const secretValue = await this.#
|
|
285
|
+
const secretValue = await this.#defaultsSecretService!.getSecret(bunSecretName);
|
|
321
286
|
if (secretValue === null) {
|
|
322
287
|
throw new Error(
|
|
323
288
|
`Secret not found in OS secret store for sentinel: '${SECRET_SENTINEL_PREFIX}${bunSecretName}'`,
|
|
@@ -332,104 +297,39 @@ export default class ConfigurationServiceProvider implements ServiceProvider {
|
|
|
332
297
|
}
|
|
333
298
|
|
|
334
299
|
/**
|
|
335
|
-
*
|
|
336
|
-
*
|
|
337
|
-
*
|
|
338
|
-
* @param commandName the name of the command to scope the key-value data to.
|
|
339
|
-
*
|
|
340
|
-
* @throws {Error} if an existing scope is set.
|
|
341
|
-
*/
|
|
342
|
-
public setCommandKeyValueScope(commandName: string): void {
|
|
343
|
-
if (!this.keyValueServiceEnabled && !this.secretServiceEnabled) {
|
|
344
|
-
throw new Error(`Attempt to use KeyValueService/SecretService which is not enabled`);
|
|
345
|
-
}
|
|
346
|
-
if (this.#currentCommandNameKeyValueScope) {
|
|
347
|
-
throw new Error(
|
|
348
|
-
`Attempt to set CommandNameKeyValueScope to ${commandName} when it is currently set to ${this.#currentCommandNameKeyValueScope}`,
|
|
349
|
-
);
|
|
350
|
-
}
|
|
351
|
-
if (this.#currentServiceIdKeyValueScope) {
|
|
352
|
-
throw new Error(
|
|
353
|
-
`Attempt to set CommandNameKeyValueScope to ${commandName} when ServiceIdKeyValueScope is currently set to ${this.#currentCommandNameKeyValueScope}`,
|
|
354
|
-
);
|
|
355
|
-
}
|
|
356
|
-
this.#currentCommandNameKeyValueScope = commandName;
|
|
357
|
-
|
|
358
|
-
logger.debug("currentCommandNameKeyValueScope: %s", commandName);
|
|
359
|
-
|
|
360
|
-
if (this.#defaultKeyValueService) {
|
|
361
|
-
if (!this.#commandKeyValueData.has(commandName)) {
|
|
362
|
-
this.#commandKeyValueData.set(commandName, new Map());
|
|
363
|
-
}
|
|
364
|
-
this.#defaultKeyValueService.setKeyValueData(this.#commandKeyValueData.get(commandName)!);
|
|
365
|
-
}
|
|
366
|
-
if (this.#defaultSecretService) {
|
|
367
|
-
this.#defaultSecretService.setScope("command_" + commandName);
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
/**
|
|
372
|
-
* Indicate that the contained {@link DefaultKeyValueService} should be set to have a scope of the provided
|
|
373
|
-
* service ID.
|
|
374
|
-
*
|
|
375
|
-
* @param serviceId the ID of the service to scope the key-value data to.
|
|
300
|
+
* Get-or-create the underlying, raw {@link ValueNode} `Map` for the given scope - for
|
|
301
|
+
* {@link KeyValueServiceProvider}'s exclusive use, via its direct reference to this provider.
|
|
376
302
|
*
|
|
377
|
-
* @
|
|
303
|
+
* @param scopeType whether `scopeKey` is a command name or a service/task ID.
|
|
304
|
+
* @param scopeKey the command name or service/task ID to scope the key-value data to.
|
|
378
305
|
*/
|
|
379
|
-
public
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
);
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
throw new Error(
|
|
390
|
-
`Attempt to set ServiceIdKeyValueScope to ${serviceId} when CommandNameKeyValueScope is currently set to ${this.#currentServiceIdKeyValueScope}`,
|
|
391
|
-
);
|
|
392
|
-
}
|
|
393
|
-
this.#currentServiceIdKeyValueScope = serviceId;
|
|
394
|
-
|
|
395
|
-
logger.debug("currentServiceIdKeyValueScope: %s", serviceId);
|
|
396
|
-
|
|
397
|
-
if (this.#defaultKeyValueService) {
|
|
398
|
-
if (!this.#serviceKeyValueData.has(serviceId)) {
|
|
399
|
-
this.#serviceKeyValueData.set(serviceId, new Map());
|
|
400
|
-
}
|
|
401
|
-
this.#defaultKeyValueService.setKeyValueData(this.#serviceKeyValueData.get(serviceId)!);
|
|
402
|
-
}
|
|
403
|
-
if (this.#defaultSecretService) {
|
|
404
|
-
this.#defaultSecretService.setScope("service_" + serviceId);
|
|
405
|
-
}
|
|
306
|
+
public getKeyValueData(
|
|
307
|
+
scopeType: KeyValueServiceScopeType,
|
|
308
|
+
scopeKey: string,
|
|
309
|
+
): Map<string, ValueNode> {
|
|
310
|
+
const keyValueData =
|
|
311
|
+
scopeType === "command" ? this.#commandKeyValueData : this.#serviceKeyValueData;
|
|
312
|
+
if (!keyValueData.has(scopeKey)) {
|
|
313
|
+
keyValueData.set(scopeKey, new Map());
|
|
314
|
+
}
|
|
315
|
+
return keyValueData.get(scopeKey)!;
|
|
406
316
|
}
|
|
407
317
|
|
|
408
318
|
/**
|
|
409
|
-
*
|
|
319
|
+
* Write the configuration file, if `isDirty` is true - for {@link KeyValueServiceProvider}'s
|
|
320
|
+
* exclusive use, via its direct reference to this provider, called from its shutdown-time
|
|
321
|
+
* flush task.
|
|
410
322
|
*
|
|
411
|
-
*
|
|
412
|
-
* to the current configuration location.
|
|
323
|
+
* @param isDirty whether any scoped KeyValueService has unwritten changes.
|
|
413
324
|
*/
|
|
414
|
-
public async
|
|
415
|
-
if (!
|
|
416
|
-
|
|
417
|
-
}
|
|
418
|
-
if (this.#defaultKeyValueService && this.#defaultKeyValueService.isDirty()) {
|
|
419
|
-
if (this.configLocation === undefined) {
|
|
420
|
-
throw new Error("Attempt to write updated config with no configLocation set");
|
|
421
|
-
}
|
|
422
|
-
await this.#writeConfig(this.configLocation);
|
|
423
|
-
}
|
|
424
|
-
this.#currentCommandNameKeyValueScope = undefined;
|
|
425
|
-
this.#currentServiceIdKeyValueScope = undefined;
|
|
426
|
-
|
|
427
|
-
if (this.#defaultKeyValueService) {
|
|
428
|
-
this.#defaultKeyValueService.clearKeyValueData();
|
|
325
|
+
public async flushIfDirty(isDirty: boolean): Promise<void> {
|
|
326
|
+
if (!isDirty) {
|
|
327
|
+
return;
|
|
429
328
|
}
|
|
430
|
-
if (this
|
|
431
|
-
|
|
329
|
+
if (this.configLocation === undefined) {
|
|
330
|
+
throw new Error("Attempt to write updated config with no configLocation set");
|
|
432
331
|
}
|
|
332
|
+
await this.#writeConfig(this.configLocation);
|
|
433
333
|
}
|
|
434
334
|
|
|
435
335
|
public async initService(context: Context): Promise<void> {
|
|
@@ -7,26 +7,19 @@ import { Secret, SECRET_SENTINEL_PREFIX } from "@flowscripter/dynamic-cli-framew
|
|
|
7
7
|
import type DefaultSecretService from "./DefaultSecretService.ts";
|
|
8
8
|
import resolveSecrets from "./resolveSecrets.ts";
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Bound permanently to a single scope's data at construction - never re-pointed afterward. Create
|
|
12
|
+
* a separate instance per scope (see {@link KeyValueServiceProvider.getScopedKeyValueService}),
|
|
13
|
+
* rather than sharing one instance across scopes.
|
|
14
|
+
*/
|
|
10
15
|
export default class DefaultKeyValueService implements KeyValueService {
|
|
11
|
-
#keyValueData: Map<string, ValueNode
|
|
16
|
+
readonly #keyValueData: Map<string, ValueNode>;
|
|
12
17
|
#dirty = false;
|
|
13
18
|
readonly #secretService: DefaultSecretService | undefined;
|
|
14
19
|
|
|
15
|
-
constructor(secretService?: DefaultSecretService) {
|
|
16
|
-
this.#secretService = secretService;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
public setKeyValueData(keyValueData: Map<string, ValueNode>) {
|
|
20
|
-
if (this.#keyValueData) {
|
|
21
|
-
throw new Error("Attempt to overwrite key-value data, it should be cleared first");
|
|
22
|
-
}
|
|
20
|
+
constructor(keyValueData: Map<string, ValueNode>, secretService?: DefaultSecretService) {
|
|
23
21
|
this.#keyValueData = keyValueData;
|
|
24
|
-
this.#
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
public clearKeyValueData() {
|
|
28
|
-
this.#keyValueData = undefined;
|
|
29
|
-
this.#dirty = false;
|
|
22
|
+
this.#secretService = secretService;
|
|
30
23
|
}
|
|
31
24
|
|
|
32
25
|
public isDirty(): boolean {
|
|
@@ -34,9 +27,6 @@ export default class DefaultKeyValueService implements KeyValueService {
|
|
|
34
27
|
}
|
|
35
28
|
|
|
36
29
|
public async get<T extends ValueNode = ValueNode>(key: string): Promise<T> {
|
|
37
|
-
if (this.#keyValueData === undefined) {
|
|
38
|
-
throw new Error("Attempt to access undefined key-value data");
|
|
39
|
-
}
|
|
40
30
|
const value = this.#keyValueData.get(key);
|
|
41
31
|
if (value === undefined) {
|
|
42
32
|
throw new Error("Attempt to access unknown key");
|
|
@@ -61,16 +51,10 @@ export default class DefaultKeyValueService implements KeyValueService {
|
|
|
61
51
|
}
|
|
62
52
|
|
|
63
53
|
public has(key: string): Promise<boolean> {
|
|
64
|
-
if (this.#keyValueData === undefined) {
|
|
65
|
-
return Promise.reject(new Error("Attempt to access undefined key-value data"));
|
|
66
|
-
}
|
|
67
54
|
return Promise.resolve(this.#keyValueData.has(key));
|
|
68
55
|
}
|
|
69
56
|
|
|
70
57
|
public async set(key: string, value: SettableValueNode): Promise<void> {
|
|
71
|
-
if (this.#keyValueData === undefined) {
|
|
72
|
-
throw new Error("Attempt to access undefined key-value data");
|
|
73
|
-
}
|
|
74
58
|
this.#keyValueData.set(key, await this.#storeSecrets(key, value, []));
|
|
75
59
|
this.#dirty = true;
|
|
76
60
|
}
|
|
@@ -112,9 +96,6 @@ export default class DefaultKeyValueService implements KeyValueService {
|
|
|
112
96
|
}
|
|
113
97
|
|
|
114
98
|
public async delete(key: string): Promise<void> {
|
|
115
|
-
if (this.#keyValueData === undefined) {
|
|
116
|
-
throw new Error("Attempt to access undefined key-value data");
|
|
117
|
-
}
|
|
118
99
|
const value = this.#keyValueData.get(key);
|
|
119
100
|
if (value !== undefined) {
|
|
120
101
|
await this.#deleteSecrets(value);
|
|
@@ -13,15 +13,16 @@ export interface SecretsApi {
|
|
|
13
13
|
export default class DefaultSecretService implements SecretService {
|
|
14
14
|
readonly #serviceName: string;
|
|
15
15
|
readonly #secrets: SecretsApi;
|
|
16
|
-
#scope: string
|
|
16
|
+
readonly #scope: string;
|
|
17
17
|
|
|
18
|
-
constructor(serviceName: string, secretsApi?: SecretsApi) {
|
|
18
|
+
constructor(serviceName: string, scope: string, secretsApi?: SecretsApi) {
|
|
19
19
|
this.#serviceName = DefaultSecretService.#sanitize(serviceName);
|
|
20
20
|
if (this.#serviceName.length > MAX_NAME_LENGTH) {
|
|
21
21
|
throw new Error(
|
|
22
22
|
`Service name exceeds ${MAX_NAME_LENGTH} characters after sanitization: '${this.#serviceName}'`,
|
|
23
23
|
);
|
|
24
24
|
}
|
|
25
|
+
this.#scope = DefaultSecretService.#sanitize(scope);
|
|
25
26
|
this.#secrets = secretsApi ?? bunSecrets;
|
|
26
27
|
}
|
|
27
28
|
|
|
@@ -29,18 +30,7 @@ export default class DefaultSecretService implements SecretService {
|
|
|
29
30
|
return value.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
public setScope(scope: string): void {
|
|
33
|
-
this.#scope = DefaultSecretService.#sanitize(scope);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
public clearScope(): void {
|
|
37
|
-
this.#scope = undefined;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
33
|
public async setSecret(key: string, value: string): Promise<string> {
|
|
41
|
-
if (this.#scope === undefined) {
|
|
42
|
-
throw new Error("Attempt to set secret without a scope being set");
|
|
43
|
-
}
|
|
44
34
|
const name = this.#scope + "_" + DefaultSecretService.#sanitize(key);
|
|
45
35
|
if (name.length > MAX_NAME_LENGTH) {
|
|
46
36
|
throw new Error(`Secret name exceeds ${MAX_NAME_LENGTH} characters: '${name}'`);
|