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

210 lines (207 loc) • 10.9 kB
/* jscpd:ignore-start */ import { SfCommand, Flags } from '@salesforce/sf-plugins-core'; import { Messages, SfError } from '@salesforce/core'; import c from 'chalk'; import fs from 'fs-extra'; import { glob } from 'glob'; import sortArray from 'sort-array'; import { catchMatches, generateReports, uxLog, uxLogTable } from '../../../../common/utils/index.js'; Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); const messages = Messages.loadMessages('sfdx-hardis', 'org'); import { GLOB_IGNORE_PATTERNS } from '../../../../common/utils/projectUtils.js'; export default class CallInCallOut extends SfCommand { static title = 'Audit Metadatas API Version'; static description = `This command identifies metadata with an apiVersion lower than the value specified in the --minimumapiversion parameter. It can also update the apiVersion to a specific value: - When --fix parameter is provided (updates to minimumapiversion) - When --newapiversion is specified (updates to that version) Example to handle [ApexClass / Trigger & ApexPage mandatory version upgrade](https://help.salesforce.com/s/articleView?id=sf.admin_locales_update_api.htm&type=5) : \`sf hardis:project:audit:apiversion --metadatatype ApexClass,ApexTrigger,ApexPage --minimumapiversion 45 --newapiversion 50\` `; static examples = [ '$ sf hardis:project:audit:apiversion', '$ sf hardis:project:audit:apiversion --metadatatype ApexClass,ApexTrigger,ApexPage --minimumapiversion 45', '$ sf hardis:project:audit:apiversion --metadatatype ApexClass,ApexTrigger,ApexPage --minimumapiversion 45 --fix', '$ sf hardis:project:audit:apiversion --metadatatype ApexClass,ApexTrigger,ApexPage --minimumapiversion 45 --newapiversion 50' ]; // public static args = [{name: 'file'}]; static flags = { minimumapiversion: Flags.integer({ char: 'm', default: 20, description: messages.getMessage('minimumApiVersion'), }), failiferror: Flags.boolean({ char: 'f', default: false, description: messages.getMessage('failIfError'), }), debug: Flags.boolean({ char: 'd', default: false, description: messages.getMessage('debugMode'), }), websocket: Flags.string({ description: messages.getMessage('websocket'), }), skipauth: Flags.boolean({ description: 'Skip authentication check when a default username is required', }), metadatatype: Flags.string({ description: 'Metadata Types to fix. Comma separated. Supported Metadata types: ApexClass, ApexTrigger, ApexPage' }), fix: Flags.boolean({ // can't use "f", already use for failiferror default: false, description: 'Automatically update API versions in files that are below the minimum version threshold to match the minimum version', }), newapiversion: Flags.integer({ char: 'n', description: 'Define an API version value to apply when updating files', }), }; // Set this to true if your command requires a project workspace; 'requiresProject' is false by default static requiresProject = true; matchResults = []; async run() { const { flags } = await this.parse(CallInCallOut); const minimumApiVersion = flags.minimumapiversion || false; const failIfError = flags.failiferror || false; const newApiVersion = flags.newapiversion; // Apply fixes if either fix flag is present or a new API version is specified const shouldFix = flags.fix || (newApiVersion !== undefined); const fixApiVersion = newApiVersion || minimumApiVersion; const metadataType = flags.metadatatype || ''; const fixAllowedExtensions = { "ApexClass": "cls", "ApexTrigger": "trigger", "ApexPage": "page", }; const fixAllowedMetadataTypes = Object.keys(fixAllowedExtensions); const fixTargetedMetadataTypes = metadataType.trim() === '' ? [] : (metadataType || '').replace(/\s+/g, '').split(','); const fixInvalidMetadataTypes = fixTargetedMetadataTypes.filter(value => !fixAllowedMetadataTypes.includes(value)); if (fixTargetedMetadataTypes.length > 0 && fixInvalidMetadataTypes.length > 0 && shouldFix) { uxLog("warning", this, c.yellow(`[sfdx-hardis] WARNING: --fix Invalid Metadata Type(s) found: ${c.bold(fixInvalidMetadataTypes.join(', '))}. Only ${c.bold(fixAllowedMetadataTypes.join(', '))} Metadata Types are allowed for the fix.`)); if (failIfError) { throw new SfError(c.red(`[sfdx-hardis] WARNING: --fix Invalid Metadata Type(s) found: ${c.bold(fixInvalidMetadataTypes.join(', '))}. Only ${c.bold(fixAllowedMetadataTypes.join(', '))} Metadata Types are allowed for the fix.`)); } } // Metadata Type Extensions to fix const fixTargetedMetadataTypesExtensions = fixTargetedMetadataTypes.map(type => fixAllowedExtensions[type]); const fixTargetedMetadataTypesPattern = new RegExp(`\\.(${fixTargetedMetadataTypesExtensions.join('|')})-meta\\.xml$`); let pattern = '**/*.xml'; if (fixTargetedMetadataTypes.length > 0) { // Check if there's only one extension type if (fixTargetedMetadataTypesExtensions.length === 1) { pattern = `**/*.${fixTargetedMetadataTypesExtensions[0]}-meta.xml`; } else { pattern = `**/*.{${fixTargetedMetadataTypesExtensions.join(',')}}-meta.xml`; } } const catchers = [ { type: 'apiVersion', subType: '', regex: /<apiVersion>(.*?)<\/apiVersion>/gims, detail: [{ name: 'apiVersion', regex: /<apiVersion>(.*?)<\/apiVersion>/gims }], fixed: false, }, ]; const xmlFiles = await glob(pattern, { ignore: GLOB_IGNORE_PATTERNS }); this.matchResults = []; uxLog("other", this, `Browsing ${xmlFiles.length} files`); // Loop in files for (const file of xmlFiles) { try { const fileText = await fs.readFile(file, 'utf8'); // Update ApiVersion on file let fixed = false; if (shouldFix && fixTargetedMetadataTypes.length > 0 && fixTargetedMetadataTypesPattern.test(file)) { const apiVersionMatch = fileText.match(/<apiVersion>(.*?)<\/apiVersion>/); if (apiVersionMatch && apiVersionMatch[1]) { const currentApiVersion = parseFloat(apiVersionMatch[1]); if (currentApiVersion < minimumApiVersion) { const updatedContent = fileText.replace(/<apiVersion>(.*?)<\/apiVersion>/, `<apiVersion>${fixApiVersion}.0</apiVersion>`); await fs.promises.writeFile(file, updatedContent, 'utf-8'); fixed = true; uxLog("other", this, `Updated apiVersion in file: ${file} from ${currentApiVersion}.0 to ${fixApiVersion}.0`); } } } // Loop on criteria to find matches in this file for (const catcher of catchers) { const catcherMatchResults = await catchMatches(catcher, file, fileText, this); // Add the "fixed" flag const enrichedResults = catcherMatchResults.map(result => ({ ...result, fixed, })); this.matchResults.push(...enrichedResults); } } catch (error) { if (error instanceof Error) { uxLog("warning", this, c.yellow(`Error processing file ${file}: ${error.message}`)); } else { uxLog("warning", this, c.yellow(`Error processing file ${file}: ${String(error)}`)); } } } // Format result const result = this.matchResults.map((item) => { return { type: item.type, fileName: item.fileName, nameSpace: item.fileName.includes('__') ? item.fileName.split('__')[0] : 'Custom', apiVersion: parseFloat(item.detail['apiVersion']), valid: parseFloat(item.detail['apiVersion']) >= (minimumApiVersion || 100) ? 'yes' : 'no', fixed: item.fixed ? 'yes' : 'no', }; }); // Sort array const resultSorted = sortArray(result, { by: ['type', 'subType', 'fileName'], order: ['asc', 'asc', 'asc'], }); // Display as table uxLog("action", this, c.cyan(`Found ${c.bold(resultSorted.length)} metadata files with API Version.`)); const resultsLight = JSON.parse(JSON.stringify(resultSorted)); uxLogTable(this, resultsLight.map((item) => { delete item.detail; return item; })); const numberOfInvalid = result.filter((res) => res.valid === 'no').length; const numberOfValid = result.length - numberOfInvalid; if (numberOfInvalid > 0) { uxLog("warning", this, c.yellow(`WARNING: Your sources contain ${c.bold(numberOfInvalid)} metadata files with API Version lesser than ${c.bold(minimumApiVersion)}`)); if (failIfError) { throw new SfError(c.red(`${c.bold(numberOfInvalid)} metadata files with wrong API version detected`)); } } else { uxLog("success", this, c.green(`SUCCESS: Your sources contain ${c.bold(numberOfValid)} metadata files with API Version superior to ${c.bold(minimumApiVersion)}`)); } // Generate output files const columns = [ { key: 'type', header: 'IN/OUT' }, { key: 'fileName', header: 'Apex' }, { key: 'nameSpace', header: 'Namespace' }, { key: 'apiVersion', header: 'API Version' }, { key: 'valid', header: `Valid ( > ${minimumApiVersion} )` }, { key: 'fixed', header: 'Fixed' }, ]; const reportFiles = await generateReports(resultSorted, columns, this, { logFileName: 'api-versions', logLabel: 'Extract and Fix Metadata Api Versions', }); // Return an object to be displayed with --json return { outputString: 'Processed apiVersion audit', result: resultSorted, reportFiles, }; } } //# sourceMappingURL=apiversion.js.map