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
126 lines (123 loc) • 6.88 kB
JavaScript
/* jscpd:ignore-start */
import { SfCommand, Flags, requiredOrgFlagWithDeprecations } from '@salesforce/sf-plugins-core';
import { Messages } from '@salesforce/core';
import c from 'chalk';
import { uxLog, uxLogTable } from '../../../../common/utils/index.js';
import { soqlQueryTooling } from '../../../../common/utils/apiUtils.js';
import { NotifProvider } from '../../../../common/notifProvider/index.js';
import { generateCsvFile, generateReportPath } from '../../../../common/utils/filesUtils.js';
import { getNotificationButtons, getOrgMarkdown, getSeverityIcon } from '../../../../common/utils/notifUtils.js';
import moment from 'moment';
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');
export default class DiagnoseReleaseUpdates extends SfCommand {
static title = 'Check Release Updates of an org';
static description = `Export Release Updates into a CSV file with selected criteria, and highlight Release Updates that should be checked.
Before publishing **Breaking Changes** ❌, Salesforce announce them in the setup menu [**Release Updates**](https://help.salesforce.com/s/articleView?id=sf.release_updates.htm&type=5)
⚠️ Some of them are very important, because if you don't make the related upgrades in time (ex: before Winter 25) , your production org can crash !
This command is part of [sfdx-hardis Monitoring](${CONSTANTS.DOC_URL_ROOT}/salesforce-monitoring-release-updates/) and can output Grafana, Slack and MsTeams Notifications.
`;
static examples = [
'$ sf hardis:org:diagnose:releaseupdates',
];
static flags = {
outputfile: Flags.string({
char: 'f',
description: 'Force the path and name of output report file. Must end with .csv',
}),
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',
}),
'target-org': requiredOrgFlagWithDeprecations,
};
static requiresProject = false;
debugMode = false;
releaseUpdatesRecords = [];
outputFile;
outputFilesRes = {};
/* jscpd:ignore-end */
async run() {
const { flags } = await this.parse(DiagnoseReleaseUpdates);
this.debugMode = flags.debug || false;
this.outputFile = flags.outputfile || null;
const conn = flags['target-org'].getConnection();
uxLog("action", this, c.cyan(`Extracting Release Updates and checks to perform in ${conn.instanceUrl} ...`));
// Fetch ReleaseUpdate records
const releaseUpdatesQuery = `SELECT StepStage,Status,Category,Title,DueDate,Description,Release,ReleaseLabel,ReleaseDate,ApiVersion,DurableId,HasNewSteps,IsReleased,SupportsRevoke,DeveloperName ` +
`FROM ReleaseUpdate ` +
`WHERE StepStage IN ('Upcoming','OverDue') AND Status IN ('Pending','Invocable','Revocable','Nascent','Invoked','Info') AND DueDate >= LAST_N_DAYS:60 ` +
`ORDER BY DueDate ASC`;
const queryRes = await soqlQueryTooling(releaseUpdatesQuery, conn);
const severityIconWarning = getSeverityIcon('warning');
const severityIconError = getSeverityIcon('error');
this.releaseUpdatesRecords = queryRes.records.map((record) => {
delete record.attributes;
record.severityIcon = record.StepStage === 'OverDue' ? severityIconError : severityIconWarning;
return record;
});
// Process result
if (this.releaseUpdatesRecords.length > 0) {
// Generate output CSV file
this.outputFile = await generateReportPath('release-updates', this.outputFile);
this.outputFilesRes = await generateCsvFile(this.releaseUpdatesRecords, this.outputFile, { fileTitle: 'Release Updates to Check' });
// Build notification
const orgMarkdown = await getOrgMarkdown(flags['target-org']?.getConnection()?.instanceUrl);
const notifButtons = await getNotificationButtons();
const notifSeverity = 'warning';
const notifText = `${this.releaseUpdatesRecords.length} Release Updates to check have been found in ${orgMarkdown}`;
let notifDetailText = '';
for (const releaseUpdate of this.releaseUpdatesRecords) {
notifDetailText += `• *${releaseUpdate.Title}* (${releaseUpdate.StepStage},${releaseUpdate.Status},${releaseUpdate.Category}), due for ${moment(releaseUpdate.DueDate).format("ll")}\n`;
}
const notifAttachments = [{ text: notifDetailText }];
// Post notif
await setConnectionVariables(flags['target-org']?.getConnection()); // Required for some notifications providers like Email
await NotifProvider.postNotifications({
type: 'RELEASE_UPDATES',
text: notifText,
attachments: notifAttachments,
buttons: notifButtons,
severity: notifSeverity,
attachedFiles: this.outputFilesRes.xlsxFile ? [this.outputFilesRes.xlsxFile] : [],
logElements: this.releaseUpdatesRecords,
data: { metric: this.releaseUpdatesRecords.length },
metrics: {
ReleaseUpdates: this.releaseUpdatesRecords.length,
},
});
// Display output
const releaseUpdatesLight = this.releaseUpdatesRecords.map(releaseUpdate => {
return {
Title: releaseUpdate.Title,
StepStage: releaseUpdate.StepStage,
Status: releaseUpdate.Status,
Category: releaseUpdate.Category,
DueDate: moment(releaseUpdate.DueDate).format('ll')
};
});
const summaryText = `${this.releaseUpdatesRecords.length} Release Updates to check have been found in ${conn.instanceUrl}`;
uxLog("action", this, c.cyan(summaryText));
uxLogTable(this, releaseUpdatesLight);
}
else {
uxLog("success", this, c.green("No release updates has been found"));
}
// Return an object to be displayed with --json
return {
status: this.releaseUpdatesRecords.length > 0 ? 1 : 0,
suspectRecords: this.releaseUpdatesRecords,
csvLogFile: this.outputFile,
};
}
}
//# sourceMappingURL=releaseupdates.js.map