@salesforce/source-deploy-retrieve
Version:
JavaScript library to run Salesforce metadata deploys and retrieves
627 lines • 35.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.simpleKey = exports.ComponentSet = void 0;
/*
* 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
*/
/* eslint @typescript-eslint/unified-signatures:0 */
const fast_xml_parser_1 = require("fast-xml-parser");
const core_1 = require("@salesforce/core");
const ts_types_1 = require("@salesforce/ts-types");
const decomposed_1 = require("../utils/decomposed");
const metadataApiDeploy_1 = require("../client/metadataApiDeploy");
const metadataApiRetrieve_1 = require("../client/metadataApiRetrieve");
const constants_1 = require("../common/constants");
const sourceComponent_1 = require("../resolve/sourceComponent");
const metadataResolver_1 = require("../resolve/metadataResolver");
const connectionResolver_1 = require("../resolve/connectionResolver");
const manifestResolver_1 = require("../resolve/manifestResolver");
const registryAccess_1 = require("../registry/registryAccess");
const coverage_1 = require("../registry/coverage");
const types_1 = require("./types");
const lazyCollection_1 = require("./lazyCollection");
const decodeableMap_1 = require("./decodeableMap");
;
const messages = new core_1.Messages('@salesforce/source-deploy-retrieve', 'sdr', new Map([["md_request_fail", "Metadata API request failed: %s"], ["error_convert_invalid_format", "Invalid conversion format '%s'"], ["error_could_not_infer_type", "%s: Could not infer a metadata type"], ["error_unexpected_child_type", "Unexpected child metadata [%s] found for parent type [%s]"], ["noParent", "Could not find parent type for %s (%s)"], ["error_expected_source_files", "%s: Expected source files for type '%s'"], ["error_failed_convert", "Component conversion failed: %s"], ["error_merge_metadata_target_unsupported", "Merge convert for metadata target format currently unsupported"], ["error_missing_adapter", "Missing adapter '%s' for metadata type '%s'"], ["error_missing_transformer", "Missing transformer '%s' for metadata type '%s'"], ["error_missing_type_definition", "Missing metadata type definition in registry for id '%s'."], ["error_missing_child_type_definition", "Type %s does not have a child type definition %s."], ["noChildTypes", "No child types found in registry for %s (reading %s at %s)"], ["error_no_metadata_xml_ignore", "Metadata xml file %s is forceignored but is required for %s."], ["noSourceIgnore", "%s metadata types require source files, but %s is forceignored."], ["noSourceIgnore.actions", "- Metadata types with content are composed of two files: a content file (ie MyApexClass.cls) and a -meta.xml file (i.e MyApexClass.cls-meta.xml). You must include both files in your .forceignore file. Or try appending \u201C\\*\u201D to your existing .forceignore entry.\n\nSee <https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_exclude_source.htm> for examples"], ["error_path_not_found", "%s: File or folder not found"], ["noContentFound", "SourceComponent %s (metadata type = %s) is missing its content file."], ["noContentFound.actions", ["Ensure the content file exists in the expected location.", "If the content file is in your .forceignore file, ensure the meta-xml file is also ignored to completely exclude it."]], ["error_parsing_xml", "SourceComponent %s (metadata type = %s) does not have an associated metadata xml to parse"], ["error_expected_file_path", "%s: path is to a directory, expected a file"], ["error_expected_directory_path", "%s: path is to a file, expected a directory"], ["error_directory_not_found_or_not_directory", "%s: path is not a directory"], ["error_no_directory_stream", "%s doesn't support readable streams on directories."], ["error_no_source_to_deploy", "No source-backed components present in the package."], ["error_no_components_to_retrieve", "No components in the package to retrieve."], ["error_static_resource_expected_archive_type", "A StaticResource directory must have a content type of application/zip or application/jar - found %s for %s."], ["error_static_resource_missing_resource_file", "A StaticResource must have an associated .resource file, missing %s.resource-meta.xml"], ["error_no_job_id", "The %s operation is missing a job ID. Initialize an operation with an ID, or start a new job."], ["missingApiVersion", "Could not determine an API version to use for the generated manifest. Tried looking for sourceApiVersion in sfdx-project.json, apiVersion from config vars, and the highest apiVersion from the APEX REST endpoint. Using API version 58.0 as a last resort."], ["invalid_xml_parsing", "error parsing %s due to:\\n message: %s\\n line: %s\\n code: %s"], ["zipBufferError", "Zip buffer was not created during conversion"], ["undefinedComponentSet", "Unable to construct a componentSet. Check the logs for more information."], ["replacementsFileNotRead", "The file \"%s\" specified in the \"replacements\" property of sfdx-project.json could not be read."], ["unsupportedBundleType", "Unsupported Bundle Type: %s"], ["filePathGeneratorNoTypeSupport", "Type not supported for filepath generation: %s"], ["missingFolderType", "The registry has %s as is inFolder but it does not have a folderType"], ["tooManyFiles", "Multiple files found for path: %s."], ["cantGetName", "Unable to calculate fullName from path: %s (%s)"], ["missingMetaFileSuffix", "The metadata registry is configured incorrectly for %s. Expected a metaFileSuffix."], ["uniqueIdElementNotInRegistry", "No uniqueIdElement found in registry for %s (reading %s at %s)."], ["uniqueIdElementNotInChild", "The uniqueIdElement %s was not found the child (reading %s at %s)."], ["suggest_type_header", "A metadata type lookup for \"%s\" found the following close matches:"], ["suggest_type_did_you_mean", "-- Did you mean \".%s%s\" instead for the \"%s\" metadata type?"], ["suggest_type_more_suggestions", "Additional suggestions:\nConfirm the file name, extension, and directory names are correct. Validate against the registry at:\n<https://github.com/forcedotcom/source-deploy-retrieve/blob/main/src/registry/metadataRegistry.json>\n\nIf the type is not listed in the registry, check that it has Metadata API support via the Metadata Coverage Report:\n<https://developer.salesforce.com/docs/metadata-coverage>\n\nIf the type is available via Metadata API but not in the registry\n\n- Open an issue <https://github.com/forcedotcom/cli/issues>\n- Add the type via PR. Instructions: <https://github.com/forcedotcom/source-deploy-retrieve/blob/main/contributing/metadata.md>"], ["type_name_suggestions", "Confirm the metadata type name is correct. Validate against the registry at:\n<https://github.com/forcedotcom/source-deploy-retrieve/blob/main/src/registry/metadataRegistry.json>\n\nIf the type is not listed in the registry, check that it has Metadata API support via the Metadata Coverage Report:\n<https://developer.salesforce.com/docs/metadata-coverage>\n\nIf the type is available via Metadata API but not in the registry\n\n- Open an issue <https://github.com/forcedotcom/cli/issues>\n- Add the type via PR. Instructions: <https://github.com/forcedotcom/source-deploy-retrieve/blob/main/contributing/metadata.md>"]]));
const KEY_DELIMITER = '#';
/**
* A collection containing no duplicate metadata members (`fullName` and `type` pairs). `ComponentSets`
* are a convenient way of constructing a unique collection of components to perform operations such as
* deploying and retrieving.
*
* Multiple {@link SourceComponent}s can be present in the set and correspond to the same member.
* This is typically the case when a component's source files are split across locations. For an example, see
* the [multiple package directories](https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_ws_mpd.htm)
* scenario.
*/
class ComponentSet extends lazyCollection_1.LazyCollection {
static WILDCARD = '*';
/**
* The metadata API version to use. E.g., 52.0
*/
apiVersion;
/**
* The metadata API version of the deployed/retrieved source.
* This is used as the value for the `version` field in the manifest.
*/
sourceApiVersion;
/**
* Used to explicitly set the project directory for the component set.
* When not present, sfdx-core's SfProject will use the current working directory.
*/
projectDirectory;
fullName;
forceIgnoredPaths;
logger;
registry;
// all components stored here, regardless of what manifest they belong to
components = new decodeableMap_1.DecodeableMap();
// whether this component set is being used for a deploy
// @ts-expect-error this is currently not used but could be used in the future
forDeploy = false;
// whether this component set is being used for a retrieve
forRetrieve = false;
// internal component maps used by this.getObject() when building manifests.
destructiveComponents = {
[types_1.DestructiveChangesType.PRE]: new decodeableMap_1.DecodeableMap(),
[types_1.DestructiveChangesType.POST]: new decodeableMap_1.DecodeableMap(),
};
// used to store components meant for a "constructive" (not destructive) manifest
manifestComponents = new decodeableMap_1.DecodeableMap();
destructiveChangesType = types_1.DestructiveChangesType.POST;
constructor(components = [], registry = new registryAccess_1.RegistryAccess()) {
super();
this.registry = registry;
this.logger = core_1.Logger.childFromRoot(this.constructor.name);
for (const component of components) {
const destructiveType = component instanceof sourceComponent_1.SourceComponent ? component.getDestructiveChangesType() : this.destructiveChangesType;
this.add(component, destructiveType);
}
}
/**
* Each {@link SourceComponent} counts as an element in the set, even if multiple
* ones map to the same `fullName` and `type` pair.
*
* @returns number of metadata components in the set
*/
get size() {
let size = 0;
for (const collection of this.components.values()) {
// just having an entry in the parent map counts as 1
size += collection.size === 0 ? 1 : collection.size;
}
return size;
}
get destructiveChangesPre() {
return this.destructiveComponents[types_1.DestructiveChangesType.PRE];
}
get destructiveChangesPost() {
return this.destructiveComponents[types_1.DestructiveChangesType.POST];
}
static fromSource(input) {
const parseFromSourceInputs = (given) => {
if (Array.isArray(given)) {
return { fsPaths: given };
}
else if (typeof given === 'object') {
return given;
}
else {
return { fsPaths: [given] };
}
};
const { fsPaths, registry, tree, include, fsDeletePaths = [] } = parseFromSourceInputs(input);
const resolver = new metadataResolver_1.MetadataResolver(registry, tree);
const set = new ComponentSet([], registry);
const buildComponents = (paths, destructiveType) => {
for (const path of paths) {
for (const component of resolver.getComponentsFromPath(path, include)) {
set.add(component, destructiveType);
}
}
};
buildComponents(fsPaths);
buildComponents(fsDeletePaths, types_1.DestructiveChangesType.POST);
set.forceIgnoredPaths = resolver.forceIgnoredPaths;
return set;
}
static async fromManifest(input) {
const manifestPath = typeof input === 'string' ? input : input.manifestPath;
const options = (typeof input === 'object' ? input : {});
const manifestResolver = new manifestResolver_1.ManifestResolver(options.tree, options.registry);
const manifest = await manifestResolver.resolve(manifestPath);
const resolveIncludeSet = options.resolveSourcePaths ? new ComponentSet([], options.registry) : undefined;
const resolvePostSet = options.resolveSourcePaths ? new ComponentSet([], options.registry) : undefined;
const resolvePreSet = options.resolveSourcePaths ? new ComponentSet([], options.registry) : undefined;
const result = new ComponentSet([], options.registry);
result.logger.debug(`Setting sourceApiVersion of ${manifest.apiVersion} on ComponentSet from manifest`);
result.sourceApiVersion = manifest.apiVersion;
result.fullName = manifest.fullName;
const addComponent = (component, deletionType) => {
if (resolveIncludeSet && !deletionType) {
resolveIncludeSet.add(component);
}
if (resolvePreSet && deletionType === types_1.DestructiveChangesType.PRE) {
resolvePreSet.add(component, types_1.DestructiveChangesType.PRE);
}
if (resolvePostSet && deletionType === types_1.DestructiveChangesType.POST) {
resolvePostSet.add(component, types_1.DestructiveChangesType.POST);
}
const memberIsWildcard = component.fullName === ComponentSet.WILDCARD;
if (options.resolveSourcePaths === undefined || !memberIsWildcard || options.forceAddWildcards) {
result.add(component, deletionType);
}
};
const resolveDestructiveChanges = async (path, destructiveChangeType) => {
const destructiveManifest = await manifestResolver.resolve(path);
for (const comp of destructiveManifest.components) {
addComponent(new sourceComponent_1.SourceComponent({ type: comp.type, name: comp.fullName }), destructiveChangeType);
}
};
if (options.destructivePre) {
await resolveDestructiveChanges(options.destructivePre, types_1.DestructiveChangesType.PRE);
}
if (options.destructivePost) {
await resolveDestructiveChanges(options.destructivePost, types_1.DestructiveChangesType.POST);
}
for (const component of manifest.components) {
addComponent(component);
}
if (options.resolveSourcePaths) {
const components = ComponentSet.fromSource({
fsPaths: options.resolveSourcePaths,
tree: options.tree,
include: resolveIncludeSet,
registry: options.registry,
});
result.forceIgnoredPaths = components.forceIgnoredPaths;
for (const component of components) {
result.add(component);
}
// if there was nothing in the resolveIncludeSet, then we can be missing information that we display to the user for deletes
if (resolveIncludeSet?.size === 0) {
const preCS = ComponentSet.fromSource({
fsPaths: options.resolveSourcePaths,
tree: options.tree,
include: resolvePreSet,
registry: options.registry,
});
for (const component of preCS) {
result.add(component, types_1.DestructiveChangesType.PRE);
}
const postCS = ComponentSet.fromSource({
fsPaths: options.resolveSourcePaths,
tree: options.tree,
include: resolvePostSet,
registry: options.registry,
});
for (const component of postCS) {
result.add(component, types_1.DestructiveChangesType.POST);
}
}
}
return result;
}
static async fromConnection(input) {
let usernameOrConnection = typeof input === 'string' ? input : input.usernameOrConnection;
const options = (typeof input === 'object' ? input : {});
if (typeof usernameOrConnection === 'string') {
usernameOrConnection = await core_1.Connection.create({
authInfo: await core_1.AuthInfo.create({ username: usernameOrConnection }),
});
if (options.apiVersion && options.apiVersion !== usernameOrConnection.version) {
usernameOrConnection.setApiVersion(options.apiVersion);
}
}
const connectionResolver = new connectionResolver_1.ConnectionResolver(usernameOrConnection, options.registry, options.metadataTypes);
const manifest = await connectionResolver.resolve(options.componentFilter);
const result = new ComponentSet([], options.registry);
result.apiVersion = manifest.apiVersion;
for (const component of manifest.components) {
result.add(component);
}
return result;
}
/**
* Constructs a deploy operation using the components in the set and starts
* the deployment. There must be at least one source-backed component in
* the set to create an operation.
*
* @param options
* @returns Metadata API deploy operation
*/
async deploy(options) {
const toDeploy = Array.from(this.getSourceComponents());
if (toDeploy.length === 0) {
throw new core_1.SfError(messages.getMessage('error_no_source_to_deploy'), 'ComponentSetError');
}
this.forDeploy = true;
if (typeof options.usernameOrConnection !== 'string' &&
this.apiVersion &&
this.apiVersion !== options.usernameOrConnection.version) {
options.usernameOrConnection.setApiVersion(this.apiVersion);
this.logger.debug(`Received conflicting apiVersion values for deploy. Using option=${this.apiVersion}, Ignoring apiVersion on connection=${options.usernameOrConnection.version}.`);
}
const operationOptions = Object.assign({}, options, {
components: this,
registry: this.registry,
apiVersion: this.apiVersion,
});
const mdapiDeploy = new metadataApiDeploy_1.MetadataApiDeploy(operationOptions);
await mdapiDeploy.start();
return mdapiDeploy;
}
/**
* Constructs a retrieve operation using the components in the set and
* starts the retrieval.
*
* @param options
* @returns Metadata API retrieve operation
*/
async retrieve(options) {
const operationOptions = Object.assign({}, options, {
components: this,
registry: this.registry,
apiVersion: this.apiVersion,
});
this.forRetrieve = true;
if (typeof options.usernameOrConnection !== 'string' &&
this.apiVersion &&
this.apiVersion !== options.usernameOrConnection.version) {
options.usernameOrConnection.setApiVersion(this.apiVersion);
this.logger.debug(`Received conflicting apiVersion values for retrieve. Using option=${this.apiVersion}, Ignoring apiVersion on connection=${options.usernameOrConnection.version}.`);
}
const mdapiRetrieve = new metadataApiRetrieve_1.MetadataApiRetrieve(operationOptions);
await mdapiRetrieve.start();
return mdapiRetrieve;
}
/**
* Get an object representation of a package manifest based on the set components.
*
* @param destructiveType Optional value for generating objects representing destructive change manifests
* @returns Object representation of a package manifest
*/
async getObject(destructiveType) {
// If this ComponentSet has components marked for delete, we need to
// only include those components in a destructiveChanges.xml and
// all other components in the regular manifest.
const components = this.getTypesOfDestructiveChanges().length
? destructiveType
? this.destructiveComponents[destructiveType]
: this.manifestComponents
: this.components;
const typeMap = new Map();
[...components.entries()].map(([key, cmpMap]) => {
const [typeId, fullName] = splitOnFirstDelimiter(key);
const type = this.registry.getTypeByName(typeId);
// Add children
[...(cmpMap?.values() ?? [])]
.flatMap((c) => c.getChildren())
.map((child) => addToTypeMap({ typeMap, type: child.type, fullName: child.fullName, destructiveType }));
// logic: if this is a decomposed type not being retrieved, skip its inclusion in the manifest if the parent is "empty"
if (!this.forRetrieve &&
type.strategies?.transformer === 'decomposed' &&
// exclude (ex: CustomObjectTranslation) where there are no addressable children
Object.values(type.children?.types ?? {}).some((t) => t.unaddressableWithoutParent !== true) &&
Object.values(type.children?.types ?? {}).some((t) => t.isAddressable !== false)) {
const parentComp = [...(cmpMap?.values() ?? [])].find((c) => c.fullName === fullName);
if (parentComp?.xml && !(0, decomposed_1.objectHasSomeRealValues)(type)(parentComp.parseXmlSync())) {
return;
}
}
addToTypeMap({
typeMap,
type: type.folderContentType ? this.registry.getTypeByName(type.folderContentType) : type,
fullName: constructFullName(this.registry, type, fullName),
destructiveType,
});
});
const typeMembers = Array.from(typeMap.entries())
.map(([typeName, members]) => ({ members: [...members].sort(), name: typeName }))
.sort((a, b) => (a.name > b.name ? 1 : -1));
return {
Package: {
...{
types: typeMembers,
version: await this.getApiVersion(),
},
...(this.fullName ? { fullName: this.fullName } : {}),
},
};
}
/**
* Create a manifest in xml format based on the set components and the
* type of manifest to create.
*
* E.g. package.xml or destructiveChanges.xml
*
* @param indentation Number of spaces to indent lines by.
* @param destructiveType What type of destructive manifest to build.
*/
async getPackageXml(indentation = 4, destructiveType) {
const builder = new fast_xml_parser_1.XMLBuilder({
format: true,
indentBy: ''.padEnd(indentation, ' '),
ignoreAttributes: false,
});
const toParse = await this.getObject(destructiveType);
toParse.Package[constants_1.XML_NS_KEY] = constants_1.XML_NS_URL;
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
return constants_1.XML_DECL.concat(builder.build(toParse));
}
/**
* Get only the source-backed metadata components in the set.
*
* @param member Member to retrieve source-backed components for.
* @returns Collection of source-backed components
*/
getSourceComponents(member) {
let iter;
if (member) {
// filter optimization
const memberCollection = this.components.get((0, exports.simpleKey)(member));
iter = memberCollection && memberCollection.size > 0 ? memberCollection.values() : [];
}
else {
// eslint-disable-next-line @typescript-eslint/no-this-alias
iter = this;
}
return new lazyCollection_1.LazyCollection(iter).filter((c) => c instanceof sourceComponent_1.SourceComponent);
}
add(component, deletionType) {
const key = (0, exports.simpleKey)(component);
if (!this.components.has(key)) {
this.components.set(key, new decodeableMap_1.DecodeableMap());
}
if (!deletionType && typeof component.type !== 'string') {
// this component is meant to be added to manifestComponents, even if it's not a fully validated source component,
// this ensures when getObject is called, we created consistent manifests whether using this.components, or this.manifestComponents
// when no destructive changes are present, we use this.components (not fully validated as source components, so typos end up in the generated manifest)
// when destructive changes are used, we use this.manifestComponents (fully validated, would not match this.components)
// this ensures this.components manifest === this.manifestComponents manifest
const sc = new sourceComponent_1.SourceComponent({ type: component.type, name: component.fullName });
const srcKey = sourceKey(sc);
if (!this.manifestComponents.has(key)) {
this.manifestComponents.set(key, new decodeableMap_1.DecodeableMap());
}
this.manifestComponents.get(key)?.set(srcKey, sc);
}
if (!(component instanceof sourceComponent_1.SourceComponent)) {
return;
}
const srcKey = sourceKey(component);
// we're working with SourceComponents now
this.components.get(key)?.set(srcKey, component);
// Build maps of destructive components and regular components as they are added
// as an optimization when building manifests.
if (deletionType) {
component.setMarkedForDelete(deletionType);
this.logger.debug(`Marking component for delete: ${component.fullName}`);
if (!this.destructiveComponents[deletionType].has(key)) {
this.destructiveComponents[deletionType].set(key, new decodeableMap_1.DecodeableMap());
}
this.destructiveComponents[deletionType].get(key)?.set(srcKey, component);
// updated with deletion information
this.components.get(key)?.set(srcKey, component);
}
else {
if (!this.manifestComponents.has(key)) {
this.manifestComponents.set(key, new decodeableMap_1.DecodeableMap());
}
this.manifestComponents.get(key)?.set(srcKey, component);
}
}
/**
* Tests whether or not a `fullName` and `type` pair is present in the set.
*
* A pair is considered present in the set if one of the following criteria is met:
*
* - The pair is directly in the set, matching the component key "as is" or decoded.
* - A wildcard component with the same `type` as the pair
* - If a parent is attached to the pair and the parent is directly in the set
* - If a parent is attached to the pair, and a wildcard component's `type` matches the parent's `type`
*
* @param component Component to test for membership in the set
* @returns `true` if the component is in the set
*/
has(component) {
const key = (0, exports.simpleKey)(component);
if (this.components.has(key)) {
return true;
}
const wildcardMember = {
fullName: ComponentSet.WILDCARD,
type: typeof component.type === 'object' ? component.type.name : component.type,
};
const isIncludedInWildcard = this.components.has((0, exports.simpleKey)(wildcardMember));
if (isIncludedInWildcard) {
return true;
}
if (typeof component.type === 'object') {
const { parent } = component;
if (parent) {
const parentDirectlyInSet = this.components.has((0, exports.simpleKey)(parent));
if (parentDirectlyInSet) {
return true;
}
const wildcardKey = (0, exports.simpleKey)({
fullName: ComponentSet.WILDCARD,
type: parent.type,
});
const parentInWildcard = this.components.has(wildcardKey);
if (parentInWildcard) {
return true;
}
const partialWildcardKey = (0, exports.simpleKey)({
fullName: `${parent.fullName}.${ComponentSet.WILDCARD}`,
type: component.type,
});
const parentInPartialWildcard = this.components.has(partialWildcardKey);
if (parentInPartialWildcard) {
return true;
}
}
}
return false;
}
/**
* For a fullName and type, this returns the filenames the matching component, or an empty array if the component is not present
*
* @param param Object with fullName and type properties
* @returns string[]
*/
getComponentFilenamesByNameAndType({ fullName, type }) {
const key = (0, exports.simpleKey)({ fullName, type });
const componentMap = this.components.get(key);
if (!componentMap) {
return [];
}
const output = new Set();
componentMap.forEach((component) => {
[...component.walkContent(), component.content, component.xml]
.filter(ts_types_1.isString)
.map((filename) => output.add(filename));
});
return Array.from(output);
}
*[Symbol.iterator]() {
for (const [key, sourceComponents] of this.components.entries()) {
if (sourceComponents.size === 0) {
const [typeName, fullName] = splitOnFirstDelimiter(key);
yield {
fullName,
type: this.registry.getTypeByName(typeName),
};
}
else {
for (const component of sourceComponents.values()) {
yield component;
}
}
}
}
/**
* If this `ComponentSet` has components marked for delete, this sets
* whether those components are deleted before any other changes are
* deployed (`destructiveChangesPre.xml`) or after changes are deployed
* (`destructiveChangesPost.xml`).
*
* @param type The type of destructive changes to make; i.e., pre or post deploy.
*/
setDestructiveChangesType(type) {
this.destructiveChangesType = type;
}
/**
* If this `ComponentSet` has components marked for delete it will use this
* type to build the appropriate destructive changes manifest.
*
* @returns The type of destructive changes to make; i.e., pre or post deploy.
*/
getDestructiveChangesType() {
return this.destructiveChangesType;
}
/**
* Will return the types of destructive changes in the component set
* or an empty array if there aren't destructive components present
*
* @return DestructiveChangesType[]
*/
getTypesOfDestructiveChanges() {
const destructiveChangesTypes = [];
if (this.destructiveChangesPre.size) {
destructiveChangesTypes.push(types_1.DestructiveChangesType.PRE);
}
if (this.destructiveChangesPost.size) {
destructiveChangesTypes.push(types_1.DestructiveChangesType.POST);
}
return destructiveChangesTypes;
}
/**
* Returns an API version to use as the value of the `version` field
* in a manifest (package.xml) for MDAPI calls in the following order
* of preference:
*
* 1. this.sourceApiVersion
* 2. this.apiVersion
* 3. sourceApiVersion set in sfdx-project.json
* 4. apiVersion from ConfigAggregator (config files and Env Vars)
* 5. http call to apexrest endpoint for highest apiVersion
* 6. hardcoded value of "58.0" as a last resort
*
* @returns string The resolved API version to use in a manifest
*/
async getApiVersion() {
let version = this.sourceApiVersion ?? this.apiVersion;
if (!version) {
try {
const project = await core_1.SfProject.resolve(this.projectDirectory);
const projectConfig = await project.resolveProjectConfig();
version = projectConfig?.sourceApiVersion;
}
catch (e) {
// If there's any problem just move on to ConfigAggregator
}
}
if (!version) {
try {
version = core_1.ConfigAggregator.getValue(core_1.OrgConfigProperties.ORG_API_VERSION).value;
}
catch (e) {
// If there's any problem just move on to the REST endpoint
}
}
if (!version) {
try {
version = `${await (0, coverage_1.getCurrentApiVersion)()}.0`;
}
catch (e) {
version = '58.0';
this.logger.warn(messages.getMessage('missingApiVersion'));
}
}
return version;
}
}
exports.ComponentSet = ComponentSet;
const sourceKey = (component) => {
const { fullName, type, xml, content } = component;
return `${type.name}${fullName}${xml ?? ''}${content ?? ''}`;
};
const simpleKey = (component) => {
const typeName = typeof component.type === 'string' ? component.type.toLowerCase().trim() : component.type.id;
return `${typeName}${KEY_DELIMITER}${component.fullName}`;
};
exports.simpleKey = simpleKey;
const splitOnFirstDelimiter = (input) => {
const indexOfSplitChar = input.indexOf(KEY_DELIMITER);
return [input.substring(0, indexOfSplitChar), input.substring(indexOfSplitChar + 1)];
};
const constructFullName = (registry, type, fullName) =>
// Some InFolder types are different. e.g., Report/ReportFolder & Dashboard/DashboardFolder.
// ReportFolders are deployed/retrieved as Reports. If a ReportFolder is being added append
// a "/" so the metadata API can identify it as a folder.
['DashboardFolder', 'ReportFolder', 'EmailTemplateFolder'].includes(type.name) && !fullName.endsWith('/')
? `${fullName}/`
: registry.getParentType(type.name)?.strategies?.recomposition === 'startEmpty' && fullName.includes('.')
? // they're reassembled like CustomLabels.MyLabel
fullName.split('.')[1]
: fullName;
/** side effect: mutates the typeMap property */
const addToTypeMap = ({ typeMap, type, fullName, destructiveType, }) => {
if (type.isAddressable === false)
return;
if (fullName === ComponentSet.WILDCARD && !type.supportsWildcardAndName && !destructiveType) {
// if the type doesn't support mixed wildcards and specific names, overwrite the names to be a wildcard
typeMap.set(type.name, new Set([fullName]));
return;
}
const existing = typeMap.get(type.name) ?? new Set();
if (!existing.has(ComponentSet.WILDCARD) || type.supportsWildcardAndName) {
// if the type supports both wildcards and names, add them regardless
typeMap.set(type.name, existing.add(fullName));
}
};
//# sourceMappingURL=componentSet.js.map