UNPKG

@codedungeon/gunner

Version:
1,057 lines (889 loc) • 33.1 kB
/*------------------------------------------------------------------------------------------- * Copyright (c) 2018-2021 Mike Erickson / Codedungeon. All rights reserved. * Licensed under the MIT license. See LICENSE in the project root for license information. * -----------------------------------------------------------------------------------------*/ const App = require('./toolbox/App') const path = require('path') const colors = require('chalk') const semver = require('semver') const shell = require('shelljs') const datetime = require('dayjs') const debug = require('dumper.js') const tildify = require('tildify') const api = require('./toolbox/api') const arrays = require('./toolbox/arrays') const config = require('./toolbox/config') const helpers = require('./toolbox/helpers') const prompts = require('./toolbox/prompt') const table = require('./toolbox/table.js') const utils = require('@codedungeon/utils') const strings = require('./toolbox/strings') const system = require('./toolbox/system.js') const template = require('./toolbox/template') const filesystem = require('./toolbox/filesystem') const environment = require('./toolbox/environment') const print = require('./toolbox/print')(this.quiet) const packageManager = require('./toolbox/packageManager') const Messenger = require('@codedungeon/messenger') const HELP_PAD = 28 const REQUIRED_MARK = 'āœ–ļøŽ' const SPACER = ' ' // 23 chars const log = (command, resource, args) => { if (resource?.length === 0) { return } let cmd = '' Object.keys(args).forEach((item) => { if (item !== 'anonymous' && item !== 'sub' && item !== 'log' && item.length > 1) { cmd += typeof args[item] === 'boolean' ? '--' + item + ' ' : '--' + item + ' ' + args[item] + ' ' } }) cmd = `${command} ${resource} ${cmd}`.trim() Messenger.loggerDebug(cmd) } class CLI { constructor(argv = [], projectRootDir = null, pkgInfo = null) { // argv = !argv ? process.argv : argv if (pkgInfo && pkgInfo.name === '@codedungeon/gunner') { require('./inspector.js').startup() } if (argv.length === 0) { argv.push(system.which('node')) argv.push(system.which('gunner')) } // load CLI globals require('./toolbox/globals').init() this.argv = argv this.fs = this.filesystem = require('./toolbox/filesystem') // get this early as it will be used during bootstrap // this.projectRoot = projectRootDir || this.fs.realpathSync(path.dirname(argv[1])) // this.projectRoot = projectRootDir || path.dirname(path.dirname(this.fs.realpathSync(argv[1]))) Messenger.loggerDebug('make sure this change works (see previous call)') print.warn('make sure this change works (see previous call)', 'gunner constructor') this.projectRoot = projectRootDir || path.dirname(this.fs.realpathSync(argv[1])) // console.log(this.projectRoot) // process.exit() this.app = new App({ projectRoot: this.projectRoot }) this.pkgInfo = require(path.join(this.fs.realpathSync(this.projectRoot), 'package.json')) this.versionInfo = this.pkgInfo.version this.name = this.pkgInfo.name this.appName = this.pkgInfo.packageName this.packageName = this.pkgInfo.packageName this.author = this.pkgInfo.author || '' this.contributors = this.pkgInfo.contributors || [] this.packageName = this.pkgInfo.packageName || '' this.command = this.getCommand(argv) this.commandName = this.getCommandName(argv) // sub command (see make:command for example) this.arguments = this.getArguments(argv, this) // setup global options this.verbose = this.arguments.verbose || false // dont add shortcut -v as that is reserved for version this.debug = this.arguments.debug || false this.overwrite = this.arguments.overwrite || this.arguments.o || false this.help = this.arguments.help || this.arguments.h || this.arguments.H || false this.quiet = this.arguments.quiet || this.arguments.q || false // help is activated if (this.arguments.help || this.arguments.h || this.arguments.H) { this.arguments.help = this.arguments.h = this.arguments.H = true } // version is activated if (this.arguments.version || this.arguments.v || this.arguments.V) { this.arguments.version = this.arguments.v = this.arguments.V = true } this.helpInfo = '' this.usageInfo = '' this.commandInfo = '' this.optionInfo = '' this.exampleInfo = '' this.versionStr = '' this.hookData = {} /** Setup Toolbox */ this.toolbox = { // toolbox properties (some replication, see `env` for more properties) appName: this.packageName, version: this.versionInfo, packageName: this.packageName, getOptionValue: this.getOptionValue, argumentHasOption: this.argumentHasOption, logToFile: log, // toolbox modules api, app: this.app, arguments: this.arguments, commandName: this.commandName, colors, config, debug, env: { appName: this.appName, arguments: this.arguments, command: this.command, commandName: this.commandName, name: this.name, debug: this.debug, overwrite: this.overwrite, packageName: this.packageName, projectRoot: this.projectRoot, verbose: this.verbose, version: this.versionInfo, }, filesystem: this.fs, fs: this.fs, datetime, path, packageManager, prompts, print, semver, strings, arrays, shell, system, table, template, utils, } // load project extensions (will be appended to app namespace) this.loadExtensions(this) // show gunner object properties if debug mode and verbose options supplied this.debug && this.verbose ? this.toolbox.table.verboseInfo(['Property', 'Value'], Object.entries(this)) : '' return this } src(path = '') { if (path.length > 0) { this.projectRoot = path } return this } run(commandInfo = {}) { return this.handleCommand(this.argv, commandInfo) } name(name = '') { this.packageName = name return this } usage(usage = '') { this.usageInfo = usage return this } help(help = '') { this.helpInfo = help return this } commands(command = '') { this.commandInfo = command return this } // options array example // .options([{option: 'xxx', description: 'yyy'}]) options(options = '') { let optionsType = typeof options if (optionsType === 'string' && options.length > 0) { this.optionInfo = options } else { let globalOptions = [ ' --debug, -d Debug Mode', ' --help, -h, -H Shows Help (this screen)', ' --log-dir Log directory (if different than default)', ' --verbose Show Verbose Help', ] if (Array.isArray(options)) { options.forEach((item) => { globalOptions.push(` --${item.option}`.padEnd(HELP_PAD + 6) + item?.description) }) } if (optionsType === 'object' && !Array.isArray(options)) { globalOptions.push(` --${options.option}`.padEnd(HELP_PAD + 6) + options?.description) } globalOptions.push(' --quiet, -q Quiet mode (suppress console output)') globalOptions.push(' --version, -v, -V Show Version') globalOptions.sort() this.toolbox.globalOptions = globalOptions this.optionInfo = globalOptions.join('\n') } return this } version(version = '') { if (version.length > 0) { this.versionStr = version } return this } logger(userOptions = null) { if (typeof userOptions === 'string' && userOptions !== 'system') { userOptions = { directory: userOptions.startsWith('./') ? path.resolve(this.projectRoot, userOptions) : userOptions, } } let options = { ...{ directory: 'system', filename: null, alwaysLog: true }, ...userOptions } if (options.alwaysLog) { this.toolbox.arguments.log = true } if (this.toolbox.arguments?.log) { let logName = options.filename ? options.filename : this.pkgInfo.name.replace('@codedungeon/', '').replace('/', '-') Messenger.initLogger(true, options.directory, logName) } return this } hooks(data = {}) { this.hookData = { ...data } return this } examples(examples = '') { this.exampleInfo = examples return this } formatInfo(info = '') { return ' ' + info } getCommand(argv) { let command = argv.length > 2 ? argv[2] : '<command>' if (command[0] == '-') { command = '' } return command } getCommandName(argv) { let parseArgs = require('minimist') // command name will be 4th position if (argv.length >= 4 && argv[3].startsWith('-')) { return '' } // do some hacking if argv contains --constructor let idx = argv.indexOf('--constructor') if (idx >= 0) { argv[idx] = ':constructor:' } // parse arguments let parsedArguments = parseArgs(argv) // restore --constructor if it exiss if (idx >= 0) { argv[idx] = '--constructor' } let commandName = parsedArguments._.length >= 4 ? parsedArguments._[3] : '' return commandName } getArguments(argv, module) { let parseArgs = require('minimist') // need to hack around a bit here since minimist thinks --constructor argument is bad let idx = argv.indexOf('--constructor') if (idx >= 0) { argv[idx] = '--tempConstructor' // quick rename } // now parse with possible temporary constructor argment let args = parseArgs(argv) // and if we did have a --construtor argument, restore if (idx >= 0) { args.constructor = true delete args['tempConstructor'] } let argKeys = Object.keys(args) if (args.hasOwnProperty('_')) { delete args['_'] } if (args) { let moduleRef = this.loadModule(module) if (moduleRef) { Object.keys(args).forEach((arg) => { if (moduleRef?.flags?.arg) { if (moduleRef.flags[arg].aliases) { const aliases = moduleRef.flags[arg].aliases aliases.forEach((alias) => { args[alias] = args[arg] }) } } }) // see if argument has an associated alias if (moduleRef?.flags) { const flags = Object.keys(moduleRef.flags) flags.forEach((flag) => { moduleRef.flags[flag]?.aliases?.forEach((alias) => { argKeys.includes(alias) ? (args[flag] = args[alias]) : null }) }) } } } return args } isModuleValid(module) { let hidden = module?.hidden if (module?.name) { if (module.name === 'default') { hidden = false } } return this.toolbox.utils.has(module, 'name') // && !hidden } isValidCommand(command) { const commands = this.app.getCommandList() return commands.includes(command) } loadModuleByCommand(command = null) { let commandFiles = this.filesystem.directoryList(this.app.getCommandPath(), { filesOnly: true }) for (let index = 0; index < commandFiles.length; index++) { let module = require(commandFiles[index]) if (module?.name === command) { return module } } return {} } loadModule(commandName = '') { let commandPath = this.app.getCommandPath() let filename = '' if (!filesystem.existsSync(commandPath)) { filesystem.mkdirSync(commandPath, { recursive: true }) } let commands = filesystem.directoryList(commandPath, { filesOnly: true }) commands.forEach((file) => { let module = require(file) if (module?.name && module?.name === commandName) { filename = file } }) return filename.length > 0 ? require(filename) : {} } hasRequiredArguments(module, args) { let missingArguments = [] for (let flag in module.flags) { let hasPrompt = module.flags[flag]?.prompt && module?.autoPrompt if (module.flags[flag]?.required && module.flags[flag].required && !hasPrompt) { let hasAlias = false if (!args[flag]) { if (module.flags[flag]?.aliases) { let alias = module.flags[flag].aliases[0] hasAlias = args?.alias } if (module.flags[flag]?.description) { !hasAlias ? missingArguments.push(module.flags[flag].description) : null } else { !hasAlias ? missingArguments.push(flag) : null } } } } return missingArguments } validateArguments(args, flags) { let valid = { status: true } // if command does not have flags, it will be valid if (flags) { Object.keys(flags).forEach((item) => { let flagType = flags[item]?.type ? flags[item].type : 'any' let value = args.hasOwnProperty(item) ? args[item] : null if (flagType === 'boolean') { if (typeof value === 'string' && flagType === 'boolean') { if (value === 'false') { value = false } if (value === 'true') { value = true } } } if (flagType === 'string' && typeof value === 'number') { value = value.toString() } if (flagType !== 'any' && value && flagType !== typeof value) { valid = { status: false, flag: item, message: `${item} expected ${flagType}, ${typeof value} supplied` } } }) } return valid } setDefaultFlags(cli, flags) { if (!flags) { return {} } let keys = Object.keys(flags) let defaults = {} keys.map((flag) => { let alias = '' if (flags[flag]?.hasOwnProperty('aliases')) { alias = flags[flag].aliases[0] } let defaultValue = this.toolbox.utils.dot.get(flags[flag].default) if (defaultValue === undefined) { defaultValue = null if (flag.type === 'boolean') { defaultValue = false } } defaults = Object.assign(defaults, { [flag]: this.toolbox.utils.dot.get( cli.arguments[flag] || this.toolbox.utils.dot.get(cli.arguments[alias] || defaultValue) ), [alias]: this.toolbox.utils.dot.get( cli.arguments[alias] || this.toolbox.utils.dot.get(cli.arguments[flag] || defaultValue) ), }) }) return defaults } argumentHasOption(args, needles) { if (typeof args === 'undefined') { this.print.error('Invalid Arguments') return false } if (typeof needles === 'undefined') { this.print.error('Invalid Option Value') return false } let items = typeof needles === 'string' ? needles.split(',') : needles for (let i = 0; i < items.length; i++) { if (items[i] === undefined) { return false } if (args.hasOwnProperty(items[i])) { return true } } return false } getOptionValue(args, optName, defaultValue = null) { if (this.argumentHasOption(args, optName)) { let options = typeof optName === 'string' ? [optName] : optName for (let i = 0; i < options.length; i++) { let option = options[i] if (args.hasOwnProperty(option)) { return args[option] } } return defaultValue } return defaultValue } /** * CLI Interface Commands */ showCommands() { this.fs.mkdirSync(this.app.getCommandPath(), { recursive: true }) let commandFiles = this.filesystem.directoryList(this.app.getCommandPath(), { filesOnly: true }) let commands = '' let projectHome = tildify(this.app.getCommandPath()) if (commandFiles.length === 0) { commands += this.toolbox.colors.red(' There are no defined commands\n') commands += this.toolbox.colors.red(` Please review your ${projectHome} directory\n`) } commandFiles.forEach((filename) => { let module = require(filename) let disabled = module.disabled || false let hidden = module.hidden || false if (!disabled && !hidden) { let name = module.name || '' if (module?.arguments) { if (Object.keys(module.arguments).length > 0) { name += ' <args>' } } if (module?.flags) { if (Object.keys(module.flags).length > 0) { name += ' [options]' } } let description = module?.description || `${module.name} command` if (name.length > 0) { commands += ' ' + name.padEnd(HELP_PAD + 4) + description + '\n' } } }) return commands } showDebugCommand(command) { if (this.debug) { let separator = '='.repeat(this.toolbox.utils.windowSize().width) if (command !== undefined && command !== '') { let subCommand = this.toolbox.commandName.length > 0 ? this.toolbox.commandName : 'n/a' console.log('\n') let msg = ' 🚦 DEBUG COMMAND: \n' + ` command name: ${command}\n` + ` subcommand name: ${subCommand}\n` + ' options / flags: ' + JSON.stringify(this.arguments).replace(/,/gi, ', ').replace(/:/gi, ': ') console.log(this.toolbox.colors.gray(separator)) this.toolbox.print.debug(msg) console.log(this.toolbox.colors.gray(separator)) process.exit(0) } } } showVersion(options = { simple: false }) { if (this.versionStr.length > 0) { console.log(this.versionStr) } else { let versionStr = this.pkgInfo.version let buildStr = this.pkgInfo?.build ? ` (${this.pkgInfo.build})` : '' const name = this.packageName.length > 0 ? this.packageName : this.toolbox.strings.titleCase(this.name) if (!options.simple) { console.log('') // give some breathing room when show --help } console.log(`🚧 ${this.toolbox.colors.blue.bold(name)} ${this.toolbox.colors.blue('v' + versionStr + buildStr)}`) if (!options.simple) { if (this.pkgInfo?.info) { let msg = colors.keyword('pink').dim.italic(this.pkgInfo.info) console.log(` ${msg}`) } else { if (this.contributors.length > 0) { let info = this.contributors[0] console.log( ` ${this.toolbox.colors.green.italic('Crafted with love by ' + info.name + ' (' + info?.url + ')')}` ) } } } console.log() } } showGlobalOptions() { if (this.optionInfo.length > 1) { console.log(colors.yellow('Global Options:')) console.log(this.optionInfo + '\n') } } showHelp(appName = '') { // if we have defined custom help, display it if (this.helpInfo.length > 0) { return this.toolbox.print.log(this.helpInfo) } // otherwise build CLI help if (this.command.length === 0) { this.showVersion() if (this.usageInfo.length > 0) { console.log(colors.yellow('Usage:')) console.log(this.formatInfo(this.usageInfo) + '\n') } console.log(colors.yellow('Commands:')) if (this.commandInfo.length > 0) { console.log(this.commandInfo + '\n') } else { let commands = this.showCommands() // make things pretty and colorful so they stand out commands = strings.replaceAll(commands, '<args>', colors.magenta('<args>')) commands = strings.replaceAll(commands, '[options]', colors.cyan('[options]')) if (commands.length > 0) { console.log(commands) } else { console.log(' No Command Available\n') } } if (this.optionInfo.length > 1) { console.log(colors.yellow('Global Options:')) console.log(this.optionInfo + '\n') } if (this.exampleInfo.length > 0) { console.log(colors.yellow('Examples:')) console.log(this.formatInfo(this.exampleInfo) + '\n') } } else { this.showCommandHelp(this.command) this.showCommandHelpExample(this.command) } } validateCommand(command) { if (!this.isValidCommand(command) && this.hookData.hasOwnProperty('commandPrefix')) { // command not found, try adding commandPrefix if (!command.includes(this.hookData?.commandPrefix)) { let adjustedCommand = this.hookData.commandPrefix + command if (this.isValidCommand(adjustedCommand)) { command = adjustedCommand } Messenger.write('info', `added commandPrefix ${command}`) } } return command } showCommandHelp(command = '') { command = this.validateCommand(command) if (this.commandInfo.length > 0) { return this.commandInfo } let module = this.loadModuleByCommand(command) if (!this.toolbox.utils.has(module, 'name')) { let commandFilename = strings.titleCase(strings.camelCase(command)) + '.js' let message = `Unable to locate valid command file matching ${command}` console.log(this.toolbox.colors.red(`\n🚫 ${message}\n`)) this.toolbox.print.note(`Make sure command such as ${commandFilename} exits\n`, 'TIP') process.exit(1) } if (module.disabled) { console.log('') this.toolbox.print.error(`Invalid Command "${command}"`, 'ERROR') let projectName = this.pkgInfo?.packageName || this.pkgInfo.name this.toolbox.print.warn(` For list of available commands, execute '${projectName} --help'\n`) process.exit(1) } let buildStr = this.pkgInfo?.build ? ` (${this.pkgInfo.build})` : '' console.log( `\n🚧 ${this.toolbox.colors.blue.bold(this.pkgInfo.packageName)} ${this.toolbox.colors.blue( 'v' + this.pkgInfo.version + buildStr )}` ) console.log('') console.log(this.toolbox.colors.cyan(`šŸ›  ${module.name}`)) // show module description, or built description if property does not exist let description = this.toolbox.utils.has(module, 'description') ? module.description : `${module.name} command` console.log(` ${description}`) if (module?.usage) { console.log(colors.yellow('\nUsage:')) console.log(' ' + module.usage) } let argSpacer = SPACER if (this.toolbox.utils.has(module, 'arguments')) { console.log(colors.yellow('\nArguments:')) for (const [key, value] of Object.entries(module.arguments)) { let required = value?.required ? this.toolbox.colors.red.bold(REQUIRED_MARK) : ' ' let hint = this.toolbox.utils.dot.get(module, `arguments.${key}.help`) || '' if (hint.length === 0) { hint = this.toolbox.utils.dot.get(module, `arguments.${key}.prompt.hint`) } // let hint = this.toolbox.utils.dot.get(module, `arguments.${key}.prompt.hint`) hint = hint?.length > 0 ? '(' + hint + ')' : '' console.log(` ${key}${argSpacer} ${required} ${value.description} ${colors.gray(hint)}`) } } console.log('') if (!module?.flags) { process.exit(0) } let keys = Object.keys(module.flags) let COL_WIDTH = 20 keys.forEach((key) => { if (key.length >= COL_WIDTH) { COL_WIDTH = key.length + 5 } }) if (keys.length > 0) { let flags = keys.filter((item) => { return !module.flags['name']?.hidden }) if (flags.length > 0) { console.log(this.toolbox.colors.yellow('Command Options:')) keys.forEach((flag) => { if (module.flags[flag]?.hidden) { return false } let description = module.flags[flag]['description'] || colors.yellow('<missing description>') let help = module.flags[flag]?.help ? module.flags[flag].help : '' // let hint = this.toolbox.utils.dot.get(module.flags[flag], 'prompt.hint') || '' // changed from hint to help 2021-08-30 help = help.length > 0 ? '(' + help + ')' : '' if (help.length > 0) { description += colors.gray(` ${help}`) } let defaultValue = '' if (module.flags[flag]?.default) { defaultValue = this.toolbox.colors.cyan('[default: ' + module.flags[flag].default + ']') defaultValue = defaultValue.replace(/,/gi, ', ') } let required = ' ' if (module.flags[flag]?.required) { required = this.toolbox.colors.red.bold(REQUIRED_MARK) // this.toolbox.colors.red('-required-') } let aliases = '' if (module.flags[flag]?.aliases && module.flags[flag].aliases.length > 0) { aliases = ', ' + '-' + module.flags[flag].aliases } let flags = '--' + flag + aliases let flagValue = flags.padEnd(HELP_PAD + 2) console.log(` ${flagValue}${required} ${description} ${defaultValue}`) if (module.flags[flag]?.prompt?.hint && this.arguments?.verbose) { let hint = ' | ' + module.flags[flag]?.prompt?.hint console.log(colors.gray.dim(`${hint}`)) } }) console.log('') console.log(this.toolbox.colors.red(' āœ–ļøŽ') + ' denotes required argument / options') console.log('') } } return `${module.name} help displayed` } showCommandHelpExample(command = '') { let module = this.loadModuleByCommand(command) if (!module.disabled) { if (module?.examples) { console.log(this.toolbox.colors.yellow('Examples:')) module.examples.forEach((example) => { console.log(' ' + this.appName + ' ' + example) }) console.log('') } } } executeHook(hook = '', command = '', args = []) { if (this.hookData.hasOwnProperty(hook)) { this.hookData[hook](this.toolbox, command, args) } } async executeCommand(command, args) { let output = '' command = this.validateCommand(command) this.executeHook('beforeExecute', command, args) if (command.length > 0) { if (!this.isValidCommand(command)) { this.toolbox.print.error(`Invalid Command "${command}"`, 'ERROR') this.toolbox.print.note(` For list of available commands, execute '${this.packageName} --help'\n`) process.exit(1) } let module = this.loadModuleByCommand(command) let disabled = module.disabled || false let argName = module?.arguments ? Object.keys(module.arguments)[0] : null let required = module?.arguments ? module.arguments[argName]?.required : false let prompt = module?.arguments ? module.arguments[argName]?.prompt && module.autoPrompt : false if (typeof prompt === 'undefined') { prompt = false } if (this.argv.length <= 3 && module?.arguments && argName && required && !prompt) { console.log('') if (module.arguments[argName]?.help) { this.toolbox.print.error(module.arguments[argName].help, 'ERROR') } else { this.toolbox.print.error('"' + module.arguments[argName].description + '"' + ' Required', 'ERROR') this.toolbox.print.warn(colors.dim(' ' + strings.raw(module.usage))) } process.exit(0) } else { if (prompt && !disabled) { let argName = module?.arguments ? Object.keys(module.arguments)[0] : '' let answers = await prompts.run(this, module) this.toolbox.arguments = { ...answers, ...this.toolbox.arguments } this.toolbox.arguments[argName] = this.commandName || answers[argName] || answers.commandName } } if (!disabled) { let requiredArguments = this.hasRequiredArguments(module, this.toolbox.arguments) if (module?.arguments) { let argName = Object.keys(module.arguments)[0] if (module.arguments[argName]?.required && this.commandName.length === 0) { requiredArguments.unshift(module.arguments[argName].description) } } if (requiredArguments.length > 0 && !prompt) { output = ' - ' + requiredArguments.join(', ') + '\n' console.log('') this.toolbox.print.error('Missing Required Arguments:', 'ERROR') requiredArguments.forEach((arg) => { this.toolbox.print.error(' - ' + arg) }) this.executeHook('afterExecute', command, args, output) return output } if (module?.execute && !disabled) { let validator = this.validateArguments(this.arguments, module.flags) if (!validator.status) { console.log('') print.error(validator.message, 'ERROR') print.info(' Review command help for details on arguments and flags') process.exit() } this.arguments = this.setDefaultFlags(this, module.flags) if (module.hasOwnProperty('arguments')) { let keys = Object.keys(module.arguments) this.toolbox[keys[0]] = this.commandName } let result = module.execute(this.toolbox) this.executeHook('afterExecute', command, args, result) } } else { console.log('') let output = `Invalid Command "${command}"` this.toolbox.print.error(output, 'ERROR') let packageName = this.pkgInfo?.packageName ? this.pkgInfo.packageName : this.pkgInfo.name this.toolbox.print.warn(` For list of available commands, execute '${packageName} --help'`) } return output } } handleCommand(argv, commandInfo = {}) { this.logToFile(argv, commandInfo.args || this.arguments) // special handle if default supplied in .run() commandInfo = argv.length >= 3 ? {} : commandInfo let command = commandInfo.name || this.command let args = commandInfo.args || this.arguments // if command not supplied, show help regardless if (command === '<command>') { command = '' this.command = '' } if (this.argumentHasOption(args, ['V', 'version'])) { console.log('') this.showVersion({ simple: true }) process.exit(0) } this.debug ? this.showDebugCommand(command) : null if (this.command.length > 0 && this.arguments.help) { this.showCommandHelp(this.command) this.showGlobalOptions() this.showCommandHelpExample(this.command) return } // if no command or --help supplied, use default command if it exists if (commandInfo?.default) { let defaultCommand = this.toolbox.strings.camelCase(commandInfo.default.replace('.js', '')) if (this.fs.exists(path.resolve(this.app.getCommandPath(), defaultCommand + '.js'))) { command = commandInfo.default } } else { if (command.length === 0 && !this.help) { let defaultCommand = path.resolve(this.app.getCommandPath(), 'default.js') if (this.fs.existsSync(defaultCommand)) { let module = require(defaultCommand) command = !module.disabled ? 'default' : command } } } return command.length > 0 ? this.executeCommand(command, args) : this.showHelp(this.appName) } loadExtensions(cli) { let extensionPath = this.app.getExtensionPath() if (!this.fs.existsSync(extensionPath)) { this.fs.mkdirSync(extensionPath) } let extensionFiles = this.fs.readdirSync(extensionPath) extensionFiles.forEach((filename) => { let extFilename = path.join(extensionPath, filename) let module = require(extFilename)(cli) }) } logToFile(argv = [], args = {}) { let command = argv[2] || '' let resource = argv[3] || '' if (resource.length === 0) { return } let cmd = '' Object.keys(args).forEach((item) => { if (item !== 'anonymous' && item !== 'sub' && item !== 'log' && item.length > 1) { cmd += typeof args[item] === 'boolean' ? '--' + item + ' ' : '--' + item + ' ' + args[item] + ' ' } }) cmd = `${command} ${resource} ${cmd}`.trim() Messenger.loggerDebug(cmd) } } module.exports = { CLI, utils, } // expose toolbox module.exports.api = api module.exports.arrays = arrays module.exports.colors = colors module.exports.config = config module.exports.environment = environment module.exports.filesystem = filesystem module.exports.helpers = require('./toolbox/helpers') module.exports.packageManager = packageManager module.exports.print = print module.exports.prompt = prompts module.exports.strings = strings module.exports.system = system module.exports.table = table module.exports.template = template module.exports.utils = utils module.exports.path = path module.exports.shell = shell module.exports.semver = semver