bandersnatch
Advanced tools
Comparing version 1.0.0-alpha.4 to 1.0.0-alpha.5
@@ -18,10 +18,24 @@ import { Argv, Arguments as BaseArguments } from 'yargs'; | ||
constructor(command?: string | string[] | undefined, description?: string | undefined); | ||
/** | ||
* Adds a new positional argument to the command. | ||
* This is shorthand for .add(argument(...)) | ||
*/ | ||
argument<K extends string, O extends ArgumentOptions>(name: K, descriptionOrOptions?: string | O, options?: O): Command<T & { [key in K]: InferArgType<O, string>; }>; | ||
/** | ||
* Adds a new option to the command. | ||
* This is shorthand for .add(option(...)) | ||
*/ | ||
option<K extends string, O extends OptionOptions>(name: K, descriptionOrOptions?: string | O, options?: O): Command<T & { [key in K]: InferArgType<O, unknown>; }>; | ||
/** | ||
* This is the base method for adding arguments and options, but it doesn't provide | ||
* type hints. Use .argument() and .option() instead. | ||
* This is the base method for adding arguments, options and commands, but it | ||
* doesn't provide type hints. Use .argument() and .option() instead. | ||
*/ | ||
add(obj: Argument | Option | Command): this; | ||
/** | ||
* Mark as the default command. | ||
*/ | ||
default(): this; | ||
/** | ||
* Provide a function to execute when this command is invoked. | ||
*/ | ||
action(fn: HandlerFn<T>): this; | ||
@@ -31,3 +45,2 @@ private getArguments; | ||
private getCommands; | ||
private toModule; | ||
/** | ||
@@ -38,2 +51,3 @@ * Calls the command() method on the passed in yargs instance and returns it. | ||
toYargs(yargs: Argv): Argv<T>; | ||
private toModule; | ||
/** | ||
@@ -49,5 +63,10 @@ * Returns a formatted command which can be used in the command() function | ||
/** | ||
* Returns the command handler | ||
* Wraps the actual command handler to insert prompt and async handler logic. | ||
*/ | ||
private getHandler; | ||
/** | ||
* Returns an array of arguments and options which should be prompted, because | ||
* they are promptable (isPromptable() returned true) and they are not | ||
* provided in the args passed in to this function. | ||
*/ | ||
private getQuestions; | ||
@@ -54,0 +73,0 @@ /** |
@@ -42,3 +42,4 @@ "use strict"; | ||
} | ||
/* | ||
/** | ||
* Adds a new positional argument to the command. | ||
* This is shorthand for .add(argument(...)) | ||
@@ -55,3 +56,4 @@ */ | ||
} | ||
/* | ||
/** | ||
* Adds a new option to the command. | ||
* This is shorthand for .add(option(...)) | ||
@@ -69,4 +71,4 @@ */ | ||
/** | ||
* This is the base method for adding arguments and options, but it doesn't provide | ||
* type hints. Use .argument() and .option() instead. | ||
* This is the base method for adding arguments, options and commands, but it | ||
* doesn't provide type hints. Use .argument() and .option() instead. | ||
*/ | ||
@@ -95,2 +97,5 @@ add(obj) { | ||
} | ||
/** | ||
* Mark as the default command. | ||
*/ | ||
default() { | ||
@@ -100,2 +105,5 @@ this.command = '$0'; | ||
} | ||
/** | ||
* Provide a function to execute when this command is invoked. | ||
*/ | ||
action(fn) { | ||
@@ -114,2 +122,9 @@ this.handler = fn; | ||
} | ||
/** | ||
* Calls the command() method on the passed in yargs instance and returns it. | ||
* See https://github.com/yargs/yargs/blob/master/docs/advanced.md#providing-a-command-module | ||
*/ | ||
toYargs(yargs) { | ||
return yargs.command(this.toModule()); | ||
} | ||
toModule() { | ||
@@ -126,9 +141,2 @@ const module = { | ||
/** | ||
* Calls the command() method on the passed in yargs instance and returns it. | ||
* See https://github.com/yargs/yargs/blob/master/docs/advanced.md#providing-a-command-module | ||
*/ | ||
toYargs(yargs) { | ||
return yargs.command(this.toModule()); | ||
} | ||
/** | ||
* Returns a formatted command which can be used in the command() function | ||
@@ -164,3 +172,3 @@ * of yargs. | ||
/** | ||
* Returns the command handler | ||
* Wraps the actual command handler to insert prompt and async handler logic. | ||
*/ | ||
@@ -176,2 +184,4 @@ getHandler() { | ||
chain = chain.then(async (args) => { | ||
// @todo check if command has sub-commands, and if so, do not throw an | ||
// error but maybe show help instead? | ||
if (!this.handler) { | ||
@@ -188,2 +198,7 @@ throw new Error('No handler defined for this command.'); | ||
} | ||
/** | ||
* Returns an array of arguments and options which should be prompted, because | ||
* they are promptable (isPromptable() returned true) and they are not | ||
* provided in the args passed in to this function. | ||
*/ | ||
getQuestions(args) { | ||
@@ -194,2 +209,3 @@ // If we need to prompt for things, fill questions array | ||
const presentInArgs = Object.constructor.hasOwnProperty.call(args, name); | ||
// @todo How can we force prompting when default was used? | ||
if (!presentInArgs && arg.isPromptable()) { | ||
@@ -196,0 +212,0 @@ questions.push({ |
@@ -5,6 +5,4 @@ export * from './argument'; | ||
export * from './option'; | ||
export * from './printer'; | ||
export * from './program'; | ||
export * from './repl'; | ||
export * from './runner'; | ||
export * from './utils'; |
@@ -10,6 +10,4 @@ "use strict"; | ||
__export(require("./option")); | ||
__export(require("./printer")); | ||
__export(require("./program")); | ||
__export(require("./repl")); | ||
__export(require("./runner")); | ||
__export(require("./utils")); |
import { Argv } from 'yargs'; | ||
import { Command } from './command'; | ||
import { Arguments } from './command'; | ||
import { Runner } from './runner'; | ||
declare type FailFn = (msg: string, err: Error, args: Arguments, usage?: string) => void; | ||
import { Repl } from './repl'; | ||
declare type ProgramOptions = { | ||
/** | ||
* Whether or not to add a global help command that displays an overview of | ||
* commands. Can also be enabled by calling `program().withHelp()`. | ||
* | ||
* Defaults to `false`. | ||
*/ | ||
help?: boolean; | ||
/** | ||
* Whether or not to add a global version command that displays the version as | ||
* specified in the package.json file. Can also be enabled by calling | ||
* `program().withVersion()`. | ||
* | ||
* Defaults to `false`. | ||
*/ | ||
version?: boolean; | ||
fail?: FailFn; | ||
/** | ||
* Sets a custom REPL prompt. | ||
* | ||
* Defaults to `>`. | ||
*/ | ||
prompt?: string; | ||
exitOnError?: boolean; | ||
}; | ||
/** | ||
* Creates a new bandersnatch program. | ||
*/ | ||
export declare function program(description?: string, options?: ProgramOptions): Program; | ||
@@ -19,29 +36,52 @@ export declare class Program { | ||
private replInstance?; | ||
private runnerInstance?; | ||
constructor(description?: string | undefined, options?: ProgramOptions); | ||
/** | ||
* Create a new yargs instance. Not intended for public use. | ||
* Create a new yargs instance. This method may change at any time, not | ||
* intended for public use. | ||
* | ||
* @private | ||
*/ | ||
createYargsInstance(): Argv<{}>; | ||
/** | ||
* Adds a new command to the program. | ||
*/ | ||
add<T>(command: Command<T>): this; | ||
/** | ||
* Adds a new command to the program and marks it as the default command. | ||
*/ | ||
default<T>(command: Command<T>): this; | ||
prompt(prompt: string): this; | ||
/** | ||
* Adds a global help command that displays an overview of commands. | ||
*/ | ||
withHelp(): this; | ||
/** | ||
* Adds a global version command that displays the version as specified in the | ||
* package.json file. | ||
*/ | ||
withVersion(): this; | ||
fail(fn: FailFn): this; | ||
/** | ||
* Evaluate command (or process.argv) and return runner instance. | ||
* Sets a custom REPL prompt. | ||
*/ | ||
eval(command?: string | ReadonlyArray<string>): Runner<unknown>; | ||
prompt(prompt: string): this; | ||
/** | ||
* Run a command (or process.argv) and print output. | ||
* Evaluate command (or process.argv) and return promise. | ||
*/ | ||
run(command?: string | ReadonlyArray<string>): Runner<unknown>; | ||
run(command?: string | ReadonlyArray<string>): Promise<unknown>; | ||
/** | ||
* Run event loop which reads command from stdin. | ||
*/ | ||
repl(): void; | ||
repl(): Repl; | ||
/** | ||
* When argv is set, run the program, otherwise start repl loop. | ||
*/ | ||
runOrRepl(): Promise<unknown> | Repl; | ||
/** | ||
* Method to execute when a failure occurs, rather than printing the failure | ||
* message. | ||
* | ||
* Called with the failure message that would have been printed, the Error | ||
* instance originally thrown and yargs state when the failure occured. | ||
*/ | ||
private failHandler; | ||
private defaultFailFn; | ||
} | ||
export {}; |
@@ -7,7 +7,8 @@ "use strict"; | ||
const yargs_1 = __importDefault(require("yargs/yargs")); | ||
const ansi_colors_1 = require("ansi-colors"); | ||
const command_1 = require("./command"); | ||
const repl_1 = require("./repl"); | ||
const utils_1 = require("./utils"); | ||
const runner_1 = require("./runner"); | ||
/** | ||
* Creates a new bandersnatch program. | ||
*/ | ||
function program(description, options = {}) { | ||
@@ -17,2 +18,5 @@ return new Program(description, options); | ||
exports.program = program; | ||
function extractCommandFromProcess() { | ||
return process.argv.slice(2); | ||
} | ||
class Program { | ||
@@ -23,15 +27,8 @@ constructor(description, options = {}) { | ||
this.commands = []; | ||
this.defaultFailFn = (msg, err, args, usage) => { | ||
if (msg) { | ||
console.error(ansi_colors_1.yellow(msg)); | ||
} | ||
if (usage) { | ||
console.error(''); | ||
console.error(usage); | ||
} | ||
process.exit(1); | ||
}; | ||
} | ||
/** | ||
* Create a new yargs instance. Not intended for public use. | ||
* Create a new yargs instance. This method may change at any time, not | ||
* intended for public use. | ||
* | ||
* @private | ||
*/ | ||
@@ -52,8 +49,7 @@ createYargsInstance() { | ||
// Custom fail function. | ||
// TODO current yargs types doesn't include the third parameter. | ||
yargs.fail(this.failHandler.bind(this)); | ||
// Exit on errors? | ||
yargs.exitProcess(!!this.options.exitOnError); | ||
// In case we're in a REPL session, do not exit on errors. | ||
yargs.exitProcess(!this.replInstance); | ||
// Add commands | ||
this.commands.forEach(command => { | ||
this.commands.forEach((command) => { | ||
command.toYargs(yargs); | ||
@@ -63,2 +59,5 @@ }); | ||
} | ||
/** | ||
* Adds a new command to the program. | ||
*/ | ||
add(command) { | ||
@@ -68,2 +67,5 @@ this.commands.push(command); | ||
} | ||
/** | ||
* Adds a new command to the program and marks it as the default command. | ||
*/ | ||
default(command) { | ||
@@ -73,6 +75,5 @@ this.commands.push(command.default()); | ||
} | ||
prompt(prompt) { | ||
this.options.prompt = prompt; | ||
return this; | ||
} | ||
/** | ||
* Adds a global help command that displays an overview of commands. | ||
*/ | ||
withHelp() { | ||
@@ -82,2 +83,6 @@ this.options.help = true; | ||
} | ||
/** | ||
* Adds a global version command that displays the version as specified in the | ||
* package.json file. | ||
*/ | ||
withVersion() { | ||
@@ -87,20 +92,35 @@ this.options.version = true; | ||
} | ||
fail(fn) { | ||
this.options.fail = fn; | ||
/** | ||
* Sets a custom REPL prompt. | ||
*/ | ||
prompt(prompt) { | ||
this.options.prompt = prompt; | ||
return this; | ||
} | ||
/** | ||
* Evaluate command (or process.argv) and return runner instance. | ||
* Evaluate command (or process.argv) and return promise. | ||
*/ | ||
eval(command) { | ||
const cmd = command || process.argv.slice(2); | ||
// Set executor to promise resolving to the return value of the command | ||
run(command) { | ||
const cmd = command || extractCommandFromProcess(); | ||
// Return promise resolving to the return value of the command | ||
// handler. | ||
this.runnerInstance = runner_1.runner((resolve, reject) => { | ||
return new Promise((resolve, reject) => { | ||
this.createYargsInstance().parse(cmd, {}, (err, argv, output) => { | ||
// Output is a string for built-in commands like --version and --help | ||
/** | ||
* From the yargs docs: | ||
* > any text that would have been output by yargs to the terminal, | ||
* > had a callback not been provided. | ||
* http://yargs.js.org/docs/#api-parseargs-context-parsecallback | ||
* | ||
* Seems that this is primarily used for built-in commands like | ||
* --version and --help. | ||
*/ | ||
if (output) { | ||
console.log(output); | ||
} | ||
// TODO when is err defined? | ||
/** | ||
* From the yargs docs: | ||
* > Populated if any validation errors raised while parsing. | ||
* http://yargs.js.org/docs/#api-parseargs-context-parsecallback | ||
*/ | ||
if (err) { | ||
@@ -120,11 +140,4 @@ console.error(err); | ||
}); | ||
return this.runnerInstance.eval(); | ||
} | ||
/** | ||
* Run a command (or process.argv) and print output. | ||
*/ | ||
run(command) { | ||
return this.eval(command).print(); | ||
} | ||
/** | ||
* Run event loop which reads command from stdin. | ||
@@ -134,4 +147,2 @@ */ | ||
this.replInstance = repl_1.repl(this, this.options.prompt); | ||
// Don't exit on errors. | ||
this.options.exitOnError = false; | ||
// Add exit command | ||
@@ -142,32 +153,36 @@ this.add(command_1.command('exit', 'Exit the application').action(() => { | ||
this.replInstance.start(); | ||
return this.replInstance; | ||
} | ||
/** | ||
* When argv is set, run the program, otherwise start repl loop. | ||
*/ | ||
runOrRepl() { | ||
return extractCommandFromProcess().length ? this.run() : this.repl(); | ||
} | ||
/** | ||
* Method to execute when a failure occurs, rather than printing the failure | ||
* message. | ||
* | ||
* Called with the failure message that would have been printed, the Error | ||
* instance originally thrown and yargs state when the failure occured. | ||
*/ | ||
failHandler(msg, err, yargs) { | ||
// TODO needs more use-cases: only do something when msg is set, and have | ||
// errors always handled in the runner? | ||
if (this.replInstance) { | ||
// In case we're in a REPL session, forward the message which may | ||
// originate from yargs. Errors are handled in the runner. | ||
msg && this.replInstance.setError(msg); | ||
if (msg) { | ||
// If msg is set, it's probably a validation error from yargs we want to | ||
// print. | ||
console.error(msg); | ||
if (this.replInstance) { | ||
// In case we're in a REPL session, indicate we printed a message, so we | ||
// can prevent the program resolve handler to execute. | ||
this.replInstance.gotYargsMsg(); | ||
} | ||
else { | ||
// In other cases, exit with status of 1. | ||
process.exit(1); | ||
} | ||
} | ||
else { | ||
const args = yargs.argv; | ||
const usage = yargs.help(); | ||
const cb = () => { | ||
if (this.options.fail) { | ||
// Call custom fail function. | ||
this.options.fail(msg, err, args, usage); | ||
} | ||
else { | ||
// Call default fail function. | ||
this.defaultFailFn(msg, err, args, usage); | ||
} | ||
}; | ||
// We call the fail function in the runner chain if available, to give | ||
// async printer a chance to complete first. | ||
this.runnerInstance | ||
? this.runnerInstance.then(cb) | ||
: Promise.resolve().then(cb); | ||
} | ||
} | ||
} | ||
exports.Program = Program; |
@@ -7,9 +7,30 @@ import { Program } from './program'; | ||
private server?; | ||
private lastError; | ||
private autocompleter; | ||
private receivedYargsMsg; | ||
private successHandler; | ||
private errorHandler; | ||
constructor(program: Program, prompt?: string); | ||
/** | ||
* @todo Add meaningful comments here. | ||
*/ | ||
start(): Promise<void>; | ||
setError(err: string): void; | ||
gotYargsMsg(): void; | ||
/** | ||
* Emulates promise.then, but saves the callback instead to be executed on | ||
* each command which resolves. | ||
*/ | ||
then(cb: (value?: unknown) => void): this; | ||
/** | ||
* Emulates promise.catch, but saves the callback instead to be executed on | ||
* each command which rejects. | ||
*/ | ||
catch(cb: (reason?: any) => void): this; | ||
/** | ||
* @todo Add meaningful comments here. | ||
*/ | ||
private completer; | ||
/** | ||
* @todo Add meaningful comments here. | ||
*/ | ||
private eval; | ||
} |
@@ -8,3 +8,2 @@ "use strict"; | ||
const string_argv_1 = require("string-argv"); | ||
const ansi_colors_1 = require("ansi-colors"); | ||
const autocompleter_1 = require("./autocompleter"); | ||
@@ -19,5 +18,10 @@ function repl(program, prefix = '> ') { | ||
this.prompt = prompt; | ||
this.lastError = null; | ||
this.receivedYargsMsg = false; | ||
this.successHandler = () => { }; | ||
this.errorHandler = (reason) => console.error(reason); | ||
this.autocompleter = autocompleter_1.autocompleter(program); | ||
} | ||
/** | ||
* @todo Add meaningful comments here. | ||
*/ | ||
async start() { | ||
@@ -28,27 +32,57 @@ this.server = repl_1.default.start({ | ||
completer: this.completer.bind(this), | ||
ignoreUndefined: true | ||
ignoreUndefined: true, | ||
}); | ||
} | ||
setError(err) { | ||
this.lastError = err; | ||
gotYargsMsg() { | ||
this.receivedYargsMsg = true; | ||
} | ||
/** | ||
* Emulates promise.then, but saves the callback instead to be executed on | ||
* each command which resolves. | ||
*/ | ||
then(cb) { | ||
this.successHandler = cb; | ||
return this; | ||
} | ||
/** | ||
* Emulates promise.catch, but saves the callback instead to be executed on | ||
* each command which rejects. | ||
*/ | ||
catch(cb) { | ||
this.errorHandler = cb; | ||
return this; | ||
} | ||
/** | ||
* @todo Add meaningful comments here. | ||
*/ | ||
async completer(line, cb) { | ||
function addSpace(str) { | ||
return `${str} `; | ||
} | ||
const argv = string_argv_1.parseArgsStringToArgv(line); | ||
const current = argv.slice(-1).toString(); | ||
const completions = await this.autocompleter.completions(argv); | ||
let hits = completions.filter(completion => completion.startsWith(current)); | ||
// Add trailing space to each hit | ||
hits = hits.map(hit => `${hit} `); | ||
const completions = (await this.autocompleter.completions(argv)).map(addSpace); | ||
let hits = completions.filter((completion) => completion.startsWith(current)); | ||
// Show all completions if none found | ||
cb(null, [hits.length ? hits : completions, current]); | ||
} | ||
/** | ||
* @todo Add meaningful comments here. | ||
*/ | ||
async eval(line, context, file, cb) { | ||
this.lastError = null; | ||
const result = await this.program.run(line.trim()); | ||
if (this.lastError) { | ||
console.error(ansi_colors_1.red(this.lastError)); | ||
this.receivedYargsMsg = false; | ||
try { | ||
const result = await this.program.run(line.trim()); | ||
// Execute success handler only if we have not received a msg from yargs, | ||
// which was probably a validation error. | ||
this.receivedYargsMsg || this.successHandler(result); | ||
} | ||
cb(null, result); | ||
catch (error) { | ||
this.errorHandler(error); | ||
} | ||
// The result passed to this function is printed by the Node REPL server, | ||
// but we don't want to use that, so we pass undefined instead. | ||
cb(null, undefined); | ||
} | ||
} | ||
exports.Repl = Repl; |
{ | ||
"name": "bandersnatch", | ||
"description": "", | ||
"version": "1.0.0-alpha.4", | ||
"version": "1.0.0-alpha.5", | ||
"main": "lib/index.js", | ||
@@ -14,3 +14,5 @@ "files": [ | ||
"test": "jest", | ||
"start": "ts-node" | ||
"start": "ts-node", | ||
"doc:toc": "doctoc README.md", | ||
"doc:todos": "leasot --exit-nicely --reporter markdown \"src/**/*.ts\" > TODO.md" | ||
}, | ||
@@ -20,27 +22,27 @@ "author": "", | ||
"dependencies": { | ||
"@types/inquirer": "^6.5.0", | ||
"@types/yargs": "^13.0.3", | ||
"ansi-colors": "^4.1.1", | ||
"inquirer": "^7.0.0", | ||
"string-argv": "^0.3.1", | ||
"yargs": "^15.0.2" | ||
"@types/inquirer": "6.5.0", | ||
"@types/yargs": "15.0.4", | ||
"inquirer": "7.1.0", | ||
"string-argv": "0.3.1", | ||
"yargs": "15.3.1" | ||
}, | ||
"devDependencies": { | ||
"@types/jest": "^24.0.23", | ||
"@types/node": "^12.12.17", | ||
"doctoc": "^1.4.0", | ||
"husky": "^3.1.0", | ||
"jest": "^24.9.0", | ||
"mock-argv": "^1.1.2", | ||
"prettier": "^1.19.1", | ||
"promise.prototype.finally": "^3.1.2", | ||
"ts-jest": "^24.2.0", | ||
"ts-node": "^8.5.4", | ||
"typescript": "^3.7.3" | ||
"@types/jest": "25.2.1", | ||
"@types/node": "12.12.35", | ||
"doctoc": "1.4.0", | ||
"husky": "4.2.5", | ||
"jest": "26.0.1", | ||
"leasot": "10.3.0", | ||
"mock-argv": "1.1.6", | ||
"prettier": "2.0.5", | ||
"promise.prototype.finally": "3.1.2", | ||
"ts-jest": "25.5.0", | ||
"ts-node": "8.10.1", | ||
"typescript": "3.8.3" | ||
}, | ||
"husky": { | ||
"hooks": { | ||
"pre-commit": "yarn doctoc . && git add README.md" | ||
"pre-commit": "yarn doc:toc && yarn doc:todos" | ||
} | ||
} | ||
} |
186
README.md
@@ -15,14 +15,14 @@ # bandersnatch | ||
- ➰ Built-in REPL | ||
- 🌊 [Fluid](https://www.martinfowler.com/bliki/FluentInterface.html) syntax | ||
- ➰ Built-in [REPL](https://en.wikipedia.org/wiki/Read–eval–print_loop) | ||
- 💬 Prompts for missing arguments | ||
- ➡ Autocompletes arguments, options and values | ||
- 🤯 Fully typed | ||
- ⚡ Uses the power of `yargs` & `inquirer` | ||
- ⚡ Uses the power of `yargs` and `inquirer` | ||
It's built in TypeScript and while it's of course possible to write your app | ||
with JavaScript, you're missing out on some very handy type hints. | ||
It's built-in TypeScript to provide you with some very handy type hints. | ||
We don't have a generator, auto-updater and we don't make any decisions for you | ||
(apart from using inquirer for prompts). This makes bandersnatch pretty easy and | ||
intuitive to work with. | ||
Bandersnatch is not designed to be used as a full CLI framework like oclif, | ||
and tries to minimize the assumptions made about your program to make | ||
bandersnatch easy and intuitive to work with. | ||
@@ -34,3 +34,2 @@ ## Table of contents | ||
- [Getting started](#getting-started) | ||
@@ -41,2 +40,4 @@ - [Installation](#installation) | ||
- [Prompt](#prompt) | ||
- [Principles](#principles) | ||
- [Output](#output) | ||
- [API](#api) | ||
@@ -49,6 +50,5 @@ - [`program(description)`](#programdescription) | ||
- [`program.withVersion()`](#programwithversion) | ||
- [`program.fail(function)`](#programfailfunction) | ||
- [`program.eval(command)`](#programevalcommand) | ||
- [`program.run(command)`](#programruncommand) | ||
- [`program.repl()`](#programrepl) | ||
- [`program.runOrRepl()`](#programrunorrepl) | ||
- [`command(name, description)`](#commandname-description) | ||
@@ -60,10 +60,2 @@ - [`command.argument(name, description, options)`](#commandargumentname-description-options) | ||
- [`command.action(function)`](#commandactionfunction) | ||
- [`runner`](#runner) | ||
- [`runner.print(printer)`](#runnerprintprinter) | ||
- [`runner.then(function)`](#runnerthenfunction) | ||
- [`runner.catch(function)`](#runnercatchfunction) | ||
- [`runner.print(printer)`](#runnerprintprinter-1) | ||
- [`printer`](#printer) | ||
- [`printer.write(string)`](#printerwritestring) | ||
- [`printer.error(Error)`](#printererrorerror) | ||
- [Bundle](#bundle) | ||
@@ -87,3 +79,3 @@ - [Todo](#todo) | ||
Now create a simple app `echo.ts`: | ||
Now create a simple app `concat.ts`: | ||
@@ -93,9 +85,7 @@ ```ts | ||
const echo = command('echo', 'Echo something in the terminal') | ||
.argument('words', 'Say some kind words', { variadic: true }) | ||
.action(args => args.words.map(word => `${word}!`).join(' ')) | ||
const concat = command('concat', 'Concatenate input') | ||
.argument('input', 'List of inputs to concatenate', { variadic: true }) | ||
.action((args) => console.log(args.input.join(', ')) | ||
program() | ||
.default(echo) | ||
.run() | ||
program().default(concat).run() | ||
``` | ||
@@ -106,4 +96,4 @@ | ||
```bash | ||
$ ts-node echo.ts Hello world | ||
Hello! world! | ||
$ ts-node concat.ts Hello world | ||
Hello, world | ||
``` | ||
@@ -126,3 +116,3 @@ | ||
.option('color', 'Enables colorized output', { type: 'boolean' }) | ||
.action(async args => { | ||
.action(async (args) => { | ||
const json = JSON.parse(args.json) | ||
@@ -135,3 +125,3 @@ args.color | ||
process.argv.slice(2).length ? app.run() : app.repl() | ||
app.runOrRepl() | ||
``` | ||
@@ -166,12 +156,12 @@ | ||
.argument('name', "What's your name?", { | ||
prompt: true | ||
prompt: true, | ||
}) | ||
.argument('question', "What's your question?", { | ||
prompt: true | ||
prompt: true, | ||
}) | ||
.action(args => `Hi ${args.name}, the answer to "${args.question}" is 42.`) | ||
.action((args) => { | ||
console.log(`Hi ${args.name}, the answer to "${args.question}" is 42.`) | ||
}) | ||
program('Ask me anything') | ||
.default(cmd) | ||
.run() | ||
program('Ask me anything').default(cmd).run() | ||
``` | ||
@@ -183,4 +173,4 @@ | ||
$ ts-node ama.ts --name Joram | ||
? What's your question? What is everything in ASCII? | ||
Hi Joram, the answer to "What is everything in ASCII?" is 42. | ||
? What's your question? What is the meaning of life? | ||
Hi Joram, the answer to "What is the meaning of life?" is 42. | ||
``` | ||
@@ -194,2 +184,22 @@ | ||
## Principles | ||
In general, bandersnatch is designed to create [twelve-factor apps](https://12factor.net/). | ||
### Output | ||
Programs are encouraged to use the following conventions with regards to output, | ||
based on the [POSIX standard](https://pubs.opengroup.org/onlinepubs/9699919799/functions/stdin.html). | ||
- When a program is designed to be used in a scripting environment and its | ||
output should be available as stdin for other programs, use stdout for | ||
printing output and stderr for diagnostic output (e.g. progress and/or error | ||
messages). | ||
- When a program is designed to be used as a service (twelve-factor app), use | ||
stdout/stderr as a logging mechanism for informative messages/error and | ||
diagnostic messages. | ||
Bandersnatch has no built-in method for writing to stdout/stderr. Node.js | ||
provides [everything you need](https://nodejs.org/api/console.html). | ||
## API | ||
@@ -227,22 +237,13 @@ | ||
Adds `help` and `--help` to program which displays program usage information. | ||
Adds `help` and `--help` to the program which displays program usage information. | ||
#### `program.withVersion()` | ||
Adds `version` and `--version` to program which displays program version from | ||
Adds `version` and `--version` to the program which displays program version from | ||
package.json. | ||
#### `program.fail(function)` | ||
#### `program.run(command)` | ||
Use custom error handler. Function will be called with 4 arguments: | ||
- Message (string) will contain internal message about e.g. missing arguments | ||
- Error (Error) is only set when an error was explicitly thrown | ||
- Args (array) contains program arguments | ||
- Usage (string) contains usage information from --help | ||
#### `program.eval(command)` | ||
Uses process.argv or passed in command (string, optional) to match and execute | ||
command. Returns runner instance. | ||
command. Returns promise. | ||
@@ -252,8 +253,8 @@ ```ts | ||
.add(command(...)) | ||
.eval() | ||
.run() | ||
``` | ||
#### `program.run(command)` | ||
#### `program.repl()` | ||
Shorthand for `eval().print()`. | ||
Start a read-eval-print loop. | ||
@@ -263,8 +264,8 @@ ```ts | ||
.add(command(...)) | ||
.run() | ||
.repl() | ||
``` | ||
#### `program.repl()` | ||
#### `program.runOrRepl()` | ||
Start a read-eval-print loop. | ||
Invokes `run()` if arguments are passed in, `repl()` otherwise. | ||
@@ -274,3 +275,3 @@ ```ts | ||
.add(command(...)) | ||
.repl() | ||
.runOrRepl() | ||
``` | ||
@@ -282,4 +283,4 @@ | ||
- Name (string, optional) is used to invoke a command. When not used as default | ||
command, name is required. | ||
- Name (string, optional) is used to invoke a command. When not used as the | ||
default command, a name is required. | ||
- Description (string, optional) is used in --help output. | ||
@@ -294,7 +295,7 @@ | ||
- Description (string, optional) is used in --help output. | ||
- Options can be provided to change the behaviour of the | ||
- Options can be provided to change the behavior of the | ||
argument. Object with any of these keys: | ||
- `optional` (boolean) makes this argument optional. | ||
- `variadic` (boolean) eagerly take all remaining arguments and parse as array. | ||
Only valid for last argument. | ||
- `variadic` (boolean) eagerly take all remaining arguments and parse as an array. | ||
Only valid for the last argument. | ||
- ... | ||
@@ -308,3 +309,3 @@ | ||
- Description (string, optional) is used in --help output. | ||
- Options (OptionOptions) can be provided to change the behaviour of the | ||
- Options (OptionOptions) can be provided to change the behavior of the | ||
option. Object with any of these keys: | ||
@@ -324,58 +325,5 @@ - `alias` (string or array of strings) alias(es) for the option key. | ||
Function to execute when command is invoked. Is called with one argument: an | ||
Function to execute when the command is invoked. Is called with one argument: an | ||
object containing key/value pairs of parsed arguments and options. | ||
### `runner` | ||
Returned from `program().eval()`, can't be invoked directly. | ||
#### `runner.print(printer)` | ||
Prints resolved and rejected command executions to the terminal. Uses the | ||
built-in printer if invoked without arguments. | ||
```ts | ||
const runner = program() | ||
.default( | ||
command().action(() => { | ||
throw new Error('Test customer printer') | ||
}) | ||
) | ||
.eval() | ||
runner.print({ | ||
write(str: any) { | ||
str && console.log(str) | ||
}, | ||
error(error: any) { | ||
console.error(`${red('‼')} ${bgRed(error)}`) | ||
} | ||
}) | ||
``` | ||
#### `runner.then(function)` | ||
Function is invoked when command handler resolves. | ||
#### `runner.catch(function)` | ||
Function is invoked when command handler rejects. | ||
#### `runner.print(printer)` | ||
Attaches a printer to the runner. Uses a default printer unless called with a | ||
custom printer argument. | ||
### `printer` | ||
Used by runner, can't be invoked directly. | ||
#### `printer.write(string)` | ||
Handles output. Prints to stdout by default. | ||
#### `printer.error(Error)` | ||
Handles errors. Prints stack trace to stderr by default. | ||
## Bundle | ||
@@ -463,3 +411,3 @@ | ||
Next, we need to create a simple entrypoint `echo.js`, which can be run with | ||
Next, we need to create a simple entry point `echo.js`, which can be run with | ||
node: | ||
@@ -559,3 +507,3 @@ | ||
Copyright (c) 2019 Joram van den Boezem. Licensed under the MIT license. | ||
Copyright (c) 2020 Joram van den Boezem. Licensed under the MIT license. | ||
@@ -562,0 +510,0 @@ --- |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
51801
5
1071
12
490
+ Added@types/yargs@15.0.4(transitive)
+ Addedchalk@3.0.0(transitive)
+ Addedcli-width@2.2.1(transitive)
+ Addedinquirer@7.1.0(transitive)
+ Addedstring-argv@0.3.1(transitive)
+ Addedyargs@15.3.1(transitive)
- Removedansi-colors@^4.1.1
- Removed@types/yargs@13.0.12(transitive)
- Removedansi-colors@4.1.3(transitive)
- Removedchalk@4.1.2(transitive)
- Removedcli-width@3.0.0(transitive)
- Removedinquirer@7.3.3(transitive)
- Removedstring-argv@0.3.2(transitive)
- Removedyargs@15.4.1(transitive)
Updated@types/inquirer@6.5.0
Updated@types/yargs@15.0.4
Updatedinquirer@7.1.0
Updatedstring-argv@0.3.1
Updatedyargs@15.3.1