Skip to content

Commit 71c1682

Browse files
authored
feat: use both http and https ports when configuring local dev server (#118)
* feat: use both http and https ports when configuring local dev server * chore: fix yarn lock file * chore: pr feedback
1 parent f85905d commit 71c1682

File tree

10 files changed

+1580
-2089
lines changed

10 files changed

+1580
-2089
lines changed

package.json

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,33 +5,33 @@
55
"author": "Salesforce",
66
"bugs": "https://github.com/forcedotcom/cli/issues",
77
"dependencies": {
8-
"@lwrjs/api": "0.13.1",
9-
"@lwc/lwc-dev-server": "^9.3.0",
10-
"@lwc/sfdc-lwc-compiler": "^9.3.0",
11-
"@oclif/core": "^4.0.12",
12-
"@salesforce/core": "^8.2.3",
8+
"@lwrjs/api": "0.13.3",
9+
"@lwc/lwc-dev-server": "^9.4.0",
10+
"@lwc/sfdc-lwc-compiler": "^9.4.0",
11+
"@oclif/core": "^4.0.17",
12+
"@salesforce/core": "^8.2.7",
1313
"@salesforce/kit": "^3.1.6",
14-
"@salesforce/lwc-dev-mobile-core": "4.0.0-alpha.5",
15-
"@salesforce/sf-plugins-core": "^11.2.1",
16-
"@inquirer/select": "^2.4.2",
14+
"@salesforce/lwc-dev-mobile-core": "4.0.0-alpha.6",
15+
"@salesforce/sf-plugins-core": "^11.2.4",
16+
"@inquirer/select": "^2.4.3",
1717
"chalk": "^5.3.0",
18-
"lwc": "7.1.2",
18+
"lwc": "7.2.0",
1919
"lwr": "0.13.3",
2020
"node-fetch": "^3.3.2",
2121
"tar": "^7.4.3"
2222
},
2323
"devDependencies": {
2424
"@oclif/plugin-command-snapshot": "^5.2.10",
25-
"@salesforce/cli-plugins-testkit": "^5.3.17",
26-
"@salesforce/dev-scripts": "^10.2.2",
27-
"@salesforce/plugin-command-reference": "^3.1.8",
25+
"@salesforce/cli-plugins-testkit": "^5.3.20",
26+
"@salesforce/dev-scripts": "^10.2.7",
27+
"@salesforce/plugin-command-reference": "^3.1.13",
2828
"@types/node-fetch": "^2.6.11",
2929
"@types/tar": "^6.1.13",
30-
"eslint-plugin-sf-plugin": "^1.18.11",
30+
"eslint-plugin-sf-plugin": "^1.20.1",
3131
"esmock": "^2.6.7",
32-
"oclif": "^4.13.10",
32+
"oclif": "^4.14.12",
3333
"ts-node": "^10.9.2",
34-
"typescript": "^5.5.3"
34+
"typescript": "^5.5.4"
3535
},
3636
"engines": {
3737
"node": ">=18.0.0 <22"

src/commands/lightning/preview/app.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -199,22 +199,22 @@ export default class LightningPreviewApp extends SfCommand<void> {
199199
}
200200

201201
logger.debug('Determining the next available port for Local Dev Server');
202-
const serverPort = await PreviewUtils.getNextAvailablePort();
203-
logger.debug(`Next available port is ${serverPort}`);
202+
const serverPorts = await PreviewUtils.getNextAvailablePorts();
203+
logger.debug(`Next available ports are http=${serverPorts.httpPort} , https=${serverPorts.httpsPort}`);
204204

205205
logger.debug('Determining Local Dev Server url');
206-
const ldpServerUrl = PreviewUtils.generateWebSocketUrlForLocalDevServer(platform, serverPort, logger);
206+
const ldpServerUrl = PreviewUtils.generateWebSocketUrlForLocalDevServer(platform, serverPorts, logger);
207207
logger.debug(`Local Dev Server url is ${ldpServerUrl}`);
208208

209209
const entityId = await PreviewUtils.getEntityId(username);
210210

211211
if (platform === Platform.desktop) {
212-
await this.desktopPreview(sfdxProjectRootPath, serverPort, token, entityId, ldpServerUrl, appId, logger);
212+
await this.desktopPreview(sfdxProjectRootPath, serverPorts, token, entityId, ldpServerUrl, appId, logger);
213213
} else {
214214
await this.mobilePreview(
215215
platform,
216216
sfdxProjectRootPath,
217-
serverPort,
217+
serverPorts,
218218
token,
219219
entityId,
220220
ldpServerUrl,
@@ -228,7 +228,7 @@ export default class LightningPreviewApp extends SfCommand<void> {
228228

229229
private async desktopPreview(
230230
sfdxProjectRootPath: string,
231-
serverPort: number,
231+
serverPorts: { httpPort: number; httpsPort: number },
232232
token: string,
233233
entityId: string,
234234
ldpServerUrl: string,
@@ -271,7 +271,7 @@ export default class LightningPreviewApp extends SfCommand<void> {
271271
);
272272

273273
// Start the LWC Dev Server
274-
await startLWCServer(logger, sfdxProjectRootPath, token, serverPort);
274+
await startLWCServer(logger, sfdxProjectRootPath, token, serverPorts);
275275

276276
// Open the browser and navigate to the right page
277277
await this.config.runCommand('org:open', launchArguments);
@@ -280,7 +280,7 @@ export default class LightningPreviewApp extends SfCommand<void> {
280280
private async mobilePreview(
281281
platform: Platform.ios | Platform.android,
282282
sfdxProjectRootPath: string,
283-
serverPort: number,
283+
serverPorts: { httpPort: number; httpsPort: number },
284284
token: string,
285285
entityId: string,
286286
ldpServerUrl: string,
@@ -357,7 +357,7 @@ export default class LightningPreviewApp extends SfCommand<void> {
357357

358358
// Start the LWC Dev Server
359359

360-
await startLWCServer(logger, sfdxProjectRootPath, token, serverPort, certData);
360+
await startLWCServer(logger, sfdxProjectRootPath, token, serverPorts, certData);
361361

362362
// Launch the native app for previewing (launchMobileApp will show its own spinner)
363363
// eslint-disable-next-line camelcase

src/lwc-dev-server/index.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { Logger } from '@salesforce/core';
1313
import { SSLCertificateData } from '@salesforce/lwc-dev-mobile-core';
1414
import {
1515
ConfigUtils,
16-
LOCAL_DEV_SERVER_DEFAULT_PORT,
16+
LOCAL_DEV_SERVER_DEFAULT_HTTP_PORT,
1717
LOCAL_DEV_SERVER_DEFAULT_WORKSPACE,
1818
} from '../shared/configUtils.js';
1919

@@ -47,7 +47,7 @@ async function createLWCServerConfig(
4747
logger: Logger,
4848
rootDir: string,
4949
token: string,
50-
serverPort?: number,
50+
serverPorts?: { httpPort: number; httpsPort: number },
5151
certData?: SSLCertificateData,
5252
workspace?: Workspace
5353
): Promise<ServerConfig> {
@@ -76,10 +76,16 @@ async function createLWCServerConfig(
7676
}
7777
}
7878

79+
const ports = serverPorts ??
80+
(await ConfigUtils.getLocalDevServerPorts()) ?? {
81+
httpPort: LOCAL_DEV_SERVER_DEFAULT_HTTP_PORT,
82+
httpsPort: LOCAL_DEV_SERVER_DEFAULT_HTTP_PORT + 1,
83+
};
84+
7985
const serverConfig: ServerConfig = {
8086
rootDir,
8187
// use custom port if any is provided, or fetch from config file (if any), otherwise use the default port
82-
port: serverPort ?? (await ConfigUtils.getLocalDevServerPort()) ?? LOCAL_DEV_SERVER_DEFAULT_PORT,
88+
port: ports.httpPort,
8389
paths: namespacePaths,
8490
// use custom workspace if any is provided, or fetch from config file (if any), otherwise use the default workspace
8591
workspace: workspace ?? (await ConfigUtils.getLocalDevServerWorkspace()) ?? LOCAL_DEV_SERVER_DEFAULT_WORKSPACE,
@@ -91,6 +97,7 @@ async function createLWCServerConfig(
9197
serverConfig.https = {
9298
cert: certData.pemCertificate,
9399
key: certData.pemPrivateKey,
100+
port: ports.httpsPort,
94101
};
95102
}
96103

@@ -101,11 +108,11 @@ export async function startLWCServer(
101108
logger: Logger,
102109
rootDir: string,
103110
token: string,
104-
serverPort?: number,
111+
serverPorts?: { httpPort: number; httpsPort: number },
105112
certData?: SSLCertificateData,
106113
workspace?: Workspace
107114
): Promise<LWCServer> {
108-
const config = await createLWCServerConfig(logger, rootDir, token, serverPort, certData, workspace);
115+
const config = await createLWCServerConfig(logger, rootDir, token, serverPorts, certData, workspace);
109116

110117
logger.trace(`Starting LWC Dev Server with config: ${JSON.stringify(config)}`);
111118
let lwcDevServer: LWCServer | null = await startLwcDevServer(config);

src/shared/configUtils.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export type IdentityTokenService = {
1414
saveTokenToServer(token: string): Promise<string>;
1515
};
1616

17-
export const LOCAL_DEV_SERVER_DEFAULT_PORT = 8081;
17+
export const LOCAL_DEV_SERVER_DEFAULT_HTTP_PORT = 8081;
1818
export const LOCAL_DEV_SERVER_DEFAULT_WORKSPACE = Workspace.SfCli;
1919

2020
export type LocalWebServerIdentityData = {
@@ -104,11 +104,11 @@ export class ConfigUtils {
104104
await config.write();
105105
}
106106

107-
public static async getLocalDevServerPort(): Promise<number | undefined> {
107+
public static async getLocalDevServerPorts(): Promise<{ httpPort: number; httpsPort: number } | undefined> {
108108
const config = await this.getLocalConfig();
109-
const configPort = config.get(ConfigVars.LOCAL_DEV_SERVER_PORT) as number;
109+
const ports = config.get(ConfigVars.LOCAL_DEV_SERVER_PORT) as { httpPort: number; httpsPort: number };
110110

111-
return configPort;
111+
return ports;
112112
}
113113

114114
public static async getLocalDevServerWorkspace(): Promise<Workspace | undefined> {

src/shared/previewUtils.ts

Lines changed: 38 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import {
3131
} from '@salesforce/lwc-dev-mobile-core';
3232
import { Progress, Spinner } from '@salesforce/sf-plugins-core';
3333
import fetch from 'node-fetch';
34-
import { ConfigUtils, LOCAL_DEV_SERVER_DEFAULT_PORT } from './configUtils.js';
34+
import { ConfigUtils, LOCAL_DEV_SERVER_DEFAULT_HTTP_PORT } from './configUtils.js';
3535

3636
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
3737
const messages = Messages.loadMessages('@salesforce/plugin-lightning-dev', 'lightning.preview.app');
@@ -40,14 +40,14 @@ const DevPreviewAuraMode = 'DEVPREVIEW';
4040
export class PreviewUtils {
4141
public static generateWebSocketUrlForLocalDevServer(
4242
platform: string,
43-
port: string | number,
43+
ports: { httpPort: number; httpsPort: number },
4444
logger?: Logger
4545
): string {
46-
return LwcDevMobileCorePreviewUtils.generateWebSocketUrlForLocalDevServer(platform, port.toString(), logger);
46+
return LwcDevMobileCorePreviewUtils.generateWebSocketUrlForLocalDevServer(platform, ports, logger);
4747
}
4848

4949
/**
50-
* Returns a port number to be used by the local dev server.
50+
* Returns a pair of port numbers to be used by the local dev server for http and https.
5151
*
5252
* It starts by checking whether the user has configured a port in their config file.
5353
* If so then we are only allowed to use that port, regardless of whether it is in use
@@ -58,38 +58,19 @@ export class PreviewUtils {
5858
* If it is in use then we increment the port number by 2 and check if it is in use or not.
5959
* This process is repeated until a port that is not in use is found.
6060
*
61-
* @returns a port number to be used by the local dev server.
61+
* @returns a pair of port numbers to be used by the local dev server for http and https.
6262
*/
63-
public static async getNextAvailablePort(): Promise<number> {
64-
const userConfiguredPort = await ConfigUtils.getLocalDevServerPort();
63+
public static async getNextAvailablePorts(): Promise<{ httpPort: number; httpsPort: number }> {
64+
const userConfiguredPorts = await ConfigUtils.getLocalDevServerPorts();
6565

66-
if (userConfiguredPort) {
67-
return Promise.resolve(userConfiguredPort);
66+
if (userConfiguredPorts) {
67+
return Promise.resolve(userConfiguredPorts);
6868
}
6969

70-
let port = LOCAL_DEV_SERVER_DEFAULT_PORT;
71-
let done = false;
72-
73-
while (!done) {
74-
const cmd =
75-
process.platform === 'win32' ? `netstat -an | find "LISTENING" | find ":${port}"` : `lsof -i :${port}`;
70+
const httpPort = await this.doGetNextAvailablePort(LOCAL_DEV_SERVER_DEFAULT_HTTP_PORT);
71+
const httpsPort = await this.doGetNextAvailablePort(httpPort + 1);
7672

77-
try {
78-
const result = CommonUtils.executeCommandSync(cmd);
79-
if (result.trim()) {
80-
port = port + 2; // that port is in use so try another
81-
} else {
82-
done = true;
83-
}
84-
} catch (error) {
85-
// On some platforms (like mac) if the command doesn't produce
86-
// any results then that is considered an error but in our case
87-
// that means the port is not in use and is ready for us to use.
88-
done = true;
89-
}
90-
}
91-
92-
return Promise.resolve(port);
73+
return Promise.resolve({ httpPort, httpsPort });
9374
}
9475

9576
/**
@@ -516,4 +497,30 @@ export class PreviewUtils {
516497
return entityId;
517498
}
518499
}
500+
501+
private static async doGetNextAvailablePort(startingPort: number): Promise<number> {
502+
let port = startingPort;
503+
let done = false;
504+
505+
while (!done) {
506+
const cmd =
507+
process.platform === 'win32' ? `netstat -an | find "LISTENING" | find ":${port}"` : `lsof -i :${port}`;
508+
509+
try {
510+
const result = CommonUtils.executeCommandSync(cmd);
511+
if (result.trim()) {
512+
port = port + 2; // that port is in use so try another
513+
} else {
514+
done = true;
515+
}
516+
} catch (error) {
517+
// On some platforms (like mac) if the command doesn't produce
518+
// any results then that is considered an error but in our case
519+
// that means the port is not in use and is ready for us to use.
520+
done = true;
521+
}
522+
}
523+
524+
return Promise.resolve(port);
525+
}
519526
}

test/commands/lightning/preview/app.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,10 @@ describe('lightning preview app', () => {
438438
const expectedOutputDir = path.dirname(testBundleArchive);
439439
const expectedFinalBundlePath =
440440
platform === Platform.ios ? path.join(expectedOutputDir, 'Chatter.app') : testBundleArchive;
441-
const expectedLdpServerUrl = PreviewUtils.generateWebSocketUrlForLocalDevServer(platform, '8081');
441+
const expectedLdpServerUrl = PreviewUtils.generateWebSocketUrlForLocalDevServer(platform, {
442+
httpPort: 8081,
443+
httpsPort: 8082,
444+
});
442445
const expectedDeviceId = platform === Platform.ios ? testIOSDevice.udid : testAndroidDevice.name;
443446
const expectedAppConfig =
444447
platform === Platform.ios ? iOSSalesforceAppPreviewConfig : androidSalesforceAppPreviewConfig;

test/lwc-dev-server/index.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ describe('lwc-dev-server', () => {
4141

4242
beforeEach(async () => {
4343
$$.SANDBOX.stub(ConfigUtils, 'getOrCreateIdentityToken').resolves('testIdentityToken');
44-
$$.SANDBOX.stub(ConfigUtils, 'getLocalDevServerPort').resolves(1234);
44+
$$.SANDBOX.stub(ConfigUtils, 'getLocalDevServerPorts').resolves({ httpPort: 1234, httpsPort: 5678 });
4545
$$.SANDBOX.stub(ConfigUtils, 'getLocalDevServerWorkspace').resolves(Workspace.SfCli);
4646
$$.SANDBOX.stub(ConfigUtils, 'getCertData').resolves(undefined);
4747
});

test/shared/configUtils.test.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -140,22 +140,24 @@ describe('configUtils', () => {
140140
expect(resolved).to.equal(undefined);
141141
});
142142

143-
it('getLocalDevServerPort returns undefined when value not found in config', async () => {
143+
it('getLocalDevServerPorts returns undefined when value not found in config', async () => {
144144
$$.SANDBOX.stub(Config, 'create').withArgs($$.SANDBOX.match.any).resolves(Config.prototype);
145145
$$.SANDBOX.stub(Config, 'addAllowedProperties').withArgs($$.SANDBOX.match.any);
146146
$$.SANDBOX.stub(Config.prototype, 'get').withArgs(ConfigVars.LOCAL_DEV_SERVER_PORT).returns(undefined);
147-
const resolved = await ConfigUtils.getLocalDevServerPort();
147+
const resolved = await ConfigUtils.getLocalDevServerPorts();
148148

149149
expect(resolved).to.be.undefined;
150150
});
151151

152-
it('getLocalDevServerPort resolves to port value in config', async () => {
152+
it('getLocalDevServerPorts resolves to port values in config', async () => {
153153
$$.SANDBOX.stub(Config, 'create').withArgs($$.SANDBOX.match.any).resolves(Config.prototype);
154154
$$.SANDBOX.stub(Config, 'addAllowedProperties').withArgs($$.SANDBOX.match.any);
155-
$$.SANDBOX.stub(Config.prototype, 'get').withArgs(ConfigVars.LOCAL_DEV_SERVER_PORT).returns(123);
156-
const resolved = await ConfigUtils.getLocalDevServerPort();
155+
$$.SANDBOX.stub(Config.prototype, 'get')
156+
.withArgs(ConfigVars.LOCAL_DEV_SERVER_PORT)
157+
.returns({ httpPort: 123, httpsPort: 456 });
158+
const resolved = await ConfigUtils.getLocalDevServerPorts();
157159

158-
expect(resolved).to.equal(123);
160+
expect(resolved).to.deep.equal({ httpPort: 123, httpsPort: 456 });
159161
});
160162

161163
it('getLocalDevServerWorkspace returns undefined when value not found in config', async () => {

0 commit comments

Comments
 (0)