@flowscripter/dynamic-cli-framework 10.0.0 → 10.1.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/runtime/parser.d.ts +8 -2
- package/dist/src/runtime/parser.d.ts.map +1 -1
- package/dist/src/runtime/parser.js +11 -6
- package/dist/src/runtime/runner.d.ts.map +1 -1
- package/dist/src/runtime/runner.js +4 -1
- package/dist/src/runtime/values/ValuePopulationResult.d.ts +4 -0
- package/dist/src/runtime/values/ValuePopulationResult.d.ts.map +1 -1
- package/dist/src/runtime/values/globalCommandValuePopulation.d.ts.map +1 -1
- package/dist/src/runtime/values/globalCommandValuePopulation.js +11 -3
- package/dist/src/runtime/values/subCommandValuePopulation.d.ts.map +1 -1
- package/dist/src/runtime/values/subCommandValuePopulation.js +39 -9
- package/dist/src/util/runnerHelper.d.ts +2 -0
- package/dist/src/util/runnerHelper.d.ts.map +1 -1
- package/dist/src/util/runnerHelper.js +10 -0
- package/dist/src/util/secretRedaction.d.ts +11 -0
- package/dist/src/util/secretRedaction.d.ts.map +1 -0
- package/dist/src/util/secretRedaction.js +45 -0
- package/package.json +1 -1
- package/src/runtime/parser.ts +22 -16
- package/src/runtime/runner.ts +4 -0
- package/src/runtime/values/ValuePopulationResult.ts +5 -0
- package/src/runtime/values/globalCommandValuePopulation.ts +12 -5
- package/src/runtime/values/subCommandValuePopulation.ts +47 -12
- package/src/util/runnerHelper.ts +18 -0
- package/src/util/secretRedaction.ts +81 -0
|
@@ -2,18 +2,24 @@ import type { PopulatedSingleValueType, PopulatedValues } from "@flowscripter/dy
|
|
|
2
2
|
import type { ParseResult } from "@flowscripter/dynamic-cli-framework-api";
|
|
3
3
|
import type { CommandClause } from "./scanner.ts";
|
|
4
4
|
export type { ParseResult } from "@flowscripter/dynamic-cli-framework-api";
|
|
5
|
+
/**
|
|
6
|
+
* A {@link ParseResult} which also reports any SECRET arguments supplied as plain command line arguments.
|
|
7
|
+
*/
|
|
8
|
+
export type SecretAwareParseResult = ParseResult & {
|
|
9
|
+
readonly secretArgumentNames?: ReadonlyArray<string>;
|
|
10
|
+
};
|
|
5
11
|
/**
|
|
6
12
|
* Parse the arguments for the specified {@link CommandClause} assuming it contains a {@link SubCommand}.
|
|
7
13
|
*
|
|
8
14
|
* @param commandClause the clause to parse.
|
|
9
15
|
* @param defaultValues optional default {@link PopulatedValues} to use for population before parsing the provided arguments.
|
|
10
16
|
*/
|
|
11
|
-
export declare function parseSubCommandClause(commandClause: CommandClause, defaultValues?: PopulatedValues):
|
|
17
|
+
export declare function parseSubCommandClause(commandClause: CommandClause, defaultValues?: PopulatedValues): SecretAwareParseResult;
|
|
12
18
|
/**
|
|
13
19
|
* Parse the arguments for the specified {@link CommandClause} assuming it contains a {@link GlobalCommand} or {@link GlobalModifierCommand}.
|
|
14
20
|
*
|
|
15
21
|
* @param commandClause the clause to parse.
|
|
16
22
|
* @param defaultValue optional default {@link PopulatedSingleValueType} to use for population before parsing the provided arguments.
|
|
17
23
|
*/
|
|
18
|
-
export declare function parseGlobalCommandClause(commandClause: CommandClause, defaultValue?: PopulatedSingleValueType):
|
|
24
|
+
export declare function parseGlobalCommandClause(commandClause: CommandClause, defaultValue?: PopulatedSingleValueType): SecretAwareParseResult;
|
|
19
25
|
//# sourceMappingURL=parser.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../../src/runtime/parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,wBAAwB,EACxB,eAAe,EAChB,MAAM,yCAAyC,CAAC;AAEjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yCAAyC,CAAC;AAC3E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../../src/runtime/parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,wBAAwB,EACxB,eAAe,EAChB,MAAM,yCAAyC,CAAC;AAEjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yCAAyC,CAAC;AAC3E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAelD,YAAY,EAAE,WAAW,EAAE,MAAM,yCAAyC,CAAC;AAE3E;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG,WAAW,GAAG;IACjD,QAAQ,CAAC,mBAAmB,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CACtD,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,aAAa,EAAE,aAAa,EAC5B,aAAa,CAAC,EAAE,eAAe,GAC9B,sBAAsB,CAiFxB;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,aAAa,EAAE,aAAa,EAC5B,YAAY,CAAC,EAAE,wBAAwB,GACtC,sBAAsB,CAwDxB"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import populateGlobalCommandValue from "./values/globalCommandValuePopulation.js";
|
|
2
2
|
import { validateGlobalCommandArgumentValue, validateOptionValue, validatePositionalValue, } from "./values/argumentValueValidation.js";
|
|
3
3
|
import getLogger from "../util/logger.js";
|
|
4
|
+
import { redactGlobalCommandValue, redactSubCommandValues } from "../util/secretRedaction.js";
|
|
4
5
|
import populateSubCommandValues from "./values/subCommandValuePopulation.js";
|
|
5
6
|
const logger = getLogger("parser");
|
|
6
7
|
/**
|
|
@@ -24,7 +25,7 @@ export function parseSubCommandClause(commandClause, defaultValues) {
|
|
|
24
25
|
unusedArgs: potentialArgs,
|
|
25
26
|
};
|
|
26
27
|
}
|
|
27
|
-
const { populatedArgumentValues, unusedArgs, invalidArgument } = populateSubCommandValues(command, potentialArgs, defaultValues);
|
|
28
|
+
const { populatedArgumentValues, unusedArgs, invalidArgument, secretArgumentNames } = populateSubCommandValues(command, potentialArgs, defaultValues);
|
|
28
29
|
if (invalidArgument) {
|
|
29
30
|
invalidArguments.push(invalidArgument);
|
|
30
31
|
}
|
|
@@ -36,7 +37,7 @@ export function parseSubCommandClause(commandClause, defaultValues) {
|
|
|
36
37
|
populatedArgumentValues[option.name] = validatedValue;
|
|
37
38
|
}
|
|
38
39
|
});
|
|
39
|
-
logger.debug("Command arguments after options validated: %O with invalid args: %O", populatedArgumentValues, invalidArguments);
|
|
40
|
+
logger.debug("Command arguments after options validated: %O with invalid args: %O", redactSubCommandValues(command, populatedArgumentValues), invalidArguments);
|
|
40
41
|
if (invalidArguments.length === 0) {
|
|
41
42
|
// validate the positional values
|
|
42
43
|
command.positionals.forEach((positional) => {
|
|
@@ -45,9 +46,10 @@ export function parseSubCommandClause(commandClause, defaultValues) {
|
|
|
45
46
|
populatedArgumentValues[positional.name] = validatedValue;
|
|
46
47
|
}
|
|
47
48
|
});
|
|
48
|
-
logger.debug("Command arguments after positionals validated: %O with invalid args: %O", populatedArgumentValues, invalidArguments);
|
|
49
|
+
logger.debug("Command arguments after positionals validated: %O with invalid args: %O", redactSubCommandValues(command, populatedArgumentValues), invalidArguments);
|
|
49
50
|
}
|
|
50
51
|
}
|
|
52
|
+
const secrets = secretArgumentNames ? { secretArgumentNames } : {};
|
|
51
53
|
if (groupCommand) {
|
|
52
54
|
return {
|
|
53
55
|
command,
|
|
@@ -55,6 +57,7 @@ export function parseSubCommandClause(commandClause, defaultValues) {
|
|
|
55
57
|
groupCommand,
|
|
56
58
|
invalidArguments,
|
|
57
59
|
unusedArgs,
|
|
60
|
+
...secrets,
|
|
58
61
|
};
|
|
59
62
|
}
|
|
60
63
|
return {
|
|
@@ -62,6 +65,7 @@ export function parseSubCommandClause(commandClause, defaultValues) {
|
|
|
62
65
|
populatedArgumentValues,
|
|
63
66
|
invalidArguments,
|
|
64
67
|
unusedArgs: unusedArgs,
|
|
68
|
+
...secrets,
|
|
65
69
|
};
|
|
66
70
|
}
|
|
67
71
|
/**
|
|
@@ -83,11 +87,11 @@ export function parseGlobalCommandClause(commandClause, defaultValue) {
|
|
|
83
87
|
unusedArgs: potentialArgs,
|
|
84
88
|
};
|
|
85
89
|
}
|
|
86
|
-
let { populatedArgumentValue, unusedArgs, invalidArgument } = populateGlobalCommandValue(command, potentialArgs, defaultValue);
|
|
90
|
+
let { populatedArgumentValue, unusedArgs, invalidArgument, secretArgumentNames } = populateGlobalCommandValue(command, potentialArgs, defaultValue);
|
|
87
91
|
if (invalidArgument) {
|
|
88
92
|
invalidArguments.push(invalidArgument);
|
|
89
93
|
}
|
|
90
|
-
logger.debug("Command arguments for command: %s after value population: %O with invalid args: %O and unused args: %O", command.name, populatedArgumentValue, invalidArguments, unusedArgs);
|
|
94
|
+
logger.debug("Command arguments for command: %s after value population: %O with invalid args: %O and unused args: %O", command.name, redactGlobalCommandValue(command, populatedArgumentValue), invalidArguments, unusedArgs);
|
|
91
95
|
// don't validate the value if the argument is already invalid
|
|
92
96
|
if (invalidArguments.length === 0) {
|
|
93
97
|
const validatedValue = validateGlobalCommandArgumentValue(command, populatedArgumentValue, invalidArguments);
|
|
@@ -95,11 +99,12 @@ export function parseGlobalCommandClause(commandClause, defaultValue) {
|
|
|
95
99
|
populatedArgumentValue = validatedValue;
|
|
96
100
|
}
|
|
97
101
|
}
|
|
98
|
-
logger.debug("Command arguments for command: %s after value validation: %O with invalid args: %O", command.name, populatedArgumentValue, invalidArguments);
|
|
102
|
+
logger.debug("Command arguments for command: %s after value validation: %O with invalid args: %O", command.name, redactGlobalCommandValue(command, populatedArgumentValue), invalidArguments);
|
|
99
103
|
return {
|
|
100
104
|
command,
|
|
101
105
|
populatedArgumentValues: populatedArgumentValue,
|
|
102
106
|
invalidArguments,
|
|
103
107
|
unusedArgs,
|
|
108
|
+
...(secretArgumentNames && { secretArgumentNames }),
|
|
104
109
|
};
|
|
105
110
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../../src/runtime/runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AACvE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../../src/runtime/runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AACvE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAkB/E,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AAEvF,OAAO,KAAK,4BAA4B,MAAM,0DAA0D,CAAC;AACzG,OAAO,KAAK,uBAAuB,MAAM,qDAAqD,CAAC;AAK/F,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AAKvE,OAAO,qBAAqB,MAAM,6CAA6C,CAAC;AAMhF;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG;IAAE,QAAQ,CAAC,aAAa,CAAC,EAAE,IAAI,CAAA;CAAE,CAAC;AAsfzE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2EG;AACH,wBAAsB,GAAG,CACvB,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,EAC3B,eAAe,EAAE,eAAe,EAChC,uBAAuB,EAAE,uBAAuB,EAChD,4BAA4B,EAAE,4BAA4B,GAAG,SAAS,EACtE,uBAAuB,EAAE,uBAAuB,GAAG,SAAS,EAC5D,OAAO,EAAE,OAAO,EAChB,cAAc,CAAC,EAAE,OAAO,EACxB,cAAc,CAAC,EAAE,qBAAqB,GACrC,OAAO,CAAC,YAAY,CAAC,CAkKvB"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { RunState } from "@flowscripter/dynamic-cli-framework-api";
|
|
2
2
|
import { isGlobalCommand, isSubCommand } from "./command/CommandTypeGuards.js";
|
|
3
3
|
import getLogger from "../util/logger.js";
|
|
4
|
-
import { printCommandExecutionError, printNoCommandRecognisedError, printNoCommandSpecifiedError, printParseResultError, printUnusedArgsWarning, } from "../util/runnerHelper.js";
|
|
4
|
+
import { printCommandExecutionError, printNoCommandRecognisedError, printNoCommandSpecifiedError, printParseResultError, printSecretArgsWarning, printUnusedArgsWarning, } from "../util/runnerHelper.js";
|
|
5
5
|
import { scanForGlobalModifierCommandClauses, scanForNonModifierCommandClause } from "./scanner.js";
|
|
6
6
|
import { parseGlobalCommandClause, parseSubCommandClause } from "./parser.js";
|
|
7
7
|
import { ARGUMENT_PROMPTER_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
@@ -113,6 +113,7 @@ async function findAndExecuteGlobalModifierCommands(availableArgSequences, unuse
|
|
|
113
113
|
? await configurationServiceProvider.getDefaultArgumentValues(context.cliConfig, globalModifierCommandClause.command)
|
|
114
114
|
: undefined;
|
|
115
115
|
const parseResult = parseGlobalCommandClause(globalModifierCommandClause, defaultArgumentValues);
|
|
116
|
+
await printSecretArgsWarning(context, parseResult);
|
|
116
117
|
if (parseResult.invalidArguments.length > 0) {
|
|
117
118
|
await printParseResultError(context, parseResult);
|
|
118
119
|
return {
|
|
@@ -194,6 +195,7 @@ async function findAndExecuteNonModifierCommand(availableArgs, unusedArgs, nonMo
|
|
|
194
195
|
else {
|
|
195
196
|
parseResult = parseSubCommandClause(scanResult.nonModifierCommandClause, defaultArgumentValues);
|
|
196
197
|
}
|
|
198
|
+
await printSecretArgsWarning(context, parseResult);
|
|
197
199
|
if (parseResult.invalidArguments.length > 0) {
|
|
198
200
|
const prompted = await attemptPromptForMissingArguments(parseResult, context);
|
|
199
201
|
if (prompted) {
|
|
@@ -298,6 +300,7 @@ async function findAndExecuteDefaultNonModifierCommand(availableArgs, unusedArgs
|
|
|
298
300
|
potentialArgs: [...argSequence],
|
|
299
301
|
}, defaultArgumentValues);
|
|
300
302
|
}
|
|
303
|
+
await printSecretArgsWarning(context, parseResult);
|
|
301
304
|
// add any unused args back to the list of still unused args
|
|
302
305
|
if (parseResult.unusedArgs.length > 0) {
|
|
303
306
|
unusedArgs.push(parseResult.unusedArgs);
|
|
@@ -12,6 +12,10 @@ export interface ValuePopulationResult {
|
|
|
12
12
|
* Any invalid argument discovered while populating values.
|
|
13
13
|
*/
|
|
14
14
|
readonly invalidArgument?: InvalidArgument;
|
|
15
|
+
/**
|
|
16
|
+
* Names of any SECRET arguments whose values were supplied as plain command line arguments.
|
|
17
|
+
*/
|
|
18
|
+
readonly secretArgumentNames?: ReadonlyArray<string>;
|
|
15
19
|
}
|
|
16
20
|
/**
|
|
17
21
|
* A container holding the result of argument values population for a {@link SubCommand}.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ValuePopulationResult.d.ts","sourceRoot":"","sources":["../../../../src/runtime/values/ValuePopulationResult.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EACxB,eAAe,EAChB,MAAM,yCAAyC,CAAC;AACjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAE/E;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAE3C;;OAEG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"ValuePopulationResult.d.ts","sourceRoot":"","sources":["../../../../src/runtime/values/ValuePopulationResult.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EACxB,eAAe,EAChB,MAAM,yCAAyC,CAAC;AACjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAE/E;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAE3C;;OAEG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,eAAe,CAAC;IAE3C;;OAEG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CACtD;AAED;;GAEG;AACH,MAAM,WAAW,+BAAgC,SAAQ,qBAAqB;IAC5E;;OAEG;IACH,QAAQ,CAAC,uBAAuB,EAAE,eAAe,CAAC;CACnD;AAED;;GAEG;AACH,MAAM,WAAW,kCAAmC,SAAQ,qBAAqB;IAC/E;;OAEG;IACH,QAAQ,CAAC,sBAAsB,EAAE,wBAAwB,CAAC;CAC3D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"globalCommandValuePopulation.d.ts","sourceRoot":"","sources":["../../../../src/runtime/values/globalCommandValuePopulation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,KAAK,EAAE,kCAAkC,EAAE,MAAM,4BAA4B,CAAC;AAErF,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,yCAAyC,CAAC;
|
|
1
|
+
{"version":3,"file":"globalCommandValuePopulation.d.ts","sourceRoot":"","sources":["../../../../src/runtime/values/globalCommandValuePopulation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,KAAK,EAAE,kCAAkC,EAAE,MAAM,4BAA4B,CAAC;AAErF,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,yCAAyC,CAAC;AAOxF;;;;;;GAMG;AACH,MAAM,CAAC,OAAO,UAAU,0BAA0B,CAChD,aAAa,EAAE,aAAa,EAC5B,aAAa,EAAE,aAAa,CAAC,MAAM,CAAC,EACpC,YAAY,EAAE,wBAAwB,GACrC,kCAAkC,CAwEpC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ValueTypeName } from "@flowscripter/dynamic-cli-framework-api";
|
|
2
2
|
import getLogger from "../../util/logger.js";
|
|
3
|
+
import { REDACTED } from "../../util/secretRedaction.js";
|
|
3
4
|
import { InvalidArgumentReason } from "@flowscripter/dynamic-cli-framework-api";
|
|
4
5
|
const logger = getLogger("globalCommandValuePopulation");
|
|
5
6
|
/**
|
|
@@ -10,14 +11,20 @@ const logger = getLogger("globalCommandValuePopulation");
|
|
|
10
11
|
* @param defaultValue optional default value to use for population before parsing the provided arguments.
|
|
11
12
|
*/
|
|
12
13
|
export default function populateGlobalCommandValue(globalCommand, potentialArgs, defaultValue) {
|
|
14
|
+
const argument = globalCommand.argument;
|
|
15
|
+
const isSecret = argument.type === ValueTypeName.SECRET;
|
|
13
16
|
logger.debug(() => {
|
|
14
|
-
const message = `Populating value for global command: '${globalCommand.name}' using potential args: ${
|
|
17
|
+
const message = `Populating value for global command: '${globalCommand.name}' using potential args: ${(isSecret
|
|
18
|
+
? potentialArgs
|
|
19
|
+
.slice(0, 1)
|
|
20
|
+
.map(() => REDACTED)
|
|
21
|
+
.concat(potentialArgs.slice(1))
|
|
22
|
+
: potentialArgs).join(" ")}`;
|
|
15
23
|
if (defaultValue !== undefined) {
|
|
16
|
-
return `${message} and configured value: '${defaultValue}'`;
|
|
24
|
+
return `${message} and configured value: '${isSecret ? REDACTED : defaultValue}'`;
|
|
17
25
|
}
|
|
18
26
|
return message;
|
|
19
27
|
});
|
|
20
|
-
const argument = globalCommand.argument;
|
|
21
28
|
// default to defined argument default value
|
|
22
29
|
let populatedArgumentValue = argument.defaultValue;
|
|
23
30
|
// override with provided configured value
|
|
@@ -63,5 +70,6 @@ export default function populateGlobalCommandValue(globalCommand, potentialArgs,
|
|
|
63
70
|
return {
|
|
64
71
|
populatedArgumentValue,
|
|
65
72
|
unusedArgs,
|
|
73
|
+
...(isSecret && potentialArgs.length > 0 && { secretArgumentNames: [globalCommand.name] }),
|
|
66
74
|
};
|
|
67
75
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subCommandValuePopulation.d.ts","sourceRoot":"","sources":["../../../../src/runtime/values/subCommandValuePopulation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yCAAyC,CAAC;AAC1E,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,yCAAyC,CAAC;
|
|
1
|
+
{"version":3,"file":"subCommandValuePopulation.d.ts","sourceRoot":"","sources":["../../../../src/runtime/values/subCommandValuePopulation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yCAAyC,CAAC;AAC1E,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,yCAAyC,CAAC;AAIjD,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,4BAA4B,CAAC;AA2kBlF;;;;;;GAMG;AACH,MAAM,CAAC,OAAO,UAAU,wBAAwB,CAC9C,UAAU,EAAE,UAAU,EACtB,aAAa,EAAE,aAAa,CAAC,MAAM,CAAC,EACpC,aAAa,EAAE,eAAe,GAAG,SAAS,GACzC,+BAA+B,CA+EjC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ValueTypeName, } from "@flowscripter/dynamic-cli-framework-api";
|
|
2
2
|
import getLogger from "../../util/logger.js";
|
|
3
|
+
import { REDACTED, redactSubCommandValues } from "../../util/secretRedaction.js";
|
|
3
4
|
import argumentValueMerge from "./argumentValueMerge.js";
|
|
4
5
|
import { MAXIMUM_COMPLEX_OPTION_NESTING_DEPTH } from "@flowscripter/dynamic-cli-framework-api";
|
|
5
6
|
import { isComplexOption } from "../argument/ArgumentTypeGuards.js";
|
|
@@ -62,6 +63,18 @@ class ParseContext {
|
|
|
62
63
|
* which will be populated with a positional value if found.
|
|
63
64
|
*/
|
|
64
65
|
currentPositionalIndex;
|
|
66
|
+
/**
|
|
67
|
+
* Set when the most recent argument supplied a value for a SECRET option or positional.
|
|
68
|
+
*/
|
|
69
|
+
secretArgument;
|
|
70
|
+
/**
|
|
71
|
+
* Return and clear the {@link secretArgument} recorded by the most recent {@link parse}.
|
|
72
|
+
*/
|
|
73
|
+
takeSecretArgument() {
|
|
74
|
+
const secretArgument = this.secretArgument;
|
|
75
|
+
this.secretArgument = undefined;
|
|
76
|
+
return secretArgument;
|
|
77
|
+
}
|
|
65
78
|
/**
|
|
66
79
|
* Use to store lazily created map of option paths e.g. --alpha.b.g and -a.b.gamma for {@link ComplexOption} or {@link Option} instances.
|
|
67
80
|
*/
|
|
@@ -294,7 +307,10 @@ class ParseContext {
|
|
|
294
307
|
* @return `false` if there is a problem populating the value, in which case the {@link state} will be set to
|
|
295
308
|
* {@link ParseState.ERROR} and {@link invalidArgument} will be populated.
|
|
296
309
|
*/
|
|
297
|
-
setOptionValue(value) {
|
|
310
|
+
setOptionValue(value, inline = false) {
|
|
311
|
+
if (this.currentOption.type === ValueTypeName.SECRET) {
|
|
312
|
+
this.secretArgument = { name: this.currentOption.name, inline };
|
|
313
|
+
}
|
|
298
314
|
let optionValue = this.populatedArgumentValues;
|
|
299
315
|
const optionPath = this.currentOptionPath;
|
|
300
316
|
// traverse the current populated values using the validated option path elements
|
|
@@ -367,6 +383,9 @@ class ParseContext {
|
|
|
367
383
|
setPositionalValue(value) {
|
|
368
384
|
const positional = this.subCommand.positionals[this.currentPositionalIndex];
|
|
369
385
|
const currentValue = this.populatedArgumentValues[positional.name];
|
|
386
|
+
if (positional.type === ValueTypeName.SECRET) {
|
|
387
|
+
this.secretArgument = { name: positional.name, inline: false };
|
|
388
|
+
}
|
|
370
389
|
// if we haven't populated the argument value before...
|
|
371
390
|
if (currentValue === undefined) {
|
|
372
391
|
if (positional.isVarargMultiple) {
|
|
@@ -460,7 +479,7 @@ class ParseContext {
|
|
|
460
479
|
// try to set the value if we have one
|
|
461
480
|
if (potentialOptionValue !== undefined) {
|
|
462
481
|
// set the value
|
|
463
|
-
this.setOptionValue(potentialOptionValue);
|
|
482
|
+
this.setOptionValue(potentialOptionValue, true);
|
|
464
483
|
// return regardless
|
|
465
484
|
return;
|
|
466
485
|
}
|
|
@@ -491,19 +510,21 @@ class ParseContext {
|
|
|
491
510
|
* @param defaultValues optional default {@link Values} to use for population before parsing the provided arguments.
|
|
492
511
|
*/
|
|
493
512
|
export default function populateSubCommandValues(subCommand, potentialArgs, defaultValues) {
|
|
494
|
-
logger.debug(() => {
|
|
495
|
-
const message = `Populating values for sub-command: '${subCommand.name}' using potential args: ${potentialArgs.join(" ")}`;
|
|
496
|
-
if (defaultValues !== undefined) {
|
|
497
|
-
return `${message} and configured values: ${JSON.stringify(defaultValues)}`;
|
|
498
|
-
}
|
|
499
|
-
return message;
|
|
500
|
-
});
|
|
501
513
|
const parseContext = new ParseContext(subCommand);
|
|
502
514
|
const unusedArgs = [];
|
|
515
|
+
const loggedArgs = [...potentialArgs];
|
|
516
|
+
const secretArgumentNames = new Set();
|
|
503
517
|
for (let i = 0; i < potentialArgs.length; i++) {
|
|
504
518
|
const potentialArg = potentialArgs[i];
|
|
505
519
|
// parse the next arg
|
|
506
520
|
parseContext.parse(potentialArg);
|
|
521
|
+
const secretArgument = parseContext.takeSecretArgument();
|
|
522
|
+
if (secretArgument) {
|
|
523
|
+
secretArgumentNames.add(secretArgument.name);
|
|
524
|
+
loggedArgs[i] = secretArgument.inline
|
|
525
|
+
? `${potentialArg.slice(0, potentialArg.indexOf("=") + 1)}${REDACTED}`
|
|
526
|
+
: REDACTED;
|
|
527
|
+
}
|
|
507
528
|
// check if we had a parsing error, record the error, flush the remaining args and stop parsing
|
|
508
529
|
if (parseContext.state === ParseState.ERROR) {
|
|
509
530
|
if (i + 1 < potentialArgs.length) {
|
|
@@ -517,6 +538,13 @@ export default function populateSubCommandValues(subCommand, potentialArgs, defa
|
|
|
517
538
|
parseContext.state = ParseState.EMPTY;
|
|
518
539
|
}
|
|
519
540
|
}
|
|
541
|
+
logger.debug(() => {
|
|
542
|
+
const message = `Populated values for sub-command: '${subCommand.name}' using potential args: ${loggedArgs.join(" ")}`;
|
|
543
|
+
if (defaultValues !== undefined) {
|
|
544
|
+
return `${message} and configured values: ${JSON.stringify(redactSubCommandValues(subCommand, defaultValues))}`;
|
|
545
|
+
}
|
|
546
|
+
return message;
|
|
547
|
+
});
|
|
520
548
|
// Check if the last arg specified an option but no value
|
|
521
549
|
if (parseContext.state === ParseState.OPTION_VALUE_EXPECTED) {
|
|
522
550
|
// special case if the last arg was for a boolean option where the value was implicitly `true`
|
|
@@ -536,6 +564,7 @@ export default function populateSubCommandValues(subCommand, potentialArgs, defa
|
|
|
536
564
|
populatedArgumentValues: parseContext.populatedArgumentValues,
|
|
537
565
|
unusedArgs: unusedArgs,
|
|
538
566
|
invalidArgument: parseContext.invalidArgument,
|
|
567
|
+
...(secretArgumentNames.size > 0 && { secretArgumentNames: [...secretArgumentNames] }),
|
|
539
568
|
};
|
|
540
569
|
}
|
|
541
570
|
// merge the configured values with the parsed values
|
|
@@ -546,5 +575,6 @@ export default function populateSubCommandValues(subCommand, potentialArgs, defa
|
|
|
546
575
|
return {
|
|
547
576
|
populatedArgumentValues,
|
|
548
577
|
unusedArgs: unusedArgs,
|
|
578
|
+
...(secretArgumentNames.size > 0 && { secretArgumentNames: [...secretArgumentNames] }),
|
|
549
579
|
};
|
|
550
580
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SecretAwareParseResult } from "../runtime/parser.ts";
|
|
1
2
|
import type { ParseResult } from "../runtime/parser.ts";
|
|
2
3
|
import type { Context } from "@flowscripter/dynamic-cli-framework-api";
|
|
3
4
|
import type { CommandRegistry } from "@flowscripter/dynamic-cli-framework-api";
|
|
@@ -7,4 +8,5 @@ export declare function printRuntimeError(context: Context, err: Error): Promise
|
|
|
7
8
|
export declare function printNoCommandSpecifiedError(context: Context): Promise<void>;
|
|
8
9
|
export declare function printNoCommandRecognisedError(context: Context, commandRegistry: CommandRegistry, availableArgs: ReadonlyArray<string>): Promise<void>;
|
|
9
10
|
export declare function printUnusedArgsWarning(context: Context, overallUnusedArgs: ReadonlyArray<string>): Promise<void>;
|
|
11
|
+
export declare function printSecretArgsWarning(context: Context, parseResult: SecretAwareParseResult): Promise<void>;
|
|
10
12
|
//# sourceMappingURL=runnerHelper.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runnerHelper.d.ts","sourceRoot":"","sources":["../../../src/util/runnerHelper.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AAEvE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAY/E,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,WAAW,EACxB,gBAAgB,UAAQ,iBAiBzB;AAED,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,WAAW,EACxB,GAAG,EAAE,KAAK,EACV,gBAAgB,UAAQ,iBAiBzB;AAED,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,iBAMnE;AAED,wBAAsB,4BAA4B,CAAC,OAAO,EAAE,OAAO,iBAGlE;AAED,wBAAsB,6BAA6B,CACjD,OAAO,EAAE,OAAO,EAChB,eAAe,EAAE,eAAe,EAChC,aAAa,EAAE,aAAa,CAAC,MAAM,CAAC,iBAgCrC;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,OAAO,EAChB,iBAAiB,EAAE,aAAa,CAAC,MAAM,CAAC,iBAQzC"}
|
|
1
|
+
{"version":3,"file":"runnerHelper.d.ts","sourceRoot":"","sources":["../../../src/util/runnerHelper.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AAEvE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAY/E,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,WAAW,EACxB,gBAAgB,UAAQ,iBAiBzB;AAED,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,WAAW,EACxB,GAAG,EAAE,KAAK,EACV,gBAAgB,UAAQ,iBAiBzB;AAED,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,iBAMnE;AAED,wBAAsB,4BAA4B,CAAC,OAAO,EAAE,OAAO,iBAGlE;AAED,wBAAsB,6BAA6B,CACjD,OAAO,EAAE,OAAO,EAChB,eAAe,EAAE,eAAe,EAChC,aAAa,EAAE,aAAa,CAAC,MAAM,CAAC,iBAgCrC;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,OAAO,EAChB,iBAAiB,EAAE,aAAa,CAAC,MAAM,CAAC,iBAQzC;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,sBAAsB,iBAapC"}
|
|
@@ -78,3 +78,13 @@ export async function printUnusedArgsWarning(context, overallUnusedArgs) {
|
|
|
78
78
|
await printerService.warn(`Unused args: ${overallUnusedArgs.join(" ")}\n\n`, Icon.ALERT);
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
|
+
export async function printSecretArgsWarning(context, parseResult) {
|
|
82
|
+
const secretArgumentNames = parseResult.secretArgumentNames;
|
|
83
|
+
if (!secretArgumentNames || secretArgumentNames.length === 0) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const printerService = context.getServiceById(PRINTER_SERVICE_ID);
|
|
87
|
+
await printerService.warn(`SECRET value passed as a plain argument: ${secretArgumentNames.join(", ")}. ` +
|
|
88
|
+
"This is not recommended as it may be exposed via shell history and process listings. " +
|
|
89
|
+
"Use an environment variable, a prompt or a configuration file default instead.\n\n", Icon.ALERT);
|
|
90
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type GlobalCommand, type PopulatedSingleValueType, type PopulatedValues, type SubCommand } from "@flowscripter/dynamic-cli-framework-api";
|
|
2
|
+
export declare const REDACTED = "***";
|
|
3
|
+
/**
|
|
4
|
+
* Copy of the provided values suitable for logging, with the values of any SECRET options or positionals replaced.
|
|
5
|
+
*/
|
|
6
|
+
export declare function redactSubCommandValues<T extends PopulatedValues | undefined>(subCommand: SubCommand, values: T): T;
|
|
7
|
+
/**
|
|
8
|
+
* Copy of the provided value suitable for logging, with the value replaced if the global command argument is a SECRET.
|
|
9
|
+
*/
|
|
10
|
+
export declare function redactGlobalCommandValue(globalCommand: GlobalCommand, value: PopulatedSingleValueType): PopulatedSingleValueType;
|
|
11
|
+
//# sourceMappingURL=secretRedaction.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secretRedaction.d.ts","sourceRoot":"","sources":["../../../src/util/secretRedaction.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,aAAa,EAElB,KAAK,wBAAwB,EAC7B,KAAK,eAAe,EAEpB,KAAK,UAAU,EAChB,MAAM,yCAAyC,CAAC;AAGjD,eAAO,MAAM,QAAQ,QAAQ,CAAC;AA4C9B;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,eAAe,GAAG,SAAS,EAC1E,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,CAAC,GACR,CAAC,CAKH;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,aAAa,EAAE,aAAa,EAC5B,KAAK,EAAE,wBAAwB,GAC9B,wBAAwB,CAK1B"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ValueTypeName, } from "@flowscripter/dynamic-cli-framework-api";
|
|
2
|
+
import { isComplexOption } from "../runtime/argument/ArgumentTypeGuards.js";
|
|
3
|
+
export const REDACTED = "***";
|
|
4
|
+
function redactValue(definition, value) {
|
|
5
|
+
if (value === undefined || value === null) {
|
|
6
|
+
return value;
|
|
7
|
+
}
|
|
8
|
+
if (isComplexOption(definition)) {
|
|
9
|
+
const properties = definition.properties;
|
|
10
|
+
if (Array.isArray(value)) {
|
|
11
|
+
return value.map((v) => redactValue(definition, v));
|
|
12
|
+
}
|
|
13
|
+
return redactDefinedValues(properties, [], value);
|
|
14
|
+
}
|
|
15
|
+
if (definition.type === ValueTypeName.SECRET) {
|
|
16
|
+
return Array.isArray(value) ? value.map(() => REDACTED) : REDACTED;
|
|
17
|
+
}
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
function redactDefinedValues(options, positionals, values) {
|
|
21
|
+
const redacted = {};
|
|
22
|
+
for (const [name, value] of Object.entries(values)) {
|
|
23
|
+
const definition = options.find((o) => o.name === name) ?? positionals.find((p) => p.name === name);
|
|
24
|
+
redacted[name] = definition ? redactValue(definition, value) : value;
|
|
25
|
+
}
|
|
26
|
+
return redacted;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Copy of the provided values suitable for logging, with the values of any SECRET options or positionals replaced.
|
|
30
|
+
*/
|
|
31
|
+
export function redactSubCommandValues(subCommand, values) {
|
|
32
|
+
if (values === undefined) {
|
|
33
|
+
return values;
|
|
34
|
+
}
|
|
35
|
+
return redactDefinedValues(subCommand.options, subCommand.positionals, values);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Copy of the provided value suitable for logging, with the value replaced if the global command argument is a SECRET.
|
|
39
|
+
*/
|
|
40
|
+
export function redactGlobalCommandValue(globalCommand, value) {
|
|
41
|
+
if (value !== undefined && globalCommand.argument?.type === ValueTypeName.SECRET) {
|
|
42
|
+
return REDACTED;
|
|
43
|
+
}
|
|
44
|
+
return value;
|
|
45
|
+
}
|
package/package.json
CHANGED
package/src/runtime/parser.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
validatePositionalValue,
|
|
14
14
|
} from "./values/argumentValueValidation.ts";
|
|
15
15
|
import getLogger from "../util/logger.ts";
|
|
16
|
+
import { redactGlobalCommandValue, redactSubCommandValues } from "../util/secretRedaction.ts";
|
|
16
17
|
import populateSubCommandValues from "./values/subCommandValuePopulation.ts";
|
|
17
18
|
import type { GlobalCommand } from "@flowscripter/dynamic-cli-framework-api";
|
|
18
19
|
import type { SubCommand } from "@flowscripter/dynamic-cli-framework-api";
|
|
@@ -21,6 +22,13 @@ const logger = getLogger("parser");
|
|
|
21
22
|
|
|
22
23
|
export type { ParseResult } from "@flowscripter/dynamic-cli-framework-api";
|
|
23
24
|
|
|
25
|
+
/**
|
|
26
|
+
* A {@link ParseResult} which also reports any SECRET arguments supplied as plain command line arguments.
|
|
27
|
+
*/
|
|
28
|
+
export type SecretAwareParseResult = ParseResult & {
|
|
29
|
+
readonly secretArgumentNames?: ReadonlyArray<string>;
|
|
30
|
+
};
|
|
31
|
+
|
|
24
32
|
/**
|
|
25
33
|
* Parse the arguments for the specified {@link CommandClause} assuming it contains a {@link SubCommand}.
|
|
26
34
|
*
|
|
@@ -30,7 +38,7 @@ export type { ParseResult } from "@flowscripter/dynamic-cli-framework-api";
|
|
|
30
38
|
export function parseSubCommandClause(
|
|
31
39
|
commandClause: CommandClause,
|
|
32
40
|
defaultValues?: PopulatedValues,
|
|
33
|
-
):
|
|
41
|
+
): SecretAwareParseResult {
|
|
34
42
|
const command = commandClause.command as SubCommand;
|
|
35
43
|
const potentialArgs = commandClause.potentialArgs;
|
|
36
44
|
const groupCommand = commandClause.groupCommand;
|
|
@@ -48,11 +56,8 @@ export function parseSubCommandClause(
|
|
|
48
56
|
};
|
|
49
57
|
}
|
|
50
58
|
|
|
51
|
-
const { populatedArgumentValues, unusedArgs, invalidArgument } =
|
|
52
|
-
command,
|
|
53
|
-
potentialArgs,
|
|
54
|
-
defaultValues,
|
|
55
|
-
);
|
|
59
|
+
const { populatedArgumentValues, unusedArgs, invalidArgument, secretArgumentNames } =
|
|
60
|
+
populateSubCommandValues(command, potentialArgs, defaultValues);
|
|
56
61
|
|
|
57
62
|
if (invalidArgument) {
|
|
58
63
|
invalidArguments.push(invalidArgument);
|
|
@@ -73,7 +78,7 @@ export function parseSubCommandClause(
|
|
|
73
78
|
|
|
74
79
|
logger.debug(
|
|
75
80
|
"Command arguments after options validated: %O with invalid args: %O",
|
|
76
|
-
populatedArgumentValues,
|
|
81
|
+
redactSubCommandValues(command, populatedArgumentValues),
|
|
77
82
|
invalidArguments,
|
|
78
83
|
);
|
|
79
84
|
|
|
@@ -91,11 +96,12 @@ export function parseSubCommandClause(
|
|
|
91
96
|
});
|
|
92
97
|
logger.debug(
|
|
93
98
|
"Command arguments after positionals validated: %O with invalid args: %O",
|
|
94
|
-
populatedArgumentValues,
|
|
99
|
+
redactSubCommandValues(command, populatedArgumentValues),
|
|
95
100
|
invalidArguments,
|
|
96
101
|
);
|
|
97
102
|
}
|
|
98
103
|
}
|
|
104
|
+
const secrets = secretArgumentNames ? { secretArgumentNames } : {};
|
|
99
105
|
if (groupCommand) {
|
|
100
106
|
return {
|
|
101
107
|
command,
|
|
@@ -103,6 +109,7 @@ export function parseSubCommandClause(
|
|
|
103
109
|
groupCommand,
|
|
104
110
|
invalidArguments,
|
|
105
111
|
unusedArgs,
|
|
112
|
+
...secrets,
|
|
106
113
|
};
|
|
107
114
|
}
|
|
108
115
|
return {
|
|
@@ -110,6 +117,7 @@ export function parseSubCommandClause(
|
|
|
110
117
|
populatedArgumentValues,
|
|
111
118
|
invalidArguments,
|
|
112
119
|
unusedArgs: unusedArgs,
|
|
120
|
+
...secrets,
|
|
113
121
|
};
|
|
114
122
|
}
|
|
115
123
|
|
|
@@ -122,7 +130,7 @@ export function parseSubCommandClause(
|
|
|
122
130
|
export function parseGlobalCommandClause(
|
|
123
131
|
commandClause: CommandClause,
|
|
124
132
|
defaultValue?: PopulatedSingleValueType,
|
|
125
|
-
):
|
|
133
|
+
): SecretAwareParseResult {
|
|
126
134
|
const command = commandClause.command as GlobalCommand;
|
|
127
135
|
const potentialArgs = commandClause.potentialArgs;
|
|
128
136
|
const invalidArguments: Array<InvalidArgument> = [];
|
|
@@ -137,11 +145,8 @@ export function parseGlobalCommandClause(
|
|
|
137
145
|
};
|
|
138
146
|
}
|
|
139
147
|
|
|
140
|
-
let { populatedArgumentValue, unusedArgs, invalidArgument } =
|
|
141
|
-
command,
|
|
142
|
-
potentialArgs,
|
|
143
|
-
defaultValue,
|
|
144
|
-
);
|
|
148
|
+
let { populatedArgumentValue, unusedArgs, invalidArgument, secretArgumentNames } =
|
|
149
|
+
populateGlobalCommandValue(command, potentialArgs, defaultValue);
|
|
145
150
|
|
|
146
151
|
if (invalidArgument) {
|
|
147
152
|
invalidArguments.push(invalidArgument);
|
|
@@ -150,7 +155,7 @@ export function parseGlobalCommandClause(
|
|
|
150
155
|
logger.debug(
|
|
151
156
|
"Command arguments for command: %s after value population: %O with invalid args: %O and unused args: %O",
|
|
152
157
|
command.name,
|
|
153
|
-
populatedArgumentValue,
|
|
158
|
+
redactGlobalCommandValue(command, populatedArgumentValue),
|
|
154
159
|
invalidArguments,
|
|
155
160
|
unusedArgs,
|
|
156
161
|
);
|
|
@@ -170,7 +175,7 @@ export function parseGlobalCommandClause(
|
|
|
170
175
|
logger.debug(
|
|
171
176
|
"Command arguments for command: %s after value validation: %O with invalid args: %O",
|
|
172
177
|
command.name,
|
|
173
|
-
populatedArgumentValue,
|
|
178
|
+
redactGlobalCommandValue(command, populatedArgumentValue),
|
|
174
179
|
invalidArguments,
|
|
175
180
|
);
|
|
176
181
|
|
|
@@ -179,5 +184,6 @@ export function parseGlobalCommandClause(
|
|
|
179
184
|
populatedArgumentValues: populatedArgumentValue,
|
|
180
185
|
invalidArguments,
|
|
181
186
|
unusedArgs,
|
|
187
|
+
...(secretArgumentNames && { secretArgumentNames }),
|
|
182
188
|
};
|
|
183
189
|
}
|
package/src/runtime/runner.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
printNoCommandRecognisedError,
|
|
16
16
|
printNoCommandSpecifiedError,
|
|
17
17
|
printParseResultError,
|
|
18
|
+
printSecretArgsWarning,
|
|
18
19
|
printUnusedArgsWarning,
|
|
19
20
|
} from "../util/runnerHelper.ts";
|
|
20
21
|
import { scanForGlobalModifierCommandClauses, scanForNonModifierCommandClause } from "./scanner.ts";
|
|
@@ -198,6 +199,7 @@ async function findAndExecuteGlobalModifierCommands(
|
|
|
198
199
|
globalModifierCommandClause,
|
|
199
200
|
defaultArgumentValues as PopulatedSingleValueType,
|
|
200
201
|
);
|
|
202
|
+
await printSecretArgsWarning(context, parseResult);
|
|
201
203
|
|
|
202
204
|
if (parseResult.invalidArguments.length > 0) {
|
|
203
205
|
await printParseResultError(context, parseResult);
|
|
@@ -329,6 +331,7 @@ async function findAndExecuteNonModifierCommand(
|
|
|
329
331
|
defaultArgumentValues as PopulatedValues,
|
|
330
332
|
);
|
|
331
333
|
}
|
|
334
|
+
await printSecretArgsWarning(context, parseResult);
|
|
332
335
|
|
|
333
336
|
if (parseResult.invalidArguments.length > 0) {
|
|
334
337
|
const prompted = await attemptPromptForMissingArguments(parseResult, context);
|
|
@@ -464,6 +467,7 @@ async function findAndExecuteDefaultNonModifierCommand(
|
|
|
464
467
|
defaultArgumentValues as PopulatedValues,
|
|
465
468
|
);
|
|
466
469
|
}
|
|
470
|
+
await printSecretArgsWarning(context, parseResult);
|
|
467
471
|
|
|
468
472
|
// add any unused args back to the list of still unused args
|
|
469
473
|
if (parseResult.unusedArgs.length > 0) {
|
|
@@ -17,6 +17,11 @@ export interface ValuePopulationResult {
|
|
|
17
17
|
* Any invalid argument discovered while populating values.
|
|
18
18
|
*/
|
|
19
19
|
readonly invalidArgument?: InvalidArgument;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Names of any SECRET arguments whose values were supplied as plain command line arguments.
|
|
23
|
+
*/
|
|
24
|
+
readonly secretArgumentNames?: ReadonlyArray<string>;
|
|
20
25
|
}
|
|
21
26
|
|
|
22
27
|
/**
|
|
@@ -3,6 +3,7 @@ import type { GlobalCommandValuePopulationResult } from "./ValuePopulationResult
|
|
|
3
3
|
import { ValueTypeName } from "@flowscripter/dynamic-cli-framework-api";
|
|
4
4
|
import type { PopulatedSingleValueType } from "@flowscripter/dynamic-cli-framework-api";
|
|
5
5
|
import getLogger from "../../util/logger.ts";
|
|
6
|
+
import { REDACTED } from "../../util/secretRedaction.ts";
|
|
6
7
|
import { InvalidArgumentReason } from "@flowscripter/dynamic-cli-framework-api";
|
|
7
8
|
|
|
8
9
|
const logger = getLogger("globalCommandValuePopulation");
|
|
@@ -19,16 +20,21 @@ export default function populateGlobalCommandValue(
|
|
|
19
20
|
potentialArgs: ReadonlyArray<string>,
|
|
20
21
|
defaultValue: PopulatedSingleValueType,
|
|
21
22
|
): GlobalCommandValuePopulationResult {
|
|
23
|
+
const argument = globalCommand.argument!;
|
|
24
|
+
const isSecret = argument.type === ValueTypeName.SECRET;
|
|
22
25
|
logger.debug(() => {
|
|
23
|
-
const message = `Populating value for global command: '${globalCommand.name}' using potential args: ${
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
const message = `Populating value for global command: '${globalCommand.name}' using potential args: ${(isSecret
|
|
27
|
+
? potentialArgs
|
|
28
|
+
.slice(0, 1)
|
|
29
|
+
.map(() => REDACTED)
|
|
30
|
+
.concat(potentialArgs.slice(1))
|
|
31
|
+
: potentialArgs
|
|
32
|
+
).join(" ")}`;
|
|
26
33
|
if (defaultValue !== undefined) {
|
|
27
|
-
return `${message} and configured value: '${defaultValue}'`;
|
|
34
|
+
return `${message} and configured value: '${isSecret ? REDACTED : defaultValue}'`;
|
|
28
35
|
}
|
|
29
36
|
return message;
|
|
30
37
|
});
|
|
31
|
-
const argument = globalCommand.argument!;
|
|
32
38
|
|
|
33
39
|
// default to defined argument default value
|
|
34
40
|
let populatedArgumentValue = argument.defaultValue;
|
|
@@ -83,5 +89,6 @@ export default function populateGlobalCommandValue(
|
|
|
83
89
|
return {
|
|
84
90
|
populatedArgumentValue,
|
|
85
91
|
unusedArgs,
|
|
92
|
+
...(isSecret && potentialArgs.length > 0 && { secretArgumentNames: [globalCommand.name] }),
|
|
86
93
|
};
|
|
87
94
|
}
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type PopulatedValueType,
|
|
9
9
|
} from "@flowscripter/dynamic-cli-framework-api";
|
|
10
10
|
import getLogger from "../../util/logger.ts";
|
|
11
|
+
import { REDACTED, redactSubCommandValues } from "../../util/secretRedaction.ts";
|
|
11
12
|
import argumentValueMerge from "./argumentValueMerge.ts";
|
|
12
13
|
import type { SubCommandValuePopulationResult } from "./ValuePopulationResult.ts";
|
|
13
14
|
import type { ComplexOption } from "@flowscripter/dynamic-cli-framework-api";
|
|
@@ -87,6 +88,20 @@ class ParseContext {
|
|
|
87
88
|
*/
|
|
88
89
|
currentPositionalIndex;
|
|
89
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Set when the most recent argument supplied a value for a SECRET option or positional.
|
|
93
|
+
*/
|
|
94
|
+
secretArgument?: { name: string; inline: boolean };
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Return and clear the {@link secretArgument} recorded by the most recent {@link parse}.
|
|
98
|
+
*/
|
|
99
|
+
takeSecretArgument(): { name: string; inline: boolean } | undefined {
|
|
100
|
+
const secretArgument = this.secretArgument;
|
|
101
|
+
this.secretArgument = undefined;
|
|
102
|
+
return secretArgument;
|
|
103
|
+
}
|
|
104
|
+
|
|
90
105
|
/**
|
|
91
106
|
* Use to store lazily created map of option paths e.g. --alpha.b.g and -a.b.gamma for {@link ComplexOption} or {@link Option} instances.
|
|
92
107
|
*/
|
|
@@ -351,7 +366,10 @@ class ParseContext {
|
|
|
351
366
|
* @return `false` if there is a problem populating the value, in which case the {@link state} will be set to
|
|
352
367
|
* {@link ParseState.ERROR} and {@link invalidArgument} will be populated.
|
|
353
368
|
*/
|
|
354
|
-
setOptionValue(value: SingleValueType): boolean {
|
|
369
|
+
setOptionValue(value: SingleValueType, inline = false): boolean {
|
|
370
|
+
if (this.currentOption!.type === ValueTypeName.SECRET) {
|
|
371
|
+
this.secretArgument = { name: this.currentOption!.name, inline };
|
|
372
|
+
}
|
|
355
373
|
let optionValue: Array<PopulatedValues> | PopulatedValues | PopulatedValueType =
|
|
356
374
|
this.populatedArgumentValues;
|
|
357
375
|
const optionPath = this.currentOptionPath as Array<string | number>;
|
|
@@ -439,6 +457,10 @@ class ParseContext {
|
|
|
439
457
|
const positional = this.subCommand.positionals[this.currentPositionalIndex]!;
|
|
440
458
|
const currentValue = this.populatedArgumentValues[positional.name];
|
|
441
459
|
|
|
460
|
+
if (positional.type === ValueTypeName.SECRET) {
|
|
461
|
+
this.secretArgument = { name: positional.name, inline: false };
|
|
462
|
+
}
|
|
463
|
+
|
|
442
464
|
// if we haven't populated the argument value before...
|
|
443
465
|
if (currentValue === undefined) {
|
|
444
466
|
if (positional.isVarargMultiple) {
|
|
@@ -546,7 +568,7 @@ class ParseContext {
|
|
|
546
568
|
// try to set the value if we have one
|
|
547
569
|
if (potentialOptionValue !== undefined) {
|
|
548
570
|
// set the value
|
|
549
|
-
this.setOptionValue(potentialOptionValue);
|
|
571
|
+
this.setOptionValue(potentialOptionValue, true);
|
|
550
572
|
|
|
551
573
|
// return regardless
|
|
552
574
|
return;
|
|
@@ -587,24 +609,23 @@ export default function populateSubCommandValues(
|
|
|
587
609
|
potentialArgs: ReadonlyArray<string>,
|
|
588
610
|
defaultValues: PopulatedValues | undefined,
|
|
589
611
|
): SubCommandValuePopulationResult {
|
|
590
|
-
logger.debug(() => {
|
|
591
|
-
const message = `Populating values for sub-command: '${subCommand.name}' using potential args: ${potentialArgs.join(
|
|
592
|
-
" ",
|
|
593
|
-
)}`;
|
|
594
|
-
if (defaultValues !== undefined) {
|
|
595
|
-
return `${message} and configured values: ${JSON.stringify(defaultValues)}`;
|
|
596
|
-
}
|
|
597
|
-
return message;
|
|
598
|
-
});
|
|
599
|
-
|
|
600
612
|
const parseContext = new ParseContext(subCommand);
|
|
601
613
|
const unusedArgs: Array<string> = [];
|
|
614
|
+
const loggedArgs = [...potentialArgs];
|
|
615
|
+
const secretArgumentNames = new Set<string>();
|
|
602
616
|
|
|
603
617
|
for (let i = 0; i < potentialArgs.length; i++) {
|
|
604
618
|
const potentialArg = potentialArgs[i]!;
|
|
605
619
|
|
|
606
620
|
// parse the next arg
|
|
607
621
|
parseContext.parse(potentialArg);
|
|
622
|
+
const secretArgument = parseContext.takeSecretArgument();
|
|
623
|
+
if (secretArgument) {
|
|
624
|
+
secretArgumentNames.add(secretArgument.name);
|
|
625
|
+
loggedArgs[i] = secretArgument.inline
|
|
626
|
+
? `${potentialArg.slice(0, potentialArg.indexOf("=") + 1)}${REDACTED}`
|
|
627
|
+
: REDACTED;
|
|
628
|
+
}
|
|
608
629
|
|
|
609
630
|
// check if we had a parsing error, record the error, flush the remaining args and stop parsing
|
|
610
631
|
if (parseContext.state === ParseState.ERROR) {
|
|
@@ -620,6 +641,18 @@ export default function populateSubCommandValues(
|
|
|
620
641
|
}
|
|
621
642
|
}
|
|
622
643
|
|
|
644
|
+
logger.debug(() => {
|
|
645
|
+
const message = `Populated values for sub-command: '${subCommand.name}' using potential args: ${loggedArgs.join(
|
|
646
|
+
" ",
|
|
647
|
+
)}`;
|
|
648
|
+
if (defaultValues !== undefined) {
|
|
649
|
+
return `${message} and configured values: ${JSON.stringify(
|
|
650
|
+
redactSubCommandValues(subCommand, defaultValues),
|
|
651
|
+
)}`;
|
|
652
|
+
}
|
|
653
|
+
return message;
|
|
654
|
+
});
|
|
655
|
+
|
|
623
656
|
// Check if the last arg specified an option but no value
|
|
624
657
|
if (parseContext.state === ParseState.OPTION_VALUE_EXPECTED) {
|
|
625
658
|
// special case if the last arg was for a boolean option where the value was implicitly `true`
|
|
@@ -639,6 +672,7 @@ export default function populateSubCommandValues(
|
|
|
639
672
|
populatedArgumentValues: parseContext.populatedArgumentValues,
|
|
640
673
|
unusedArgs: unusedArgs,
|
|
641
674
|
invalidArgument: parseContext.invalidArgument,
|
|
675
|
+
...(secretArgumentNames.size > 0 && { secretArgumentNames: [...secretArgumentNames] }),
|
|
642
676
|
};
|
|
643
677
|
}
|
|
644
678
|
|
|
@@ -651,5 +685,6 @@ export default function populateSubCommandValues(
|
|
|
651
685
|
return {
|
|
652
686
|
populatedArgumentValues,
|
|
653
687
|
unusedArgs: unusedArgs,
|
|
688
|
+
...(secretArgumentNames.size > 0 && { secretArgumentNames: [...secretArgumentNames] }),
|
|
654
689
|
};
|
|
655
690
|
}
|
package/src/util/runnerHelper.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { PrinterService } from "@flowscripter/dynamic-cli-framework-api";
|
|
2
2
|
import { Icon, PRINTER_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
3
|
+
import type { SecretAwareParseResult } from "../runtime/parser.ts";
|
|
3
4
|
import type { ParseResult } from "../runtime/parser.ts";
|
|
4
5
|
import { isGlobalCommand, isGlobalModifierCommand } from "../runtime/command/CommandTypeGuards.ts";
|
|
5
6
|
import type { Context } from "@flowscripter/dynamic-cli-framework-api";
|
|
@@ -122,3 +123,20 @@ export async function printUnusedArgsWarning(
|
|
|
122
123
|
await printerService.warn(`Unused args: ${overallUnusedArgs.join(" ")}\n\n`, Icon.ALERT);
|
|
123
124
|
}
|
|
124
125
|
}
|
|
126
|
+
|
|
127
|
+
export async function printSecretArgsWarning(
|
|
128
|
+
context: Context,
|
|
129
|
+
parseResult: SecretAwareParseResult,
|
|
130
|
+
) {
|
|
131
|
+
const secretArgumentNames = parseResult.secretArgumentNames;
|
|
132
|
+
if (!secretArgumentNames || secretArgumentNames.length === 0) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const printerService = context.getServiceById(PRINTER_SERVICE_ID) as PrinterService;
|
|
136
|
+
await printerService.warn(
|
|
137
|
+
`SECRET value passed as a plain argument: ${secretArgumentNames.join(", ")}. ` +
|
|
138
|
+
"This is not recommended as it may be exposed via shell history and process listings. " +
|
|
139
|
+
"Use an environment variable, a prompt or a configuration file default instead.\n\n",
|
|
140
|
+
Icon.ALERT,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ValueTypeName,
|
|
3
|
+
type ComplexOption,
|
|
4
|
+
type GlobalCommand,
|
|
5
|
+
type Option,
|
|
6
|
+
type PopulatedSingleValueType,
|
|
7
|
+
type PopulatedValues,
|
|
8
|
+
type PopulatedValueType,
|
|
9
|
+
type SubCommand,
|
|
10
|
+
} from "@flowscripter/dynamic-cli-framework-api";
|
|
11
|
+
import { isComplexOption } from "../runtime/argument/ArgumentTypeGuards.ts";
|
|
12
|
+
|
|
13
|
+
export const REDACTED = "***";
|
|
14
|
+
|
|
15
|
+
type NamedArgument = { readonly name: string; readonly type?: unknown };
|
|
16
|
+
|
|
17
|
+
type RedactableValue =
|
|
18
|
+
| PopulatedValueType
|
|
19
|
+
| PopulatedValues
|
|
20
|
+
| Array<PopulatedValues | undefined>
|
|
21
|
+
| undefined;
|
|
22
|
+
|
|
23
|
+
function redactValue(
|
|
24
|
+
definition: NamedArgument | ComplexOption,
|
|
25
|
+
value: RedactableValue | PopulatedValues,
|
|
26
|
+
): unknown {
|
|
27
|
+
if (value === undefined || value === null) {
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
if (isComplexOption(definition as ComplexOption)) {
|
|
31
|
+
const properties = (definition as ComplexOption).properties;
|
|
32
|
+
if (Array.isArray(value)) {
|
|
33
|
+
return value.map((v) => redactValue(definition, v));
|
|
34
|
+
}
|
|
35
|
+
return redactDefinedValues(properties, [], value as PopulatedValues);
|
|
36
|
+
}
|
|
37
|
+
if (definition.type === ValueTypeName.SECRET) {
|
|
38
|
+
return Array.isArray(value) ? value.map(() => REDACTED) : REDACTED;
|
|
39
|
+
}
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function redactDefinedValues(
|
|
44
|
+
options: ReadonlyArray<Option | ComplexOption>,
|
|
45
|
+
positionals: ReadonlyArray<NamedArgument>,
|
|
46
|
+
values: PopulatedValues,
|
|
47
|
+
): PopulatedValues {
|
|
48
|
+
const redacted: Record<string, unknown> = {};
|
|
49
|
+
for (const [name, value] of Object.entries(values)) {
|
|
50
|
+
const definition =
|
|
51
|
+
options.find((o) => o.name === name) ?? positionals.find((p) => p.name === name);
|
|
52
|
+
redacted[name] = definition ? redactValue(definition, value) : value;
|
|
53
|
+
}
|
|
54
|
+
return redacted as PopulatedValues;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Copy of the provided values suitable for logging, with the values of any SECRET options or positionals replaced.
|
|
59
|
+
*/
|
|
60
|
+
export function redactSubCommandValues<T extends PopulatedValues | undefined>(
|
|
61
|
+
subCommand: SubCommand,
|
|
62
|
+
values: T,
|
|
63
|
+
): T {
|
|
64
|
+
if (values === undefined) {
|
|
65
|
+
return values;
|
|
66
|
+
}
|
|
67
|
+
return redactDefinedValues(subCommand.options, subCommand.positionals, values) as T;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Copy of the provided value suitable for logging, with the value replaced if the global command argument is a SECRET.
|
|
72
|
+
*/
|
|
73
|
+
export function redactGlobalCommandValue(
|
|
74
|
+
globalCommand: GlobalCommand,
|
|
75
|
+
value: PopulatedSingleValueType,
|
|
76
|
+
): PopulatedSingleValueType {
|
|
77
|
+
if (value !== undefined && globalCommand.argument?.type === ValueTypeName.SECRET) {
|
|
78
|
+
return REDACTED;
|
|
79
|
+
}
|
|
80
|
+
return value;
|
|
81
|
+
}
|