Skip to content

Commit 7b6cde7

Browse files
committed
fix: simplify versionresolver
1 parent 1fc902c commit 7b6cde7

9 files changed

Lines changed: 131 additions & 324 deletions

File tree

package.json

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@
99
"@inquirer/select": "^2.4.7",
1010
"@lwc/lwc-dev-server": "~13.3.8",
1111
"@lwc/lwc-dev-server-latest": "npm:@lwc/lwc-dev-server@~13.2.x",
12-
"@lwc/lwc-dev-server-prerelease": "npm:@lwc/lwc-dev-server@~13.3.x",
1312
"@lwc/lwc-dev-server-next": "npm:@lwc/lwc-dev-server@~13.3.x",
13+
"@lwc/lwc-dev-server-prerelease": "npm:@lwc/lwc-dev-server@~13.3.x",
1414
"@lwc/sfdc-lwc-compiler": "~13.3.8",
1515
"@lwc/sfdc-lwc-compiler-latest": "npm:@lwc/sfdc-lwc-compiler@~13.2.x",
16-
"@lwc/sfdc-lwc-compiler-prerelease": "npm:@lwc/sfdc-lwc-compiler@~13.3.x",
1716
"@lwc/sfdc-lwc-compiler-next": "npm:@lwc/sfdc-lwc-compiler@~13.3.x",
17+
"@lwc/sfdc-lwc-compiler-prerelease": "npm:@lwc/sfdc-lwc-compiler@~13.3.x",
1818
"@lwrjs/api": "0.18.3",
1919
"@oclif/core": "^4.5.6",
2020
"@salesforce/core": "^8.24.0",
@@ -25,8 +25,8 @@
2525
"glob": "^13.0.0",
2626
"lwc": "~8.27.0",
2727
"lwc-latest": "npm:lwc@~8.23.x",
28-
"lwc-prerelease": "npm:lwc@~8.24.x",
2928
"lwc-next": "npm:lwc@~8.24.x",
29+
"lwc-prerelease": "npm:lwc@~8.24.x",
3030
"node-fetch": "^3.3.2",
3131
"open": "^10.2.0",
3232
"xml2js": "^0.6.2"
@@ -121,7 +121,9 @@
121121
"access": "public"
122122
},
123123
"resolutions": {
124-
"cliui": "7.0.4"
124+
"cliui": "7.0.4",
125+
"prettier": "^3.7.4",
126+
"pretty-quick": "^4.2.2"
125127
},
126128
"wireit": {
127129
"build": {

src/shared/orgUtils.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616

1717
import { Connection } from '@salesforce/core';
18-
import { VersionChannel, VersionResolver } from './versionResolver.js';
18+
import { VersionChannel, resolveChannel, getDefaultChannel } from './versionResolver.js';
1919

2020
type LightningPreviewMetadataResponse = {
2121
enableLightningPreviewPref?: string;
@@ -77,7 +77,7 @@ export class OrgUtils {
7777
const appMenuItemsQuery =
7878
'SELECT Label,Description,Name FROM AppMenuItem WHERE IsAccessible=true AND IsVisible=true';
7979
const appMenuItems = await connection.query<{ Label: string; Description: string; Name: string }>(
80-
appMenuItemsQuery
80+
appMenuItemsQuery,
8181
);
8282

8383
const appDefinitionsQuery = "SELECT DeveloperName,DurableId FROM AppDefinition WHERE UiType='Lightning'";
@@ -149,31 +149,27 @@ export class OrgUtils {
149149
return envOverride as VersionChannel;
150150
} else {
151151
throw new Error(
152-
`Invalid FORCE_VERSION_CHANNEL value: "${envOverride}". ` + `Valid values are: ${validChannels.join(', ')}`
152+
`Invalid FORCE_VERSION_CHANNEL value: "${envOverride}". ` + `Valid values are: ${validChannels.join(', ')}`,
153153
);
154154
}
155155
}
156156

157157
// Priority 3: Skip check for testing (legacy compatibility)
158158
if (process.env.SKIP_API_VERSION_CHECK === 'true') {
159-
return VersionResolver.getDefaultChannel();
159+
return getDefaultChannel();
160160
}
161161

162162
// Priority 4: Automatic detection based on org version
163163
const orgVersion = connection.version;
164164

165165
try {
166-
const orgId = connection.getAuthInfoFields().orgId;
167-
if (!orgId) {
168-
throw new Error('Could not determine org ID from connection.');
169-
}
170-
return VersionResolver.resolveChannelWithCache(orgId, orgVersion);
166+
return resolveChannel(orgVersion);
171167
} catch (error) {
172168
// Enhance error with helpful message
173169
throw new Error(
174170
`${error instanceof Error ? error.message : String(error)}\n` +
175171
`Your org is on API version ${orgVersion}. ` +
176-
'Please ensure you are using the correct version of the CLI and this plugin.'
172+
'Please ensure you are using the correct version of the CLI and this plugin.',
177173
);
178174
}
179175
}

src/shared/typeUtils.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright 2026, Salesforce, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
type ValueOf<T> = T[keyof T];
18+
type Entries<T> = Array<[keyof T, ValueOf<T>]>;
19+
20+
// Same as `Object.entries()` but with type inference
21+
export function objectEntries<T extends object>(obj: T): Entries<T> {
22+
return Object.entries(obj) as Entries<T>;
23+
}

src/shared/versionResolver.ts

Lines changed: 46 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -14,170 +14,68 @@
1414
* limitations under the License.
1515
*/
1616

17-
import path from 'node:path';
18-
import url from 'node:url';
19-
import { CommonUtils } from '@salesforce/lwc-dev-mobile-core';
17+
import packageJson from '../../package.json' with { type: 'json' };
18+
import { objectEntries } from './typeUtils.js';
2019

2120
/**
2221
* Resolves org API version to appropriate dependency channel
2322
*/
24-
export type VersionChannel = 'latest' | 'prerelease' | 'next';
23+
export type VersionChannel = keyof typeof packageJson.apiVersionMetadata.channels;
2524

26-
export type ChannelConfig = {
27-
supportedApiVersions: string[];
28-
dependencies: {
29-
[key: string]: string;
30-
};
31-
};
32-
33-
type CacheEntry = {
34-
apiVersion: string;
35-
channel: VersionChannel;
36-
timestamp: number;
37-
};
25+
/**
26+
* Extracts major.minor from a version string (e.g., "65.0" from "65.0.1")
27+
*/
28+
function getMajorMinor(version: string): string {
29+
const parts = version.split('.');
30+
return `${parts[0]}.${parts[1]}`;
31+
}
3832

39-
type PackageJson = {
40-
apiVersionMetadata: {
41-
channels: {
42-
[key in VersionChannel]: ChannelConfig;
43-
};
44-
defaultChannel: VersionChannel;
45-
};
46-
};
33+
/**
34+
* Returns a formatted list of all supported API versions
35+
*/
36+
function getSupportedVersionsList(): string {
37+
const channels = packageJson.apiVersionMetadata.channels;
38+
const allVersions: string[] = [];
4739

48-
export class VersionResolver {
49-
private static channelMetadata: Map<VersionChannel, ChannelConfig> | null = null;
50-
private static versionCache: Map<string, CacheEntry> = new Map();
51-
private static readonly CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
40+
for (const config of Object.values(channels)) {
41+
allVersions.push(...config.supportedApiVersions);
42+
}
5243

53-
/**
54-
* Given an org API version, returns the appropriate channel
55-
*
56-
* @param orgApiVersion - The API version from the org (e.g., "65.0")
57-
* @returns The channel to use ('latest' or 'prerelease')
58-
* @throws Error if the API version is not supported by any channel
59-
*/
60-
public static resolveChannel(orgApiVersion: string): VersionChannel {
61-
const channels = this.loadChannelMetadata();
44+
return allVersions.join(', ');
45+
}
6246

63-
for (const [channel, config] of channels.entries()) {
64-
if (config.supportedApiVersions.includes(orgApiVersion)) {
65-
return channel;
66-
}
67-
}
47+
/**
48+
* Given an org API version, returns the appropriate channel
49+
*
50+
* @param orgApiVersion - The API version from the org (e.g., "65.0")
51+
* @returns The channel to use ('latest' or 'prerelease')
52+
* @throws Error if the API version is not supported by any channel
53+
*/
54+
export function resolveChannel(orgApiVersion: string): VersionChannel {
55+
const channels = packageJson.apiVersionMetadata.channels;
6856

69-
// If no exact match, try to find by major.minor comparison
70-
const orgMajorMinor = this.getMajorMinor(orgApiVersion);
71-
for (const [channel, config] of channels.entries()) {
72-
for (const supportedVersion of config.supportedApiVersions) {
73-
if (this.getMajorMinor(supportedVersion) === orgMajorMinor) {
74-
return channel;
75-
}
76-
}
57+
for (const [channel, config] of objectEntries(channels)) {
58+
if (config.supportedApiVersions.includes(orgApiVersion)) {
59+
return channel;
7760
}
78-
79-
throw new Error(
80-
`Unsupported org API version: ${orgApiVersion}. This plugin supports: ${this.getSupportedVersionsList()}`
81-
);
8261
}
8362

84-
/**
85-
* Resolves channel with caching support
86-
*
87-
* @param orgId - Unique identifier for the org
88-
* @param orgApiVersion - The API version from the org
89-
* @returns The channel to use
90-
*/
91-
public static resolveChannelWithCache(orgId: string, orgApiVersion: string): VersionChannel {
92-
// Check cache first
93-
const cached = this.versionCache.get(orgId);
94-
if (cached) {
95-
const age = Date.now() - cached.timestamp;
96-
if (age < this.CACHE_TTL_MS && cached.apiVersion === orgApiVersion) {
97-
return cached.channel;
63+
// If no exact match, try to find by major.minor comparison
64+
const orgMajorMinor = getMajorMinor(orgApiVersion);
65+
for (const [channel, config] of objectEntries(channels)) {
66+
for (const supportedVersion of config.supportedApiVersions) {
67+
if (getMajorMinor(supportedVersion) === orgMajorMinor) {
68+
return channel;
9869
}
99-
// Cache expired or version changed, remove it
100-
this.versionCache.delete(orgId);
101-
}
102-
103-
// Resolve and cache
104-
const channel = this.resolveChannel(orgApiVersion);
105-
this.versionCache.set(orgId, {
106-
apiVersion: orgApiVersion,
107-
channel,
108-
timestamp: Date.now(),
109-
});
110-
111-
return channel;
112-
}
113-
114-
/**
115-
* Returns the default channel from package.json
116-
*/
117-
public static getDefaultChannel(): VersionChannel {
118-
const packageJson = this.getPackageJson();
119-
return packageJson.apiVersionMetadata.defaultChannel;
120-
}
121-
122-
/**
123-
* Clears the version cache (useful for testing or when orgs are upgraded)
124-
*/
125-
public static clearCache(): void {
126-
this.versionCache.clear();
127-
this.channelMetadata = null;
128-
}
129-
130-
/**
131-
* Removes a specific org from the cache
132-
*/
133-
public static removeCacheEntry(orgId: string): void {
134-
this.versionCache.delete(orgId);
135-
}
136-
137-
/**
138-
* Loads channel metadata from package.json
139-
*/
140-
private static loadChannelMetadata(): Map<VersionChannel, ChannelConfig> {
141-
if (this.channelMetadata) {
142-
return this.channelMetadata;
14370
}
144-
145-
const packageJson = this.getPackageJson();
146-
const channels = packageJson.apiVersionMetadata.channels;
147-
148-
this.channelMetadata = new Map();
149-
for (const [channel, config] of Object.entries(channels)) {
150-
this.channelMetadata.set(channel as VersionChannel, config);
151-
}
152-
153-
return this.channelMetadata;
15471
}
15572

156-
/**
157-
* Extracts major.minor from a version string (e.g., "65.0" from "65.0.1")
158-
*/
159-
private static getMajorMinor(version: string): string {
160-
const parts = version.split('.');
161-
return `${parts[0]}.${parts[1]}`;
162-
}
163-
164-
/**
165-
* Returns a formatted list of all supported API versions
166-
*/
167-
private static getSupportedVersionsList(): string {
168-
const channels = this.loadChannelMetadata();
169-
const allVersions: string[] = [];
170-
171-
for (const config of channels.values()) {
172-
allVersions.push(...config.supportedApiVersions);
173-
}
174-
175-
return allVersions.join(', ');
176-
}
73+
throw new Error(`Unsupported org API version: ${orgApiVersion}. This plugin supports: ${getSupportedVersionsList()}`);
74+
}
17775

178-
private static getPackageJson(): PackageJson {
179-
const dirname = path.dirname(url.fileURLToPath(import.meta.url));
180-
const packageJsonFilePath = path.resolve(dirname, '../../package.json');
181-
return CommonUtils.loadJsonFromFile(packageJsonFilePath) as unknown as PackageJson;
182-
}
76+
/**
77+
* Returns the default channel from package.json
78+
*/
79+
export function getDefaultChannel(): VersionChannel {
80+
return packageJson.apiVersionMetadata.defaultChannel as VersionChannel;
18381
}

test/shared/orgUtils.test.ts

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -17,38 +17,11 @@
1717
import { TestContext } from '@salesforce/core/testSetup';
1818
import { expect } from 'chai';
1919
import { AuthInfo, Connection } from '@salesforce/core';
20-
import { CommonUtils } from '@salesforce/lwc-dev-mobile-core';
2120
import { OrgUtils } from '../../src/shared/orgUtils.js';
22-
import { VersionResolver } from '../../src/shared/versionResolver.js';
2321

2422
describe('orgUtils', () => {
2523
const $$ = new TestContext();
2624

27-
const mockPackageJson = {
28-
apiVersionMetadata: {
29-
channels: {
30-
latest: {
31-
supportedApiVersions: ['65.0'],
32-
dependencies: {},
33-
},
34-
prerelease: {
35-
supportedApiVersions: ['66.0'],
36-
dependencies: {},
37-
},
38-
next: {
39-
supportedApiVersions: ['67.0'],
40-
dependencies: {},
41-
},
42-
},
43-
defaultChannel: 'latest',
44-
},
45-
};
46-
47-
beforeEach(() => {
48-
$$.SANDBOX.stub(CommonUtils, 'loadJsonFromFile').returns(mockPackageJson);
49-
VersionResolver.clearCache();
50-
});
51-
5225
afterEach(() => {
5326
$$.restore();
5427
});
@@ -91,7 +64,6 @@ describe('orgUtils', () => {
9164
it('auto-detects channel based on org version', async () => {
9265
const conn = new Connection({ authInfo: new AuthInfo() });
9366
$$.SANDBOX.stub(conn, 'version').get(() => '65.0');
94-
$$.SANDBOX.stub(conn, 'getAuthInfoFields').returns({ orgId: 'org1' });
9567

9668
const channel = OrgUtils.getVersionChannel(conn);
9769
expect(channel).to.equal('latest');
@@ -100,7 +72,6 @@ describe('orgUtils', () => {
10072
it('throws error for unsupported org version', async () => {
10173
const conn = new Connection({ authInfo: new AuthInfo() });
10274
$$.SANDBOX.stub(conn, 'version').get(() => '64.0');
103-
$$.SANDBOX.stub(conn, 'getAuthInfoFields').returns({ orgId: 'org1' });
10475

10576
expect(() => OrgUtils.getVersionChannel(conn)).to.throw(/Unsupported org API version: 64.0/);
10677
});

0 commit comments

Comments
 (0)