Skip to content

Commit 14f2f35

Browse files
abdulsattarsvc-cli-botnrkruk
authored
feat: use the right LWC/Dev server version for the org @W-20405334@ (#596)
* feat: use the right LWC/Dev server version for the org * fix: tests * fix: remove check-versions script * fix: types * fix: simplify versionresolver * fix: simplify dependency loader * fix: remove duplication * fix: remove deprecated ensureMatchingAPIVersion * refactor: simplify VersionResolver and DependencyLoader and improve error handling * feat: implement dual version support via NPM aliasing and dynamic loading - Added aliased dependencies for @lwc/lwc-dev-server, @lwc/sfdc-lwc-compiler, and lwc - Implemented DependencyLoader for dynamic runtime branching based on org API version - Removed 'version channel' concept in favor of direct API version mapping - Updated CLI commands to pass org connection for version detection - Added .husky/check-versions.js to ensure dependency/metadata alignment - Cleaned up unused VersionResolver and type utilities * fix: use new package that has no peerDependencies * fix: use the right version for 260 * chore(release): 6.0.2-canary.0 [skip ci] * fix: use latest lwc * fix: enable TypeScript dependency check in CI workflow * fix: use new lwc for 260 * chore(release): 6.0.2-canary.1 [skip ci] * fix: add back 258 * fix: add test to check versions * fix: remove DUAL_VERSION.md * fix: remove unused env var * fix: feedback * fix: yarn install * fix: cleanup * fix: stuff * fix: bump 66.0 to 13.2.23 for removing typescript * fix: fix 66.0 version * chore(release): 6.1.1-canary.0 [skip ci] * fix: remove 65.0 and use canary 66.0, 67.0 * fix: feedback * fix: yarn install --------- Co-authored-by: svc-cli-bot <Svc_cli_bot@salesforce.com> Co-authored-by: Nicolas Kruk <nkruk@salesforce.com>
1 parent 7783c3d commit 14f2f35

28 files changed

+913
-417
lines changed

.github/workflows/test.yml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,24 @@ on:
77
workflow_dispatch:
88

99
jobs:
10+
check-versions:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
- uses: actions/setup-node@v4
15+
with:
16+
node-version: '20'
17+
cache: 'yarn'
18+
- run: yarn install --frozen-lockfile
19+
- run: node .husky/check-versions.js
1020
yarn-lockfile-check:
1121
uses: salesforcecli/github-workflows/.github/workflows/lockFileCheck.yml@main
1222
# Since the Windows unit tests take much longer, we run the linux unit tests first and then run the windows unit tests in parallel with NUTs
1323
linux-unit-tests:
1424
needs: yarn-lockfile-check
1525
uses: salesforcecli/github-workflows/.github/workflows/unitTestsLinux.yml@main
1626
with:
17-
skipTsDepCheck: true
27+
skipTsDepCheck: false
1828
windows-unit-tests:
1929
needs: linux-unit-tests
2030
uses: salesforcecli/github-workflows/.github/workflows/unitTestsWindows.yml@main

.github/workflows/validate-pr.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,15 @@ on:
99
- '2*-patch'
1010

1111
jobs:
12+
check-versions:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v4
16+
- uses: actions/setup-node@v4
17+
with:
18+
node-version: '20'
19+
cache: 'yarn'
20+
- run: yarn install --frozen-lockfile
21+
- run: node .husky/check-versions.js
1222
pr-validation:
1323
uses: salesforcecli/github-workflows/.github/workflows/validatePR.yml@main

.husky/check-versions.js

Lines changed: 87 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,99 @@
11
import fs from 'node:fs';
22
import semver from 'semver';
33

4+
const SFDX_LOCAL_DEV_DIST_BASE = '@lwc/sfdx-local-dev-dist';
5+
const SFDX_LOCAL_DEV_DIST_PATTERN = /^@lwc\/sfdx-local-dev-dist-([\d.]+)$/;
6+
7+
/**
8+
* This script ensures that the aliased dependencies in package.json stay in sync
9+
* with the versions defined in apiVersionMetadata.
10+
* It also ensures @lwc/sfdx-local-dev-dist matches the version of the highest-numbered
11+
* @lwc/sfdx-local-dev-dist-<number> alias.
12+
*/
13+
414
// Read package.json
515
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
16+
const apiVersionMetadata = packageJson.apiVersionMetadata;
17+
18+
if (!apiVersionMetadata) {
19+
console.error('Error: missing apiVersionMetadata in package.json');
20+
process.exit(1);
21+
}
22+
23+
let hasError = false;
24+
25+
// Check that @lwc/sfdx-local-dev-dist version matches the highest-numbered alias
26+
const baseVersion = packageJson.dependencies[SFDX_LOCAL_DEV_DIST_BASE];
27+
if (baseVersion) {
28+
const aliasedDeps = Object.keys(packageJson.dependencies).filter((key) => SFDX_LOCAL_DEV_DIST_PATTERN.test(key));
29+
if (aliasedDeps.length > 0) {
30+
const highestAlias = aliasedDeps
31+
.map((key) => {
32+
const m = key.match(SFDX_LOCAL_DEV_DIST_PATTERN);
33+
return { key, number: m ? parseFloat(m[1]) : -1 };
34+
})
35+
.sort((a, b) => b.number - a.number)[0];
36+
37+
const highestAliasValue = packageJson.dependencies[highestAlias.key];
38+
const highestMatch = highestAliasValue?.match(/@([^@]+)$/);
39+
const highestRange = highestMatch ? highestMatch[1] : null;
640

7-
// Extract versions
8-
const devServerDependencyVersion = packageJson.dependencies['@lwc/lwc-dev-server'];
9-
const devServerTargetVersionRange = packageJson.apiVersionMetadata?.target?.matchingDevServerVersion;
41+
if (!highestRange) {
42+
console.error(`Error: Could not parse version range from '${highestAlias.key}': ${highestAliasValue}`);
43+
hasError = true;
44+
} else if (baseVersion !== highestRange) {
45+
console.error(
46+
`Error: Version of '${SFDX_LOCAL_DEV_DIST_BASE}' (${baseVersion}) must be the same as the version of the highest-numbered alias '${highestAlias.key}' (${highestRange}).`,
47+
);
48+
hasError = true;
49+
}
50+
}
51+
}
52+
53+
// Iterate through each API version defined in metadata
54+
for (const [apiVersion, metadata] of Object.entries(apiVersionMetadata)) {
55+
const expectedDeps = metadata.dependencies;
56+
if (!expectedDeps) continue;
57+
58+
for (const [depName, expectedRange] of Object.entries(expectedDeps)) {
59+
// For each dependency in metadata, find its aliased counterpart in package.json dependencies
60+
// e.g. @lwc/lwc-dev-server -> @lwc/lwc-dev-server-65.0
61+
const aliasName = `${depName}-${apiVersion}`;
62+
const actualAliasValue = packageJson.dependencies[aliasName];
63+
64+
if (!actualAliasValue) {
65+
console.error(`Error: Missing aliased dependency '${aliasName}' in package.json for API version ${apiVersion}`);
66+
hasError = true;
67+
continue;
68+
}
1069

11-
if (!devServerDependencyVersion || !devServerTargetVersionRange) {
12-
console.error('Error: missing @lwc/lwc-dev-server or matchingDevServerVersion');
13-
process.exit(1); // Fail the check
70+
// actualAliasValue looks like "npm:@lwc/lwc-dev-server@~13.2.x" or "npm:lwc@~8.23.x"
71+
// We want to extract the version range after the last @
72+
const match = actualAliasValue.match(/@([^@]+)$/);
73+
if (!match) {
74+
console.error(`Error: Could not parse version range from aliased dependency '${aliasName}': ${actualAliasValue}`);
75+
hasError = true;
76+
continue;
77+
}
78+
79+
const actualRange = match[1];
80+
81+
// Compare the range in metadata with the range in the aliased dependency
82+
if (!semver.intersects(expectedRange, actualRange)) {
83+
console.error(
84+
`Error: Version mismatch for '${aliasName}'. ` +
85+
`Expected ${expectedRange} in apiVersionMetadata, but found ${actualRange} in dependencies.`,
86+
);
87+
hasError = true;
88+
}
89+
}
1490
}
1591

16-
// Compare versions
17-
if (semver.intersects(devServerTargetVersionRange, devServerDependencyVersion)) {
18-
process.exit(0); // Pass the check
19-
} else {
92+
if (hasError) {
2093
console.error(
21-
`Error: @lwc/lwc-dev-server versions do not match between 'dependencies' and 'apiVersionMetadata' in package.json. Expected ${devServerDependencyVersion} in apiVersionMetadata > target > matchingDevServerVersion. Got ${devServerTargetVersionRange} instead. When updating the @lwc/lwc-dev-server dependency, you must ensure that it is compatible with the supported API version in this branch, then update apiVersionMetadata > target > matchingDevServerVersion to match, in order to "sign off" on this dependency change.`
94+
'\nWhen updating LWC dependencies, you must ensure that the versions in apiVersionMetadata match the aliased dependencies in package.json.',
2295
);
23-
process.exit(1); // Fail the check
96+
process.exit(1);
2497
}
98+
99+
process.exit(0);

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ EXAMPLES
203203
$ sf lightning dev app --target-org myOrg --device-type ios --device-id "iPhone 15 Pro Max"
204204
```
205205

206-
_See code: [src/commands/lightning/dev/app.ts](https://github.com/salesforcecli/plugin-lightning-dev/blob/6.1.0/src/commands/lightning/dev/app.ts)_
206+
_See code: [src/commands/lightning/dev/app.ts](https://github.com/salesforcecli/plugin-lightning-dev/blob/6.0.2-canary.2/src/commands/lightning/dev/app.ts)_
207207

208208
## `sf lightning dev component`
209209

@@ -251,7 +251,7 @@ EXAMPLES
251251
$ sf lightning dev component --name myComponent
252252
```
253253

254-
_See code: [src/commands/lightning/dev/component.ts](https://github.com/salesforcecli/plugin-lightning-dev/blob/6.1.0/src/commands/lightning/dev/component.ts)_
254+
_See code: [src/commands/lightning/dev/component.ts](https://github.com/salesforcecli/plugin-lightning-dev/blob/6.0.2-canary.2/src/commands/lightning/dev/component.ts)_
255255

256256
## `sf lightning dev site`
257257

@@ -308,6 +308,6 @@ EXAMPLES
308308
$ sf lightning dev site --name "Partner Central" --target-org myOrg --get-latest
309309
```
310310

311-
_See code: [src/commands/lightning/dev/site.ts](https://github.com/salesforcecli/plugin-lightning-dev/blob/6.1.0/src/commands/lightning/dev/site.ts)_
311+
_See code: [src/commands/lightning/dev/site.ts](https://github.com/salesforcecli/plugin-lightning-dev/blob/6.0.2-canary.2/src/commands/lightning/dev/site.ts)_
312312

313313
<!-- commandsstop -->

messages/shared.utils.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ Couldn't find identity data while generating preview arguments
5454

5555
Couldn't find entity ID while generating preview arguments
5656

57+
# error.org.api-unsupported
58+
59+
Your org is on API Version %s. This version of the plugin supports only versions %s. Please update your plugin.
60+
5761
# error.no-project
5862

5963
This command is required to run from within a Salesforce project directory. %s

package.json

Lines changed: 16 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
{
22
"name": "@salesforce/plugin-lightning-dev",
33
"description": "Lightning development tools for LEX, Mobile, and Experience Sites",
4-
"version": "6.1.0",
4+
"version": "6.1.1-canary.0",
55
"author": "Salesforce",
66
"bugs": "https://github.com/forcedotcom/cli/issues",
77
"dependencies": {
88
"@inquirer/prompts": "^5.3.8",
99
"@inquirer/select": "^2.4.7",
10-
"@lwc/lwc-dev-server": "~13.3.14",
11-
"@lwc/sfdc-lwc-compiler": "~13.3.8",
10+
"@lwc/sfdx-local-dev-dist": "~13.3.19",
11+
"@lwc/sfdx-local-dev-dist-66.0": "npm:@lwc/sfdx-local-dev-dist@~13.2.24-alpha.0",
12+
"@lwc/sfdx-local-dev-dist-67.0": "npm:@lwc/sfdx-local-dev-dist@~13.3.19",
1213
"@lwrjs/api": "0.21.5",
1314
"@oclif/core": "^4.5.6",
1415
"@salesforce/core": "^8.25.0",
@@ -43,7 +44,7 @@
4344
"typescript": "^5.5.4"
4445
},
4546
"engines": {
46-
"node": ">=18.0.0"
47+
"node": ">=20.0.0"
4748
},
4849
"files": [
4950
"/lib",
@@ -112,7 +113,9 @@
112113
"access": "public"
113114
},
114115
"resolutions": {
115-
"cliui": "7.0.4"
116+
"cliui": "7.0.4",
117+
"prettier": "^3.7.4",
118+
"pretty-quick": "^4.2.2"
116119
},
117120
"wireit": {
118121
"build": {
@@ -230,41 +233,16 @@
230233
}
231234
},
232235
"apiVersionMetadata": {
233-
"x-apiVersionMetadata-comments": [
234-
"Refer to ApiVersionMetadata in orgUtils.ts for more details.",
235-
"The 'target' section defines the dev server version (matchingDevServerVersion) and the API version that it can support (versionNumber).",
236-
"The 'versionToTagMappings' section defines the mapping between released tags for our CLI plugin and the org version that each tag supports."
237-
],
238-
"target": {
239-
"versionNumber": "67.0",
240-
"matchingDevServerVersion": "~13.3.8"
236+
"66.0": {
237+
"dependencies": {
238+
"@lwc/sfdx-local-dev-dist": "~13.2.23"
239+
}
241240
},
242-
"versionToTagMappings": [
243-
{
244-
"versionNumber": "62.0",
245-
"tagName": "v1"
246-
},
247-
{
248-
"versionNumber": "63.0",
249-
"tagName": "v2"
250-
},
251-
{
252-
"versionNumber": "64.0",
253-
"tagName": "v3"
254-
},
255-
{
256-
"versionNumber": "65.0",
257-
"tagName": "latest"
258-
},
259-
{
260-
"versionNumber": "66.0",
261-
"tagName": "prerelease"
262-
},
263-
{
264-
"versionNumber": "67.0",
265-
"tagName": "next"
241+
"67.0": {
242+
"dependencies": {
243+
"@lwc/sfdx-local-dev-dist": "~13.3.11"
266244
}
267-
]
245+
}
268246
},
269247
"exports": "./lib/index.js",
270248
"type": "module",

src/commands/lightning/dev/app.ts

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

1717
import path from 'node:path';
18-
import { Logger, Messages, SfProject } from '@salesforce/core';
18+
import { Connection, Logger, Messages, SfProject } from '@salesforce/core';
1919
import {
2020
AndroidAppPreviewConfig,
2121
AndroidDevice,
@@ -112,16 +112,18 @@ export default class LightningDevApp extends SfCommand<void> {
112112

113113
if (platform === Platform.desktop) {
114114
await this.desktopPreview(
115+
connection,
115116
sfdxProjectRootPath,
116117
serverPorts,
117118
ldpServerToken,
118119
ldpServerId,
119120
ldpServerUrl,
120121
appId,
121-
logger
122+
logger,
122123
);
123124
} else {
124125
await this.mobilePreview(
126+
connection,
125127
platform,
126128
sfdxProjectRootPath,
127129
serverPorts,
@@ -131,25 +133,26 @@ export default class LightningDevApp extends SfCommand<void> {
131133
appName,
132134
appId,
133135
deviceId,
134-
logger
136+
logger,
135137
);
136138
}
137139
}
138140

139141
private async desktopPreview(
142+
connection: Connection,
140143
sfdxProjectRootPath: string,
141144
serverPorts: { httpPort: number; httpsPort: number },
142145
ldpServerToken: string,
143146
ldpServerId: string,
144147
ldpServerUrl: string,
145148
appId: string | undefined,
146-
logger: Logger
149+
logger: Logger,
147150
): Promise<void> {
148151
if (!appId) {
149152
logger.debug('No Lightning Experience application name provided.... using the default app instead.');
150153
}
151154

152-
const targetOrg = PreviewUtils.getTargetOrgFromArguments(this.argv);
155+
const targetOrgArg = PreviewUtils.getTargetOrgFromArguments(this.argv);
153156

154157
if (ldpServerUrl.startsWith('wss')) {
155158
this.log(`\n${messages.getMessage('trust.local.dev.server')}`);
@@ -159,17 +162,18 @@ export default class LightningDevApp extends SfCommand<void> {
159162
ldpServerUrl,
160163
ldpServerId,
161164
appId,
162-
targetOrg
165+
targetOrgArg,
163166
);
164167

165168
// Start the LWC Dev Server
166-
await startLWCServer(logger, sfdxProjectRootPath, ldpServerToken, Platform.desktop, serverPorts);
169+
await startLWCServer(logger, connection, sfdxProjectRootPath, ldpServerToken, Platform.desktop, serverPorts);
167170

168171
// Open the browser and navigate to the right page
169172
await this.config.runCommand('org:open', launchArguments);
170173
}
171174

172175
private async mobilePreview(
176+
connection: Connection,
173177
platform: Platform.ios | Platform.android,
174178
sfdxProjectRootPath: string,
175179
serverPorts: { httpPort: number; httpsPort: number },
@@ -179,7 +183,7 @@ export default class LightningDevApp extends SfCommand<void> {
179183
appName: string | undefined,
180184
appId: string | undefined,
181185
deviceId: string | undefined,
182-
logger: Logger
186+
logger: Logger,
183187
): Promise<void> {
184188
try {
185189
// Verify that user environment is set up for mobile (i.e. has right tooling)
@@ -250,7 +254,7 @@ export default class LightningDevApp extends SfCommand<void> {
250254
platform,
251255
logger,
252256
this.spinner,
253-
this.progress
257+
this.progress,
254258
);
255259

256260
// on iOS the bundle comes as a ZIP archive so we need to extract it first
@@ -270,15 +274,15 @@ export default class LightningDevApp extends SfCommand<void> {
270274
}
271275

272276
// Start the LWC Dev Server
273-
await startLWCServer(logger, sfdxProjectRootPath, ldpServerToken, platform, serverPorts, certData);
277+
await startLWCServer(logger, connection, sfdxProjectRootPath, ldpServerToken, platform, serverPorts, certData);
274278

275279
// Launch the native app for previewing (launchMobileApp will show its own spinner)
276280
// eslint-disable-next-line camelcase
277281
appConfig.launch_arguments = PreviewUtils.generateMobileAppPreviewLaunchArguments(
278282
ldpServerUrl,
279283
ldpServerId,
280284
appName,
281-
appId
285+
appId,
282286
);
283287
const targetActivity = (appConfig as AndroidAppPreviewConfig)?.activity;
284288
const targetApp = targetActivity ? `${appConfig.id}/${targetActivity}` : appConfig.id;

0 commit comments

Comments
 (0)