Skip to content

Latest commit

 

History

History
207 lines (173 loc) · 11 KB

bf-vs-yargs.md

File metadata and controls

207 lines (173 loc) · 11 KB

Black Flag: Differences Versus Vanilla Yargs

Aside from the expanded feature set, there are some minor differences between Yargs and Black Flag. They should not be relevant given proper use of Black Flag, but are noted below nonetheless.

Minor Differences

  • The yargs::argv magic property is soft-disabled (always returns undefined) because having such an expensive "hot" getter is not optimal in a language where properties can be accessed unpredictably. For instance, deep cloning a Yargs instance results in yargs::parse (and the handlers of any registered commands!) getting invoked several times, even after an error occurred in an earlier invocation. This can lead to undefined or even dangerous behavior.

    Who in their right mind is out here cloning Yargs instances, you may ask? Jest does so whenever you use certain asymmetric matchers.

    Regardless, you should never have to reach below Black Flag's abstraction over Yargs to call methods like yargs::parse, yargs::parseAsync, yargs::argv, etc. Instead, just use Black Flag as intended.

    Therefore, this is effectively a non-issue with proper declarative use of Black Flag.

  • Yargs middleware, while technically supported at the command level, isn't supported CLI-wide since the functionality is covered by configuration hooks.

    If you have a Yargs middleware function you want run with a specific command, pass it to yargs::middleware via that command's builder function. If you have a middleware function you want to apply across all commands in your CLI, invoke it in configureArguments. If neither solution is desirable, you can also muck about with the relevant Yargs instances manually in configureExecutionPrologue (example).

  • By default, Black Flag enables the --help and --version options same as vanilla Yargs. However, since vanilla Yargs lacks the ability to modify or remove options added by yargs::option, calling yargs::help/yargs::version will throw. If you require the functionality of yargs::help/yargs::version to disable or modify the --help/--version option, update ExecutionContext::state.globalHelpOption/ExecutionContext::state.globalVersionOption directly in configureExecutionContext (example).

Note

Black Flag enables built-in help and version options, never a help or version command.

Note

Only the root command supports the built-in --version option. Calling --version on a child command will have no effect unless you make it so. This dodges another Yargs footgun, and setting ExecutionContext::state.globalVersionOption = undefined will prevent Yargs from clobbering any custom version arguments on the root command too.

Irrelevant Differences

  • A bug in [email protected] prevents yargs::showHelp/--help from printing anything when using an async builder function (or promise-returning function) for a Yargs default command.

    Black Flag addresses this with its types, in that attempting to pass an async builder will be flagged as problematic by intellisense and trigger an assertion error. Moreover, Black Flag supports an asynchronous function as the value of module.exports in CJS code, and top-level await in ESM code, so if you really do need an async builder function, hoist the async logic to work around this bug for now.

  • A bug? in [email protected] causes yargs::showHelp to erroneously print the second element in the yargs::aliases array of the Yargs default command when said command also has child commands.

    Black Flag addresses this by using a "helper" program to generate help text more consistently than vanilla Yargs. For instance, the default help text for a Black Flag command includes the full command and description strings while the commands under "Commands:" are listed in alpha-sort order as their full canonical names only; unlike vanilla Yargs, no positional arguments or aliases will be confusingly mixed into help text output unless you make it so.

  • As of [email protected], attempting to add two sibling commands with the exact same name causes all sorts of runtime insanity, especially if the commands also have aliases.

    Black Flag prevents you from shooting yourself in the foot with this. Specifically: Black Flag will throw if you attempt to add a command with a name or alias that conflicts with its sibling commands' name or alias.

  • As of [email protected], and similar to the above point, attempting to add two options with conflicting names/aliases to the same command leads to undefined and potentially dangerous runtime behavior from Yargs.

    Unfortunately, since Yargs allows adding options through a wide variety of means, Black Flag cannot protect you from this footgun. However, Black Flag Extensions (BFE) can.

    Specifically: BFE will throw if you attempt to add a command option with a name or alias that conflicts another of that command's options. BFE also takes into account the following yargs-parser settings configuration settings: camel-case-expansion, strip-aliased, strip-dashed. See BFE's documentation for details.

  • Unfortunately, [email protected] doesn't really support calling yargs::parse or yargs::parseAsync multiple times on the same instance if it's using the commands-based API. This might be a regression since, among other things, there are comments within Yargs's source that indicate these functions were intended to be called multiple times.

    Black Flag addresses this in two ways. First, the runProgram helper takes care of state isolation for you, making it safe to call runProgram multiple times. Easy peasy. Second, PreExecutionContext::execute (the wrapper around yargs::parseAsync) will throw if invoked more than once.

  • One of Black Flag's features is simple comprehensive error reporting via the configureErrorHandlingEpilogue configuration hook. Therefore, Black Flag's overridden version of the yargs::showHelpOnFail method will ignore the redundant "message" parameter. If you want that functionality, use said hook to output an epilogue after Yargs outputs an error message, or use yargs::epilogue/yargs::example.

    Also, any invocation of Black Flag's yargs::showHelpOnFail method applies globally to all commands in your hierarchy; internally, the method is just updating ExecutionContext::state.showHelpOnFail.

  • Since every auto-discovered command translates into its own Yargs instances, the command property, if exported by your command file(s), must start with "$0" or an error will be thrown. This is also enforced by intellisense.

  • The yargs::check, yargs::global, and yargs::onFinishCommand methods, while they may work as expected on commands and their direct child commands, will not function "globally" across your entire command hierarchy since there are several distinct Yargs instances in play when Black Flag executes.

    If you want a uniform check or so-called "global" argument to apply to every command across your entire hierarchy, the "Black Flag way" would be to just use normal JavaScript instead: export a shared builder function (or high-order function) from a utility file and call it in each of your command files (example). If you want something fancier than that, you can leverage configureExecutionPrologue to call yargs::global or yargs::check by hand (example).

    Similarly, yargs::onFinishCommand should only be called when the argv parameter in builder is not undefined (i.e. only on effector programs). This would prevent the callback from being executed twice. Further, the "Black Flag way" would be to ditch yargs::onFinishCommand entirely and use plain old JavaScript and/or the configureExecutionPrologue configuration hook instead.

  • Since Black Flag is built from the ground up to be asynchronous, calling yargs::parseSync will throw immediately. You shouldn't be calling the yargs::parseX functions directly anyway.

  • Black Flag sets several defaults compared to vanilla Yargs. These defaults are detailed here. If you want to tweak these defaults across all commands, use a shared builder (example). If you want something fancier than that, you can leverage configureExecutionPrologue (example).

  • For UX reasons, Black Flag will "unwrap" errors of type CliError, sending only CliError::message to the terminal when an error occurs. Black Flag will not unwrap Yargs's native errors (because they're usually simple strings already) nor custom errors thrown by the end-developer that do not extend CliError.

  • Exporting an "invalid" command string will cause Black Flag to throw (while vanilla Yargs will silently fail). command strings, if given, must be consistent with the Yargs DSL as described here.