Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

clipanion

Package Overview
Dependencies
Maintainers
1
Versions
84
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

clipanion - npm Package Compare versions

Comparing version 2.3.1 to 2.4.0

lib/advanced/entries/help.d.ts

123

lib/advanced/Cli.d.ts
/// <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;
}

483

lib/advanced/Cli.js
"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; } });

@@ -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;

"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`]);

@@ -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;

"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) {

@@ -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;
"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` : ``;

{
"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": {

@@ -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.
![](assets/example-general-help.png)
### 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 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc