@flowscripter/dynamic-cli-framework 1.1.5 → 1.2.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/.github/workflows/release-bun-library.yml +1 -0
- package/README/core-commands.md +40 -0
- package/README/core-service-providers.md +198 -0
- package/README/development.md +60 -0
- package/README/implementation-details.md +474 -0
- package/README/key-concepts.md +751 -0
- package/README.md +12 -1413
- package/bun.lock +20 -18
- package/index.ts +98 -1
- package/package.json +8 -7
- package/src/api/BaseCLIFeatureOptions.ts +10 -0
- package/src/api/CLIConfig.ts +5 -0
- package/src/api/RunResult.ts +16 -0
- package/src/api/argument/Argument.ts +40 -1
- package/src/api/argument/ArgumentValueTypes.ts +1 -1
- package/src/api/command/SubCommand.ts +1 -1
- package/src/api/service/ServiceProvider.ts +3 -3
- package/src/api/service/core/ArgumentPrompterService.ts +10 -0
- package/src/api/service/core/AsciiBannerGeneratorService.ts +44 -7
- package/src/api/service/core/CompletionService.ts +36 -0
- package/src/api/service/core/DataDumpGeneratorService.ts +27 -0
- package/src/api/service/core/ImagePrinterService.ts +26 -0
- package/src/api/service/core/KeyValueService.ts +22 -5
- package/src/api/service/core/PrinterService.ts +127 -1
- package/src/api/service/core/PrompterService.ts +49 -0
- package/src/api/service/core/SecretService.ts +27 -0
- package/src/api/service/core/ShutdownService.ts +17 -1
- package/src/api/service/core/Table.ts +85 -0
- package/src/api/service/core/TableGeneratorService.ts +38 -0
- package/src/api/service/core/TreePrinterService.ts +11 -0
- package/src/cli/BaseCLI.ts +116 -44
- package/src/cli/DefaultRuntimeCLI.ts +10 -18
- package/src/command/CompletionCommand.ts +179 -0
- package/src/command/MultiCommandCliHelpCommand.ts +20 -32
- package/src/command/SingleCommandCliHelpCommand.ts +10 -1
- package/src/launcher.ts +16 -58
- package/src/runtime/command/CommandValidator.ts +70 -6
- package/src/runtime/runner.ts +112 -31
- package/src/runtime/scanner.ts +18 -15
- package/src/runtime/values/argumentValueValidation.ts +93 -10
- package/src/runtime/values/globalCommandValuePopulation.ts +1 -1
- package/src/runtime/values/subCommandValuePopulation.ts +9 -8
- package/src/service/argumentPrompter/ArgumentPrompterServiceProvider.ts +34 -0
- package/src/service/argumentPrompter/DefaultArgumentPrompterService.ts +330 -0
- package/src/service/asciiBannerGenerator/AsciiBannerGeneratorServiceProvider.ts +1 -1
- package/src/service/asciiBannerGenerator/DefaultAsciiBannerGeneratorService.ts +254 -17
- package/src/service/banner/BannerServiceProvider.ts +2 -2
- package/src/service/chiselAsciiBannerGenerator/ChiselFontAsciiBannerGeneratorService.ts +85 -0
- package/src/service/chiselAsciiBannerGenerator/chisel.flf.json +3 -0
- package/src/service/completion/CompletionServiceProvider.ts +212 -0
- package/src/service/completion/DefaultCompletionService.ts +364 -0
- package/src/service/completion/shell/ShellHandler.ts +16 -0
- package/src/service/completion/shell/bash.ts +47 -0
- package/src/service/completion/shell/fish.ts +43 -0
- package/src/service/completion/shell/powershell.ts +66 -0
- package/src/service/completion/shell/zsh.ts +50 -0
- package/src/service/configuration/ConfigurationServiceProvider.ts +138 -42
- package/src/service/configuration/DefaultKeyValueService.ts +53 -7
- package/src/service/configuration/DefaultSecretService.ts +85 -0
- package/src/service/dataDumpGenerator/DataDumpGeneratorServiceProvider.ts +41 -0
- package/src/service/dataDumpGenerator/DefaultDataDumpGeneratorService.ts +80 -0
- package/src/service/imagePrinter/DefaultImagePrinterService.ts +23 -0
- package/src/service/imagePrinter/ImagePrinterServiceProvider.ts +32 -0
- package/src/service/prettyPrinter/PrettyPrinterServiceProvider.ts +1 -1
- package/src/service/printer/DefaultPrinterService.ts +147 -27
- package/src/service/printer/PrinterServiceProvider.ts +7 -7
- package/src/service/printer/command/LogLevelCommand.ts +2 -1
- package/src/service/printer/command/NoHyperlinksCommand.ts +40 -0
- package/src/service/printer/terminal/Progress.ts +21 -4
- package/src/service/printer/terminal/Spinner.ts +32 -8
- package/src/service/printer/terminal/Theme.ts +1 -1
- package/src/service/prompter/DefaultPrompterService.ts +87 -0
- package/src/service/prompter/PrompterServiceProvider.ts +36 -0
- package/src/service/prompter/command/NoPromptCommand.ts +40 -0
- package/src/service/prompter/prompt/PromptContext.ts +47 -0
- package/src/service/prompter/prompt/promptAcknowledge.ts +28 -0
- package/src/service/prompter/prompt/promptMultiSelect.ts +99 -0
- package/src/service/prompter/prompt/promptOpenUrl.ts +97 -0
- package/src/service/prompter/prompt/promptSingleSelect.ts +102 -0
- package/src/service/prompter/prompt/promptText.ts +107 -0
- package/src/service/prompter/prompt/promptToggle.ts +28 -0
- package/src/service/shutdown/DefaultShutdownService.ts +13 -0
- package/src/service/shutdown/ShutdownServiceProvider.ts +28 -9
- package/src/service/shutdown/ShutdownState.ts +5 -0
- package/src/service/syntaxHighlighter/SyntaxHighlighterServiceProvider.ts +1 -1
- package/src/service/tableGenerator/DefaultTableGeneratorService.ts +415 -0
- package/src/service/tableGenerator/TableGeneratorServiceProvider.ts +43 -0
- package/src/service/treePrinter/DefaultTreePrinterService.ts +49 -0
- package/src/service/treePrinter/TreePrinterServiceProvider.ts +40 -0
- package/src/terminal/Ansi.ts +27 -0
- package/src/terminal/ImageRenderer.ts +199 -0
- package/src/terminal/KeyReader.ts +23 -0
- package/src/terminal/PngDecoder.ts +143 -0
- package/src/{service/printer/terminal → terminal}/Styler.ts +3 -0
- package/src/{service/printer/terminal → terminal}/Terminal.ts +1 -0
- package/src/terminal/TtyKeyReader.ts +83 -0
- package/src/terminal/TtyStyler.ts +78 -0
- package/src/{service/printer/terminal → terminal}/TtyTerminal.ts +9 -1
- package/src/terminal/supportsHyperlinks.ts +110 -0
- package/src/util/configHelper.ts +2 -2
- package/src/util/envVarHelper.ts +2 -2
- package/src/util/helpHelper.ts +139 -123
- package/src/util/logger.ts +16 -14
- package/src/util/runnerHelper.ts +41 -0
- package/tests/cli/BaseCLI.test.ts +50 -54
- package/tests/command/CompletionCommand.test.ts +68 -0
- package/tests/command/UsageCommand.test.ts +3 -2
- package/tests/command/VersionCommand.test.ts +3 -2
- package/tests/fixtures/Context.ts +9 -2
- package/tests/fixtures/util.ts +1 -1
- package/tests/runtime/command/CommandValidator.test.ts +103 -0
- package/tests/runtime/parser.test.ts +9 -7
- package/tests/runtime/registry/DefaultServiceProviderRegistry.test.ts +3 -3
- package/tests/runtime/runner.test.ts +36 -0
- package/tests/runtime/values/argumentValueValidation.test.ts +215 -1
- package/tests/service/argumentPrompter/ArgumentPrompterServiceProvider.test.ts +47 -0
- package/tests/service/argumentPrompter/DefaultArgumentPrompterService.test.ts +754 -0
- package/tests/service/asciiBannerGenerator/AsciiBannerGeneratorServiceProvider.test.ts +5 -4
- package/tests/service/asciiBannerGenerator/DefaultAsciiBannerGeneratorService.test.ts +163 -3
- package/tests/service/asciiBannerGenerator/__snapshots__/DefaultAsciiBannerGeneratorService.test.ts.snap +1 -1
- package/tests/service/banner/BannerServiceProvider.test.ts +8 -4
- package/tests/service/chiselAsciiBannerGenerator/ChiselFontAsciiBannerGeneratorService.test.ts +52 -0
- package/tests/service/completion/CompletionServiceProvider.test.ts +404 -0
- package/tests/service/completion/DefaultCompletionService.test.ts +278 -0
- package/tests/service/completion/shell/bash.test.ts +73 -0
- package/tests/service/completion/shell/fish.test.ts +62 -0
- package/tests/service/completion/shell/powershell.test.ts +59 -0
- package/tests/service/completion/shell/zsh.test.ts +60 -0
- package/tests/service/configuration/ConfigurationServiceProvider.test.ts +24 -12
- package/tests/service/configuration/DefaultKeyValueService.test.ts +118 -16
- package/tests/service/configuration/DefaultSecretService.test.ts +148 -0
- package/tests/service/configuration/command/ConfigCommand.test.ts +29 -0
- package/tests/service/configuration/command/DumpConfigCommand.test.ts +60 -0
- package/tests/service/dataDumpGenerator/DataDumpGeneratorServiceProvider.test.ts +57 -0
- package/tests/service/dataDumpGenerator/DefaultDataDumpGeneratorService.test.ts +217 -0
- package/tests/service/imagePrinter/DefaultImagePrinterService.test.ts +96 -0
- package/tests/service/imagePrinter/ImagePrinterServiceProvider.test.ts +17 -0
- package/tests/service/prettyPrinter/DefaultPrettyPrinterService.test.ts +2 -2
- package/tests/service/prettyPrinter/PrettyPrinterServiceProvider.test.ts +5 -4
- package/tests/service/printer/DefaultPrinterService.test.ts +233 -2
- package/tests/service/printer/PrinterServiceProvider.test.ts +6 -5
- package/tests/service/printer/command/DarkModeCommand.test.ts +83 -0
- package/tests/service/printer/command/LogLevelCommand.test.ts +8 -6
- package/tests/service/printer/command/NoColorCommand.test.ts +83 -0
- package/tests/service/printer/command/NoHyperlinksCommand.test.ts +89 -0
- package/tests/service/printer/terminal/Progress.test.ts +64 -4
- package/tests/service/printer/terminal/Spinner.test.ts +44 -3
- package/tests/service/prompter/DefaultPrompterService.test.ts +615 -0
- package/tests/service/prompter/PrompterServiceProvider.test.ts +47 -0
- package/tests/service/prompter/command/NoPromptCommand.test.ts +58 -0
- package/tests/service/shutdown/ShutdownServiceProvider.test.ts +4 -2
- package/tests/service/syntaxHighlighter/DefaultSyntaxHighlighterService.test.ts +3 -2
- package/tests/service/syntaxHighlighter/SyntaxHighlighterServiceProvider.test.ts +5 -4
- package/tests/service/tableGenerator/DefaultTableGeneratorService.test.ts +363 -0
- package/tests/service/tableGenerator/TableGeneratorServiceProvider.test.ts +47 -0
- package/tests/service/treePrinter/DefaultTreePrinterService.test.ts +144 -0
- package/tests/service/treePrinter/TreePrinterServiceProvider.test.ts +55 -0
- package/tests/terminal/ImageRenderer.test.ts +161 -0
- package/tests/terminal/PngDecoder.test.ts +223 -0
- package/tests/terminal/supportsHyperlinks.test.ts +149 -0
- package/tests/util/configHelper.test.ts +4 -4
- package/tests/util/logger.test.ts +23 -1
- package/tsconfig.json +31 -0
- package/src/service/printer/terminal/Ansi.ts +0 -10
- package/src/service/printer/terminal/TtyStyler.ts +0 -39
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import type { CompletionItem } from "../../../api/service/core/CompletionService.ts";
|
|
5
|
+
import type ShellHandler from "./ShellHandler.ts";
|
|
6
|
+
import type { CompletionContext } from "./ShellHandler.ts";
|
|
7
|
+
|
|
8
|
+
// Reference: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/register-argumentcompleter
|
|
9
|
+
|
|
10
|
+
export default class PowerShellShellHandler implements ShellHandler {
|
|
11
|
+
getDefaultConfigPath(): string {
|
|
12
|
+
if (process.platform === "win32") {
|
|
13
|
+
return join(
|
|
14
|
+
homedir(),
|
|
15
|
+
"Documents",
|
|
16
|
+
"PowerShell",
|
|
17
|
+
"Microsoft.PowerShell_profile.ps1",
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
return join(
|
|
21
|
+
homedir(),
|
|
22
|
+
".config",
|
|
23
|
+
"powershell",
|
|
24
|
+
"Microsoft.PowerShell_profile.ps1",
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
getBootstrapScript(cliName: string): string {
|
|
29
|
+
return [
|
|
30
|
+
`Register-ArgumentCompleter -Native -CommandName ${cliName} -ScriptBlock {`,
|
|
31
|
+
` param($wordToComplete, $commandAst, $cursorPosition)`,
|
|
32
|
+
` $completions = & ${cliName} completions:complete powershell "$commandAst" "$cursorPosition"`,
|
|
33
|
+
` $completions -split '\\n' | ForEach-Object {`,
|
|
34
|
+
` [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)`,
|
|
35
|
+
` }`,
|
|
36
|
+
`}`,
|
|
37
|
+
].join("\n");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
validateEnvironment(): Promise<boolean> {
|
|
41
|
+
try {
|
|
42
|
+
const result = Bun.spawnSync(["pwsh", "--version"]);
|
|
43
|
+
if (result.exitCode === 0) {
|
|
44
|
+
return Promise.resolve(true);
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
// try powershell fallback
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const result = Bun.spawnSync(["powershell", "-Command", "echo ok"]);
|
|
51
|
+
return Promise.resolve(result.exitCode === 0);
|
|
52
|
+
} catch {
|
|
53
|
+
return Promise.resolve(false);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
formatCompletions(completions: ReadonlyArray<CompletionItem>): string {
|
|
58
|
+
return completions.map((c) => c.value).join("\n");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
parseCompletionContext(args: ReadonlyArray<string>): CompletionContext {
|
|
62
|
+
const line = args[0] || "";
|
|
63
|
+
const cursorPosition = parseInt(args[1] || "0", 10) || line.length;
|
|
64
|
+
return { line, cursorPosition };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import type { CompletionItem } from "../../../api/service/core/CompletionService.ts";
|
|
5
|
+
import type ShellHandler from "./ShellHandler.ts";
|
|
6
|
+
import type { CompletionContext } from "./ShellHandler.ts";
|
|
7
|
+
|
|
8
|
+
// Reference: https://zsh.sourceforge.io/Doc/Release/Completion-System.html
|
|
9
|
+
|
|
10
|
+
export default class ZshShellHandler implements ShellHandler {
|
|
11
|
+
getDefaultConfigPath(): string {
|
|
12
|
+
return join(homedir(), ".zshrc");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
getBootstrapScript(cliName: string): string {
|
|
16
|
+
return [
|
|
17
|
+
`_${cliName}_completions() {`,
|
|
18
|
+
` local -a completions`,
|
|
19
|
+
` completions=("\${(@f)$(${cliName} completions:complete zsh "\${words}" "\${CURSOR}")}")`,
|
|
20
|
+
` _describe '${cliName}' completions`,
|
|
21
|
+
`}`,
|
|
22
|
+
`compdef _${cliName}_completions ${cliName}`,
|
|
23
|
+
].join("\n");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
validateEnvironment(): Promise<boolean> {
|
|
27
|
+
const shell = process.env.SHELL || "";
|
|
28
|
+
if (shell.endsWith("/zsh")) {
|
|
29
|
+
return Promise.resolve(true);
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const result = Bun.spawnSync(["zsh", "--version"]);
|
|
33
|
+
return Promise.resolve(result.exitCode === 0);
|
|
34
|
+
} catch {
|
|
35
|
+
return Promise.resolve(false);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
formatCompletions(completions: ReadonlyArray<CompletionItem>): string {
|
|
40
|
+
return completions
|
|
41
|
+
.map((c) => c.description ? `${c.value}:${c.description}` : c.value)
|
|
42
|
+
.join("\n");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
parseCompletionContext(args: ReadonlyArray<string>): CompletionContext {
|
|
46
|
+
const line = args[0] || "";
|
|
47
|
+
const cursorPosition = parseInt(args[1] || "0", 10) || line.length;
|
|
48
|
+
return { line, cursorPosition };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -16,8 +16,12 @@ import type {
|
|
|
16
16
|
import getLogger from "../../util/logger.ts";
|
|
17
17
|
import type Context from "../../api/Context.ts";
|
|
18
18
|
import DumpConfigCommand from "./command/DumpConfigCommand.ts";
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
KEY_VALUE_SERVICE_ID,
|
|
21
|
+
SECRET_SENTINEL_PREFIX,
|
|
22
|
+
} from "../../api/service/core/KeyValueService.ts";
|
|
20
23
|
import DefaultKeyValueService from "./DefaultKeyValueService.ts";
|
|
24
|
+
import DefaultSecretService from "./DefaultSecretService.ts";
|
|
21
25
|
import type Command from "../../api/command/Command.ts";
|
|
22
26
|
import argumentValueMerge from "../../runtime/values/argumentValueMerge.ts";
|
|
23
27
|
import {
|
|
@@ -79,6 +83,11 @@ const logger = getLogger("ConfigurationServiceProvider");
|
|
|
79
83
|
* default configuration will be used. The location of the configuration file can be modified via the
|
|
80
84
|
* {@link ConfigCommand}.
|
|
81
85
|
*
|
|
86
|
+
* NOTE: You may store default arguments for secrets by manually configuring secrets in your OS
|
|
87
|
+
* credential store and referencing them in the config file using the sentinel format
|
|
88
|
+
* `__SECRET__:<bun_secret_name>`. These values will be resolved from the OS secret store
|
|
89
|
+
* before being returned as default argument values. See "Generic Key-Value Service" below.
|
|
90
|
+
*
|
|
82
91
|
* *Environment Variables*
|
|
83
92
|
*
|
|
84
93
|
* Values are parsed using a key path defined by custom {@link Argument.configurationKey}
|
|
@@ -119,12 +128,21 @@ const logger = getLogger("ConfigurationServiceProvider");
|
|
|
119
128
|
* **Generic Key-Value Service**
|
|
120
129
|
*
|
|
121
130
|
* The same configuration file above is used to provide storage for the provided {@link KeyValueService}. The values
|
|
122
|
-
* are stored under a top level `key-values` property. These are
|
|
131
|
+
* are stored under a top level `key-values` property. These are expected to not be modified by the user of the CLI.
|
|
123
132
|
*
|
|
124
133
|
* The second and third level of properties is used to scope the key-values to specific command names
|
|
125
134
|
* (via {@link Command.name} values) and specific service IDs (via {@link ServiceInfo.serviceId} values).
|
|
126
135
|
* Both keys and values are string based.
|
|
127
136
|
*
|
|
137
|
+
* Values may be stored as OS-native secrets via {@link KeyValueService.setKey} with `isSecret=true`.
|
|
138
|
+
* When a value is stored as a secret, a sentinel value of the format `__SECRET__:<bun_secret_name>`
|
|
139
|
+
* is stored in the config file instead of the actual value. The actual value is stored in the OS
|
|
140
|
+
* credential store via Bun.secrets. The sentinel prefix `__SECRET__:` is reserved and must not be
|
|
141
|
+
* used for regular key-value data.
|
|
142
|
+
*
|
|
143
|
+
* Secret support requires `secretServiceEnabled=true` in the constructor (which also requires
|
|
144
|
+
* `configEnabled=true`).
|
|
145
|
+
*
|
|
128
146
|
* As an example:
|
|
129
147
|
* ```
|
|
130
148
|
* {
|
|
@@ -135,7 +153,7 @@ const logger = getLogger("ConfigurationServiceProvider");
|
|
|
135
153
|
* "commands": {
|
|
136
154
|
* "command1": {
|
|
137
155
|
* "foo1": "bar1",
|
|
138
|
-
* "foo2": "
|
|
156
|
+
* "foo2": "__SECRET__:command_command1_foo2"
|
|
139
157
|
* },
|
|
140
158
|
* "command2": {
|
|
141
159
|
* "foo1": "bar3"
|
|
@@ -157,6 +175,7 @@ export default class ConfigurationServiceProvider implements ServiceProvider {
|
|
|
157
175
|
public readonly envVarsEnabled: boolean;
|
|
158
176
|
public readonly configEnabled: boolean;
|
|
159
177
|
public readonly keyValueServiceEnabled: boolean;
|
|
178
|
+
public readonly secretServiceEnabled: boolean;
|
|
160
179
|
|
|
161
180
|
// the location of the currently managed configuration data
|
|
162
181
|
public configLocation: string | undefined;
|
|
@@ -178,7 +197,10 @@ export default class ConfigurationServiceProvider implements ServiceProvider {
|
|
|
178
197
|
#serviceKeyValueData = new Map<string, Map<string, string>>();
|
|
179
198
|
|
|
180
199
|
// the optional service providing scope limited key-value data to commands and other services
|
|
181
|
-
|
|
200
|
+
#defaultKeyValueService: DefaultKeyValueService | undefined;
|
|
201
|
+
|
|
202
|
+
// the optional service providing OS-native secret storage
|
|
203
|
+
#defaultSecretService: DefaultSecretService | undefined;
|
|
182
204
|
|
|
183
205
|
#currentCommandNameKeyValueScope: string | undefined;
|
|
184
206
|
#currentServiceIdKeyValueScope: string | undefined;
|
|
@@ -190,36 +212,49 @@ export default class ConfigurationServiceProvider implements ServiceProvider {
|
|
|
190
212
|
* @param envVarsEnabled optionally support checking env variables for default argument values.
|
|
191
213
|
* @param configEnabled optionally enable configuration file support for default argument values.
|
|
192
214
|
* @param keyValueServiceEnabled optionally provide a {@link KeyValueService} implementation: `configEnabled` must be true in this case
|
|
215
|
+
* @param secretServiceEnabled optionally enable OS-native secret storage via Bun.secrets: `configEnabled` must be true in this case
|
|
193
216
|
*/
|
|
194
217
|
public constructor(
|
|
195
218
|
servicePriority: number,
|
|
196
219
|
envVarsEnabled = false,
|
|
197
220
|
configEnabled = false,
|
|
198
221
|
keyValueServiceEnabled = false,
|
|
222
|
+
secretServiceEnabled = false,
|
|
199
223
|
) {
|
|
200
224
|
if (!configEnabled && keyValueServiceEnabled) {
|
|
201
225
|
throw new Error(
|
|
202
226
|
"configEnabled must be true if keyValueServiceEnabled is true",
|
|
203
227
|
);
|
|
204
228
|
}
|
|
229
|
+
if (!configEnabled && secretServiceEnabled) {
|
|
230
|
+
throw new Error(
|
|
231
|
+
"configEnabled must be true if secretServiceEnabled is true",
|
|
232
|
+
);
|
|
233
|
+
}
|
|
205
234
|
this.servicePriority = servicePriority;
|
|
206
235
|
this.envVarsEnabled = envVarsEnabled;
|
|
207
236
|
this.configEnabled = configEnabled;
|
|
208
237
|
this.keyValueServiceEnabled = keyValueServiceEnabled;
|
|
209
|
-
|
|
210
|
-
this.#defaultKeyValueService = new DefaultKeyValueService();
|
|
211
|
-
}
|
|
238
|
+
this.secretServiceEnabled = secretServiceEnabled;
|
|
212
239
|
}
|
|
213
240
|
|
|
214
|
-
public
|
|
241
|
+
public getServiceInfo(cliConfig: CLIConfig): Promise<ServiceInfo> {
|
|
215
242
|
const commands: Array<Command> = [];
|
|
216
243
|
|
|
217
244
|
if (this.configEnabled) {
|
|
218
245
|
commands.push(new ConfigCommand(this, this.servicePriority));
|
|
219
246
|
commands.push(new DumpConfigCommand(this));
|
|
220
247
|
}
|
|
248
|
+
if (this.secretServiceEnabled) {
|
|
249
|
+
this.#defaultSecretService = new DefaultSecretService(cliConfig.name);
|
|
250
|
+
}
|
|
251
|
+
if (this.keyValueServiceEnabled || this.secretServiceEnabled) {
|
|
252
|
+
this.#defaultKeyValueService = new DefaultKeyValueService(
|
|
253
|
+
this.#defaultSecretService,
|
|
254
|
+
);
|
|
255
|
+
}
|
|
221
256
|
return Promise.resolve({
|
|
222
|
-
// this may be undefined if keyValueServiceEnabled
|
|
257
|
+
// this may be undefined if keyValueServiceEnabled and secretServiceEnabled are false
|
|
223
258
|
service: this.#defaultKeyValueService,
|
|
224
259
|
// this may be empty if configEnabled is false
|
|
225
260
|
commands,
|
|
@@ -231,17 +266,19 @@ export default class ConfigurationServiceProvider implements ServiceProvider {
|
|
|
231
266
|
}
|
|
232
267
|
|
|
233
268
|
/**
|
|
234
|
-
* Retrieve the
|
|
269
|
+
* Retrieve the default command argument values (if any) for the provided {@link Command}.
|
|
235
270
|
*
|
|
236
271
|
* This will retrieve values both from the configuration location and environment variables.
|
|
237
272
|
*
|
|
238
273
|
* @param cliConfig the {@link CLIConfig} to use for retrieving configuration keys.
|
|
239
274
|
* @param command the {@link Command} instance to retrieve default argument values for.
|
|
240
275
|
*/
|
|
241
|
-
public getDefaultArgumentValues(
|
|
276
|
+
public async getDefaultArgumentValues(
|
|
242
277
|
cliConfig: CLIConfig,
|
|
243
278
|
command: Command,
|
|
244
|
-
):
|
|
279
|
+
): Promise<
|
|
280
|
+
PopulatedArgumentValues | PopulatedArgumentValueType | undefined
|
|
281
|
+
> {
|
|
245
282
|
if (!this.configEnabled) {
|
|
246
283
|
logger.debug(
|
|
247
284
|
"configuration of default values is not enabled",
|
|
@@ -265,6 +302,11 @@ export default class ConfigurationServiceProvider implements ServiceProvider {
|
|
|
265
302
|
return undefined;
|
|
266
303
|
}
|
|
267
304
|
|
|
305
|
+
let result:
|
|
306
|
+
| PopulatedArgumentValues
|
|
307
|
+
| PopulatedArgumentValueType
|
|
308
|
+
| undefined;
|
|
309
|
+
|
|
268
310
|
if (isGlobalModifierCommand(command) || isGlobalCommand(command)) {
|
|
269
311
|
const configuredValue = this.defaultsData.get(
|
|
270
312
|
command.name,
|
|
@@ -276,11 +318,7 @@ export default class ConfigurationServiceProvider implements ServiceProvider {
|
|
|
276
318
|
)
|
|
277
319
|
: undefined;
|
|
278
320
|
// default to environment variable value
|
|
279
|
-
|
|
280
|
-
return envVarValue;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
return configuredValue;
|
|
321
|
+
result = envVarValue !== undefined ? envVarValue : configuredValue;
|
|
284
322
|
} else if (isSubCommand(command)) {
|
|
285
323
|
const configuredValues = this.defaultsData.get(
|
|
286
324
|
command.name,
|
|
@@ -292,16 +330,56 @@ export default class ConfigurationServiceProvider implements ServiceProvider {
|
|
|
292
330
|
)
|
|
293
331
|
: undefined;
|
|
294
332
|
if (envVarValues === undefined) {
|
|
295
|
-
|
|
333
|
+
result = configuredValues;
|
|
334
|
+
} else if (configuredValues === undefined) {
|
|
335
|
+
result = envVarValues;
|
|
336
|
+
} else {
|
|
337
|
+
result = argumentValueMerge(
|
|
338
|
+
envVarValues,
|
|
339
|
+
configuredValues,
|
|
340
|
+
) as PopulatedArgumentValues;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (result !== undefined && this.#defaultSecretService) {
|
|
345
|
+
result = await this.#resolveSecrets(result);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return result;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
async #resolveSecrets(
|
|
352
|
+
value: PopulatedArgumentValues | PopulatedArgumentValueType,
|
|
353
|
+
): Promise<PopulatedArgumentValues | PopulatedArgumentValueType> {
|
|
354
|
+
if (typeof value === "string" && value.startsWith(SECRET_SENTINEL_PREFIX)) {
|
|
355
|
+
const bunSecretName = value.slice(SECRET_SENTINEL_PREFIX.length);
|
|
356
|
+
const secretValue = await this.#defaultSecretService!.getSecret(
|
|
357
|
+
bunSecretName,
|
|
358
|
+
);
|
|
359
|
+
if (secretValue === null) {
|
|
360
|
+
throw new Error(
|
|
361
|
+
`Secret not found in OS secret store for sentinel: '${value}'`,
|
|
362
|
+
);
|
|
296
363
|
}
|
|
297
|
-
|
|
298
|
-
|
|
364
|
+
return secretValue;
|
|
365
|
+
}
|
|
366
|
+
if ((typeof value === "object") && (value !== null)) {
|
|
367
|
+
if (Array.isArray(value)) {
|
|
368
|
+
const resolved = [];
|
|
369
|
+
for (const item of value) {
|
|
370
|
+
resolved.push(await this.#resolveSecrets(item));
|
|
371
|
+
}
|
|
372
|
+
return resolved as unknown as PopulatedArgumentValues;
|
|
373
|
+
}
|
|
374
|
+
const resolved: Record<string, unknown> = {};
|
|
375
|
+
for (const [key, val] of Object.entries(value)) {
|
|
376
|
+
resolved[key] = await this.#resolveSecrets(
|
|
377
|
+
val as PopulatedArgumentValueType,
|
|
378
|
+
);
|
|
299
379
|
}
|
|
300
|
-
return
|
|
301
|
-
envVarValues,
|
|
302
|
-
configuredValues,
|
|
303
|
-
) as PopulatedArgumentValues;
|
|
380
|
+
return resolved as unknown as PopulatedArgumentValues;
|
|
304
381
|
}
|
|
382
|
+
return value;
|
|
305
383
|
}
|
|
306
384
|
|
|
307
385
|
/**
|
|
@@ -313,9 +391,9 @@ export default class ConfigurationServiceProvider implements ServiceProvider {
|
|
|
313
391
|
* @throws {Error} if an existing scope is set.
|
|
314
392
|
*/
|
|
315
393
|
public setCommandKeyValueScope(commandName: string): void {
|
|
316
|
-
if (!this.keyValueServiceEnabled) {
|
|
394
|
+
if (!this.keyValueServiceEnabled && !this.secretServiceEnabled) {
|
|
317
395
|
throw new Error(
|
|
318
|
-
`Attempt to use KeyValueService which is not enabled`,
|
|
396
|
+
`Attempt to use KeyValueService/SecretService which is not enabled`,
|
|
319
397
|
);
|
|
320
398
|
}
|
|
321
399
|
if (this.#currentCommandNameKeyValueScope) {
|
|
@@ -332,12 +410,17 @@ export default class ConfigurationServiceProvider implements ServiceProvider {
|
|
|
332
410
|
|
|
333
411
|
logger.debug("currentCommandNameKeyValueScope: %s", commandName);
|
|
334
412
|
|
|
335
|
-
if (
|
|
336
|
-
this.#commandKeyValueData.
|
|
413
|
+
if (this.#defaultKeyValueService) {
|
|
414
|
+
if (!this.#commandKeyValueData.has(commandName)) {
|
|
415
|
+
this.#commandKeyValueData.set(commandName, new Map());
|
|
416
|
+
}
|
|
417
|
+
this.#defaultKeyValueService.setKeyValueData(
|
|
418
|
+
this.#commandKeyValueData.get(commandName)!,
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
if (this.#defaultSecretService) {
|
|
422
|
+
this.#defaultSecretService.setScope("command_" + commandName);
|
|
337
423
|
}
|
|
338
|
-
this.#defaultKeyValueService!.setKeyValueData(
|
|
339
|
-
this.#commandKeyValueData.get(commandName)!,
|
|
340
|
-
);
|
|
341
424
|
}
|
|
342
425
|
|
|
343
426
|
/**
|
|
@@ -349,9 +432,9 @@ export default class ConfigurationServiceProvider implements ServiceProvider {
|
|
|
349
432
|
* @throws {Error} if an existing scope is set.
|
|
350
433
|
*/
|
|
351
434
|
public setServiceKeyValueScope(serviceId: string): void {
|
|
352
|
-
if (!this.keyValueServiceEnabled) {
|
|
435
|
+
if (!this.keyValueServiceEnabled && !this.secretServiceEnabled) {
|
|
353
436
|
throw new Error(
|
|
354
|
-
`Attempt to use KeyValueService which is not enabled`,
|
|
437
|
+
`Attempt to use KeyValueService/SecretService which is not enabled`,
|
|
355
438
|
);
|
|
356
439
|
}
|
|
357
440
|
if (this.#currentServiceIdKeyValueScope) {
|
|
@@ -368,12 +451,17 @@ export default class ConfigurationServiceProvider implements ServiceProvider {
|
|
|
368
451
|
|
|
369
452
|
logger.debug("currentServiceIdKeyValueScope: %s", serviceId);
|
|
370
453
|
|
|
371
|
-
if (
|
|
372
|
-
this.#serviceKeyValueData.
|
|
454
|
+
if (this.#defaultKeyValueService) {
|
|
455
|
+
if (!this.#serviceKeyValueData.has(serviceId)) {
|
|
456
|
+
this.#serviceKeyValueData.set(serviceId, new Map());
|
|
457
|
+
}
|
|
458
|
+
this.#defaultKeyValueService.setKeyValueData(
|
|
459
|
+
this.#serviceKeyValueData.get(serviceId)!,
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
if (this.#defaultSecretService) {
|
|
463
|
+
this.#defaultSecretService.setScope("service_" + serviceId);
|
|
373
464
|
}
|
|
374
|
-
this.#defaultKeyValueService!.setKeyValueData(
|
|
375
|
-
this.#serviceKeyValueData.get(serviceId)!,
|
|
376
|
-
);
|
|
377
465
|
}
|
|
378
466
|
|
|
379
467
|
/**
|
|
@@ -383,12 +471,15 @@ export default class ConfigurationServiceProvider implements ServiceProvider {
|
|
|
383
471
|
* to the current configuration location.
|
|
384
472
|
*/
|
|
385
473
|
public async clearKeyValueScope(): Promise<void> {
|
|
386
|
-
if (!this.keyValueServiceEnabled) {
|
|
474
|
+
if (!this.keyValueServiceEnabled && !this.secretServiceEnabled) {
|
|
387
475
|
throw new Error(
|
|
388
|
-
`Attempt to use KeyValueService which is not enabled`,
|
|
476
|
+
`Attempt to use KeyValueService/SecretService which is not enabled`,
|
|
389
477
|
);
|
|
390
478
|
}
|
|
391
|
-
if (
|
|
479
|
+
if (
|
|
480
|
+
this.#defaultKeyValueService &&
|
|
481
|
+
this.#defaultKeyValueService.isDirty()
|
|
482
|
+
) {
|
|
392
483
|
if (this.configLocation === undefined) {
|
|
393
484
|
throw new Error(
|
|
394
485
|
"Attempt to write updated config with no configLocation set",
|
|
@@ -399,7 +490,12 @@ export default class ConfigurationServiceProvider implements ServiceProvider {
|
|
|
399
490
|
this.#currentCommandNameKeyValueScope = undefined;
|
|
400
491
|
this.#currentServiceIdKeyValueScope = undefined;
|
|
401
492
|
|
|
402
|
-
this.#defaultKeyValueService
|
|
493
|
+
if (this.#defaultKeyValueService) {
|
|
494
|
+
this.#defaultKeyValueService.clearKeyValueData();
|
|
495
|
+
}
|
|
496
|
+
if (this.#defaultSecretService) {
|
|
497
|
+
this.#defaultSecretService.clearScope();
|
|
498
|
+
}
|
|
403
499
|
}
|
|
404
500
|
|
|
405
501
|
public async initService(context: Context): Promise<void> {
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import type KeyValueService from "../../api/service/core/KeyValueService.ts";
|
|
2
|
+
import { SECRET_SENTINEL_PREFIX } from "../../api/service/core/KeyValueService.ts";
|
|
3
|
+
import type DefaultSecretService from "./DefaultSecretService.ts";
|
|
2
4
|
|
|
3
5
|
export default class DefaultKeyValueService implements KeyValueService {
|
|
4
6
|
#keyValueData: Map<string, string> | undefined;
|
|
5
7
|
#dirty = false;
|
|
8
|
+
readonly #secretService: DefaultSecretService | undefined;
|
|
9
|
+
|
|
10
|
+
constructor(secretService?: DefaultSecretService) {
|
|
11
|
+
this.#secretService = secretService;
|
|
12
|
+
}
|
|
6
13
|
|
|
7
14
|
public setKeyValueData(keyValueData: Map<string, string>) {
|
|
8
15
|
if (this.#keyValueData) {
|
|
@@ -23,7 +30,7 @@ export default class DefaultKeyValueService implements KeyValueService {
|
|
|
23
30
|
return this.#dirty;
|
|
24
31
|
}
|
|
25
32
|
|
|
26
|
-
public getKey(key: string): string {
|
|
33
|
+
public async getKey(key: string): Promise<string> {
|
|
27
34
|
if (this.#keyValueData === undefined) {
|
|
28
35
|
throw new Error("Attempt to access undefined key-value data");
|
|
29
36
|
}
|
|
@@ -32,28 +39,67 @@ export default class DefaultKeyValueService implements KeyValueService {
|
|
|
32
39
|
throw new Error("Attempt to access unknown key");
|
|
33
40
|
}
|
|
34
41
|
|
|
42
|
+
if (value.startsWith(SECRET_SENTINEL_PREFIX)) {
|
|
43
|
+
if (!this.#secretService) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
"Secret sentinel found but no secret service is available",
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
const bunSecretName = value.slice(SECRET_SENTINEL_PREFIX.length);
|
|
49
|
+
const secretValue = await this.#secretService.getSecret(bunSecretName);
|
|
50
|
+
if (secretValue === null) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`Secret not found in OS secret store for key: '${key}'`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
return secretValue;
|
|
56
|
+
}
|
|
57
|
+
|
|
35
58
|
return value;
|
|
36
59
|
}
|
|
37
60
|
|
|
38
|
-
public hasKey(key: string): boolean {
|
|
61
|
+
public hasKey(key: string): Promise<boolean> {
|
|
39
62
|
if (this.#keyValueData === undefined) {
|
|
40
|
-
|
|
63
|
+
return Promise.reject(
|
|
64
|
+
new Error("Attempt to access undefined key-value data"),
|
|
65
|
+
);
|
|
41
66
|
}
|
|
42
|
-
return this.#keyValueData.has(key);
|
|
67
|
+
return Promise.resolve(this.#keyValueData.has(key));
|
|
43
68
|
}
|
|
44
69
|
|
|
45
|
-
public setKey(
|
|
70
|
+
public async setKey(
|
|
71
|
+
key: string,
|
|
72
|
+
value: string,
|
|
73
|
+
isSecret = false,
|
|
74
|
+
): Promise<void> {
|
|
46
75
|
if (this.#keyValueData === undefined) {
|
|
47
76
|
throw new Error("Attempt to access undefined key-value data");
|
|
48
77
|
}
|
|
49
|
-
|
|
78
|
+
if (isSecret) {
|
|
79
|
+
if (!this.#secretService) {
|
|
80
|
+
throw new Error(
|
|
81
|
+
"Attempt to set a secret but no secret service is available",
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
const bunSecretName = await this.#secretService.setSecret(key, value);
|
|
85
|
+
this.#keyValueData.set(key, SECRET_SENTINEL_PREFIX + bunSecretName);
|
|
86
|
+
} else {
|
|
87
|
+
this.#keyValueData.set(key, value);
|
|
88
|
+
}
|
|
50
89
|
this.#dirty = true;
|
|
51
90
|
}
|
|
52
91
|
|
|
53
|
-
public deleteKey(key: string): void {
|
|
92
|
+
public async deleteKey(key: string): Promise<void> {
|
|
54
93
|
if (this.#keyValueData === undefined) {
|
|
55
94
|
throw new Error("Attempt to access undefined key-value data");
|
|
56
95
|
}
|
|
96
|
+
const value = this.#keyValueData.get(key);
|
|
97
|
+
if (value !== undefined && value.startsWith(SECRET_SENTINEL_PREFIX)) {
|
|
98
|
+
if (this.#secretService) {
|
|
99
|
+
const bunSecretName = value.slice(SECRET_SENTINEL_PREFIX.length);
|
|
100
|
+
await this.#secretService.deleteSecret(bunSecretName);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
57
103
|
this.#keyValueData.delete(key);
|
|
58
104
|
this.#dirty = true;
|
|
59
105
|
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type SecretService from "../../api/service/core/SecretService.ts";
|
|
2
|
+
import { secrets as bunSecrets } from "bun";
|
|
3
|
+
|
|
4
|
+
const MAX_NAME_LENGTH = 255;
|
|
5
|
+
const MAX_VALUE_BYTES = 2047;
|
|
6
|
+
|
|
7
|
+
interface SecretsApi {
|
|
8
|
+
get(options: { service: string; name: string }): Promise<string | null>;
|
|
9
|
+
set(
|
|
10
|
+
options: { service: string; name: string; value: string },
|
|
11
|
+
): Promise<void>;
|
|
12
|
+
delete(options: { service: string; name: string }): Promise<boolean>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default class DefaultSecretService implements SecretService {
|
|
16
|
+
readonly #serviceName: string;
|
|
17
|
+
readonly #secrets: SecretsApi;
|
|
18
|
+
#scope: string | undefined;
|
|
19
|
+
|
|
20
|
+
constructor(serviceName: string, secretsApi?: SecretsApi) {
|
|
21
|
+
this.#serviceName = DefaultSecretService.#sanitize(serviceName);
|
|
22
|
+
if (this.#serviceName.length > MAX_NAME_LENGTH) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
`Service name exceeds ${MAX_NAME_LENGTH} characters after sanitization: '${this.#serviceName}'`,
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
this.#secrets = secretsApi ?? bunSecrets;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static #sanitize(value: string): string {
|
|
31
|
+
return value.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public setScope(scope: string): void {
|
|
35
|
+
this.#scope = DefaultSecretService.#sanitize(scope);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public clearScope(): void {
|
|
39
|
+
this.#scope = undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public async setSecret(key: string, value: string): Promise<string> {
|
|
43
|
+
if (this.#scope === undefined) {
|
|
44
|
+
throw new Error("Attempt to set secret without a scope being set");
|
|
45
|
+
}
|
|
46
|
+
const name = this.#scope + "_" + DefaultSecretService.#sanitize(key);
|
|
47
|
+
if (name.length > MAX_NAME_LENGTH) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`Secret name exceeds ${MAX_NAME_LENGTH} characters: '${name}'`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
if (new TextEncoder().encode(value).byteLength > MAX_VALUE_BYTES) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`Secret value exceeds ${MAX_VALUE_BYTES} bytes`,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
await this.#secrets.set({
|
|
58
|
+
service: this.#serviceName,
|
|
59
|
+
name,
|
|
60
|
+
value,
|
|
61
|
+
});
|
|
62
|
+
return name;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public async getSecret(bunSecretName: string): Promise<string | null> {
|
|
66
|
+
return await this.#secrets.get({
|
|
67
|
+
service: this.#serviceName,
|
|
68
|
+
name: bunSecretName,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
public async deleteSecret(bunSecretName: string): Promise<boolean> {
|
|
73
|
+
return await this.#secrets.delete({
|
|
74
|
+
service: this.#serviceName,
|
|
75
|
+
name: bunSecretName,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
public async hasSecret(bunSecretName: string): Promise<boolean> {
|
|
80
|
+
return (await this.#secrets.get({
|
|
81
|
+
service: this.#serviceName,
|
|
82
|
+
name: bunSecretName,
|
|
83
|
+
})) !== null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ServiceInfo,
|
|
3
|
+
ServiceProvider,
|
|
4
|
+
} from "../../api/service/ServiceProvider.ts";
|
|
5
|
+
import DefaultDataDumpGeneratorService from "./DefaultDataDumpGeneratorService.ts";
|
|
6
|
+
import { DATA_DUMP_GENERATOR_SERVICE_ID } from "../../api/service/core/DataDumpGeneratorService.ts";
|
|
7
|
+
import type Context from "../../api/Context.ts";
|
|
8
|
+
import type PrinterService from "../../api/service/core/PrinterService.ts";
|
|
9
|
+
import { PRINTER_SERVICE_ID } from "../../api/service/core/PrinterService.ts";
|
|
10
|
+
import type CLIConfig from "../../api/CLIConfig.ts";
|
|
11
|
+
|
|
12
|
+
export default class DataDumpGeneratorServiceProvider
|
|
13
|
+
implements ServiceProvider {
|
|
14
|
+
readonly serviceId: string = DATA_DUMP_GENERATOR_SERVICE_ID;
|
|
15
|
+
readonly #defaultService: DefaultDataDumpGeneratorService;
|
|
16
|
+
|
|
17
|
+
public constructor(
|
|
18
|
+
readonly servicePriority: number,
|
|
19
|
+
) {
|
|
20
|
+
this.#defaultService = new DefaultDataDumpGeneratorService();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public getServiceInfo(_cliConfig: CLIConfig): Promise<ServiceInfo> {
|
|
24
|
+
return Promise.resolve({
|
|
25
|
+
service: this.#defaultService,
|
|
26
|
+
commands: [],
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
initService(context: Context): Promise<void> {
|
|
31
|
+
const printerService = context.getServiceById(
|
|
32
|
+
PRINTER_SERVICE_ID,
|
|
33
|
+
) as PrinterService;
|
|
34
|
+
|
|
35
|
+
this.#defaultService.colorEnabled = printerService.colorEnabled;
|
|
36
|
+
this.#defaultService.colorFunction = printerService.color
|
|
37
|
+
.bind(printerService);
|
|
38
|
+
|
|
39
|
+
return Promise.resolve(undefined);
|
|
40
|
+
}
|
|
41
|
+
}
|