@salesforce/source-deploy-retrieve
Version:
JavaScript library to run Salesforce metadata deploys and retrieves
105 lines • 5.44 kB
JavaScript
/*
* Copyright (c) 2021, 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.ManifestResolver = void 0;
const fast_xml_parser_1 = require("fast-xml-parser");
const kit_1 = require("@salesforce/kit");
const core_1 = require("@salesforce/core");
const registryAccess_1 = require("../registry/registryAccess");
const treeContainers_1 = require("./treeContainers");
/**
* Resolve MetadataComponents from a manifest file (package.xml)
*/
class ManifestResolver {
tree;
registry;
constructor(tree = new treeContainers_1.NodeFSTreeContainer(), registry = new registryAccess_1.RegistryAccess()) {
this.tree = tree;
this.registry = registry;
}
async resolve(manifestPath) {
const contents = (await this.tree.readFile(manifestPath)).toString();
const validatedContents = validateFileContents(manifestPath)(contents);
const parser = new fast_xml_parser_1.XMLParser({
stopNodes: ['version'],
// In order to preserve the .0 on the apiVersion skip parsing it
numberParseOptions: { leadingZeros: false, hex: false, skipLike: /\.0$/ },
});
const parsedManifest = parser.parse(validatedContents).Package;
const components = (0, kit_1.ensureArray)(parsedManifest.types)
.map(getValidatedType(manifestPath))
.flatMap((typeMembers) => {
const type = this.registry.getTypeByName(typeMembers.name);
const parentType = type.folderType ? this.registry.getTypeByName(type.folderType) : undefined;
return (0, kit_1.ensureArray)(typeMembers.members).map((fullName, _index, members) => ({
fullName: resolveFullName(fullName, parentType),
type: !parentType ? type : resolveType(fullName, type, members, parentType),
}));
});
return { components, apiVersion: parsedManifest.version };
}
}
exports.ManifestResolver = ManifestResolver;
/** throw a nice validation error if the contents are invalid. Otherwise, returns the contents */
const validateFileContents = (manifestPath) => (file) => {
const validateResult = fast_xml_parser_1.XMLValidator.validate(file);
if (validateResult !== true) {
const error = new core_1.SfError(`Invalid manifest file: ${manifestPath}. ${validateResult.err.code}: ${validateResult.err.msg} (Line ${validateResult.err.line} Column ${validateResult.err.col})`, 'InvalidManifest');
error.setData(validateResult.err);
throw error;
}
return file;
};
/** protect against empty/invalid typeMember definitions in the manifest */
const getValidatedType = (manifestPath) => (typeMembers) => {
let typeName = typeMembers.name;
// protect against empty/invalid typeMember definitions in the manifest
if (typeof typeName !== 'string' || typeName.length === 0) {
if (typeof typeName === 'object') {
typeName = JSON.stringify(typeName);
}
const err = new Error(`Invalid types definition in manifest file: ${manifestPath}\nFound: "${typeName ?? ''}"`);
err.name = 'InvalidManifest';
throw err;
}
return typeMembers;
};
// Mostly for parents of InFolder types to strip off trailing "/" characters
// in fullNames. Otherwise just returns the fullName.
const resolveFullName = (fullName, parentType) => parentType?.folderContentType && fullName.endsWith('/') ? fullName.substring(0, fullName.length - 1) : fullName;
// Resolve the correct metadata type from metadata entries in a manifest.
// Parents of InFolder types can be detected by looking for a trailing "/"
// character.
const resolveType = (fullName, type, members, parentType) => {
// Quick short-circuit for non-parent types and non-folderTypes
if (!parentType || !type.folderType) {
return type;
}
// Detect parents of InFolder types by looking for a trailing slash on InFolder types
if (parentType?.folderContentType && fullName.endsWith('/')) {
return parentType;
}
return isMemberNestedInFolder(fullName, type, parentType, members) ? parentType : type;
};
// Use the folderType instead of the type from the manifest when:
// 1. InFolder types: (report, dashboard, emailTemplate, document)
// 1a. type.inFolder === true (from metadataRegistry.json) AND
// 1b. The fullName doesn't contain a forward slash character AND
// 1c. The fullName with a slash appended is contained in another member entry
// OR
// 2. Non-InFolder, folder types: (territory2, territory2Model, territory2Type, territory2Rule)
// 2a. type.inFolder !== true (from metadataRegistry.json) AND
// 2b. type.folderType has a value (from metadataRegistry.json) AND
// 2c. This type's parent type has a folderType that doesn't match its ID.
const isMemberNestedInFolder = (fullName, type, parentType, members) => {
const isInFolderType = type.inFolder;
const isNestedInFolder = !fullName.includes('/') || members.some((m) => m.includes(`${fullName}/`));
const isNonMatchingFolder = parentType && parentType.folderType !== parentType.id;
return isInFolderType ? isNestedInFolder : isNonMatchingFolder;
};
//# sourceMappingURL=manifestResolver.js.map
;