salesforce-alm
Version:
This package contains tools, and APIs, for an improved salesforce.com developer experience.
230 lines (228 loc) • 10.5 kB
JavaScript
;
/*
* 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.DescribeMetadataDecomposition = exports.DescribeMetadataAnnotation = void 0;
const util = require("util");
const path = require("path");
const _ = require("lodash");
const xmlMetadataDocument_1 = require("../xmlMetadataDocument");
class DescribeMetadataAnnotation {
}
exports.DescribeMetadataAnnotation = DescribeMetadataAnnotation;
/**
* Decomposition strategy driven by describeMetadata results.
* Each childXmlName is assumed to be a subtype for decomposition.
*
* For this decomposition the aggregate and decomposed documents
* are all mdapi XML. The WSDL is used (while building the decomposition
* configuration in the metadata registry) to interpret the contents
* of the aggregate metadata document.
*/
class DescribeMetadataDecomposition {
constructor(decompositionConfig) {
this.decompositionConfig = decompositionConfig;
}
newDocument(metadataName, xmlAttributes) {
return new xmlMetadataDocument_1.XmlMetadataDocument(metadataName, xmlAttributes);
}
newContainerDocument(metadataName) {
return new xmlMetadataDocument_1.XmlMetadataDocument(metadataName);
}
newDecompositionDocument(metadataName) {
return new xmlMetadataDocument_1.XmlMetadataDocument(metadataName);
}
newComposedDocument(metadataName) {
return new xmlMetadataDocument_1.XmlMetadataDocument(metadataName);
}
compose(container, decompositions) {
const xmlAttributes = DescribeMetadataDecomposition.getXmlAttributesFromDecomposedSource(container, decompositions);
const composed = new xmlMetadataDocument_1.XmlMetadataDocument(this.decompositionConfig.metadataName, xmlAttributes);
if (!util.isNullOrUndefined(container)) {
const metadataElement = DescribeMetadataDecomposition.getFirstElement(container);
let child = metadataElement.firstChild;
while (child !== null) {
const node = composed.data.importNode(child, true);
composed.data.documentElement.appendChild(node);
child = child.nextSibling;
}
}
if (!util.isNullOrUndefined(decompositions)) {
for (const decomposedSubtypeConfig of decompositions.keys()) {
for (const decomposition of decompositions.get(decomposedSubtypeConfig)) {
const fragmentDoc = decomposition.data;
const fragmentElement = composed.data.createElement(decomposedSubtypeConfig.xmlFragmentName);
composed.data.documentElement.appendChild(fragmentElement);
let child = fragmentDoc.documentElement.firstChild;
while (child !== null) {
const node = composed.data.importNode(child, true);
fragmentElement.appendChild(node);
child = child.nextSibling;
}
}
}
}
return composed;
}
decompose(composed, name, manifest, metadataType) {
const xmlAttributes = DescribeMetadataDecomposition.getXmlAttributes(composed);
let container = this.newDocument(this.decompositionConfig.metadataName, xmlAttributes);
let decompositions = new Map();
let child = composed.data.documentElement.firstChild;
while (child !== null) {
if (child.nodeType === 1) {
const decomposedSubtypeConfig = DescribeMetadataDecomposition.findDecompositionSubtypeConfig(this.decompositionConfig.decompositions, child.nodeName);
if (!util.isNullOrUndefined(decomposedSubtypeConfig)) {
const decomposition = new xmlMetadataDocument_1.XmlMetadataDocument(decomposedSubtypeConfig.metadataName, xmlAttributes);
let elt = child.firstChild;
while (elt !== null) {
if (elt.nodeType === 1) {
DescribeMetadataDecomposition.importDocumentElementChild(decomposition.data, elt);
}
elt = elt.nextSibling;
}
const annotation = this.getAnnotation(decomposition, decomposedSubtypeConfig);
decomposition.setAnnotation(annotation);
if (util.isNullOrUndefined(decompositions.get(decomposedSubtypeConfig))) {
decompositions.set(decomposedSubtypeConfig, []);
}
decompositions.get(decomposedSubtypeConfig).push(decomposition);
}
else {
DescribeMetadataDecomposition.importDocumentElementChild(container.data, child);
}
}
child = child.nextSibling;
}
/*
* An aggregate mdapi file can be sparse, but that doesn't necessarily mean anything - either for
* retrieve or deploy. Unfortunately it's also possible to get a dense aggregate even when very
* specific bits have been requested (eg. when, say, a custom field was requested, but also the container
* object by virtue of an object label change).
* So, we use the manifest to see what's been explicitly requested from the server. Anything else
* is not material and should be pruned from the effective decomposition.
*/
container = this.pruneContainer(container, this.decompositionConfig.metadataName, name, manifest, metadataType);
decompositions = this.pruneDocuments(decompositions, name, manifest);
return [container, decompositions];
}
isComposable() {
return true;
}
newAnnotation() {
return new DescribeMetadataAnnotation();
}
getAnnotation(decomposition, subtypeConfig) {
const annotation = this.newAnnotation();
annotation.name = DescribeMetadataDecomposition.getName(decomposition, subtypeConfig.metadataEntityNameElement);
return annotation;
}
pruneDocuments(decompositions, parentName, manifest) {
if (util.isNullOrUndefined(manifest)) {
return decompositions;
}
const pruned = new Map();
decompositions.forEach((value, key) => {
if (key.isAddressable) {
const subtypeMembers = manifest.Package.types.reduce((members, type) => {
if (type.name === key.metadataName) {
return members.concat(type.members);
}
else {
return members;
}
}, []);
const docs = value.filter((doc) => {
const annotation = doc.getAnnotation();
return (subtypeMembers.filter((subtypeMember) => subtypeMember === `${parentName}.${annotation.name}`).length > 0);
});
pruned.set(key, docs);
}
else {
pruned.set(key, value);
}
});
return pruned;
}
pruneContainer(container, containerType, name, manifest, metadataType) {
if (metadataType && !metadataType.isContainerValid(container)) {
return null;
}
if (util.isNullOrUndefined(manifest)) {
return container;
}
const subtypeMembers = manifest.Package.types.reduce((members, type) => {
// eslint-disable-next-line @typescript-eslint/no-shadow
const _getCorrespondingManifestType = (containerType) => {
if (containerType.endsWith('Folder')) {
return containerType.replace('Folder', '');
}
return containerType;
};
if (type.name === _getCorrespondingManifestType(containerType)) {
return members.concat(type.members);
}
else {
return members;
}
}, []);
// The package.xml is using forward slashes, so replace these with path separators so the matching works on Windows
return subtypeMembers.some((subTypeMember) => subTypeMember.replace('/', path.sep) === name) ? container : null;
}
static getName(decomposition, metadataEntityNameElement) {
let child = decomposition.data.documentElement.firstChild;
while (child !== null) {
if (child.nodeType === 1 && child.nodeName === metadataEntityNameElement) {
return child.firstChild.nodeValue;
}
child = child.nextSibling;
}
return null;
}
static importDocumentElementChild(document, child) {
const node = document.importNode(child, true);
document.documentElement.appendChild(node);
}
static findDecompositionSubtypeConfig(decompositionDefs, tag) {
for (const decompositionDef of decompositionDefs) {
if (decompositionDef.xmlFragmentName === tag) {
return decompositionDef;
}
}
return null;
}
static getFirstElement(doc) {
for (let i = 0; i < doc.data.childNodes.length; ++i) {
const child = doc.data.childNodes[i];
if (child.nodeType === 1) {
return child;
}
}
return null;
}
static getXmlAttributes(document) {
if (document) {
return _.values(document.data.documentElement.attributes).map((attribute) => ({
nodeName: attribute.nodeName,
nodeValue: attribute.nodeValue,
}));
}
return [];
}
static getXmlAttributesFromDecomposedSource(container, decompositions) {
if (container) {
return DescribeMetadataDecomposition.getXmlAttributes(container);
}
if (decompositions.size > 0) {
const firstDecomposition = Array.from(decompositions.values())[0][0];
return DescribeMetadataDecomposition.getXmlAttributes(firstDecomposition);
}
return [];
}
}
exports.DescribeMetadataDecomposition = DescribeMetadataDecomposition;
//# sourceMappingURL=describeMetadataDecomposition.js.map