Skip to content

Commit 67f642a

Browse files
committed
feat: support env var as default flag value
Signed-off-by: Michael Molisani <[email protected]>
1 parent 60f760d commit 67f642a

File tree

8 files changed

+942
-83
lines changed

8 files changed

+942
-83
lines changed

docs/docs/features/argument-parsing/flags.mdx

+62
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,68 @@ import DefaultFlagCode from "./examples/default-flag.txt";
147147
{DefaultFlagCode}
148148
</StricliPlayground>
149149

150+
#### Default from Environment Variables
151+
152+
The default value can be sourced from anywhere, as long as it is a valid string. This includes environment variables, which can be useful for providing default values that are specific to the user's environment.
153+
154+
```ts
155+
// output-next-line
156+
/// impl.ts
157+
export default function(flags: { token: string }) {
158+
console.log(flags.token);
159+
}
160+
161+
// output-next-line
162+
/// command.ts
163+
buildCommand({
164+
loader: async () => import("./impl"),
165+
parameters: {
166+
flags: {
167+
token: {
168+
kind: "parsed",
169+
parse: String,
170+
brief: "Token used for authentication",
171+
// highlight-next-line
172+
default: process.env["SOME_TOKEN"],
173+
},
174+
},
175+
...
176+
},
177+
...
178+
});
179+
```
180+
181+
However, Stricli has built-in support for loading default values from environment variables. Specifying the default this way enables additional formatting to indicate to the user where this value came from, and the option to "redact" the value from the output. This is done by passing an option with the `env` property in the flag configuration. The value of this property should be a string that represents the name of the environment variable to load. If the user does not pass in a value for the flag and the environment variable is not set, it will throw an `UnsatisfiedFlagError`.
182+
183+
The additional `redact` config is optional, and if enabled will replace the value with `***` in the **Stricli-generated output**. Note that the value is still just a string passed to the flag, so there is nothing preventing the command implementation from displaying the value. There is no way for the command implementation to know where the value was sourced from, so it is up to the developer to ensure that the value is not printed if contains sensitive information.
184+
185+
```ts
186+
// output-next-line
187+
/// impl.ts
188+
export default function(flags: { token: string }) {
189+
console.log(flags.token);
190+
}
191+
192+
// output-next-line
193+
/// command.ts
194+
buildCommand({
195+
loader: async () => import("./impl"),
196+
parameters: {
197+
flags: {
198+
token: {
199+
kind: "parsed",
200+
parse: String,
201+
brief: "Token used for authentication",
202+
// highlight-next-line
203+
default: { env: "SOME_TOKEN", redact: true },
204+
},
205+
},
206+
...
207+
},
208+
...
209+
});
210+
```
211+
150212
### Variadic
151213

152214
A flag can be variadic when the type it represents is an array of values. In this case, the flag can be specified multiple times and each value is then parsed individually and added to a single array. If the type of a flag is an array it must be set as variadic.

packages/core/src/context.ts

+8-8
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,6 @@ interface WritableStreams {
2727
readonly stderr: Writable;
2828
}
2929

30-
/**
31-
* Command-level context that provides necessary process information and is available to all command runs.
32-
* This type should be extended to include context specific to your command implementations.
33-
*/
34-
export interface CommandContext {
35-
readonly process: WritableStreams;
36-
}
37-
3830
/**
3931
* Simple interface that mirrors NodeJS.Process but only requires the minimum API required by Stricli.
4032
*/
@@ -51,6 +43,14 @@ export interface StricliProcess extends WritableStreams {
5143
exitCode?: number | string;
5244
}
5345

46+
/**
47+
* Command-level context that provides necessary process information and is available to all command runs.
48+
* This type should be extended to include context specific to your command implementations.
49+
*/
50+
export interface CommandContext {
51+
readonly process: StricliProcess;
52+
}
53+
5454
/**
5555
* Environment variable names used by Stricli.
5656
*

packages/core/src/parameter/flag/formatting.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,11 @@ export function formatDocumentationForFlagParameters(
5959
}
6060
if (hasDefault(flag)) {
6161
const defaultKeyword = args.ansiColor ? `\x1B[90m${keywords.default}\x1B[39m` : keywords.default;
62-
suffixParts.push(`${defaultKeyword} ${flag.default === "" ? `""` : String(flag.default)}`);
62+
if (typeof flag.default === "object") {
63+
suffixParts.push(`${defaultKeyword} <from env ${flag.default.env}>`);
64+
} else {
65+
suffixParts.push(`${defaultKeyword} ${flag.default === "" ? `""` : String(flag.default)}`);
66+
}
6367
}
6468
const suffix = suffixParts.length > 0 ? `[${suffixParts.join(", ")}]` : void 0;
6569

packages/core/src/parameter/flag/types.ts

+26-6
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,22 @@
33
import type { CommandContext } from "../../context";
44
import type { ParsedParameter } from "../types";
55

6+
/**
7+
* Default value for flag when one is not provided by the end user.
8+
*/
9+
export type DefaultFlagValue<T> =
10+
| T
11+
| {
12+
/**
13+
* Default value should be read from the environment variable with this name.
14+
*/
15+
readonly env: string;
16+
/**
17+
* When enabled, redact the value from the environment variable in all Stricli-generated output.
18+
*/
19+
readonly redact?: boolean;
20+
};
21+
622
interface BaseFlagParameter {
723
/**
824
* In-line documentation for this flag.
@@ -12,6 +28,10 @@ interface BaseFlagParameter {
1228
* String that serves as placeholder for the value in the generated usage line. Defaults to "value".
1329
*/
1430
readonly placeholder?: string;
31+
/**
32+
* If no value was provided, attempt to read the environment variable with this name for the value.
33+
*/
34+
readonly env?: string;
1535
}
1636

1737
interface BaseBooleanFlagParameter extends BaseFlagParameter {
@@ -25,7 +45,7 @@ interface BaseBooleanFlagParameter extends BaseFlagParameter {
2545
*
2646
* If no value is provided, boolean flags default to `false`.
2747
*/
28-
readonly default?: boolean;
48+
readonly default?: DefaultFlagValue<boolean>;
2949
}
3050

3151
type RequiredBooleanFlagParameter = BaseBooleanFlagParameter & {
@@ -44,7 +64,7 @@ type RequiredBooleanFlagParameter = BaseBooleanFlagParameter & {
4464
/**
4565
* Default input value if one is not provided at runtime.
4666
*/
47-
readonly default: boolean;
67+
readonly default: DefaultFlagValue<boolean>;
4868
/**
4969
* Parameter should be hidden from all help text and proposed completions.
5070
* Only available for runtime-optional parameters.
@@ -117,7 +137,7 @@ export interface BaseEnumFlagParameter<T extends string> extends BaseFlagParamet
117137
/**
118138
* Default input value if one is not provided at runtime.
119139
*/
120-
readonly default?: T;
140+
readonly default?: DefaultFlagValue<T>;
121141
readonly optional?: boolean;
122142
readonly hidden?: boolean;
123143
readonly variadic?: boolean;
@@ -203,7 +223,7 @@ export interface BaseParsedFlagParameter<T, CONTEXT extends CommandContext>
203223
/**
204224
* Default input value if one is not provided at runtime.
205225
*/
206-
readonly default?: string;
226+
readonly default?: DefaultFlagValue<string>;
207227
/**
208228
* If flag is specified with no corresponding input, infer an empty string `""` as the input.
209229
*/
@@ -233,7 +253,7 @@ type RequiredParsedFlagParameter<T, CONTEXT extends CommandContext> = BaseParsed
233253
/**
234254
* Default input value if one is not provided at runtime.
235255
*/
236-
readonly default: string;
256+
readonly default: DefaultFlagValue<string>;
237257
/**
238258
* Parameter should be hidden from all help text and proposed completions.
239259
* Only available for runtime-optional parameters.
@@ -361,7 +381,7 @@ export type FlagParameters<CONTEXT extends CommandContext = CommandContext> = Re
361381

362382
export function hasDefault<CONTEXT extends CommandContext>(
363383
flag: FlagParameter<CONTEXT>,
364-
): flag is FlagParameter<CONTEXT> & { default: string | boolean } {
384+
): flag is FlagParameter<CONTEXT> & { default: DefaultFlagValue<string | boolean> } {
365385
return "default" in flag && typeof flag.default !== "undefined";
366386
}
367387

0 commit comments

Comments
 (0)