UNPKG

easy-cli-framework

Version:

A framework for building CLI applications that are robust and easy to maintain. Supports theming, configuration files, interactive prompts, and more.

289 lines (285 loc) 9.96 kB
'use strict'; var yargs = require('yargs'); /** * @packageDocumentation A framework for building CLI applications that are robust and easy to maintain. Supports theming, configuration files, interactive prompts, and more. * @module easy-cli */ /** * @class EasyCLI * The primary class for composing and running an EasyCLI application. * This class is responsible for managing the commands, global flags, and themes for the CLI. * It also handles the parsing of the arguments and executing the commands. * * @template TGlobalParams The global params for the CLI * * @example * ```typescript * const cli = new EasyCLI({ * executionName: 'my-cli', * }); * * const command = new EasyCLICommand(...); * cli.addCommand(command); * * cli.execute(); * ``` */ class EasyCLI { /** * Creates a new EasyCLI instance * * @param {EasyCLIConfig} [config={}] The configuration for the CLI */ constructor(config = {}) { var _a, _b, _c, _d; this.executionName = ''; this.defaultCommand = 'help'; this.commands = []; this.globalFlags = {}; this.verboseFlag = null; this.configFlag = null; this.configFile = null; this.executionName = (_a = config === null || config === undefined ? undefined : config.executionName) !== null && _a !== undefined ? _a : ''; this.commands = (_b = config === null || config === undefined ? undefined : config.commands) !== null && _b !== undefined ? _b : []; this.defaultCommand = (_c = config === null || config === undefined ? undefined : config.defaultCommand) !== null && _c !== undefined ? _c : 'help'; this.globalFlags = (_d = config === null || config === undefined ? undefined : config.globalFlags) !== null && _d !== undefined ? _d : {}; } /** * Set the theme for the CLI, will overwrite any existing theme, and this theme will be passed to all commands unless overridden. * * @param {EasyCLITheme} theme The theme to use * * @returns {EasyCLI} The EasyCLI instance * * @example * ```typescript * const theme = new EasyCLITheme(); * * const cli = new EasyCLI(); * cli.setTheme(theme); * ``` */ setTheme(theme) { this.theme = theme; return this; } /** * Set the configuration file for the CLI * * @param {EasyCLIConfigFile} config The configuration file to use * * @returns {EasyCLI} The EasyCLI instance * * @example * ```typescript * const configFile = new EasyCLIConfigFile({ * ... * }); * * const cli = new EasyCLI(); * cli.setConfigFile(configFile); * ``` */ setConfigFile(config) { this.configFile = config; return this; } /** * Dangerously sets all the commands for the CLI, overwriting any existing commands. * * @param {EasyCLICommand[]} commands The commands to add to the CLI * * @returns {EasyCLI} The EasyCLI instance * * @example * ```typescript * const command = new EasyCLICommand(...); * const cli = new EasyCLI(); * cli.setCommands([command]); * ``` */ setCommands(commands) { this.commands = commands; return this; } /** * Adds a command to the CLI * * @template TParams The params that this command accepts. * * @param {EasyCLICommand} command The command to add * * @returns {EasyCLI} The EasyCLI instance * * @example * ```typescript * const command = new EasyCLICommand(...); * const cli = new EasyCLI(); * cli.addCommand(command); * ``` */ addCommand(command) { this.commands.push(command); return this; } /** * Manage the verbose flag for the CLI * * @param {number} [defaultVerbosity=0] The default verbosity level * @param {Partial<CommandOption & { name: string }>} [overrides={}] Any overrides for the verbose flag * * @returns {EasyCLI} The EasyCLI instance * * @example * ```typescript * const cli = new EasyCLI(); * cli.handleVerboseFlag(0, { ... }); * ``` */ handleVerboseFlag(defaultVerbosity = 0, overrides = {}) { var _a, _b; this.verboseFlag = (_a = overrides === null || overrides === undefined ? undefined : overrides.name) !== null && _a !== undefined ? _a : 'verbose'; this.globalFlags = { ...this.globalFlags, [(_b = overrides === null || overrides === undefined ? undefined : overrides.name) !== null && _b !== undefined ? _b : 'verbose']: { alias: 'v', description: 'Set the verbosity level', type: 'count', default: defaultVerbosity, ...overrides, }, }; return this; } /** * Manage the configuration file flag for the CLI * * @param {Partial<CommandOption & { name: string }>} [overrides={}] Any overrides for the configuration file flag * * @returns {EasyCLI} The EasyCLI instance * * @example * ```typescript * const cli = new EasyCLI(); * * // This will add a `--config` flag to the CLI * cli.handleConfigFileFlag(); * * // This will add a `--my-config` flag to the CLI * cli.handleConfigFileFlag({ name: 'my-config' }); * ``` * */ handleConfigFileFlag(overrides = {}) { var _a, _b; if (!this.configFile) throw new Error('No configuration file provided'); this.configFlag = (_a = overrides === null || overrides === undefined ? undefined : overrides.name) !== null && _a !== undefined ? _a : 'config'; this.globalFlags = { ...this.globalFlags, [(_b = overrides === null || overrides === undefined ? undefined : overrides.name) !== null && _b !== undefined ? _b : 'config']: { alias: 'c', description: 'Specify a configuration file', type: 'string', ...overrides, }, }; return this; } /** * An internal method to get the default values for the global flags * * @returns The default values for the global flags */ getDefaults() { return Object.keys(this.globalFlags).reduce((acc, key) => { if (this.globalFlags[key].default !== undefined) { acc[key] = this.globalFlags[key].default; } return acc; }, {}); } /** * @returns A middleware function to load the configuration file */ configMiddleware() { return (argv) => { var _a; if (!this.configFile) return argv; // TODO: Extract the default values from the command args as well const command = this.commands.find(command => command.getNames().includes(argv['_'][0])); if (command === null || command === undefined ? undefined : command.skipConfigLoad()) return argv; const configPath = (_a = argv === null || argv === undefined ? undefined : argv[this === null || this === undefined ? undefined : this.configFlag]) !== null && _a !== undefined ? _a : null; const config = this.configFile.load(configPath, argv); const defaults = this.getDefaults(); if (command) { const commandDefaults = command.getDefaultArgv(); Object.assign(defaults, commandDefaults); } const argvRemovingDefaults = Object.keys(argv).reduce((acc, key) => { if (defaults[key] === argv[key]) return acc; acc[key] = argv[key]; return acc; }, {}); return { ...defaults, ...config, ...argvRemovingDefaults, // Ensure that the arguments passed in take precedence }; }; } /** * @returns A middleware function to set the verbosity level */ verboseMiddleware() { return (argv) => { var _a; if (this.verboseFlag && argv[this.verboseFlag]) (_a = this.theme) === null || _a === undefined ? undefined : _a.setVerbosity(argv[this.verboseFlag]); }; } /** * Run the CLI with the provided arguments. * * @param {((app: typeof yargs) => typeof yargs) | null} [callback=null] A callback to add additional configuration to the CLI via yargs * * @returns {Promise<void>} A promise that resolves when the CLI has finished executing * * @example * ```typescript * const cli = new EasyCLI(...); * cli.execute(); * ``` */ async execute(callback = null) { const app = yargs; app.scriptName(this.executionName); // Add the global flags Object.entries(this.globalFlags).forEach(([name, config]) => { app.option(name, config); }); // Add the commands this.commands.forEach(command => { command.setGlobalFlags(this.globalFlags); app.command(command.convertToYargsCommand(command.getName() === this.defaultCommand, // If this is the default command it needs to add $0 to the command this === null || this === undefined ? undefined : this.theme // Pass the theme to the command )); }); app.help().wrap(72); const middleware = []; middleware.push(this.configMiddleware()); middleware.push(this.verboseMiddleware()); app.middleware(middleware, true); // To add any additional configuration if (callback) { callback(app); } // Parse the arguments app.parse(); } } exports.EasyCLI = EasyCLI;