salesforce-alm
Version:
This package contains tools, and APIs, for an improved salesforce.com developer experience.
244 lines (242 loc) • 13.8 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.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