UNPKG

salesforce-alm

Version:

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

255 lines (253 loc) 12.6 kB
"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.MdapiPullApi = void 0; // 3pp const util = require("util"); const path = require("path"); const BBPromise = require("bluebird"); const _ = require("lodash"); // Local const kit_1 = require("@salesforce/kit"); const core_1 = require("@salesforce/core"); const mdapiRetrieveApi_1 = require("../mdapi/mdapiRetrieveApi"); const messagesApi = require("../messages"); const sourceUtil_1 = require("./sourceUtil"); const ManifestCreateApi = require("./manifestCreateApi"); const SourceMetadataMemberRetrieveHelper = require("./sourceMetadataMemberRetrieveHelper"); const syncCommandHelper = require("./syncCommandHelper"); const MetadataRegistry = require("./metadataRegistry"); const bundleMetadataType_1 = require("./metadataTypeImpl/bundleMetadataType"); const pathUtil = require("./sourcePathUtil"); const sourceWorkspaceAdapter_1 = require("./sourceWorkspaceAdapter"); const aggregateSourceElements_1 = require("./aggregateSourceElements"); const srcStatusApi_1 = require("./srcStatusApi"); const remoteSourceTrackingService_1 = require("./remoteSourceTrackingService"); const sourceLocations_1 = require("./sourceLocations"); class MdapiPullApi extends kit_1.AsyncCreatable { constructor(options) { super(options); this.swa = options.adapter; if (this.swa) { this.smmHelper = new SourceMetadataMemberRetrieveHelper(this.swa); } this.scratchOrg = options.org; this.force = this.scratchOrg.force; this.messages = messagesApi(this.force.config.getLocale()); this.obsoleteNames = []; } async init() { this.remoteSourceTrackingService = await remoteSourceTrackingService_1.RemoteSourceTrackingService.getInstance({ username: this.scratchOrg.name, }); this.logger = await core_1.Logger.child(this.constructor.name); if (!this.swa) { const options = { org: this.scratchOrg, metadataRegistryImpl: MetadataRegistry, defaultPackagePath: this.force.getConfig().getAppConfig().defaultPackagePath, }; this.swa = await sourceWorkspaceAdapter_1.SourceWorkspaceAdapter.create(options); this.smmHelper = new SourceMetadataMemberRetrieveHelper(this.swa); } } async doPull(options) { // Remove this when pull 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); // if no remote changes were made, quick exit if (this.statusApi && !this.statusApi.getRemoteChanges().length) { return []; } const packages = await this.smmHelper.getRevisionsAsPackage(this.obsoleteNames); const results = await BBPromise.mapSeries(Object.keys(packages), async (pkgName) => { core_1.SfdxProject.getInstance().setActivePackage(pkgName); const pkg = packages[pkgName]; const opts = Object.assign({}, options); this.logger.debug('Retrieving', pkgName); try { // Create a temp directory opts.retrievetargetdir = await sourceUtil_1.createOutputDir('pull'); // Create a manifest (package.xml). const manifestOptions = Object.assign({}, opts, { outputdir: opts.retrievetargetdir, }); const manifest = await this._createPackageManifest(manifestOptions, pkg); this.logger.debug(util.inspect(manifest, { depth: 6 })); let result; if (manifest.empty) { if (this.obsoleteNames.length > 0) { result = { fileProperties: [], success: true, status: 'Succeeded' }; } } else { // Get default metadata retrieve options const retrieveOptions = Object.assign(mdapiRetrieveApi_1.MdRetrieveApi.getDefaultOptions(), { retrievetargetdir: opts.retrievetargetdir, unpackaged: manifest.file, wait: opts.wait, }); // Retrieve the metadata result = await new mdapiRetrieveApi_1.MdRetrieveApi(this.scratchOrg).retrieve(retrieveOptions).catch((err) => err.result); } this.logger.debug('Retrieve result:', result); // Update local metadata source. return await this._postRetrieve(result, opts); } finally { // Delete the output dir. await sourceUtil_1.cleanupOutputDir(opts.retrievetargetdir); } }); return results; } // eslint-disable-next-line @typescript-eslint/require-await async _createPackageManifest(options, pkg) { if (pkg.isEmpty()) { return BBPromise.resolve({ empty: true }); } if (_.isNil(options.packageXml) || !options.debug) { const configSourceApiVersion = this.force.getConfig().getAppConfig().sourceApiVersion; const sourceApiVersion = !_.isNil(configSourceApiVersion) ? configSourceApiVersion : this.force.getConfig().getApiVersion(); pkg.setVersion(sourceApiVersion); return BBPromise.resolve(new ManifestCreateApi(this.force).createManifestForMdapiPackage(options, pkg, this.smmHelper.metadataRegistry)); } else { return BBPromise.resolve({ file: options.packageXml }); } } static _didRetrieveSucceed(result) { return (!_.isNil(result) && result.success && result.status === 'Succeeded' && _.isNil(result.messages) && !_.isNil(result.fileProperties) && Array.isArray(result.fileProperties)); } async _postRetrieve(result, options) { let changedSourceElements; let inboundFiles; if (MdapiPullApi._didRetrieveSucceed(result)) { changedSourceElements = await this._syncDownSource(result, options, this.swa); // NOTE: Even if no updates were made, we need to update source tracking for those elements // E.g., we pulled metadata but it's the same locally so it's not seen as a change. inboundFiles = changedSourceElements .getAllWorkspaceElements() .map((workspaceElement) => workspaceElement.toObject()); await sourceLocations_1.SourceLocations.nonDecomposedElementsIndex.maybeRefreshIndex(inboundFiles); await this.remoteSourceTrackingService.sync(); } return this._processResults(result, inboundFiles); } async _syncDownSource(result, options, swa) { const changedSourceElements = new aggregateSourceElements_1.AggregateSourceElements(); // Each Aura bundle has a definition file that has one of the suffixes: .app, .cmp, .design, .evt, etc. // In order to associate each sub-component of an aura bundle (e.g. controller, style, etc.) with // its parent aura definition type, we must find its parent's file properties and pass those along // to processMdapiFileProperty. Similarly, for other BundleMetadataTypes. const bundleFileProperties = bundleMetadataType_1.BundleMetadataType.getDefinitionProperties(result.fileProperties, this.swa.metadataRegistry); const postRetrieveHookInfo = {}; result.fileProperties.forEach((fileProperty) => { let { fullName, fileName } = fileProperty; if (fileProperty.type === 'Package') { return; } // After retrieving, switch back to path separators (for Windows) fileProperty.fullName = fullName = pathUtil.replaceForwardSlashes(fullName); fileProperty.fileName = fileName = pathUtil.replaceForwardSlashes(fileName); this.swa.processMdapiFileProperty(changedSourceElements, options.retrievetargetdir, fileProperty, bundleFileProperties); if (!(typeof postRetrieveHookInfo[fullName] === 'object')) { postRetrieveHookInfo[fullName] = { mdapiFilePath: [], }; } postRetrieveHookInfo[fullName].mdapiFilePath = postRetrieveHookInfo[fullName].mdapiFilePath.concat(path.join(options.retrievetargetdir, fileName)); }); // emit post retrieve event await core_1.Lifecycle.getInstance().emit('postretrieve', postRetrieveHookInfo); this.obsoleteNames.forEach((obsoleteName) => { this.swa.handleObsoleteSource(changedSourceElements, obsoleteName.fullName, obsoleteName.type); }); const sourcePromise = swa.updateSource(changedSourceElements, options.manifest, false /** check for duplicates **/, options.unsupportedMimeTypes, options.forceoverwrite); return sourcePromise .then((updatedSource) => { // emit post source update event const postSourceUpdateHookInfo = {}; updatedSource.forEach((sourceElementMap) => { sourceElementMap.forEach((sourceElement) => { const fullName = sourceElement.aggregateFullName; if (!postSourceUpdateHookInfo[fullName]) { postSourceUpdateHookInfo[fullName] = { workspaceElements: [], }; } const hookInfo = postSourceUpdateHookInfo[fullName]; const newElements = hookInfo.workspaceElements.concat(sourceElement.workspaceElements.map((we) => we.toObject())); hookInfo.workspaceElements = [...newElements]; postSourceUpdateHookInfo[fullName] = hookInfo; }); }); core_1.Lifecycle.getInstance() .emit('postsourceupdate', postSourceUpdateHookInfo) // eslint-disable-next-line @typescript-eslint/no-empty-function .then(() => { }); }) .then(() => sourcePromise); } _processResults(result, inboundFiles) { if (_.isNil(result)) { return; } else if (MdapiPullApi._didRetrieveSucceed(result)) { return { inboundFiles }; } else { const retrieveFailed = new Error(syncCommandHelper.getRetrieveFailureMessage(result, this.messages)); retrieveFailed.name = 'RetrieveFailed'; throw retrieveFailed; } } async _checkForConflicts(options) { if (options.forceoverwrite) { // do not check for conflicts when pull --forceoverwrite return []; } this.statusApi = await srcStatusApi_1.SrcStatusApi.create({ org: this.scratchOrg, adapter: this.swa }); return this.statusApi .doStatus({ local: true, remote: true }) // rely on status so that we centralize the logic .then(() => this.statusApi.getLocalConflicts()) .catch((err) => { const sfdxError = core_1.SfdxError.wrap(err); if (err.errorCode === 'INVALID_TYPE') { const messages = core_1.Messages.loadMessages('salesforce-alm', 'source_pull'); sfdxError.message = messages.getMessage('NonScratchOrgPull'); } else if (err.errorCode === 'INVALID_SESSION_ID') { sfdxError.actions = [this.messages.getMessage('invalidInstanceUrlForAccessTokenAction')]; } else { sfdxError.message = err.message; } throw sfdxError; }) .then((conflicts) => { if (conflicts.length > 0) { const error = new Error('Conflicts found during sync down'); error['name'] = 'SourceConflict'; error['sourceConflictElements'] = conflicts; throw error; } }); } } exports.MdapiPullApi = MdapiPullApi; //# sourceMappingURL=sourcePullApi.js.map