UNPKG

@kadena/kadena-cli

Version:

Kadena CLI tool to interact with the Kadena blockchain (manage keys, transactions, etc.)

228 lines 10.1 kB
import { CLINAME, IS_TEST } from '../constants/config.js'; import { CommandError, printCommandError } from './command.util.js'; import { notEmpty } from './globalHelpers.js'; import { globalOptions } from './globalOptions.js'; import { handleNoKadenaDirectory, handlePromptError } from './helpers.js'; import { log } from './logger.js'; import { readStdin } from './stdin.js'; async function executeOption(option, args = {}, originalArgs = {}) { let value = args[option.key]; let prompted = false; if (value === undefined && option.isInQuestions) { if (args.quiet !== true && args.quiet !== 'true') { prompted = true; value = await option.prompt(args, originalArgs); } else if (args.quiet === true || args.quiet === 'true') { value = option.defaultValue; } else if (option.isOptional === false && option.defaultValue === undefined) { // Should have been handled earlier, but just in case throw new Error(`Missing required argument: ${option.key} (${option.option.flags})`); } } const validate = (option.isOptional ? option.validation.optional() : option.validation).safeParse(value); if (validate.success === false) { log.warning(`Invalid value for ${option.key}: ${value}`); } const newConfig = { [option.key]: value }; if ('expand' in option) { if (typeof option.expand === 'function') { const expanded = await option.expand(value, args); if (expanded !== undefined && expanded !== null) { // @ts-ignore newConfig[`${option.key}Config`] = expanded; } } } if ('transform' in option) { if (typeof option.transform === 'function') { // @ts-ignore newConfig[option.key] = await option.transform(value, args); } } return { value: value, config: newConfig, prompted, }; } const printCommandExecution = (command, args, updateArgs, values) => { // Give the option to update args used in the command by returning an object // Only update args that are already defined if (updateArgs !== undefined && typeof updateArgs === 'object') { for (const [key, value] of Object.entries(updateArgs)) { if (Object.hasOwn(args, key) === true) args[key] = value; } } log.info(log.color.gray(`\nExecuted:\n${getCommandExecution(command, args, values)}`)); }; const generateBugReportLink = (command, error) => { const platform = encodeURIComponent(process.platform); const browser = encodeURIComponent(`Node.JS ${process.version}`); const reproduction = encodeURIComponent(`Executed command:\n${command}`); const description = encodeURIComponent(`Describe the issue:\n\n\nError stacktrace:\n${error}`); return `https://github.com/kadena-community/kadena.js/issues/new?assignees=&labels=bug&projects=&template=001-bug_report.yml&os=${platform}&browser=${browser}&description=${description}&reproduction=${reproduction}`; }; export const createCommand = (name, description, options, action) => (program) => { // anything after the first newline is only shown on the command specific help page const [_description, ...helpText] = description.split('\n'); let command = program.command(name).description(_description); if (helpText.length > 0) { command.configureHelp({ commandDescription: () => `${[_description, '', ...helpText].join('\n')}`, }); } let allowsUnknownOptions = false; if (options.some((option) => option.allowUnknownOptions === true)) { command = command.allowUnknownOption(true); allowsUnknownOptions = true; } command.addOption(globalOptions.quiet().option); command.addOption(globalOptions.json().option); command.addOption(globalOptions.yaml().option); options.forEach((option) => { command.addOption(option.option); }); command.action(async (originalArgs, ...rest) => { var _a; // args outside try-catch to be able to use it in catch let args = { ...originalArgs }; const values = rest.flatMap((r) => r.args); let prompted = false; try { const stdin = await readStdin(); if (allowsUnknownOptions) { log.debug(`Command ${name} allows unknown options`); } // Automatically enable quiet mode if not in interactive and test environment if (!process.stderr.isTTY && !IS_TEST) args.quiet = true; handleQuietOption(args, options); if (args.json === true) { log.setOutputMode('json'); } else if (args.yaml === true) { log.setOutputMode('yaml'); } const optionIndex = new Map(); const collectOptionsMap = options.reduce((acc, option, index) => { acc[option.key] = async (customArgs = {}) => { try { const { value, config, prompted: _prompted, } = await executeOption(option, { stdin: stdin, ...args, ...customArgs, }, originalArgs); // Keep track of previous args to prompts can use them args = { ...args, [option.key]: value }; prompted = prompted || _prompted; return config; } catch (error) { handlePromptError(error); } }; optionIndex.set(acc[option.key], index); return acc; // eslint-disable-next-line @typescript-eslint/no-explicit-any }, {}); const result = await action(collectOptionsMap, { values, stdin: stdin !== null && stdin !== void 0 ? stdin : undefined, collect: async (optionObject) => { const options = Object.entries(optionObject); options.sort((a, b) => { var _a, _b; return ((_a = optionIndex.get(a[1])) !== null && _a !== void 0 ? _a : 0) - ((_b = optionIndex.get(b[1])) !== null && _b !== void 0 ? _b : 0); }); // eslint-disable-next-line @typescript-eslint/no-explicit-any let result = {}; for (const option of options) { result = { ...result, ...(await option[1]()), }; } return result; }, }); if (prompted) { printCommandExecution(`${program.name()} ${name}`, args, result !== null && result !== void 0 ? result : undefined, values); } } catch (error) { if (handleNoKadenaDirectory(error)) return; if (error instanceof CommandError) { printCommandError(error); if (prompted) { printCommandExecution(`${program.name()} ${name}`, args, error.args, values); } process.exitCode = error.exitCode; return; } log.error(`\nAn error occurred: ${error.message}\n`); if (IS_TEST) { log.error(error.stack); } else { log.debug(error.stack); log.info(`Is this a bug? Let us know:\n${generateBugReportLink(getCommandExecution(`${program.name()} ${name}`, args, values), (_a = error.stack) !== null && _a !== void 0 ? _a : error.message)}`); } process.exitCode = 1; } }); }; function handleQuietOption(args, options) { if (args.quiet === true) { const missing = options.filter((option) => option.isOptional === false && args[option.key] === undefined && option.defaultValue === undefined); if (missing.length) { log.error(`\nMissing required arguments:\n${missing .map((m) => options.find((q) => q.key === m.key)) .map((m) => `- ${m.key} (${m.option.flags})\n`) .join('')}`); log.warning('Remove the --quiet flag to enable interactive prompts'); throw new CommandError({ exitCode: 1 }); } } } function getCommandExecution(command, args, generalArgs = []) { return `${CLINAME} ${command} ${Object.getOwnPropertyNames(args) .map((arg) => { let displayValue = null; const value = args[arg]; if (Array.isArray(value)) { displayValue = `"${value.join(',')}"`; } else if (typeof value === 'string') { displayValue = `"${value}"`; } else if (typeof value === 'number') { displayValue = value.toString(); } else if (typeof value === 'boolean' && value) { return arg.length === 1 ? `-${arg}` : `--${arg}`; } else if (value === null || (typeof value === 'boolean' && !value)) { return undefined; } else if (typeof value === 'object' && Object.getPrototypeOf(value) === Object.prototype) { return Object.entries(value) .map(([key, val]) => // Do not show keys starting with _ (used for password) !key.startsWith('_') ? `--${key}="${val}"` : null) .filter(notEmpty) .join(' '); } const key = arg.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`); return displayValue !== null && displayValue !== undefined ? `--${key}=${displayValue}` : ''; }) .filter(Boolean) .join(' ')} ${generalArgs.join(' ')}`; } //# sourceMappingURL=createCommand.js.map