Skip to content

feat: add api version validation #218

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .husky/check-versions.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const fs = require('fs');

// Read package.json
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));

// Extract versions
const devServerDependencyVersion = packageJson.dependencies['@lwc/lwc-dev-server'];
const devServerTargetVersion = packageJson.apiVersionMetadata?.target?.matchingDevServerVersion;

if (!devServerDependencyVersion || !devServerTargetVersion) {
console.error('Error: missing @lwc/lwc-dev-server or matchingDevServerVersion');
process.exit(1); // Fail the check
}

// Compare versions
if (devServerDependencyVersion === devServerTargetVersion) {
process.exit(0); // Pass the check
} else {
console.error(
`Error: Versions do not match. @lwc/lwc-dev-server: ${devServerDependencyVersion}, matchingDevServerVersion: ${devServerTargetVersion}`
);
process.exit(1); // Fail the check
}
3 changes: 3 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

# Run the custom version check script
node .husky/check-versions.cjs

yarn lint && yarn pretty-quick --staged
8 changes: 8 additions & 0 deletions messages/shared.utils.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,11 @@ You must provide valid SSL certificate data
# error.localdev.not.enabled

Local Dev is not enabled for your org. See https://developer.salesforce.com/docs/platform/lwc/guide/get-started-test-components.html for more information on enabling and using Local Dev.

# error.org.api-mismatch.message

Your org is on API version %s but this CLI plugin supports API version %s.

# error.org.api-mismatch.remediation

Please reinstall or update the plugin using %s tag.
17 changes: 17 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,23 @@
"output": []
}
},
"apiVersionMetadata": {
"comment": "Refer to apiVersionMetadata in orgUtils.ts for details",
"target": {
"versionNumber": "62.0",
"matchingDevServerVersion": "^9.5.1"
},
"versionToTagMappings": [
{
"versionNumber": "62.0",
"tagName": "latest"
},
{
"versionNumber": "63.0",
"tagName": "next"
}
]
},
"exports": "./lib/index.js",
"type": "module",
"volta": {
Expand Down
7 changes: 5 additions & 2 deletions src/commands/lightning/dev/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ export default class LightningDevApp extends SfCommand<void> {

const targetOrg = flags['target-org'];
const appName = flags['name'];
const platform = flags['device-type'] ?? (await PromptUtils.promptUserToSelectPlatform());
const deviceId = flags['device-id'];

let sfdxProjectRootPath = '';
Expand All @@ -78,7 +77,6 @@ export default class LightningDevApp extends SfCommand<void> {
return Promise.reject(new Error(messages.getMessage('error.no-project', [(error as Error)?.message ?? ''])));
}

logger.debug('Configuring local web server identity');
const connection = targetOrg.getConnection(undefined);
const username = connection.getUsername();
if (!username) {
Expand All @@ -90,6 +88,11 @@ export default class LightningDevApp extends SfCommand<void> {
return Promise.reject(new Error(sharedMessages.getMessage('error.localdev.not.enabled')));
}

OrgUtils.ensureMatchingAPIVersion(connection);

const platform = flags['device-type'] ?? (await PromptUtils.promptUserToSelectPlatform());

logger.debug('Configuring local web server identity');
const appServerIdentity = await PreviewUtils.getOrCreateAppServerIdentity(connection);
const ldpServerToken = appServerIdentity.identityToken;
const ldpServerId = appServerIdentity.usernameToServerEntityIdMap[username];
Expand Down
6 changes: 5 additions & 1 deletion src/commands/lightning/dev/site.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,15 @@ export default class LightningDevSite extends SfCommand<void> {
const org = flags['target-org'];
let siteName = flags.name;

const localDevEnabled = await OrgUtils.isLocalDevEnabled(org.getConnection(undefined));
const connection = org.getConnection(undefined);

const localDevEnabled = await OrgUtils.isLocalDevEnabled(connection);
if (!localDevEnabled) {
throw new Error(sharedMessages.getMessage('error.localdev.not.enabled'));
}

OrgUtils.ensureMatchingAPIVersion(connection);

// If user doesn't specify a site, prompt the user for one
if (!siteName) {
const allSites = await ExperienceSite.getAllExpSites(org);
Expand Down
81 changes: 79 additions & 2 deletions src/shared/orgUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import { Connection } from '@salesforce/core';
import path from 'node:path';
import url from 'node:url';
import { Connection, Messages } from '@salesforce/core';
import { CommonUtils, Version } from '@salesforce/lwc-dev-mobile-core';

Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('@salesforce/plugin-lightning-dev', 'shared.utils');

type LightningPreviewMetadataResponse = {
enableLightningPreviewPref?: string;
Expand All @@ -18,6 +24,37 @@ export type AppDefinition = {
DurableId: string;
};

/**
* As we go through different phases of release cycles, in order to ensure that the API version supported by
* the local dev server matches with Org API versions we rely on defining a metadata section in package.json
*
* The apiVersionMetadata entry in this json file defines "target" and "versionToTagMappings" sections.
*
* "target.versionNumber" defines the API version that the local dev server supports. As we pull in new versions
* of the lwc-dev-server we need to manually update "target.versionNumber" in package.json In order to ensure
* that we don't forget this step, we also have "target.matchingDevServerVersion" which is used husky during
* the pre-commit check to ensure that we have not forgotten to update apiVersionMetadata. Whenever we pull in
* a new version of lwc-dev-server in our dependencies, we must also update "target.matchingDevServerVersion"
* to the same version otherwise the pre-commit will fail.
*
* The "versionToTagMappings" section will provide a mapping between supported API version by the dev server,
* and the tagged version of our plugin. We use "versionToTagMappings" to convey to the user which version of
* our plugin should they be using to match with the API version of their org (i.e which version of our plugin
* contains the lwc-dev-server dependency that can support the API version of the user's org.)
*/
type apiVersionMetadata = {
target: {
versionNumber: string;
matchingDevServerVersion: string;
};
versionToTagMappings: [
{
versionNumber: string;
tagName: string;
}
];
};

export class OrgUtils {
/**
* Given an app name, it queries the AppDefinition table in the org to find
Expand Down Expand Up @@ -61,7 +98,7 @@ export class OrgUtils {
const results: AppDefinition[] = [];

const appMenuItemsQuery =
'SELECT Label,Description,Name FROM AppMenuItem WHERE IsAccessible=true AND IsVisible=TRUE';
'SELECT Label,Description,Name FROM AppMenuItem WHERE IsAccessible=true AND IsVisible=true';
const appMenuItems = await connection.query<{ Label: string; Description: string; Name: string }>(
appMenuItemsQuery
);
Expand Down Expand Up @@ -112,4 +149,44 @@ export class OrgUtils {
}
throw new Error('Could not save the app server identity token to the org.');
}

/**
* Given a connection to an Org, it ensures that org API version matches what the local dev server expects.
* To do this, it compares the org API version with the meta data stored in package.json under apiVersionMetadata.
* If the API versions do not match then this method will throw an exception.
*
* @param connection the connection to the org
*/
public static ensureMatchingAPIVersion(connection: Connection): void {
const dirname = path.dirname(url.fileURLToPath(import.meta.url));
const packageJsonFilePath = path.resolve(dirname, '../../package.json');

const pkg = CommonUtils.loadJsonFromFile(packageJsonFilePath) as {
name: string;
apiVersionMetadata: apiVersionMetadata;
};
const targetVersion = pkg.apiVersionMetadata.target.versionNumber;
const orgVersion = connection.version;

if (Version.same(orgVersion, targetVersion) === false) {
let errorMessage = messages.getMessage('error.org.api-mismatch.message', [orgVersion, targetVersion]);
const tagName = pkg.apiVersionMetadata.versionToTagMappings.find(
(info) => info.versionNumber === targetVersion
)?.tagName;
if (tagName) {
const remediation = messages.getMessage('error.org.api-mismatch.remediation', [`${pkg.name}@${tagName}`]);
errorMessage = `${errorMessage} ${remediation}`;
}

// Examples of error messages are as below (where the tag name comes from apiVersionMetadata in package.json):
//
// Your org is on API version 61 but this CLI plugin supports API version 62. Please reinstall or update the plugin using @salesforce/plugin-lightning-dev@latest tag.
//
// Your org is on API version 62 but this CLI plugin supports API version 63. Please reinstall or update the plugin using @salesforce/plugin-lightning-dev@next tag.
//
// Your org is on API version 63 but this CLI plugin supports API version 62. Please reinstall or update the plugin using @salesforce/plugin-lightning-dev@latest tag.

throw new Error(errorMessage);
}
}
}
1 change: 1 addition & 0 deletions test/commands/lightning/dev/app.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ describe('lightning dev app', () => {
$$.SANDBOX.stub(Connection.prototype, 'getUsername').returns(testUsername);
$$.SANDBOX.stub(PreviewUtils, 'getOrCreateAppServerIdentity').resolves(testIdentityData);
$$.SANDBOX.stub(OrgUtils, 'isLocalDevEnabled').resolves(true);
$$.SANDBOX.stub(OrgUtils, 'ensureMatchingAPIVersion').returns();

MockedLightningPreviewApp = await esmock<typeof LightningDevApp>('../../../../src/commands/lightning/dev/app.js', {
'../../../../src/lwc-dev-server/index.js': {
Expand Down