| import { Command } from '../Command'; | ||
| export declare class HelpCommand extends Command { | ||
| execute(): Promise<void>; | ||
| } |
| "use strict"; | ||
| var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { | ||
| var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; | ||
| if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); | ||
| else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; | ||
| return c > 3 && r && Object.defineProperty(target, key, r), r; | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.HelpCommand = void 0; | ||
| const Command_1 = require("../Command"); | ||
| let HelpCommand = /** @class */ (() => { | ||
| class HelpCommand extends Command_1.Command { | ||
| async execute() { | ||
| this.context.stdout.write(this.cli.usage(null)); | ||
| } | ||
| } | ||
| __decorate([ | ||
| Command_1.Command.Path(`--help`), | ||
| Command_1.Command.Path(`-h`) | ||
| ], HelpCommand.prototype, "execute", null); | ||
| return HelpCommand; | ||
| })(); | ||
| exports.HelpCommand = HelpCommand; |
| import { Command } from '../Command'; | ||
| export declare class VersionCommand extends Command { | ||
| execute(): Promise<void>; | ||
| } |
| "use strict"; | ||
| var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { | ||
| var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; | ||
| if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); | ||
| else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; | ||
| return c > 3 && r && Object.defineProperty(target, key, r), r; | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.VersionCommand = void 0; | ||
| const Command_1 = require("../Command"); | ||
| let VersionCommand = /** @class */ (() => { | ||
| class VersionCommand extends Command_1.Command { | ||
| async execute() { | ||
| var _a; | ||
| this.context.stdout.write(`${(_a = this.cli.binaryVersion) !== null && _a !== void 0 ? _a : `<unknown>`}\n`); | ||
| } | ||
| } | ||
| __decorate([ | ||
| Command_1.Command.Path(`--version`), | ||
| Command_1.Command.Path(`-v`) | ||
| ], VersionCommand.prototype, "execute", null); | ||
| return VersionCommand; | ||
| })(); | ||
| exports.VersionCommand = VersionCommand; |
+110
-13
| /// <reference types="node" /> | ||
| import { Readable, Writable } from 'stream'; | ||
| import { CommandClass, Command } from './Command'; | ||
| import { CommandClass, Command, Definition } from './Command'; | ||
| /** | ||
| * The base context of the CLI. | ||
| * | ||
| * All Contexts have to extend it. | ||
| */ | ||
| export declare type BaseContext = { | ||
| /** | ||
| * The input stream of the CLI. | ||
| * | ||
| * @default | ||
| * process.stdin | ||
| */ | ||
| stdin: Readable; | ||
| /** | ||
| * The output stream of the CLI. | ||
| * | ||
| * @default | ||
| * process.stdout | ||
| */ | ||
| stdout: Writable; | ||
| /** | ||
| * The error stream of the CLI. | ||
| * | ||
| * @default | ||
| * process.stderr | ||
| */ | ||
| stderr: Writable; | ||
@@ -13,8 +36,57 @@ }; | ||
| export declare type MiniCli<Context extends BaseContext> = { | ||
| definitions(): Object; | ||
| /** | ||
| * The label of the binary. | ||
| * | ||
| * Shown at the top of the usage information. | ||
| */ | ||
| readonly binaryLabel?: string; | ||
| /** | ||
| * The name of the binary. | ||
| * | ||
| * Included in the path and the examples of the definitions. | ||
| */ | ||
| readonly binaryName: string; | ||
| /** | ||
| * The version of the binary. | ||
| * | ||
| * Shown at the top of the usage information. | ||
| */ | ||
| readonly binaryVersion?: string; | ||
| /** | ||
| * Returns an Array representing the definitions of all registered commands. | ||
| */ | ||
| definitions(): Definition[]; | ||
| /** | ||
| * Formats errors using colors. | ||
| * | ||
| * @param error The error to format. If `error.name` is `'Error'`, it is replaced with `'Internal Error'`. | ||
| * @param opts.command The command whose usage will be included in the formatted error. | ||
| */ | ||
| error(error: Error, opts?: { | ||
| command?: Command<Context> | null; | ||
| }): string; | ||
| /** | ||
| * Compiles a command and its arguments using the `CommandBuilder`. | ||
| * | ||
| * @param input An array containing the name of the command and its arguments | ||
| * | ||
| * @returns The compiled `Command`, with its properties populated with the arguments. | ||
| */ | ||
| process(input: string[]): Command<Context>; | ||
| /** | ||
| * Runs a command. | ||
| * | ||
| * @param input An array containing the name of the command and its arguments | ||
| * @param context Overrides the Context of the main `Cli` instance | ||
| * | ||
| * @returns The exit code of the command | ||
| */ | ||
| run(input: string[], context?: Partial<Context>): Promise<number>; | ||
| /** | ||
| * Returns the usage of a command. | ||
| * | ||
| * @param command The `Command` whose usage will be returned or `null` to return the usage of all commands. | ||
| * @param opts.detailed If `true`, the usage of a command will also include its description, details, and examples. Doesn't have any effect if `command` is `null` or doesn't have a `usage` property. | ||
| * @param opts.prefix The prefix displayed before each command. Defaults to `$`. | ||
| */ | ||
| usage(command?: CommandClass<Context> | Command<Context> | null, opts?: { | ||
@@ -25,3 +97,11 @@ detailed?: boolean; | ||
| }; | ||
| /** | ||
| * @template Context The context shared by all commands. Contexts are a set of values, defined when calling the `run`/`runExit` functions from the CLI instance, that will be made available to the commands via `this.context`. | ||
| */ | ||
| export declare class Cli<Context extends BaseContext = BaseContext> implements MiniCli<Context> { | ||
| /** | ||
| * The default context of the CLI. | ||
| * | ||
| * Contains the stdio of the current `process`. | ||
| */ | ||
| static defaultContext: { | ||
@@ -37,26 +117,42 @@ stdin: NodeJS.ReadStream; | ||
| readonly binaryVersion?: string; | ||
| readonly enableColors: boolean; | ||
| /** | ||
| * Creates a new Cli and registers all commands passed as parameters. | ||
| * | ||
| * @param commandClasses The Commands to register | ||
| * @returns The created `Cli` instance | ||
| */ | ||
| static from<Context extends BaseContext = BaseContext>(commandClasses: CommandClass<Context>[]): Cli<Context>; | ||
| constructor({ binaryLabel, binaryName, binaryVersion }?: { | ||
| constructor({ binaryLabel, binaryName, binaryVersion, enableColors }?: { | ||
| binaryLabel?: string; | ||
| binaryName?: string; | ||
| binaryVersion?: string; | ||
| enableColors?: boolean; | ||
| }); | ||
| /** | ||
| * Registers a command inside the CLI. | ||
| */ | ||
| register(commandClass: CommandClass<Context>): void; | ||
| process(input: string[]): Command<Context>; | ||
| run(input: Command<Context> | string[], context: Context): Promise<number>; | ||
| /** | ||
| * 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) | ||
| */ | ||
| runExit(input: Command<Context> | string[], context: Context): Promise<void>; | ||
| suggest(input: string[], partial: boolean): string[][]; | ||
| definitions(): { | ||
| path: string; | ||
| usage: string; | ||
| category: string | undefined; | ||
| description: string | undefined; | ||
| details: string | undefined; | ||
| examples: string[][] | undefined; | ||
| }[]; | ||
| usage(command?: CommandClass<Context> | Command<Context> | null, { detailed, prefix }?: { | ||
| definitions({ colored }?: { | ||
| colored?: boolean; | ||
| }): Definition[]; | ||
| usage(command?: CommandClass<Context> | Command<Context> | null, { colored, detailed, prefix }?: { | ||
| colored?: boolean; | ||
| detailed?: boolean; | ||
| prefix?: string; | ||
| }): string; | ||
| error(error: Error, { command }?: { | ||
| error(error: Error | any, { colored, command }?: { | ||
| colored?: boolean; | ||
| command?: Command<Context> | null; | ||
@@ -66,2 +162,3 @@ }): string; | ||
| private getUsageByIndex; | ||
| private format; | ||
| } |
+263
-220
| "use strict"; | ||
| var __importDefault = (this && this.__importDefault) || function (mod) { | ||
| return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| const chalk_1 = __importDefault(require("chalk")); | ||
| exports.Cli = void 0; | ||
| const core_1 = require("../core"); | ||
@@ -11,247 +8,293 @@ const core_2 = require("../core"); | ||
| const HelpCommand_1 = require("./HelpCommand"); | ||
| class Cli { | ||
| constructor({ binaryLabel, binaryName = `...`, binaryVersion } = {}) { | ||
| this.registrations = new Map(); | ||
| this.builder = new core_2.CliBuilder({ binaryName }); | ||
| this.binaryLabel = binaryLabel; | ||
| this.binaryName = binaryName; | ||
| this.binaryVersion = binaryVersion; | ||
| } | ||
| static from(commandClasses) { | ||
| const cli = new Cli(); | ||
| for (const commandClass of commandClasses) | ||
| cli.register(commandClass); | ||
| return 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 core_1.HELP_COMMAND_INDEX: | ||
| { | ||
| return HelpCommand_1.HelpCommand.from(state, this, contexts); | ||
| function getDefaultColorSettings() { | ||
| if (process.env.FORCE_COLOR === `0`) | ||
| return false; | ||
| if (process.env.FORCE_COLOR === `1`) | ||
| return true; | ||
| if (typeof process.stdout !== `undefined` && process.stdout.isTTY) | ||
| return true; | ||
| return false; | ||
| } | ||
| /** | ||
| * @template Context The context shared by all commands. Contexts are a set of values, defined when calling the `run`/`runExit` functions from the CLI instance, that will be made available to the commands via `this.context`. | ||
| */ | ||
| let Cli = /** @class */ (() => { | ||
| class Cli { | ||
| constructor({ binaryLabel, binaryName = `...`, binaryVersion, enableColors = getDefaultColorSettings() } = {}) { | ||
| this.registrations = new Map(); | ||
| this.builder = new core_2.CliBuilder({ binaryName }); | ||
| this.binaryLabel = binaryLabel; | ||
| this.binaryName = binaryName; | ||
| 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 core_1.HELP_COMMAND_INDEX: | ||
| { | ||
| return HelpCommand_1.HelpCommand.from(state, this, contexts); | ||
| } | ||
| break; | ||
| 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; | ||
| } | ||
| break; | ||
| } | ||
| } | ||
| async run(input, context) { | ||
| let command; | ||
| if (!Array.isArray(input)) { | ||
| command = input; | ||
| } | ||
| else { | ||
| try { | ||
| command = this.process(input); | ||
| } | ||
| break; | ||
| 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; | ||
| catch (error) { | ||
| context.stdout.write(this.error(error)); | ||
| return 1; | ||
| } | ||
| break; | ||
| } | ||
| } | ||
| async run(input, context) { | ||
| let command; | ||
| if (!Array.isArray(input)) { | ||
| command = input; | ||
| } | ||
| else { | ||
| } | ||
| 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; | ||
| try { | ||
| command = this.process(input); | ||
| exitCode = await command.validateAndExecute(); | ||
| } | ||
| catch (error) { | ||
| context.stdout.write(this.error(error)); | ||
| context.stdout.write(this.error(error, { command })); | ||
| return 1; | ||
| } | ||
| return exitCode; | ||
| } | ||
| if (command.help) { | ||
| context.stdout.write(this.usage(command, { detailed: true })); | ||
| return 0; | ||
| /** | ||
| * 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); | ||
| } | ||
| command.context = context; | ||
| command.cli = { | ||
| 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(); | ||
| suggest(input, partial) { | ||
| const { contexts, process, suggest } = this.builder.compile(); | ||
| return suggest(input, partial); | ||
| } | ||
| catch (error) { | ||
| context.stdout.write(this.error(error, { command })); | ||
| return 1; | ||
| } | ||
| return exitCode; | ||
| } | ||
| 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() { | ||
| 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` | ||
| ? format_1.formatMarkdownish(commandClass.usage.category, false) | ||
| : undefined; | ||
| const description = typeof commandClass.usage.description !== `undefined` | ||
| ? format_1.formatMarkdownish(commandClass.usage.description, false) | ||
| : undefined; | ||
| const details = typeof commandClass.usage.details !== `undefined` | ||
| ? format_1.formatMarkdownish(commandClass.usage.details, true) | ||
| : undefined; | ||
| const examples = typeof commandClass.usage.examples !== `undefined` | ||
| ? commandClass.usage.examples.map(([label, cli]) => [format_1.formatMarkdownish(label, false), cli.replace(/\$0/g, this.binaryName)]) | ||
| : undefined; | ||
| data.push({ path, usage, category, description, details, examples }); | ||
| } | ||
| return data; | ||
| } | ||
| usage(command = null, { 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()) { | ||
| 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` | ||
| ? format_1.formatMarkdownish(commandClass.usage.category, false) | ||
| : null; | ||
| let categoryCommands = commandsByCategories.get(category); | ||
| if (typeof categoryCommands === `undefined`) | ||
| commandsByCategories.set(category, categoryCommands = []); | ||
| const usage = this.getUsageByIndex(number); | ||
| categoryCommands.push({ commandClass, usage }); | ||
| ? format_1.formatMarkdownish(commandClass.usage.category, { format: this.format(colored), paragraphs: false }) | ||
| : undefined; | ||
| const description = typeof commandClass.usage.description !== `undefined` | ||
| ? format_1.formatMarkdownish(commandClass.usage.description, { format: this.format(colored), paragraphs: false }) | ||
| : undefined; | ||
| const details = typeof commandClass.usage.details !== `undefined` | ||
| ? format_1.formatMarkdownish(commandClass.usage.details, { format: this.format(colored), paragraphs: true }) | ||
| : undefined; | ||
| const examples = typeof commandClass.usage.examples !== `undefined` | ||
| ? commandClass.usage.examples.map(([label, cli]) => [format_1.formatMarkdownish(label, { format: this.format(colored), paragraphs: false }), cli.replace(/\$0/g, this.binaryName)]) | ||
| : undefined; | ||
| data.push({ path, usage, category, description, details, examples }); | ||
| } | ||
| 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 += `${chalk_1.default.bold(`${this.binaryLabel} - ${this.binaryVersion}`)}\n\n`; | ||
| else if (hasLabel) | ||
| result += `${chalk_1.default.bold(`${this.binaryLabel}`)}\n`; | ||
| else | ||
| result += `${chalk_1.default.bold(`${this.binaryVersion}`)}\n`; | ||
| result += ` ${chalk_1.default.bold(prefix)}${this.binaryName} <command>\n`; | ||
| } | ||
| else { | ||
| result += `${chalk_1.default.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` }); | ||
| 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` | ||
| ? format_1.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 header = categoryName !== null | ||
| ? categoryName.trim() | ||
| : `Where <command> is one of`; | ||
| result += `\n`; | ||
| result += `${chalk_1.default.bold(`${header}:`)}\n`; | ||
| for (let { commandClass, usage } of commands) { | ||
| const doc = commandClass.usage.description || `undocumented`; | ||
| 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`; | ||
| result += `\n`; | ||
| result += ` ${chalk_1.default.bold(usage)}\n`; | ||
| result += ` ${format_1.formatMarkdownish(doc, false)}`; | ||
| 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 += ` ${format_1.formatMarkdownish(doc, { format: this.format(colored), paragraphs: false })}`; | ||
| } | ||
| } | ||
| result += `\n`; | ||
| result += format_1.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 += format_1.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.`, true); | ||
| } | ||
| else { | ||
| if (!detailed) { | ||
| result += `${chalk_1.default.bold(prefix)}${this.getUsageByRegistration(commandClass)}\n`; | ||
| } | ||
| else { | ||
| const { description = ``, details = ``, examples = [], } = commandClass.usage || {}; | ||
| if (description !== ``) { | ||
| result += format_1.formatMarkdownish(description, false).replace(/^./, $0 => $0.toUpperCase()); | ||
| result += `\n`; | ||
| if (!detailed) { | ||
| result += `${this.format(colored).bold(prefix)}${this.getUsageByRegistration(commandClass)}\n`; | ||
| } | ||
| if (details !== `` || examples.length > 0) { | ||
| result += `${chalk_1.default.bold(`Usage:`)}\n`; | ||
| result += `\n`; | ||
| } | ||
| result += `${chalk_1.default.bold(prefix)}${this.getUsageByRegistration(commandClass)}\n`; | ||
| if (details !== ``) { | ||
| result += `\n`; | ||
| result += `${chalk_1.default.bold(`Details:`)}\n`; | ||
| result += `\n`; | ||
| result += format_1.formatMarkdownish(details, true); | ||
| } | ||
| if (examples.length > 0) { | ||
| result += `\n`; | ||
| result += `${chalk_1.default.bold(`Examples:`)}\n`; | ||
| for (let [description, example] of examples) { | ||
| else { | ||
| const { description = ``, details = ``, examples = [], } = commandClass.usage || {}; | ||
| if (description !== ``) { | ||
| result += format_1.formatMarkdownish(description, { format: this.format(colored), paragraphs: false }).replace(/^./, $0 => $0.toUpperCase()); | ||
| result += `\n`; | ||
| result += format_1.formatMarkdownish(description, false); | ||
| result += example | ||
| .replace(/^/m, ` ${chalk_1.default.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 += format_1.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 += format_1.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; | ||
| } | ||
| return result; | ||
| } | ||
| error(error, { command = null } = {}) { | ||
| let result = ``; | ||
| let name = error.name.replace(/([a-z])([A-Z])/g, `$1 $2`); | ||
| if (name === `Error`) | ||
| name = `Internal Error`; | ||
| result += `${chalk_1.default.red.bold(name)}: ${error.message}\n`; | ||
| // @ts-ignore | ||
| const meta = error.clipanion; | ||
| if (typeof meta !== `undefined`) { | ||
| if (meta.type === `usage`) { | ||
| result += `\n`; | ||
| result += this.usage(command); | ||
| 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; | ||
| } | ||
| 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 ? format_1.richFormat : format_1.textFormat; | ||
| } | ||
| } | ||
| 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); | ||
| } | ||
| } | ||
| /** | ||
| * 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; | ||
| })(); | ||
| exports.Cli = Cli; | ||
| Cli.defaultContext = { | ||
| stdin: process.stdin, | ||
| stdout: process.stdout, | ||
| stderr: process.stderr, | ||
| }; |
| import { CommandBuilder, RunState } from '../core'; | ||
| import { BaseContext, CliContext, MiniCli } from './Cli'; | ||
| import type { HelpCommand } from './entries/help'; | ||
| import type { VersionCommand } from './entries/version'; | ||
| export declare type Meta<Context extends BaseContext> = { | ||
@@ -7,14 +9,68 @@ definitions: ((command: CommandBuilder<CliContext<Context>>) => void)[]; | ||
| }; | ||
| /** | ||
| * The usage of a Command. | ||
| */ | ||
| export declare type Usage = { | ||
| /** | ||
| * The category of the command. | ||
| * | ||
| * Included in the detailed usage. | ||
| */ | ||
| category?: string; | ||
| /** | ||
| * The short description of the command, formatted as Markdown. | ||
| * | ||
| * Included in the detailed usage. | ||
| */ | ||
| description?: string; | ||
| /** | ||
| * The extended details of the command, formatted as Markdown. | ||
| * | ||
| * Included in the detailed usage. | ||
| */ | ||
| details?: string; | ||
| /** | ||
| * Examples of the command represented as an Array of tuples. | ||
| * | ||
| * The first element of the tuple represents the description of the example. | ||
| * | ||
| * The second element of the tuple represents the command of the example. | ||
| * If present, the leading `$0` is replaced with `cli.binaryName`. | ||
| */ | ||
| examples?: [string, string][]; | ||
| }; | ||
| /** | ||
| * The definition of a Command. | ||
| */ | ||
| export declare type Definition = Usage & { | ||
| /** | ||
| * The path of the command, starting with `cli.binaryName`. | ||
| */ | ||
| path: string; | ||
| /** | ||
| * The detailed usage of the command. | ||
| */ | ||
| usage: string; | ||
| }; | ||
| /** | ||
| * The schema used to validate the Command instance. | ||
| * | ||
| * The easiest way to validate it is by using the [Yup](https://github.com/jquense/yup) library. | ||
| * | ||
| * @example | ||
| * yup.object().shape({ | ||
| * a: yup.number().integer(), | ||
| * b: yup.number().integer(), | ||
| * }) | ||
| */ | ||
| export declare type Schema<C extends Command<any>> = { | ||
| /** | ||
| * A function that takes the `Command` instance as a parameter and validates it, throwing an Error if the validation fails. | ||
| */ | ||
| validate: (object: C) => void; | ||
| }; | ||
| export declare type CommandClass<Context extends BaseContext = BaseContext> = { | ||
| new (): Command<Context>; | ||
| resolveMeta(prototype: Command<Context>): Meta<Context>; | ||
| schema?: { | ||
| validate: (object: any) => void; | ||
| }; | ||
| schema?: Schema<any>; | ||
| usage?: Usage; | ||
@@ -49,2 +105,3 @@ }; | ||
| static String(descriptor: string, opts?: { | ||
| tolerateBoolean?: boolean; | ||
| hidden?: boolean; | ||
@@ -96,2 +153,11 @@ }): PropertyDecorator; | ||
| /** | ||
| * Defines the schema for the given command. | ||
| * @param schema | ||
| */ | ||
| static Schema<C extends Command<any> = Command<BaseContext>>(schema: Schema<C>): Schema<C>; | ||
| /** | ||
| * The schema used to validate the Command instance. | ||
| */ | ||
| static schema?: Schema<any>; | ||
| /** | ||
| * Standard command that'll get executed by `Cli#run` and `Cli#runExit`. Expected to return an exit code or nothing (which Clipanion will treat as if 0 had been returned). | ||
@@ -118,1 +184,26 @@ */ | ||
| } | ||
| export declare namespace 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); | ||
| */ | ||
| const Entries: { | ||
| /** | ||
| * A command that prints the usage of all commands. | ||
| * | ||
| * Paths: `-h`, `--help` | ||
| */ | ||
| Help: typeof HelpCommand; | ||
| /** | ||
| * A command that prints the version of the binary (`cli.binaryVersion`). | ||
| * | ||
| * Paths: `-v`, `--version` | ||
| */ | ||
| Version: typeof VersionCommand; | ||
| }; | ||
| } |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.Command = void 0; | ||
| class Command { | ||
@@ -74,3 +75,3 @@ constructor() { | ||
| this.registerDefinition(prototype, command => { | ||
| command.addOption({ names: optNames, arity: 0, hidden }); | ||
| command.addOption({ names: optNames, arity: 0, hidden, allowBinding: false }); | ||
| }); | ||
@@ -87,3 +88,3 @@ this.registerTransformer(prototype, (state, command) => { | ||
| } | ||
| static String(descriptor = { required: true }, { hidden = false } = {}) { | ||
| static String(descriptor = { required: true }, { tolerateBoolean = false, hidden = false } = {}) { | ||
| return (prototype, propertyName) => { | ||
@@ -93,3 +94,5 @@ if (typeof descriptor === `string`) { | ||
| 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 }); | ||
| }); | ||
@@ -172,2 +175,9 @@ this.registerTransformer(prototype, (state, command) => { | ||
| } | ||
| /** | ||
| * Defines the schema for the given command. | ||
| * @param schema | ||
| */ | ||
| static Schema(schema) { | ||
| return schema; | ||
| } | ||
| async validateAndExecute() { | ||
@@ -196,1 +206,26 @@ const commandClass = this.constructor; | ||
| exports.Command = Command; | ||
| (function (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 = { | ||
| /** | ||
| * A command that prints the usage of all commands. | ||
| * | ||
| * Paths: `-h`, `--help` | ||
| */ | ||
| Help: require(`./entries/help`).HelpCommand, | ||
| /** | ||
| * A command that prints the version of the binary (`cli.binaryVersion`). | ||
| * | ||
| * Paths: `-v`, `--version` | ||
| */ | ||
| Version: require(`./entries/version`).VersionCommand, | ||
| }; | ||
| })(Command = exports.Command || (exports.Command = {})); |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.HelpCommand = void 0; | ||
| const Command_1 = require("./Command"); | ||
@@ -4,0 +5,0 @@ class HelpCommand extends Command_1.Command { |
| export { BaseContext, Cli } from './Cli'; | ||
| export { CommandClass, Command, Usage } from './Command'; | ||
| export { CommandClass, Command, Usage, Definition, Schema } from './Command'; | ||
| export { UsageError } from '../errors'; |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| var Cli_1 = require("./Cli"); | ||
| exports.Cli = Cli_1.Cli; | ||
| Object.defineProperty(exports, "Cli", { enumerable: true, get: function () { return Cli_1.Cli; } }); | ||
| var Command_1 = require("./Command"); | ||
| exports.Command = Command_1.Command; | ||
| Object.defineProperty(exports, "Command", { enumerable: true, get: function () { return Command_1.Command; } }); | ||
| var errors_1 = require("../errors"); | ||
| exports.UsageError = errors_1.UsageError; | ||
| Object.defineProperty(exports, "UsageError", { enumerable: true, get: function () { return errors_1.UsageError; } }); |
+3
-2
@@ -84,3 +84,3 @@ export declare const NODE_INITIAL = 0; | ||
| isBatchOption: (state: RunState, segment: string, names: string[]) => boolean; | ||
| isBoundOption: (state: RunState, segment: string, names: string[]) => boolean; | ||
| isBoundOption: (state: RunState, segment: string, names: string[], options: OptDefinition[]) => boolean; | ||
| isNegatedOption: (state: RunState, segment: string, name: string) => boolean; | ||
@@ -328,2 +328,3 @@ isHelp: (state: RunState, segment: string) => boolean; | ||
| hidden: boolean; | ||
| allowBinding: boolean; | ||
| }; | ||
@@ -353,3 +354,3 @@ export declare class CommandBuilder<Context> { | ||
| }): void; | ||
| addOption({ names, arity, hidden }: Partial<OptDefinition> & { | ||
| addOption({ names, arity, hidden, allowBinding }: Partial<OptDefinition> & { | ||
| names: string[]; | ||
@@ -356,0 +357,0 @@ }): void; |
+22
-7
| "use strict"; | ||
| var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); | ||
| }) : (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| o[k2] = m[k]; | ||
| })); | ||
| var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
| Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
| }) : function(o, v) { | ||
| o["default"] = v; | ||
| }); | ||
| var __importStar = (this && this.__importStar) || function (mod) { | ||
| if (mod && mod.__esModule) return mod; | ||
| var result = {}; | ||
| if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; | ||
| result["default"] = mod; | ||
| if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
| __setModuleDefault(result, mod); | ||
| return result; | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.CliBuilder = exports.CommandBuilder = exports.NoLimits = exports.reducers = exports.tests = exports.suggest = exports.execute = exports.registerStatic = exports.registerShortcut = exports.registerDynamic = exports.cloneNode = exports.cloneTransition = exports.isTerminalNode = exports.makeNode = exports.aggregateHelpStates = exports.selectBestState = exports.trimSmallerBranches = exports.runMachineInternal = exports.debugMachine = exports.simplifyMachine = exports.injectNode = exports.makeAnyOfMachine = exports.makeStateMachine = exports.debug = exports.DEBUG = exports.BINDING_REGEX = exports.BATCH_REGEX = exports.OPTION_REGEX = exports.HELP_REGEX = exports.HELP_COMMAND_INDEX = exports.END_OF_INPUT = exports.START_OF_INPUT = exports.NODE_ERRORED = exports.NODE_SUCCESS = exports.NODE_INITIAL = void 0; | ||
| const errors = __importStar(require("./errors")); | ||
@@ -428,5 +441,7 @@ exports.NODE_INITIAL = 0; | ||
| }, | ||
| isBoundOption: (state, segment, names) => { | ||
| isBoundOption: (state, segment, names, options) => { | ||
| const optionParsing = segment.match(exports.BINDING_REGEX); | ||
| return !state.ignoreOptions && !!optionParsing && exports.OPTION_REGEX.test(optionParsing[1]) && names.includes(optionParsing[1]); | ||
| return !state.ignoreOptions && !!optionParsing && exports.OPTION_REGEX.test(optionParsing[1]) && names.includes(optionParsing[1]) | ||
| // Disallow bound options with no arguments (i.e. booleans) | ||
| && options.filter(opt => opt.names.includes(optionParsing[1])).every(opt => opt.allowBinding); | ||
| }, | ||
@@ -551,5 +566,5 @@ isNegatedOption: (state, segment, name) => { | ||
| } | ||
| addOption({ names, arity = 0, hidden = false }) { | ||
| addOption({ names, arity = 0, hidden = false, allowBinding = true }) { | ||
| this.allOptionNames.push(...names); | ||
| this.options.push({ names, arity, hidden }); | ||
| this.options.push({ names, arity, hidden, allowBinding }); | ||
| } | ||
@@ -675,3 +690,3 @@ setContext(context) { | ||
| registerDynamic(machine, node, [`isBatchOption`, this.allOptionNames], node, `pushBatch`); | ||
| registerDynamic(machine, node, [`isBoundOption`, this.allOptionNames], node, `pushBound`); | ||
| registerDynamic(machine, node, [`isBoundOption`, this.allOptionNames, this.options], node, `pushBound`); | ||
| registerDynamic(machine, node, [`isUnsupportedOption`, this.allOptionNames], exports.NODE_ERRORED, [`setError`, `Unsupported option name`]); | ||
@@ -678,0 +693,0 @@ registerDynamic(machine, node, [`isInvalidOption`], exports.NODE_ERRORED, [`setError`, `Invalid option name`]); |
+5
-0
@@ -6,2 +6,7 @@ export declare type ErrorMeta = { | ||
| }; | ||
| /** | ||
| * A generic usage error with the name `UsageError`. | ||
| * | ||
| * It should be used over `Error` only when it's the user's fault. | ||
| */ | ||
| export declare class UsageError extends Error { | ||
@@ -8,0 +13,0 @@ clipanion: ErrorMeta; |
+6
-0
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.AmbiguousSyntaxError = exports.UnknownSyntaxError = exports.UsageError = void 0; | ||
| const core_1 = require("./core"); | ||
| /** | ||
| * A generic usage error with the name `UsageError`. | ||
| * | ||
| * It should be used over `Error` only when it's the user's fault. | ||
| */ | ||
| class UsageError extends Error { | ||
@@ -5,0 +11,0 @@ constructor(message) { |
+11
-1
@@ -1,1 +0,11 @@ | ||
| export declare function formatMarkdownish(text: string, paragraphs: boolean): string; | ||
| export interface ColorFormat { | ||
| bold(str: string): string; | ||
| error(str: string): string; | ||
| code(str: string): string; | ||
| } | ||
| export declare const richFormat: ColorFormat; | ||
| export declare const textFormat: ColorFormat; | ||
| export declare function formatMarkdownish(text: string, { format, paragraphs }: { | ||
| format: ColorFormat; | ||
| paragraphs: boolean; | ||
| }): string; |
+14
-6
| "use strict"; | ||
| var __importDefault = (this && this.__importDefault) || function (mod) { | ||
| return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.formatMarkdownish = exports.textFormat = exports.richFormat = void 0; | ||
| ; | ||
| exports.richFormat = { | ||
| bold: str => `\x1b[1m${str}\x1b[22m`, | ||
| error: str => `\x1b[31m\x1b[1m${str}\x1b[22m\x1b[39m`, | ||
| code: str => `\x1b[36m${str}\x1b[39m`, | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| const chalk_1 = __importDefault(require("chalk")); | ||
| function formatMarkdownish(text, paragraphs) { | ||
| exports.textFormat = { | ||
| bold: str => str, | ||
| error: str => str, | ||
| code: str => str, | ||
| }; | ||
| function formatMarkdownish(text, { format, paragraphs }) { | ||
| // Enforce \n as newline character | ||
@@ -33,3 +41,3 @@ text = text.replace(/\r\n?/g, `\n`); | ||
| text = text.replace(/(`+)((?:.|[\n])*?)\1/g, function ($0, $1, $2) { | ||
| return chalk_1.default.cyan($1 + $2 + $1); | ||
| return format.code($1 + $2 + $1); | ||
| }); | ||
@@ -36,0 +44,0 @@ return text ? text + `\n` : ``; |
+12
-17
| { | ||
| "name": "clipanion", | ||
| "version": "2.3.1", | ||
| "version": "2.4.0", | ||
| "main": "lib/advanced", | ||
| "license": "MIT", | ||
| "dependencies": { | ||
| "chalk": "^2.4.2" | ||
| }, | ||
| "devDependencies": { | ||
| "@berry/pnpify": "^0.0.6", | ||
| "@types/chai": "^4.1.7", | ||
| "@types/chai-as-promised": "^7.1.0", | ||
| "@types/chalk": "^2.2.0", | ||
| "@types/mocha": "^5.2.7", | ||
| "@types/node": "^12.6.2", | ||
| "@types/yup": "^0.26.21", | ||
| "@types/chai": "^4.2.11", | ||
| "@types/chai-as-promised": "^7.1.2", | ||
| "@types/mocha": "^7.0.2", | ||
| "@types/node": "^14.0.1", | ||
| "@types/yup": "^0.28.3", | ||
| "chai": "^4.2.0", | ||
| "chai-as-promised": "^7.1.1", | ||
| "get-stream": "^5.1.0", | ||
| "mocha": "^6.1.4", | ||
| "ts-node": "^8.3.0", | ||
| "typescript": "^3.5.3", | ||
| "yup": "^0.27.0" | ||
| "mocha": "^7.1.2", | ||
| "ts-node": "^8.10.1", | ||
| "typescript": "^3.9.2", | ||
| "yup": "^0.28.5" | ||
| }, | ||
| "scripts": { | ||
| "prepack": "rm -rf lib && yarn pnpify tsc", | ||
| "prepack": "rm -rf lib && yarn tsc", | ||
| "postpack": "rm -rf lib", | ||
| "test": "yarn pnpify mocha --require ts-node/register --extension ts tests" | ||
| "test": "FORCE_COLOR=1 mocha --require ts-node/register --extension ts tests" | ||
| }, | ||
@@ -30,0 +25,0 @@ "publishConfig": { |
+37
-11
@@ -22,2 +22,3 @@ # <img src="./logo.svg" height="25" /> Clipanion | ||
| - Clipanion generates good-looking help pages out of the box | ||
| - Clipanion offers common optional command entries out-of-the-box (e.g. version command, help command) | ||
@@ -124,6 +125,25 @@ Clipanion is used in [Yarn](https://github.com/yarnpkg/berry) with great success. | ||
| #### `@Command.String({required?: boolean})` | ||
| #### `@Command.String({required?: boolean, tolerateBoolean?: boolean})` | ||
| Specifies that the command accepts a positional argument. By default it will be required, but this can be toggled off. | ||
| `tolerateBoolean` specifies that the command will act like a boolean flag if it doesn't have a value. With this option on, an argument value can only be specified using `=`. It is off by default. | ||
| ```ts | ||
| class RunCommand extends Command { | ||
| @Command.String(`--inspect`, { tolerateBoolean: true }) | ||
| public debug: boolean | string = false; | ||
| // ... | ||
| } | ||
| run --inspect | ||
| => debug = true | ||
| run --inspect=1234 | ||
| => debug = "1234" | ||
| run --inspect 1234 | ||
| => invalid | ||
| ``` | ||
| #### `@Command.String(optionNames: string)` | ||
@@ -169,20 +189,26 @@ | ||
| ## General Help Page | ||
| ## Optional Built-in Command Entries | ||
| In order to support using the `-h` option to list the commands available to the application, just register a new command as such: | ||
| Clipanion offers common optional command entries out-of-the-box, under the `Command.Entries` namespace. | ||
| They have to be manually registered: | ||
| ```ts | ||
| class HelpCommand extends Command { | ||
| @Command.Path(`--help`) | ||
| @Command.Path(`-h`) | ||
| async execute() { | ||
| this.context.stdout.write(this.cli.usage(null)); | ||
| } | ||
| } | ||
| cli.register(Command.Entries.Help); | ||
| cli.register(Command.Entries.Version); | ||
| ``` | ||
| This will print a block similar to the following: | ||
| ### Help Command - General Help Page | ||
| > Paths: `-h`, `--help` | ||
| The `Command.Entries.Help` command displays the list of commands available to the application, printing a block similar to the following. | ||
|  | ||
| ### Version Command | ||
| > Paths: `-v`, `--version` | ||
| The `Command.Entries.Version` command displays the version of the binary provided under `binaryVersion` when creating the CLI. | ||
| ## Composition | ||
@@ -189,0 +215,0 @@ |
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
107598
17.75%0
-100%12
-14.29%20
25%2325
18.87%269
10.7%7
40%- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed