Skip to content

Commit

Permalink
feat: use both http and https ports when configuring local dev server (
Browse files Browse the repository at this point in the history
…#118)

* feat: use both http and https ports when configuring local dev server

* chore: fix yarn lock file

* chore: pr feedback
  • Loading branch information
maliroteh-sf authored Jul 29, 2024
1 parent f85905d commit 71c1682
Show file tree
Hide file tree
Showing 10 changed files with 1,580 additions and 2,089 deletions.
30 changes: 15 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,33 @@
"author": "Salesforce",
"bugs": "https://github.com/forcedotcom/cli/issues",
"dependencies": {
"@lwrjs/api": "0.13.1",
"@lwc/lwc-dev-server": "^9.3.0",
"@lwc/sfdc-lwc-compiler": "^9.3.0",
"@oclif/core": "^4.0.12",
"@salesforce/core": "^8.2.3",
"@lwrjs/api": "0.13.3",
"@lwc/lwc-dev-server": "^9.4.0",
"@lwc/sfdc-lwc-compiler": "^9.4.0",
"@oclif/core": "^4.0.17",
"@salesforce/core": "^8.2.7",
"@salesforce/kit": "^3.1.6",
"@salesforce/lwc-dev-mobile-core": "4.0.0-alpha.5",
"@salesforce/sf-plugins-core": "^11.2.1",
"@inquirer/select": "^2.4.2",
"@salesforce/lwc-dev-mobile-core": "4.0.0-alpha.6",
"@salesforce/sf-plugins-core": "^11.2.4",
"@inquirer/select": "^2.4.3",
"chalk": "^5.3.0",
"lwc": "7.1.2",
"lwc": "7.2.0",
"lwr": "0.13.3",
"node-fetch": "^3.3.2",
"tar": "^7.4.3"
},
"devDependencies": {
"@oclif/plugin-command-snapshot": "^5.2.10",
"@salesforce/cli-plugins-testkit": "^5.3.17",
"@salesforce/dev-scripts": "^10.2.2",
"@salesforce/plugin-command-reference": "^3.1.8",
"@salesforce/cli-plugins-testkit": "^5.3.20",
"@salesforce/dev-scripts": "^10.2.7",
"@salesforce/plugin-command-reference": "^3.1.13",
"@types/node-fetch": "^2.6.11",
"@types/tar": "^6.1.13",
"eslint-plugin-sf-plugin": "^1.18.11",
"eslint-plugin-sf-plugin": "^1.20.1",
"esmock": "^2.6.7",
"oclif": "^4.13.10",
"oclif": "^4.14.12",
"ts-node": "^10.9.2",
"typescript": "^5.5.3"
"typescript": "^5.5.4"
},
"engines": {
"node": ">=18.0.0 <22"
Expand Down
18 changes: 9 additions & 9 deletions src/commands/lightning/preview/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,22 +199,22 @@ export default class LightningPreviewApp extends SfCommand<void> {
}

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

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

const entityId = await PreviewUtils.getEntityId(username);

if (platform === Platform.desktop) {
await this.desktopPreview(sfdxProjectRootPath, serverPort, token, entityId, ldpServerUrl, appId, logger);
await this.desktopPreview(sfdxProjectRootPath, serverPorts, token, entityId, ldpServerUrl, appId, logger);
} else {
await this.mobilePreview(
platform,
sfdxProjectRootPath,
serverPort,
serverPorts,
token,
entityId,
ldpServerUrl,
Expand All @@ -228,7 +228,7 @@ export default class LightningPreviewApp extends SfCommand<void> {

private async desktopPreview(
sfdxProjectRootPath: string,
serverPort: number,
serverPorts: { httpPort: number; httpsPort: number },
token: string,
entityId: string,
ldpServerUrl: string,
Expand Down Expand Up @@ -271,7 +271,7 @@ export default class LightningPreviewApp extends SfCommand<void> {
);

// Start the LWC Dev Server
await startLWCServer(logger, sfdxProjectRootPath, token, serverPort);
await startLWCServer(logger, sfdxProjectRootPath, token, serverPorts);

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

// Start the LWC Dev Server

await startLWCServer(logger, sfdxProjectRootPath, token, serverPort, certData);
await startLWCServer(logger, sfdxProjectRootPath, token, serverPorts, certData);

// Launch the native app for previewing (launchMobileApp will show its own spinner)
// eslint-disable-next-line camelcase
Expand Down
17 changes: 12 additions & 5 deletions src/lwc-dev-server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { Logger } from '@salesforce/core';
import { SSLCertificateData } from '@salesforce/lwc-dev-mobile-core';
import {
ConfigUtils,
LOCAL_DEV_SERVER_DEFAULT_PORT,
LOCAL_DEV_SERVER_DEFAULT_HTTP_PORT,
LOCAL_DEV_SERVER_DEFAULT_WORKSPACE,
} from '../shared/configUtils.js';

Expand Down Expand Up @@ -47,7 +47,7 @@ async function createLWCServerConfig(
logger: Logger,
rootDir: string,
token: string,
serverPort?: number,
serverPorts?: { httpPort: number; httpsPort: number },
certData?: SSLCertificateData,
workspace?: Workspace
): Promise<ServerConfig> {
Expand Down Expand Up @@ -76,10 +76,16 @@ async function createLWCServerConfig(
}
}

const ports = serverPorts ??
(await ConfigUtils.getLocalDevServerPorts()) ?? {
httpPort: LOCAL_DEV_SERVER_DEFAULT_HTTP_PORT,
httpsPort: LOCAL_DEV_SERVER_DEFAULT_HTTP_PORT + 1,
};

const serverConfig: ServerConfig = {
rootDir,
// use custom port if any is provided, or fetch from config file (if any), otherwise use the default port
port: serverPort ?? (await ConfigUtils.getLocalDevServerPort()) ?? LOCAL_DEV_SERVER_DEFAULT_PORT,
port: ports.httpPort,
paths: namespacePaths,
// use custom workspace if any is provided, or fetch from config file (if any), otherwise use the default workspace
workspace: workspace ?? (await ConfigUtils.getLocalDevServerWorkspace()) ?? LOCAL_DEV_SERVER_DEFAULT_WORKSPACE,
Expand All @@ -91,6 +97,7 @@ async function createLWCServerConfig(
serverConfig.https = {
cert: certData.pemCertificate,
key: certData.pemPrivateKey,
port: ports.httpsPort,
};
}

Expand All @@ -101,11 +108,11 @@ export async function startLWCServer(
logger: Logger,
rootDir: string,
token: string,
serverPort?: number,
serverPorts?: { httpPort: number; httpsPort: number },
certData?: SSLCertificateData,
workspace?: Workspace
): Promise<LWCServer> {
const config = await createLWCServerConfig(logger, rootDir, token, serverPort, certData, workspace);
const config = await createLWCServerConfig(logger, rootDir, token, serverPorts, certData, workspace);

logger.trace(`Starting LWC Dev Server with config: ${JSON.stringify(config)}`);
let lwcDevServer: LWCServer | null = await startLwcDevServer(config);
Expand Down
8 changes: 4 additions & 4 deletions src/shared/configUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export type IdentityTokenService = {
saveTokenToServer(token: string): Promise<string>;
};

export const LOCAL_DEV_SERVER_DEFAULT_PORT = 8081;
export const LOCAL_DEV_SERVER_DEFAULT_HTTP_PORT = 8081;
export const LOCAL_DEV_SERVER_DEFAULT_WORKSPACE = Workspace.SfCli;

export type LocalWebServerIdentityData = {
Expand Down Expand Up @@ -104,11 +104,11 @@ export class ConfigUtils {
await config.write();
}

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

return configPort;
return ports;
}

public static async getLocalDevServerWorkspace(): Promise<Workspace | undefined> {
Expand Down
69 changes: 38 additions & 31 deletions src/shared/previewUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import {
} from '@salesforce/lwc-dev-mobile-core';
import { Progress, Spinner } from '@salesforce/sf-plugins-core';
import fetch from 'node-fetch';
import { ConfigUtils, LOCAL_DEV_SERVER_DEFAULT_PORT } from './configUtils.js';
import { ConfigUtils, LOCAL_DEV_SERVER_DEFAULT_HTTP_PORT } from './configUtils.js';

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

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

if (userConfiguredPort) {
return Promise.resolve(userConfiguredPort);
if (userConfiguredPorts) {
return Promise.resolve(userConfiguredPorts);
}

let port = LOCAL_DEV_SERVER_DEFAULT_PORT;
let done = false;

while (!done) {
const cmd =
process.platform === 'win32' ? `netstat -an | find "LISTENING" | find ":${port}"` : `lsof -i :${port}`;
const httpPort = await this.doGetNextAvailablePort(LOCAL_DEV_SERVER_DEFAULT_HTTP_PORT);
const httpsPort = await this.doGetNextAvailablePort(httpPort + 1);

try {
const result = CommonUtils.executeCommandSync(cmd);
if (result.trim()) {
port = port + 2; // that port is in use so try another
} else {
done = true;
}
} catch (error) {
// On some platforms (like mac) if the command doesn't produce
// any results then that is considered an error but in our case
// that means the port is not in use and is ready for us to use.
done = true;
}
}

return Promise.resolve(port);
return Promise.resolve({ httpPort, httpsPort });
}

/**
Expand Down Expand Up @@ -516,4 +497,30 @@ export class PreviewUtils {
return entityId;
}
}

private static async doGetNextAvailablePort(startingPort: number): Promise<number> {
let port = startingPort;
let done = false;

while (!done) {
const cmd =
process.platform === 'win32' ? `netstat -an | find "LISTENING" | find ":${port}"` : `lsof -i :${port}`;

try {
const result = CommonUtils.executeCommandSync(cmd);
if (result.trim()) {
port = port + 2; // that port is in use so try another
} else {
done = true;
}
} catch (error) {
// On some platforms (like mac) if the command doesn't produce
// any results then that is considered an error but in our case
// that means the port is not in use and is ready for us to use.
done = true;
}
}

return Promise.resolve(port);
}
}
5 changes: 4 additions & 1 deletion test/commands/lightning/preview/app.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,10 @@ describe('lightning preview app', () => {
const expectedOutputDir = path.dirname(testBundleArchive);
const expectedFinalBundlePath =
platform === Platform.ios ? path.join(expectedOutputDir, 'Chatter.app') : testBundleArchive;
const expectedLdpServerUrl = PreviewUtils.generateWebSocketUrlForLocalDevServer(platform, '8081');
const expectedLdpServerUrl = PreviewUtils.generateWebSocketUrlForLocalDevServer(platform, {
httpPort: 8081,
httpsPort: 8082,
});
const expectedDeviceId = platform === Platform.ios ? testIOSDevice.udid : testAndroidDevice.name;
const expectedAppConfig =
platform === Platform.ios ? iOSSalesforceAppPreviewConfig : androidSalesforceAppPreviewConfig;
Expand Down
2 changes: 1 addition & 1 deletion test/lwc-dev-server/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ describe('lwc-dev-server', () => {

beforeEach(async () => {
$$.SANDBOX.stub(ConfigUtils, 'getOrCreateIdentityToken').resolves('testIdentityToken');
$$.SANDBOX.stub(ConfigUtils, 'getLocalDevServerPort').resolves(1234);
$$.SANDBOX.stub(ConfigUtils, 'getLocalDevServerPorts').resolves({ httpPort: 1234, httpsPort: 5678 });
$$.SANDBOX.stub(ConfigUtils, 'getLocalDevServerWorkspace').resolves(Workspace.SfCli);
$$.SANDBOX.stub(ConfigUtils, 'getCertData').resolves(undefined);
});
Expand Down
14 changes: 8 additions & 6 deletions test/shared/configUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,22 +140,24 @@ describe('configUtils', () => {
expect(resolved).to.equal(undefined);
});

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

expect(resolved).to.be.undefined;
});

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

expect(resolved).to.equal(123);
expect(resolved).to.deep.equal({ httpPort: 123, httpsPort: 456 });
});

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

0 comments on commit 71c1682

Please sign in to comment.