UNPKG

@jayree/sfdx-plugin-manifest

Version:

A Salesforce CLI plugin containing commands for creating manifest files from Salesforce orgs or git commits of sfdx projects.

172 lines 7.64 kB
/* * Copyright 2025, jayree * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { Messages } from '@salesforce/core'; import { CLIError } from '@oclif/core/errors'; import fs from 'fs-extra'; import { SfCommand, Flags, StandardColors } from '@salesforce/sf-plugins-core'; import { ensureArray } from '@salesforce/kit'; import { XMLParser, XMLBuilder } from 'fast-xml-parser'; import { XML_DECL, XML_NS_KEY, XML_NS_URL } from '@salesforce/source-deploy-retrieve/lib/src/common/index.js'; Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); const messages = Messages.loadMessages('@jayree/sfdx-plugin-manifest', 'manifestcleanup'); export default class CleanupManifest extends SfCommand { static summary = messages.getMessage('summary'); static description = messages.getMessage('description'); static examples = messages.getMessages('examples'); static requiresProject = true; static flags = { manifest: Flags.file({ char: 'x', summary: messages.getMessage('flags.manifest.summary'), }), file: Flags.file({ char: 'f', required: true, summary: messages.getMessage('flags.file.summary'), }), }; async run() { const { flags } = await this.parse(CleanupManifest); const file = flags['file']; if (!(await fs.pathExists(file))) { await fs.ensureFile(file); await fs.writeFile(file, `<?xml version="1.0" encoding="UTF-8"?> <Package xmlns="http://soap.sforce.com/2006/04/metadata"> <types> <name>Audience</name> <!--Remove all members/the entire type--> <members>*</members> </types> <types> <name>SharingRules</name> <!--Remove specified members from type--> <members>VideoCall</members> <!--Remove all members starting with Video from type--> <members>Video*</members> </types> <types> <name>Report</name> <!--Remove all members from type, but keep members: MyFolder, MyFolder/MyReport. If you don't need all your reports in your repository--> <members>*</members> <members>MyFolder</members> <members>MyFolder/MyReport</members> </types> <types> <name>CustomObjectTranslation</name> <!--Remove all members from type, but keep members ending with -de. If you don't want to store all CustomObjectTranslation in your repository--> <members>*</members> <members>*-de</members> </types> <types> <name>CustomObject</name> <!--Add members ObjectName1, ObjectName2. If you have used 'excludemanaged' with 'jayree:manifest:generate' to re-add required managed components--> <members>!ObjectName1</members> <members>!ObjectName2</members> </types> <version>52.0</version> </Package> `); this.log(`Cleanup manifest file template '${file}' was created`); } else { if (!flags['manifest'] || !(await fs.pathExists(flags['manifest']))) { throw new CLIError(`The following error occurred:\n ${StandardColors.info('Missing required flag manifest')}`); } await this.cleanupManifestFile(flags['manifest'], file); } return; } async cleanupManifestFile(manifest, ignoreManifest) { const { packageTypeMembers: manifestTypeMembers, version } = parseManifest(fs.readFileSync(manifest, 'utf8')); this.log(`apply '${ignoreManifest}' to '${manifest}'`); const typeMap = new Map(); manifestTypeMembers.forEach((value) => { typeMap.set(value.name, ensureArray(value.members)); }); const { packageTypeMembers: ignoreTypeMembers } = parseManifest(fs.readFileSync(ignoreManifest, 'utf8')); const resolveWildCard = (name, members) => { members .filter((m) => m.includes('*') && m.length > 1) .forEach((i) => { members.splice(members.indexOf(i), 1); const wildCard = i.split('*'); members = members.concat(typeMap.get(name)?.filter((m) => { if (wildCard.length === 2) { if (i.startsWith('*')) { return m.endsWith(wildCard[1]); } else if (i.endsWith('*')) { return m.startsWith(wildCard[0]); } else { return m.startsWith(wildCard[0]) && m.endsWith(wildCard[1]); } } })); }); return members; }; ignoreTypeMembers.forEach((types) => { if (!typeMap.get(types.name)) { typeMap.set(types.name, []); } const packageTypeMembers = resolveWildCard(types.name, ensureArray(types.members)); if (packageTypeMembers.includes('*') && packageTypeMembers.length > 1) { const includemembers = packageTypeMembers.slice(); includemembers.splice(includemembers.indexOf('*'), 1); const includedmembers = typeMap.get(types.name)?.filter((value) => includemembers.includes(value)); if (includedmembers?.length) { this.log('include only members ' + includedmembers.toString() + ' for type ' + types.name); typeMap.set(types.name, includedmembers); } } if (packageTypeMembers.includes('*') && packageTypeMembers.length === 1) { this.log('exclude all members for type ' + types.name); typeMap.delete(types.name); } if (!packageTypeMembers.includes('*')) { const includedmembers = typeMap.get(types.name)?.filter((value) => !packageTypeMembers.includes(value)); if (includedmembers) { typeMap.set(types.name, includedmembers); } } packageTypeMembers.forEach((member) => { if (member.startsWith('!') && !typeMap.get(types.name)?.includes(member.substring(1))) { typeMap.get(types.name)?.push(member.substring(1)); } }); }); const typeMembers = []; for (const [typeName, members] of typeMap.entries()) { if (members.length) { typeMembers.push({ members, name: typeName }); } } await fs.writeFile(manifest, js2Manifest({ Package: { types: typeMembers, version } })); } } function parseManifest(xmlData) { const parser = new XMLParser({ stopNodes: ['version'], parseTagValue: false }); const { Package: { types, version }, } = parser.parse(xmlData); const packageTypeMembers = ensureArray(types); return { packageTypeMembers, version }; } function js2Manifest(jsData) { const js2Xml = new XMLBuilder({ format: true, indentBy: ' ', ignoreAttributes: false }); jsData.Package[XML_NS_KEY] = XML_NS_URL; return XML_DECL.concat(js2Xml.build(jsData)); } //# sourceMappingURL=cleanup.js.map