@salesforce/source-deploy-retrieve
Version:
JavaScript library to run Salesforce metadata deploys and retrieves
169 lines • 13.6 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.BaseSourceAdapter = 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
*/
const node_path_1 = require("node:path");
const core_1 = require("@salesforce/core");
const ts_types_1 = require("@salesforce/ts-types");
const path_1 = require("../../utils/path");
const forceIgnore_1 = require("../forceIgnore");
const treeContainers_1 = require("../treeContainers");
const sourceComponent_1 = require("../sourceComponent");
const registryAccess_1 = require("../../registry/registryAccess");
;
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>"]]));
class BaseSourceAdapter {
type;
registry;
forceIgnore;
tree;
/**
* Whether or not an adapter should expect a component to be in its own, self-named
* folder, including its root metadata xml file.
*/
ownFolder = false;
metadataWithContent = true;
constructor(type, registry = new registryAccess_1.RegistryAccess(), forceIgnore = new forceIgnore_1.ForceIgnore(), tree = new treeContainers_1.NodeFSTreeContainer()) {
this.type = type;
this.registry = registry;
this.forceIgnore = forceIgnore;
this.tree = tree;
}
getComponent(path, isResolvingSource = true) {
let rootMetadata = this.parseAsRootMetadataXml(path);
if (!rootMetadata) {
const rootMetadataPath = this.getRootMetadataXmlPath(path);
if (rootMetadataPath) {
rootMetadata = this.parseMetadataXml(rootMetadataPath);
}
}
if (rootMetadata && this.forceIgnore.denies(rootMetadata.path)) {
throw new core_1.SfError(messages.getMessage('error_no_metadata_xml_ignore', [rootMetadata.path, path]), 'UnexpectedForceIgnore');
}
let component;
if (rootMetadata) {
const name = calculateName(this.registry)(this.type)(rootMetadata);
component = new sourceComponent_1.SourceComponent({
name,
type: this.type,
xml: rootMetadata.path,
parentType: this.type.folderType ? this.registry.getTypeByName(this.type.folderType) : undefined,
}, this.tree, this.forceIgnore);
}
return this.populate(path, component, isResolvingSource);
}
/**
* Control whether metadata and content metadata files are allowed for an adapter.
*/
allowMetadataWithContent() {
return this.metadataWithContent;
}
/**
* If the path given to `getComponent` is the root metadata xml file for a component,
* parse the name and return it. This is an optimization to not make a child adapter do
* anymore work to find it.
*
* @param path File path of a metadata component
*/
parseAsRootMetadataXml(path) {
const metaXml = this.parseMetadataXml(path);
if (metaXml) {
let isRootMetadataXml = false;
if (this.type.strictDirectoryName) {
const parentPath = (0, node_path_1.dirname)(path);
const typeDirName = (0, node_path_1.basename)(this.type.inFolder ? (0, node_path_1.dirname)(parentPath) : parentPath);
const nameMatchesParent = (0, node_path_1.basename)(parentPath) === metaXml.fullName;
const inTypeDir = typeDirName === this.type.directoryName;
// if the parent folder name matches the fullName OR parent folder name is
// metadata type's directory name, it's a root metadata xml.
isRootMetadataXml = nameMatchesParent || inTypeDir;
}
else {
isRootMetadataXml = true;
}
return isRootMetadataXml ? metaXml : undefined;
}
const folderMetadataXml = parseAsFolderMetadataXml(path);
if (folderMetadataXml) {
return folderMetadataXml;
}
if (!this.allowMetadataWithContent()) {
return parseAsContentMetadataXml(this.type)(path);
}
}
// allowed to preserve API
// eslint-disable-next-line class-methods-use-this
parseMetadataXml(path) {
return (0, path_1.parseMetadataXml)(path);
}
}
exports.BaseSourceAdapter = BaseSourceAdapter;
/**
* If the path given to `getComponent` serves as the sole definition (metadata and content)
* for a component, parse the name and return it. This allows matching files in metadata
* format such as:
*
* .../tabs/MyTab.tab
*
* @param path File path of a metadata component
*/
const parseAsContentMetadataXml = (type) => (path) => {
// InFolder metadata can be nested more than 1 level beneath its
// associated directoryName.
if (type.inFolder) {
const fullName = (0, path_1.parseNestedFullName)(path, type.directoryName);
if (fullName && type.suffix) {
return { fullName, suffix: type.suffix, path };
}
}
const parentPath = (0, node_path_1.dirname)(path);
const parts = parentPath.split(node_path_1.sep);
const typeFolderIndex = parts.lastIndexOf(type.directoryName);
// nestedTypes (ex: territory2) have a folderType equal to their type but are themselves
// in a folder per metadata item, with child folders for rules/territories
const allowedIndex = type.folderType === type.id ? parts.length - 2 : parts.length - 1;
if (typeFolderIndex !== allowedIndex) {
return undefined;
}
const match = new RegExp(/(.+)\.(.+)/).exec((0, node_path_1.basename)(path));
if (match && type.suffix === match[2]) {
return { fullName: match[1], suffix: match[2], path };
}
};
const parseAsFolderMetadataXml = (fsPath) => {
const match = new RegExp(/(.+)-meta\.xml$/).exec((0, node_path_1.basename)(fsPath));
const parts = fsPath.split(node_path_1.sep);
if (match && !match[1].includes('.') && parts.length > 1) {
return { fullName: match[1], suffix: undefined, path: fsPath };
}
};
// Given a MetadataXml, build a fullName from the path and type.
const calculateName = (registry) => (type) => (rootMetadata) => {
const { directoryName, inFolder, folderType, folderContentType } = type;
// inFolder types (report, dashboard, emailTemplate, document) and their folder
// container types (reportFolder, dashboardFolder, emailFolder, documentFolder)
if (folderContentType ?? inFolder) {
return (0, ts_types_1.ensureString)((0, path_1.parseNestedFullName)(rootMetadata.path, directoryName), `Unable to calculate fullName from component at path: ${rootMetadata.path} (${type.name})`);
}
// not using folders? then name is fullname
if (!folderType) {
return rootMetadata.fullName;
}
const grandparentType = registry.getTypeByName(folderType);
// type is nested inside another type (ex: Territory2Model). So the names are modelName.ruleName or modelName.territoryName
if (grandparentType.folderType && grandparentType.folderType !== type.id) {
const splits = rootMetadata.path.split(node_path_1.sep);
return `${splits[splits.indexOf(grandparentType.directoryName) + 1]}.${rootMetadata.fullName}`;
}
// this is the top level of nested types (ex: in a Territory2Model, the Territory2Model)
if (grandparentType.folderType === type.id) {
return rootMetadata.fullName;
}
throw messages.createError('cantGetName', [rootMetadata.path, type.name]);
};
//# sourceMappingURL=baseSourceAdapter.js.map
;