UNPKG

17 kBJavaScriptView Raw
1"use strict";
2/*
3* Copyright (c) 2019, salesforce.com, inc.
4* All rights reserved.
5* Licensed under the BSD 3-Clause license.
6* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
7*/
8Object.defineProperty(exports, "__esModule", { value: true });
9const command_1 = require("@salesforce/command");
10const ts_types_1 = require("@salesforce/ts-types");
11const core_1 = require("@salesforce/core");
12const kit_1 = require("@salesforce/kit");
13const indexErrorProcessor_1 = require("./lib/indexErrorProcessor");
14const perfMetricsRequest_1 = require("./lib/perfMetricsRequest");
15const logger = require("./lib/core/logApi");
16const Org = require("./lib/core/scratchOrgApi");
17const srcDevUtil = require("./lib/core/srcDevUtil");
18// This should use the new message framework, but it is an old legacy method so fine for now.
19const Messages = require("./lib/messages");
20const messages = Messages();
21class ToolbeltCommand extends command_1.SfdxCommand {
22 /**
23 * In order to get the help property displayed during --help, we need to append it onto the description.
24 * This means that all commands that extend ToolbeltCommand can't use `public static readme description = ''`.
25 * To get around this, we use the `theDescription` name instead.
26 *
27 * When commands migrate to SfdxCommand, they should change the messages for description to include both the
28 * description and the help messages.
29 */
30 static get description() {
31 let description = this.theDescription;
32 if (this.deprecated) {
33 description = `(deprecated) ${description}\n\nWARNING: ${logger.formatDeprecationWarning(this.id, this.deprecated, 'command')}`;
34 }
35 if (this.extraHelp) {
36 description += `\n\n${this.extraHelp}`;
37 }
38 if (this.help) {
39 description += `\n\n${this.help}`;
40 }
41 return description;
42 }
43 static get extraHelp() {
44 let extraHelp = '';
45 // It would be nice to have this in SfdxCommand, but until there is a way
46 // dynamically add info to a static property without making it a getter,
47 // we don't have many options here.
48 if (this.requiresProject) {
49 extraHelp += 'NOTE: This command must be run from within a project.';
50 }
51 return extraHelp;
52 }
53 static get flags() {
54 const cmdFlags = super.flags;
55 if (this.supportsPerfLogLevelFlag) {
56 // Should this be part of SfdxCommand?
57 cmdFlags['perflog'] = command_1.flags.boolean({
58 description: messages.getMessage('perfLogLevelOption'),
59 longDescription: messages.getMessage('perfLogLevelOptionLong')
60 });
61 }
62 if (this.schema) {
63 cmdFlags['confighelp'] = command_1.flags.boolean({
64 description: messages.getMessage('schemaInfoOption', this.schema.flag),
65 longDescription: messages.getMessage('schemaInfoOptionLong')
66 });
67 }
68 Object.keys(cmdFlags).forEach(flagName => {
69 const flag = cmdFlags[flagName];
70 if (flag.deprecated && !flag.description.startsWith('(deprecated)')) {
71 flag.description = `(deprecated) ${flag.description}`;
72 }
73 });
74 return cmdFlags;
75 }
76 /**
77 * Call to stringify parsed flags for backward compatibility.
78 * Don't invoke this if you wish to use new-style parsed flags.
79 */
80 stringifyFlags() {
81 Object.keys(this.flags).forEach(name => {
82 const flag = this.flags[name];
83 if (flag == null)
84 return;
85 switch (typeof this.flags[name]) {
86 case 'string':
87 case 'number':
88 this.flags[name] = flag + '';
89 break;
90 case 'boolean':
91 break;
92 case 'object':
93 if (Array.isArray(flag)) {
94 this.flags[name] = flag.join(',');
95 break;
96 }
97 else if (flag instanceof Date) {
98 this.flags[name] = flag.toISOString();
99 break;
100 // This used to be (flag instanceof Duration) but this stopped working and I'm not sure why.
101 // I had a similar problem with SfdxError in command here:
102 // https://github.com/forcedotcom/cli-packages/pull/75
103 }
104 else if (flag.constructor.name === 'Duration') {
105 this.flags[name] = flag.quantity + '';
106 break;
107 }
108 else if (ts_types_1.isFunction(flag.toString)) {
109 this.flags[name] = flag.toString();
110 break;
111 }
112 // intentional fallthrough
113 default:
114 throw new core_1.SfdxError(`Unexpected value type for flag ${name}`, 'UnexpectedFlagValueType');
115 }
116 });
117 }
118 /**
119 * This is a bridge to for all old cli-engine commands.
120 * All new oclif commands should be refactored to NOT use
121 * this method anymore, and should move the old Command classes
122 * in lib that have a validate and execute method to be in the
123 * actual oclif command class.
124 *
125 * TODO Provide instructions on how to get people to move off of execLegacyCommand
126 * * Remove src/lib/path/to/*Command file and put logic in the oclif/force/path/to/command file
127 * * Change getColumnData to protected static results {} // TODO more info here.
128 * * Remove getHumanSuccessMessage and just put it at the end of your run command
129 * * Remove getHumanErrorMessage and throw an SfdxError
130 * * ...
131 * @param command
132 */
133 async execLegacyCommand(command, context, stdin) {
134 // TODO: REMOVE THIS WHEN IT'S IN SfdxCommand
135 // Reset process.exitCode since during testing we only soft exit.
136 process.exitCode = undefined;
137 if (this.statics.supportsPerfLogLevelFlag && (context.flags.perflog === true)) {
138 context.org.force.setCallOptions('perfOption', 'MINIMUM');
139 }
140 const logger = require('./lib/core/logApi');
141 logger.setHumanConsumable(!context.flags.json && !context.flags.quiet);
142 context.logger = logger;
143 if (ts_types_1.isFunction(command.validate)) {
144 context = await command.validate(context) || context;
145 }
146 try {
147 const results = await command.execute(context, stdin);
148 this.legacyOutput(command, results);
149 return results;
150 }
151 catch (error) {
152 if (ts_types_1.isFunction(command.getHumanErrorMessage)) {
153 const humanMessage = command.getHumanErrorMessage(error);
154 if (humanMessage) {
155 kit_1.set(error, 'message', humanMessage);
156 }
157 }
158 /**
159 * Legacy FCT errors sometimes used a result attribute, but newer code is using SfdxError which has a more
160 * generic 'data' attribute. In determining if we need to display a table for row data we will also look in
161 * error.data for row information.
162 *
163 * Going forward we need to deprecate result attributes in error objects.
164 */
165 const errorResultForTable = error.result || error.data;
166 /**
167 * I would think it would be better to use isArray(errorResult) and I thought about changing it. However,
168 * I decided against it because isArray is slightly more narrow in type inference than isObject. Might
169 * break something. Note: isObject([]) evaluates to true and ux.table takes and any[]
170 */
171 if (error.columns && ts_types_1.isObject(errorResultForTable)) {
172 this.ux.table(errorResultForTable, { columns: error.columns });
173 }
174 // TODO This might look a little different than it use to...
175 // because of getErrorMessage and the ux.table while still
176 // throwing. Make sure it is OK
177 throw error;
178 }
179 }
180 /**
181 * returns true if a wildcard like expansion or behavior is detected
182 * @param param the next parameter passed into the cli
183 */
184 checkIfWildcardError(param) {
185 return this.argv.length > 2 &&
186 (this.id.includes('source:deploy') || this.id.includes('source:retrieve')) &&
187 (this.argv.indexOf('-p') >= 0) && //makes sure -p flag present
188 (param.indexOf('-') != 0); //if wildcard param will be path, can't start with '-', but flags have to start with '-'
189 }
190 /**
191 * SfdxError.wrap does not keep actions, so we need to convert ourselves if using almError
192 * @param err
193 */
194 async catch(err) {
195 // Let oclif handle exit signal errors.
196 if (ts_types_1.getString(err, 'code') === 'EEXIT') {
197 throw err;
198 }
199 let project;
200 let appConfig = {};
201 try {
202 project = await core_1.SfdxProject.resolve();
203 appConfig = await project.resolveProjectConfig();
204 }
205 catch (noopError) { }
206 // AuthInfo is a @salesforce/core centric thing. We should convert this message in core
207 // which makes more sense.
208 if (err.name === 'NamedOrgNotFound') {
209 kit_1.set(err, 'name', 'NoOrgFound');
210 try {
211 const username = err.message.match(/No AuthInfo found for name (.*)/)[1];
212 kit_1.set(err, 'message', messages.getMessage('namedOrgNotFound', username));
213 }
214 catch (err) { } // In the offcase the match fails, don't throw a random error
215 // If this is a parse error then this.flags.json may not be defined.
216 // So look on the argv list.
217 if (this.argv.includes('--json')) {
218 this.ux.warn('The error message "NoOrgFound" has been deprecated and will be removed in v46 or later. It will become "NamedOrgNotFound".');
219 }
220 if (this.statics.requiresUsername) {
221 return super.catch(err);
222 }
223 }
224 try {
225 let context = {};
226 try {
227 context = await this.resolveLegacyContext();
228 }
229 catch (e) { }
230 // TODO Processors should be moved to command??
231 const processors = indexErrorProcessor_1.getProcessors(appConfig, context, err);
232 for (const processor of processors) {
233 await processor;
234 }
235 }
236 catch (newError) {
237 err = newError;
238 }
239 if (!(err instanceof core_1.SfdxError)) {
240 const sfdxErr = core_1.SfdxError.wrap(err);
241 if (ts_types_1.has(err, 'action')) {
242 if (!sfdxErr.actions) {
243 sfdxErr.actions = [];
244 }
245 if (ts_types_1.isArray(err.action)) {
246 for (const action of err.action) {
247 if (ts_types_1.isString(action)) {
248 sfdxErr.actions.push(action);
249 }
250 }
251 }
252 else if (ts_types_1.isString(err.action)) {
253 sfdxErr.actions.push(err.action);
254 }
255 }
256 let index = this.argv.indexOf('-p') > 0 ? this.argv.indexOf('-p') : 0;
257 let param = this.argv[index + 2]; // should be flag
258 if (this.checkIfWildcardError(param)) { //makes sure that the next arg is +2 from this and starts with - or exists
259 sfdxErr.message = messages.getMessage('WildCardError');
260 }
261 if (ts_types_1.has(err, 'result')) {
262 sfdxErr.data = err.result;
263 }
264 return super.catch(sfdxErr);
265 }
266 return super.catch(err);
267 }
268 getJsonResultObject(result, status) {
269 return ToolbeltCommand.logPerfMetrics(super.getJsonResultObject(result, status));
270 }
271 async resolveLegacyContext() {
272 if (this.legacyContext) {
273 return this.legacyContext;
274 }
275 const logger = require('./lib/core/logApi');
276 logger.setHumanConsumable(!this.flags.json && !this.flags.quiet);
277 this.stringifyFlags();
278 const legacyArgs = [];
279 Object.keys(this.args || {}).forEach(argKey => {
280 const val = this.args[argKey] || '';
281 legacyArgs.push(`${argKey}=${val}`);
282 });
283 // We need just the args, not the flags in argv as well
284 const strict = this.statics.strict;
285 if (!strict) {
286 const { args, argv } = this.parse({
287 flags: this.statics.flags,
288 args: this.statics.args,
289 strict
290 });
291 const argVals = Object.values(args);
292 const varargs = argv.filter((val) => !argVals.includes(val));
293 varargs.forEach(argKey => {
294 legacyArgs.push(argKey);
295 });
296 }
297 Object.keys(this.varargs || {}).forEach(argKey => {
298 const val = this.varargs[argKey] || '';
299 legacyArgs.push(`${argKey}=${val}`);
300 });
301 const context = {
302 flags: this.flags,
303 args: legacyArgs,
304 varargs: this.varargs
305 };
306 if (this.org || this.hubOrg) {
307 const org = this.org || this.hubOrg;
308 const username = org.getUsername();
309 if (this.flags.apiversion) {
310 // The legacy force gets the apiVersion for the config. We could set that on the
311 // new instance we create, but anyone creating their own version of force would
312 // experience a problem. Hack the envs to get it use the flag version across the board.
313 process.env.SFDX_API_VERSION = this.flags.apiversion;
314 }
315 context.org = await Org.create(username);
316 }
317 this.legacyContext = context;
318 kit_1.set(this.legacyContext, 'command.flags', Object.values(this.ctor.flags));
319 return context;
320 }
321 legacyOutput(command, obj) {
322 // For tables with no results we will display a simple message "No results found"
323 if (Array.isArray(obj) && obj.length < 1) {
324 this.ux.log(messages.getMessage('noResultsFound'));
325 return;
326 }
327 // If the command produces tabular output
328 if (ts_types_1.isFunction(command.getColumnData)) {
329 const columnData = command.getColumnData();
330 // If the output is an object we are assuming multiple table are being displayed.
331 if (ts_types_1.isPlainObject(columnData)) {
332 // Each table
333 for (const key of Object.keys(columnData)) {
334 const val = columnData[key];
335 const rows = ts_types_1.get(obj, key);
336 if (kit_1.isEmpty(rows)) {
337 // If the rows are empty provide a nice message and a way to customize the message.
338 let message = messages.getMessage('noResultsFound');
339 if (command.getEmptyResultMessage) {
340 const _message = command.getEmptyResultMessage(key);
341 if (_message != null) {
342 message = _message;
343 }
344 }
345 this.ux.log(message);
346 }
347 else {
348 // One or more row s are available.
349 this.ux.table(ts_types_1.getArray(obj, key), { columns: val });
350 // separate the table by a blank line.
351 this.ux.log();
352 }
353 }
354 }
355 else {
356 // Single output
357 this.ux.table(obj, { columns: columnData });
358 }
359 }
360 else {
361 const message = command.getHumanSuccessMessage && command.getHumanSuccessMessage(obj);
362 if (message != null && message !== '') {
363 this.ux.log(message);
364 }
365 }
366 }
367 // TypeScript does not yet have assertion-free polymorphic access to a class's static side from the instance side
368 get statics() {
369 return this.constructor;
370 }
371 static logPerfMetrics(obj) {
372 if (perfMetricsRequest_1.requestPerfMetrics.length > 0) {
373 obj.perfMetrics = perfMetricsRequest_1.requestPerfMetrics;
374 srcDevUtil.saveGlobalConfig('apiPerformanceLog.json', perfMetricsRequest_1.requestPerfMetrics);
375 }
376 return obj;
377 }
378}
379ToolbeltCommand.supportsPerfLogLevelFlag = false;
380exports.ToolbeltCommand = ToolbeltCommand;
381
382//# sourceMappingURL=ToolbeltCommand.js.map