@flowscripter/dynamic-cli-framework 3.0.2 → 5.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/dist/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -3
- package/dist/src/cli/BaseCLI.d.ts +1 -0
- package/dist/src/cli/BaseCLI.d.ts.map +1 -1
- package/dist/src/cli/BaseCLI.js +8 -0
- package/dist/src/cli/BaseCLIFeatureOptions.d.ts +4 -0
- package/dist/src/cli/BaseCLIFeatureOptions.d.ts.map +1 -1
- package/dist/src/command/VersionCommand.js +2 -2
- package/dist/src/launcher.d.ts +0 -2
- package/dist/src/launcher.d.ts.map +1 -1
- package/dist/src/launcher.js +0 -20
- package/dist/src/service/banner/BannerServiceProvider.js +2 -2
- package/dist/src/service/completion/CompletionServiceProvider.js +4 -4
- package/dist/src/service/configuration/ConfigurationServiceProvider.d.ts +19 -8
- package/dist/src/service/configuration/ConfigurationServiceProvider.d.ts.map +1 -1
- package/dist/src/service/configuration/ConfigurationServiceProvider.js +26 -33
- package/dist/src/service/configuration/DefaultKeyValueService.d.ts +6 -6
- package/dist/src/service/configuration/DefaultKeyValueService.d.ts.map +1 -1
- package/dist/src/service/configuration/DefaultKeyValueService.js +60 -19
- package/dist/src/service/configuration/resolveSecrets.d.ts +11 -0
- package/dist/src/service/configuration/resolveSecrets.d.ts.map +1 -0
- package/dist/src/service/configuration/resolveSecrets.js +31 -0
- package/dist/src/service/plugin/DefaultPluginService.d.ts +5 -2
- package/dist/src/service/plugin/DefaultPluginService.d.ts.map +1 -1
- package/dist/src/service/plugin/DefaultPluginService.js +21 -2
- package/dist/src/service/plugin/PluginServiceProvider.d.ts +17 -0
- package/dist/src/service/plugin/PluginServiceProvider.d.ts.map +1 -0
- package/dist/src/service/plugin/PluginServiceProvider.js +95 -0
- package/dist/src/service/upgrade/DefaultUpgradeService.d.ts +2 -2
- package/dist/src/service/upgrade/DefaultUpgradeService.d.ts.map +1 -1
- package/dist/src/service/upgrade/DefaultUpgradeService.js +79 -43
- package/dist/src/service/upgrade/UpgradeServiceProvider.d.ts.map +1 -1
- package/dist/src/service/upgrade/UpgradeServiceProvider.js +9 -5
- package/dist/src/service/upgrade/command/UpgradeSubCommand.d.ts.map +1 -1
- package/dist/src/service/upgrade/command/UpgradeSubCommand.js +10 -1
- package/index.ts +2 -7
- package/package.json +3 -3
- package/src/cli/BaseCLI.ts +17 -0
- package/src/cli/BaseCLIFeatureOptions.ts +7 -0
- package/src/command/VersionCommand.ts +2 -2
- package/src/launcher.ts +0 -35
- package/src/service/banner/BannerServiceProvider.ts +2 -2
- package/src/service/completion/CompletionServiceProvider.ts +4 -4
- package/src/service/configuration/ConfigurationServiceProvider.ts +36 -40
- package/src/service/configuration/DefaultKeyValueService.ts +76 -22
- package/src/service/configuration/resolveSecrets.ts +35 -0
- package/src/service/plugin/DefaultPluginService.ts +41 -3
- package/src/service/plugin/PluginServiceProvider.ts +142 -0
- package/src/service/upgrade/DefaultUpgradeService.ts +98 -52
- package/src/service/upgrade/UpgradeServiceProvider.ts +9 -5
- package/src/service/upgrade/command/UpgradeSubCommand.ts +15 -1
- package/dist/src/cli/DynamicPluginRuntimeCLI.d.ts +0 -20
- package/dist/src/cli/DynamicPluginRuntimeCLI.d.ts.map +0 -1
- package/dist/src/cli/DynamicPluginRuntimeCLI.js +0 -50
- package/dist/src/service/plugin/DefaultPluginServiceProvider.d.ts +0 -15
- package/dist/src/service/plugin/DefaultPluginServiceProvider.d.ts.map +0 -1
- package/dist/src/service/plugin/DefaultPluginServiceProvider.js +0 -56
- package/src/cli/DynamicPluginRuntimeCLI.ts +0 -77
- package/src/service/plugin/DefaultPluginServiceProvider.ts +0 -82
|
@@ -72,8 +72,8 @@ export default class BannerServiceProvider implements ServiceProvider {
|
|
|
72
72
|
}
|
|
73
73
|
if (context.doesServiceExist(UPGRADE_SERVICE_ID)) {
|
|
74
74
|
const upgradeService = context.getServiceById(UPGRADE_SERVICE_ID) as UpgradeService;
|
|
75
|
-
const result = await upgradeService.getUpgradeCheckResult()
|
|
76
|
-
if (result
|
|
75
|
+
const result = await upgradeService.getUpgradeCheckResult();
|
|
76
|
+
if (result.status === "checked" && result.updateAvailable) {
|
|
77
77
|
await printerService.info(
|
|
78
78
|
` ${printerService.secondary(
|
|
79
79
|
`(${result.latestVersion} available, run '${cliConfig.name} upgrade')`,
|
|
@@ -61,8 +61,8 @@ export default class CompletionServiceProvider implements ServiceProvider {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
const keyValueService = context.getServiceById(KEY_VALUE_SERVICE_ID) as KeyValueService;
|
|
64
|
-
if (await keyValueService.
|
|
65
|
-
const status = await keyValueService.
|
|
64
|
+
if (await keyValueService.has("completion-status")) {
|
|
65
|
+
const status = await keyValueService.get("completion-status");
|
|
66
66
|
if (status === "installed" || status === "declined") {
|
|
67
67
|
logger.debug(() => `Completion status is '${status}', skipping auto-prompt`);
|
|
68
68
|
return;
|
|
@@ -99,7 +99,7 @@ export default class CompletionServiceProvider implements ServiceProvider {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
if (enableResult.value !== true) {
|
|
102
|
-
await keyValueService.
|
|
102
|
+
await keyValueService.set("completion-status", "declined");
|
|
103
103
|
return;
|
|
104
104
|
}
|
|
105
105
|
|
|
@@ -131,7 +131,7 @@ export default class CompletionServiceProvider implements ServiceProvider {
|
|
|
131
131
|
|
|
132
132
|
try {
|
|
133
133
|
await this.#installCompletion(context, shellType);
|
|
134
|
-
await keyValueService.
|
|
134
|
+
await keyValueService.set("completion-status", "installed");
|
|
135
135
|
} catch (error) {
|
|
136
136
|
if (context.doesServiceExist(PRINTER_SERVICE_ID)) {
|
|
137
137
|
const printerService = context.getServiceById(PRINTER_SERVICE_ID) as PrinterService;
|
|
@@ -7,6 +7,7 @@ import ConfigCommand from "./command/ConfigCommand.ts";
|
|
|
7
7
|
import type {
|
|
8
8
|
SingleValueType,
|
|
9
9
|
Values,
|
|
10
|
+
ValueNode,
|
|
10
11
|
PopulatedValues,
|
|
11
12
|
PopulatedValueType,
|
|
12
13
|
} from "@flowscripter/dynamic-cli-framework-api";
|
|
@@ -19,6 +20,7 @@ import {
|
|
|
19
20
|
} from "@flowscripter/dynamic-cli-framework-api";
|
|
20
21
|
import DefaultKeyValueService from "./DefaultKeyValueService.ts";
|
|
21
22
|
import DefaultSecretService from "./DefaultSecretService.ts";
|
|
23
|
+
import resolveSecrets from "./resolveSecrets.ts";
|
|
22
24
|
import type { Command } from "@flowscripter/dynamic-cli-framework-api";
|
|
23
25
|
import argumentValueMerge from "../../runtime/values/argumentValueMerge.ts";
|
|
24
26
|
import {
|
|
@@ -129,13 +131,21 @@ const logger = getLogger("ConfigurationServiceProvider");
|
|
|
129
131
|
*
|
|
130
132
|
* The second and third level of properties is used to scope the key-values to specific command names
|
|
131
133
|
* (via {@link Command.name} values) and specific service IDs (via {@link ServiceProvider.serviceId} values).
|
|
132
|
-
*
|
|
134
|
+
* Keys are strings, but values are arbitrary JSON (see {@link ValueNode}) - not limited to strings -
|
|
135
|
+
* and may be deep objects or arrays.
|
|
133
136
|
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
* is
|
|
137
|
-
*
|
|
138
|
-
*
|
|
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.
|
|
139
149
|
*
|
|
140
150
|
* Secret support requires `secretServiceEnabled=true` in the constructor (which also requires
|
|
141
151
|
* `configEnabled=true`).
|
|
@@ -150,7 +160,10 @@ const logger = getLogger("ConfigurationServiceProvider");
|
|
|
150
160
|
* "commands": {
|
|
151
161
|
* "command1": {
|
|
152
162
|
* "foo1": "bar1",
|
|
153
|
-
* "foo2": "__SECRET__:command_command1_foo2"
|
|
163
|
+
* "foo2": "__SECRET__:command_command1_foo2",
|
|
164
|
+
* "foo3": {
|
|
165
|
+
* "nested": ["a", "__SECRET__:command_command1_foo3_nested"]
|
|
166
|
+
* }
|
|
154
167
|
* },
|
|
155
168
|
* "command2": {
|
|
156
169
|
* "foo1": "bar3"
|
|
@@ -184,11 +197,11 @@ export default class ConfigurationServiceProvider implements ServiceProvider {
|
|
|
184
197
|
|
|
185
198
|
// the configuration data to be used by the CLI runner implementation
|
|
186
199
|
// when updating command scoped access to key-value data via the key-value service.
|
|
187
|
-
#commandKeyValueData = new Map<string, Map<string,
|
|
200
|
+
#commandKeyValueData = new Map<string, Map<string, ValueNode>>();
|
|
188
201
|
|
|
189
202
|
// the configuration data to be used by the CLI runner implementation
|
|
190
203
|
// when updating service scoped access to key-value data via the key-value service.
|
|
191
|
-
#serviceKeyValueData = new Map<string, Map<string,
|
|
204
|
+
#serviceKeyValueData = new Map<string, Map<string, ValueNode>>();
|
|
192
205
|
|
|
193
206
|
// the optional service providing scope limited key-value data to commands and other services
|
|
194
207
|
#defaultKeyValueService: DefaultKeyValueService | undefined;
|
|
@@ -301,40 +314,23 @@ export default class ConfigurationServiceProvider implements ServiceProvider {
|
|
|
301
314
|
}
|
|
302
315
|
|
|
303
316
|
if (result !== undefined && this.#defaultSecretService) {
|
|
304
|
-
result = await
|
|
317
|
+
result = await resolveSecrets<PopulatedValues | PopulatedValueType>(
|
|
318
|
+
result,
|
|
319
|
+
async (bunSecretName) => {
|
|
320
|
+
const secretValue = await this.#defaultSecretService!.getSecret(bunSecretName);
|
|
321
|
+
if (secretValue === null) {
|
|
322
|
+
throw new Error(
|
|
323
|
+
`Secret not found in OS secret store for sentinel: '${SECRET_SENTINEL_PREFIX}${bunSecretName}'`,
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
return secretValue;
|
|
327
|
+
},
|
|
328
|
+
);
|
|
305
329
|
}
|
|
306
330
|
|
|
307
331
|
return result;
|
|
308
332
|
}
|
|
309
333
|
|
|
310
|
-
async #resolveSecrets(
|
|
311
|
-
value: PopulatedValues | PopulatedValueType,
|
|
312
|
-
): Promise<PopulatedValues | PopulatedValueType> {
|
|
313
|
-
if (typeof value === "string" && value.startsWith(SECRET_SENTINEL_PREFIX)) {
|
|
314
|
-
const bunSecretName = value.slice(SECRET_SENTINEL_PREFIX.length);
|
|
315
|
-
const secretValue = await this.#defaultSecretService!.getSecret(bunSecretName);
|
|
316
|
-
if (secretValue === null) {
|
|
317
|
-
throw new Error(`Secret not found in OS secret store for sentinel: '${value}'`);
|
|
318
|
-
}
|
|
319
|
-
return secretValue;
|
|
320
|
-
}
|
|
321
|
-
if (typeof value === "object" && value !== null) {
|
|
322
|
-
if (Array.isArray(value)) {
|
|
323
|
-
const resolved = [];
|
|
324
|
-
for (const item of value) {
|
|
325
|
-
resolved.push(await this.#resolveSecrets(item));
|
|
326
|
-
}
|
|
327
|
-
return resolved as unknown as PopulatedValues;
|
|
328
|
-
}
|
|
329
|
-
const resolved: Record<string, unknown> = {};
|
|
330
|
-
for (const [key, val] of Object.entries(value)) {
|
|
331
|
-
resolved[key] = await this.#resolveSecrets(val as PopulatedValueType);
|
|
332
|
-
}
|
|
333
|
-
return resolved as unknown as PopulatedValues;
|
|
334
|
-
}
|
|
335
|
-
return value;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
334
|
/**
|
|
339
335
|
* Indicate that the contained {@link DefaultKeyValueService} should be set to have a scope of the provided
|
|
340
336
|
* command name.
|
|
@@ -542,8 +538,8 @@ export default class ConfigurationServiceProvider implements ServiceProvider {
|
|
|
542
538
|
const config: {
|
|
543
539
|
defaults?: Record<string, Values | SingleValueType>;
|
|
544
540
|
"key-values"?: {
|
|
545
|
-
commands?: Record<string, Record<string,
|
|
546
|
-
services?: Record<string, Record<string,
|
|
541
|
+
commands?: Record<string, Record<string, ValueNode>>;
|
|
542
|
+
services?: Record<string, Record<string, ValueNode>>;
|
|
547
543
|
};
|
|
548
544
|
} = {};
|
|
549
545
|
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
1
|
+
import type {
|
|
2
|
+
ValueNode,
|
|
3
|
+
KeyValueService,
|
|
4
|
+
SettableValueNode,
|
|
5
|
+
} from "@flowscripter/dynamic-cli-framework-api";
|
|
6
|
+
import { Secret, SECRET_SENTINEL_PREFIX } from "@flowscripter/dynamic-cli-framework-api";
|
|
3
7
|
import type DefaultSecretService from "./DefaultSecretService.ts";
|
|
8
|
+
import resolveSecrets from "./resolveSecrets.ts";
|
|
4
9
|
|
|
5
10
|
export default class DefaultKeyValueService implements KeyValueService {
|
|
6
|
-
#keyValueData: Map<string,
|
|
11
|
+
#keyValueData: Map<string, ValueNode> | undefined;
|
|
7
12
|
#dirty = false;
|
|
8
13
|
readonly #secretService: DefaultSecretService | undefined;
|
|
9
14
|
|
|
@@ -11,7 +16,7 @@ export default class DefaultKeyValueService implements KeyValueService {
|
|
|
11
16
|
this.#secretService = secretService;
|
|
12
17
|
}
|
|
13
18
|
|
|
14
|
-
public setKeyValueData(keyValueData: Map<string,
|
|
19
|
+
public setKeyValueData(keyValueData: Map<string, ValueNode>) {
|
|
15
20
|
if (this.#keyValueData) {
|
|
16
21
|
throw new Error("Attempt to overwrite key-value data, it should be cleared first");
|
|
17
22
|
}
|
|
@@ -28,7 +33,7 @@ export default class DefaultKeyValueService implements KeyValueService {
|
|
|
28
33
|
return this.#dirty;
|
|
29
34
|
}
|
|
30
35
|
|
|
31
|
-
public async
|
|
36
|
+
public async get<T extends ValueNode = ValueNode>(key: string): Promise<T> {
|
|
32
37
|
if (this.#keyValueData === undefined) {
|
|
33
38
|
throw new Error("Attempt to access undefined key-value data");
|
|
34
39
|
}
|
|
@@ -37,56 +42,105 @@ export default class DefaultKeyValueService implements KeyValueService {
|
|
|
37
42
|
throw new Error("Attempt to access unknown key");
|
|
38
43
|
}
|
|
39
44
|
|
|
40
|
-
|
|
45
|
+
const resolveSecret = async (bunSecretName: string): Promise<unknown> => {
|
|
41
46
|
if (!this.#secretService) {
|
|
42
47
|
throw new Error("Secret sentinel found but no secret service is available");
|
|
43
48
|
}
|
|
44
|
-
const bunSecretName = value.slice(SECRET_SENTINEL_PREFIX.length);
|
|
45
49
|
const secretValue = await this.#secretService.getSecret(bunSecretName);
|
|
46
50
|
if (secretValue === null) {
|
|
47
51
|
throw new Error(`Secret not found in OS secret store for key: '${key}'`);
|
|
48
52
|
}
|
|
49
|
-
|
|
50
|
-
|
|
53
|
+
try {
|
|
54
|
+
return JSON.parse(secretValue);
|
|
55
|
+
} catch {
|
|
56
|
+
return secretValue;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
51
59
|
|
|
52
|
-
return value
|
|
60
|
+
return resolveSecrets(value, resolveSecret) as Promise<T>;
|
|
53
61
|
}
|
|
54
62
|
|
|
55
|
-
public
|
|
63
|
+
public has(key: string): Promise<boolean> {
|
|
56
64
|
if (this.#keyValueData === undefined) {
|
|
57
65
|
return Promise.reject(new Error("Attempt to access undefined key-value data"));
|
|
58
66
|
}
|
|
59
67
|
return Promise.resolve(this.#keyValueData.has(key));
|
|
60
68
|
}
|
|
61
69
|
|
|
62
|
-
public async
|
|
70
|
+
public async set(key: string, value: SettableValueNode): Promise<void> {
|
|
63
71
|
if (this.#keyValueData === undefined) {
|
|
64
72
|
throw new Error("Attempt to access undefined key-value data");
|
|
65
73
|
}
|
|
66
|
-
|
|
74
|
+
this.#keyValueData.set(key, await this.#storeSecrets(key, value, []));
|
|
75
|
+
this.#dirty = true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async #storeSecrets(
|
|
79
|
+
key: string,
|
|
80
|
+
value: SettableValueNode,
|
|
81
|
+
path: Array<string>,
|
|
82
|
+
): Promise<ValueNode> {
|
|
83
|
+
if (value instanceof Secret) {
|
|
67
84
|
if (!this.#secretService) {
|
|
68
85
|
throw new Error("Attempt to set a secret but no secret service is available");
|
|
69
86
|
}
|
|
70
|
-
const
|
|
71
|
-
this.#
|
|
72
|
-
|
|
73
|
-
|
|
87
|
+
const secretName = [key, ...path].join("_");
|
|
88
|
+
const bunSecretName = await this.#secretService.setSecret(
|
|
89
|
+
secretName,
|
|
90
|
+
JSON.stringify(value.value),
|
|
91
|
+
);
|
|
92
|
+
return SECRET_SENTINEL_PREFIX + bunSecretName;
|
|
74
93
|
}
|
|
75
|
-
|
|
94
|
+
if (Array.isArray(value)) {
|
|
95
|
+
const result: Array<ValueNode> = [];
|
|
96
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
97
|
+
result.push(await this.#storeSecrets(key, value[i]!, [...path, String(i)]));
|
|
98
|
+
}
|
|
99
|
+
return result as ValueNode;
|
|
100
|
+
}
|
|
101
|
+
if (typeof value === "object" && value !== null) {
|
|
102
|
+
const result: Record<string, ValueNode> = {};
|
|
103
|
+
for (const [propertyName, propertyValue] of Object.entries(value)) {
|
|
104
|
+
result[propertyName] = await this.#storeSecrets(key, propertyValue, [
|
|
105
|
+
...path,
|
|
106
|
+
propertyName,
|
|
107
|
+
]);
|
|
108
|
+
}
|
|
109
|
+
return result as ValueNode;
|
|
110
|
+
}
|
|
111
|
+
return value;
|
|
76
112
|
}
|
|
77
113
|
|
|
78
|
-
public async
|
|
114
|
+
public async delete(key: string): Promise<void> {
|
|
79
115
|
if (this.#keyValueData === undefined) {
|
|
80
116
|
throw new Error("Attempt to access undefined key-value data");
|
|
81
117
|
}
|
|
82
118
|
const value = this.#keyValueData.get(key);
|
|
83
|
-
if (value !== undefined
|
|
119
|
+
if (value !== undefined) {
|
|
120
|
+
await this.#deleteSecrets(value);
|
|
121
|
+
}
|
|
122
|
+
this.#keyValueData.delete(key);
|
|
123
|
+
this.#dirty = true;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async #deleteSecrets(value: ValueNode): Promise<void> {
|
|
127
|
+
if (typeof value === "string" && value.startsWith(SECRET_SENTINEL_PREFIX)) {
|
|
84
128
|
if (this.#secretService) {
|
|
85
129
|
const bunSecretName = value.slice(SECRET_SENTINEL_PREFIX.length);
|
|
86
130
|
await this.#secretService.deleteSecret(bunSecretName);
|
|
87
131
|
}
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (Array.isArray(value)) {
|
|
135
|
+
for (const item of value) {
|
|
136
|
+
await this.#deleteSecrets(item);
|
|
137
|
+
}
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (typeof value === "object" && value !== null) {
|
|
141
|
+
for (const propertyValue of Object.values(value)) {
|
|
142
|
+
await this.#deleteSecrets(propertyValue as ValueNode);
|
|
143
|
+
}
|
|
88
144
|
}
|
|
89
|
-
this.#keyValueData.delete(key);
|
|
90
|
-
this.#dirty = true;
|
|
91
145
|
}
|
|
92
146
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { SECRET_SENTINEL_PREFIX } from "@flowscripter/dynamic-cli-framework-api";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Recursively walk the provided value and resolve any string leaf - at any depth - which starts
|
|
5
|
+
* with {@link SECRET_SENTINEL_PREFIX}, via the provided resolver callback.
|
|
6
|
+
*
|
|
7
|
+
* @param value the value to walk (a JSON-serializable value or a `PopulatedValues` /
|
|
8
|
+
* `PopulatedValueType` tree).
|
|
9
|
+
* @param resolveSecret callback used to resolve a sentinel-prefixed string (with the prefix
|
|
10
|
+
* stripped) to its actual value. Should throw if the secret cannot be found.
|
|
11
|
+
*/
|
|
12
|
+
export default async function resolveSecrets<T>(
|
|
13
|
+
value: T,
|
|
14
|
+
resolveSecret: (sentinelValue: string) => Promise<unknown>,
|
|
15
|
+
): Promise<T> {
|
|
16
|
+
if (typeof value === "string" && value.startsWith(SECRET_SENTINEL_PREFIX)) {
|
|
17
|
+
const bunSecretName = value.slice(SECRET_SENTINEL_PREFIX.length);
|
|
18
|
+
return (await resolveSecret(bunSecretName)) as unknown as T;
|
|
19
|
+
}
|
|
20
|
+
if (typeof value === "object" && value !== null) {
|
|
21
|
+
if (Array.isArray(value)) {
|
|
22
|
+
const resolved = [];
|
|
23
|
+
for (const item of value) {
|
|
24
|
+
resolved.push(await resolveSecrets(item, resolveSecret));
|
|
25
|
+
}
|
|
26
|
+
return resolved as unknown as T;
|
|
27
|
+
}
|
|
28
|
+
const resolved: Record<string, unknown> = {};
|
|
29
|
+
for (const [key, val] of Object.entries(value)) {
|
|
30
|
+
resolved[key] = await resolveSecrets(val, resolveSecret);
|
|
31
|
+
}
|
|
32
|
+
return resolved as unknown as T;
|
|
33
|
+
}
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
@@ -1,15 +1,53 @@
|
|
|
1
1
|
import type { PluginService } from "@flowscripter/dynamic-cli-framework-api";
|
|
2
|
+
import type { KeyValueService } from "@flowscripter/dynamic-cli-framework-api";
|
|
2
3
|
import type {
|
|
3
4
|
MarketplacePluginManager,
|
|
4
5
|
SearchQuery,
|
|
5
6
|
VersionedPluginDescriptor,
|
|
7
|
+
NpmjsPluginRepositoryConfig,
|
|
8
|
+
NpmPluginRepositoryConfig,
|
|
9
|
+
} from "@flowscripter/dynamic-plugin-framework";
|
|
10
|
+
import {
|
|
11
|
+
NpmPluginManager,
|
|
12
|
+
NpmjsPluginRepository,
|
|
13
|
+
NpmPluginRepository,
|
|
6
14
|
} from "@flowscripter/dynamic-plugin-framework";
|
|
7
15
|
|
|
8
16
|
export default class DefaultPluginService implements PluginService {
|
|
9
|
-
readonly #
|
|
17
|
+
readonly #defaultRemoteConfig: NpmjsPluginRepositoryConfig;
|
|
18
|
+
readonly #defaultLocalConfig: NpmPluginRepositoryConfig;
|
|
19
|
+
#pluginManager: MarketplacePluginManager;
|
|
20
|
+
|
|
21
|
+
constructor(remoteConfig: NpmjsPluginRepositoryConfig, localConfig: NpmPluginRepositoryConfig) {
|
|
22
|
+
this.#defaultRemoteConfig = remoteConfig;
|
|
23
|
+
this.#defaultLocalConfig = localConfig;
|
|
24
|
+
this.#pluginManager = new NpmPluginManager(
|
|
25
|
+
[new NpmjsPluginRepository(remoteConfig)],
|
|
26
|
+
new NpmPluginRepository(localConfig),
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get pluginManager(): MarketplacePluginManager {
|
|
31
|
+
return this.#pluginManager;
|
|
32
|
+
}
|
|
10
33
|
|
|
11
|
-
|
|
12
|
-
|
|
34
|
+
async applyKeyValueOverrides(keyValueService: KeyValueService): Promise<void> {
|
|
35
|
+
let remotesConfig: NpmjsPluginRepositoryConfig[] = [this.#defaultRemoteConfig];
|
|
36
|
+
if (await keyValueService.has("remotes-config")) {
|
|
37
|
+
remotesConfig = (await keyValueService.get(
|
|
38
|
+
"remotes-config",
|
|
39
|
+
)) as unknown as NpmjsPluginRepositoryConfig[];
|
|
40
|
+
}
|
|
41
|
+
let localConfig: NpmPluginRepositoryConfig = this.#defaultLocalConfig;
|
|
42
|
+
if (await keyValueService.has("local-config")) {
|
|
43
|
+
localConfig = (await keyValueService.get(
|
|
44
|
+
"local-config",
|
|
45
|
+
)) as unknown as NpmPluginRepositoryConfig;
|
|
46
|
+
}
|
|
47
|
+
this.#pluginManager = new NpmPluginManager(
|
|
48
|
+
remotesConfig.map((config) => new NpmjsPluginRepository(config)),
|
|
49
|
+
new NpmPluginRepository(localConfig),
|
|
50
|
+
);
|
|
13
51
|
}
|
|
14
52
|
|
|
15
53
|
search(query: Readonly<SearchQuery>): AsyncIterable<Readonly<VersionedPluginDescriptor>> {
|
|
@@ -0,0 +1,142 @@
|
|
|
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 { PrinterService, SpawnService } from "@flowscripter/dynamic-cli-framework-api";
|
|
5
|
+
import { PRINTER_SERVICE_ID, SPAWN_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
6
|
+
import type { FetchService } from "@flowscripter/dynamic-cli-framework-api";
|
|
7
|
+
import { FETCH_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
8
|
+
import { KEY_VALUE_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
9
|
+
import type { KeyValueService } from "@flowscripter/dynamic-cli-framework-api";
|
|
10
|
+
import { DYNAMIC_CLI_FRAMEWORK_COMMAND_FACTORY_EXTENSION_POINT } from "@flowscripter/dynamic-cli-framework-api";
|
|
11
|
+
import { DYNAMIC_CLI_FRAMEWORK_SERVICE_PROVIDER_FACTORY_EXTENSION_POINT } from "@flowscripter/dynamic-cli-framework-api";
|
|
12
|
+
import type { CommandFactory } from "@flowscripter/dynamic-cli-framework-api";
|
|
13
|
+
import type { ServiceProviderFactory } from "@flowscripter/dynamic-cli-framework-api";
|
|
14
|
+
import type {
|
|
15
|
+
FetchCapable,
|
|
16
|
+
NpmjsPluginRepositoryConfig,
|
|
17
|
+
NpmPluginRepositoryConfig,
|
|
18
|
+
SpawnCapable,
|
|
19
|
+
} from "@flowscripter/dynamic-plugin-framework";
|
|
20
|
+
import { PLUGIN_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
21
|
+
import DefaultPluginService from "./DefaultPluginService.ts";
|
|
22
|
+
import { PluginGroupCommand } from "./command/PluginGroupCommand.ts";
|
|
23
|
+
import SpawnInterfaceAdapter, {
|
|
24
|
+
type SpawnInterfaceAdapterOptions,
|
|
25
|
+
} from "./SpawnInterfaceAdapter.ts";
|
|
26
|
+
import FetchInterfaceAdapter, {
|
|
27
|
+
type FetchInterfaceAdapterOptions,
|
|
28
|
+
} from "./FetchInterfaceAdapter.ts";
|
|
29
|
+
import DefaultCommandRegistry from "../../runtime/registry/DefaultCommandRegistry.ts";
|
|
30
|
+
import DefaultServiceProviderRegistry from "../../runtime/registry/DefaultServiceProviderRegistry.ts";
|
|
31
|
+
import type DefaultContext from "../../runtime/DefaultContext.ts";
|
|
32
|
+
import getLogger from "../../util/logger.ts";
|
|
33
|
+
|
|
34
|
+
const logger = getLogger("PluginServiceProvider");
|
|
35
|
+
|
|
36
|
+
export default class PluginServiceProvider implements ServiceProvider {
|
|
37
|
+
readonly serviceId = PLUGIN_SERVICE_ID;
|
|
38
|
+
readonly servicePriority: number;
|
|
39
|
+
readonly #remoteConfig: NpmjsPluginRepositoryConfig;
|
|
40
|
+
readonly #localConfig: NpmPluginRepositoryConfig;
|
|
41
|
+
readonly #commandRegistry: DefaultCommandRegistry;
|
|
42
|
+
readonly #serviceProviderRegistry: DefaultServiceProviderRegistry;
|
|
43
|
+
readonly #spawnInterfaceAdapterOptions: SpawnInterfaceAdapterOptions;
|
|
44
|
+
readonly #fetchInterfaceAdapterOptions: FetchInterfaceAdapterOptions;
|
|
45
|
+
#pluginService: DefaultPluginService | undefined;
|
|
46
|
+
#cliConfig: CLIConfig | undefined;
|
|
47
|
+
|
|
48
|
+
constructor(
|
|
49
|
+
servicePriority: number,
|
|
50
|
+
remoteConfig: NpmjsPluginRepositoryConfig,
|
|
51
|
+
localConfig: NpmPluginRepositoryConfig,
|
|
52
|
+
commandRegistry: DefaultCommandRegistry,
|
|
53
|
+
serviceProviderRegistry: DefaultServiceProviderRegistry,
|
|
54
|
+
spawnInterfaceAdapterOptions: SpawnInterfaceAdapterOptions = {},
|
|
55
|
+
fetchInterfaceAdapterOptions: FetchInterfaceAdapterOptions = {},
|
|
56
|
+
) {
|
|
57
|
+
this.servicePriority = servicePriority;
|
|
58
|
+
this.#remoteConfig = remoteConfig;
|
|
59
|
+
this.#localConfig = localConfig;
|
|
60
|
+
this.#commandRegistry = commandRegistry;
|
|
61
|
+
this.#serviceProviderRegistry = serviceProviderRegistry;
|
|
62
|
+
this.#spawnInterfaceAdapterOptions = spawnInterfaceAdapterOptions;
|
|
63
|
+
this.#fetchInterfaceAdapterOptions = fetchInterfaceAdapterOptions;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async getServiceInfo(cliConfig: CLIConfig): Promise<ServiceInfo> {
|
|
67
|
+
this.#cliConfig = cliConfig;
|
|
68
|
+
this.#pluginService = new DefaultPluginService(this.#remoteConfig, this.#localConfig);
|
|
69
|
+
return {
|
|
70
|
+
service: this.#pluginService,
|
|
71
|
+
commands: [new PluginGroupCommand()],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async initService(context: Context): Promise<void> {
|
|
76
|
+
const pluginService = this.#pluginService!;
|
|
77
|
+
|
|
78
|
+
if (context.doesServiceExist(KEY_VALUE_SERVICE_ID)) {
|
|
79
|
+
const keyValueService = context.getServiceById(KEY_VALUE_SERVICE_ID) as KeyValueService;
|
|
80
|
+
await pluginService.applyKeyValueOverrides(keyValueService);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const pluginManager = pluginService.pluginManager;
|
|
84
|
+
|
|
85
|
+
if (!context.doesServiceExist(SPAWN_SERVICE_ID)) {
|
|
86
|
+
logger.debug("SpawnService not available, plugin manager will spawn processes directly");
|
|
87
|
+
} else if (!("setSpawn" in pluginManager)) {
|
|
88
|
+
logger.debug("Plugin manager does not support SpawnCapable, skipping spawn injection");
|
|
89
|
+
} else {
|
|
90
|
+
const spawnService = context.getServiceById(SPAWN_SERVICE_ID) as SpawnService;
|
|
91
|
+
const printerService = context.getServiceById(PRINTER_SERVICE_ID) as PrinterService;
|
|
92
|
+
const adapter = new SpawnInterfaceAdapter(
|
|
93
|
+
spawnService,
|
|
94
|
+
printerService,
|
|
95
|
+
this.#spawnInterfaceAdapterOptions,
|
|
96
|
+
);
|
|
97
|
+
(pluginManager as unknown as SpawnCapable).setSpawn(adapter);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!context.doesServiceExist(FETCH_SERVICE_ID)) {
|
|
101
|
+
logger.debug("FetchService not available, plugin manager will fetch directly");
|
|
102
|
+
} else if (!("setFetch" in pluginManager)) {
|
|
103
|
+
logger.debug("Plugin manager does not support FetchCapable, skipping fetch injection");
|
|
104
|
+
} else {
|
|
105
|
+
const fetchService = context.getServiceById(FETCH_SERVICE_ID) as FetchService;
|
|
106
|
+
const adapter = new FetchInterfaceAdapter(fetchService, this.#fetchInterfaceAdapterOptions);
|
|
107
|
+
(pluginManager as unknown as FetchCapable).setFetch(adapter);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
await pluginManager.registerExtensions(DYNAMIC_CLI_FRAMEWORK_COMMAND_FACTORY_EXTENSION_POINT);
|
|
111
|
+
await pluginManager.registerExtensions(
|
|
112
|
+
DYNAMIC_CLI_FRAMEWORK_SERVICE_PROVIDER_FACTORY_EXTENSION_POINT,
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const commandFactoryExtensions = await pluginManager.getRegisteredExtensions(
|
|
116
|
+
DYNAMIC_CLI_FRAMEWORK_COMMAND_FACTORY_EXTENSION_POINT,
|
|
117
|
+
);
|
|
118
|
+
for (const ext of commandFactoryExtensions) {
|
|
119
|
+
const factory = (await pluginManager.instantiate(ext.extensionHandle)) as CommandFactory;
|
|
120
|
+
for (const command of factory.getCommands()) {
|
|
121
|
+
this.#commandRegistry.addCommand(command);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const spFactoryExtensions = await pluginManager.getRegisteredExtensions(
|
|
126
|
+
DYNAMIC_CLI_FRAMEWORK_SERVICE_PROVIDER_FACTORY_EXTENSION_POINT,
|
|
127
|
+
);
|
|
128
|
+
for (const ext of spFactoryExtensions) {
|
|
129
|
+
const factory = (await pluginManager.instantiate(
|
|
130
|
+
ext.extensionHandle,
|
|
131
|
+
)) as ServiceProviderFactory;
|
|
132
|
+
for (const sp of factory.getServiceProviders()) {
|
|
133
|
+
const info = await sp.getServiceInfo(this.#cliConfig!);
|
|
134
|
+
if (info.service) {
|
|
135
|
+
(context as DefaultContext).addServiceInstance(sp.serviceId, info.service);
|
|
136
|
+
}
|
|
137
|
+
info.commands.forEach((cmd) => this.#commandRegistry.addCommand(cmd, sp.serviceId));
|
|
138
|
+
this.#serviceProviderRegistry.addServiceProvider(sp);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|