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

236 lines (229 loc) â€ĸ 15.1 kB
/* jscpd:ignore-start */ import { SfCommand, Flags, requiredOrgFlagWithDeprecations } from '@salesforce/sf-plugins-core'; import { Messages, SfError } from '@salesforce/core'; import c from 'chalk'; import fs from 'fs-extra'; import * as path from 'path'; import open from 'open'; import { ensureGitBranch, ensureGitRepository, execCommand, generateSSLCertificate, getCurrentGitBranch, getGitRepoName, git, gitAddCommitPush, uxLog, } from '../../../../common/utils/index.js'; import { prompts } from '../../../../common/utils/prompts.js'; import { CONSTANTS, setInConfigFile } from '../../../../config/index.js'; import { PACKAGE_ROOT_DIR } from '../../../../settings.js'; import { promptOrg } from '../../../../common/utils/orgUtils.js'; import { WebSocketClient } from '../../../../common/websocketClient.js'; Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); const messages = Messages.loadMessages('sfdx-hardis', 'org'); export default class OrgConfigureMonitoring extends SfCommand { static title = 'Configure org monitoring'; static description = ` ## Command Behavior **Configures the monitoring of a Salesforce org within a dedicated Git repository.** This command streamlines the setup of continuous monitoring for a Salesforce organization, ensuring that changes and health metrics are tracked and reported. It is designed to be run within a Git repository specifically dedicated to monitoring configurations. Key functionalities include: - **Git Repository Validation:** Ensures the current Git repository's name contains "monitoring" to enforce best practices for separating monitoring configurations from deployment sources. - **Prerequisite Check:** Guides the user to confirm that necessary monitoring prerequisites (CI/CD variables, permissions) are configured on their Git server. - **Org Selection:** Prompts the user to select or connect to the Salesforce org they wish to monitor. - **Monitoring Branch Creation:** Creates or checks out a dedicated Git branch (e.g., \`monitoring_yourinstanceurl\`) for the monitoring configuration. - **SFDX Project Setup:** Initializes an SFDX project structure within the repository if it doesn't already exist, and copies default monitoring files. - **Configuration File Update:** Updates the local \`.sfdx-hardis.yml\` file with the target org's username and instance URL. - **SSL Certificate Generation:** Generates an SSL certificate for secure authentication to the monitored org. - **Automated Commit and Push:** Offers to automatically commit and push the generated configuration files to the remote Git repository. - **Scheduling Guidance:** Provides instructions and links for scheduling the monitoring job on the Git server. <details markdown="1"> <summary>Technical explanations</summary> The command's technical implementation involves a series of Git operations, file system manipulations, and Salesforce CLI interactions: - **Git Operations:** Utilizes \`ensureGitRepository\`, \`getGitRepoName\`, \`execCommand\` (for \`git add\`, \`git stash\`), \`ensureGitBranch\`, and \`gitAddCommitPush\` to manage the Git repository, branches, and commits. - **Interactive Prompts:** Employs the \`prompts\` library to interact with the user for confirmations and selections. - **File System Management:** Uses \`fs-extra\` for copying default monitoring files (\`defaults/monitoring\`) and managing the SFDX project structure. - **Salesforce CLI Integration:** Calls \`sf project generate\` to create a new SFDX project and uses \`promptOrg\` for Salesforce org authentication and selection. - **Configuration Management:** Updates the \`.sfdx-hardis.yml\` file using \`setInConfigFile\` to store org-specific monitoring configurations. - **SSL Certificate Generation:** Leverages \`generateSSLCertificate\` to create the necessary SSL certificates for JWT-based authentication to the Salesforce org. - **External Tool Integration:** Requires \`openssl\` to be installed on the system for SSL certificate generation. - **WebSocket Communication:** Uses \`WebSocketClient.sendRunSfdxHardisCommandMessage\` to restart the command in VS Code if the default org changes, and \`WebSocketClient.sendRefreshStatusMessage\` to update the status. </details> `; static examples = ['$ sf hardis:org:configure:monitoring']; static flags = { orginstanceurl: Flags.string({ description: 'Org instance url (technical param, do not use manually)', }), 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, }; // Comment this out if your command does not require an org username static requiresProject = false; /* jscpd:ignore-end */ static requiresDependencies = ['openssl']; async run() { const { flags } = await this.parse(OrgConfigureMonitoring); // Make sure that we are located in a git repository await ensureGitRepository(); // Check git repo name is valid (contains monitoring) const repoName = (await getGitRepoName()) || ''; if (!repoName.includes('monitoring')) { const confirmMix = await prompts({ type: 'select', name: 'value', choices: [ { title: "Yes, I'm sure because I know what I'm doing, like Roman 😊", value: 'yes' }, { title: 'Mmmmm no, let me create another repo with the word "monitoring" in its name !', value: 'no' }, ], message: c.cyanBright("Are you sure you want to mix monitoring and deployment sources ?"), description: 'It is recommended to separate monitoring configuration from deployment sources in different repositories', placeholder: 'Select an option', }); if (confirmMix.value === 'no') { throw new SfError('Your git repository name must contain the expression "monitoring"'); } } const preRequisitesUrl = `${CONSTANTS.DOC_URL_ROOT}/salesforce-monitoring-config-home/#instructions`; uxLog("warning", this, c.yellow('Monitoring pre-requisites documentation: ' + c.bold(preRequisitesUrl))); WebSocketClient.sendReportFileMessage(preRequisitesUrl, 'Monitoring pre-requisites', "docUrl"); // Confirm pre-requisites const confirmPreRequisites = await prompts({ type: 'select', name: 'value', choices: [ { title: '😎 Yes', value: 'yes' }, { title: 'â„šī¸ No, bring me to the documentation!', value: 'no' }, ], message: c.cyanBright('Did you configure the sfdx-hardis monitoring pre-requisites on your Git server ?'), description: 'Confirm that you have set up the required CI/CD variables and permissions for monitoring', placeholder: 'Select an option', }); if (confirmPreRequisites.value === 'no') { const msg = 'Please follow the instructions to configure the sfdx-hardis monitoring pre-requisites on your Git server\n' + preRequisitesUrl; uxLog("warning", this, c.yellow(msg)); await open(preRequisitesUrl, { wait: true }); return { outputString: msg }; } // Get current default org const currentOrgId = flags['target-org']?.getOrgId() || ''; if (flags.orginstanceurl && flags['target-org']?.getConnection()?.instanceUrl === flags.orginstanceurl) { uxLog("action", this, c.cyan(`Default org ${flags['target-org'].getConnection()?.instanceUrl} is selected, let's configure its monitoring !`)); } else { // Select the org that must be monitored const org = await promptOrg(this, { devHub: false, setDefault: true, scratch: false, promptMessage: 'Please select or connect to the org that you want to monitor', defaultOrgUsername: flags['target-org']?.getUsername(), }); // Restart command so the org is selected as default org (will help to select profiles) if (currentOrgId !== org.orgId) { const infoMsg = 'Default org changed. Please restart the same command if VS Code does not do that automatically for you 😊'; uxLog("warning", this, c.yellow(infoMsg)); const currentCommand = 'sf ' + this.id + ' ' + this.argv.join(' ') + ' --orginstanceurl ' + org.instanceUrl; WebSocketClient.sendRunSfdxHardisCommandMessage(currentCommand); return { outputString: infoMsg }; } } // Build monitoring branch name const branchName = 'monitoring_' + flags['target-org'] ?.getConnection() .instanceUrl.replace('https://', '') .replace('.my.salesforce.com', '') .replace(/\./gm, '_') .replace(/--/gm, '__') .replace(/-/gm, '_'); uxLog("action", this, c.cyan(`Handling monitoring git branch ${c.bold(branchName)}...`)); // Checkout branch, or create it if not existing (stash before if necessary) await execCommand('git add --all', this, { output: true, fail: false }); await execCommand('git stash', this, { output: true, fail: false }); await ensureGitBranch(branchName, { parent: 'main', logAsAction: true }); // Create sfdx project if not existing yet if (!fs.existsSync('sfdx-project.json')) { const createCommand = 'sf project generate' + ` --name "sfdx-hardis-monitoring"`; uxLog("action", this, c.cyan('Creating sfdx-project...')); await execCommand(createCommand, this, { output: true, fail: true, }); uxLog("action", this, c.cyan('Moving sfdx-project to root...')); await fs.copy('sfdx-hardis-monitoring', process.cwd(), { overwrite: true }); await fs.remove('sfdx-hardis-monitoring'); // Copying monitoring folder structure uxLog("other", this, 'Copying default monitoring files...'); if (fs.existsSync('README.md') && fs.readFileSync('README.md', 'utf8').toString().split('\n').length < 5) { // Remove default README if necessary await fs.remove('README.md'); } await fs.copy(path.join(PACKAGE_ROOT_DIR, 'defaults/monitoring', '.'), process.cwd(), { overwrite: true }); } // Update config file await setInConfigFile([], { targetUsername: flags['target-org'].getUsername(), instanceUrl: flags['target-org'].getConnection().instanceUrl, }, './.sfdx-hardis.yml'); // Check if current branch is existing on remote origin, and if not suggest to push (with confirmation) const remoteBranches = await git().listRemote(['--heads', 'origin']); const branchExistsOnRemote = remoteBranches.includes(`refs/heads/${branchName}`); if (!branchExistsOnRemote) { const confirmPushToRemote = await prompts({ type: 'confirm', initial: true, message: c.cyanBright(`Branch ${branchName} does not exist on server. Do you want to push it now? (it is harmless but required to configure Azure Pipelines for example)`), description: 'This will create the branch on remote origin; if you don\'t understand this, just say yes 😊', }); if (confirmPushToRemote.value === true) { uxLog("action", this, c.cyan(`Pushing branch ${c.bold(branchName)} to git remote server...`)); await gitAddCommitPush({ message: '[sfdx-hardis] Update monitoring configuration', }); } } // Generate SSL certificate (requires openssl to be installed on computer) await generateSSLCertificate(branchName, './.ssh', this, flags['target-org'].getConnection(), {}); // Confirm & push on server const confirmPush = await prompts({ type: 'confirm', name: 'value', initial: true, message: c.cyanBright('Do you want sfdx-hardis to save your configuration on server ?'), description: 'Automatically commit and push the monitoring configuration files to your git repository (recommended)', }); if (confirmPush.value === true) { await gitAddCommitPush({ message: '[sfdx-hardis] Update monitoring configuration', }); uxLog("success", this, c.green('Your configuration for org monitoring is now ready 😊')); } else { uxLog("warning", this, c.yellow('Please manually git add, commit and push to the remote repository 😊')); } const branch = await getCurrentGitBranch(); uxLog("warning", this, c.yellow(`Now you must schedule monitoring to run the job automatically every night on branch ${c.bold(branch)}😊`)); const scheduleMonitoringUrl = `${CONSTANTS.DOC_URL_ROOT}/salesforce-monitoring-config-home/#instructions`; const msg = 'Please follow the instructions to schedule sfdx-hardis monitoring on your Git server: ' + c.bold(scheduleMonitoringUrl); uxLog("warning", this, c.yellow(msg)); WebSocketClient.sendReportFileMessage(scheduleMonitoringUrl, 'Schedule sfdx-hardis monitoring', "actionUrl"); uxLog("warning", this, c.yellow('You can also configure Slack/Teams notifications and Grafana integration to visualize monitoring results more easily.')); const slackIntegrationUrl = `${CONSTANTS.DOC_URL_ROOT}/salesforce-ci-cd-setup-integration-slack/`; WebSocketClient.sendReportFileMessage(slackIntegrationUrl, 'Slack integration', "docUrl"); const teamsIntegrationUrl = `${CONSTANTS.DOC_URL_ROOT}/salesforce-ci-cd-setup-integration-ms-teams/`; WebSocketClient.sendReportFileMessage(teamsIntegrationUrl, 'Teams integration', "docUrl"); const grafanaIntegrationUrl = `${CONSTANTS.DOC_URL_ROOT}/salesforce-ci-cd-setup-integration-api/`; WebSocketClient.sendReportFileMessage(grafanaIntegrationUrl, 'Grafana integration', "docUrl"); uxLog("log", this, 'Slack integration doc: ' + slackIntegrationUrl); uxLog("log", this, 'Teams integration doc: ' + teamsIntegrationUrl); uxLog("log", this, 'Grafana integration doc: ' + grafanaIntegrationUrl); // Return an object to be displayed with --json return { outputString: 'Configured branch for authentication' }; } } //# sourceMappingURL=monitoring.js.map