Skip to content

Commit

Permalink
fix: add doctor diagnostic test from plugin-source
Browse files Browse the repository at this point in the history
  • Loading branch information
shetzel committed Feb 8, 2024
1 parent ca8d8fd commit 13e9c52
Show file tree
Hide file tree
Showing 4 changed files with 418 additions and 7 deletions.
19 changes: 19 additions & 0 deletions messages/diagnostics.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# apiVersionMismatch

The sourceApiVersion in sfdx-project.json doesn't match the apiVersion. The commands that deploy and retrieve source use the sourceApiVersion in this case. The version mismatch isn't a problem, as long as it's the behavior you actually want.

# apiVersionUnset

Neither sourceApiVersion nor apiVersion are defined. The commands that deploy and retrieve source use the max apiVersion of the target org in this case. The issue isn't a problem, as long as it's the behavior you actually want.

# maxApiVersionMismatch

The max apiVersion of the default DevHub org doesn't match the max apiVersion of the default target org. This mismatch means that the default target orgs are running different API versions. Be sure you explicitly set the apiVersion when you deploy or retrieve source, or you will likely run into problems.

# sourceApiVersionMaxMismatch

The sourceApiVersion in sfdx-project.json doesn't match the max apiVersion of the default target org. As a result, you're not using the latest features available in API version %s. The version mismatch isn't a problem, as long as it's the behavior you actually want.

# apiVersionMaxMismatch

The apiVersion doesn't match the max apiVersion of the default target org. As a result, you're not using the latest features available in API version %s. The version mismatch isn't a problem, as long as it's the behavior you actually want.
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"@salesforce/apex-node": "^3.0.2",
"@salesforce/core": "^6.4.4",
"@salesforce/kit": "^3.0.15",
"@salesforce/plugin-info": "^3.0.21",
"@salesforce/sf-plugins-core": "^7.1.4",
"@salesforce/source-deploy-retrieve": "^10.2.13",
"@salesforce/source-tracking": "^5.1.9",
Expand Down Expand Up @@ -91,6 +92,9 @@
}
}
},
"hooks": {
"sf-doctor-@salesforce/plugin-deploy-retrieve": "./lib/hooks/diagnostics"
},
"flexibleTaxonomy": true
},
"repository": "salesforcecli/plugin-deploy-retrieve",
Expand Down
143 changes: 143 additions & 0 deletions src/hooks/diagnostics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/*
* Copyright (c) 2022, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import { ConfigAggregator, Lifecycle, Logger, Messages, SfProject, OrgConfigProperties, Org } from '@salesforce/core';
import { SfDoctor } from '@salesforce/plugin-info';

type HookFunction = (options: { doctor: SfDoctor }) => Promise<[void]>;

let logger: Logger;
const getLogger = (): Logger => {
if (!logger) {
logger = Logger.childFromRoot('plugin-deploy-retrieve-diagnostics');
}
return logger;
};

const pluginName = '@salesforce/plugin-deploy-retrieve';
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages(pluginName, 'diagnostics');

export const hook: HookFunction = async (options) => {
getLogger().debug(`Running SfDoctor diagnostics for ${pluginName}`);
return Promise.all([apiVersionTest(options.doctor)]);
};

// ============================
// *** DIAGNOSTIC TESTS ***
// ============================

// Gathers and compares the following API versions:
// 1. apiVersion (if set) from the sfdx config, including environment variable
// 2. sourceApiVersion (if set) from sfdx-project.json
// 3. max apiVersion of the default target dev hub org (if set)
// 4. max apiVersion of the default target org (if set)
//
// Warns if:
// 1. apiVersion and sourceApiVersion are set but not equal
// 2. apiVersion and sourceApiVersion are both not set
// 3. default devhub target org and default target org have different max apiVersions
// 4. sourceApiVersion is set and does not match max apiVersion of default target org
// 5. apiVersion is set and does not match max apiVersion of default target org
const apiVersionTest = async (doctor: SfDoctor): Promise<void> => {
getLogger().debug('Running API Version tests');

// check org-api-version from ConfigAggregator
const aggregator = await ConfigAggregator.create();
const apiVersion = aggregator.getPropertyValue<string>(OrgConfigProperties.ORG_API_VERSION);

const sourceApiVersion = await getSourceApiVersion();

const targetDevHub = aggregator.getPropertyValue<string>(OrgConfigProperties.TARGET_DEV_HUB);
const targetOrg = aggregator.getPropertyValue<string>(OrgConfigProperties.TARGET_ORG);
const targetDevHubApiVersion = targetDevHub && (await getMaxApiVersion(aggregator, targetDevHub));
const targetOrgApiVersion = targetOrg && (await getMaxApiVersion(aggregator, targetOrg));

doctor.addPluginData(pluginName, {
apiVersion,
sourceApiVersion,
targetDevHubApiVersion,
targetOrgApiVersion,
});

const testName1 = `[${pluginName}] sourceApiVersion matches apiVersion`;
let status1 = 'pass';
if (diff(sourceApiVersion, apiVersion)) {
status1 = 'warn';
doctor.addSuggestion(messages.getMessage('apiVersionMismatch'));
}
if (sourceApiVersion === undefined && apiVersion === undefined) {
status1 = 'warn';
doctor.addSuggestion(messages.getMessage('apiVersionUnset'));
}
void Lifecycle.getInstance().emit('Doctor:diagnostic', { testName: testName1, status: status1 });

if (targetDevHubApiVersion && targetOrgApiVersion) {
const testName2 = `[${pluginName}] default target DevHub max apiVersion matches default target org max apiVersion`;
let status2 = 'pass';
if (diff(targetDevHubApiVersion, targetOrgApiVersion)) {
status2 = 'warn';
doctor.addSuggestion(messages.getMessage('maxApiVersionMismatch'));
}
void Lifecycle.getInstance().emit('Doctor:diagnostic', { testName: testName2, status: status2 });
}

// Only run this test if both sourceApiVersion and the default target org max version are set.
if (sourceApiVersion?.length && targetOrgApiVersion?.length) {
const testName3 = `[${pluginName}] sourceApiVersion matches default target org max apiVersion`;
let status3 = 'pass';
if (diff(sourceApiVersion, targetOrgApiVersion)) {
status3 = 'warn';
doctor.addSuggestion(messages.getMessage('sourceApiVersionMaxMismatch', [targetOrgApiVersion]));
}
void Lifecycle.getInstance().emit('Doctor:diagnostic', { testName: testName3, status: status3 });
}

// Only run this test if both apiVersion and the default target org max version are set.
if (apiVersion?.length && targetOrgApiVersion?.length) {
const testName4 = `[${pluginName}] apiVersion matches default target org max apiVersion`;
let status4 = 'pass';
if (diff(apiVersion, targetOrgApiVersion)) {
status4 = 'warn';
doctor.addSuggestion(messages.getMessage('apiVersionMaxMismatch', [targetOrgApiVersion]));
}
void Lifecycle.getInstance().emit('Doctor:diagnostic', { testName: testName4, status: status4 });
}
};

// check sfdx-project.json for sourceApiVersion
const getSourceApiVersion = async (): Promise<string | undefined> => {
try {
const project = SfProject.getInstance();
const projectJson = await project.resolveProjectConfig();
return projectJson.sourceApiVersion as string | undefined;
} catch (error) {
const errMsg = (error as Error).message;
getLogger().debug(`Cannot determine sourceApiVersion due to: ${errMsg}`);
}
};

// check max API version for default orgs
const getMaxApiVersion = async (aggregator: ConfigAggregator, aliasOrUsername: string): Promise<string | undefined> => {
try {
const org = await Org.create({ aliasOrUsername, aggregator });
return await org.retrieveMaxApiVersion();
} catch (error) {
const errMsg = (error as Error).message;
getLogger().debug(`Cannot determine the max ApiVersion for org: [${aliasOrUsername}] due to: ${errMsg}`);
}
};

// Compare 2 API versions that have values and return if they are different.
// E.g.,
// Comparing undefined with 56.0 would return false.
// Comparing undefined with undefined would return false.
// Comparing 55.0 with 55.0 would return false.
// Comparing 55.0 with 56.0 would return true.
const diff = (version1: string | undefined, version2: string | undefined): boolean => {
getLogger().debug(`Comparing API versions: [${version1},${version2}]`);
return !!version1?.length && !!version2?.length && version1 !== version2;
};
Loading

0 comments on commit 13e9c52

Please sign in to comment.