UNPKG

@salesforce/source-deploy-retrieve

Version:

JavaScript library to run Salesforce metadata deploys and retrieves

156 lines 8.73 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getPackageOptions = exports.extract = void 0; /* * Copyright (c) 2023, 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 path = __importStar(require("node:path")); const ts_types_1 = require("@salesforce/ts-types"); const graceful_fs_1 = __importDefault(require("graceful-fs")); const metadataConverter_1 = require("../convert/metadataConverter"); const componentSet_1 = require("../collections/componentSet"); const treeContainers_1 = require("../resolve/treeContainers"); const path_1 = require("../utils/path"); const types_1 = require("./types"); const extract = async ({ zip, options, logger, mainComponents, }) => { const components = []; const { merge, output, registry } = options; const converter = new metadataConverter_1.MetadataConverter(registry); const tree = await treeContainers_1.ZipTreeContainer.create(zip); const partialDeleteFileResponses = []; const packages = [ { zipTreeLocation: 'unpackaged', outputDir: output }, ...(0, exports.getPackageOptions)(options.packageOptions).map(({ name, outputDir }) => ({ zipTreeLocation: name, outputDir, })), ]; for (const pkg of packages) { const outputConfig = merge ? { type: 'merge', mergeWith: mainComponents?.getSourceComponents() ?? [], defaultDirectory: pkg.outputDir, forceIgnoredPaths: mainComponents?.forceIgnoredPaths ?? new Set(), } : { type: 'directory', outputDirectory: pkg.outputDir, }; const retrievedComponents = componentSet_1.ComponentSet.fromSource({ fsPaths: [pkg.zipTreeLocation], registry, tree, }) .getSourceComponents() .toArray(); if (merge) { partialDeleteFileResponses.push(...handlePartialDeleteMerges({ retrievedComponents, tree, mainComponents, logger })); } // this is intentional sequential // eslint-disable-next-line no-await-in-loop const convertResult = await converter.convert(retrievedComponents, 'source', outputConfig); components.push(...(convertResult?.converted ?? [])); // additional partialDelete logic for decomposed types are handled in the transformer partialDeleteFileResponses.push(...(convertResult?.deleted ?? [])); } return { componentSet: new componentSet_1.ComponentSet(components, registry), partialDeleteFileResponses }; }; exports.extract = extract; const getPackageOptions = (packageOptions) => (packageOptions ?? []).map((po) => (0, ts_types_1.isString)(po) ? { name: po, outputDir: po } : { name: po.name, outputDir: po.outputDir ?? po.name }); exports.getPackageOptions = getPackageOptions; // Some bundle-like components can be partially deleted in the org, then retrieved. When this // happens, the deleted files need to be deleted on the file system and added to the FileResponses // that are returned by `RetrieveResult.getFileResponses()` for accuracy. The component types that // support this behavior are defined in the metadata registry with `"supportsPartialDelete": true`. // However, not all types can be partially deleted in the org. Currently this only applies to // DigitalExperienceBundle and ExperienceBundle. // side effect: deletes files const handlePartialDeleteMerges = ({ mainComponents, retrievedComponents, tree, logger, }) => { // Find all merge (local) components that support partial delete. const partialDeleteComponents = new Map((mainComponents?.getSourceComponents().toArray() ?? []) .filter(supportsPartialDeleteAndHasContent) .map((comp) => [comp.fullName, { contentPath: comp.content, contentList: graceful_fs_1.default.readdirSync(comp.content) }])); // Compare the contents of the retrieved components that support partial delete with the // matching merge components. If the merge components have files that the retrieved components // don't, delete the merge component and add all locally deleted files to the partial delete list // so that they are added to the `FileResponses` as deletes. return partialDeleteComponents.size === 0 ? [] // If no partial delete components were in the mergeWith ComponentSet, no need to continue. : retrievedComponents .filter(supportsPartialDeleteAndIsInMap(partialDeleteComponents)) .filter((comp) => partialDeleteComponents.get(comp.fullName)?.contentPath) .filter(supportsPartialDeleteAndHasZipContent(tree)) .flatMap((comp) => { // asserted to be defined by the filter above const matchingLocalComp = partialDeleteComponents.get(comp.fullName); const remoteContentList = new Set(tree.readDirectory(comp.content)); return matchingLocalComp.contentList .filter((fileName) => !remoteContentList.has(fileName)) .filter((fileName) => !pathOrSomeChildIsIgnored(logger)(comp)(matchingLocalComp)(fileName)) .map((fileName) => ({ fullName: comp.fullName, type: comp.type.name, state: types_1.ComponentStatus.Deleted, filePath: path.join(matchingLocalComp.contentPath, fileName), })) .map(deleteFilePath(logger)); }); }; const supportsPartialDeleteAndHasContent = (comp) => supportsPartialDelete(comp) && typeof comp.content === 'string' && graceful_fs_1.default.statSync(comp.content).isDirectory(); const supportsPartialDeleteAndHasZipContent = (tree) => (comp) => supportsPartialDelete(comp) && typeof comp.content === 'string' && tree.isDirectory(comp.content); const supportsPartialDeleteAndIsInMap = (partialDeleteComponents) => (comp) => supportsPartialDelete(comp) && partialDeleteComponents.has(comp.fullName); const supportsPartialDelete = (comp) => comp.type.supportsPartialDelete === true; // If fileName is forceignored it is not counted as a diff. If fileName is a directory // we have to read the contents to check forceignore status or we might get a false // negative with `denies()` due to how the ignore library works. const pathOrSomeChildIsIgnored = (logger) => (component) => (localComp) => (fileName) => { const fileNameFullPath = path.join(localComp.contentPath, fileName); return graceful_fs_1.default.statSync(fileNameFullPath).isDirectory() ? graceful_fs_1.default.readdirSync(fileNameFullPath).map((0, path_1.fnJoin)(fileNameFullPath)).some(isForceIgnored(logger)(component)) : isForceIgnored(logger)(component)(fileNameFullPath); }; const isForceIgnored = (logger) => (comp) => (filePath) => { const ignored = comp.getForceIgnore().denies(filePath); if (ignored) { logger.debug(`Local component has ${filePath} while remote does not, but it is forceignored so ignoring.`); } return ignored; }; const deleteFilePath = (logger) => (fr) => { if (fr.filePath) { logger.debug(`Local component (${fr.fullName}) contains ${fr.filePath} while remote component does not. This file is being removed.`); graceful_fs_1.default.rmSync(fr.filePath, { recursive: true, force: true }); } return fr; }; //# sourceMappingURL=retrieveExtract.js.map