Comparing version 2.4.1 to 2.4.2
/// <reference types="node" /> | ||
/// <reference types="mocha" /> | ||
import { Readable, Writable } from "stream"; | ||
@@ -50,3 +49,2 @@ // ------------------------------------------------------------------------ | ||
// @ts-ignore | ||
// @ts-ignore | ||
declare const reducers: { | ||
@@ -450,3 +448,3 @@ setCandidateUsage: (state: RunState, segment: string, usage: string) => { | ||
static from<Context extends BaseContext = BaseContext>(commandClasses: CommandClass<Context>[]): Cli<Context>; | ||
constructor({ binaryLabel, binaryName$0, binaryVersion, enableColors }?: { | ||
constructor({ binaryLabel, binaryName: binaryNameOpt, binaryVersion, enableColors }?: { | ||
binaryLabel?: string; | ||
@@ -453,0 +451,0 @@ binaryName?: string; |
894
lib/index.js
@@ -5,125 +5,93 @@ 'use strict'; | ||
let Command = /** @class */ (() => { | ||
class Command { | ||
constructor() { | ||
/** | ||
* Predefined that will be set to true if `-h,--help` has been used, in which case `Command#execute` shouldn't be called. | ||
*/ | ||
this.help = false; | ||
} | ||
static getMeta(prototype) { | ||
const base = prototype.constructor; | ||
return base.meta = Object.prototype.hasOwnProperty.call(base, `meta`) ? base.meta : { | ||
definitions: [], | ||
transformers: [ | ||
(state, command) => { | ||
for (const { name, value } of state.options) { | ||
if (name === `-h` || name === `--help`) { | ||
// @ts-ignore: The property is meant to have been defined by the child class | ||
command.help = value; | ||
} | ||
} | ||
}, | ||
], | ||
}; | ||
} | ||
static resolveMeta(prototype) { | ||
const definitions = []; | ||
const transformers = []; | ||
for (let proto = prototype; proto instanceof Command; proto = proto.__proto__) { | ||
const meta = this.getMeta(proto); | ||
for (const definition of meta.definitions) | ||
definitions.push(definition); | ||
for (const transformer of meta.transformers) { | ||
transformers.push(transformer); | ||
} | ||
} | ||
return { | ||
definitions, | ||
transformers, | ||
}; | ||
} | ||
static registerDefinition(prototype, definition) { | ||
this.getMeta(prototype).definitions.push(definition); | ||
} | ||
static registerTransformer(prototype, transformer) { | ||
this.getMeta(prototype).transformers.push(transformer); | ||
} | ||
static addPath(...path) { | ||
this.Path(...path)(this.prototype, `execute`); | ||
} | ||
static addOption(name, builder) { | ||
builder(this.prototype, name); | ||
} | ||
class Command { | ||
constructor() { | ||
/** | ||
* Wrap the specified command to be attached to the given path on the command line. | ||
* The first path thus attached will be considered the "main" one, and all others will be aliases. | ||
* @param path The command path. | ||
* Predefined that will be set to true if `-h,--help` has been used, in which case `Command#execute` shouldn't be called. | ||
*/ | ||
static Path(...path) { | ||
return (prototype, propertyName) => { | ||
this.registerDefinition(prototype, command => { | ||
command.addPath(path); | ||
}); | ||
}; | ||
} | ||
/** | ||
* Register a boolean listener for the given option names. When Clipanion detects that this argument is present, the value will be set to false. The value won't be set unless the option is found, so you must remember to set it to an appropriate default value. | ||
* @param descriptor the option names. | ||
*/ | ||
static Boolean(descriptor, { hidden = false } = {}) { | ||
return (prototype, propertyName) => { | ||
const optNames = descriptor.split(`,`); | ||
this.registerDefinition(prototype, command => { | ||
command.addOption({ names: optNames, arity: 0, hidden, allowBinding: false }); | ||
}); | ||
this.registerTransformer(prototype, (state, command) => { | ||
this.help = false; | ||
} | ||
static getMeta(prototype) { | ||
const base = prototype.constructor; | ||
return base.meta = Object.prototype.hasOwnProperty.call(base, `meta`) ? base.meta : { | ||
definitions: [], | ||
transformers: [ | ||
(state, command) => { | ||
for (const { name, value } of state.options) { | ||
if (optNames.includes(name)) { | ||
if (name === `-h` || name === `--help`) { | ||
// @ts-ignore: The property is meant to have been defined by the child class | ||
command[propertyName] = value; | ||
command.help = value; | ||
} | ||
} | ||
}); | ||
}; | ||
}, | ||
], | ||
}; | ||
} | ||
static resolveMeta(prototype) { | ||
const definitions = []; | ||
const transformers = []; | ||
for (let proto = prototype; proto instanceof Command; proto = proto.__proto__) { | ||
const meta = this.getMeta(proto); | ||
for (const definition of meta.definitions) | ||
definitions.push(definition); | ||
for (const transformer of meta.transformers) { | ||
transformers.push(transformer); | ||
} | ||
} | ||
static String(descriptor = { required: true }, { tolerateBoolean = false, hidden = false } = {}) { | ||
return (prototype, propertyName) => { | ||
if (typeof descriptor === `string`) { | ||
const optNames = descriptor.split(`,`); | ||
this.registerDefinition(prototype, command => { | ||
// If tolerateBoolean is specified, the command will only accept a string value | ||
// using the bind syntax and will otherwise act like a boolean option | ||
command.addOption({ names: optNames, arity: tolerateBoolean ? 0 : 1, hidden }); | ||
}); | ||
this.registerTransformer(prototype, (state, command) => { | ||
for (const { name, value } of state.options) { | ||
if (optNames.includes(name)) { | ||
// @ts-ignore: The property is meant to have been defined by the child class | ||
command[propertyName] = value; | ||
} | ||
} | ||
}); | ||
return { | ||
definitions, | ||
transformers, | ||
}; | ||
} | ||
static registerDefinition(prototype, definition) { | ||
this.getMeta(prototype).definitions.push(definition); | ||
} | ||
static registerTransformer(prototype, transformer) { | ||
this.getMeta(prototype).transformers.push(transformer); | ||
} | ||
static addPath(...path) { | ||
this.Path(...path)(this.prototype, `execute`); | ||
} | ||
static addOption(name, builder) { | ||
builder(this.prototype, name); | ||
} | ||
/** | ||
* Wrap the specified command to be attached to the given path on the command line. | ||
* The first path thus attached will be considered the "main" one, and all others will be aliases. | ||
* @param path The command path. | ||
*/ | ||
static Path(...path) { | ||
return (prototype, propertyName) => { | ||
this.registerDefinition(prototype, command => { | ||
command.addPath(path); | ||
}); | ||
}; | ||
} | ||
/** | ||
* Register a boolean listener for the given option names. When Clipanion detects that this argument is present, the value will be set to false. The value won't be set unless the option is found, so you must remember to set it to an appropriate default value. | ||
* @param descriptor the option names. | ||
*/ | ||
static Boolean(descriptor, { hidden = false } = {}) { | ||
return (prototype, propertyName) => { | ||
const optNames = descriptor.split(`,`); | ||
this.registerDefinition(prototype, command => { | ||
command.addOption({ names: optNames, arity: 0, hidden, allowBinding: false }); | ||
}); | ||
this.registerTransformer(prototype, (state, command) => { | ||
for (const { name, value } of state.options) { | ||
if (optNames.includes(name)) { | ||
// @ts-ignore: The property is meant to have been defined by the child class | ||
command[propertyName] = value; | ||
} | ||
} | ||
else { | ||
this.registerDefinition(prototype, command => { | ||
command.addPositional({ name: propertyName, required: descriptor.required }); | ||
}); | ||
this.registerTransformer(prototype, (state, command) => { | ||
if (state.positionals.length > 0) { | ||
// @ts-ignore: The property is meant to have been defined by the child class | ||
command[propertyName] = state.positionals.shift().value; | ||
} | ||
}); | ||
} | ||
}; | ||
} | ||
/** | ||
* Register a listener that looks for an option and its followup argument. When Clipanion detects that this argument is present, the value will be pushed into the array represented in the property. | ||
*/ | ||
static Array(descriptor, { hidden = false } = {}) { | ||
return (prototype, propertyName) => { | ||
}); | ||
}; | ||
} | ||
static String(descriptor = { required: true }, { tolerateBoolean = false, hidden = false } = {}) { | ||
return (prototype, propertyName) => { | ||
if (typeof descriptor === `string`) { | ||
const optNames = descriptor.split(`,`); | ||
this.registerDefinition(prototype, command => { | ||
command.addOption({ names: optNames, arity: 1, hidden }); | ||
// If tolerateBoolean is specified, the command will only accept a string value | ||
// using the bind syntax and will otherwise act like a boolean option | ||
command.addOption({ names: optNames, arity: tolerateBoolean ? 0 : 1, hidden }); | ||
}); | ||
@@ -134,84 +102,113 @@ this.registerTransformer(prototype, (state, command) => { | ||
// @ts-ignore: The property is meant to have been defined by the child class | ||
command[propertyName] = command[propertyName] || []; | ||
// @ts-ignore: The property is meant to have been defined by the child class | ||
command[propertyName].push(value); | ||
command[propertyName] = value; | ||
} | ||
} | ||
}); | ||
}; | ||
} | ||
static Rest({ required = 0 } = {}) { | ||
return (prototype, propertyName) => { | ||
} | ||
else { | ||
this.registerDefinition(prototype, command => { | ||
command.addRest({ name: propertyName, required }); | ||
command.addPositional({ name: propertyName, required: descriptor.required }); | ||
}); | ||
this.registerTransformer(prototype, (state, command) => { | ||
// @ts-ignore: The property is meant to have been defined by the child class | ||
command[propertyName] = state.positionals.map(({ value }) => value); | ||
if (state.positionals.length > 0) { | ||
// @ts-ignore: The property is meant to have been defined by the child class | ||
command[propertyName] = state.positionals.shift().value; | ||
} | ||
}); | ||
}; | ||
} | ||
/** | ||
* Register a listener that takes all the arguments remaining (including options and such) and store them into the selected property. | ||
* Note that all methods affecting positional arguments are evaluated in the definition order; don't mess with it (for example sorting your properties in ascendent order might have adverse results). | ||
*/ | ||
static Proxy({ required = 0 } = {}) { | ||
return (prototype, propertyName) => { | ||
this.registerDefinition(prototype, command => { | ||
command.addProxy({ required }); | ||
}); | ||
this.registerTransformer(prototype, (state, command) => { | ||
// @ts-ignore: The property is meant to have been defined by the child class | ||
command[propertyName] = state.positionals.map(({ value }) => value); | ||
}); | ||
}; | ||
} | ||
/** | ||
* Defines the usage information for the given command. | ||
* @param usage | ||
*/ | ||
static Usage(usage) { | ||
return usage; | ||
} | ||
/** | ||
* Defines the schema for the given command. | ||
* @param schema | ||
*/ | ||
static Schema(schema) { | ||
return schema; | ||
} | ||
async validateAndExecute() { | ||
const commandClass = this.constructor; | ||
const schema = commandClass.schema; | ||
if (typeof schema !== `undefined`) { | ||
try { | ||
await schema.validate(this); | ||
} | ||
}; | ||
} | ||
/** | ||
* Register a listener that looks for an option and its followup argument. When Clipanion detects that this argument is present, the value will be pushed into the array represented in the property. | ||
*/ | ||
static Array(descriptor, { hidden = false } = {}) { | ||
return (prototype, propertyName) => { | ||
const optNames = descriptor.split(`,`); | ||
this.registerDefinition(prototype, command => { | ||
command.addOption({ names: optNames, arity: 1, hidden }); | ||
}); | ||
this.registerTransformer(prototype, (state, command) => { | ||
for (const { name, value } of state.options) { | ||
if (optNames.includes(name)) { | ||
// @ts-ignore: The property is meant to have been defined by the child class | ||
command[propertyName] = command[propertyName] || []; | ||
// @ts-ignore: The property is meant to have been defined by the child class | ||
command[propertyName].push(value); | ||
} | ||
} | ||
catch (error) { | ||
if (error.name === `ValidationError`) | ||
error.clipanion = { type: `usage` }; | ||
throw error; | ||
} | ||
}); | ||
}; | ||
} | ||
static Rest({ required = 0 } = {}) { | ||
return (prototype, propertyName) => { | ||
this.registerDefinition(prototype, command => { | ||
command.addRest({ name: propertyName, required }); | ||
}); | ||
this.registerTransformer(prototype, (state, command) => { | ||
// @ts-ignore: The property is meant to have been defined by the child class | ||
command[propertyName] = state.positionals.map(({ value }) => value); | ||
}); | ||
}; | ||
} | ||
/** | ||
* Register a listener that takes all the arguments remaining (including options and such) and store them into the selected property. | ||
* Note that all methods affecting positional arguments are evaluated in the definition order; don't mess with it (for example sorting your properties in ascendent order might have adverse results). | ||
*/ | ||
static Proxy({ required = 0 } = {}) { | ||
return (prototype, propertyName) => { | ||
this.registerDefinition(prototype, command => { | ||
command.addProxy({ required }); | ||
}); | ||
this.registerTransformer(prototype, (state, command) => { | ||
// @ts-ignore: The property is meant to have been defined by the child class | ||
command[propertyName] = state.positionals.map(({ value }) => value); | ||
}); | ||
}; | ||
} | ||
/** | ||
* Defines the usage information for the given command. | ||
* @param usage | ||
*/ | ||
static Usage(usage) { | ||
return usage; | ||
} | ||
/** | ||
* Defines the schema for the given command. | ||
* @param schema | ||
*/ | ||
static Schema(schema) { | ||
return schema; | ||
} | ||
async validateAndExecute() { | ||
const commandClass = this.constructor; | ||
const schema = commandClass.schema; | ||
if (typeof schema !== `undefined`) { | ||
try { | ||
await schema.validate(this); | ||
} | ||
const exitCode = await this.execute(); | ||
if (typeof exitCode !== `undefined`) { | ||
return exitCode; | ||
catch (error) { | ||
if (error.name === `ValidationError`) | ||
error.clipanion = { type: `usage` }; | ||
throw error; | ||
} | ||
else { | ||
return 0; | ||
} | ||
} | ||
const exitCode = await this.execute(); | ||
if (typeof exitCode !== `undefined`) { | ||
return exitCode; | ||
} | ||
else { | ||
return 0; | ||
} | ||
} | ||
/** | ||
* A list of useful semi-opinionated command entries that have to be registered manually. | ||
* | ||
* They cover the basic needs of most CLIs (e.g. help command, version command). | ||
* | ||
* @example | ||
* cli.register(Command.Entries.Help); | ||
* cli.register(Command.Entries.Version); | ||
*/ | ||
Command.Entries = {}; | ||
return Command; | ||
})(); | ||
} | ||
/** | ||
* A list of useful semi-opinionated command entries that have to be registered manually. | ||
* | ||
* They cover the basic needs of most CLIs (e.g. help command, version command). | ||
* | ||
* @example | ||
* cli.register(Command.Entries.Help); | ||
* cli.register(Command.Entries.Version); | ||
*/ | ||
Command.Entries = {}; | ||
@@ -240,28 +237,22 @@ /*! ***************************************************************************** | ||
let HelpCommand = /** @class */ (() => { | ||
class HelpCommand extends Command { | ||
async execute() { | ||
this.context.stdout.write(this.cli.usage(null)); | ||
} | ||
class HelpCommand extends Command { | ||
async execute() { | ||
this.context.stdout.write(this.cli.usage(null)); | ||
} | ||
__decorate([ | ||
Command.Path(`--help`), | ||
Command.Path(`-h`) | ||
], HelpCommand.prototype, "execute", null); | ||
return HelpCommand; | ||
})(); | ||
} | ||
__decorate([ | ||
Command.Path(`--help`), | ||
Command.Path(`-h`) | ||
], HelpCommand.prototype, "execute", null); | ||
let VersionCommand = /** @class */ (() => { | ||
class VersionCommand extends Command { | ||
async execute() { | ||
var _a; | ||
this.context.stdout.write(`${(_a = this.cli.binaryVersion) !== null && _a !== void 0 ? _a : `<unknown>`}\n`); | ||
} | ||
class VersionCommand extends Command { | ||
async execute() { | ||
var _a; | ||
this.context.stdout.write(`${(_a = this.cli.binaryVersion) !== null && _a !== void 0 ? _a : `<unknown>`}\n`); | ||
} | ||
__decorate([ | ||
Command.Path(`--version`), | ||
Command.Path(`-v`) | ||
], VersionCommand.prototype, "execute", null); | ||
return VersionCommand; | ||
})(); | ||
} | ||
__decorate([ | ||
Command.Path(`--version`), | ||
Command.Path(`-v`) | ||
], VersionCommand.prototype, "execute", null); | ||
@@ -1146,279 +1137,276 @@ const NODE_INITIAL = 0; | ||
*/ | ||
let Cli = /** @class */ (() => { | ||
class Cli { | ||
constructor({ binaryLabel, binaryName = `...`, binaryVersion, enableColors = getDefaultColorSettings() } = {}) { | ||
this.registrations = new Map(); | ||
this.builder = new CliBuilder({ binaryName }); | ||
this.binaryLabel = binaryLabel; | ||
this.binaryName = binaryName; | ||
this.binaryVersion = binaryVersion; | ||
this.enableColors = enableColors; | ||
class Cli { | ||
constructor({ binaryLabel, binaryName: binaryNameOpt = `...`, binaryVersion, enableColors = getDefaultColorSettings() } = {}) { | ||
this.registrations = new Map(); | ||
this.builder = new CliBuilder({ binaryName: binaryNameOpt }); | ||
this.binaryLabel = binaryLabel; | ||
this.binaryName = binaryNameOpt; | ||
this.binaryVersion = binaryVersion; | ||
this.enableColors = enableColors; | ||
} | ||
/** | ||
* Creates a new Cli and registers all commands passed as parameters. | ||
* | ||
* @param commandClasses The Commands to register | ||
* @returns The created `Cli` instance | ||
*/ | ||
static from(commandClasses) { | ||
const cli = new Cli(); | ||
for (const commandClass of commandClasses) | ||
cli.register(commandClass); | ||
return cli; | ||
} | ||
/** | ||
* Registers a command inside the CLI. | ||
*/ | ||
register(commandClass) { | ||
const commandBuilder = this.builder.command(); | ||
this.registrations.set(commandClass, commandBuilder.cliIndex); | ||
const { definitions } = commandClass.resolveMeta(commandClass.prototype); | ||
for (const definition of definitions) | ||
definition(commandBuilder); | ||
commandBuilder.setContext({ | ||
commandClass, | ||
}); | ||
} | ||
process(input) { | ||
const { contexts, process } = this.builder.compile(); | ||
const state = process(input); | ||
switch (state.selectedIndex) { | ||
case HELP_COMMAND_INDEX: | ||
{ | ||
return HelpCommand$1.from(state, this, contexts); | ||
} | ||
default: | ||
{ | ||
const { commandClass } = contexts[state.selectedIndex]; | ||
const command = new commandClass(); | ||
command.path = state.path; | ||
const { transformers } = commandClass.resolveMeta(commandClass.prototype); | ||
for (const transformer of transformers) | ||
transformer(state, command); | ||
return command; | ||
} | ||
} | ||
/** | ||
* Creates a new Cli and registers all commands passed as parameters. | ||
* | ||
* @param commandClasses The Commands to register | ||
* @returns The created `Cli` instance | ||
*/ | ||
static from(commandClasses) { | ||
const cli = new Cli(); | ||
for (const commandClass of commandClasses) | ||
cli.register(commandClass); | ||
return cli; | ||
} | ||
async run(input, context) { | ||
let command; | ||
if (!Array.isArray(input)) { | ||
command = input; | ||
} | ||
/** | ||
* Registers a command inside the CLI. | ||
*/ | ||
register(commandClass) { | ||
const commandBuilder = this.builder.command(); | ||
this.registrations.set(commandClass, commandBuilder.cliIndex); | ||
const { definitions } = commandClass.resolveMeta(commandClass.prototype); | ||
for (const definition of definitions) | ||
definition(commandBuilder); | ||
commandBuilder.setContext({ | ||
commandClass, | ||
}); | ||
} | ||
process(input) { | ||
const { contexts, process } = this.builder.compile(); | ||
const state = process(input); | ||
switch (state.selectedIndex) { | ||
case HELP_COMMAND_INDEX: | ||
{ | ||
return HelpCommand$1.from(state, this, contexts); | ||
} | ||
default: | ||
{ | ||
const { commandClass } = contexts[state.selectedIndex]; | ||
const command = new commandClass(); | ||
command.path = state.path; | ||
const { transformers } = commandClass.resolveMeta(commandClass.prototype); | ||
for (const transformer of transformers) | ||
transformer(state, command); | ||
return command; | ||
} | ||
} | ||
} | ||
async run(input, context) { | ||
let command; | ||
if (!Array.isArray(input)) { | ||
command = input; | ||
} | ||
else { | ||
try { | ||
command = this.process(input); | ||
} | ||
catch (error) { | ||
context.stdout.write(this.error(error)); | ||
return 1; | ||
} | ||
} | ||
if (command.help) { | ||
context.stdout.write(this.usage(command, { detailed: true })); | ||
return 0; | ||
} | ||
command.context = context; | ||
command.cli = { | ||
binaryLabel: this.binaryLabel, | ||
binaryName: this.binaryName, | ||
binaryVersion: this.binaryVersion, | ||
definitions: () => this.definitions(), | ||
error: (error, opts) => this.error(error, opts), | ||
process: input => this.process(input), | ||
run: (input, subContext) => this.run(input, Object.assign(Object.assign({}, context), subContext)), | ||
usage: (command, opts) => this.usage(command, opts), | ||
}; | ||
let exitCode; | ||
else { | ||
try { | ||
exitCode = await command.validateAndExecute(); | ||
command = this.process(input); | ||
} | ||
catch (error) { | ||
context.stdout.write(this.error(error, { command })); | ||
context.stdout.write(this.error(error)); | ||
return 1; | ||
} | ||
return exitCode; | ||
} | ||
/** | ||
* Runs a command and exits the current `process` with the exit code returned by the command. | ||
* | ||
* @param input An array containing the name of the command and its arguments. | ||
* | ||
* @example | ||
* cli.runExit(process.argv.slice(2), Cli.defaultContext) | ||
*/ | ||
async runExit(input, context) { | ||
process.exitCode = await this.run(input, context); | ||
if (command.help) { | ||
context.stdout.write(this.usage(command, { detailed: true })); | ||
return 0; | ||
} | ||
suggest(input, partial) { | ||
const { contexts, process, suggest } = this.builder.compile(); | ||
return suggest(input, partial); | ||
command.context = context; | ||
command.cli = { | ||
binaryLabel: this.binaryLabel, | ||
binaryName: this.binaryName, | ||
binaryVersion: this.binaryVersion, | ||
definitions: () => this.definitions(), | ||
error: (error, opts) => this.error(error, opts), | ||
process: input => this.process(input), | ||
run: (input, subContext) => this.run(input, Object.assign(Object.assign({}, context), subContext)), | ||
usage: (command, opts) => this.usage(command, opts), | ||
}; | ||
let exitCode; | ||
try { | ||
exitCode = await command.validateAndExecute(); | ||
} | ||
definitions({ colored = false } = {}) { | ||
const data = []; | ||
for (const [commandClass, number] of this.registrations) { | ||
catch (error) { | ||
context.stdout.write(this.error(error, { command })); | ||
return 1; | ||
} | ||
return exitCode; | ||
} | ||
/** | ||
* Runs a command and exits the current `process` with the exit code returned by the command. | ||
* | ||
* @param input An array containing the name of the command and its arguments. | ||
* | ||
* @example | ||
* cli.runExit(process.argv.slice(2), Cli.defaultContext) | ||
*/ | ||
async runExit(input, context) { | ||
process.exitCode = await this.run(input, context); | ||
} | ||
suggest(input, partial) { | ||
const { contexts, process, suggest } = this.builder.compile(); | ||
return suggest(input, partial); | ||
} | ||
definitions({ colored = false } = {}) { | ||
const data = []; | ||
for (const [commandClass, number] of this.registrations) { | ||
if (typeof commandClass.usage === `undefined`) | ||
continue; | ||
const path = this.getUsageByIndex(number, { detailed: false }); | ||
const usage = this.getUsageByIndex(number, { detailed: true }); | ||
const category = typeof commandClass.usage.category !== `undefined` | ||
? formatMarkdownish(commandClass.usage.category, { format: this.format(colored), paragraphs: false }) | ||
: undefined; | ||
const description = typeof commandClass.usage.description !== `undefined` | ||
? formatMarkdownish(commandClass.usage.description, { format: this.format(colored), paragraphs: false }) | ||
: undefined; | ||
const details = typeof commandClass.usage.details !== `undefined` | ||
? formatMarkdownish(commandClass.usage.details, { format: this.format(colored), paragraphs: true }) | ||
: undefined; | ||
const examples = typeof commandClass.usage.examples !== `undefined` | ||
? commandClass.usage.examples.map(([label, cli]) => [formatMarkdownish(label, { format: this.format(colored), paragraphs: false }), cli.replace(/\$0/g, this.binaryName)]) | ||
: undefined; | ||
data.push({ path, usage, category, description, details, examples }); | ||
} | ||
return data; | ||
} | ||
usage(command = null, { colored, detailed = false, prefix = `$ ` } = {}) { | ||
// @ts-ignore | ||
const commandClass = command !== null && typeof command.getMeta === `undefined` | ||
? command.constructor | ||
: command; | ||
let result = ``; | ||
if (!commandClass) { | ||
const commandsByCategories = new Map(); | ||
for (const [commandClass, number] of this.registrations.entries()) { | ||
if (typeof commandClass.usage === `undefined`) | ||
continue; | ||
const path = this.getUsageByIndex(number, { detailed: false }); | ||
const usage = this.getUsageByIndex(number, { detailed: true }); | ||
const category = typeof commandClass.usage.category !== `undefined` | ||
? formatMarkdownish(commandClass.usage.category, { format: this.format(colored), paragraphs: false }) | ||
: undefined; | ||
const description = typeof commandClass.usage.description !== `undefined` | ||
? formatMarkdownish(commandClass.usage.description, { format: this.format(colored), paragraphs: false }) | ||
: undefined; | ||
const details = typeof commandClass.usage.details !== `undefined` | ||
? formatMarkdownish(commandClass.usage.details, { format: this.format(colored), paragraphs: true }) | ||
: undefined; | ||
const examples = typeof commandClass.usage.examples !== `undefined` | ||
? commandClass.usage.examples.map(([label, cli]) => [formatMarkdownish(label, { format: this.format(colored), paragraphs: false }), cli.replace(/\$0/g, this.binaryName)]) | ||
: undefined; | ||
data.push({ path, usage, category, description, details, examples }); | ||
: null; | ||
let categoryCommands = commandsByCategories.get(category); | ||
if (typeof categoryCommands === `undefined`) | ||
commandsByCategories.set(category, categoryCommands = []); | ||
const usage = this.getUsageByIndex(number); | ||
categoryCommands.push({ commandClass, usage }); | ||
} | ||
return data; | ||
} | ||
usage(command = null, { colored, detailed = false, prefix = `$ ` } = {}) { | ||
// @ts-ignore | ||
const commandClass = command !== null && typeof command.getMeta === `undefined` | ||
? command.constructor | ||
: command; | ||
let result = ``; | ||
if (!commandClass) { | ||
const commandsByCategories = new Map(); | ||
for (const [commandClass, number] of this.registrations.entries()) { | ||
if (typeof commandClass.usage === `undefined`) | ||
continue; | ||
const category = typeof commandClass.usage.category !== `undefined` | ||
? formatMarkdownish(commandClass.usage.category, { format: this.format(colored), paragraphs: false }) | ||
: null; | ||
let categoryCommands = commandsByCategories.get(category); | ||
if (typeof categoryCommands === `undefined`) | ||
commandsByCategories.set(category, categoryCommands = []); | ||
const usage = this.getUsageByIndex(number); | ||
categoryCommands.push({ commandClass, usage }); | ||
} | ||
const categoryNames = Array.from(commandsByCategories.keys()).sort((a, b) => { | ||
if (a === null) | ||
return -1; | ||
if (b === null) | ||
return +1; | ||
return a.localeCompare(b, `en`, { usage: `sort`, caseFirst: `upper` }); | ||
const categoryNames = Array.from(commandsByCategories.keys()).sort((a, b) => { | ||
if (a === null) | ||
return -1; | ||
if (b === null) | ||
return +1; | ||
return a.localeCompare(b, `en`, { usage: `sort`, caseFirst: `upper` }); | ||
}); | ||
const hasLabel = typeof this.binaryLabel !== `undefined`; | ||
const hasVersion = typeof this.binaryVersion !== `undefined`; | ||
if (hasLabel || hasVersion) { | ||
if (hasLabel && hasVersion) | ||
result += `${this.format(colored).bold(`${this.binaryLabel} - ${this.binaryVersion}`)}\n\n`; | ||
else if (hasLabel) | ||
result += `${this.format(colored).bold(`${this.binaryLabel}`)}\n`; | ||
else | ||
result += `${this.format(colored).bold(`${this.binaryVersion}`)}\n`; | ||
result += ` ${this.format(colored).bold(prefix)}${this.binaryName} <command>\n`; | ||
} | ||
else { | ||
result += `${this.format(colored).bold(prefix)}${this.binaryName} <command>\n`; | ||
} | ||
for (let categoryName of categoryNames) { | ||
const commands = commandsByCategories.get(categoryName).slice().sort((a, b) => { | ||
return a.usage.localeCompare(b.usage, `en`, { usage: `sort`, caseFirst: `upper` }); | ||
}); | ||
const hasLabel = typeof this.binaryLabel !== `undefined`; | ||
const hasVersion = typeof this.binaryVersion !== `undefined`; | ||
if (hasLabel || hasVersion) { | ||
if (hasLabel && hasVersion) | ||
result += `${this.format(colored).bold(`${this.binaryLabel} - ${this.binaryVersion}`)}\n\n`; | ||
else if (hasLabel) | ||
result += `${this.format(colored).bold(`${this.binaryLabel}`)}\n`; | ||
else | ||
result += `${this.format(colored).bold(`${this.binaryVersion}`)}\n`; | ||
result += ` ${this.format(colored).bold(prefix)}${this.binaryName} <command>\n`; | ||
} | ||
else { | ||
result += `${this.format(colored).bold(prefix)}${this.binaryName} <command>\n`; | ||
} | ||
for (let categoryName of categoryNames) { | ||
const commands = commandsByCategories.get(categoryName).slice().sort((a, b) => { | ||
return a.usage.localeCompare(b.usage, `en`, { usage: `sort`, caseFirst: `upper` }); | ||
}); | ||
const header = categoryName !== null | ||
? categoryName.trim() | ||
: `Where <command> is one of`; | ||
const header = categoryName !== null | ||
? categoryName.trim() | ||
: `Where <command> is one of`; | ||
result += `\n`; | ||
result += `${this.format(colored).bold(`${header}:`)}\n`; | ||
for (let { commandClass, usage } of commands) { | ||
const doc = commandClass.usage.description || `undocumented`; | ||
result += `\n`; | ||
result += `${this.format(colored).bold(`${header}:`)}\n`; | ||
for (let { commandClass, usage } of commands) { | ||
const doc = commandClass.usage.description || `undocumented`; | ||
result += `\n`; | ||
result += ` ${this.format(colored).bold(usage)}\n`; | ||
result += ` ${formatMarkdownish(doc, { format: this.format(colored), paragraphs: false })}`; | ||
} | ||
result += ` ${this.format(colored).bold(usage)}\n`; | ||
result += ` ${formatMarkdownish(doc, { format: this.format(colored), paragraphs: false })}`; | ||
} | ||
result += `\n`; | ||
result += formatMarkdownish(`You can also print more details about any of these commands by calling them after adding the \`-h,--help\` flag right after the command name.`, { format: this.format(colored), paragraphs: true }); | ||
} | ||
result += `\n`; | ||
result += formatMarkdownish(`You can also print more details about any of these commands by calling them after adding the \`-h,--help\` flag right after the command name.`, { format: this.format(colored), paragraphs: true }); | ||
} | ||
else { | ||
if (!detailed) { | ||
result += `${this.format(colored).bold(prefix)}${this.getUsageByRegistration(commandClass)}\n`; | ||
} | ||
else { | ||
if (!detailed) { | ||
result += `${this.format(colored).bold(prefix)}${this.getUsageByRegistration(commandClass)}\n`; | ||
const { description = ``, details = ``, examples = [], } = commandClass.usage || {}; | ||
if (description !== ``) { | ||
result += formatMarkdownish(description, { format: this.format(colored), paragraphs: false }).replace(/^./, $0 => $0.toUpperCase()); | ||
result += `\n`; | ||
} | ||
else { | ||
const { description = ``, details = ``, examples = [], } = commandClass.usage || {}; | ||
if (description !== ``) { | ||
result += formatMarkdownish(description, { format: this.format(colored), paragraphs: false }).replace(/^./, $0 => $0.toUpperCase()); | ||
if (details !== `` || examples.length > 0) { | ||
result += `${this.format(colored).bold(`Usage:`)}\n`; | ||
result += `\n`; | ||
} | ||
result += `${this.format(colored).bold(prefix)}${this.getUsageByRegistration(commandClass)}\n`; | ||
if (details !== ``) { | ||
result += `\n`; | ||
result += `${this.format(colored).bold(`Details:`)}\n`; | ||
result += `\n`; | ||
result += formatMarkdownish(details, { format: this.format(colored), paragraphs: true }); | ||
} | ||
if (examples.length > 0) { | ||
result += `\n`; | ||
result += `${this.format(colored).bold(`Examples:`)}\n`; | ||
for (let [description, example] of examples) { | ||
result += `\n`; | ||
result += formatMarkdownish(description, { format: this.format(colored), paragraphs: false }); | ||
result += example | ||
.replace(/^/m, ` ${this.format(colored).bold(prefix)}`) | ||
.replace(/\$0/g, this.binaryName) | ||
+ `\n`; | ||
} | ||
if (details !== `` || examples.length > 0) { | ||
result += `${this.format(colored).bold(`Usage:`)}\n`; | ||
result += `\n`; | ||
} | ||
result += `${this.format(colored).bold(prefix)}${this.getUsageByRegistration(commandClass)}\n`; | ||
if (details !== ``) { | ||
result += `\n`; | ||
result += `${this.format(colored).bold(`Details:`)}\n`; | ||
result += `\n`; | ||
result += formatMarkdownish(details, { format: this.format(colored), paragraphs: true }); | ||
} | ||
if (examples.length > 0) { | ||
result += `\n`; | ||
result += `${this.format(colored).bold(`Examples:`)}\n`; | ||
for (let [description, example] of examples) { | ||
result += `\n`; | ||
result += formatMarkdownish(description, { format: this.format(colored), paragraphs: false }); | ||
result += example | ||
.replace(/^/m, ` ${this.format(colored).bold(prefix)}`) | ||
.replace(/\$0/g, this.binaryName) | ||
+ `\n`; | ||
} | ||
} | ||
} | ||
} | ||
return result; | ||
} | ||
error(error, { colored, command = null } = {}) { | ||
if (!(error instanceof Error)) | ||
error = new Error(`Execution failed with a non-error rejection (rejected value: ${JSON.stringify(error)})`); | ||
let result = ``; | ||
let name = error.name.replace(/([a-z])([A-Z])/g, `$1 $2`); | ||
if (name === `Error`) | ||
name = `Internal Error`; | ||
result += `${this.format(colored).error(name)}: ${error.message}\n`; | ||
// @ts-ignore | ||
const meta = error.clipanion; | ||
if (typeof meta !== `undefined`) { | ||
if (meta.type === `usage`) { | ||
result += `\n`; | ||
result += this.usage(command); | ||
} | ||
return result; | ||
} | ||
error(error, { colored, command = null } = {}) { | ||
if (!(error instanceof Error)) | ||
error = new Error(`Execution failed with a non-error rejection (rejected value: ${JSON.stringify(error)})`); | ||
let result = ``; | ||
let name = error.name.replace(/([a-z])([A-Z])/g, `$1 $2`); | ||
if (name === `Error`) | ||
name = `Internal Error`; | ||
result += `${this.format(colored).error(name)}: ${error.message}\n`; | ||
// @ts-ignore | ||
const meta = error.clipanion; | ||
if (typeof meta !== `undefined`) { | ||
if (meta.type === `usage`) { | ||
result += `\n`; | ||
result += this.usage(command); | ||
} | ||
else { | ||
if (error.stack) { | ||
result += `${error.stack.replace(/^.*\n/, ``)}\n`; | ||
} | ||
} | ||
else { | ||
if (error.stack) { | ||
result += `${error.stack.replace(/^.*\n/, ``)}\n`; | ||
} | ||
return result; | ||
} | ||
getUsageByRegistration(klass, opts) { | ||
const index = this.registrations.get(klass); | ||
if (typeof index === `undefined`) | ||
throw new Error(`Assertion failed: Unregistered command`); | ||
return this.getUsageByIndex(index, opts); | ||
} | ||
getUsageByIndex(n, opts) { | ||
return this.builder.getBuilderByIndex(n).usage(opts); | ||
} | ||
format(colored = this.enableColors) { | ||
return colored ? richFormat : textFormat; | ||
} | ||
return result; | ||
} | ||
/** | ||
* The default context of the CLI. | ||
* | ||
* Contains the stdio of the current `process`. | ||
*/ | ||
Cli.defaultContext = { | ||
stdin: process.stdin, | ||
stdout: process.stdout, | ||
stderr: process.stderr, | ||
}; | ||
return Cli; | ||
})(); | ||
getUsageByRegistration(klass, opts) { | ||
const index = this.registrations.get(klass); | ||
if (typeof index === `undefined`) | ||
throw new Error(`Assertion failed: Unregistered command`); | ||
return this.getUsageByIndex(index, opts); | ||
} | ||
getUsageByIndex(n, opts) { | ||
return this.builder.getBuilderByIndex(n).usage(opts); | ||
} | ||
format(colored = this.enableColors) { | ||
return colored ? richFormat : textFormat; | ||
} | ||
} | ||
/** | ||
* The default context of the CLI. | ||
* | ||
* Contains the stdio of the current `process`. | ||
*/ | ||
Cli.defaultContext = { | ||
stdin: process.stdin, | ||
stdout: process.stdout, | ||
stderr: process.stderr, | ||
}; | ||
@@ -1425,0 +1413,0 @@ Command.Entries.Help = HelpCommand; |
{ | ||
"name": "clipanion", | ||
"version": "2.4.1", | ||
"version": "2.4.2", | ||
"main": "lib/index", | ||
"license": "MIT", | ||
"repository": { | ||
"url": "https://github.com/arcanis/clipanion", | ||
"type": "git" | ||
}, | ||
"devDependencies": { | ||
@@ -10,14 +14,14 @@ "@types/chai": "^4.2.11", | ||
"@types/mocha": "^7.0.2", | ||
"@types/node": "^14.0.1", | ||
"@types/yup": "^0.28.3", | ||
"@wessberg/rollup-plugin-ts": "^1.2.24", | ||
"@types/node": "^14.0.13", | ||
"@types/yup": "^0.29.3", | ||
"@wessberg/rollup-plugin-ts": "^1.2.25", | ||
"chai": "^4.2.0", | ||
"chai-as-promised": "^7.1.1", | ||
"get-stream": "^5.1.0", | ||
"mocha": "^7.1.2", | ||
"rollup": "^2.12.0", | ||
"ts-node": "^8.10.1", | ||
"mocha": "^8.0.1", | ||
"rollup": "^2.16.1", | ||
"ts-node": "^8.10.2", | ||
"tslib": "^2.0.0", | ||
"typescript": "^3.9.2", | ||
"yup": "^0.28.5" | ||
"typescript": "^3.9.5", | ||
"yup": "^0.29.1" | ||
}, | ||
@@ -24,0 +28,0 @@ "scripts": { |
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
0
0
156158
3514