UNPKG

sfdx-hardis

Version:

Swiss-army-knife Toolbox for Salesforce. Allows you to define a complete CD/CD Pipeline. Orchestrate base commands and assist users with interactive wizards

208 lines (207 loc) 9.22 kB
/* jscpd:ignore-start */ // External Libraries import { glob } from 'glob'; import fs from 'fs-extra'; import * as xml2js from 'xml2js'; import * as path from 'path'; // Salesforce Specific import { SfCommand, Flags, optionalOrgFlagWithDeprecations } from '@salesforce/sf-plugins-core'; import { Messages } from '@salesforce/core'; // Project Specific Utilities import { NotifProvider } from '../../../common/notifProvider/index.js'; import { getNotificationButtons, getBranchMarkdown, getSeverityIcon } from '../../../common/utils/notifUtils.js'; import { generateCsvFile, generateReportPath } from '../../../common/utils/filesUtils.js'; import { uxLog } from '../../../common/utils/index.js'; import { GLOB_IGNORE_PATTERNS } from '../../../common/utils/projectUtils.js'; import { CONSTANTS } from '../../../config/index.js'; import { setConnectionVariables } from '../../../common/utils/orgUtils.js'; Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); const messages = Messages.loadMessages('sfdx-hardis', 'org'); /* jscpd:ignore-end */ export default class UnusedMetadatas extends SfCommand { static title = 'check unused labels and custom permissions'; static description = `Check if elements (custom labels and custom permissions) are used in the project This command is part of [sfdx-hardis Monitoring](${CONSTANTS.DOC_URL_ROOT}/salesforce-monitoring-unused-metadata/) and can output Grafana, Slack and MsTeams Notifications. `; static examples = ['$ sf hardis:lint:unusedmetadatas']; /* jscpd:ignore-start */ static flags = { debug: Flags.boolean({ char: 'd', default: false, description: messages.getMessage('debugMode'), }), outputfile: Flags.string({ char: 'f', description: 'Force the path and name of output report file. Must end with .csv', }), websocket: Flags.string({ description: messages.getMessage('websocket'), }), skipauth: Flags.boolean({ description: 'Skip authentication check when a default username is required', }), 'target-org': optionalOrgFlagWithDeprecations, }; /* jscpd:ignore-end */ unusedData = []; outputFile; outputFilesRes = {}; static supportsDevhubUsername = false; // Set this to true if your command requires a project workspace; 'requiresProject' is false by default static requiresProject = true; ignorePatterns = GLOB_IGNORE_PATTERNS; projectFiles; labelFilePattern = '**/CustomLabels.labels-meta.xml'; customPermissionFilePattern = '**/customPermissions/*.xml'; async run() { const { flags } = await this.parse(UnusedMetadatas); await this.setProjectFiles(); const unusedLabels = await this.verifyLabels(); const unusedCustomPermissions = await this.verifyCustomPermissions(); // Build notification const branchMd = await getBranchMarkdown(); const notifButtons = await getNotificationButtons(); let notifSeverity = 'log'; let notifText = `No unused metadatas has been detected in ${branchMd}`; const attachments = []; if (unusedLabels.length > 0) { attachments.push({ text: `*Unused Labels*\n${unusedLabels.map((label) => `• ${label.name}`).join('\n')}`, }); } if (unusedCustomPermissions.length > 0) { attachments.push({ text: `*Unused Custom Permissions*\n${unusedCustomPermissions .map((permission) => `• ${permission.name}`) .join('\n')}`, }); } if (unusedLabels.length > 0 || unusedCustomPermissions.length > 0) { notifSeverity = 'warning'; notifText = `${this.unusedData.length} unused metadatas have been detected in ${branchMd}`; await this.buildCsvFile(unusedLabels, unusedCustomPermissions); } else { uxLog(this, 'No unused labels or custom permissions detected.'); } // Post notification await setConnectionVariables(flags['target-org']?.getConnection()); // Required for some notifications providers like Email await NotifProvider.postNotifications({ type: 'UNUSED_METADATAS', text: notifText, attachments: attachments, buttons: notifButtons, severity: notifSeverity, sideImage: 'flow', logElements: this.unusedData, data: { metric: this.unusedData.length }, metrics: { MetadatasNotUsed: this.unusedData.length, }, }); return {}; } /** * @description Verify if custom labels are used in the project * @returns */ async verifyLabels() { const labelFiles = await glob(this.labelFilePattern, { ignore: this.ignorePatterns }); const labelFilePath = labelFiles[0]; if (!labelFilePath) { console.warn('No label file found.'); return []; } return new Promise((resolve, reject) => { fs.readFile(labelFilePath, 'utf-8', (errorReadingFile, data) => { if (errorReadingFile) { reject(errorReadingFile); return; } xml2js.parseString(data, (errorParseString, result) => { if (errorParseString) { reject(errorParseString); return; } const severityIconInfo = getSeverityIcon('info'); const labelsArray = result.CustomLabels.labels.map((label) => label.fullName[0]); const unusedLabels = labelsArray .filter((label) => { const labelLower = `label.${label.toLowerCase()}`; const cLower = `c.${label.toLowerCase()}`; const auraPattern = `{!$Label.c.${label.toLowerCase()}}`; return !this.projectFiles.some((filePath) => { const fileContent = fs.readFileSync(filePath, 'utf-8').toLowerCase(); return (fileContent.includes(labelLower) || fileContent.includes(cLower) || fileContent.includes(auraPattern)); }); }) .map((label) => { return { name: label, severity: 'info', severityIcon: severityIconInfo, }; }); resolve(unusedLabels); }); }); }); } /** * @description Verify if custom permissions are used in the project * @returns */ async verifyCustomPermissions() { const foundLabels = new Map(); const customPermissionFiles = await glob(this.customPermissionFilePattern, { ignore: this.ignorePatterns, }); if (!customPermissionFiles) { console.warn('No custom permission file found.'); return []; } for (const file of customPermissionFiles) { const fileData = await fs.readFile(file, 'utf-8'); const fileName = path.basename(file, '.customPermission-meta.xml'); let label = ''; xml2js.parseString(fileData, (error, result) => { if (error) { console.error(`Error parsing XML: ${error}`); return; } label = result.CustomPermission.label[0]; }); for (const filePath of this.projectFiles) { const fileContent = fs.readFileSync(filePath, 'utf-8'); if (fileContent.includes(fileName) || fileContent.includes(label)) { const currentCount = foundLabels.get(fileName) || 0; foundLabels.set(fileName, currentCount + 1); } } } const severityIconInfo = getSeverityIcon('info'); const result = [...foundLabels.keys()] .filter((key) => (foundLabels.get(key) || 0) < 2) .map((name) => { return { name: name, severity: 'info', severityIcon: severityIconInfo, }; }); return result; } async setProjectFiles() { this.projectFiles = await glob('**/*.{cls,trigger,js,html,xml,cmp,email,page}', { ignore: this.ignorePatterns }); } async buildCsvFile(unusedLabels, unusedCustomPermissions) { this.outputFile = await generateReportPath('lint-unusedmetadatas', this.outputFile); this.unusedData = [ ...unusedLabels.map((label) => ({ type: 'Label', name: label })), ...unusedCustomPermissions.map((permission) => ({ type: 'Custom Permission', name: permission })), ]; this.outputFilesRes = await generateCsvFile(this.unusedData, this.outputFile); } } //# sourceMappingURL=unusedmetadatas.js.map