@flowscripter/dynamic-cli-framework 12.1.0 → 12.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/dist/src/cli/BaseCLI.d.ts.map +1 -1
- package/dist/src/cli/BaseCLI.js +1 -1
- package/dist/src/runtime/runner.d.ts.map +1 -1
- package/dist/src/runtime/runner.js +4 -1
- package/dist/src/service/configuration/ConfigurationServiceProvider.d.ts +23 -13
- package/dist/src/service/configuration/ConfigurationServiceProvider.d.ts.map +1 -1
- package/dist/src/service/configuration/ConfigurationServiceProvider.js +69 -179
- package/dist/src/service/configuration/DefaultConfigurationService.d.ts +78 -0
- package/dist/src/service/configuration/DefaultConfigurationService.d.ts.map +1 -0
- package/dist/src/service/configuration/DefaultConfigurationService.js +296 -0
- package/dist/src/service/configuration/DefaultKeyValueService.d.ts.map +1 -1
- package/dist/src/service/configuration/DefaultKeyValueService.js +7 -3
- package/dist/src/service/configuration/DefaultSecretService.d.ts +17 -2
- package/dist/src/service/configuration/DefaultSecretService.d.ts.map +1 -1
- package/dist/src/service/configuration/DefaultSecretService.js +44 -5
- package/dist/src/service/configuration/KeyValueServiceProvider.d.ts +3 -1
- package/dist/src/service/configuration/KeyValueServiceProvider.d.ts.map +1 -1
- package/dist/src/service/configuration/KeyValueServiceProvider.js +6 -3
- package/dist/src/service/configuration/command/ConfigDumpSubCommand.d.ts +3 -7
- package/dist/src/service/configuration/command/ConfigDumpSubCommand.d.ts.map +1 -1
- package/dist/src/service/configuration/command/ConfigDumpSubCommand.js +5 -10
- package/dist/src/service/configuration/command/ConfigGetSubCommand.d.ts +16 -0
- package/dist/src/service/configuration/command/ConfigGetSubCommand.d.ts.map +1 -0
- package/dist/src/service/configuration/command/ConfigGetSubCommand.js +137 -0
- package/dist/src/service/configuration/command/ConfigSetSubCommand.d.ts +18 -0
- package/dist/src/service/configuration/command/ConfigSetSubCommand.d.ts.map +1 -0
- package/dist/src/service/configuration/command/ConfigSetSubCommand.js +289 -0
- package/dist/src/service/configuration/command/ConfigUnsetSubCommand.d.ts +15 -0
- package/dist/src/service/configuration/command/ConfigUnsetSubCommand.d.ts.map +1 -0
- package/dist/src/service/configuration/command/ConfigUnsetSubCommand.js +101 -0
- package/dist/src/service/configuration/configTargets.d.ts +91 -0
- package/dist/src/service/configuration/configTargets.d.ts.map +1 -0
- package/dist/src/service/configuration/configTargets.js +348 -0
- package/dist/src/service/configuration/resolveSecrets.d.ts.map +1 -1
- package/dist/src/service/configuration/resolveSecrets.js +7 -1
- package/package.json +1 -1
- package/src/cli/BaseCLI.ts +1 -0
- package/src/runtime/runner.ts +4 -1
- package/src/service/configuration/ConfigurationServiceProvider.ts +95 -227
- package/src/service/configuration/DefaultConfigurationService.ts +378 -0
- package/src/service/configuration/DefaultKeyValueService.ts +9 -3
- package/src/service/configuration/DefaultSecretService.ts +51 -8
- package/src/service/configuration/KeyValueServiceProvider.ts +7 -2
- package/src/service/configuration/command/ConfigDumpSubCommand.ts +6 -10
- package/src/service/configuration/command/ConfigGetSubCommand.ts +198 -0
- package/src/service/configuration/command/ConfigSetSubCommand.ts +401 -0
- package/src/service/configuration/command/ConfigUnsetSubCommand.ts +126 -0
- package/src/service/configuration/configTargets.ts +481 -0
- package/src/service/configuration/resolveSecrets.ts +7 -1
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import { MAXIMUM_COMPLEX_OPTION_NESTING_DEPTH, SECRET_SENTINEL_PREFIX, ValueTypeName, } from "@flowscripter/dynamic-cli-framework-api";
|
|
2
|
+
import { isComplexOption } from "../../runtime/argument/ArgumentTypeGuards.js";
|
|
3
|
+
import { getInvalidArgumentString, runCustomValidation, validateGlobalCommandArgumentValue, validateOptionValue, } from "../../runtime/values/argumentValueValidation.js";
|
|
4
|
+
import { getGlobalCommandValueFromEnvVars, getSubCommandValuesFromEnvVars, } from "../../util/envVarHelper.js";
|
|
5
|
+
import { isGlobalModifierCommand } from "../../runtime/command/CommandTypeGuards.js";
|
|
6
|
+
export const MASK = "********";
|
|
7
|
+
/**
|
|
8
|
+
* Collect all commands in the registry with `enableConfiguration` set, except the `config` modifier.
|
|
9
|
+
*/
|
|
10
|
+
export function getConfigTargets(registry) {
|
|
11
|
+
const targets = new Map();
|
|
12
|
+
const globals = [...registry.getGlobalCommands(), ...registry.getGlobalModifierCommands()];
|
|
13
|
+
for (const command of globals) {
|
|
14
|
+
if (command.enableConfiguration !== true || command.argument === undefined) {
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
if (isGlobalModifierCommand(command) && command.name === "config") {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
targets.set(command.name, {
|
|
21
|
+
key: command.name,
|
|
22
|
+
command,
|
|
23
|
+
argument: command.argument,
|
|
24
|
+
options: [],
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
for (const command of registry.getSubCommands()) {
|
|
28
|
+
if (command.enableConfiguration === true) {
|
|
29
|
+
targets.set(command.name, { key: command.name, command, options: command.options });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
for (const [key, { groupCommand, command }] of registry.getGroupAndMemberCommandsByJoinedName()) {
|
|
33
|
+
if (command.enableConfiguration === true) {
|
|
34
|
+
targets.set(key, { key, command, group: groupCommand, options: command.options });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return targets;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Parse an option path such as `address[1].port` into steps.
|
|
41
|
+
*/
|
|
42
|
+
export function parseOptionPath(path) {
|
|
43
|
+
const elements = path.split(".");
|
|
44
|
+
if (elements.length > MAXIMUM_COMPLEX_OPTION_NESTING_DEPTH) {
|
|
45
|
+
return `option path '${path}' exceeds the maximum nesting depth`;
|
|
46
|
+
}
|
|
47
|
+
const steps = [];
|
|
48
|
+
for (const element of elements) {
|
|
49
|
+
const match = /^([^[\]]+)(?:\[(\d+)\])?$/.exec(element);
|
|
50
|
+
if (match === null) {
|
|
51
|
+
return `invalid option path '${path}'`;
|
|
52
|
+
}
|
|
53
|
+
steps.push(match[2] === undefined ? { name: match[1] } : { name: match[1], index: Number(match[2]) });
|
|
54
|
+
}
|
|
55
|
+
return steps;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Walk the steps through the target's options returning the option the path refers to.
|
|
59
|
+
*/
|
|
60
|
+
export function resolveOptionPath(target, path) {
|
|
61
|
+
const steps = parseOptionPath(path);
|
|
62
|
+
if (typeof steps === "string") {
|
|
63
|
+
return steps;
|
|
64
|
+
}
|
|
65
|
+
let candidates = target.options;
|
|
66
|
+
let leaf;
|
|
67
|
+
for (let i = 0; i < steps.length; i++) {
|
|
68
|
+
const step = steps[i];
|
|
69
|
+
leaf = candidates.find((o) => o.name === step.name);
|
|
70
|
+
if (leaf === undefined) {
|
|
71
|
+
return `unknown option '${step.name}' in '${path}'`;
|
|
72
|
+
}
|
|
73
|
+
if (step.index !== undefined && leaf.isArray !== true) {
|
|
74
|
+
return `option '${step.name}' is not an array`;
|
|
75
|
+
}
|
|
76
|
+
if (i < steps.length - 1) {
|
|
77
|
+
if (!isComplexOption(leaf)) {
|
|
78
|
+
return `option '${step.name}' has no properties`;
|
|
79
|
+
}
|
|
80
|
+
if (leaf.isArray === true && step.index === undefined) {
|
|
81
|
+
return `option '${step.name}' is an array and requires an index`;
|
|
82
|
+
}
|
|
83
|
+
candidates = leaf.properties;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return { steps, leaf: leaf };
|
|
87
|
+
}
|
|
88
|
+
export function containsSecret(option) {
|
|
89
|
+
if (isComplexOption(option)) {
|
|
90
|
+
return option.properties.some(containsSecret);
|
|
91
|
+
}
|
|
92
|
+
return option.type === ValueTypeName.SECRET;
|
|
93
|
+
}
|
|
94
|
+
function asOptional(option) {
|
|
95
|
+
if (isComplexOption(option)) {
|
|
96
|
+
return {
|
|
97
|
+
...option,
|
|
98
|
+
isOptional: true,
|
|
99
|
+
properties: option.properties.map(asOptional),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
return { ...option, isOptional: true };
|
|
103
|
+
}
|
|
104
|
+
function clone(value) {
|
|
105
|
+
return value === undefined ? value : structuredClone(value);
|
|
106
|
+
}
|
|
107
|
+
export function getAtPath(root, steps) {
|
|
108
|
+
let current = root;
|
|
109
|
+
for (const step of steps) {
|
|
110
|
+
if (typeof current !== "object" || current === null) {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
current = current[step.name];
|
|
114
|
+
if (step.index !== undefined) {
|
|
115
|
+
if (!Array.isArray(current)) {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
current = current[step.index];
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return current;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Set a value at the path in the provided (mutable) root creating parents as needed.
|
|
125
|
+
*/
|
|
126
|
+
export function setAtPath(root, steps, value) {
|
|
127
|
+
let container = root;
|
|
128
|
+
steps.forEach((step, i) => {
|
|
129
|
+
const isLast = i === steps.length - 1;
|
|
130
|
+
if (step.index === undefined) {
|
|
131
|
+
if (isLast) {
|
|
132
|
+
container[step.name] = value;
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (typeof container[step.name] !== "object" || container[step.name] === null) {
|
|
136
|
+
container[step.name] = {};
|
|
137
|
+
}
|
|
138
|
+
container = container[step.name];
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (!Array.isArray(container[step.name])) {
|
|
142
|
+
container[step.name] = [];
|
|
143
|
+
}
|
|
144
|
+
const array = container[step.name];
|
|
145
|
+
if (step.index > array.length) {
|
|
146
|
+
throw new Error(`cannot set '${step.name}[${step.index}]': array has ${array.length} element(s) and sparse arrays are not supported`);
|
|
147
|
+
}
|
|
148
|
+
if (isLast) {
|
|
149
|
+
array[step.index] = value;
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (typeof array[step.index] !== "object" || array[step.index] === null) {
|
|
153
|
+
array[step.index] = {};
|
|
154
|
+
}
|
|
155
|
+
container = array[step.index];
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Remove the value at the path and any parent objects left empty. Returns false if there was no value.
|
|
160
|
+
*/
|
|
161
|
+
export function removeAtPath(root, steps) {
|
|
162
|
+
if (getAtPath(root, steps) === undefined) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
const last = steps[steps.length - 1];
|
|
166
|
+
if (last.index !== undefined) {
|
|
167
|
+
throw new Error("unsetting an array element is not supported: unset the whole option instead");
|
|
168
|
+
}
|
|
169
|
+
const parents = [root];
|
|
170
|
+
let container = root;
|
|
171
|
+
for (const step of steps.slice(0, -1)) {
|
|
172
|
+
container =
|
|
173
|
+
step.index === undefined
|
|
174
|
+
? container[step.name]
|
|
175
|
+
: container[step.name][step.index];
|
|
176
|
+
parents.push(container);
|
|
177
|
+
}
|
|
178
|
+
delete container[last.name];
|
|
179
|
+
for (let i = steps.length - 2; i >= 0; i--) {
|
|
180
|
+
const step = steps[i];
|
|
181
|
+
if (step.index !== undefined || Object.keys(parents[i + 1]).length > 0) {
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
delete parents[i][step.name];
|
|
185
|
+
}
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* All `__SECRET__:` sentinel strings within the value.
|
|
190
|
+
*/
|
|
191
|
+
export function collectSentinels(value) {
|
|
192
|
+
if (typeof value === "string") {
|
|
193
|
+
return value.startsWith(SECRET_SENTINEL_PREFIX) ? [value] : [];
|
|
194
|
+
}
|
|
195
|
+
if (Array.isArray(value)) {
|
|
196
|
+
return value.flatMap(collectSentinels);
|
|
197
|
+
}
|
|
198
|
+
if (typeof value === "object" && value !== null) {
|
|
199
|
+
return Object.values(value).flatMap(collectSentinels);
|
|
200
|
+
}
|
|
201
|
+
return [];
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* @param secrets plain text SECRET values which must not appear in the message.
|
|
205
|
+
*/
|
|
206
|
+
function invalidMessage(invalid, secrets = []) {
|
|
207
|
+
return invalid
|
|
208
|
+
.map((i) => {
|
|
209
|
+
const hidden = secrets.includes(i.value) ? { ...i, value: undefined } : i;
|
|
210
|
+
return getInvalidArgumentString(hidden, false) + (i.message ?? "");
|
|
211
|
+
})
|
|
212
|
+
.join("; ");
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Validate and convert the raw (string) value for the option path of the target. Required checks are
|
|
216
|
+
* not applied. Custom validators of the target are run later by {@link validateChanges}.
|
|
217
|
+
*/
|
|
218
|
+
export function prepareChange(target, path, raw, deferred) {
|
|
219
|
+
const resolved = resolveOptionPath(target, path);
|
|
220
|
+
if (typeof resolved === "string") {
|
|
221
|
+
return resolved;
|
|
222
|
+
}
|
|
223
|
+
const { steps, leaf } = resolved;
|
|
224
|
+
const hasIndex = steps[steps.length - 1].index !== undefined;
|
|
225
|
+
const whole = isComplexOption(leaf) || (leaf.isArray === true && !hasIndex);
|
|
226
|
+
const invalid = [];
|
|
227
|
+
let value;
|
|
228
|
+
if (whole) {
|
|
229
|
+
if (!/^\s*[[{]/.test(raw)) {
|
|
230
|
+
return `option '${path}' requires a JSON ${leaf.isArray === true && !hasIndex ? "array" : "object"} value`;
|
|
231
|
+
}
|
|
232
|
+
if (containsSecret(leaf)) {
|
|
233
|
+
return `option '${path}' contains SECRET values: set each secret property individually`;
|
|
234
|
+
}
|
|
235
|
+
try {
|
|
236
|
+
value = JSON.parse(raw);
|
|
237
|
+
}
|
|
238
|
+
catch (err) {
|
|
239
|
+
return `invalid JSON value for '${path}': ${err.message}`;
|
|
240
|
+
}
|
|
241
|
+
value = validateOptionValue(asOptional(leaf), value, invalid, deferred);
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
value = validateOptionValue({ ...asOptional(leaf), isArray: false }, raw, invalid, deferred);
|
|
245
|
+
}
|
|
246
|
+
if (invalid.length > 0) {
|
|
247
|
+
return `${path}: ${invalidMessage(invalid, [raw])}`;
|
|
248
|
+
}
|
|
249
|
+
const isSecret = !isComplexOption(leaf) && leaf.type === ValueTypeName.SECRET;
|
|
250
|
+
return { steps, path, leaf, value, isSecret };
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Validate a raw value for a global command argument.
|
|
254
|
+
*/
|
|
255
|
+
export function prepareGlobalValue(target, raw, deferred) {
|
|
256
|
+
const invalid = [];
|
|
257
|
+
const argument = { ...target.argument, isOptional: true };
|
|
258
|
+
const value = validateGlobalCommandArgumentValue({ ...target.command, argument }, raw, invalid, deferred);
|
|
259
|
+
if (invalid.length > 0 || value === undefined) {
|
|
260
|
+
return invalidMessage(invalid, [raw]);
|
|
261
|
+
}
|
|
262
|
+
return { value, isSecret: target.argument.type === ValueTypeName.SECRET };
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Apply the changes to a copy of the existing values and run the custom validators of the target.
|
|
266
|
+
* Returns the new values or an error message.
|
|
267
|
+
*/
|
|
268
|
+
export function applyChanges(target, existing, changes, deferred) {
|
|
269
|
+
const values = clone(existing ?? {});
|
|
270
|
+
try {
|
|
271
|
+
for (const change of changes) {
|
|
272
|
+
setAtPath(values, change.steps, change.value);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
catch (err) {
|
|
276
|
+
return err.message;
|
|
277
|
+
}
|
|
278
|
+
const invalid = [];
|
|
279
|
+
runCustomValidation(deferred, target.command, values, invalid);
|
|
280
|
+
if (invalid.length > 0) {
|
|
281
|
+
return invalidMessage(invalid, changes.filter((c) => c.isSecret).map((c) => c.value));
|
|
282
|
+
}
|
|
283
|
+
return values;
|
|
284
|
+
}
|
|
285
|
+
function maskSentinels(value) {
|
|
286
|
+
if (typeof value === "string") {
|
|
287
|
+
return value.startsWith(SECRET_SENTINEL_PREFIX) ? MASK : value;
|
|
288
|
+
}
|
|
289
|
+
if (Array.isArray(value)) {
|
|
290
|
+
return value.map(maskSentinels);
|
|
291
|
+
}
|
|
292
|
+
if (typeof value === "object" && value !== null) {
|
|
293
|
+
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, maskSentinels(v)]));
|
|
294
|
+
}
|
|
295
|
+
return value;
|
|
296
|
+
}
|
|
297
|
+
function maskOption(option, value) {
|
|
298
|
+
if (value === undefined || value === null) {
|
|
299
|
+
return value;
|
|
300
|
+
}
|
|
301
|
+
if (isComplexOption(option)) {
|
|
302
|
+
if (Array.isArray(value)) {
|
|
303
|
+
return value.map((v) => maskOption({ ...option, isArray: false }, v));
|
|
304
|
+
}
|
|
305
|
+
return maskOptions(option.properties, value);
|
|
306
|
+
}
|
|
307
|
+
if (option.type === ValueTypeName.SECRET) {
|
|
308
|
+
return Array.isArray(value) ? value.map(() => MASK) : MASK;
|
|
309
|
+
}
|
|
310
|
+
return value;
|
|
311
|
+
}
|
|
312
|
+
function maskOptions(options, value) {
|
|
313
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
314
|
+
return value;
|
|
315
|
+
}
|
|
316
|
+
return Object.fromEntries(Object.entries(value).map(([name, v]) => {
|
|
317
|
+
const option = options.find((o) => o.name === name);
|
|
318
|
+
return [name, option === undefined ? v : maskOption(option, v)];
|
|
319
|
+
}));
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Copy of a stored default value with SECRET typed values and any secret sentinels masked.
|
|
323
|
+
*/
|
|
324
|
+
export function maskDefault(target, value) {
|
|
325
|
+
let masked = value;
|
|
326
|
+
if (target?.argument !== undefined) {
|
|
327
|
+
if (target.argument.type === ValueTypeName.SECRET && value !== undefined) {
|
|
328
|
+
masked = MASK;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
else if (target !== undefined) {
|
|
332
|
+
masked = maskOptions(target.options, value);
|
|
333
|
+
}
|
|
334
|
+
return maskSentinels(masked);
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Whether an environment variable would override the stored value at the path.
|
|
338
|
+
*/
|
|
339
|
+
export function isOverriddenByEnvVar(provider, cliConfig, target, steps) {
|
|
340
|
+
if (!provider.envVarsEnabled) {
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
if (target.argument !== undefined) {
|
|
344
|
+
return (getGlobalCommandValueFromEnvVars(cliConfig, target.command) !== undefined);
|
|
345
|
+
}
|
|
346
|
+
const values = getSubCommandValuesFromEnvVars(cliConfig, target.command, target.group);
|
|
347
|
+
return getAtPath(values, steps) !== undefined;
|
|
348
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolveSecrets.d.ts","sourceRoot":"","sources":["../../../../src/service/configuration/resolveSecrets.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,wBAA8B,cAAc,CAAC,CAAC,EAC5C,KAAK,EAAE,CAAC,EACR,aAAa,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,GACzD,OAAO,CAAC,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"resolveSecrets.d.ts","sourceRoot":"","sources":["../../../../src/service/configuration/resolveSecrets.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,wBAA8B,cAAc,CAAC,CAAC,EAC5C,KAAK,EAAE,CAAC,EACR,aAAa,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,GACzD,OAAO,CAAC,CAAC,CAAC,CA0BZ"}
|
|
@@ -23,7 +23,13 @@ export default async function resolveSecrets(value, resolveSecret) {
|
|
|
23
23
|
}
|
|
24
24
|
const resolved = {};
|
|
25
25
|
for (const [key, val] of Object.entries(value)) {
|
|
26
|
-
|
|
26
|
+
// defineProperty so a "__proto__" key is an own property rather than replacing the prototype
|
|
27
|
+
Object.defineProperty(resolved, key, {
|
|
28
|
+
value: await resolveSecrets(val, resolveSecret),
|
|
29
|
+
enumerable: true,
|
|
30
|
+
writable: true,
|
|
31
|
+
configurable: true,
|
|
32
|
+
});
|
|
27
33
|
}
|
|
28
34
|
return resolved;
|
|
29
35
|
}
|
package/package.json
CHANGED
package/src/cli/BaseCLI.ts
CHANGED
|
@@ -395,6 +395,7 @@ export default class BaseCLI implements CLI {
|
|
|
395
395
|
this.#options.configFileSupportEnabled,
|
|
396
396
|
this.#options.secretServiceEnabled,
|
|
397
397
|
this.#options.configFormat,
|
|
398
|
+
this.#commandRegistry,
|
|
398
399
|
);
|
|
399
400
|
this.addServiceProvider(configurationServiceProvider);
|
|
400
401
|
const keyValueServiceProvider = new KeyValueServiceProvider(
|
package/src/runtime/runner.ts
CHANGED
|
@@ -121,8 +121,11 @@ async function executeParsedCommand(
|
|
|
121
121
|
parseResult!.populatedArgumentValues,
|
|
122
122
|
);
|
|
123
123
|
|
|
124
|
+
const commandScopeKey = parseResult.groupCommand
|
|
125
|
+
? `${parseResult.groupCommand.name}:${parseResult.command.name}`
|
|
126
|
+
: parseResult.command.name;
|
|
124
127
|
const commandContext = keyValueServiceProvider
|
|
125
|
-
? keyValueServiceProvider.getContextForScope(context, "command",
|
|
128
|
+
? keyValueServiceProvider.getContextForScope(context, "command", commandScopeKey)
|
|
126
129
|
: context;
|
|
127
130
|
if (isSubCommand(parseResult.command)) {
|
|
128
131
|
await parseResult.command.execute(
|