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

145 lines • 6.68 kB
import { requiredOrgFlagWithDeprecations, SfCommand } from '@salesforce/sf-plugins-core'; import { Flags } from '@salesforce/sf-plugins-core'; import sortArray from 'sort-array'; import { generateReports, uxLog } from '../../../common/utils/index.js'; import { soqlQuery, soqlQueryTooling } from '../../../common/utils/apiUtils.js'; export default class HardisDocFieldusage extends SfCommand { static flags = { 'target-org': requiredOrgFlagWithDeprecations, 'sObjects': Flags.string({ char: 's', description: 'Comma-separated list of sObjects to filter', required: false, }), }; static description = ` Retrieves custom field usage from metadata dependencies for specified sObjects. !["Find custom fields usage"](https://github.com/hardisgroupcom/sfdx-hardis/raw/main/docs/assets/images/doc-fieldusage.png) `; static examples = [ '$ sf hardis:doc:fieldusage', '$ sf hardis:doc:fieldusage --sObjects Account,Contact,Opportunity', '$ sf hardis:doc:fieldusage --target-org myOrgAlias --sObjects CustomObject__c' ]; async querySObjects(connection, sObjectsFilter) { let sObjectsQuery = ` SELECT Id, DeveloperName, PublisherId, IsCustomizable, IsCustomSetting FROM EntityDefinition WHERE IsCustomizable = true `; if (sObjectsFilter && sObjectsFilter.length > 0) { const sObjectsList = sObjectsFilter .map(sObject => sObject.trim().replace(/__c$/, '')) .map(sObject => `'${sObject}'`) .join(','); sObjectsQuery += ` AND DeveloperName IN (${sObjectsList})`; } const sObjectResults = await soqlQuery(sObjectsQuery, connection); uxLog(this, `Found ${sObjectResults.records.length} sObjects.`); return sObjectResults; } async getFilteredSObjects(connection, sObjectsFilter) { const sObjectResults = await this.querySObjects(connection, sObjectsFilter); const sObjectsDict = {}; sObjectResults.records.forEach((record) => { if (!record.DeveloperName.endsWith('__Share') && !record.DeveloperName.endsWith('__ChangeEvent')) { sObjectsDict[record.DeveloperName] = { publisherId: record.PublisherId, fields: [] }; } }); return sObjectsDict; } async queryCustomFields(connection, sObjectName) { uxLog(this, `Extracting fields for sObject: ${sObjectName}`); const queryTooling = ` SELECT Id, DeveloperName FROM CustomField WHERE EntityDefinition.DeveloperName = '${sObjectName}' `; const fieldResults = await soqlQueryTooling(queryTooling, connection); return fieldResults; } async queryMetadataComponentDependency(connection, fieldIds) { const metadataQuery = ` SELECT MetadataComponentId, MetadataComponentType, MetadataComponentName, RefMetadataComponentName, RefMetadataComponentId FROM MetadataComponentDependency WHERE RefMetadataComponentId IN (${fieldIds.join(',')}) `; const dependencyResults = await soqlQueryTooling(metadataQuery, connection); return dependencyResults; } async run() { const { flags } = await this.parse(HardisDocFieldusage); const connection = flags['target-org'].getConnection(); const sObjectsFilter = flags['sObjects'] ? flags['sObjects'].split(',').map(s => s.trim()) : undefined; const sObjectsDict = await this.getFilteredSObjects(connection, sObjectsFilter); const fieldQueries = Object.keys(sObjectsDict).map(async (sObjectName) => { const fieldResults = await this.queryCustomFields(connection, sObjectName); if (fieldResults.records.length > 0) { fieldResults.records.forEach((field) => { sObjectsDict[sObjectName].fields.push({ id: field.Id, name: field.DeveloperName, type: "custom", usedIn: [] }); }); } }); await Promise.all(fieldQueries); const dependencyQueries = Object.entries(sObjectsDict).map(async ([sObjectName, { fields }]) => { if (fields.length === 0) { uxLog(this, `sObject ${sObjectName} does not have any custom fields, skipping dependencies.`); return; } uxLog(this, `Retrieving dependencies for sObject: ${sObjectName}`); const fieldIds = fields.map((field) => `'${field.id}'`); const dependencyResults = await this.queryMetadataComponentDependency(connection, fieldIds); dependencyResults.records.forEach((dep) => { const field = fields.find(f => f.id === dep.RefMetadataComponentId); if (field) { field.usedIn.push({ id: dep.MetadataComponentId, type: dep.MetadataComponentType, name: dep.MetadataComponentName }); } }); }); await Promise.all(dependencyQueries); const columns = [ { key: 'sObjectName', header: 'sObject Name' }, { key: 'fieldName', header: 'Field Name' }, { key: 'fieldType', header: 'Field Type' }, { key: 'dependencyType', header: 'Dependency Type' }, { key: 'dependencyName', header: 'Dependency Name' } ]; const rows = []; for (const [sObjectName, { fields }] of Object.entries(sObjectsDict)) { fields.forEach((field) => { field.usedIn.forEach((dep) => { const row = {}; row[columns[0].key] = sObjectName; row[columns[1].key] = field.name; row[columns[2].key] = field.type; row[columns[3].key] = dep.type; row[columns[4].key] = dep.name; rows.push(row); }); }); } const resultSorted = sortArray(rows, { by: [columns[0].key, columns[1].key, columns[3].key], order: ['asc', 'asc', 'asc'], }); console.table(rows); const reportFiles = await generateReports(resultSorted, columns, this, { logFileName: 'fields-usage', logLabel: 'Find fields usage', }); return { outputString: 'Processed fieldusage doc', result: resultSorted, reportFiles, }; } } //# sourceMappingURL=fieldusage.js.map