Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 | 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 21588x 930x 930x 20658x 20658x 161x 161x 161x 161x 161x 161x 97x 97x 97x 97x 161x 20658x 20658x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 186x 186x 9x 9x 177x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 161x 161x 161x 161x 161x 161x 619x 619x 161x 161x 161x 161x 1424x 1424x 1424x 161x 161x 161x 193x 193x 193x 193x 193x 193x 193x 193x 193x 25x 25x 25x 25x 25x 25x 25x 25x 12x 18x 18x 18x 18x 12x 12x 12x 25x 25x 25x 21x 110x 110x 110x 110x 110x 110x 110x 110x 110x 21x 21x 21x 25x 25x 25x 25x 25x 25x 25x 25x 25x 25x 3x 3x 25x 12x 12x 25x 21x 21x 25x 25x 25x 193x 193x 193x 193x 193x 193x 61x 61x 61x 193x 193x 193x 193x 193x 193x 36x 36x 36x 36x 193x 193x 193x 64962x 64962x 203x 203x 203x 16x 16x 64962x 64962x 193x 193x 193x 20658x 20658x 20658x 20658x 193x 193x 193x 193x 52290x 52290x 52290x 52290x 20100x 20100x 52290x 193x 193x 193x 193x 193x 12672x 12672x 12672x 12672x 558x 558x 12672x 193x 193x 193x | import cliparseOriginal from 'cliparse';
import cliparseArgumentModule from 'cliparse/src/argument.js';
import cliparseCommandModule from 'cliparse/src/command.js';
import semver from 'semver';
import pkg from '../../package.json' with { type: 'json' };
import { Logger } from '../logger.js';
import { getCommandInfo } from './get-command-info.js';
import { styleText } from './style-text.js';
// Patch cliparse.command so we can catch errors
const cliparseCommand = cliparseOriginal.command;
cliparseOriginal.command = function (name, options, commandFunction) {
if (commandFunction == null) {
return cliparseCommand(name, options);
}
const command = cliparseCommand(name, options, (params) => {
const args = params.args ?? [];
// Map options from cliparse format (using option.name as key, e.g. 'index-prefix')
// to the original keys from the command definition (e.g. 'indexPrefix')
const mappedOptions = mapOptionsToDefinitionKeys(params.options, command._definition);
const promise = commandFunction(mappedOptions, ...args);
promise.catch((error) => {
Logger.error(error);
const semverIsOk = semver.satisfies(process.version, pkg.engines.node);
if (!semverIsOk) {
Logger.warn(
`You are using node ${process.version}, some of our commands require node ${pkg.engines.node}. The error may be caused by this.`,
);
}
process.exit(1);
});
});
return command;
};
// Patch cliparse.argument.parseList to drop the parse results that succeeded
// from its error payload. When several positional args are given and only one
// fails its parser, parseList returns the *whole* result list (successes
// included) as the error. Downstream, displayErrors prints the valid siblings
// as `<arg-name>: undefined` (they carry an `.argument` but no `.error`).
// We keep only the entries that actually failed, fixing the issue at its root.
// The success path is untouched (we only rewrite the error array), and
// missing-value errors carry an `.error` ("missing value") so they are kept.
const originalParseList = cliparseArgumentModule.parseList;
cliparseArgumentModule.parseList = function (args, providedArguments) {
const result = originalParseList(args, providedArguments);
if (Array.isArray(result.error)) {
return cliparseOriginal.parsers.error(result.error.filter((entry) => entry.error != null));
}
return result;
};
/**
* Map options from cliparse format (using option.name) to the original definition keys.
* For example, { theOptionName: defineOption({ name: 'the-option-name', ... }) },
* cliparse will return { 'the-option-name': value }, and we need to convert it to { theOptionName: value }.
* @param {Record<string, unknown>} options - Options object from cliparse with option.name as keys
* @param {CommandDefinition} definition - Command definition
* @returns {Record<string, unknown>} Options object with original definition keys
*/
function mapOptionsToDefinitionKeys(options, definition) {
if (!definition?.options) {
return options;
}
// Build a reverse map: option.name -> definition key
const nameToKey = new Map();
for (const [key, optionDef] of Object.entries(definition.options)) {
nameToKey.set(optionDef.name, key);
}
// Map options to use definition keys
const mappedOptions = {};
for (const [name, value] of Object.entries(options)) {
const key = nameToKey.get(name) ?? name;
mappedOptions[key] = value;
}
return mappedOptions;
}
/**
* Patched help function for cliparse commands.
* Called internally by cliparse when --help is used.
* Generates formatted help output with usage, arguments, options, and subcommands.
* @param {Array<{name: string, description: string, commands: Array, _definition: CommandDefinition}>} context - Command context stack
* @returns {string} Formatted help text
*/
cliparseCommandModule.help = function (context) {
const cmd = context.at(-1);
const path = context.slice(1).map((c) => c.name);
const commandInfo = getCommandInfo(path, cmd._definition);
const allRows = [];
let argumentsRows;
if (commandInfo.args) {
argumentsRows = commandInfo.args.map((arg) => {
let description = arg.description;
if (arg.enumValues) description += ` (${arg.enumValues.join(', ')})`;
if (arg.optional) description += ` ${styleText('dim', arg.optional)}`;
return [arg.name, description];
});
allRows.push(...argumentsRows);
}
let optionsRows;
if (commandInfo.options) {
optionsRows = commandInfo.options.map((opt) => {
const placeholder = opt.placeholder ? ` ${opt.placeholder}` : '';
const aliasesPadding = opt.aliases[0].length === 2 ? '' : ' ';
const aliases = aliasesPadding + opt.aliases.join(', ') + placeholder;
let description = opt.description;
if (opt.enumValues) description += ` (${opt.enumValues.join(', ')})`;
if (opt.deprecated) description += ` ${styleText('dim', opt.deprecated)}`;
if (opt.required) description += ` ${styleText('dim', opt.required)}`;
if (opt.default) description += ` ${styleText('dim', opt.default)}`;
return [aliases, description];
});
allRows.push(...optionsRows);
}
const availableCommandsRows = cmd.commands.map((cmd) => [cmd.name, cmd.description.split('\n')[0]]);
allRows.push(...availableCommandsRows);
const firstColumnWith = Math.max(...allRows.map(([cell]) => cell.length));
const parts = [cmd._definition.description];
parts.push(formatSection('USAGE', [commandInfo.usage]));
if (availableCommandsRows.length > 0) {
parts.push(formatSectionWithColumns('COMMANDS', availableCommandsRows, firstColumnWith));
}
if (argumentsRows) {
parts.push(formatSectionWithColumns('ARGUMENTS', argumentsRows, firstColumnWith));
}
if (optionsRows) {
parts.push(formatSectionWithColumns('OPTIONS', optionsRows, firstColumnWith));
}
if (cmd._definition.examples?.length > 0) {
parts.push(formatSection('EXAMPLES', cmd._definition.examples));
}
return parts.join('\n\n');
};
/**
* @param {string} title
* @param {Array<string>} lines
*/
function formatSection(title, lines) {
return [styleText('bold', title), ...lines.map((l) => ` ${l}`)].join('\n');
}
/**
* @param {string} title
* @param {string[][]} rows
* @param {number} firstColumnWidth
*/
function formatSectionWithColumns(title, rows, firstColumnWidth) {
const lines = rows.map(([firstColumn, otherColumn]) => firstColumn.padEnd(firstColumnWidth + 4, ' ') + otherColumn);
return formatSection(title, lines);
}
// Wrap parser functions to allow them to simply return values or throw errors
// instead of using cliparse.parsers.success/error
function wrapParser(parser) {
return (value) => {
try {
return cliparseOriginal.parsers.success(parser(value));
} catch (error) {
return cliparseOriginal.parsers.error(error.message);
}
};
}
// Wrap complete functions to allow returning plain arrays
// instead of cliparse.autocomplete.words(array)
function wrapComplete(complete) {
return (word) => {
// Support both functions and static arrays (e.g. complete: Drain.DRAIN_TYPE_CLI_CODES)
const result = typeof complete === 'function' ? complete(word) : complete;
return Promise.resolve(result).then(cliparse.autocomplete.words);
};
}
// Patch cliparse.option to wrap parser and complete functions
const cliparseOption = cliparseOriginal.option;
cliparseOriginal.option = function (name, options) {
if (options?.parser != null) {
options.parser = wrapParser(options.parser);
}
if (options?.complete != null) {
options.complete = wrapComplete(options.complete);
}
return cliparseOption(name, options);
};
// Patch cliparse.argument to wrap parser and complete functions
const cliparseArgument = cliparseOriginal.argument;
cliparseOriginal.argument = function (name, options) {
if (options?.parser != null) {
options.parser = wrapParser(options.parser);
}
if (options?.complete != null) {
options.complete = wrapComplete(options.complete);
}
return cliparseArgument(name, options);
};
export const cliparse = cliparseOriginal;
|