A collection of set-theoretic declarative-first APIs for yargs and Black Flag
npm install @black-flag/extensions

A collection of set-theoretic declarative-first APIs for Yargs and Black Flag
[![Black Lives Matter!][x-badge-blm-image]][x-badge-blm-link]
[![Last commit timestamp][x-badge-lastcommit-image]][x-badge-repo-link]
[![Codecov][x-badge-codecov-image]][x-badge-codecov-link]
[![Source license][x-badge-license-image]][x-badge-license-link]
[![Uses Semantic Release!][x-badge-semanticrelease-image]][x-badge-semanticrelease-link]
[![NPM version][x-badge-npm-image]][x-badge-npm-link]
[![Monthly Downloads][x-badge-downloads-image]][x-badge-downloads-link]
Black Flag Extensions (BFE) is a collection of surprisingly simple set-theoretic
APIs that wrap Black Flag commands' exports to provide a bevy of new declarative
features, some of which are heavily inspired by [Yargs's GitHub Issues
reports][1]. It's like type-fest or jest-extended, but for Black Flag and Yargs!
In exchange for straying a bit from the vanilla Yargs API, BFE greatly increases
Black Flag's declarative powers.
Note that BFE does not represent a [complete][2] [propositional logic][3] and so
cannot describe every possible relation between arguments. Nor should it; BFE
makes it easy to fall back to using the Yargs API imperatively in those rare
instances it's necessary.
---
- Install
- Usage
- withBuilderExtensions
- withUsageExtensions
- getInvocableExtendedHandler
- Examples
- Appendix 🏴
- Differences between Black Flag Extensions and Yargs
- Black Flag versus Black Flag Extensions
- Published Package Details
- License
- Contributing and Support
- Contributors
To install:
``shell`
npm install @black-flag/extensions
> [!NOTE]
>
> See also: [differences between BFE and Yargs][4].
> [!WARNING]
>
> Most of the examples in this section (not including syntax-highlighted code
> blocks) are using hyphen characters followed by a [word joiner character][5]
> to prevent breaking examples awkwardly across lines. Be cautious copying and
> pasting.
> ⪢ API reference: [withBuilderExtensions][6]
This function enables several additional options-related units of functionality
via analysis of the returned options configuration object and the parsed command
line arguments (i.e. argv).
`javascript
import { withBuilderExtensions } from '@black-flag/extensions';
export default function command({ state }) {
const [builder, withHandlerExtensions] = withBuilderExtensions(
(blackFlag, helpOrVersionSet, argv) => {
blackFlag.strict(false);
// ▼ The "returned options configuration object"
return {
'my-argument': {
alias: ['arg1'],
demandThisOptionXor: ['arg2'],
string: true
},
arg2: {
boolean: true,
demandThisOptionXor: ['my-argument']
}
};
},
{ disableAutomaticGrouping: true }
);
return {
name: 'my-command',
builder,
handler: withHandlerExtensions(({ myArgument, arg2 }) => {
state.outputManager.log(
'Executing command with arguments: arg1=${myArgument} arg2=${arg2}'
);
})
};
}
`
Note how, in the previous example, the option names passed to configuration
keys, e.g. { demandThisOptionXor: ['my-argument'] }, are represented by their'my-argument'
exact _canonical_ names as defined (e.g. ) and not their aliases'arg1'
() or camel-case expanded forms ('myArgument'). All BFE configuration
keys expect canonical option names in this way; passing an alias or a camel-case
expansion will result in erroneous behavior.
In the same vein, withBuilderExtensions will throw if you attempt to add acamel-case-expansion
command option with a name, alias, or camel-case expansion that conflicts with
another of that command's options. This sanity check takes into account the
following [yargs-parser][7] configuration settings: ,strip-aliased, strip-dashed.
Also note how withBuilderExtensions returns a two-element array of the form:[builder, withHandlerExtensions]. [builder][8] should be exported as yourbuilder
command's [][8] function without being invoked. If you want tocustomBuilder
implement additional imperative logic, pass a _function_ towithBuilderExtensions as demonstrated in the previous example; otherwise, you
should pass an options configuration _object_.
On the other hand, withHandlerExtensions should be invoked immediately,handler
and its return value should be exported as your command's [][9]customHandler
function as demonstrated in the previous example. You should pass a to withHandlerExtensions upon invocation, though this is notwithHandlerExtensions()
required. If you call without providing acustomHandler, a placeholder function that throws CommandNotImplementedError
will be used instead, indicating that the command has not yet been implemented.
This mirrors [Black Flag's default behavior for unimplemented command
handlers][10].
#### New Option Configuration Keys
This section details the new configuration keys made available by BFE, each
implementing an options-related unit of functionality beyond that offered by
vanilla Yargs and Black Flag.
Note that the checks enabled by these configuration keys:
- Are run on Black Flag's [second parsing pass][11] except where noted. This
allows BFE to perform checks against argument _values_ in addition to the
argument existence checks enabled by vanilla Yargs.
- Will ignore the existence of the [default][12] key ([unless it's a customrequires
check][13]). This means you can use keys like [][14] andconflicts
[][15] alongside [default][12] without causing unresolvable CLI
errors. This avoids a rather unintuitive [Yargs footgun][16].
- Will take into account the following [yargs-parser settings][7] configuration
settings: camel-case-expansion, strip-aliased, strip-dashed. Note thatdot-notation
is _not_ currently recognized or considered by BFE, but may be
in the future.
Logical Keys
> [!NOTE]
>
> In the below definitions, P, Q, and R are arguments (or argument-valueblackFlag.options({ P: { [key]: [Q, R] }})
> pairs) configured via a hypothetical call to
> [][17]. The [truth values][3] ofP
> , Q, and R as described in the "Definition" column represent theargv
> existence of each respective argument (and its value) in the parsegwav
> result. is a predicate standing for "given with any value," meaning the
> argument was given on the command line.
| Key | Definition |
| :-------------------------- | :-------------------------------------------- |
| [requires][14] | P ⟹ (Q ∧ R) or ¬P ∨ (Q ∧ R) |conflicts
| [][15] | P ⟹ (¬Q ∧ ¬R) or ¬P ∨ (¬Q ∧ ¬R) |implies
| [][18] | P ⟹ (Q ∧ R ∧ (gwav(Q) ⟹ Q) ∧ (gwav(R) ⟹ R)) |demandThisOptionIf
| [][19] | (Q ∨ R) ⟹ P or P ∨ (¬Q ∧ ¬R) |demandThisOption
| [][20] | P |demandThisOptionOr
| [][21] | P ∨ Q ∨ R |demandThisOptionXor
| [][22] | P ⊕ Q ⊕ R |
Relational Keys
| Key |
| :-------------------------- |
| [check][13] |subOptionOf
| [][23] |looseImplications
| [][24] |vacuousImplications
| [][25] |
---
##### requires
> ⪢ API reference: [requires][26]
> [!IMPORTANT]
>
> requires is a superset of and replacement for vanilla Yargs'simplies
> [][27]. BFE also has [its own implication implementation][18].
requires enables checks to ensure the specified arguments, or argument-value
pairs, are given conditioned on the existence of another argument. For example:
`jsonc`
{
"x": { "requires": "y" }, // ◄ Disallows x without y
"y": {}
}
This configuration will trigger a check to ensure that -y is given whenever-x is given.
requires also supports checks against the parsed _values_ of arguments in
addition to the argument existence checks demonstrated above. For example:
`jsonc`
{
// ▼ Disallows x unless y == 'one' and z is given
"x": { "requires": [{ "y": "one" }, "z"] },
"y": {},
"z": { "requires": "y" } // ◄ Disallows z unless y is given
}
This configuration allows the following arguments: no arguments (∅),-y=..., -y=... -z, -xz -y=one; and disallows: -x, -z,-x -y=..., -xz -y=..., -xz.
Note that, when performing a check using the parsed value of an argument and
that argument is configured as an array ({ array: true }), that array will be
searched for said value. Otherwise, a strict deep equality check is performed.
###### requires versus implies
Choose [BFE's implies][18] over requires when you want one argument to implyargv
the value of another _without_ requiring the other argument to be explicitly
given in (e.g. via the command line).
---
##### conflicts
> ⪢ API reference: [conflicts][28]
> [!IMPORTANT]
>
> conflicts is a superset of vanilla Yargs's [conflicts][29].
conflicts enables checks to ensure the specified arguments, or argument-value
pairs, are _never_ given conditioned on the existence of another argument. For
example:
`jsonc`
{
"x": { "conflicts": "y" }, // ◄ Disallows y if x is given
"y": {}
}
This configuration will trigger a check to ensure that -y is never given-x
whenever is given.
conflicts also supports checks against the parsed _values_ of arguments in
addition to the argument existence checks demonstrated above. For example:
`jsonc`
{
// ▼ Disallows y == 'one' or z if x is given
"x": { "conflicts": [{ "y": "one" }, "z"] },
"y": {},
"z": { "conflicts": "y" } // ◄ Disallows y if z is given
}
This configuration allows the following arguments: no arguments (∅),-y=..., -x, -z, -x -y=...; and disallows: -y=... -z,-x -y=one, -xz -y=one, -xz.
Note that, when performing a check using the parsed value of an argument and
that argument is configured as an array ({ array: true }), that array will be
searched for said value. Otherwise, a strict deep equality check is performed.
###### conflicts versus implies
Choose [BFE's implies][18] over conflicts when you want the existence of one
argument to override the default/given value of another argument while not
preventing the two arguments from being given simultaneously.
---
##### implies
> ⪢ API reference: [implies][30]
> [!IMPORTANT]
>
> BFE's implies replaces vanilla Yargs's implies in a breaking way. The tworequires
> implementations are nothing alike. If you're looking for vanilla Yargs's
> functionality, see [][14].
implies will set a default value for the specified arguments conditioned on
the existence of another argument. This will _override_ the default value of the
specified arguments.
Unless [looseImplications][24] is set to true, if any of the specifiedargv
arguments are explicitly given in (e.g. via the command line), theirrequires
values must match the specified argument-value pairs respectively (similar to
[][14]/[conflicts][15]). For this reason, implies only accepts one
or more argument-value pairs and not raw strings. For example:
`jsonc`
{
"x": { "implies": { "y": true } }, // ◄ x becomes synonymous with xy
"y": {}
}
This configuration makes it so that -x and -x -y=true result in the exactargv
same . Further, unlike requires, implies _makes no demands on argument∅
existence_ and so allows the following arguments: no arguments (), -x,-y=true, -y=false, -x -y=true; and disallows: -x -y=false.
Note that attempting to imply a value for a non-existent option will throw a
framework error.
Additionally, if any of the specified arguments have their own [default][12]simplies
configured, said defaults will be overridden by the values of . For
example:
`jsonc`
{
"x": { "implies": { "y": true } },
"y": { "default": false } // ◄ y will still default to true if x is given
}
Also note the [special behavior][25] of implies specifically in the case whereargv
an argument value in is strictly equal to false.
For describing much more intricate implications between various arguments and
their values, see [subOptionOf][23].
###### Handling Transitive Implications
implies configurations do not cascade transitively. This means if argumentP implies argument Q, and argument Q implies argument R, and P isP
given, the only check that will be performed is on and Q. If P mustQ
imply some value for both _and R_, specify this explicitly in P's
configuration. For example:
`diff`
{
- P: { "implies": { Q: true } },
+ P: { "implies": { Q: true, R: true } },
Q: { "implies": { R: true } },
R: {}
}
This has implications beyond just implies. **An implied value will notdemandThisOptionXor
transitively satisfy any other BFE logic checks** (such as
[][22]) or trigger any relational behavior (such assubOptionOf
with [][23]). The implied argument-value pair will simply be mergedargv
into as if you had done it manually in your command's [handler][9]. Ifimplies
this is a problem, prefer the explicit direct relationships described by other
[configuration keys][31] instead of relying on the implicit transitive
relationships described by .
Despite this constraint, any per-option [check][13]s you've configured, whichwithHandlerExtensions
are run last (at the very end of ), _will_ see thecheck
implied argument-value pairs. Therefore, use [][13] to guarantee anyimplies
complex invariants, if necessary; ideally, you shouldn't be setting bad defaults
via , but BFE won't stop you from doing so.
###### Handling Parser Configuration
Like other BFE checks, implies _does_ take into account the [yargs-parsercamel-case-expansion
settings][7] , strip-aliased, and strip-dashed; butdot-notation
_does not_ currently pay attention to orduplicate-arguments-array. implies may still work when using the latter
parser configurations, but it is recommended you turn them off instead.
###### implies versus requires/conflicts
BFE's implies, since it sets arguments in argv if they are not explicitlyrequires
given, is a weaker form of [][14]/[conflicts][15].
Choose requires over BFE's implies when you want one argument to imply theargv
value of another _while_ requiring the other argument to be explicitly given in (e.g. via the command line).
Choose conflicts over BFE's implies when you think you want to use implies
but you don't actually need to override the default value of the implied
argument and only want the conflict semantics.
Alternatively, choose [subOptionOf][23] over BFE's implies when you want the
value of one argument to imply something complex about another argument and/or
its value, such as updating the other argument's options configuration.
###### looseImplications
If looseImplications is set to true, any of the specified arguments, whenargv
explicitly given in (e.g. via the command line), will _override_ anylooseImplications
configured implications instead of causing an error. When isfalse
set to , which is the default, values explicitly given in argv mustrequires
match the specified argument-value pairs respectively (similar to
[][14]/[conflicts][15]).
###### vacuousImplications
By default, an option's configured implications will only take effect if said
option is given in argv _with a non-false value_. For example:
`jsonc`
{
"x": {
"boolean": true,
"implies": { "y": true }
},
"y": {
// This example works regardless of the type of y!
"boolean": true,
//"array": true,
//"count": true,
//"number": true,
//"string": true,
"default": false
}
}
If -x (or -x=true) is given, it is synonymous with -x -y (or-x=true -y=true) being given and vice-versa. However, if -x=false (or-no-x) is given, the implies key is effectively ignored. This means-x=false _does not imply anything about -y_; -x=false -y=true and-x=false -y=false are both accepted by BFE without incident.
In this way, the configured implications of [boolean][32]-type options arefalse
_never [vacuously satisfied][33]_; a strictly condition does not "imply"
anything about its [consequent][34].
This feature reduces confusion for end users. For instance, suppose we had a CLI
build tool that accepted the arguments -patch and -only-patch. -patch-only-patch
instructs the tool to patch any output before committing it to disk while instructs the tool to _only_ patch pre-existing output already
on disk. The command's options configuration could look something like the
following:
`jsonc`
{
"patch": {
"boolean": true,
"description": "Patch output using the nearest patcher file",
"default": true
},
"only-patch": {
"boolean": true,
"description": "Instead of building new output, only patch existing output",
"default": false,
"implies": { "patch": true }
}
}
The following are rightly allowed by BFE (synonymous commands are grouped):
_Is building and patching:_
- build-toolbuild-tool -patch
- build-tool -patch=true
- build-tool -only-patch=false
- build-tool -no-only-patch
-
_Is building and not patching:_
- build-tool -patch=falsebuild-tool -no-patch
- build-tool -no-patch -no-only-patch
- __ (this is the interesting one)
_Is patching and not building:_
- build-tool -only-patchbuild-tool -only-patch=true
- build-tool -patch -only-patch
-
On the other hand, the following rightly cause BFE to throw:
- build-tool -patch=false -only-patchbuild-tool -no-patch -only-patch
-
If BFE didn't ignore vacuous implications by default, the command
build-tool -no-patch -no-only-patch would erroneously cause BFE to throwimplies: { patch: true }
since means "any time -only-patch is given, set{ patch: true } in argv", which conflicts with -no-patch which already{ patch: false }
sets in argv. This can be confusing for end users since thebuild-tool -no-only-patch
command, while redundant, technically makes sense; it is logically
indistinguishable from , which does not throw an
error.
To remedy this, BFE simply ignores the implies configurations of options whenfalse
their argument value is strictly equal to in argv. To disable thisvacuousImplications
behavior for a specific option, set to true (it isfalse by default) or consider usingrequires
[][14]/[conflicts][15]/[subOptionOf][23] over implies.
---
##### demandThisOptionIf
> ⪢ API reference: [demandThisOptionIf][35]
> [!IMPORTANT]
>
> demandThisOptionIf is a superset of vanilla Yargs's [demandOption][32].
demandThisOptionIf enables checks to ensure an argument is given when at least
one of the specified groups of arguments, or argument-value pairs, is also
given. For example:
`jsonc`
{
"x": {},
"y": { "demandThisOptionIf": "x" }, // ◄ Demands y if x is given
"z": { "demandThisOptionIf": "x" } // ◄ Demands z if x is given
}
This configuration allows the following arguments: no arguments (∅), -y,-z, -yz, -xyz; and disallows: -x, -xy, -xz.
demandThisOptionIf also supports checks against the parsed _values_ of
arguments in addition to the argument existence checks demonstrated above. For
example:
`jsonc`
{
// ▼ Demands x if y == 'one' or z is given
"x": { "demandThisOptionIf": [{ "y": "one" }, "z"] },
"y": {},
"z": {}
}
This configuration allows the following arguments: no arguments (∅), -x,-y=..., -x -y=..., -xz, -xz y=...; and disallows: -z, -y=one,-y=... -z.
Note that, when performing a check using the parsed value of an argument and
that argument is configured as an array ({ array: true }), that array will be
searched for said value. Otherwise, a strict deep equality check is performed.
Also note that a more powerful implementation of demandThisOptionIf can besubOptionOf
achieved via [][23].
---
##### demandThisOption
> ⪢ API reference: [demandThisOption][36]
> [!IMPORTANT]
>
> demandThisOption is an alias of vanilla Yargs's [demandOption][32].demandOption
> is disallowed by intellisense.
demandThisOption enables checks to ensure an argument is always given. This isdemandOption
equivalent to from vanilla Yargs. For example:
`jsonc`
{
"x": { "demandThisOption": true }, // ◄ Disallows ∅, y
"y": { "demandThisOption": false }
}
This configuration will trigger a check to ensure that -x is given.
> [!NOTE]
>
> As an alias of vanilla Yargs's [demandOption][32], this check is outsourced
> to Yargs, which means it runs on Black Flag's _first and second parsing
> passes_ like any other configurations key coming from vanilla Yargs.
---
##### demandThisOptionOr
> ⪢ API reference: [demandThisOptionOr][37]
> [!IMPORTANT]
>
> demandThisOptionOr is a superset of vanilla Yargs's [demandOption][32].
demandThisOptionOr enables non-optional inclusive disjunction checks perdemandThisOptionOr
group. Put another way, enforces a "logical or" relation
within groups of required options. For example:
`jsonc`
{
"x": { "demandThisOptionOr": ["y", "z"] }, // ◄ Demands x or y or z
"y": { "demandThisOptionOr": ["x", "z"] }, // ◄ Mirrors the above (discarded)
"z": { "demandThisOptionOr": ["x", "y"] } // ◄ Mirrors the above (discarded)
}
This configuration will trigger a check to ensure _at least one_ of x, y, orz is given. In other words, this configuration allows the following arguments:-x, -y, -z, -xy, -xz, -yz, -xyz; and disallows: no∅
arguments ().
In the interest of readability, consider mirroring the appropriate
demandThisOptionOr configuration to the other relevant options, though this is
not required (redundant groups are discarded). The previous example demonstrates
proper mirroring.
demandThisOptionOr also supports checks against the parsed _values_ of
arguments in addition to the argument existence checks demonstrated above. For
example:
`jsonc`
{
// ▼ Demands x or y == 'one' or z
"x": { "demandThisOptionOr": [{ "y": "one" }, "z"] },
"y": {},
"z": {}
}
This configuration allows the following arguments: -x, -y=one, -z,-x -y=..., -xz, -y=... -z, -xz -y=...; and disallows: no∅
arguments (), -y=....
Note that, when performing a check using the parsed value of an argument and
that argument is configured as an array ({ array: true }), that array will be
searched for said value. Otherwise, a strict deep equality check is performed.
---
##### demandThisOptionXor
> ⪢ API reference: [demandThisOptionXor][38]
> [!IMPORTANT]
>
> demandThisOptionXor is a superset of vanilla Yargs's [demandOption][32] +conflicts
> [][29].
demandThisOptionXor enables non-optional exclusive disjunction checks perdemandThisOptionXor
exclusivity group. Put another way, enforces mutual
exclusivity within groups of required options. For example:
`jsonc`
{
"x": { "demandThisOptionXor": ["y"] }, // ◄ Disallows ∅, z, w, xy, xyw, xyz, xyzw
"y": { "demandThisOptionXor": ["x"] }, // ◄ Mirrors the above (discarded)
"z": { "demandThisOptionXor": ["w"] }, // ◄ Disallows ∅, x, y, zw, xzw, yzw, xyzw
"w": { "demandThisOptionXor": ["z"] } // ◄ Mirrors the above (discarded)
}
This configuration will trigger a check to ensure _exactly one_ of -x or-y is given, and _exactly one_ of -z or -w is given. In other words,-xz
this configuration allows the following arguments: , -xw, -yz,-yw; and disallows: no arguments (∅), -x, -y, -z, -w, -xy,-zw, -xyz, -xyw, -xzw, -yzw, -xyzw.
In the interest of readability, consider mirroring the appropriate
demandThisOptionXor configuration to the other relevant options, though this
is not required (redundant groups are discarded). The previous example
demonstrates proper mirroring.
demandThisOptionXor also supports checks against the parsed _values_ of
arguments in addition to the argument existence checks demonstrated above. For
example:
`jsonc`
{
// ▼ Demands x xor y == 'one' xor z
"x": { "demandThisOptionXor": [{ "y": "one" }, "z"] },
"y": {},
"z": {}
}
This configuration allows the following arguments: -x, -y=one, -z,-x -y=..., -y=... -z; and disallows: no arguments (∅), -y=...,-x -y=one, -xz, -y=one -z, -xz -y=....
Note that, when performing a check using the parsed value of an argument and
that argument is configured as an array ({ array: true }), that array will be
searched for said value. Otherwise, a strict deep equality check is performed.
---
##### check
> ⪢ API reference: [check][39]
check is the declarative option-specific version of vanilla Yargs'syargs::check()
[][40].
This function receives the currentArgumentValue, which you are free to type asargv
you please, and the fully parsed . If this function throws, the exceptionError
will bubble. If this function returns an instance of , a string, or anyundefined
non-truthy value (including or not returning anything), Black FlagCliError
will throw a on your behalf.
All check functions are run in definition order and always at the very end ofargv
the [second parsing pass][11], well after all other BFE checks have passed and
all updates to have been applied (including from [subOptionOf][23] andimplies
[BFE's ][18]). This means check always sees the _final_ version ofargv, which is the same version that the command's [handler][9] is passed.
> [!IMPORTANT]
>
> check functions are skipped if their corresponding argument does not existargv
> in .
When a check fails, execution of its command's [handler][9] function willconfigureErrorHandlingEpilogue
cease and [][41] will be invoked (unless youGracefulEarlyExitError
threw/returned a [][42]). For example:
`javascript"x" must be between 0 and 10 (inclusive), saw: ${currentXArgValue}
export const [builder, withHandlerExtensions] = withBuilderExtensions({
x: {
number: true,
check: function (currentXArgValue, fullArgv) {
if (currentXArgValue < 0 || currentXArgValue > 10) {
throw new Error(
);
}
return true;
}
},
y: {
boolean: true,
default: false,
requires: 'x',
check: function (currentYArgValue, fullArgv) {
if (currentYArgValue && fullArgv.x <= 5) {
throw new Error(
"x" must be greater than 5 to use 'y', saw: ${fullArgv.x}
);
}
return true;
}
}
});
`
You may also pass an array of check functions, each being executed after the
other. This makes it easy to reuse checks between options. For example:
> [!WARNING]
>
> Providing an array with one or more _async_ check functions will result in
> them all being awaited concurrently.
`javascript
export const [builder, withHandlerExtensions] = withBuilderExtensions({
x: {
number: true,
check: [checkArgBetween0And10('x'), checkArgGreaterThan5('x')]
},
y: {
number: true,
check: checkArgBetween0And10('y')
},
z: {
number: true,
check: checkArgGreaterThan5('z')
}
});
function checkArgBetween0And10(argName) {
return function (argValue, fullArgv) {
return (
(argValue >= 0 && argValue <= 10) ||
"${argName}" must be between 0 and 10 (inclusive), saw: ${argValue}
);
};
}
function checkArgGreaterThan5(argName) {
return function (argValue, fullArgv) {
return (
argValue > 5 || "${argName}" must be greater than 5, saw: ${argValue}`
);
};
}
See the Yargs documentation on [yargs::check()][40] for more information.
---
##### subOptionOf
> ⪢ API reference: [subOptionOf][43]
One of Black Flag's killer features is [native support for dynamic options][44].
However, taking advantage of this feature in a command's [builder][8] export
requires a strictly imperative approach.
Take, for example, [the init command from @black-flag/demo][45]:
`javascript
// Taken at 03/23/2025 from @black-flag/demo "myctl" CLI
const PYTHON_DEFAULT_VERSION = '3.13';
const NODE_DEFAULT_VERSION = '23.3';
export function builder(yargs, _helpOrVersionSet, argv) {
// Tell Yargs to leave strings that look like numbers as strings
yargs.parserConfiguration({ 'parse-numbers': false });
// ▼ This imperative logic is a bit of an eyesore...
if (argv?.lang === 'node') {
return {
lang: { choices: ['node'], default: 'node' },
version: {
choices: ['20.18', '22.12', '23.3'],
default: NODE_DEFAULT_VERSION
}
};
} else if (argv?.lang === 'python') {
return {
lang: { choices: ['python'], default: 'python' },
version: {
choices: ['3.11', '3.12', '3.13'],
default: PYTHON_DEFAULT_VERSION
}
};
}
return {
lang: {
choices: ['node', 'python'],
// ▼ Having to use a default description is a little suboptimal...
defaultDescription: '"python"',
// ▼ This imperative logic is a little ugly...
default: argv ? 'python' : undefined
},
version: {
string: true,
// ▼ Having to use a default description is a little suboptimal...
defaultDescription: "${PYTHON_DEFAULT_VERSION}",
// ▼ This imperative logic is a little ugly...
default: argv ? PYTHON_DEFAULT_VERSION : undefined
}
};
}
export function handler(argv) {
console.log(> initializing new ${argv.lang}@${argv.version} project...);`
// ...
}
Taking advantage of dynamic options support like like we did above gifts your
CLI with help text more meaningful than anything you could accomplish with
vanilla Yargs.
For example:
`text`
myctl init --lang node --version=23.3
> initializing new node@23.3 project...
`text
myctl init --lang python --version=23.3
Usage: myctl init
Options:
--help Show help text [boolean]
--lang [choices: "python"] [default: "python"]
--version [choices: "3.11", "3.12", "3.13"] [default: "3.13"]
Invalid values:
Argument: version, Given: "23.3", Choices: "3.10", "3.11", "3.12"
`
`text
myctl init --lang fake
Usage: myctl init
Options:
--help Show help text [boolean]
--lang [choices: "node", "python"] [default: "python"]
--version [string] [default: "3.13"]
Invalid values:
Argument: lang, Given: "fake", Choices: "node", "python"
`
`text
myctl init --help
Usage: myctl init
Options:
--help Show help text [boolean]
--lang [choices: "node", "python"] [default: "python"]
--version [string] [default: "3.13"]
`
`text
myctl init --lang node --help
Usage: myctl init
Options:
--help Show help text [boolean]
--lang [choices: "node"] [default: "node"]
--version [choices: "20.18", "22.12", "23.3"] [default: "23.3"]
`
Ideally, Black Flag would allow us to describe the relationship between
--lang and its _suboption_ --version declaratively, without having to
drop down to imperative interactions with the Yargs API like we did above.
This is the goal of the subOptionOf configuration key. **Using subOptionOf,
developers can take advantage of dynamic options without sweating the
implementation details.**
> [!NOTE]
>
> subOptionOf updates are run and applied during Black Flag's [second parsing
> pass][11].
For example:
`javascriptwhen
/**
* @type {import('@black-flag/core').Configuration['builder']}
*/
export const [builder, withHandlerExtensions] = withBuilderExtensions({
x: {
choices: ['a', 'b', 'c'],
demandThisOption: true,
description: 'A choice'
},
y: {
number: true,
description: 'A number'
},
z: {
// ▼ These configurations are applied as the baseline or "fallback" during
// Black Flag's first parsing pass. The updates within subOptionOf are
// evaluated and applied during Black Flag's second parsing pass.
boolean: true,
description: 'A useful context-sensitive flag',
subOptionOf: {
// ▼ Ignored if x is not given
x: [
{
when: (currentXArgValue, fullArgv) => currentXArgValue === 'a',
update:
// ▼ We can pass an updater function that returns an opt object.
// This object will replace the argument's old configuration!
(oldXArgumentConfig, fullArgv) => {
return {
// ▼ We don't want to lose the old config, so we spread it
...oldXArgumentConfig,
description: 'This is a switch specifically for the "a" choice'
};
}
},
{
when: (currentXArgValue, fullArgv) => currentXArgValue !== 'a',
update:
// ▼ Or we can just pass the replacement configuration object. Note
// that, upon multiple matches, the last update in the"z" must be an array of two or more strings, only saw: ${currentZArgValue.length ?? 0}
// chain will win. If you want merge behavior instead of overwrite,
// spread the old config in the object you return.
{
string: true,
description: 'This former-flag now accepts a string instead'
}
}
],
// ▼ Ignored if y is not given. If x and y ARE given, since this occurs
// after the x config, this update will overwrite any others. Use the
// functional form + object spread to preserve the old configuration.
y: {
when: (currentYArgValue, fullArgv) =>
fullArgv.x === 'a' && currentYArgValue > 5,
update: (oldConfig, fullArgv) => {
return {
array: true,
demandThisOption: true,
description:
'This former-flag now accepts an array of two or more strings',
check: function (currentZArgValue, fullArgv) {
return (
currentZArgValue.length >= 2 ||
`
);
}
};
}
},
// ▼ Since "does-not-exist" is not an option defined anywhere, this will
// always be ignored
'does-not-exist': []
}
}
});
> [!IMPORTANT]
>
> You cannot nest subOptionOf keys within each other nor return an objectsubOptionOf
> containing from an update that did not already have one. Doing
> so will trigger a framework error.
Now we're ready to re-implement the init command from myctl using our new
declarative superpowers:
`javascript
export const [builder, withHandlerExtensions] = withBuilderExtensions(
function (blackFlag) {
blackFlag.parserConfiguration({ 'parse-numbers': false });
return {
lang: {
// ▼ These two are fallback or "baseline" configurations for --lang
choices: ['node', 'python'],
default: 'python',
subOptionOf: {
// ▼ Yep, --lang is also a suboption of --lang
lang: [
{
when: (lang) => lang === 'node',
// ▼ Remember: updates completely overwrite baseline config...
update: {
choices: ['node'],
default: 'node'
}
},
{
when: (lang) => lang !== 'node',
// ▼ ... though we can still reuse the "old" baseline config
update(oldOptionConfig) {
return {
...oldOptionConfig,
choices: ['python']
};
}
}
]
}
},
version: {
// ▼ These two are fallback or "baseline" configurations for --version
string: true,
default: '3.13',
subOptionOf: {
// ▼ --version is a suboption of --lang
lang: [
{
when: (lang) => lang === 'node',
update: {
choices: ['20.18', '22.12', '23.3'],
default: '23.3'
}
},
{
when: (lang) => lang !== 'node',
update(oldOptionConfig) {
return {
...oldOptionConfig,
choices: ['3.11', '3.12', '3.13']
};
}
}
]
}
}
};
}
);
`
Easy peasy!
Another benefit of subOptionOf: all configuration relevant to an option is--version
co-located within that option and not spread across some function or file. We
don't have to go looking for the logic that's modifying since it'sX_DEFAULT_VERSION
all right there in one code block. We also don't have to repeat ourselves or
pass around variables to hold defaults anymore!
See [the examples directory][46] for more subOptionOf demonstrations,myctl
including a fleshed out version of implemented using BFE.
#### Support for default with conflicts/requires/etc
BFE (and, consequently, BF/Yargs when not generating help text) will ignore the
existence of the [default][12] key until near the end of BFE's execution.
> [!IMPORTANT]
>
> This means the optional customBuilder function passed towithBuilderExtensions
> will _not_ see any defaulted values. However, your
> command handlers will.
> [!WARNING]
>
> An explicitly undefined default, i.e. { default: undefined }, will beundefined
> deleted from the configuration object by BFE and completely ignored by Black
> Flag and Yargs. This differs from BF/Yargs's default behavior, which is to
> recognize defaults.
Defaults are set _before_ any [check][13] functions are run, _before_ anyhandler
[implications][18] are set, and _before_ the relevant command [][9] isrequires
invoked, but _after_ all other BFE checks have succeeded. This enables the use
of keys like [][14] and [conflicts][15] alongside [default][12]
without causing [impossible configurations][47] that throw unresolvable CLI
errors.
This workaround avoids a (in my opinion) rather unintuitive [Yargs footgun][16],
though there are decent arguments in support of vanilla Yargs's behavior.
#### Strange and Impossible Configurations
Note that **there are no sanity checks performed to prevent options
configurations that are unresolvable**, so care must be taken not to ask for
something insane.
For example, the following configurations are impossible to resolve:
`jsonc`
{
"x": { "requires": "y" },
"y": { "conflicts": "x" }
}
`jsonc`
{
"x": { "requires": "y", "demandThisOptionXor": "y" },
"y": {}
}
Similarly, silly configurations like the following, while typically resolvable,
are strange and may not work as expected:
`jsonc`
{
"x": { "requires": "x", "demandThisOptionXor": "x" }
}
`jsonc`
{
"x": { "implies": { "x": 5 } }
}
#### Automatic Grouping of Related Options
> [!CAUTION]
>
> To support this functionality, options must be described declaratively.
> [Defining options imperatively][4] will break this feature.
BFE supports automatic [grouping][48] of related options for improved UX, which
is enabled by default. These new groups are:
- "Required Options": options configured with [demandThisOption][20].demandThisOptionOr
- "Required Options (at least one)": options configured with
[][21].demandThisOptionXor
- "Required Options (mutually exclusive)": options configured with
[][22].{ commonOptions: [...] }
- "Common Options": options provided via towithBuilderExtensions
as its second parameter:withBuilderExtensions({/.../}, { commonOptions });
- "Optional Options": remaining options that do not fall into any of the
above categories.
An example from [xunnctl][49]:
`text
$ x f b --help
Usage: xunnctl firewall ban
Add an IP from the global hostile IP list.
Required Options:
--ip An ipv4, ipv6, or supported CIDR [array]
Optional Options:
--comment Include custom text with the ban comment where applicable [string]
Common Options:
--help Show help text [boolean]
--hush Set output to be somewhat less verbose [boolean] [default: false]
--quiet Set output to be dramatically less verbose (implies --hush) [boolean] [default: false]
--silent No output will be generated (implies --quiet) [boolean] [default: false]
--config-path Use a custom configuration file
[string] [default: "/home/freelance/.config/xunnctl-nodejs/state.json"]
`
`text
$ x d z u --help
Usage: xunnctl dns zone update
Reinitialize a DNS zones.
Required Options (at least one):
--apex Zero or more zone apex domains [array]
--apex-all-known Include all known zone apex domains [boolean]
Optional Options:
--force Disable protections [boolean]
--purge-first Delete pertinent records on the zone before recreating them [boolean]
Common Options:
--help Show help text [boolean]
--hush Set output to be somewhat less verbose [boolean] [default: false]
--quiet Set output to be dramatically less verbose (implies --hush) [boolean] [default: false]
--silent No output will be generated (implies --quiet) [boolean] [default: false]
--config-path Use a custom configuration file
[string] [default: "/home/freelance/.config/xunnctl-nodejs/state.json"]
`
By including an explicit [group][48] property in an option's configuration,
the option will be included in said group _in addition to_ the result of
automatic grouping, e.g.:
`typescriptcommonOptions
const [builder, withHandlerExtensions] = withBuilderExtensions({
'my-option': {
boolean: true,
description: 'mine',
default: true,
// This option will be placed into the "Custom Grouped Options" group AND
// ALSO the "Common Options" group IF it's included in `
group: 'Custom Grouped Options'
}
});
> [!NOTE]
>
> Options configured with an explicit [group][48] property will never be
> automatically included in the "Optional Options" group.
This feature can be disabled entirely by passing
{ disableAutomaticGrouping: true } to withBuilderExtensions as its second
parameter:
`typescript`
const [builder, withHandlerExtensions] = withBuilderExtensions(
{
// ...
},
{ disableAutomaticGrouping: true }
);
#### Automatic Sorting of Options
> [!CAUTION]
>
> To support this functionality, options must be described declaratively.
> [Defining options imperatively][4] will break this feature.
BFE supports automatic alpha-sorting of a command's options in help text for
improved UX, similar to how Black Flag sorts commands themselves in help text.
This feature is disabled by default, but can be enabled by passing
{ enableAutomaticSorting: true } to withBuilderExtensions as its second
parameter:
`typescript`
const [builder, withHandlerExtensions] = withBuilderExtensions(
{
// ...
},
{ enableAutomaticSorting: true }
);
> ⪢ API reference: [withUsageExtensions][50]
This thin wrapper function is used for more consistent and opinionated usage
string generation.
`javascript`
// file: xunnctl/commands/firewall/ban.js
return {
// ...
description: 'Add an IP from the global hostile IP list',
usage: withUsageExtensions(
"$1.\n\nAdditional description text that only appears in this command's help text."
)
};
`text
$ x f b --help
Usage: xunnctl firewall ban
Add an IP from the global hostile IP list.
Additional description text that only appears in this command's help text.
Required Options:
--ip An ipv4, ipv6, or supported CIDR [array]
Optional Options:
--comment Include custom text with the ban comment where applicable [string]
Common Options:
--help Show help text [boolean]
--hush Set output to be somewhat less verbose [boolean] [default: false]
--quiet Set output to be dramatically less verbose (implies --hush) [boolean] [default: false]
--silent No output will be generated (implies --quiet) [boolean] [default: false]
--config-path Use a custom configuration file
[string] [default: "/home/freelance/.config/xunnctl-nodejs/state.json"]
`
> ⪢ API reference: [getInvocableExtendedHandler][51]
Unlike Black Flag, BFE puts strict constraints on the order in which command
exports must be invoked and evaluated. Specifically: an extended command's
[builder][8] export must be invoked twice, with the correct parameters eachhandler
time, before that extended command's [][9] can be invoked.
This can make it especially cumbersome to import an extended command from a file
and then invoke its [handler][9], which is dead simple for normal Black Flag
commands, and can introduce transitive tight-couplings between commands, which
makes bugs more likely and harder to spot.
getInvocableExtendedHandler solves this by returning a version of the extendedhandler
command's [][9] function that is ready to invoke immediately. Saidhandler
[][9] expects a single argv parameter which is "safely" cloned,
merged with several defaults (see [API reference][51]), and then passed-through
to your command's handler as-is.
One of those defaults is the value of the context parameter that was suppliedgetInvocableExtendedHandler
to . Similar to argv, context will be "safely"
cloned.
> [!TIP]
>
> A "safe" clone is a [StructuredClone-like operation][52] that passes through
> as-is any values that cannot be cloned rather than throwing an error. Since
> BFE is leveraging [safeDeepClone][52] under the hood, all clone operationscontext.state.extensions.transfer
> can be tweaked by configuring [][53]context
> appropriately (where is the [ExecutionContext][54] instance passedgetInvocableExtendedHandler
> to ).context.state.extensions.transfer
>
> Setting is useful when, for instance, youcontext
> have an object stored in that should not be deep cloned but passed
> through as-is instead.
> [!TIP]
>
> Command [handler][9] exports invoked via getInvocableExtendedHandler willargv
> receive an containing the [$artificiallyInvoked][55] symbol. This$artificiallyInvoked
> allows handlers to determine programmatically when the command isn't actually
> being invoked by Black Flag, which can be useful.
>
> However, to get intellisense/TypeScript support for the existence of
> [][55] in argv, you must useBfeStrictArguments
> [][56].
> [!CAUTION]
>
> Command [handler][9] exports invoked via getInvocableExtendedHandler willargv
> _never_ check the given for correctness or update any of its$artificiallyInvoked
> keys/values (aside from setting [][55],$executionContext
> [][57], and defaults for $0 and _ if they are omitted).runProgram
>
> By invoking a command's handler function outside of Black Flag, you're
> essentially treating it like a normal function. And all handler functions
> require a "reified argv" parameter, i.e. the object given to a command handler
> after all BF/BFE checks have passed and all updates to argv have been applied.
>
> If you want to invoke a full Black Flag command programmatically, use
> [][58]. If instead you want to call an individual command'sgetInvocableExtendedHandler
> (relatively) lightweight handler function directly, use
> .
getInvocableExtendedHandler can be used with both BFE and normal Black Flag
command exports.
For example, in JavaScript:
`javascript
// file: my-cli/commands/command-B.js
export default function command(context) {
const [builder, withHandlerExtensions] = withBuilderExtensions({
// ...
});
return {
builder,
handler: withHandlerExtensions(async function (argv) {
const handler = await getInvocableExtendedHandler(
// This accepts a function, an object, a default export, a Promise, etc
import('./command-A.js'),
context
);
await handler({ somethingElse: true });
// ...
})
};
}
`
Or in TypeScript:
`typescript
// file: my-cli/commands/command-B.ts
import { type CustomExecutionContext } from '../configure';
import {
default as commandA,
type CustomCliArguments as CommandACliArguments
} from './command-A';
export type CustomCliArguments = {
/ ... /
};
export default function command(context: CustomExecutionContext) {
const [builder, withHandlerExtensions] =
withBuilderExtensions
// ...
});
return {
builder,
handler: withHandlerExtensions
const handler = await getInvocableExtendedHandler<
CommandACliArguments,
typeof context
>(commandA, context);
await handler({ somethingElse: true });
// ...
})
};
}
`
See [the examples directory][46].
Further documentation can be found under [docs/][x-repo-docs].
When using BFE, several options function differently, such as [implies][18].default
Other options have their effect deferred, like [][12]. [coerce][59]array: true
will always receive an array when the same option also has [][17].
See the [configuration keys section][31] for a list of changes and their
justifications.
Additionally, command options must be configured by [returning an optbuilder
object][17] from your command's [][8] rather than imperatively invoking
the Yargs API.
For example:
`diff`
export function builder(blackFlag) {
- // DO NOT use Yargs's imperative API to define options! This BREAKS BFE!
- blackFlag.option('f', {
- alias: 'file',
- demandOption: true,
- default: '/etc/passwd',
- describe: 'x marks the spot',
- type: 'string',
- group: 'custom'
- });
-
- // DO NOT use Yargs's imperative API to define options! This BREAKS BFE!
- blackFlag
- .alias('f', 'file')
- .demandOption('f')
- .default('f', '/etc/passwd')
- .describe('f', 'x marks the spot')
- .string('f')
- .group('custom');
-
- // DO NOT use Yargs's imperative API to define options! This BREAKS BFE!
- blackFlag.options({
- f: {
- alias: 'file',
- demandOption: true,
- default: '/etc/passwd',
- describe: 'x marks the spot',
- type: 'string',
- group: 'custom'
- }
- });
-
+ // INSTEAD, use Yargs / Black Flag's declarative API to define options 🙂
+ return {
+ f: {
+ alias: 'file',
+ demandThisOption: true,
+ default: '/etc/passwd',
+ describe: 'x marks the spot',
+ string: true,
+ group: 'custom'
+ }
+ };
}
> [!TIP]
>
> The Yargs API can and should still be invoked for purposes other than defining
> options on a command, e.g. blackFlag.strict(false).
To this end, the following [Yargs API functions][60] are soft-disabled via
intellisense:
- optionoptions
-
However, no attempt is made by BFE to restrict your use of the Yargs API at
runtime. Therefore, using Yargs's API to work around these artificial
limitations, e.g. in your command's [builder][8] function or via theconfigureExecutionPrologue
[][61] hook, will result in undefined behavior.
The goal of [Black Flag (@black-flag/core)][62] is to be as close to a drop-in
replacement as possible for vanilla Yargs, specifically for users of
[yargs::commandDir()][63]. This means Black Flag must go out of its way to
maintain 1:1 parity with the vanilla Yargs API ([with a few minor
exceptions][64]).
As a consequence, Yargs's imperative nature tends to leak through Black Flag's
abstraction at certain points, such as with [the blackFlag parameter of thebuilder export][8]. This is a good thing! Since we want access to all of
Yargs's killer features without Black Flag getting in the way.
However, this comes with costs. For one, the Yargs's API has suffered from a bit
of feature creep over the years. A result of this is a rigid API [with][65]
[an][16] [abundance][66] [of][67] [footguns][68] and an [inability][69] to
[address][70] them without introducing [massively][71] [breaking][72]
[changes][73].
BFE takes the "YOLO" approach by exporting several functions that build on top
of Black Flag's feature set without worrying too much about maintaining 1:1
parity with the vanilla Yargs's API. This way, one can opt-in to a more
opinionated but (in my opinion) cleaner, more consistent, and more intuitive
developer experience.
This is a [CJS2 package][x-pkg-cjs-mojito] with statically-analyzable exports
built by Babel for use in Node.js versions that are not end-of-life. For
TypeScript users, this package supports both "Node10" and "Node16" module
resolution strategies.
Expand details
That means both CJS2 (via require(...)) and ESM (via import { ... } from ...await import(...)
or ) source will load this package from the same entry points
when using Node. This has several benefits, the foremost being: less code
shipped/smaller package size, avoiding [dual package
hazard][x-pkg-dual-package-hazard] entirely, distributables are not
packed/bundled/uglified, a drastically less complex build process, and CJS
consumers aren't shafted.
Each entry point (i.e. ENTRY) in [package.json'sexports[ENTRY]][x-repo-package-json] object includes one or more [exportexports[ENTRY].types
conditions][x-pkg-exports-conditions]. These entries may or may not include: an
[][x-pkg-exports-types-key] condition pointing to a typeexports[ENTRY].module
declaration file for TypeScript and IDEs, a
[][x-pkg-exports-module-key] condition pointing toexports[ENTRY].node
(usually ESM) source for Webpack/Rollup, a and/orexports[ENTRY].default condition pointing to (usually CJS2) source for Node.jsrequire/import and for browsers and other environments, and [other
conditions][x-pkg-exports-conditions] not enumerated here. Check the
[package.json][x-repo-package-json] file to see which export conditions are
supported.
Note that, regardless of the [{ "type": "..." }][x-pkg-type] specified inpackage.json
[][x-repo-package-json], any JavaScript files written in ESM.mjs
syntax (including distributables) will always have the extension. Notepackage.json
also that [][x-repo-package-json] may include thesideEffects
[][x-pkg-side-effects-key] key, which is almost always false` for
optimal [tree shaking][x-pkg-tree-shaking] where appropriate.
See [LICENSE][x-repo-license].
**[New issues][x-repo-choose-new-issue] and [pull requests][x-repo-pr-compare]
are always welcome and greatly appreciated! 🤩** Just as well, you can [star 🌟
this project][x-badge-repo-link] to let me know you found it useful! ✊🏿 Or [buy
me a beer][x-repo-sponsor], I'd appreciate it. Thank you!
See [CONTRIBUTING.md][x-repo-contributing] and [SUPPORT.md][x-repo-support] for
more information.
See the [table of contributors][x-repo-contributors].
[x-badge-blm-image]: https://xunn.at/badge-blm 'Join the movement!'
[x-badge-blm-link]: https://xunn.at/donate-blm
[x-badge-codecov-image]:
https://img.shields.io/codecov/c/github/Xunnamius/black-flag/main?style=flat-square&token=HWRIOBAAPW&flag=package.main_extensions
'Is this package well-tested?'
[x-badge-codecov-link]: https://codecov.io/gh/Xunnamius/black-flag
[x-badge-downloads-image]:
https://img.shields.io/npm/dm/@black-flag/extensions?style=flat-square
'Number of times this package has been downloaded per month'
[x-badge-downloads-link]: https://npmtrends.com/@black-flag/extensions
[x-badge-lastcommit-image]:
https://img.shields.io/github/last-commit/Xunnamius/black-flag?style=flat-square
'Latest commit timestamp'
[x-badge-license-image]:
https://img.shields.io/npm/l/@black-flag/extensions?style=flat-square
"This package's source license"
[x-badge-license-link]:
https://github.com/Xunnamius/black-flag/blob/main/LICENSE
[x-badge-npm-image]:
https://xunn.at/npm-pkg-version/@black-flag/extensions
'Install this package using npm or yarn!'
[x-badge-npm-link]: https://npm.im/@black-flag/extensions
[x-badge-repo-link]: https://github.com/Xunnamius/black-flag
[x-badge-semanticrelease-image]:
https://xunn.at/badge-semantic-release
'This repo practices continuous integration and deployment!'
[x-badge-semanticrelease-link]:
https://github.com/semantic-release/semantic-release
[x-pkg-cjs-mojito]:
https://dev.to/jakobjingleheimer/configuring-commonjs-es-modules-for-nodejs-12ed#publish-only-a-cjs-distribution-with-property-exports
[x-pkg-dual-package-hazard]:
https://nodejs.org/api/packages.html#dual-package-hazard
[x-pkg-exports-conditions]:
https://webpack.js.org/guides/package-exports#reference-syntax
[x-pkg-exports-module-key]:
https://webpack.js.org/guides/package-exports#providing-commonjs-and-esm-version-stateless
[x-pkg-exports-types-key]:
https://devblogs.microsoft.com/typescript/announcing-typescript-4-5-beta#packagejson-exports-imports-and-self-referencing
[x-pkg-side-effects-key]:
https://webpack.js.org/guides/tree-shaking#mark-the-file-as-side-effect-free
[x-pkg-tree-shaking]: https://webpack.js.org/guides/tree-shaking
[x-pkg-type]:
https://github.com/nodejs/node/blob/8d8e06a345043bec787e904edc9a2f5c5e9c275f/doc/api/packages.md#type
[x-repo-choose-new-issue]:
https://github.com/Xunnamius/black-flag/issues/new/choose
[x-repo-contributing]: /CONTRIBUTING.md
[x-repo-contributors]: /README.md#contributors
[x-repo-docs]: docs
[x-repo-license]: ./LICENSE
[x-repo-package-json]: package.json
[x-repo-pr-compare]: https://github.com/Xunnamius/black-flag/compare
[x-repo-sponsor]: https://github.com/sponsors/Xunnamius
[x-repo-support]: /.github/SUPPORT.md
[1]: https://github.com/yargs/yargs/issues
[2]: https://en.wikipedia.org/wiki/Completeness_(logic)
[3]: https://en.wikipedia.org/wiki/Propositional_calculus
[4]: #differences-between-black-flag-extensions-and-yargs
[5]: https://en.wikipedia.org/wiki/Word_joiner
[6]: ./docs/index/functions/withBuilderExtensions.md
[7]: https://github.com/yargs/yargs-parser?tab=readme-ov-file#configuration
[8]: ../../docs/api/src/exports/type-aliases/Configuration.md#builder
[9]: ../../docs/api/src/exports/type-aliases/Configuration.md#handler
[10]: ../../docs/features.md#its-yargs-all-the-way-down-
[11]:
../../docs/advanced.md#justification-for-the-parent-child-and-tripartite-program-design
[12]: https://yargs.js.org/docs#api-reference-defaultkey-value-description
[13]: #check
[14]: #requires
[15]: #conflicts
[16]: https://github.com/yargs/yargs/issues/1442
[17]: https://yargs.js.org/docs#api-reference-optionskey-opt
[18]: #implies
[19]: #demandthisoptionif
[20]: #demandthisoption
[21]: #demandthisoptionor
[22]: #demandthisoptionxor
[23]: #suboptionof
[24]: #looseimplications
[25]: #vacuousimplications
[26]: ./docs/index/type-aliases/BfeBuilderObjectValueExtensions.md#requires
[27]: https://yargs.js.org/docs#api-reference-impliesx-y
[28]: ./docs/index/type-aliases/BfeBuilderObjectValueExtensions.md#conflicts
[29]: https://yargs.js.org/docs#api-reference-conflictsx-y
[30]: ./docs/index/type-aliases/BfeBuilderObjectValueExtensions.md#implies
[31]: #new-option-configuration-keys
[32]: https://yargs.js.org/docs#api-reference-demandoptionkey-msg-boolean
[33]: https://en.wikipedia.org/wiki/Vacuous_truth
[34]: https://en.wikipedia.org/wiki/Consequent
[35]:
./docs/index/type-aliases/BfeBuilderObjectValueExtensions.md#demandThisOptionIf
[36]:
./docs/index/type-aliases/BfeBuilderObjectValueExtensions.md#demandThisOption
[37]:
./docs/index/type-aliases/BfeBuilderObjectValueExtensions.md#demandThisOptionOr
[38]:
./docs/index/type-aliases/BfeBuilderObjectValueExtensions.md#demandThisOptionXor
[39]: ./docs/index/type-aliases/BfeBuilderObjectValueExtensions.md#check
[40]: https://yargs.js.org/docs#api-reference-checkfn-globaltrue
[41]: ../../docs/api/src/exports/type-aliases/ConfigureErrorHandlingEpilogue.md
[42]: ../../docs/api/src/exports/variables/GracefulEarlyExitError.md
[43]: ./docs/index/type-aliases/BfeBuilderObjectValueExtensions.md#subOptionOf
[44]: ../../docs/features.md#built-in-support-for-dynamic-options-
[45]: https://github.com/Xunnamius/black-flag-demo/blob/main/commands/init.js
[46]: ../../examples/README.md
[47]: #strange-and-impossible-configurations
[48]: https://yargs.js.org/docs#api-reference-groupkeys-groupname
[49]: https://github.com/Xunnamius/xunnctl?tab=readme-ov-file#xunnctl
[50]: ./docs/index/functions/withUsageExtensions.md
[51]: ./docs/index/functions/getInvocableExtendedHandler.md
[52]:
https://github.com/Xunnamius/js-utils/blob/main/docs/src/functions/safeDeepClone.md
[53]:
https://github.com/Xunnamius/js-utils/blob/main/docs/src/type-aliases/SafeDeepCloneOptions.md#transfer
[54]: ../../docs/api/src/exports/util/type-aliases/ExecutionContext.md
[55]: ./docs/symbols/variables/$artificiallyInvoked.md
[56]: ./docs/index/type-aliases/BfeStrictArguments.md
[57]: ../../docs/api/src/exports/variables/$executionContext.md
[58]