UNPKG

@code-pushup/cli

Version:

A CLI to run all kinds of code quality measurements to align your team with company goals

143 lines 5.29 kB
/* eslint-disable max-lines-per-function */ import ansis from 'ansis'; import yargs, {} from 'yargs'; import { formatSchema, validate, } from '@code-pushup/models'; import { TERMINAL_WIDTH, isRecord } from '@code-pushup/utils'; import { descriptionStyle, formatNestedValues, formatObjectValue, headerStyle, titleStyle, } from './implementation/formatting.js'; import { logErrorBeforeThrow } from './implementation/global.utils.js'; import { getVersion } from './implementation/version.js'; export const yargsDecorator = { 'Commands:': `${ansis.green('Commands')}:`, 'Options:': `${ansis.green('Options')}:`, 'Examples:': `${ansis.green('Examples')}:`, boolean: ansis.blue('boolean'), count: ansis.blue('count'), string: ansis.blue('string'), array: ansis.blue('array'), required: ansis.blue('required'), 'default:': `${ansis.blue('default')}:`, 'choices:': `${ansis.blue('choices')}:`, 'aliases:': `${ansis.blue('aliases')}:`, }; /** * returns configurable yargs CLI for code-pushup * * @example * // bootstrap CLI; format arguments * yargsCli(hideBin(process.argv)).argv; */ export function yargsCli(argv, cfg) { const { usageMessage, scriptName, noExitProcess } = cfg; const commands = cfg.commands ?? []; const middlewares = cfg.middlewares ?? []; const options = cfg.options ?? {}; const groups = cfg.groups ?? {}; const examples = cfg.examples ?? []; const cli = yargs(argv); // setup yargs cli .updateLocale(yargsDecorator) // take minimum of TERMINAL_WIDTH or full width of the terminal .wrap(Math.max(TERMINAL_WIDTH, cli.terminalWidth())) .help('help', descriptionStyle('Show help')) .alias('h', 'help') .showHelpOnFail(false) .version('version', ansis.dim('Show version'), getVersion()) .check(args => { const persist = args['persist']; return persist == null || validatePersistFormat(persist); }) .parserConfiguration({ 'strip-dashed': true, }) .options(formatNestedValues(options, 'describe')); // use last argument for non-array options coerceArraysByOptionType(cli, options); // usage message if (usageMessage) { cli.usage(titleStyle(usageMessage)); } // script name if (scriptName) { cli.scriptName(scriptName); } // add examples examples.forEach(([exampleName, description]) => cli.example(exampleName, descriptionStyle(description))); // add groups Object.entries(groups).forEach(([groupName, optionNames]) => cli.group(optionNames, headerStyle(groupName))); // add middlewares middlewares.forEach(({ middlewareFunction, applyBeforeValidation }) => { cli.middleware(logErrorBeforeThrow(middlewareFunction), applyBeforeValidation); }); // add commands commands.forEach(commandObj => { cli.command(formatObjectValue({ ...commandObj, handler: logErrorBeforeThrow(commandObj.handler), ...(typeof commandObj.builder === 'function' && { builder: logErrorBeforeThrow(commandObj.builder), }), }, 'describe')); }); // this flag should be set for tests and debugging purposes // when there is an error and exitProcess is called, it suppresses the error message // more info here: https://yargs.js.org/docs/#api-reference-exitprocessenable if (noExitProcess) { cli.exitProcess(false); } // return CLI object return cli; } function validatePersistFormat(persist) { try { if (persist.format != null) { persist.format .flatMap(format => format.split(',')) .forEach(format => { validate(formatSchema, format); }); } return true; } catch { throw new TypeError(`Invalid persist.format option. Valid options are: ${formatSchema.options.join(', ')}`); } } function coerceArraysByOptionType(cli, options) { Object.entries(groupOptionsByKey(options)).forEach(([key, node]) => { cli.coerce(key, (value) => coerceNode(node, value)); }); } function coerceNode(node, value) { if (node.isLeaf) { if (node.options.type === 'array') { return node.options.coerce?.(value) ?? value; } return Array.isArray(value) ? value.at(-1) : value; } return Object.entries(node.children).reduce(coerceChildNode, value); } function coerceChildNode(value, [key, node]) { if (!isRecord(value) || !(key in value)) { return value; } return { ...value, [key]: coerceNode(node, value[key]) }; } function groupOptionsByKey(options) { return Object.entries(options).reduce(addOptionToTree, {}); } function addOptionToTree(tree, [key, value]) { if (!key.includes('.')) { return { ...tree, [key]: { isLeaf: true, options: value } }; } const [parentKey, childKey] = key.split('.', 2); const prevChildren = tree[parentKey] && !tree[parentKey].isLeaf ? tree[parentKey].children : {}; return { ...tree, [parentKey]: { isLeaf: false, children: addOptionToTree(prevChildren, [childKey, value]), }, }; } //# sourceMappingURL=yargs-cli.js.map