UNPKG

serverless-spy

Version:

CDK-based library for writing elegant integration tests on AWS serverless architecture and an additional web console to monitor events in real time.

312 lines (274 loc) 8.23 kB
"use strict"; const GetterSetter = require('./utils').GetterSetter; const isPromise = require('./utils').isPromise; const Command = require('./command'); const parseArgs = require('micromist'); const Help = require('./help'); const path = require('path'); const Autocomplete = require('./autocomplete'); const createLogger = require('./logger').createLogger; const tabtabComplete = require('tabtab/src/complete'); const WrongNumberOfArgumentError = require('./error/wrong-num-of-arg'); const kebabCase = require('lodash').kebabCase; class Program extends GetterSetter { constructor() { super(); this._commands = []; this._helper = new Help(this); this.version = this.makeGetterSetter('version'); this.name = this.makeGetterSetter('name'); this.description = this.makeGetterSetter('description'); this.logger = this.makeGetterSetter('logger'); this.bin = this.makeGetterSetter('bin'); this._bin = path.basename(process.argv[1] || 'caporal'); this._autocomplete = new Autocomplete(this); this._supportedShell = ['bash', 'zsh', 'fish']; this.logger(createLogger()); this._defaultCommand = null; } /** * @private */ _help(cmdStr) { const cmd = this._commands.filter(c => (c.name() === cmdStr || c.getAlias() === cmdStr))[0]; const help = this._helper.get(cmd); console.log(help); return help; } /** * Add a global help for your program * * @param {String} help - Help string * @returns {Program} */ help(help, options) { this._helper._addCustomHelp(help, options); return this; } /** * @private */ getCommands() { return this._commands; } /** * Reset all commands * * @private * @returns {Program} */ reset() { this._commands = []; this._defaultCommand = null; return this; } /** * * @param synospis * @param description * @returns {Command} */ command(synospis, description) { const cmd = new Command(synospis, description, this); this._commands.push(cmd); return cmd; } /** * @param {Error} errObj - Error object * @private */ fatalError(errObj) { if(this.verbose) { this.logger().error("\n" + errObj.stack); } else { this.logger().error("\n" + errObj.message); } process.exit(2); } /** * Find the command to run, based on args, performs checks, then run it * * @param {Array} args - Command arguments * @param {Object} options - Options passed * @returns {*} * @private */ _run(args, options) { if (args[0] === 'help') { this._help(args.slice(1).join(' ')); return process.exit(0); } let argsCopy = args.slice(); const commandArr = []; /** * @type {Command} */ let cmd; while(!cmd && argsCopy.length) { commandArr.push(argsCopy.shift()); const cmdStr = commandArr.join(' '); cmd = this._commands.filter(c => (c.name() === cmdStr || c.getAlias() === cmdStr))[0]; } if (options.V || options.version) { console.log(this.version()); return process.exit(0); } if(!cmd) { let _filter = this._commands.filter(c => c._default === true); if(_filter.length > 0) { cmd = _filter[0]; argsCopy = args.slice(); } } if (!cmd && this._getDefaultCommand()) { cmd = this._getDefaultCommand(); argsCopy = args.slice(); } if (!cmd) { this._help(args.join(' ')); return process.exit(1); } if (options.help || options.h) { this._help(args.join(' ')); return process.exit(0); } if (process.env.CAPORAL_LOGGER_LEVEL !== undefined && Object.keys(this.logger().levels).indexOf(process.env.CAPORAL_LOGGER_LEVEL) > -1) { this._changeLogLevel(process.env.CAPORAL_LOGGER_LEVEL); } // If quiet, only output warning & errors if (options.quiet || options.silent) { this._changeLogLevel('warn'); // verbose mode } else if (options.v || options.verbose) { this._changeLogLevel('debug'); this.verbose = true; } let validated; try { validated = cmd._validateCall(argsCopy, options); if (isPromise(validated)) { return validated .then(v => cmd._run(v.args, v.options)) .catch(err => { throw this.fatalError(err); }) } } catch(err) { return this.fatalError(err); } return cmd._run(validated.args, validated.options); } /** * * @private */ _changeLogLevel(level) { const logger = this.logger(); Object.keys(logger.transports).forEach(t => logger.transports[t].level = level) } /** * Sets a unique action for the program * * @param {Function} action - Action to run */ action(action) { this._getDefaultCommand(true).action(action); return this; } /** * Set an option on the default command * * @param {String} synopsis - Option synopsis like '-f, --force', or '-f, --file <file>' * @param {String} description - Option description * @param {String|RegExp|Function|Number|Array} [validator] - Option validator, used for checking or casting * @param {*} [defaultValue] - Default value * @param {Boolean} [required] - Is the option itself required * @returns {Program} */ option(synopsis, description, validator, defaultValue, required) { const cmd = this._getDefaultCommand(true); let args = Array.prototype.slice.call(arguments); cmd.option.apply(cmd, args); return this; } /** * Add an argument to the default command * * @param {String} synopsis - Argument synopsis like `<my-argument>` or `[my-argument]`. * Angled brackets (e.g. `<item>`) indicate required input. Square brackets (e.g. `[env]`) indicate optional input. * @param {String} description - Option description * @param {String|RegExp|Function|Number|Array} [validator] - Option validator, used for checking or casting * @param {*} [defaultValue] - Default value * @public * @returns {Command} */ argument(synopsis, description, validator, defaultValue) { const cmd = this._getDefaultCommand(true); let args = Array.prototype.slice.call(arguments); cmd.argument.apply(cmd, args); return cmd; } /** * * @returns {Command} * @private */ _getDefaultCommand(create) { if (!this._defaultCommand && create) { this._defaultCommand = this.command('_default', 'Default command'); } return this._defaultCommand; } /** * @private * @param args * @param argv * @returns {*} */ _handleCompletionCommand(args, argv) { if(argv[1] === '--') { return this._autocomplete.listen({"_" : args._}); } const complete = new tabtabComplete({name: this.bin()}); if (typeof argv[1] !== "string" || this._supportedShell.indexOf(argv[1]) === -1) { this.fatalError(new WrongNumberOfArgumentError(`A valid shell must be passed (${this._supportedShell.join('/')})`, {}, this)); } else { this.logger().info(complete.script(this.bin(), this.bin(), argv[1].toLowerCase())); } } /** * Parse command line arguments. * @param {Array} [argv] argv * @public */ parse(argv) { const argvSlice = argv.slice(2); let cmd = this._commands.filter(c => (c.name() === argvSlice[0] || c.getAlias() === argvSlice[0]))[0]; if (!cmd) cmd = this._getDefaultCommand(false); const args = parseArgs(argv, cmd ? cmd.parseArgsOpts : {}); let options = Object.assign({}, args); delete options._; if (args._[0] === 'completion') { return this._handleCompletionCommand(args, argvSlice); } return this._run(args._, options); } /** * Execute input command with given arguments & options * @param args * @param options * @public */ exec(args, options) { const kebabOptions = Object.keys(options).reduce(function(result, key) { result[kebabCase(key)] = options[key]; return result; }, {}); return this._run(args, kebabOptions); } } const constants = require('./constants'); Object.keys(constants).map(c => Program.prototype[c] = constants[c]); module.exports = Program;