UNPKG

titanium

Version:

Command line interface for building Titanium SDK apps

167 lines (146 loc) 4.64 kB
import { Argument, Command, Option } from 'commander'; import { ticonfig } from './ticonfig.js'; import { TiError } from './tierror.js'; /** * Takes a Titanium CLI command config with flags, options, args, and * subcommands and adds them to the current Commander.js command context. * * @param {CLI} cli - The CLI instance. * @param {String} cmdName - The name of the command. * @param {Command} cmd - The Commander.js command instance. * @param {Object} conf - The CLI command config to apply. */ export function applyCommandConfig(cli, cmdName, cmd, conf) { if (conf.title) { cmd.title = conf.title; } if (conf.alias) { cmd.alias(conf.alias); } if (conf.flags) { for (const [name, meta] of Object.entries(conf.flags)) { cli.debugLogger.trace(`Adding "${cmdName}" flag: ${meta.abbr ? `-${meta.abbr}, ` : ''}--${name}`); const opt = new Option(`${meta.abbr ? `-${meta.abbr}, ` : ''}--${name}`, meta.desc); if (meta.default) { opt.default(meta.default); } if (meta.hidden) { opt.hideHelp(true); } cmd.addOption(opt); } } if (conf.options) { for (const [name, meta] of Object.entries(conf.options)) { if (name === 'sdk') { // --log-level and --sdk are now a global options cli.debugLogger.trace(`Skipping "${cmdName}" option: --${name}`); continue; } const long = `--${name}`; const opt = new Option(`${meta.abbr ? `-${meta.abbr}, ` : ''}${long} [value]`, meta.desc); if (meta.hidden) { opt.hideHelp(true); } if (meta.default !== undefined) { opt.default(meta.default); } if (optionExists(cmd, name)) { cli.debugLogger.trace(`Option "${name}" already exists, skipping`); continue; } cli.debugLogger.trace(`Adding "${cmdName}" option: ${meta.abbr ? `-${meta.abbr}, ` : ''}${long} [value]`); cmd.addOption(opt); if (typeof meta.callback === 'function') { cmd.on(`option:${opt.attributeName()}`, value => { cli.debugLogger.trace(`Firing --${name} option callback: ${value ?? opt.defaultValue}`); const result = meta.callback(value ?? opt.defaultValue); cmd.setOptionValue(opt.name(), result ?? value ?? opt.defaultValue); }); } conf.options[name] = new Proxy(meta, { set(obj, prop, value) { const orig = obj[prop]; obj[prop] = value; if (prop === 'required' && orig !== value) { cli.debugLogger.trace(`--${name} changed: ${prop}=${value}`); opt[prop] = value; } return true; } }); } if (conf.options['log-level'] && !conf.options.timestamp) { cli.debugLogger.trace(`Adding "${cmdName}" option: --timestamp`); cmd.option('--timestamp', 'displays a timestamp in front of log lines'); cmd.on('option:timestamp', () => { if (cli.ready) { cli.logger.timestampEnabled(true); } }); } } if (Array.isArray(conf.args)) { for (const meta of conf.args) { const v = meta.variadic ? '...' : ''; const fmt = meta.required ? `<${meta.name}${v}>` : `[${meta.name}${v}]`; const arg = new Argument(fmt, meta.desc); if (meta.default !== undefined) { arg.default(meta.default); } if (Array.isArray(meta.values)) { arg.choices(meta.values); } cli.debugLogger.trace(`Adding "${cmdName}" arg: ${fmt}`); cmd.addArgument(arg); } } if (conf.subcommands) { for (const [name, subconf] of Object.entries(conf.subcommands)) { cli.debugLogger.trace( `Adding subcommand "${name}"${ conf.defaultSubcommand === name ? ' (default)' : '' } to "${cmdName}"` ); const subcmd = new Command(name); subcmd .addHelpText('beforeAll', () => { cli.logger.bannerEnabled(true); cli.logger.skipBanner(false); cli.logger.banner(); }) .configureHelp({ helpWidth: ticonfig.get('cli.width', 80), showGlobalOptions: true, sortSubcommands: true }) .configureOutput({ outputError: msg => { // explicitly set the subcommand so the global error // handler can print the correct help screen cli.command = subcmd; throw new TiError(msg.replace(/^error:\s*/, '')); } }) .action((...args) => cli.executeCommand(args)); cli.applyConfig(name, subcmd, subconf); subcmd.conf = subconf; // subcommand shares parent command's module subcmd.module = cmd.module; cmd.addCommand(subcmd, { isDefault: conf.defaultSubcommand === name, hidden: !!subconf.hidden }); } } } function optionExists(ctx, name) { const exists = ctx.options.find(o => o.name() === name); if (exists) { return true; } if (ctx.parent) { return optionExists(ctx.parent, name); } return false; }