An open API service indexing awesome lists of open source software.

https://github.com/Xunnamius/black-flag

🏴 A declarative yargs-based framework for building fluent command line interfaces that are easy to maintain and extend
https://github.com/Xunnamius/black-flag

arguments cli command interface line option optstring parameters parse user yargs

Last synced: 3 months ago
JSON representation

🏴 A declarative yargs-based framework for building fluent command line interfaces that are easy to maintain and extend

Awesome Lists containing this project

README

          



A declarative wrapper around Yargs for building beautiful, fluent command line interfaces
$ black-pearl hoist the colors --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 🏴

Black Flag is a fairly thin library that wraps [yargs][1], extending its
capabilities with several powerful **declarative** features. It can be used to
create simple single-level CLIs or deeply nested sprawling interfaces alike.

Black Flag was built as a drop-in replacement for vanilla Yargs, specifically
for users of the [`yargs::commandDir()`][2] ([which][3] [has][4] [its][5]
[issues][6]). Its features include:

- [Declarative-first sync/async APIs][7] ✨
- [Zero configuration required][8] ✨
- [It's still yargs all the way down][9] ✨ (nothing brand new to learn!)
- [Built-in support for dynamic options][10] ✨ ([an][11] [infamous][12]
[Yargs][13] [white][14] [whale][15])
- [Consistent and safe CLI execution][16] ✨
- [Simple comprehensive error handling][17] ✨
- [A pleasant testing experience][18] ✨
- [Builtin TypeScript intellisense][19] ✨


Black Flag is tested on Ubuntu and Windows 10, and like Yargs tracks Node.js LTS
versions. Also comes with first-class support for both CJS and ESM source.


β€Œ β€Œ β€Œ β€Œβ– β€Œ β€Œ [Quick start][20]\
β€Œ β€Œ β€Œ β€Œβ€Œβ– β€Œ β€Œ [Step-by-step getting started guide][21]\
β€Œ β€Œ β€Œ β€Œβ€Œβ– β€Œ β€Œ [Black Flag versus vanilla Yargs][22]\
β€Œ β€Œ β€Œ β€Œβ€Œβ– β€Œ β€Œ [Simple demo CLI project][23] (or `npx -p @black-flag/demo myctl --help`)\
β€Œ β€Œ β€Œ β€Œβ€Œβ– β€Œ β€Œ [Black Flag recipes for solving common CLI design problems][24]\
β€Œ β€Œ β€Œ β€Œβ€Œβ– β€Œ β€Œ [Black Flag's intro examples][25] (which are just [Yargs's intro examples][26] rewritten with Black Flag)


β€Œ β€Œ β€Œ β€Œβ€Œβ– β€Œ β€Œ [Builder API][27] (essentially `yargs::options`'s `opt` keys)\
β€Œ β€Œ β€Œ β€Œβ€Œβ– β€Œ β€Œ [Command module API][28]\
β€Œ β€Œ β€Œ β€Œβ€Œβ– β€Œ β€Œ [Configuration hooks API][29]\
β€Œ β€Œ β€Œ β€Œβ€Œβ– β€Œ β€Œ [BFE extended `builder` API][30]\
β€Œ β€Œ β€Œ β€Œβ€Œβ– β€Œ β€Œ [All `@black-flag/core` exports][31]\
β€Œ β€Œ β€Œ β€Œβ€Œβ– β€Œ β€Œ [All `@black-flag/core/util` exports][32]\
β€Œ β€Œ β€Œ β€Œβ€Œβ– β€Œ β€Œ [All `@black-flag/extensions` exports][33]\
β€Œ β€Œ β€Œ β€Œβ€Œβ– β€Œ β€Œ [All `@black-flag/checks` exports][34]


> [!TIP]
>
> If you find yourself a fan of Black Flag's more declarative DX and want to go
> all the way, check out [Black Flag Extensions][35] (BFE). BFE is a collection
> of surprisingly simple set-theoretic APIs that build on
> [`yargs::options()`][27] for a **fully declarative developer experience**. BFE
> also protects you from [a couple Yargs footguns][36] that Black Flag by itself
> cannot.
>
> You may also be interested in [Black Flag Checks][37] (BFC), which offers
> several pluggable [`yargs::check`][38] functionsβ€”like `checkIsNotNegative` and
> `checkArrayNotEmpty`β€”built to work with BFE.

---

- [Install](#install)
- [Quick Start](#quick-start)
- [Appendix 🏴](#appendix-)
- [Terminology](#terminology)
- [Inspiration](#inspiration)
- [Published Package Details](#published-package-details)
- [License](#license)
- [Contributing and Support](#contributing-and-support)
- [Contributors](#contributors)


## Install

To install:

```shell
npm install @black-flag/core
```

And if you're ready to go all in on Black Flag's declarative API, check out
[Black Flag Extensions][35]:

```shell
npm install @black-flag/extensions
```

## Quick Start

Install Black Flag:

```shell
npm install @black-flag/core
```

Create the file that will run your CLI, perhaps at `./cli.js`:

> [!TIP]
>
> Both CJS and ESM source is acceptable!

```js
#!/usr/bin/env node

import { runProgram } from '@black-flag/core';
export default runProgram(import.meta.resolve('./commands'));
```

Then create the root command, perhaps at `./commands/index.js`:

```js
export const name = 'pirate-parser';
export const usage = 'Usage: $0 [args]';
```

Finally, create a subcommand, perhaps at `./commands/hello.js`:

```js
export const command = '$0 [name]';

export const description =
'Welcome ter black flag, a declarative wrapper around yargs!';

export function builder(blackFlag, helpOrVersionSet, argv) {
blackFlag.positional('name', {
type: 'string',
default: 'Cambi',
describe: 'The name to say hello to'
});

// A special --attention flag only available when greeting the captain!
if (helpOrVersionSet || argv?.name === 'CAPTAIN') {
return {
attention: {
boolean: true,
description: `Alert the watch${
helpOrVersionSet ? ' (only available when greeting captain)' : ''
}`
}
};
}
}

export async function handler(argv) {
if (argv.attention) {
console.log('-!- Captain is on the bridge -!-');
}

console.log(`Hello ${argv.name}, welcome to Black Flag!`);
}
```

> [!TIP]
>
> This example demonstrates a multi-level or "nested" command, i.e. a _root
> command_ with a _subcommand_. If instead we wanted to make a simple
> single-level CLI with no subcommands at all, we could merge
> `./commands/hello.js`'s exports (`handler`, `builder`, etc) into
> `./commands/index.js`.
>
> How you design your CLI is up to you!

Then run it:

```shell
node cli.js --help
```

```text
Usage: pirate-parser [args]

Commands:
pirate-parser hello Welcome ter black flag, a declarative wrapper around yargs!

Options:
--help Show help text [boolean]
--version Show version number [boolean]
```

---

```shell
node cli.js hello --help
```

```text
Usage: pirate-parser hello [name]

Welcome ter black flag, a declarative wrapper around yargs!

Positionals:
name The name to say hello to [string] [default: "Cambi"]

Options:
--help Show help text [boolean]
--attention Alert the watch (only available when greeting captain) [boolean]
```

---

```shell
node cli.js hello Parrot
```

```text
Hello Parrot, welcome to Black Flag!
```

---

```shell
node cli.js hello CAPTAIN
```

```text
Hello CAPTAIN, welcome to Black Flag!
```

---

```shell
node cli.js hello Parrot --attention
```

```text
Usage: pirate-parser hello [name]

Positionals:
name The name to say hello to [string] [default: "Cambi"]

Options:
--help Show help text [boolean]

Unknown argument: attention
```

---

```shell
node cli.js hello CAPTAIN --attention
```

```text
-!- Captain is on the bridge -!-
Hello CAPTAIN, welcome to Black Flag!
```

> [!TIP]
>
> Not sure what makes Black Flag "more declarative" than Yargs? Compare this
> quick start example to the
> vanilla Yargs version.

Next steps:

- [Check out the step-by-step getting started guide][21]
- [Compare Black Flag versus vanilla Yargs][22]
- [Play with a simple demo CLI project][23] (or
`npx -p @black-flag/demo myctl --help`)
- [Review Black Flag recipes for solving common CLI design problems][24]
- [Deep dive into Black Flag's internals][39]
- [Pull up Black Flag's introductory examples][25] (or [Yargs's][26])
- [Pore over Yargs's parser tricks][40] (which also apply to Black Flag)

## Appendix 🏴

Further documentation can be found under [`docs/`][x-repo-docs] and
[`docs/api/`][41]. Common CLI design "recipes" can be found under
[`examples/`][24].

### Terminology

| Term | Description |
| :-------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| command | A "command" is a functional unit associated with a [configuration][28] file and represented internally as a trio of programs: [effector, helper, and router][39]. Further, each command is classified as one of: "pure parent" (root and parent), "parent-child" (parent and child), or "pure child" (child). |
| program | A "program" is a Yargs instance wrapped in a [`Proxy`][42] granting the instance an expanded set of features. Programs are represented internally by the [`Program`][43] type. |
| root | The tippy top command in your hierarchy of commands and the entry point for any Black Flag application. Also referred to as the "root command". |
| default command | A "default command" is [Yargs parlance][44] for the CLI entry point. Technically there is no concept of a "default command" at the Black Flag level, though there is the _root command_. |

### Inspiration

Expand details

I love Yargs πŸ’• Yargs is the greatest! I've made dozens of CLI tools with Yargs,
each with drastically different interfaces and requirements. Some help manage
critical systems.

As I was copying-and-pasting some configs from past projects for yet another
tool, I realized the (irritatingly disparate πŸ˜–) structures of my CLI projects
up until this point were converging on a set of personal conventions around
Yargs. And, as I'm [always eager][45] to ["optimize" my workflows][46], I
wondered how much common functionality could be abstracted away.

The goal: make my CLIs more stable upon release, much faster to build, and more
pleasant to test. And also avoid Yargs's most egregious footguns. But perhaps
most important: I wanted CLIs that would remain simple and consistent to
maintain.

Throw in a re-watch of the PotC series and Black Flag was born! πŸ΄β€β˜ πŸΎ

### Published Package Details

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 ...`
or `await import(...)`) 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`'s
`exports[ENTRY]`][x-repo-package-json] object includes one or more [export
conditions][x-pkg-exports-conditions]. These entries may or may not include: an
[`exports[ENTRY].types`][x-pkg-exports-types-key] condition pointing to a type
declaration file for TypeScript and IDEs, a
[`exports[ENTRY].module`][x-pkg-exports-module-key] condition pointing to
(usually ESM) source for Webpack/Rollup, a `exports[ENTRY].node` and/or
`exports[ENTRY].default` condition pointing to (usually CJS2) source for Node.js
`require`/`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 in
[`package.json`][x-repo-package-json], any JavaScript files written in ESM
syntax (including distributables) will always have the `.mjs` extension. Note
also that [`package.json`][x-repo-package-json] may include the
[`sideEffects`][x-pkg-side-effects-key] key, which is almost always `false` for
optimal [tree shaking][x-pkg-tree-shaking] where appropriate.

### License

See [LICENSE][x-repo-license].

## Contributing and Support

**[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.

### Contributors

[![All Contributors](https://img.shields.io/badge/all_contributors-1-orange.svg?style=flat-square)](#contributors-)

Thanks goes to these wonderful people ([emoji
key][x-repo-all-contributors-emojis]):



Bernard
Bernard

πŸš‡ πŸ’» πŸ“– 🚧 ⚠️ πŸ‘€






Add your contributions



This project follows the [all-contributors][x-repo-all-contributors]
specification. Contributions of any kind welcome!

[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_root
'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/core?style=flat-square
'Number of times this package has been downloaded per month'
[x-badge-downloads-link]: https://npmtrends.com/@black-flag/core
[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/core?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/core
'Install this package using npm or yarn!'
[x-badge-npm-link]: https://npm.im/@black-flag/core
[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-all-contributors]: https://github.com/all-contributors/all-contributors
[x-repo-all-contributors-emojis]: https://allcontributors.org/docs/en/emoji-key
[x-repo-choose-new-issue]:
https://github.com/Xunnamius/black-flag/issues/new/choose
[x-repo-contributing]: /CONTRIBUTING.md
[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://yargs.js.org
[2]: https://yargs.js.org/docs#api-reference-commanddirdirectory-opts
[3]: https://github.com/yargs/yargs/issues/1306
[4]: https://github.com/yargs/yargs/issues/1269
[5]: https://github.com/yargs/yargs/issues/1975
[6]: https://github.com/yargs/yargs/issues/2152
[7]: ./docs/features.md#declaratively-build-deep-command-hierarchies-
[8]: ./docs/features.md#convention-over-configuration-
[9]: ./docs/features.md#its-yargs-all-the-way-down-
[10]: ./docs/features.md#built-in-support-for-dynamic-options-
[11]: https://github.com/yargs/yargs/issues/793
[12]: https://github.com/yargs/yargs/issues/2119
[13]: https://github.com/yargs/yargs/issues/2256
[14]: https://github.com/yargs/yargs/issues/2227
[15]: https://github.com/yargs/yargs/issues/2133
[16]: ./docs/features.md#run-your-tool-safely-and-consistently-
[17]: ./docs/features.md#simple-comprehensive-error-handling-and-reporting-
[18]: ./docs/features.md#a-pleasant-testing-experience-
[19]: ./docs/features.md#extensive-intellisense-support-
[20]: #quick-start
[21]: ./docs/getting-started.md
[22]: ./docs/bf-vs-yargs.md
[23]: https://github.com/Xunnamius/black-flag-demo
[24]: ./examples/README.md
[25]: ./examples/yargs-intro/README.md
[26]: https://github.com/yargs/yargs/blob/HEAD/docs/examples.md
[27]: https://yargs.js.org/docs#api-reference-optionskey-opt
[28]: ./docs/api/src/exports/type-aliases/Configuration.md
[29]: ./docs/api/src/exports/type-aliases/ConfigurationHooks.md
[30]:
./packages/extensions/docs/index/type-aliases/BfeBuilderObjectValueExtensions.md
[31]: ./docs/api/src/exports/README.md
[32]: ./docs/api/src/exports/util/README.md
[33]: ./packages/extensions/docs/index/README.md
[34]: ./packages/checks/docs/index/README.md
[35]: ./packages/extensions/README.md
[36]: ./docs/bf-vs-yargs.md#irrelevant-differences
[37]: ./packages/checks/README.md
[38]: https://yargs.js.org/docs#api-reference-checkfn-globaltrue
[39]: ./docs/advanced.md
[40]: https://github.com/yargs/yargs/blob/main/docs/tricks.md
[41]: ./docs/api/README.md
[42]:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
[43]: ./docs/api/src/exports/util/type-aliases/Program.md
[44]: https://github.com/yargs/yargs/blob/main/docs/advanced.md#default-commands
[45]: https://xkcd.com/1205
[46]: https://i.redd.it/0cm6yx27tez21.jpg