UNPKG

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