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

198 lines (191 loc) • 10.4 kB
/* jscpd:ignore-start */ import { SfCommand, Flags, requiredOrgFlagWithDeprecations } from '@salesforce/sf-plugins-core'; import { Messages } from '@salesforce/core'; import c from 'chalk'; import * as child from 'child_process'; import fs from 'fs-extra'; import * as path from 'path'; import { MetadataUtils } from '../../../../../common/metadata-utils/index.js'; import { ensureGitRepository, execCommand, isMonitoringJob, uxLog } from '../../../../../common/utils/index.js'; import LegacyApi from '../../diagnose/legacyapi.js'; import OrgTestApex from '../../test/apex.js'; import * as util from 'util'; import { PACKAGE_ROOT_DIR } from '../../../../../settings.js'; import { CONSTANTS } from '../../../../../config/index.js'; const exec = util.promisify(child.exec); Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); const messages = Messages.loadMessages('sfdx-hardis', 'org'); export default class DxSources extends SfCommand { static title = 'Retrieve sfdx sources from org'; static description = ` ## Command Behavior **Retrieves Salesforce metadata from an org into a local directory, primarily for backup and monitoring purposes.** This command is designed to pull metadata from any Salesforce org, providing a snapshot of its configuration. It's particularly useful in monitoring contexts where you need to track changes in an org's metadata over time. Key functionalities: - **Metadata Retrieval:** Connects to a target Salesforce org and retrieves metadata based on a specified \`package.xml\`. - **Managed Package Filtering:** By default, it filters out metadata from managed packages to reduce the volume of retrieved data. This can be overridden with the \`--includemanaged\` flag. - **Monitoring Integration:** Designed to be used within a monitoring CI/CD job, it performs additional post-retrieval actions like running Apex tests and checking for legacy API usage. <details markdown="1"> <summary>Technical explanations</summary> The command's technical implementation involves: - **Git Repository Check:** Ensures the current directory is a Git repository and initializes it if necessary. - **\`MetadataUtils.retrieveMetadatas\`:** This utility is the core of the retrieval process. It connects to the Salesforce org, retrieves metadata based on the provided \`package.xml\` and filtering options (e.g., \`filterManagedItems\`), and places the retrieved files in a specified folder. - **File System Operations:** Uses \`fs-extra\` to manage directories and copy retrieved files to the target folder. - **Post-Retrieval Actions (for Monitoring Jobs):** If the command detects it's running within a monitoring CI/CD job (\`isMonitoringJob()\`): - It updates the \`.gitlab-ci.yml\` file if \`AUTO_UPDATE_GITLAB_CI_YML\` is set. - It converts the retrieved metadata into SFDX format using \`sf project convert mdapi\`. - It executes \`sf hardis:org:test:apex\` to run Apex tests. - It executes \`sf hardis:org:diagnose:legacyapi\` to check for legacy API usage. - It logs warnings if post-actions fail or if the monitoring version is deprecated. - **Error Handling:** Includes robust error handling for retrieval failures and post-action execution. </details> `; static examples = [ '$ sf hardis:org:retrieve:sources:metadata', '$ SFDX_RETRIEVE_WAIT_MINUTES=200 sf hardis:org:retrieve:sources:metadata', ]; static flags = { folder: Flags.string({ char: 'f', default: '.', description: messages.getMessage('folder'), }), packagexml: Flags.string({ char: 'p', description: messages.getMessage('packageXml'), }), includemanaged: Flags.boolean({ default: false, description: 'Include items from managed packages', }), instanceurl: Flags.string({ char: 'r', description: messages.getMessage('instanceUrl'), }), 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, }; // Set this to true if your command requires a project workspace; 'requiresProject' is false by default static requiresProject = false; // Trigger notification(s) to MsTeams channel static triggerNotification = true; debugMode = false; /* jscpd:ignore-end */ async run() { const { flags } = await this.parse(DxSources); const folder = path.resolve(flags.folder || '.'); const packageXml = path.resolve(flags.packagexml || 'package.xml'); const includeManaged = flags.includemanaged || false; this.debugMode = flags.debug || false; // Check required pre-requisites await ensureGitRepository({ init: true }); const isMonitoring = await isMonitoringJob(); // Retrieve metadatas let message = ''; try { const filterManagedItems = includeManaged === false; await MetadataUtils.retrieveMetadatas(packageXml, folder, false, [], { filterManagedItems: filterManagedItems }, this, flags['target-org'].getUsername(), this.debugMode); // Copy to destination await fs.copy(path.join(folder, 'unpackaged'), path.resolve(folder)); // Remove temporary files await fs.rm(path.join(folder, 'unpackaged'), { recursive: true }); message = `[sfdx-hardis] Successfully retrieved metadatas in ${folder}`; uxLog("other", this, message); } catch (e) { if (!isMonitoring) { throw e; } message = '[sfdx-hardis] Error retrieving metadatas'; } // Post actions for monitoring CI job if (isMonitoring) { try { return await this.processPostActions(message, flags); // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { uxLog("warning", this, c.yellow('Post actions have failed !')); } uxLog("warning", this, c.yellow(c.bold('This version of sfdx-hardis monitoring is deprecated and will not be maintained anymore'))); uxLog("warning", this, c.yellow(c.bold('Switch to new sfdx-hardis monitoring that is enhanced !'))); uxLog("warning", this, c.yellow(c.bold(`Info: ${CONSTANTS.DOC_URL_ROOT}/salesforce-monitoring-home/`))); } return { orgId: flags['target-org'].getOrgId(), outputString: message }; } async processPostActions(message, flags) { uxLog("action", this, c.cyan('Monitoring repo detected')); // Update default .gitlab-ci.yml within the monitoring repo const localGitlabCiFile = path.join(process.cwd(), '.gitlab-ci.yml'); if (fs.existsSync(localGitlabCiFile) && process.env?.AUTO_UPDATE_GITLAB_CI_YML) { const localGitlabCiContent = await fs.readFile(localGitlabCiFile, 'utf8'); const latestGitlabCiFile = path.join(PACKAGE_ROOT_DIR, 'defaults/monitoring/.gitlab-ci.yml'); const latestGitlabCiContent = await fs.readFile(latestGitlabCiFile, 'utf8'); if (localGitlabCiContent !== latestGitlabCiContent) { await fs.writeFile(localGitlabCiFile, latestGitlabCiContent); uxLog("action", this, c.cyan('Updated .gitlab-ci.yml file')); } } // Also trace updates with sfdx sources, for better readability uxLog("action", this, c.cyan('Convert into sfdx format...')); if (fs.existsSync('metadatas')) { // Create sfdx project if not existing yet if (!fs.existsSync('sfdx-project')) { const createCommand = 'sf project generate' + ` --name "sfdx-project"`; uxLog("action", this, c.cyan('Creating sfdx-project...')); await execCommand(createCommand, this, { output: true, fail: true, debug: this.debugMode, }); } // Convert metadatas into sfdx sources const mdapiConvertCommand = `sf project convert mdapi --root-dir "../metadatas"`; uxLog("action", this, c.cyan('Converting metadata to source formation into sfdx-project...')); uxLog("other", this, `[command] ${c.bold(c.grey(mdapiConvertCommand))}`); const prevCwd = process.cwd(); process.chdir(path.join(process.cwd(), './sfdx-project')); try { const convertRes = await exec(mdapiConvertCommand, { maxBuffer: 10000 * 10000, }); uxLog("other", this, convertRes.stdout + convertRes.stderr); } catch (e) { uxLog("warning", this, c.yellow('Error while converting metadatas to sources:\n' + e.message)); } process.chdir(prevCwd); } let orgTestRes = null; let legacyApiRes = null; const prevExitCode = process.exitCode || 0; try { // Run test classes uxLog("action", this, c.cyan('Running Apex tests...')); orgTestRes = await new OrgTestApex([], this.config)._run(); // Check usage of Legacy API versions uxLog("action", this, c.cyan('Running Legacy API Use checks...')); legacyApiRes = await new LegacyApi([], this.config)._run(); // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { uxLog("warning", this, c.yellow('Issues found when running Apex tests or Legacy API, please check messages')); } process.exitCode = prevExitCode; // Delete report files //const reportFiles = await glob("**/hardis-report/**", { cwd: process.cwd() }); //reportFiles.map(async (file) => await fs.remove(file)); return { orgId: flags['target-org'].getOrgId(), outputString: message, orgTestRes, legacyApiRes }; } } //# sourceMappingURL=metadata.js.map