salesforce-alm
Version:
This package contains tools, and APIs, for an improved salesforce.com developer experience.
254 lines (252 loc) • 12.9 kB
JavaScript
"use strict";
/*
* 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
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.MdapiPushApi = void 0;
const Messages = require("../messages");
const MetadataRegistry = require("./metadataRegistry");
const messages = Messages();
const syncCommandHelper = require("./syncCommandHelper");
const logger = require("../core/logApi");
const sourceDeployApiBase_1 = require("./sourceDeployApiBase");
const sourceUtil_1 = require("./sourceUtil");
const parseManifestEntriesArray_1 = require("./parseManifestEntriesArray");
const sourceWorkspaceAdapter_1 = require("./sourceWorkspaceAdapter");
const aggregateSourceElements_1 = require("./aggregateSourceElements");
const srcStatusApi_1 = require("./srcStatusApi");
const remoteSourceTrackingService_1 = require("./remoteSourceTrackingService");
const kit_1 = require("@salesforce/kit");
const core_1 = require("@salesforce/core");
class MdapiPushApi extends sourceDeployApiBase_1.SourceDeployApiBase {
async init() {
await super.init();
this.metadataRegistry = new MetadataRegistry();
const options = {
org: this.orgApi,
metadataRegistryImpl: MetadataRegistry,
defaultPackagePath: this.force.getConfig().getAppConfig().defaultPackagePath,
};
this.swa = await sourceWorkspaceAdapter_1.SourceWorkspaceAdapter.create(options);
await this.swa.backupSourcePathInfos();
this.scratchOrg = options.org;
this.remoteSourceTrackingService = await remoteSourceTrackingService_1.RemoteSourceTrackingService.getInstance({
username: this.scratchOrg.name,
});
}
async deployPackage(options, packageName) {
try {
this.logger.debug(`deploying package: ${packageName}`);
const changedAggregateSourceElements = new aggregateSourceElements_1.AggregateSourceElements().set(packageName, this.swa.changedSourceElementsCache.get(packageName));
// Create a temp directory
options.deploydir = options.deploydir || (await sourceUtil_1.createOutputDir('mdpkg'));
if (!changedAggregateSourceElements.isEmpty()) {
const result = await this.convertAndDeploy(options, this.swa, changedAggregateSourceElements, true);
return await this.processResults(result, changedAggregateSourceElements, packageName);
}
}
catch (err) {
if (!err.outboundFiles) {
await this.swa.revertSourcePathInfos();
}
throw err;
}
finally {
await sourceUtil_1.cleanupOutputDir(options.deploydir);
}
}
async doDeploy(options) {
const results = { outboundFiles: [] };
// Remove this when push has been modified to support the new mdapi wait functionality;
if (isNaN(options.wait)) {
options.wait = this.force.config.getConfigContent().defaultSrcWaitMinutes;
}
await this.checkForConflicts(options);
MdapiPushApi.packagesDeployed = this.swa.changedSourceElementsCache.size;
// Deploy metadata in each package directory
let componentSuccessCount = 0;
let flattenedDeploySuccesses = [];
for (const pkg of core_1.SfdxProject.getInstance().getUniquePackageNames()) {
const sourceElements = this.swa.changedSourceElementsCache.get(pkg);
if (sourceElements && sourceElements.size) {
const opts = Object.assign({}, options);
const deployResult = await this.deployPackage(opts, pkg);
if (deployResult) {
if (deployResult.numberComponentsDeployed) {
componentSuccessCount += deployResult.numberComponentsDeployed;
}
if (deployResult.details && deployResult.details.componentSuccesses) {
flattenedDeploySuccesses = [...flattenedDeploySuccesses, ...deployResult.details.componentSuccesses];
}
if (deployResult.outboundFiles && deployResult.outboundFiles.length) {
results.outboundFiles = [...results.outboundFiles, ...deployResult.outboundFiles];
}
}
}
}
if (kit_1.env.getBoolean('SFDX_DISABLE_SOURCE_MEMBER_POLLING', false)) {
logger.warn('Not polling for SourceMembers since SFDX_DISABLE_SOURCE_MEMBER_POLLING = true.');
return results;
}
// If more than 500 metadata components were pushed, display a message
// that we're fetching the source tracking data, which happens asynchronously.
if (componentSuccessCount > 500) {
logger.log(`Updating source tracking for ${componentSuccessCount} pushed components...`);
}
// post process after all deploys have been done to update source tracking.
await this._postProcess(flattenedDeploySuccesses);
return results;
}
async checkForConflicts(options) {
if (options.forceoverwrite) {
// do not check for conflicts when doing push --forceoverwrite
return;
}
else {
const statusApi = await srcStatusApi_1.SrcStatusApi.create({ org: this.orgApi, adapter: this.swa });
let conflicts = [];
try {
await statusApi.doStatus({ local: true, remote: true });
conflicts = statusApi.getLocalConflicts();
}
catch (err) {
if (err.errorCode === 'INVALID_TYPE') {
const error = new Error(messages.getMessage('NonScratchOrgPush'));
error['name'] = 'NonScratchOrgPush';
throw error;
}
else {
throw err;
}
}
if (conflicts.length > 0) {
const error = new Error('Conflicts found during push');
error['name'] = 'SourceConflict';
error['sourceConflictElements'] = conflicts;
throw error;
}
}
}
commitChanges(packageName) {
if (this.swa.pendingSourcePathInfos.get(packageName)) {
return this.swa.commitPendingChanges(packageName);
}
return false;
}
async processResults(result, changedAggregateSourceElements, packageName) {
try {
result = this._reinterpretResults(result);
if (result.success && !!result.details.componentFailures) {
this.removeFailedAggregates(result.details.componentFailures, changedAggregateSourceElements);
}
// Update deleted items even if the deploy fails so the worksapce is consistent
await this.swa.updateSource(changedAggregateSourceElements);
}
catch (e) {
// Don't log to console
this.logger.error(false, e);
}
// We need to check both success and status because a status of 'SucceededPartial' returns success === true even though rollbackOnError is set.
if (result.success && result.status === 'Succeeded') {
await this.commitChanges(packageName);
result.outboundFiles = this.getOutboundFiles(changedAggregateSourceElements);
return result;
}
else {
const deployFailed = new Error();
if (result.timedOut) {
deployFailed.name = 'PollingTimeout';
}
else {
deployFailed.name = 'DeployFailed';
let aggregateSourceElements;
try {
// Try to get the source elements for better error messages, but still show
// deploy failures if this errors out
const packagePath = core_1.SfdxProject.getInstance().getPackagePath(packageName);
aggregateSourceElements = await this.swa.getAggregateSourceElements(false, packagePath);
}
catch (e) {
// Don't log to console
this.logger.error(false, e);
}
deployFailed.failures = syncCommandHelper.getDeployFailures(result, aggregateSourceElements, this.metadataRegistry, this.logger);
}
if (result.success && result.status === 'SucceededPartial') {
await this.commitChanges(packageName);
deployFailed.outboundFiles = this.getOutboundFiles(changedAggregateSourceElements);
}
throw deployFailed;
}
}
async _postProcess(pushSuccesses) {
await sourceUtil_1.updateSourceTracking(pushSuccesses, this.remoteSourceTrackingService, this.metadataRegistry);
}
static _isDeleteFailureBecauseDoesNotExistOnServer(failure) {
return (failure.fullName === 'destructiveChanges.xml' && syncCommandHelper.getFullNameFromDeleteFailure(failure) !== null);
}
static _isFailureToUs(failure) {
return !MdapiPushApi._isDeleteFailureBecauseDoesNotExistOnServer(failure);
}
static _convertFailureToSuccess(failure) {
/*
* Delete of non existent entity error - that's ok for push.
* Also note the weird fullName behavior in the mdapi deploy file property.
* Fortunately we can recover the fullName from the error message text!
*/
if (MdapiPushApi._isDeleteFailureBecauseDoesNotExistOnServer(failure)) {
failure.fullName = syncCommandHelper.getFullNameFromDeleteFailure(failure);
failure.deleted = 'true';
failure.problem = null;
failure.success = 'true';
}
}
_recalculateResult(result, reinterpretedComponentSuccesses, reinterpretedComponentFailures) {
result.details.componentSuccesses = parseManifestEntriesArray_1.toArray(result.details.componentSuccesses);
const originalSuccessCount = result.details.componentSuccesses.length - 1; // Ignore package.xml
if (result.status === 'Failed') {
// We can only convert a failed deploy to a success if all the failures can be ignored
// *and* there were no successes reported for components that would have been deployed
// if the deploy had succeeded, but actually failed on the server (which is not fixable here).
result.status =
reinterpretedComponentFailures.length === 0 && originalSuccessCount === 0 ? 'Succeeded' : 'Failed';
}
else {
result.status = reinterpretedComponentFailures.length === 0 ? 'Succeeded' : result.status;
}
result.success = result.status !== 'Failed';
if (result.success) {
reinterpretedComponentSuccesses.forEach((failure) => MdapiPushApi._convertFailureToSuccess(failure));
result.details.componentSuccesses = result.details.componentSuccesses.concat(reinterpretedComponentSuccesses);
result.details.componentFailures = reinterpretedComponentFailures;
result.numberComponentsDeployed = result.details.componentSuccesses.length - 1; // Ignore package.xml
result.numberComponentErrors = result.details.componentFailures.length;
result.numberComponentsTotal = result.numberComponentsDeployed + result.numberComponentErrors;
}
return result;
}
/*
* We'll take a look over the result to see if we want to change it to reflect the different
* perspectives of mdapi deploy and push. We might consider some errors to be successes from
* a push perspective. If we end up flipping some errors then we'll also need to recalculate
* whether we are a success, partial success, or failure.
*/
_reinterpretResults(result) {
result.details.componentSuccesses = parseManifestEntriesArray_1.toArray(result.details.componentSuccesses);
if (result.status === 'Succeeded') {
return result;
}
const componentFailures = parseManifestEntriesArray_1.toArray(result.details.componentFailures);
const reinterpretedComponentFailures = componentFailures.filter((failure) => MdapiPushApi._isFailureToUs(failure));
const reinterpretedComponentSuccesses = componentFailures.filter((failure) => !MdapiPushApi._isFailureToUs(failure));
if (reinterpretedComponentFailures.length !== componentFailures.length) {
return this._recalculateResult(result, reinterpretedComponentSuccesses, reinterpretedComponentFailures);
}
return result;
}
}
exports.MdapiPushApi = MdapiPushApi;
//# sourceMappingURL=sourcePushApi.js.map