@flowscripter/dynamic-cli-framework 12.0.1 → 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/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/src/cli/BaseCLI.d.ts.map +1 -1
- package/dist/src/cli/BaseCLI.js +3 -1
- package/dist/src/cli/BaseCLIFeatureOptions.d.ts +6 -0
- package/dist/src/cli/BaseCLIFeatureOptions.d.ts.map +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 +33 -15
- package/dist/src/service/configuration/ConfigurationServiceProvider.d.ts.map +1 -1
- package/dist/src/service/configuration/ConfigurationServiceProvider.js +80 -173
- 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/format/ConfigFormat.d.ts +42 -0
- package/dist/src/service/configuration/format/ConfigFormat.d.ts.map +1 -0
- package/dist/src/service/configuration/format/ConfigFormat.js +0 -0
- package/dist/src/service/configuration/format/JsonConfigFormat.d.ts +7 -0
- package/dist/src/service/configuration/format/JsonConfigFormat.d.ts.map +1 -0
- package/dist/src/service/configuration/format/JsonConfigFormat.js +17 -0
- package/dist/src/service/configuration/format/TomlConfigFormat.d.ts +10 -0
- package/dist/src/service/configuration/format/TomlConfigFormat.d.ts.map +1 -0
- package/dist/src/service/configuration/format/TomlConfigFormat.js +78 -0
- package/dist/src/service/configuration/format/YamlConfigFormat.d.ts +9 -0
- package/dist/src/service/configuration/format/YamlConfigFormat.d.ts.map +1 -0
- package/dist/src/service/configuration/format/YamlConfigFormat.js +63 -0
- package/dist/src/service/configuration/resolveSecrets.d.ts.map +1 -1
- package/dist/src/service/configuration/resolveSecrets.js +7 -1
- package/dist/toml.d.ts +2 -0
- package/dist/toml.d.ts.map +1 -0
- package/dist/toml.js +1 -0
- package/dist/yaml.d.ts +2 -0
- package/dist/yaml.d.ts.map +1 -0
- package/dist/yaml.js +1 -0
- package/index.ts +3 -0
- package/package.json +27 -3
- package/src/cli/BaseCLI.ts +4 -0
- package/src/cli/BaseCLIFeatureOptions.ts +6 -0
- package/src/runtime/runner.ts +4 -1
- package/src/service/configuration/ConfigurationServiceProvider.ts +108 -214
- 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/format/ConfigFormat.ts +42 -0
- package/src/service/configuration/format/JsonConfigFormat.ts +22 -0
- package/src/service/configuration/format/TomlConfigFormat.ts +92 -0
- package/src/service/configuration/format/YamlConfigFormat.ts +72 -0
- package/src/service/configuration/resolveSecrets.ts +7 -1
- package/toml.ts +1 -0
- package/yaml.ts +1 -0
|
@@ -2,25 +2,20 @@ import { PRINTER_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
|
2
2
|
import { SYNTAX_HIGHLIGHTER_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
3
3
|
/**
|
|
4
4
|
* Member of the `config` group which dumps the configuration loaded by
|
|
5
|
-
* {@link
|
|
6
|
-
*
|
|
7
|
-
* Takes a direct reference to {@link ConfigurationServiceProvider.getConfigString}, wired by the
|
|
8
|
-
* provider that constructs this command, rather than looking it up via {@link Context} -
|
|
9
|
-
* deliberately, since a full config dump is not something every command/task should be able to
|
|
10
|
-
* reach through a general lookup (see {@link ConfigurationService}'s doc comment).
|
|
5
|
+
* {@link DefaultConfigurationService}, with secrets masked.
|
|
11
6
|
*/
|
|
12
7
|
export default class ConfigDumpSubCommand {
|
|
13
8
|
name = "dump";
|
|
14
9
|
description = "Dump configuration values";
|
|
15
10
|
options = [];
|
|
16
11
|
positionals = [];
|
|
17
|
-
#
|
|
18
|
-
constructor(
|
|
19
|
-
this.#
|
|
12
|
+
#configurationService;
|
|
13
|
+
constructor(configurationService) {
|
|
14
|
+
this.#configurationService = configurationService;
|
|
20
15
|
}
|
|
21
16
|
async execute(context) {
|
|
22
17
|
const printerService = context.getServiceById(PRINTER_SERVICE_ID);
|
|
23
18
|
const syntaxHighlighterService = context.getServiceById(SYNTAX_HIGHLIGHTER_SERVICE_ID);
|
|
24
|
-
await printerService.print(`${syntaxHighlighterService.highlight(this.#
|
|
19
|
+
await printerService.print(`${syntaxHighlighterService.highlight(this.#configurationService.getMaskedConfigString(), "json")}\n`);
|
|
25
20
|
}
|
|
26
21
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type Context, type Option, type Positional, type SubCommand, type Values } from "@flowscripter/dynamic-cli-framework-api";
|
|
2
|
+
import type DefaultConfigurationService from "../DefaultConfigurationService.ts";
|
|
3
|
+
/**
|
|
4
|
+
* Member of the `config` group which shows the configurable commands, their options and the
|
|
5
|
+
* values currently stored for them. SECRET values are masked.
|
|
6
|
+
*/
|
|
7
|
+
export default class ConfigGetSubCommand implements SubCommand {
|
|
8
|
+
#private;
|
|
9
|
+
readonly name = "get";
|
|
10
|
+
readonly description = "Show configurable commands, their options and stored default values";
|
|
11
|
+
readonly options: ReadonlyArray<Option>;
|
|
12
|
+
readonly positionals: ReadonlyArray<Positional>;
|
|
13
|
+
constructor(configurationService: DefaultConfigurationService);
|
|
14
|
+
execute(context: Context, argumentValues: Values): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=ConfigGetSubCommand.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConfigGetSubCommand.d.ts","sourceRoot":"","sources":["../../../../../src/service/configuration/command/ConfigGetSubCommand.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,OAAO,EACZ,KAAK,MAAM,EAEX,KAAK,UAAU,EAEf,KAAK,UAAU,EACf,KAAK,MAAM,EACZ,MAAM,yCAAyC,CAAC;AACjD,OAAO,KAAK,2BAA2B,MAAM,mCAAmC,CAAC;AA6CjF;;;GAGG;AACH,MAAM,CAAC,OAAO,OAAO,mBAAoB,YAAW,UAAU;;IAC5D,QAAQ,CAAC,IAAI,SAAS;IACtB,QAAQ,CAAC,WAAW,yEAAyE;IAC7F,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAarC;IACF,QAAQ,CAAC,WAAW,EAAE,aAAa,CAAC,UAAU,CAAC,CAAM;IAIrD,YAAmB,oBAAoB,EAAE,2BAA2B,EAEnE;IAEY,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAwC5E;CAwEF"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { PRINTER_SERVICE_ID, ValueTypeName, } from "@flowscripter/dynamic-cli-framework-api";
|
|
2
|
+
import { isComplexOption } from "../../../runtime/argument/ArgumentTypeGuards.js";
|
|
3
|
+
import { isOverriddenByEnvVar, maskDefault, } from "../configTargets.js";
|
|
4
|
+
function typeName(option) {
|
|
5
|
+
const name = isComplexOptionLike(option) ? "COMPLEX" : ValueTypeName[option.type];
|
|
6
|
+
return option.isArray === true ? `${name}[]` : name;
|
|
7
|
+
}
|
|
8
|
+
function isComplexOptionLike(option) {
|
|
9
|
+
return option.type === 5;
|
|
10
|
+
}
|
|
11
|
+
function constraints(option) {
|
|
12
|
+
const parts = [];
|
|
13
|
+
if (option.allowableValues !== undefined) {
|
|
14
|
+
parts.push(`one of: ${option.allowableValues.join(", ")}`);
|
|
15
|
+
}
|
|
16
|
+
if (option.minValueInclusive !== undefined || option.maxValueInclusive !== undefined) {
|
|
17
|
+
parts.push(`range: ${option.minValueInclusive ?? ""}..${option.maxValueInclusive ?? ""}`);
|
|
18
|
+
}
|
|
19
|
+
return parts.length === 0 ? "" : ` (${parts.join("; ")})`;
|
|
20
|
+
}
|
|
21
|
+
function mismatch(option, value) {
|
|
22
|
+
if (value === undefined || isComplexOption(option) || option.isArray === true) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
switch (option.type) {
|
|
26
|
+
case ValueTypeName.BOOLEAN:
|
|
27
|
+
return typeof value !== "boolean";
|
|
28
|
+
case ValueTypeName.INTEGER:
|
|
29
|
+
return !Number.isInteger(value);
|
|
30
|
+
case ValueTypeName.NUMBER:
|
|
31
|
+
return typeof value !== "number";
|
|
32
|
+
default:
|
|
33
|
+
return typeof value !== "string";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Member of the `config` group which shows the configurable commands, their options and the
|
|
38
|
+
* values currently stored for them. SECRET values are masked.
|
|
39
|
+
*/
|
|
40
|
+
export default class ConfigGetSubCommand {
|
|
41
|
+
name = "get";
|
|
42
|
+
description = "Show configurable commands, their options and stored default values";
|
|
43
|
+
options = [
|
|
44
|
+
{
|
|
45
|
+
name: "command",
|
|
46
|
+
type: ValueTypeName.STRING,
|
|
47
|
+
description: "Show only this command (or <group>:<member>)",
|
|
48
|
+
isOptional: true,
|
|
49
|
+
validate: (value) => {
|
|
50
|
+
const targets = this.#configurationService.getConfigTargets();
|
|
51
|
+
return targets.has(value)
|
|
52
|
+
? undefined
|
|
53
|
+
: `unknown or non-configurable command, valid commands: ${[...targets.keys()].join(", ")}`;
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
];
|
|
57
|
+
positionals = [];
|
|
58
|
+
#configurationService;
|
|
59
|
+
constructor(configurationService) {
|
|
60
|
+
this.#configurationService = configurationService;
|
|
61
|
+
}
|
|
62
|
+
async execute(context, argumentValues) {
|
|
63
|
+
const printer = context.getServiceById(PRINTER_SERVICE_ID);
|
|
64
|
+
const targets = this.#configurationService.getConfigTargets();
|
|
65
|
+
const selected = argumentValues["command"];
|
|
66
|
+
const keys = selected === undefined ? [...targets.keys()] : [selected];
|
|
67
|
+
if (selected === undefined) {
|
|
68
|
+
for (const key of this.#configurationService.defaultsData.keys()) {
|
|
69
|
+
if (!targets.has(key)) {
|
|
70
|
+
keys.push(key);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const lines = [];
|
|
75
|
+
for (const key of keys) {
|
|
76
|
+
const target = targets.get(key);
|
|
77
|
+
const stored = this.#configurationService.defaultsData.get(key);
|
|
78
|
+
if (target === undefined) {
|
|
79
|
+
lines.push(`${key} (unknown command, not configurable)`);
|
|
80
|
+
lines.push(` ${JSON.stringify(maskDefault(undefined, stored))}`);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
lines.push(key);
|
|
84
|
+
if (target.argument !== undefined) {
|
|
85
|
+
lines.push(` ${typeName(target.argument)}${constraints(target.argument)} = ${this.#show(context, target, [], stored)}`);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const values = (stored ?? {});
|
|
89
|
+
this.#listOptions(context, target, target.options, [], "", values, lines);
|
|
90
|
+
for (const name of Object.keys(values)) {
|
|
91
|
+
if (!target.options.some((o) => o.name === name)) {
|
|
92
|
+
lines.push(` ${name} (unknown option) = ${JSON.stringify(maskDefault(undefined, values[name]))}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
await printer.print(`${lines.join("\n")}\n`);
|
|
97
|
+
}
|
|
98
|
+
#show(context, target, steps, value) {
|
|
99
|
+
if (value === undefined) {
|
|
100
|
+
return isOverriddenByEnvVar(this.#configurationService, context.cliConfig, target, steps)
|
|
101
|
+
? "(not set; overridden by an environment variable)"
|
|
102
|
+
: "(not set)";
|
|
103
|
+
}
|
|
104
|
+
const text = JSON.stringify(maskDefault(target, value));
|
|
105
|
+
return isOverriddenByEnvVar(this.#configurationService, context.cliConfig, target, steps)
|
|
106
|
+
? `${text} (overridden by an environment variable)`
|
|
107
|
+
: text;
|
|
108
|
+
}
|
|
109
|
+
#listOptions(context, target, options, steps, prefix, values, lines) {
|
|
110
|
+
for (const option of options) {
|
|
111
|
+
const path = `${prefix}${option.name}`;
|
|
112
|
+
const optionSteps = [...steps, { name: option.name }];
|
|
113
|
+
const value = values?.[option.name];
|
|
114
|
+
const flag = mismatch(option, value) ? " (type mismatch)" : "";
|
|
115
|
+
const indent = " ".repeat(optionSteps.length);
|
|
116
|
+
const optional = option.isOptional === true || option.defaultValue !== undefined ? "" : " required";
|
|
117
|
+
const detail = isComplexOption(option) ? "" : constraints(option);
|
|
118
|
+
const shown = value === undefined
|
|
119
|
+
? this.#show(context, target, optionSteps, undefined)
|
|
120
|
+
: this.#showValue(context, target, optionSteps, option, value);
|
|
121
|
+
lines.push(`${indent}${path} ${typeName(option)}${optional}${detail} = ${shown}${flag}`);
|
|
122
|
+
if (isComplexOption(option) && option.isArray !== true) {
|
|
123
|
+
this.#listOptions(context, target, option.properties, optionSteps, `${path}.`, typeof value === "object" && value !== null
|
|
124
|
+
? value
|
|
125
|
+
: undefined, lines);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
#showValue(context, target, steps, option, value) {
|
|
130
|
+
const wrapper = { ...target, argument: undefined, options: [option] };
|
|
131
|
+
const masked = maskDefault(wrapper, { [option.name]: value });
|
|
132
|
+
const text = JSON.stringify(masked[option.name]);
|
|
133
|
+
return isOverriddenByEnvVar(this.#configurationService, context.cliConfig, target, steps)
|
|
134
|
+
? `${text} (overridden by an environment variable)`
|
|
135
|
+
: text;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type Context, type Option, type Positional, type SubCommand, type Values } from "@flowscripter/dynamic-cli-framework-api";
|
|
2
|
+
import type DefaultConfigurationService from "../DefaultConfigurationService.ts";
|
|
3
|
+
/**
|
|
4
|
+
* Member of the `config` group which sets a default value for an option of a configurable command.
|
|
5
|
+
*
|
|
6
|
+
* Values are validated against the option declared by the target command (type, allowable values,
|
|
7
|
+
* range and custom validators) but the target's required checks are not applied.
|
|
8
|
+
*/
|
|
9
|
+
export default class ConfigSetSubCommand implements SubCommand {
|
|
10
|
+
#private;
|
|
11
|
+
readonly name = "set";
|
|
12
|
+
readonly description = "Set a default value for an option of a command";
|
|
13
|
+
readonly options: ReadonlyArray<Option>;
|
|
14
|
+
readonly positionals: ReadonlyArray<Positional>;
|
|
15
|
+
constructor(configurationService: DefaultConfigurationService, readStdinFn?: () => Promise<string>);
|
|
16
|
+
execute(context: Context, argumentValues: Values): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=ConfigSetSubCommand.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConfigSetSubCommand.d.ts","sourceRoot":"","sources":["../../../../../src/service/configuration/command/ConfigSetSubCommand.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,OAAO,EACZ,KAAK,MAAM,EACX,KAAK,UAAU,EAGf,KAAK,UAAU,EACf,KAAK,MAAM,EACZ,MAAM,yCAAyC,CAAC;AACjD,OAAO,KAAK,2BAA2B,MAAM,mCAAmC,CAAC;AAqBjF;;;;;GAKG;AACH,MAAM,CAAC,OAAO,OAAO,mBAAoB,YAAW,UAAU;;IAC5D,QAAQ,CAAC,IAAI,SAAS;IACtB,QAAQ,CAAC,WAAW,oDAAoD;IACxE,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAwDrC;IACF,QAAQ,CAAC,WAAW,EAAE,aAAa,CAAC,UAAU,CAAC,CAAM;IAKrD,YACE,oBAAoB,EAAE,2BAA2B,EACjD,WAAW,GAAE,MAAM,OAAO,CAAC,MAAM,CAAa,EAI/C;IAqEK,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA2LrE;CA+BF"}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { ARGUMENT_PROMPTER_SERVICE_ID, PRINTER_SERVICE_ID, ValueTypeName, Icon, } from "@flowscripter/dynamic-cli-framework-api";
|
|
2
|
+
import { isComplexOption } from "../../../runtime/argument/ArgumentTypeGuards.js";
|
|
3
|
+
import { applyChanges, containsSecret, isOverriddenByEnvVar, prepareChange, prepareGlobalValue, resolveOptionPath, setAtPath, } from "../configTargets.js";
|
|
4
|
+
async function readStdin() {
|
|
5
|
+
return await Bun.stdin.text();
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Member of the `config` group which sets a default value for an option of a configurable command.
|
|
9
|
+
*
|
|
10
|
+
* Values are validated against the option declared by the target command (type, allowable values,
|
|
11
|
+
* range and custom validators) but the target's required checks are not applied.
|
|
12
|
+
*/
|
|
13
|
+
export default class ConfigSetSubCommand {
|
|
14
|
+
name = "set";
|
|
15
|
+
description = "Set a default value for an option of a command";
|
|
16
|
+
options = [
|
|
17
|
+
{
|
|
18
|
+
name: "command",
|
|
19
|
+
type: ValueTypeName.STRING,
|
|
20
|
+
description: "The command (or <group>:<member>) to set a default for",
|
|
21
|
+
isOptional: true,
|
|
22
|
+
validate: (value) => this.#validateCommand(value),
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: "option",
|
|
26
|
+
type: ValueTypeName.STRING,
|
|
27
|
+
description: "The option to set, for nested or array options a path such as 'address[1].port'. Omit for a global command.",
|
|
28
|
+
isOptional: true,
|
|
29
|
+
validate: (value, ctx) => {
|
|
30
|
+
const target = this.#targetOf(ctx.values);
|
|
31
|
+
if (target === undefined) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
if (target.argument !== undefined) {
|
|
35
|
+
return `command '${target.key}' has no options: omit --option`;
|
|
36
|
+
}
|
|
37
|
+
const resolved = resolveOptionPath(target, value);
|
|
38
|
+
return typeof resolved === "string" ? this.#withValidOptions(target, resolved) : undefined;
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: "value",
|
|
43
|
+
type: ValueTypeName.STRING,
|
|
44
|
+
description: "The value to set. Arrays and complex options take a JSON value. Prefer a prompt or --value-stdin for SECRET options.",
|
|
45
|
+
isOptional: true,
|
|
46
|
+
validate: (value, ctx) => {
|
|
47
|
+
const target = this.#targetOf(ctx.values);
|
|
48
|
+
if (target === undefined) {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
const values = ctx.values;
|
|
52
|
+
const result = this.#prepare(target, values["option"], value);
|
|
53
|
+
return typeof result === "string" ? result : undefined;
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: "value-stdin",
|
|
58
|
+
type: ValueTypeName.BOOLEAN,
|
|
59
|
+
description: "Read the value from stdin instead of --value",
|
|
60
|
+
isOptional: true,
|
|
61
|
+
validate: (value, ctx) => value === true && ctx.values["value"] !== undefined
|
|
62
|
+
? "cannot be combined with --value"
|
|
63
|
+
: undefined,
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
positionals = [];
|
|
67
|
+
#configurationService;
|
|
68
|
+
#readStdin;
|
|
69
|
+
constructor(configurationService, readStdinFn = readStdin) {
|
|
70
|
+
this.#configurationService = configurationService;
|
|
71
|
+
this.#readStdin = readStdinFn;
|
|
72
|
+
}
|
|
73
|
+
#validateCommand(command) {
|
|
74
|
+
const targets = this.#configurationService.getConfigTargets();
|
|
75
|
+
return targets.has(command)
|
|
76
|
+
? undefined
|
|
77
|
+
: `unknown or non-configurable command, valid commands: ${[...targets.keys()].join(", ")}`;
|
|
78
|
+
}
|
|
79
|
+
#targetOf(values) {
|
|
80
|
+
const command = values["command"];
|
|
81
|
+
return typeof command === "string"
|
|
82
|
+
? this.#configurationService.getConfigTargets().get(command)
|
|
83
|
+
: undefined;
|
|
84
|
+
}
|
|
85
|
+
#withValidOptions(target, message) {
|
|
86
|
+
return `${message}, valid options: ${target.options.map((o) => o.name).join(", ")}`;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Validate and convert a raw value and apply it to a copy of the stored defaults (running the
|
|
90
|
+
* target's custom validators).
|
|
91
|
+
*/
|
|
92
|
+
#prepare(target, option, raw) {
|
|
93
|
+
const deferred = [];
|
|
94
|
+
if (target.argument !== undefined) {
|
|
95
|
+
if (option !== undefined) {
|
|
96
|
+
return `command '${target.key}' has no options: omit --option`;
|
|
97
|
+
}
|
|
98
|
+
return prepareGlobalValue(target, raw, deferred);
|
|
99
|
+
}
|
|
100
|
+
if (option === undefined) {
|
|
101
|
+
return "--option is required with --value";
|
|
102
|
+
}
|
|
103
|
+
const change = prepareChange(target, option, raw, deferred);
|
|
104
|
+
if (typeof change === "string") {
|
|
105
|
+
return change;
|
|
106
|
+
}
|
|
107
|
+
const applied = applyChanges(target, this.#configurationService.defaultsData.get(target.key), [change], deferred);
|
|
108
|
+
return typeof applied === "string" ? applied : { change, isSecret: change.isSecret };
|
|
109
|
+
}
|
|
110
|
+
async #promptFor(context, args, missing) {
|
|
111
|
+
if (context.doesServiceExist(ARGUMENT_PROMPTER_SERVICE_ID)) {
|
|
112
|
+
const prompter = context.getServiceById(ARGUMENT_PROMPTER_SERVICE_ID);
|
|
113
|
+
const values = await prompter.promptForArguments(args);
|
|
114
|
+
if (values !== undefined) {
|
|
115
|
+
return values;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
throw new Error(`${missing} (prompting is not available)`);
|
|
119
|
+
}
|
|
120
|
+
async execute(context, argumentValues) {
|
|
121
|
+
const printer = context.getServiceById(PRINTER_SERVICE_ID);
|
|
122
|
+
const targets = this.#configurationService.getConfigTargets();
|
|
123
|
+
const commandNames = [...targets.keys()];
|
|
124
|
+
let commandKey = argumentValues["command"];
|
|
125
|
+
if (commandKey === undefined) {
|
|
126
|
+
const prompted = await this.#promptFor(context, [
|
|
127
|
+
{
|
|
128
|
+
name: "command",
|
|
129
|
+
description: "Command to configure",
|
|
130
|
+
type: ValueTypeName.STRING,
|
|
131
|
+
allowableValues: commandNames,
|
|
132
|
+
},
|
|
133
|
+
], `--command is required, valid commands: ${commandNames.join(", ")}`);
|
|
134
|
+
commandKey = prompted["command"];
|
|
135
|
+
}
|
|
136
|
+
const target = targets.get(commandKey);
|
|
137
|
+
if (target === undefined) {
|
|
138
|
+
throw new Error(`Unknown or non-configurable command '${commandKey}'`);
|
|
139
|
+
}
|
|
140
|
+
const optionPath = argumentValues["option"];
|
|
141
|
+
let raw = argumentValues["value"];
|
|
142
|
+
if (argumentValues["value-stdin"] === true) {
|
|
143
|
+
if (context.isTty.stdin) {
|
|
144
|
+
throw new Error("--value-stdin requires the value to be piped to stdin, or use the prompt");
|
|
145
|
+
}
|
|
146
|
+
raw = (await this.#readStdin()).replace(/\r?\n$/, "");
|
|
147
|
+
if (raw === "") {
|
|
148
|
+
throw new Error("No value was provided on stdin");
|
|
149
|
+
}
|
|
150
|
+
if (target.argument === undefined && optionPath === undefined) {
|
|
151
|
+
throw new Error("--option is required with --value-stdin");
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const deferred = [];
|
|
155
|
+
const changes = [];
|
|
156
|
+
let globalValue;
|
|
157
|
+
let globalIsSecret = false;
|
|
158
|
+
if (target.argument !== undefined) {
|
|
159
|
+
if (optionPath !== undefined) {
|
|
160
|
+
throw new Error(`Command '${target.key}' has no options: omit --option`);
|
|
161
|
+
}
|
|
162
|
+
if (raw === undefined) {
|
|
163
|
+
const prompted = await this.#promptFor(context, [
|
|
164
|
+
{
|
|
165
|
+
description: `Value for ${target.key}`,
|
|
166
|
+
...target.argument,
|
|
167
|
+
name: "value",
|
|
168
|
+
isOptional: false,
|
|
169
|
+
},
|
|
170
|
+
], "--value is required");
|
|
171
|
+
globalValue = prompted["value"];
|
|
172
|
+
globalIsSecret = target.argument.type === ValueTypeName.SECRET;
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
const result = prepareGlobalValue(target, raw, deferred);
|
|
176
|
+
if (typeof result === "string") {
|
|
177
|
+
throw new Error(result);
|
|
178
|
+
}
|
|
179
|
+
globalValue = result.value;
|
|
180
|
+
globalIsSecret = result.isSecret;
|
|
181
|
+
await this.#warnIfSecretArgument(context, printer, globalIsSecret, argumentValues);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
else if (optionPath === undefined && raw !== undefined) {
|
|
185
|
+
throw new Error("--option is required with --value");
|
|
186
|
+
}
|
|
187
|
+
else if (optionPath === undefined) {
|
|
188
|
+
const mandatory = target.options.filter((o) => o.isOptional !== true && o.defaultValue === undefined);
|
|
189
|
+
if (mandatory.length > 0) {
|
|
190
|
+
const prompted = await this.#promptFor(context, mandatory, `--value is required, mandatory options: ${mandatory.map((o) => o.name).join(", ")}`);
|
|
191
|
+
for (const option of mandatory) {
|
|
192
|
+
changes.push(this.#promptedChange(option, prompted[option.name]));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
const names = target.options.map((o) => o.name);
|
|
197
|
+
const selected = await this.#promptFor(context, [
|
|
198
|
+
{
|
|
199
|
+
name: "option",
|
|
200
|
+
description: "Option to set",
|
|
201
|
+
type: ValueTypeName.STRING,
|
|
202
|
+
allowableValues: names,
|
|
203
|
+
},
|
|
204
|
+
], `--option is required, valid options: ${names.join(", ")}`);
|
|
205
|
+
const option = target.options.find((o) => o.name === selected["option"]);
|
|
206
|
+
const prompted = await this.#promptFor(context, [{ ...option, isOptional: false }], "--value is required");
|
|
207
|
+
changes.push(this.#promptedChange(option, prompted[option.name]));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
const resolved = resolveOptionPath(target, optionPath);
|
|
212
|
+
if (typeof resolved === "string") {
|
|
213
|
+
throw new Error(this.#withValidOptions(target, resolved));
|
|
214
|
+
}
|
|
215
|
+
if (raw === undefined) {
|
|
216
|
+
const { leaf, steps } = resolved;
|
|
217
|
+
const hasIndex = steps[steps.length - 1].index !== undefined;
|
|
218
|
+
const prompted = await this.#promptFor(context, [{ ...leaf, isArray: hasIndex ? false : leaf.isArray, isOptional: false }], "--value is required");
|
|
219
|
+
changes.push({
|
|
220
|
+
...this.#promptedChange(leaf, prompted[leaf.name]),
|
|
221
|
+
steps,
|
|
222
|
+
path: optionPath,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
const change = prepareChange(target, optionPath, raw, deferred);
|
|
227
|
+
if (typeof change === "string") {
|
|
228
|
+
throw new Error(change);
|
|
229
|
+
}
|
|
230
|
+
changes.push(change);
|
|
231
|
+
await this.#warnIfSecretArgument(context, printer, change.isSecret, argumentValues);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
const existing = this.#configurationService.defaultsData.get(target.key);
|
|
235
|
+
if (target.argument === undefined) {
|
|
236
|
+
const applied = applyChanges(target, existing, changes, deferred);
|
|
237
|
+
if (typeof applied === "string") {
|
|
238
|
+
throw new Error(applied);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
const secretChanges = changes.filter((c) => c.isSecret);
|
|
242
|
+
const secrets = target.argument !== undefined
|
|
243
|
+
? globalIsSecret
|
|
244
|
+
? [{ key: [target.key], value: globalValue }]
|
|
245
|
+
: []
|
|
246
|
+
: secretChanges.map((c) => ({ key: [target.key, c.path], value: c.value }));
|
|
247
|
+
await this.#configurationService.commitDefault(target.key, secrets, (sentinels) => {
|
|
248
|
+
if (target.argument !== undefined) {
|
|
249
|
+
return globalIsSecret ? sentinels[0] : globalValue;
|
|
250
|
+
}
|
|
251
|
+
const values = structuredClone(existing ?? {});
|
|
252
|
+
for (const change of changes) {
|
|
253
|
+
const secretIndex = secretChanges.indexOf(change);
|
|
254
|
+
setAtPath(values, change.steps, secretIndex >= 0 ? sentinels[secretIndex] : change.value);
|
|
255
|
+
}
|
|
256
|
+
return values;
|
|
257
|
+
});
|
|
258
|
+
for (const change of target.argument !== undefined ? [] : changes) {
|
|
259
|
+
await printer.info(`Set default ${target.key}.${change.path}\n`);
|
|
260
|
+
if (isOverriddenByEnvVar(this.#configurationService, context.cliConfig, target, change.steps)) {
|
|
261
|
+
await printer.warn(`An environment variable overrides the default for ${target.key}.${change.path}\n`, Icon.ALERT);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
if (target.argument !== undefined) {
|
|
265
|
+
await printer.info(`Set default ${target.key}\n`);
|
|
266
|
+
if (isOverriddenByEnvVar(this.#configurationService, context.cliConfig, target, [])) {
|
|
267
|
+
await printer.warn(`An environment variable overrides the default for ${target.key}\n`, Icon.ALERT);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
#promptedChange(option, value) {
|
|
272
|
+
if (containsSecret(option) && (isComplexOption(option) || option.isArray === true)) {
|
|
273
|
+
throw new Error(`Option '${option.name}' contains SECRET values: set each secret property individually using --option`);
|
|
274
|
+
}
|
|
275
|
+
return {
|
|
276
|
+
steps: [{ name: option.name }],
|
|
277
|
+
path: option.name,
|
|
278
|
+
leaf: option,
|
|
279
|
+
value,
|
|
280
|
+
isSecret: !isComplexOption(option) && option.type === ValueTypeName.SECRET,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
async #warnIfSecretArgument(context, printer, isSecret, argumentValues) {
|
|
284
|
+
if (isSecret && argumentValues["value-stdin"] !== true && context.isTty.stdin) {
|
|
285
|
+
await printer.warn("SECRET value passed as a plain argument. This is not recommended as it may be exposed via shell history " +
|
|
286
|
+
"and process listings (and in debug logs). Use the prompt or --value-stdin instead.\n\n", Icon.ALERT);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type Context, type Option, type Positional, type SubCommand, type Values } from "@flowscripter/dynamic-cli-framework-api";
|
|
2
|
+
import type DefaultConfigurationService from "../DefaultConfigurationService.ts";
|
|
3
|
+
/**
|
|
4
|
+
* Member of the `config` group which removes a stored default value (and any OS secret it refers to).
|
|
5
|
+
*/
|
|
6
|
+
export default class ConfigUnsetSubCommand implements SubCommand {
|
|
7
|
+
#private;
|
|
8
|
+
readonly name = "unset";
|
|
9
|
+
readonly description = "Remove a default value for an option of a command";
|
|
10
|
+
readonly options: ReadonlyArray<Option>;
|
|
11
|
+
readonly positionals: ReadonlyArray<Positional>;
|
|
12
|
+
constructor(configurationService: DefaultConfigurationService);
|
|
13
|
+
execute(context: Context, argumentValues: Values): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=ConfigUnsetSubCommand.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConfigUnsetSubCommand.d.ts","sourceRoot":"","sources":["../../../../../src/service/configuration/command/ConfigUnsetSubCommand.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,OAAO,EACZ,KAAK,MAAM,EACX,KAAK,UAAU,EAEf,KAAK,UAAU,EACf,KAAK,MAAM,EACZ,MAAM,yCAAyC,CAAC;AACjD,OAAO,KAAK,2BAA2B,MAAM,mCAAmC,CAAC;AAKjF;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,qBAAsB,YAAW,UAAU;;IAC9D,QAAQ,CAAC,IAAI,WAAW;IACxB,QAAQ,CAAC,WAAW,uDAAuD;IAC3E,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAuCrC;IACF,QAAQ,CAAC,WAAW,EAAE,aAAa,CAAC,UAAU,CAAC,CAAM;IAIrD,YAAmB,oBAAoB,EAAE,2BAA2B,EAEnE;IAEK,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAqDrE;CACF"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { ARGUMENT_PROMPTER_SERVICE_ID, PRINTER_SERVICE_ID, ValueTypeName, } from "@flowscripter/dynamic-cli-framework-api";
|
|
2
|
+
import { removeAtPath, resolveOptionPath } from "../configTargets.js";
|
|
3
|
+
/**
|
|
4
|
+
* Member of the `config` group which removes a stored default value (and any OS secret it refers to).
|
|
5
|
+
*/
|
|
6
|
+
export default class ConfigUnsetSubCommand {
|
|
7
|
+
name = "unset";
|
|
8
|
+
description = "Remove a default value for an option of a command";
|
|
9
|
+
options = [
|
|
10
|
+
{
|
|
11
|
+
name: "command",
|
|
12
|
+
type: ValueTypeName.STRING,
|
|
13
|
+
description: "The command (or <group>:<member>) to remove defaults from",
|
|
14
|
+
isOptional: true,
|
|
15
|
+
validate: (value) => {
|
|
16
|
+
const targets = this.#configurationService.getConfigTargets();
|
|
17
|
+
return targets.has(value) ||
|
|
18
|
+
this.#configurationService.defaultsData.has(value)
|
|
19
|
+
? undefined
|
|
20
|
+
: `unknown command, valid commands: ${[...targets.keys()].join(", ")}`;
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: "option",
|
|
25
|
+
type: ValueTypeName.STRING,
|
|
26
|
+
description: "The option (or option path) to remove. Omit to remove all defaults of the command.",
|
|
27
|
+
isOptional: true,
|
|
28
|
+
validate: (value, ctx) => {
|
|
29
|
+
const target = this.#configurationService
|
|
30
|
+
.getConfigTargets()
|
|
31
|
+
.get(ctx.values["command"]);
|
|
32
|
+
if (target === undefined) {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
if (target.argument !== undefined) {
|
|
36
|
+
return `command '${target.key}' has no options: omit --option`;
|
|
37
|
+
}
|
|
38
|
+
const resolved = resolveOptionPath(target, value);
|
|
39
|
+
if (typeof resolved === "string") {
|
|
40
|
+
return `${resolved}, valid options: ${target.options.map((o) => o.name).join(", ")}`;
|
|
41
|
+
}
|
|
42
|
+
return resolved.steps[resolved.steps.length - 1].index !== undefined
|
|
43
|
+
? "unsetting an array element is not supported: unset the whole option instead"
|
|
44
|
+
: undefined;
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
positionals = [];
|
|
49
|
+
#configurationService;
|
|
50
|
+
constructor(configurationService) {
|
|
51
|
+
this.#configurationService = configurationService;
|
|
52
|
+
}
|
|
53
|
+
async execute(context, argumentValues) {
|
|
54
|
+
const printer = context.getServiceById(PRINTER_SERVICE_ID);
|
|
55
|
+
const targets = this.#configurationService.getConfigTargets();
|
|
56
|
+
let commandKey = argumentValues["command"];
|
|
57
|
+
if (commandKey === undefined) {
|
|
58
|
+
const names = [...this.#configurationService.defaultsData.keys()];
|
|
59
|
+
const prompter = context.doesServiceExist(ARGUMENT_PROMPTER_SERVICE_ID)
|
|
60
|
+
? context.getServiceById(ARGUMENT_PROMPTER_SERVICE_ID)
|
|
61
|
+
: undefined;
|
|
62
|
+
const prompted = names.length > 0
|
|
63
|
+
? await prompter?.promptForArguments([
|
|
64
|
+
{
|
|
65
|
+
name: "command",
|
|
66
|
+
description: "Command to remove defaults from",
|
|
67
|
+
type: ValueTypeName.STRING,
|
|
68
|
+
allowableValues: names,
|
|
69
|
+
},
|
|
70
|
+
])
|
|
71
|
+
: undefined;
|
|
72
|
+
if (prompted === undefined) {
|
|
73
|
+
throw new Error(`--command is required, commands with defaults: ${names.join(", ")}`);
|
|
74
|
+
}
|
|
75
|
+
commandKey = prompted["command"];
|
|
76
|
+
}
|
|
77
|
+
const existing = this.#configurationService.defaultsData.get(commandKey);
|
|
78
|
+
const optionPath = argumentValues["option"];
|
|
79
|
+
const target = targets.get(commandKey);
|
|
80
|
+
if (optionPath !== undefined && target === undefined) {
|
|
81
|
+
throw new Error(`Unknown or non-configurable command '${commandKey}'`);
|
|
82
|
+
}
|
|
83
|
+
let next;
|
|
84
|
+
let removed = existing !== undefined;
|
|
85
|
+
if (optionPath !== undefined && existing !== undefined) {
|
|
86
|
+
const resolved = resolveOptionPath(target, optionPath);
|
|
87
|
+
if (typeof resolved === "string") {
|
|
88
|
+
throw new Error(resolved);
|
|
89
|
+
}
|
|
90
|
+
const values = structuredClone(existing);
|
|
91
|
+
removed = removeAtPath(values, resolved.steps);
|
|
92
|
+
next = Object.keys(values).length > 0 ? values : undefined;
|
|
93
|
+
}
|
|
94
|
+
if (!removed) {
|
|
95
|
+
await printer.info(`Nothing to unset for ${commandKey}${optionPath ? `.${optionPath}` : ""}\n`);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
await this.#configurationService.commitDefault(commandKey, [], () => next);
|
|
99
|
+
await printer.info(`Removed default ${commandKey}${optionPath ? `.${optionPath}` : ""}\n`);
|
|
100
|
+
}
|
|
101
|
+
}
|