UNPKG

@salesforce/source-deploy-retrieve

Version:

JavaScript library to run Salesforce metadata deploys and retrieves

105 lines 5.44 kB
"use strict"; /* * 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