UNPKG

salesforce-alm

Version:

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

244 lines (242 loc) 13.8 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.SourceRetrieve = void 0; const path_1 = require("path"); const lodash_1 = require("lodash"); const core_1 = require("@salesforce/core"); const core_2 = require("@salesforce/core"); const mdapiRetrieveApi_1 = require("../mdapi/mdapiRetrieveApi"); const MetadataRegistry = require("./metadataRegistry"); const sourceUtil_1 = require("./sourceUtil"); const manifestUtils_1 = require("./manifestUtils"); const parseManifestEntriesArray_1 = require("./parseManifestEntriesArray"); const MdapiConvertApi = require("./mdapiConvertApi"); const syncCommandHelper = require("./syncCommandHelper"); const sourceWorkspaceAdapter_1 = require("./sourceWorkspaceAdapter"); const aggregateSourceElements_1 = require("./aggregateSourceElements"); const sourcePullApi_1 = require("./sourcePullApi"); const sourceElementsResolver_1 = require("./sourceElementsResolver"); const SourceConvertApi = require("./sourceConvertApi"); core_1.Messages.importMessagesDirectory(__dirname); /** * API for retrieving metadata from an org and updating a local SFDX project. */ class SourceRetrieve { constructor(org) { this.org = org; this.projectPath = org.force.config.getProjectPath(); this.defaultPackagePath = org.config.getAppConfig().defaultPackagePath; } // Retrieves metadata from an org based on the retrieve options specified. async retrieve(options) { this.logger = await core_1.Logger.child('SourceRetrieve'); // Only put SWA in stateless mode when sourcepath param is used. const mode = options.sourcepath && sourceWorkspaceAdapter_1.SourceWorkspaceAdapter.modes.STATELESS; const swaOptions = { org: this.org, metadataRegistryImpl: MetadataRegistry, defaultPackagePath: this.defaultPackagePath, fromConvert: false, sourceMode: mode, sourcePaths: options.sourcepath && options.sourcepath.split(','), }; this.swa = await sourceWorkspaceAdapter_1.SourceWorkspaceAdapter.create(swaOptions); let results; try { this.tmpOutputDir = await sourceUtil_1.createOutputDir('sourceRetrieve'); if (options.sourcepath) { this.logger.info(`Retrieving metadata in sourcepath '${options.sourcepath}' from org: '${this.org.name}'`); results = await this.retrieveFromSourcePath(options); } else if (options.manifest) { this.logger.info(`Retrieving metadata in manifest '${options.manifest}' from org: '${this.org.name}'`); results = await this.retrieveFromManifest(options); } else if (options.packagenames) { this.logger.info(`Retrieving metadata in package(s) '${options.packagenames}' from org: '${this.org.name}'`); results = await this.retrieveFromManifest(options); } else if (options.metadata) { this.logger.info(`Retrieving metadata '${options.metadata}' from org: '${this.org.name}'`); results = await this.retrieveFromMetadata(options); } } finally { await sourceUtil_1.cleanupOutputDir(this.tmpOutputDir); // Delete the sourcePathInfos.json for this org. Ideally, we never create it but // until then, this is necessary. this.org.getSourcePathInfos().delete(); } return results; } // Retrieve specific source paths from an org and update the project. async retrieveFromSourcePath(options) { // Parse the sourcepath parameter for metadata files and build a map of AggregateSourceElements const aggregateSourceElements = await sourceUtil_1.getSourceElementsFromSourcePath(options.sourcepath, this.swa); // Create a manifest and update the options with the manifest file. options.manifest = await this.createManifest(aggregateSourceElements, options, this.tmpOutputDir); // Now that we have a package.xml, the rest is just a retrieve using the manifest. return this.retrieveFromManifest(options, aggregateSourceElements); } async retrieveFromMetadata(options) { // toManifest will also tack the manifest onto the options await manifestUtils_1.toManifest(this.org, options, this.tmpOutputDir); // Now that we have a package.xml, the rest is just a retrieve using the manifest. return this.retrieveFromManifest(options); } // Retrieve metadata specified in a manifest file (package.xml) from an org and update the project. async retrieveFromManifest(options, aggregateSourceElements) { const results = { inboundFiles: [], packages: [], warnings: [] }; const project = core_2.SfdxProject.getInstance(); const defaultPackage = project.getDefaultPackage(); // Get AggregateSourceElements (ASEs) from the manifest so we can split into // multiple requests per package. if (!aggregateSourceElements) { if (options.manifest) { const sourceElementsResolver = new sourceElementsResolver_1.SourceElementsResolver(this.org, this.swa); aggregateSourceElements = await sourceElementsResolver.getSourceElementsFromManifest(options.manifest); } else { // This happens when only packages are retrieved aggregateSourceElements = new aggregateSourceElements_1.AggregateSourceElements(); } // Always add the default package, else new things in the org may not be processed correctly. if (!aggregateSourceElements.has(defaultPackage.name)) { aggregateSourceElements.set(defaultPackage.name, null); } } let shouldRetrievePackages = !!options.packagenames; for (const [pkgName, aseMap] of aggregateSourceElements) { this.logger.info('retrieving package:', pkgName); project.setActivePackage(pkgName); let tmpPkgOutputDir; try { // Create a temp directory tmpPkgOutputDir = await sourceUtil_1.createOutputDir('sourceRetrieve_pkg'); let ases; // Create a new manifest for this package in the tmp dir. if (aseMap && options.sourcepath) { ases = new aggregateSourceElements_1.AggregateSourceElements().set(pkgName, aseMap); options.manifest = await this.createManifest(ases, options, tmpPkgOutputDir); } // Set Retrieve options for MdRetrieveApi const retrieveOptions = Object.assign(mdapiRetrieveApi_1.MdRetrieveApi.getDefaultOptions(), { apiversion: options.apiversion, forceoverwrite: true, retrievetargetdir: tmpPkgOutputDir, unpackaged: options.manifest, wait: options.wait, }); // Only retrieve packages once if the param was set. if (shouldRetrievePackages) { retrieveOptions.packagenames = Array.isArray(options.packagenames) ? options.packagenames.join() : options.packagenames; shouldRetrievePackages = false; } // Retrieve the files from the org const res = await new mdapiRetrieveApi_1.MdRetrieveApi(this.org).retrieve(retrieveOptions); if (options.packagenames) { // Convert the packages to source format and copy them to the project in their // own directory. E.g. For a package named "fooPkg" and project named "myProject": // source would be copied to: myProject/fooPkg/ const packageNames = retrieveOptions.packagenames.split(','); const projectPath = await core_2.SfdxProject.resolveProjectPath(); const mdapiConvertApi = new MdapiConvertApi(); // Turn off the active package directory when converting retrieved // packages or it will prevent proper decomposition. // See behavior of AggregateSourceElement.shouldIgnorePath() try { project.setActivePackage(null); for (const _pkgName of packageNames) { const destPath = path_1.join(projectPath, _pkgName); const pkgPath = path_1.join(retrieveOptions.retrievetargetdir, _pkgName); this.logger.info(`Converting metadata in package: ${pkgPath} to: ${destPath}`); results.packages.push({ name: _pkgName, path: destPath }); mdapiConvertApi.root = pkgPath; mdapiConvertApi.outputDirectory = destPath; await mdapiConvertApi.convertSource(this.org); } } finally { project.setActivePackage(pkgName); } } // Convert to source format and update the local project. if (SourceRetrieve.isRetrieveSuccessful(res)) { // Only do this if unpackaged source was retrieved (i.e., a manifest was created). // retrieveOptions.unpackaged will be undefined when only packages were retrieved. if (retrieveOptions.unpackaged) { const mdapiPull = await sourcePullApi_1.MdapiPullApi.create({ org: this.org, adapter: this.swa }); // Extracts the source from package.xml in the temp dir, creates a Map of AggregateSourceElements // and updates the local project with the files from the temp dir. const sourceElements = await mdapiPull._syncDownSource(res, retrieveOptions, this.swa); // Build a simple object representation of what was changed in the local project. const { inboundFiles, warnings } = await this.processResults(res, sourceElements); if (results.inboundFiles.length > 0) { // Could be duplicates with multiple package directories so don't add inbound files twice const filePaths = results.inboundFiles.map((file) => file.filePath); for (const inboundFile of inboundFiles) { if (!filePaths.includes(inboundFile.filePath)) { results.inboundFiles.push(inboundFile); } } } else { results.inboundFiles = results.inboundFiles.concat(inboundFiles); } if (warnings) { results.warnings = results.warnings.concat(warnings); } } } else { // If fileProperties is an object, it means no results were retrieved so don't throw an error if (!lodash_1.isPlainObject(res.fileProperties)) { let errMsgExtra = ''; if (res.messages) { errMsgExtra = `\nDue to: ${res.messages.problem}`; } throw core_1.SfdxError.create('salesforce-alm', 'source_retrieve', 'SourceRetrieveError', [errMsgExtra]); } } } finally { await sourceUtil_1.cleanupOutputDir(tmpPkgOutputDir); } } return results; } async createManifest(aggregateSourceElements, options, outputDirPath) { // Convert aggregateSourceElements to an array of this format: { type: 'ApexClass', name: 'MyClass' } // for use by ManifestApi.createManifest(). const mdFullPairs = SourceConvertApi.getUpdatedSourceTypeNamePairs(aggregateSourceElements.getAllSourceElements(), this.swa.metadataRegistry); // Create a manifest and update the options with the manifest file. const manifest = await manifestUtils_1.createManifest(this.org, options, mdFullPairs, outputDirPath); return manifest.file; } // eslint-disable-next-line @typescript-eslint/require-await async processResults(result, sourceElements) { const inboundFiles = []; sourceElements.getAllWorkspaceElements().forEach((workspaceElement) => { syncCommandHelper.createDisplayRows(inboundFiles, workspaceElement.toObject(), this.projectPath); }); const output = { inboundFiles }; // If there are warning messages along with successes, display them (i.e., partial success). if (result.messages) { output.warnings = parseManifestEntriesArray_1.toArray(result.messages); } return output; } static isRetrieveSuccessful(result) { return result && result.success && lodash_1.get(result, 'status') === 'Succeeded' && Array.isArray(result.fileProperties); } } exports.SourceRetrieve = SourceRetrieve; //# sourceMappingURL=sourceRetrieve.js.map