salesforce-alm
Version:
This package contains tools, and APIs, for an improved salesforce.com developer experience.
317 lines (315 loc) • 14.2 kB
JavaScript
;
/*
* 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 util = require("util");
const path = require("path");
const workspaceFileState_1 = require("./workspaceFileState");
const aggregateSourceElement_1 = require("./aggregateSourceElement");
const metadataTypeFactory_1 = require("./metadataTypeFactory");
const sourcePathUtil_1 = require("./sourcePathUtil");
const _createRowsForConflictStatus = function (rows, createDisplayRowData, outpuFileInfo, projectPath, thisSelf, isStatus) {
const row = createDisplayRowData(outpuFileInfo, thisSelf.trimParentFromPath(projectPath, outpuFileInfo.filePath));
if (isStatus && outpuFileInfo.isConflict) {
row.state += ' (Conflict)';
}
rows.push(row);
};
const _getState = function (state, deleteSupported) {
const calcState = state ? state : workspaceFileState_1.WorkspaceFileState.DELETED;
return !deleteSupported && calcState === workspaceFileState_1.WorkspaceFileState.DELETED
? `${workspaceFileState_1.toReadableState(workspaceFileState_1.WorkspaceFileState.DELETED)} (local file)`
: workspaceFileState_1.toReadableState(calcState);
};
const _getFullNameFromDeleteFailure = function (failure) {
/*
* Note the weird fullName behavior in the mdapi deploy file property.
* Fortunately we can recover the fullName from the error message text!
*/
const noComponentFoundRegex = new RegExp(`No ${failure.componentType} named: (.+) found$`);
const matches = noComponentFoundRegex.exec(failure.problem);
return matches !== null ? matches[1] : null;
};
const _getSourceElement = function (componentFailure, aggregateSourceElements, metadataRegistry) {
const failureMetadataType = metadataTypeFactory_1.MetadataTypeFactory.getMetadataTypeFromMetadataName(componentFailure.componentType, metadataRegistry);
if (failureMetadataType) {
let fullName = componentFailure.fullName;
if (fullName === 'destructiveChanges.xml') {
fullName = _getFullNameFromDeleteFailure(componentFailure);
}
else {
componentFailure.fileName = sourcePathUtil_1.replaceForwardSlashes(componentFailure.fileName); // fix windows specific file separators
fullName = failureMetadataType.getAggregateFullNameFromComponentFailure(componentFailure);
}
const sourceElementKey = aggregateSourceElement_1.AggregateSourceElement.getKeyFromMetadataNameAndFullName(failureMetadataType.getAggregateMetadataName(), fullName);
return aggregateSourceElements.findSourceElementByKey(sourceElementKey);
}
return null;
};
const _parseComponentFailure = function (componentFailure, sourceElements, metadataRegistry, logger) {
let aggregateSourceElement;
let filePath;
let fullName;
try {
// see if we can map the error to a local sourceElement
aggregateSourceElement = _getSourceElement(componentFailure, sourceElements, metadataRegistry);
if (!util.isNullOrUndefined(aggregateSourceElement)) {
const componentMetadataType = metadataTypeFactory_1.MetadataTypeFactory.getMetadataTypeFromMetadataName(componentFailure.componentType, metadataRegistry);
fullName = componentMetadataType.getWorkspaceFullNameFromComponentFailure(componentFailure);
// does the error reference the -meta file or the content file?
const componentFailureIsInMetadataFile = componentMetadataType.componentFailureIsInMetadataFile(componentFailure.fileName);
if (componentFailureIsInMetadataFile) {
filePath = aggregateSourceElement.getMetadataWorkspacePathsForTypeAndFullName(componentFailure.componentType, fullName)[0];
}
else {
// the file we are looking for is a content file
const matchingPaths = aggregateSourceElement.getContentWorkspacePathsForTypeAndFullName(componentFailure.componentType, fullName);
if (matchingPaths.length > 0) {
filePath = componentMetadataType.getComponentFailureWorkspaceContentPath(aggregateSourceElement.getMetadataFilePath(), matchingPaths);
}
}
if (!filePath) {
filePath = aggregateSourceElement.getMetadataFilePath();
}
}
else {
filePath = '';
}
}
catch (e) {
if (logger) {
logger.debug(e);
}
}
return {
columnNumber: componentFailure.columnNumber,
lineNumber: componentFailure.lineNumber,
error: componentFailure.problem,
fullName,
type: componentFailure.componentType,
filePath,
problemType: componentFailure.problemType,
};
};
const self = {
getDeployFailures(resp, aggregateSourceElements, metadataRegistry, logger) {
const deployFailures = [];
// look into component details assemble deployment failure message
if (resp.details && resp.details.componentFailures) {
if (Array.isArray(resp.details.componentFailures)) {
// Array of failures
for (let i = 0, len = resp.details.componentFailures.length; i < len; i++) {
const comp = resp.details.componentFailures[i];
deployFailures.push(_parseComponentFailure(comp, aggregateSourceElements, metadataRegistry, logger));
}
}
else {
// Single failure
const comp = resp.details.componentFailures;
deployFailures.push(_parseComponentFailure(comp, aggregateSourceElements, metadataRegistry, logger));
}
}
else if (!util.isNullOrUndefined(resp.ErrorMsg)) {
deployFailures.push({ error: resp.ErrorMsg });
}
else {
deployFailures.push({ error: 'Unknown' });
}
return deployFailures;
},
getFullNameFromDeleteFailure(failure) {
return _getFullNameFromDeleteFailure(failure);
},
/**
* report formatting for retrieve failures.
*
* @param resp - the result of toolingRetrieve
* @param detailProperty - the name of the property with the deploy details. Defaults to 'DeployDetails'.
* @returns {string} represents reported deployment errors
* @private
*/
getRetrieveFailureMessage(resp, messages) {
let msg = '';
if (!util.isNullOrUndefined(resp.messages)) {
if (resp.messages instanceof Array && resp.messages.length > 0) {
for (let i = 0, len = resp.messages.length; i < len; ++i) {
msg += resp.messages[i].problem;
if (i < len - 1) {
msg += '\n';
}
}
}
else {
msg = resp.messages.problem;
}
}
else if (!util.isNullOrUndefined(resp.errorMessage)) {
msg = resp.errorMessage;
}
else if (util.isNullOrUndefined(resp.fileProperties) || !Array.isArray(resp.fileProperties)) {
msg = messages.getMessage('mdapiPullCommandNoDataReturned');
}
else {
msg = 'Unknown';
}
return msg;
},
/**
* helper method used by the sync commands to retrieve status of a container async request.
*
* @param force - the force api
* @param api - scratch org api
* @param sobjectId - id of the container async request
* @param messages - L10N access obeject
* @param callback - callback that resolves a value once the ContainerAsyncRequest is complete.
* @param resolve - outer promise resolve function
* @param reject - outer promise reject handler
* @returns {Promise}
*/
retrieveContainerStatus(force, api, sobjectId, messages, callback, resolve, reject) {
return force.toolingRetrieve(api, 'ContainerAsyncRequest', sobjectId).then((resp) => {
switch (resp.State) {
case 'Completed':
return resolve(callback());
case 'Failed': {
const deployFailed = new Error(resp.ErrorMsg);
deployFailed.name = 'ContainerDeployFailed';
return reject(deployFailed);
}
case 'Invalidated': {
const invalidatedError =
// @todo re-label message
new Error(messages.getMessage('pushCommandAsyncRequestInvalidated'));
invalidatedError.name = 'ContainerDeployInvalidated';
return reject(invalidatedError);
}
default: {
let deployErrMsg = messages.getMessage('pushCommandAsyncRequestUnexpected');
const respError = resp.ErrorMsg;
if (!util.isNullOrUndefined(respError)) {
deployErrMsg += ` ${respError}.`;
}
const deployError = new Error(deployErrMsg);
deployError.name = 'ContainerDeployError';
return reject(deployError);
}
}
});
},
/**
* Removes a parent path to make a relative path.
*
* @param parent - the parent path (usually a project directory)
* @param elementPath - the full path that contains the parent.
* @returns {*}
*/
trimParentFromPath(parent, elementPath) {
if (util.isNullOrUndefined(elementPath) || elementPath.indexOf(parent) !== 0) {
return null;
}
if (util.isNullOrUndefined(parent)) {
return null;
}
const trimmedParent = parent.trim();
if (trimmedParent.length < 1) {
return null;
}
let newParent = trimmedParent;
if (!parent.endsWith(path.sep)) {
newParent = `${trimmedParent}${path.sep}`;
}
const paths = elementPath.trim().split(newParent);
const element = paths[paths.length - 1];
// handle the case where both the parent and elementPath are the same.
if (element === elementPath) {
return '';
}
return element;
},
// Return table column metadata. Changesets commands do not display state info since it doesn't apply.
getColumnMetaInfo(messages, withoutState) {
const stateCol = withoutState ? [] : [{ key: 'state', label: messages.getMessage('stateTableColumn') }];
const columns = [
{ key: 'fullName', label: messages.getMessage('fullNameTableColumn') },
{ key: 'type', label: messages.getMessage('typeTableColumn') },
{
key: 'filePath',
label: messages.getMessage('workspacePathTableColumn'),
},
];
return [...stateCol, ...columns];
},
createDeployFailureRow(rows, failure, projectPath) {
if (!util.isNullOrUndefined(failure.filePath)) {
if (failure.filePath === '') {
failure.filePath = 'N/A';
}
else {
failure.filePath = self.trimParentFromPath(projectPath, failure.filePath);
}
}
const columnNumber = failure.columnNumber || 0; // sometimes we only get the line number
if (failure.lineNumber) {
failure.error += ` (${failure.lineNumber}:${columnNumber})`;
}
rows.push(failure);
},
createConflictRows(rows, conflictFileInfo, projectPath) {
const _createDisplayRowData = (fileInfo, filePath) => ({
state: 'Conflict',
fullName: fileInfo.fullName,
type: fileInfo.type,
filePath,
});
_createRowsForConflictStatus(rows, _createDisplayRowData, conflictFileInfo, projectPath, self, false);
},
createStatusLocalRows(rows, outputFileInfo, projectPath) {
const _createDisplayRowData = (fileInfo, filePath) => ({
state: `Local ${_getState(fileInfo.state, fileInfo.deleteSupported)}`,
fullName: fileInfo.fullName,
type: fileInfo.type,
filePath,
});
_createRowsForConflictStatus(rows, _createDisplayRowData, outputFileInfo, projectPath, self, true);
},
// displays a row based on information pulled from a SourceMember row
createStatusRemoteRows(rows, sourceMember, projectPath) {
const _createDisplayRowData = (sm, filePath) => ({
state: `Remote ${workspaceFileState_1.toReadableState(sm.state)}`,
fullName: sm.fullName,
type: sm.type,
filePath,
});
_createRowsForConflictStatus(rows, _createDisplayRowData, sourceMember, projectPath, self, true);
},
createDisplayRows(rows, outputFileInfo, projectPath) {
if (util.isNullOrUndefined(rows)) {
const error = new Error('Row collection not specified.');
error['name'] = 'MissingRowCollection';
throw error;
}
if (util.isNullOrUndefined(outputFileInfo)) {
const error = new Error('Output file info not found.');
error['name'] = 'SourceElementNotFound';
throw error;
}
if (util.isNullOrUndefined(projectPath)) {
const error = new Error("Can't display row without the projectPath");
error['name'] = 'MissingProjectPathForDisplay';
throw error;
}
const _createDisplayRowData = (element, filePath) => ({
state: _getState(element.state, element.deleteSupported),
fullName: element.fullName,
type: element.type,
filePath,
});
const filePath = outputFileInfo.filePath;
rows.push(_createDisplayRowData(outputFileInfo, self.trimParentFromPath(projectPath, filePath)));
},
};
module.exports = self;
//# sourceMappingURL=syncCommandHelper.js.map