UNPKG

salesforce-alm

Version:

This package contains tools, and APIs, for an improved salesforce.com developer experience.

388 lines (386 loc) 16.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ToolbeltCommand = void 0; /* * Copyright (c) 2020, salesforce.com, inc. * All rights reserved. * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ const path = require("path"); const command_1 = require("@salesforce/command"); const ts_types_1 = require("@salesforce/ts-types"); const core_1 = require("@salesforce/core"); const kit_1 = require("@salesforce/kit"); const indexErrorProcessor_1 = require("./lib/indexErrorProcessor"); const logger = require("./lib/core/logApi"); const Org = require("./lib/core/scratchOrgApi"); // This should use the new message framework, but it is an old legacy method so fine for now. const Messages = require("./lib/messages"); const messages = Messages(); class ToolbeltCommand extends command_1.SfdxCommand { /** * In order to get the help property displayed during --help, we need to append it onto the description. * This means that all commands that extend ToolbeltCommand can't use `public static readme description = ''`. * To get around this, we use the `theDescription` name instead. * * When commands migrate to SfdxCommand, they should change the messages for description to include both the * description and the help messages. */ static get description() { let description = this.theDescription; if (this.deprecated) { description = `(deprecated) ${description}\n\nWARNING: ${logger.formatDeprecationWarning(this.id, this.deprecated, 'command')}`; } if (this.extraHelp) { description += `\n\n${this.extraHelp}`; } if (this.help) { description += `\n\n${this.help}`; } return description; } static get extraHelp() { let extraHelp = ''; // It would be nice to have this in SfdxCommand, but until there is a way // dynamically add info to a static property without making it a getter, // we don't have many options here. if (this.requiresProject) { extraHelp += 'NOTE: This command must be run from within a project.'; } return extraHelp; } static get flags() { const cmdFlags = super.flags; if (this.supportsPerfLogLevelFlag) { // Should this be part of SfdxCommand? cmdFlags['perflog'] = command_1.flags.boolean({ description: messages.getMessage('perfLogLevelOption'), longDescription: messages.getMessage('perfLogLevelOptionLong'), }); } if (this.schema) { cmdFlags['confighelp'] = command_1.flags.boolean({ description: messages.getMessage('schemaInfoOption', this.schema.flag), longDescription: messages.getMessage('schemaInfoOptionLong'), }); } Object.keys(cmdFlags).forEach((flagName) => { const flag = cmdFlags[flagName]; if (flag.deprecated && !flag.description.startsWith('(deprecated)')) { flag.description = `(deprecated) ${flag.description}`; } }); return cmdFlags; } /** * Call to stringify parsed flags for backward compatibility. * Don't invoke this if you wish to use new-style parsed flags. */ stringifyFlags() { Object.keys(this.flags).forEach((name) => { const flag = this.flags[name]; if (flag == null) { return; } switch (typeof this.flags[name]) { case 'string': case 'number': this.flags[name] = flag + ''; break; case 'boolean': break; case 'object': if (Array.isArray(flag)) { this.flags[name] = flag.join(','); break; } else if (flag instanceof Date) { this.flags[name] = flag.toISOString(); break; // This used to be (flag instanceof Duration) but this stopped working and I'm not sure why. // I had a similar problem with SfdxError in command here: // https://github.com/forcedotcom/cli-packages/pull/75 } else if (flag.constructor.name === 'Duration') { this.flags[name] = flag.quantity + ''; break; } else if (ts_types_1.isFunction(flag.toString)) { this.flags[name] = flag.toString(); break; } // intentional fallthrough default: throw new core_1.SfdxError(`Unexpected value type for flag ${name}`, 'UnexpectedFlagValueType'); } }); } /** * This is a bridge to for all old cli-engine commands. * All new oclif commands should be refactored to NOT use * this method anymore, and should move the old Command classes * in lib that have a validate and execute method to be in the * actual oclif command class. * * TODO Provide instructions on how to get people to move off of execLegacyCommand * * Remove src/lib/path/to/*Command file and put logic in the oclif/force/path/to/command file * * Change getColumnData to protected static results {} // TODO more info here. * * Remove getHumanSuccessMessage and just put it at the end of your run command * * Remove getHumanErrorMessage and throw an SfdxError * * ... * * @param command */ async execLegacyCommand(command, context, stdin) { // TODO: REMOVE THIS WHEN IT'S IN SfdxCommand // Reset process.exitCode since during testing we only soft exit. process.exitCode = undefined; if (this.statics.supportsPerfLogLevelFlag && context.flags.perflog === true) { context.org.force.setCallOptions('perfOption', 'MINIMUM'); } const logger = require('./lib/core/logApi'); logger.setHumanConsumable(!context.flags.json && !context.flags.quiet); context.logger = logger; if (ts_types_1.isFunction(command.validate)) { context = (await command.validate(context)) || context; } try { const results = await command.execute(context, stdin); this.legacyOutput(command, results); return results; } catch (error) { if (ts_types_1.isFunction(command.getHumanErrorMessage)) { const humanMessage = command.getHumanErrorMessage(error); if (humanMessage) { kit_1.set(error, 'message', humanMessage); } } /** * Legacy FCT errors sometimes used a result attribute, but newer code is using SfdxError which has a more * generic 'data' attribute. In determining if we need to display a table for row data we will also look in * error.data for row information. * * Going forward we need to deprecate result attributes in error objects. */ const errorResultForTable = error.result || error.data; /** * I would think it would be better to use isArray(errorResult) and I thought about changing it. However, * I decided against it because isArray is slightly more narrow in type inference than isObject. Might * break something. Note: isObject([]) evaluates to true and ux.table takes and any[] */ if (error.columns && ts_types_1.isObject(errorResultForTable)) { this.ux.table(errorResultForTable, { columns: error.columns }); } // TODO This might look a little different than it use to... // because of getErrorMessage and the ux.table while still // throwing. Make sure it is OK throw error; } } /** * returns true if a wildcard like expansion or behavior is detected * * @param param the next parameter passed into the cli */ checkIfWildcardError(param) { if (param) { return (this.argv.length > 2 && (this.id.includes('source:deploy') || this.id.includes('source:retrieve')) && param.indexOf('-') != 0 && // if wildcard param will be path, can't start with '-', but flags have to start with '-' param.indexOf('"') <= param.indexOf(', ') && param.indexOf(path.sep) >= 0); } else { return false; } } /** * SfdxError.wrap does not keep actions, so we need to convert ourselves if using almError * * @param err */ async catch(err) { // Let oclif handle exit signal errors. if (ts_types_1.getString(err, 'code') === 'EEXIT') { throw err; } let project; let appConfig = {}; try { project = await core_1.SfdxProject.resolve(); appConfig = await project.resolveProjectConfig(); // eslint-disable-next-line no-empty } catch (noopError) { } // AuthInfo is a @salesforce/core centric thing. We should convert this message in core // which makes more sense. if (err.name === 'NamedOrgNotFound') { kit_1.set(err, 'name', 'NoOrgFound'); try { const username = err.message.match(/No AuthInfo found for name (.*)/)[1]; kit_1.set(err, 'message', messages.getMessage('namedOrgNotFound', username)); // eslint-disable-next-line no-empty } catch (err) { } // In the offcase the match fails, don't throw a random error // If this is a parse error then this.flags.json may not be defined. // So look on the argv list. if (this.argv.includes('--json')) { this.ux.warn('The error message "NoOrgFound" has been deprecated and will be removed in v46 or later. It will become "NamedOrgNotFound".'); } if (this.statics.requiresUsername) { return super.catch(err); } } try { let context = {}; try { context = await this.resolveLegacyContext(); // eslint-disable-next-line no-empty } catch (e) { } // TODO Processors should be moved to command?? const processors = indexErrorProcessor_1.getProcessors(appConfig, context, err); for (const processor of processors) { await processor; } } catch (newError) { err = newError; } if (!(err instanceof core_1.SfdxError)) { const sfdxErr = core_1.SfdxError.wrap(err); if (ts_types_1.has(err, 'action')) { if (!sfdxErr.actions) { sfdxErr.actions = []; } if (ts_types_1.isArray(err.action)) { for (const action of err.action) { if (ts_types_1.isString(action)) { sfdxErr.actions.push(action); } } } else if (ts_types_1.isString(err.action)) { sfdxErr.actions.push(err.action); } } const index = this.argv.indexOf('-p') > 0 ? this.argv.indexOf('-p') : 0; const param = this.argv[index + 2]; // should be flag if (this.checkIfWildcardError(param)) { // makes sure that the next arg is +2 from this and starts with - or exists sfdxErr.message = messages.getMessage('WildCardError'); } if (ts_types_1.has(err, 'result')) { sfdxErr.data = err.result; } return super.catch(sfdxErr); } return super.catch(err); } async resolveLegacyContext() { if (this.legacyContext) { return this.legacyContext; } const logger = require('./lib/core/logApi'); logger.setHumanConsumable(!this.flags.json && !this.flags.quiet); this.stringifyFlags(); const legacyArgs = []; Object.keys(this.args || {}).forEach((argKey) => { const val = this.args[argKey] || ''; legacyArgs.push(`${argKey}=${val}`); }); // We need just the args, not the flags in argv as well const strict = this.statics.strict; if (!strict) { const { args, argv } = this.parse({ flags: this.statics.flags, args: this.statics.args, strict, }); const argVals = Object.values(args); const varargs = argv.filter((val) => !argVals.includes(val)); varargs.forEach((argKey) => { legacyArgs.push(argKey); }); } Object.keys(this.varargs || {}).forEach((argKey) => { const val = this.varargs[argKey] || ''; legacyArgs.push(`${argKey}=${val}`); }); const context = { flags: this.flags, args: legacyArgs, varargs: this.varargs, ux: this.ux, }; if (this.org || this.hubOrg) { const org = this.org || this.hubOrg; const username = org.getUsername(); if (this.flags.apiversion) { // The legacy force gets the apiVersion for the config. We could set that on the // new instance we create, but anyone creating their own version of force would // experience a problem. Hack the envs to get it use the flag version across the board. process.env.SFDX_API_VERSION = this.flags.apiversion; } context.org = await Org.create(username); } this.legacyContext = context; kit_1.set(this.legacyContext, 'command.flags', Object.values(this.ctor.flags)); return context; } legacyOutput(command, obj) { // For tables with no results we will display a simple message "No results found" if (Array.isArray(obj) && obj.length < 1) { this.ux.log(messages.getMessage('noResultsFound')); return; } // If the command produces tabular output if (ts_types_1.isFunction(command.getColumnData)) { const columnData = command.getColumnData(); // If the output is an object we are assuming multiple table are being displayed. if (ts_types_1.isPlainObject(columnData)) { // Each table for (const key of Object.keys(columnData)) { const val = columnData[key]; const rows = ts_types_1.get(obj, key); if (kit_1.isEmpty(rows)) { // If the rows are empty provide a nice message and a way to customize the message. let message = messages.getMessage('noResultsFound'); if (command.getEmptyResultMessage) { const _message = command.getEmptyResultMessage(key); if (_message != null) { message = _message; } } this.ux.log(message); } else { // One or more row s are available. this.ux.table(ts_types_1.getArray(obj, key), { columns: val }); // separate the table by a blank line. this.ux.log(); } } } else { // Single output this.ux.table(obj, { columns: columnData }); } } else { const message = command.getHumanSuccessMessage && command.getHumanSuccessMessage(obj); if (message != null && message !== '') { this.ux.log(message); } } } // TypeScript does not yet have assertion-free polymorphic access to a class's static side from the instance side get statics() { return this.constructor; } } exports.ToolbeltCommand = ToolbeltCommand; ToolbeltCommand.supportsPerfLogLevelFlag = false; //# sourceMappingURL=ToolbeltCommand.js.map