Skip to content

Commit ea20586

Browse files
committed
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
1 parent 2637a87 commit ea20586

22 files changed

Lines changed: 192 additions & 489 deletions

.husky/check-versions.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import fs from 'node:fs';
2+
import semver from 'semver';
3+
4+
/**
5+
* This script ensures that the aliased dependencies in package.json stay in sync
6+
* with the versions defined in apiVersionMetadata.
7+
*/
8+
9+
// Read package.json
10+
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
11+
const apiVersionMetadata = packageJson.apiVersionMetadata;
12+
13+
if (!apiVersionMetadata) {
14+
console.error('Error: missing apiVersionMetadata in package.json');
15+
process.exit(1);
16+
}
17+
18+
let hasError = false;
19+
20+
// Iterate through each API version defined in metadata
21+
for (const [apiVersion, metadata] of Object.entries(apiVersionMetadata)) {
22+
const expectedDeps = metadata.dependencies;
23+
if (!expectedDeps) continue;
24+
25+
for (const [depName, expectedRange] of Object.entries(expectedDeps)) {
26+
// For each dependency in metadata, find its aliased counterpart in package.json dependencies
27+
// e.g. @lwc/lwc-dev-server -> @lwc/lwc-dev-server-65.0
28+
const aliasName = `${depName}-${apiVersion}`;
29+
const actualAliasValue = packageJson.dependencies[aliasName];
30+
31+
if (!actualAliasValue) {
32+
console.error(`Error: Missing aliased dependency '${aliasName}' in package.json for API version ${apiVersion}`);
33+
hasError = true;
34+
continue;
35+
}
36+
37+
// actualAliasValue looks like "npm:@lwc/lwc-dev-server@~13.2.x" or "npm:lwc@~8.23.x"
38+
// We want to extract the version range after the last @
39+
const match = actualAliasValue.match(/@([^@]+)$/);
40+
if (!match) {
41+
console.error(`Error: Could not parse version range from aliased dependency '${aliasName}': ${actualAliasValue}`);
42+
hasError = true;
43+
continue;
44+
}
45+
46+
const actualRange = match[1];
47+
48+
// Compare the range in metadata with the range in the aliased dependency
49+
if (!semver.intersects(expectedRange, actualRange)) {
50+
console.error(
51+
`Error: Version mismatch for '${aliasName}'. ` +
52+
`Expected ${expectedRange} in apiVersionMetadata, but found ${actualRange} in dependencies.`,
53+
);
54+
hasError = true;
55+
}
56+
}
57+
}
58+
59+
if (hasError) {
60+
console.error(
61+
'\nWhen updating LWC dependencies, you must ensure that the versions in apiVersionMetadata match the aliased dependencies in package.json.',
62+
);
63+
process.exit(1);
64+
}
65+
66+
process.exit(0);

.husky/pre-commit

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
#!/bin/sh
22
. "$(dirname "$0")/_/husky.sh"
33

4+
# Run the custom version check script
5+
node .husky/check-versions.js
6+
47
yarn lint && yarn pretty-quick --staged

command-snapshot.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,23 @@
44
"command": "lightning:dev:app",
55
"flagAliases": [],
66
"flagChars": ["i", "n", "o", "t"],
7-
"flags": ["device-id", "device-type", "flags-dir", "name", "target-org", "version-channel"],
7+
"flags": ["device-id", "device-type", "flags-dir", "name", "target-org"],
88
"plugin": "@salesforce/plugin-lightning-dev"
99
},
1010
{
1111
"alias": [],
1212
"command": "lightning:dev:component",
1313
"flagAliases": [],
1414
"flagChars": ["c", "n", "o"],
15-
"flags": ["api-version", "client-select", "flags-dir", "json", "name", "target-org", "version-channel"],
15+
"flags": ["api-version", "client-select", "flags-dir", "json", "name", "target-org"],
1616
"plugin": "@salesforce/plugin-lightning-dev"
1717
},
1818
{
1919
"alias": [],
2020
"command": "lightning:dev:site",
2121
"flagAliases": [],
2222
"flagChars": ["l", "n", "o"],
23-
"flags": ["flags-dir", "get-latest", "guest", "name", "ssr", "target-org", "version-channel"],
23+
"flags": ["flags-dir", "get-latest", "guest", "name", "ssr", "target-org"],
2424
"plugin": "@salesforce/plugin-lightning-dev"
2525
}
2626
]

messages/lightning.dev.app.md

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,6 @@ Type of device to display the app preview.
3131

3232
ID of the mobile device to display the preview if device type is set to `ios` or `android`. The default value is the ID of the first available mobile device.
3333

34-
# flags.version-channel.summary
35-
36-
Manually specify which version channel to use (latest, prerelease, or next).
37-
38-
# flags.version-channel.description
39-
40-
Override automatic version detection and force a specific dependency channel. Useful for testing and debugging. Valid values: "latest", "prerelease", "next".
41-
4234
# error.fetching.app-id
4335

4436
Unable to determine App Id for %s

messages/lightning.dev.component.md

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,6 @@ Name of a component to preview.
2424

2525
Launch component preview without selecting a component
2626

27-
# flags.version-channel.summary
28-
29-
Manually specify which version channel to use (latest, prerelease, or next).
30-
31-
# flags.version-channel.description
32-
33-
Override automatic version detection and force a specific dependency channel. Useful for testing and debugging. Valid values: "latest", "prerelease", "next".
34-
3527
# error.directory
3628

3729
Unable to find components

messages/lightning.dev.site.md

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,6 @@ Preview the site as a guest user (rather than an authenticated user).
3333

3434
Preview the SSR bundle
3535

36-
# flags.version-channel.summary
37-
38-
Manually specify which version channel to use (latest, prerelease, or next).
39-
40-
# flags.version-channel.description
41-
42-
Override automatic version detection and force a specific dependency channel. Useful for testing and debugging. Valid values: "latest", "prerelease", "next".
43-
4436
# examples
4537

4638
- Select a site to preview from the org "myOrg":

package.json

Lines changed: 28 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@
88
"@inquirer/prompts": "^5.3.8",
99
"@inquirer/select": "^2.4.7",
1010
"@lwc/lwc-dev-server": "~13.3.8",
11-
"@lwc/lwc-dev-server-latest": "npm:@lwc/lwc-dev-server@~13.2.x",
12-
"@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",
11+
"@lwc/lwc-dev-server-65.0": "npm:@lwc/lwc-dev-server@~13.2.x",
12+
"@lwc/lwc-dev-server-66.0": "npm:@lwc/lwc-dev-server@~13.3.x",
13+
"@lwc/lwc-dev-server-67.0": "npm:@lwc/lwc-dev-server@~13.3.x",
1414
"@lwc/sfdc-lwc-compiler": "~13.3.8",
15-
"@lwc/sfdc-lwc-compiler-latest": "npm:@lwc/sfdc-lwc-compiler@~13.2.x",
16-
"@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",
15+
"@lwc/sfdc-lwc-compiler-65.0": "npm:@lwc/sfdc-lwc-compiler@~13.2.x",
16+
"@lwc/sfdc-lwc-compiler-66.0": "npm:@lwc/sfdc-lwc-compiler@~13.3.x",
17+
"@lwc/sfdc-lwc-compiler-67.0": "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",
@@ -24,9 +24,9 @@
2424
"axios": "^1.13.2",
2525
"glob": "^13.0.0",
2626
"lwc": "~8.27.0",
27-
"lwc-latest": "npm:lwc@~8.23.x",
28-
"lwc-next": "npm:lwc@~8.24.x",
29-
"lwc-prerelease": "npm:lwc@~8.24.x",
27+
"lwc-65.0": "npm:lwc@~8.23.x",
28+
"lwc-66.0": "npm:lwc@~8.24.x",
29+
"lwc-67.0": "npm:lwc@~8.24.x",
3030
"node-fetch": "^3.3.2",
3131
"open": "^10.2.0",
3232
"xml2js": "^0.6.2"
@@ -52,7 +52,7 @@
5252
"typescript": "^5.5.4"
5353
},
5454
"engines": {
55-
"node": ">=18.0.0"
55+
"node": ">=20.0.0"
5656
},
5757
"files": [
5858
"/lib",
@@ -241,65 +241,27 @@
241241
}
242242
},
243243
"apiVersionMetadata": {
244-
"channels": {
245-
"latest": {
246-
"supportedApiVersions": [
247-
"65.0"
248-
],
249-
"dependencies": {
250-
"@lwc/lwc-dev-server": "~13.2.x",
251-
"@lwc/sfdc-lwc-compiler": "~13.2.x",
252-
"lwc": "~8.23.x"
253-
}
254-
},
255-
"prerelease": {
256-
"supportedApiVersions": [
257-
"66.0"
258-
],
259-
"dependencies": {
260-
"@lwc/lwc-dev-server": "~13.3.x",
261-
"@lwc/sfdc-lwc-compiler": "~13.3.x",
262-
"lwc": "~8.24.x"
263-
}
264-
},
265-
"next": {
266-
"supportedApiVersions": [
267-
"67.0"
268-
],
269-
"dependencies": {
270-
"@lwc/lwc-dev-server": "~13.3.x",
271-
"@lwc/sfdc-lwc-compiler": "~13.3.x",
272-
"lwc": "~8.24.x"
273-
}
244+
"65.0": {
245+
"dependencies": {
246+
"@lwc/lwc-dev-server": "~13.2.x",
247+
"@lwc/sfdc-lwc-compiler": "~13.2.x",
248+
"lwc": "~8.23.x"
274249
}
275250
},
276-
"defaultChannel": "latest",
277-
"versionToTagMappings": [
278-
{
279-
"versionNumber": "62.0",
280-
"tagName": "v1"
281-
},
282-
{
283-
"versionNumber": "63.0",
284-
"tagName": "v2"
285-
},
286-
{
287-
"versionNumber": "64.0",
288-
"tagName": "v3"
289-
},
290-
{
291-
"versionNumber": "65.0",
292-
"tagName": "latest"
293-
},
294-
{
295-
"versionNumber": "66.0",
296-
"tagName": "prerelease"
297-
},
298-
{
299-
"versionNumber": "67.0",
300-
"tagName": "next"
251+
"66.0": {
252+
"dependencies": {
253+
"@lwc/lwc-dev-server": "~13.3.x",
254+
"@lwc/sfdc-lwc-compiler": "~13.3.x",
255+
"lwc": "~8.24.x"
301256
}
302-
]
257+
},
258+
"67.0": {
259+
"dependencies": {
260+
"@lwc/lwc-dev-server": "~13.3.x",
261+
"@lwc/sfdc-lwc-compiler": "~13.3.x",
262+
"lwc": "~8.24.x"
263+
}
264+
}
303265
},
304266
"exports": "./lib/index.js",
305267
"type": "module",

src/commands/lightning/dev/app.ts

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ import { startLWCServer } from '../../../lwc-dev-server/index.js';
3030
import { PreviewUtils } from '../../../shared/previewUtils.js';
3131
import { PromptUtils } from '../../../shared/promptUtils.js';
3232
import { MetaUtils } from '../../../shared/metaUtils.js';
33-
import { VersionChannel } from '../../../shared/versionResolver.js';
3433

3534
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
3635
const messages = Messages.loadMessages('@salesforce/plugin-lightning-dev', 'lightning.dev.app');
@@ -70,12 +69,6 @@ export default class LightningDevApp extends SfCommand<void> {
7069
summary: messages.getMessage('flags.device-id.summary'),
7170
char: 'i',
7271
}),
73-
'version-channel': Flags.string({
74-
summary: messages.getMessage('flags.version-channel.summary'),
75-
description: messages.getMessage('flags.version-channel.description'),
76-
options: ['latest', 'prerelease', 'next'],
77-
required: false,
78-
}),
7972
};
8073

8174
public async run(): Promise<void> {
@@ -118,8 +111,6 @@ export default class LightningDevApp extends SfCommand<void> {
118111
const ldpServerUrl = PreviewUtils.generateWebSocketUrlForLocalDevServer(platform, serverPorts, logger);
119112
logger.debug(`Local Dev Server url is ${ldpServerUrl}`);
120113

121-
const versionChannel = flags['version-channel'] as VersionChannel | undefined;
122-
123114
if (platform === Platform.desktop) {
124115
await this.desktopPreview(
125116
targetOrg,
@@ -130,7 +121,6 @@ export default class LightningDevApp extends SfCommand<void> {
130121
ldpServerUrl,
131122
appId,
132123
logger,
133-
versionChannel
134124
);
135125
} else {
136126
await this.mobilePreview(
@@ -145,7 +135,6 @@ export default class LightningDevApp extends SfCommand<void> {
145135
appId,
146136
deviceId,
147137
logger,
148-
versionChannel
149138
);
150139
}
151140
}
@@ -159,7 +148,6 @@ export default class LightningDevApp extends SfCommand<void> {
159148
ldpServerUrl: string,
160149
appId: string | undefined,
161150
logger: Logger,
162-
versionChannelOverride?: VersionChannel
163151
): Promise<void> {
164152
if (!appId) {
165153
logger.debug('No Lightning Experience application name provided.... using the default app instead.');
@@ -175,7 +163,7 @@ export default class LightningDevApp extends SfCommand<void> {
175163
ldpServerUrl,
176164
ldpServerId,
177165
appId,
178-
targetOrgArg
166+
targetOrgArg,
179167
);
180168

181169
// Start the LWC Dev Server
@@ -188,7 +176,6 @@ export default class LightningDevApp extends SfCommand<void> {
188176
serverPorts,
189177
undefined,
190178
undefined,
191-
versionChannelOverride
192179
);
193180

194181
// Open the browser and navigate to the right page
@@ -207,7 +194,6 @@ export default class LightningDevApp extends SfCommand<void> {
207194
appId: string | undefined,
208195
deviceId: string | undefined,
209196
logger: Logger,
210-
versionChannelOverride?: VersionChannel
211197
): Promise<void> {
212198
try {
213199
// Verify that user environment is set up for mobile (i.e. has right tooling)
@@ -278,7 +264,7 @@ export default class LightningDevApp extends SfCommand<void> {
278264
platform,
279265
logger,
280266
this.spinner,
281-
this.progress
267+
this.progress,
282268
);
283269

284270
// on iOS the bundle comes as a ZIP archive so we need to extract it first
@@ -307,7 +293,6 @@ export default class LightningDevApp extends SfCommand<void> {
307293
serverPorts,
308294
certData,
309295
undefined,
310-
versionChannelOverride
311296
);
312297

313298
// Launch the native app for previewing (launchMobileApp will show its own spinner)
@@ -316,7 +301,7 @@ export default class LightningDevApp extends SfCommand<void> {
316301
ldpServerUrl,
317302
ldpServerId,
318303
appName,
319-
appId
304+
appId,
320305
);
321306
const targetActivity = (appConfig as AndroidAppPreviewConfig)?.activity;
322307
const targetApp = targetActivity ? `${appConfig.id}/${targetActivity}` : appConfig.id;

0 commit comments

Comments
 (0)