A declarative wrapper around Yargs for building beautiful, fluent command line interfaces
$ black-pearl hoist the colors --black-flag
Black Flag 🏴
Black Flag is a fairly thin library that wraps yargs, 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()
(which has its
issues). Its features include:
- Declarative-first sync/async APIs ✨
- Zero configuration required ✨
- It's still yargs all the way down ✨ (nothing brand new to learn!)
- Built-in support for dynamic options ✨ (an infamous Yargs white whale)
- Consistent and safe CLI execution ✨
- Simple comprehensive error handling ✨
- A pleasant testing experience ✨
- Builtin TypeScript intellisense ✨
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\
❖ Step-by-step getting started guide\
❖ Black Flag versus vanilla Yargs\
❖ Simple demo CLI project (or npx -p @black-flag/demo myctl --help
)\
❖ Black Flag recipes for solving common CLI design problems\
❖ Black Flag's intro examples (which are just Yargs's intro examples rewritten with Black Flag)
❖ Builder API (essentially yargs::options
's opt
keys)\
❖ Command module API\
❖ Configuration hooks API\
❖ [BFE extended builder
API][30]\
❖ All @black-flag/core
exports\
❖ All @black-flag/core/util
exports\
❖ All @black-flag/extensions
exports\
❖ All @black-flag/checks
exports
[!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 (BFE). BFE is a collection of surprisingly simple set-theoretic APIs that build on
yargs::options()
for a fully declarative developer experience. BFE also protects you from a couple Yargs footguns that Black Flag by itself cannot.You may also be interested in Black Flag Checks (BFC), which offers several pluggable
yargs::check
functions—likecheckIsNotNegative
andcheckArrayNotEmpty
—built to work with BFE.
Install
To install:
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:
npm install @black-flag/extensions
Quick Start
Install Black Flag:
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!
#!/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
:
export const name = 'pirate-parser';
export const usage = 'Usage: $0 <cmd> [args]';
Finally, create a subcommand, perhaps at ./commands/hello.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:
node cli.js --help
Usage: pirate-parser <cmd> [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]
node cli.js hello --help
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]
node cli.js hello Parrot
Hello Parrot, welcome to Black Flag!
node cli.js hello CAPTAIN
Hello CAPTAIN, welcome to Black Flag!
node cli.js hello Parrot --attention
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
node cli.js hello CAPTAIN --attention
-!- 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
- Compare Black Flag versus vanilla Yargs
- Play with a simple demo CLI project (or
npx -p @black-flag/demo myctl --help
) - Review Black Flag recipes for solving common CLI design problems
- Deep dive into Black Flag's internals
- Pull up Black Flag's introductory examples (or Yargs's)
- Pore over Yargs's parser tricks (which also apply to Black Flag)
Appendix 🏴
Further documentation can be found under docs/
and
docs/api/
. Common CLI design "recipes" can be found under
examples/
.
Terminology
Term | Description |
---|---|
command | A "command" is a functional unit associated with a configuration file and represented internally as a trio of programs: effector, helper, and router. 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 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 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
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.
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]
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 file to see which export conditions are
supported.
Note that, regardless of the [{ "type": "..." }
][x-pkg-type] specified in
package.json
, any JavaScript files written in ESM
syntax (including distributables) will always have the .mjs
extension. Note
also that package.json
may include the
[sideEffects
][x-pkg-side-effects-key] key, which is almost always false
for
optimal tree shaking where appropriate.
License
See LICENSE.
Contributing and Support
[New issues][x-repo-choose-new-issue] and pull requests are always welcome and greatly appreciated! 🤩 Just as well, you can star 🌟 this project to let me know you found it useful! ✊🏿 Or buy me a beer, I'd appreciate it. Thank you!
See CONTRIBUTING.md and SUPPORT.md for more information.
Contributors
Thanks goes to these wonderful people (emoji key):
Bernard 🚇 💻 📖 🚧 ⚠️ 👀 |
||||||
|
This project follows the all-contributors specification. Contributions of any kind welcome!
[x-badge-blm-image]: https://xunn.at/badge-blm 'Join the movement!'
[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-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-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-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-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
[30]: ./packages/extensions/docs/index/type-aliases/BfeBuilderObjectValueExtensions.md
[42]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy