From 120fc415ad5974641c3aff48bd2cacfaa116b703 Mon Sep 17 00:00:00 2001 From: Mohammed Abdul Sattar Date: Thu, 10 Jul 2025 12:36:07 -0400 Subject: [PATCH 1/3] feat: launch site preview @W-18283821 --- command-snapshot.json | 2 +- messages/lightning.dev.app.md | 16 ------- messages/lightning.dev.site.md | 4 ++ messages/shared.utils.md | 16 +++++++ package.json | 1 + src/commands/lightning/dev/app.ts | 8 ++-- src/commands/lightning/dev/site.ts | 51 ++++++++++++++++++++- src/shared/experience/expSite.ts | 41 +++++++++++++++++ src/shared/previewUtils.ts | 4 +- test/commands/lightning/dev/app.test.ts | 2 +- yarn.lock | 59 +++++++++++++++++++++++++ 11 files changed, 179 insertions(+), 25 deletions(-) diff --git a/command-snapshot.json b/command-snapshot.json index 6112ba48..aa16dcf4 100644 --- a/command-snapshot.json +++ b/command-snapshot.json @@ -20,7 +20,7 @@ "command": "lightning:dev:site", "flagAliases": [], "flagChars": ["l", "n", "o"], - "flags": ["flags-dir", "get-latest", "guest", "name", "target-org"], + "flags": ["flags-dir", "get-latest", "guest", "name", "target-org", "ssr"], "plugin": "@salesforce/plugin-lightning-dev" } ] diff --git a/messages/lightning.dev.app.md b/messages/lightning.dev.app.md index 273f9851..ce3765f9 100644 --- a/messages/lightning.dev.app.md +++ b/messages/lightning.dev.app.md @@ -31,22 +31,6 @@ Type of device to display the app preview. 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. -# error.username - -Org must have a valid user - -# error.identitydata - -Couldn't find identity data while generating preview arguments - -# error.identitydata.entityid - -Couldn't find entity ID while generating preview arguments - -# error.no-project - -This command is required to run from within a Salesforce project directory. %s - # error.fetching.app-id Unable to determine App Id for %s diff --git a/messages/lightning.dev.site.md b/messages/lightning.dev.site.md index d8b7baad..20fd1011 100644 --- a/messages/lightning.dev.site.md +++ b/messages/lightning.dev.site.md @@ -29,6 +29,10 @@ Download the latest version of the specified site from your org, instead of usin Preview the site as a guest user (rather than an authenticated user). +# flags.ssr.summary + +Preview the SSR bundle + # examples - Select a site to preview from the org "myOrg": diff --git a/messages/shared.utils.md b/messages/shared.utils.md index 41376318..165eed20 100644 --- a/messages/shared.utils.md +++ b/messages/shared.utils.md @@ -37,3 +37,19 @@ Your org is on API version %s, but this version of the CLI plugin supports API v # error.org.api-mismatch.remediation To use the plugin with this org, you can reinstall or update the plugin using the "%s" tag. For example: "sf plugins install %s". + +# error.username + +Org must have a valid user + +# error.identitydata + +Couldn't find identity data while generating preview arguments + +# error.identitydata.entityid + +Couldn't find entity ID while generating preview arguments + +# error.no-project + +This command is required to run from within a Salesforce project directory. %s diff --git a/package.json b/package.json index 46ebe370..ed18dd89 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "lightning-base-components": "1.27.2-alpha", "lwc": "~8.20.1", "node-fetch": "^3.3.2", + "open": "^10.1.0", "xml2js": "^0.6.2" }, "devDependencies": { diff --git a/src/commands/lightning/dev/app.ts b/src/commands/lightning/dev/app.ts index 48352de7..e1bf9e92 100644 --- a/src/commands/lightning/dev/app.ts +++ b/src/commands/lightning/dev/app.ts @@ -74,18 +74,18 @@ export default class LightningDevApp extends SfCommand { try { sfdxProjectRootPath = await SfProject.resolveProjectPath(); } catch (error) { - return Promise.reject(new Error(messages.getMessage('error.no-project', [(error as Error)?.message ?? '']))); + throw new Error(sharedMessages.getMessage('error.no-project', [(error as Error)?.message ?? ''])); } const connection = targetOrg.getConnection(undefined); const username = connection.getUsername(); if (!username) { - return Promise.reject(new Error(messages.getMessage('error.username'))); + throw new Error(sharedMessages.getMessage('error.username')); } const localDevEnabled = await OrgUtils.isLocalDevEnabled(connection); if (!localDevEnabled) { - return Promise.reject(new Error(sharedMessages.getMessage('error.localdev.not.enabled'))); + throw new Error(sharedMessages.getMessage('error.localdev.not.enabled')); } OrgUtils.ensureMatchingAPIVersion(connection); @@ -97,7 +97,7 @@ export default class LightningDevApp extends SfCommand { const ldpServerToken = appServerIdentity.identityToken; const ldpServerId = appServerIdentity.usernameToServerEntityIdMap[username]; if (!ldpServerId) { - return Promise.reject(new Error(messages.getMessage('error.identitydata.entityid'))); + throw new Error(sharedMessages.getMessage('error.identitydata.entityid')); } const appId = await PreviewUtils.getLightningExperienceAppId(connection, appName, logger); diff --git a/src/commands/lightning/dev/site.ts b/src/commands/lightning/dev/site.ts index 8977e98e..7d5a32e7 100644 --- a/src/commands/lightning/dev/site.ts +++ b/src/commands/lightning/dev/site.ts @@ -6,11 +6,15 @@ */ import fs from 'node:fs'; import { SfCommand, Flags } from '@salesforce/sf-plugins-core'; -import { Messages } from '@salesforce/core'; +import { Logger, Messages, SfProject } from '@salesforce/core'; +import { Platform } from '@salesforce/lwc-dev-mobile-core'; import { expDev, SitesLocalDevOptions, setupDev } from '@lwrjs/api'; +import open from 'open'; import { OrgUtils } from '../../../shared/orgUtils.js'; import { PromptUtils } from '../../../shared/promptUtils.js'; import { ExperienceSite } from '../../../shared/experience/expSite.js'; +import { PreviewUtils } from '../../../shared/previewUtils.js'; +import { startLWCServer } from '../../../lwc-dev-server/index.js'; Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); const messages = Messages.loadMessages('@salesforce/plugin-lightning-dev', 'lightning.dev.site'); @@ -36,6 +40,10 @@ export default class LightningDevSite extends SfCommand { summary: messages.getMessage('flags.guest.summary'), default: false, }), + ssr: Flags.boolean({ + summary: messages.getMessage('flags.ssr.summary'), + default: false, + }), }; public async run(): Promise { @@ -45,6 +53,7 @@ export default class LightningDevSite extends SfCommand { const org = flags['target-org']; const getLatest = flags['get-latest']; const guest = flags.guest; + const ssr = flags.ssr; let siteName = flags.name; const connection = org.getConnection(undefined); @@ -63,6 +72,46 @@ export default class LightningDevSite extends SfCommand { } const selectedSite = new ExperienceSite(org, siteName); + + if (!ssr) { + let sfdxProjectRootPath = ''; + try { + sfdxProjectRootPath = await SfProject.resolveProjectPath(); + } catch (error) { + throw new Error(sharedMessages.getMessage('error.no-project', [(error as Error)?.message ?? ''])); + } + const previewUrl = await selectedSite.getPreviewUrl(); + const username = connection.getUsername(); + if (!username) { + throw new Error(sharedMessages.getMessage('error.username')); + } + + this.log('Configuring local web server identity'); + const appServerIdentity = await PreviewUtils.getOrCreateAppServerIdentity(connection); + const ldpServerToken = appServerIdentity.identityToken; + const ldpServerId = appServerIdentity.usernameToServerEntityIdMap[username]; + if (!ldpServerId) { + throw new Error(sharedMessages.getMessage('error.identitydata.entityid')); + } + + this.log('Determining the next available port for Local Dev Server'); + const serverPorts = await PreviewUtils.getNextAvailablePorts(); + this.log(`Next available ports are http=${serverPorts.httpPort} , https=${serverPorts.httpsPort}`); + + this.log('Determining Local Dev Server url'); + const ldpServerUrl = PreviewUtils.generateWebSocketUrlForLocalDevServer(Platform.desktop, serverPorts); + this.log(`Local Dev Server url is ${ldpServerUrl}`); + + const logger = await Logger.child(this.ctor.name); + await startLWCServer(logger, sfdxProjectRootPath, ldpServerToken, Platform.desktop, serverPorts); + const url = new URL(previewUrl); + url.searchParams.set('aura.lwcDevServerUrl', ldpServerUrl); + url.searchParams.set('aura.lwcDevServerId', ldpServerId); + url.searchParams.set('lwc.mode', 'dev'); + await open(url.toString()); + return; + } + let siteZip: string | undefined; // If the site is not setup / is not based on the current release / or get-latest is requested -> diff --git a/src/shared/experience/expSite.ts b/src/shared/experience/expSite.ts index 001d9da4..a350d544 100644 --- a/src/shared/experience/expSite.ts +++ b/src/shared/experience/expSite.ts @@ -207,6 +207,47 @@ export class ExperienceSite { return retVal; } + public async getPreviewUrl(): Promise { + // Get the community ID + const communityId = await this.getNetworkId(); + const conn = this.org.getConnection(); + const accessToken = conn.accessToken; + const instanceUrl = conn.instanceUrl; + + if (!accessToken) { + throw new SfError(`Invalid access token, unable to get preview URL for: ${this.siteDisplayName}`); + } + + try { + // Call the communities API to get the preview URL + const apiUrl = `${instanceUrl}/services/data/v64.0/connect/communities/${communityId}/preview-url/pages/Home`; + const response = await axios.get<{ previewUrl: string }>(apiUrl, { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }); + + if (response.data?.previewUrl) { + return response.data.previewUrl; + } else { + throw new SfError(`Invalid response from communities API for site: ${this.siteDisplayName}`); + } + } catch (error) { + // Handle axios errors + if (axios.isAxiosError(error)) { + if (error.response) { + // Server responded with non-200 status + throw new SfError( + `Failed to get preview URL: Server responded with status ${error.response.status} - ${error.response.statusText}` + ); + } else if (error.request) { + // Request was made but no response received + throw new SfError('Failed to get preview URL: No response received from server'); + } + } + throw new SfError(`Failed to get preview URL for site: ${this.siteDisplayName}`); + } + } /** * Generate a site bundle on demand and download it * diff --git a/src/shared/previewUtils.ts b/src/shared/previewUtils.ts index c70ef718..aa7e573a 100644 --- a/src/shared/previewUtils.ts +++ b/src/shared/previewUtils.ts @@ -62,13 +62,13 @@ export class PreviewUtils { const userConfiguredPorts = await ConfigUtils.getLocalDevServerPorts(); if (userConfiguredPorts) { - return Promise.resolve(userConfiguredPorts); + return userConfiguredPorts; } const httpPort = await this.doGetNextAvailablePort(LOCAL_DEV_SERVER_DEFAULT_HTTP_PORT); const httpsPort = await this.doGetNextAvailablePort(httpPort + 1); - return Promise.resolve({ httpPort, httpsPort }); + return { httpPort, httpsPort }; } /** diff --git a/test/commands/lightning/dev/app.test.ts b/test/commands/lightning/dev/app.test.ts index a31c5163..e88136ac 100644 --- a/test/commands/lightning/dev/app.test.ts +++ b/test/commands/lightning/dev/app.test.ts @@ -134,7 +134,7 @@ describe('lightning dev app', () => { $$.SANDBOX.stub(Connection.prototype, 'getUsername').returns(undefined); await MockedLightningPreviewApp.run(['--name', 'blah', '-o', testOrgData.username, '-t', Platform.desktop]); } catch (err) { - expect(err).to.be.an('error').with.property('message', messages.getMessage('error.username')); + expect(err).to.be.an('error').with.property('message', sharedMessages.getMessage('error.username')); } }); diff --git a/yarn.lock b/yarn.lock index 64f748e4..cc845661 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5266,6 +5266,13 @@ builtin-modules@^3.3.0: resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== +bundle-name@^4.1.0: + version "4.1.0" + resolved "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/bundle-name/-/bundle-name-4.1.0.tgz#f3b96b34160d6431a19d7688135af7cfb8797889" + integrity sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q== + dependencies: + run-applescript "^7.0.0" + bytes@3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" @@ -5933,6 +5940,19 @@ deepmerge@^4.2.2: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== +default-browser-id@^5.0.0: + version "5.0.0" + resolved "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/default-browser-id/-/default-browser-id-5.0.0.tgz#a1d98bf960c15082d8a3fa69e83150ccccc3af26" + integrity sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA== + +default-browser@^5.2.1: + version "5.2.1" + resolved "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/default-browser/-/default-browser-5.2.1.tgz#7b7ba61204ff3e425b556869ae6d3e9d9f1712cf" + integrity sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg== + dependencies: + bundle-name "^4.1.0" + default-browser-id "^5.0.0" + default-require-extensions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-3.0.1.tgz#bfae00feeaeada68c2ae256c62540f60b80625bd" @@ -5954,6 +5974,11 @@ define-data-property@^1.0.1, define-data-property@^1.1.4: es-errors "^1.3.0" gopd "^1.0.1" +define-lazy-prop@^3.0.0: + version "3.0.0" + resolved "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f" + integrity sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg== + define-properties@^1.2.0, define-properties@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" @@ -8013,6 +8038,11 @@ is-docker@^2.0.0: resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== +is-docker@^3.0.0: + version "3.0.0" + resolved "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/is-docker/-/is-docker-3.0.0.tgz#90093aa3106277d8a77a5910dbae71747e15a200" + integrity sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ== + is-extendable@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" @@ -8074,6 +8104,13 @@ is-in-ci@^0.1.0: resolved "https://registry.yarnpkg.com/is-in-ci/-/is-in-ci-0.1.0.tgz#5e07d6a02ec3a8292d3f590973357efa3fceb0d3" integrity sha512-d9PXLEY0v1iJ64xLiQMJ51J128EYHAaOR4yZqQi8aHGfw6KgifM3/Viw1oZZ1GCVmb3gBuyhLyHj0HgR2DhSXQ== +is-inside-container@^1.0.0: + version "1.0.0" + resolved "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/is-inside-container/-/is-inside-container-1.0.0.tgz#e81fba699662eb31dbdaf26766a61d4814717ea4" + integrity sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA== + dependencies: + is-docker "^3.0.0" + is-map@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" @@ -8232,6 +8269,13 @@ is-wsl@^2.2.0: dependencies: is-docker "^2.0.0" +is-wsl@^3.1.0: + version "3.1.0" + resolved "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/is-wsl/-/is-wsl-3.1.0.tgz#e1c657e39c10090afcbedec61720f6b924c3cbd2" + integrity sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw== + dependencies: + is-inside-container "^1.0.0" + isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" @@ -9666,6 +9710,16 @@ only@~0.0.2: resolved "https://registry.yarnpkg.com/only/-/only-0.0.2.tgz#2afde84d03e50b9a8edc444e30610a70295edfb4" integrity sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ== +open@^10.1.0: + version "10.1.2" + resolved "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/open/-/open-10.1.2.tgz#d5df40984755c9a9c3c93df8156a12467e882925" + integrity sha512-cxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+BZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw== + dependencies: + default-browser "^5.2.1" + define-lazy-prop "^3.0.0" + is-inside-container "^1.0.0" + is-wsl "^3.1.0" + optionator@^0.9.3: version "0.9.4" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" @@ -10541,6 +10595,11 @@ rollup@^2.79.2: optionalDependencies: fsevents "~2.3.2" +run-applescript@^7.0.0: + version "7.0.0" + resolved "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/run-applescript/-/run-applescript-7.0.0.tgz#e5a553c2bffd620e169d276c1cd8f1b64778fbeb" + integrity sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A== + run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" From b3cc9eef372be98d9ae40ad95eb3ad0e739c5bfe Mon Sep 17 00:00:00 2001 From: Mohammed Abdul Sattar Date: Thu, 10 Jul 2025 12:45:50 -0400 Subject: [PATCH 2/3] fix: yarn --- yarn.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/yarn.lock b/yarn.lock index cc845661..6b974c96 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5268,7 +5268,7 @@ builtin-modules@^3.3.0: bundle-name@^4.1.0: version "4.1.0" - resolved "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/bundle-name/-/bundle-name-4.1.0.tgz#f3b96b34160d6431a19d7688135af7cfb8797889" + resolved "https://registry.yarnpkg.com/bundle-name/-/bundle-name-4.1.0.tgz#f3b96b34160d6431a19d7688135af7cfb8797889" integrity sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q== dependencies: run-applescript "^7.0.0" @@ -5942,12 +5942,12 @@ deepmerge@^4.2.2: default-browser-id@^5.0.0: version "5.0.0" - resolved "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/default-browser-id/-/default-browser-id-5.0.0.tgz#a1d98bf960c15082d8a3fa69e83150ccccc3af26" + resolved "https://registry.yarnpkg.com/default-browser-id/-/default-browser-id-5.0.0.tgz#a1d98bf960c15082d8a3fa69e83150ccccc3af26" integrity sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA== default-browser@^5.2.1: version "5.2.1" - resolved "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/default-browser/-/default-browser-5.2.1.tgz#7b7ba61204ff3e425b556869ae6d3e9d9f1712cf" + resolved "https://registry.yarnpkg.com/default-browser/-/default-browser-5.2.1.tgz#7b7ba61204ff3e425b556869ae6d3e9d9f1712cf" integrity sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg== dependencies: bundle-name "^4.1.0" @@ -5976,7 +5976,7 @@ define-data-property@^1.0.1, define-data-property@^1.1.4: define-lazy-prop@^3.0.0: version "3.0.0" - resolved "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f" + resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f" integrity sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg== define-properties@^1.2.0, define-properties@^1.2.1: @@ -8040,7 +8040,7 @@ is-docker@^2.0.0: is-docker@^3.0.0: version "3.0.0" - resolved "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/is-docker/-/is-docker-3.0.0.tgz#90093aa3106277d8a77a5910dbae71747e15a200" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-3.0.0.tgz#90093aa3106277d8a77a5910dbae71747e15a200" integrity sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ== is-extendable@^0.1.0: @@ -8106,7 +8106,7 @@ is-in-ci@^0.1.0: is-inside-container@^1.0.0: version "1.0.0" - resolved "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/is-inside-container/-/is-inside-container-1.0.0.tgz#e81fba699662eb31dbdaf26766a61d4814717ea4" + resolved "https://registry.yarnpkg.com/is-inside-container/-/is-inside-container-1.0.0.tgz#e81fba699662eb31dbdaf26766a61d4814717ea4" integrity sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA== dependencies: is-docker "^3.0.0" @@ -8271,7 +8271,7 @@ is-wsl@^2.2.0: is-wsl@^3.1.0: version "3.1.0" - resolved "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/is-wsl/-/is-wsl-3.1.0.tgz#e1c657e39c10090afcbedec61720f6b924c3cbd2" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-3.1.0.tgz#e1c657e39c10090afcbedec61720f6b924c3cbd2" integrity sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw== dependencies: is-inside-container "^1.0.0" @@ -9712,7 +9712,7 @@ only@~0.0.2: open@^10.1.0: version "10.1.2" - resolved "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/open/-/open-10.1.2.tgz#d5df40984755c9a9c3c93df8156a12467e882925" + resolved "https://registry.yarnpkg.com/open/-/open-10.1.2.tgz#d5df40984755c9a9c3c93df8156a12467e882925" integrity sha512-cxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+BZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw== dependencies: default-browser "^5.2.1" @@ -10597,7 +10597,7 @@ rollup@^2.79.2: run-applescript@^7.0.0: version "7.0.0" - resolved "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/run-applescript/-/run-applescript-7.0.0.tgz#e5a553c2bffd620e169d276c1cd8f1b64778fbeb" + resolved "https://registry.yarnpkg.com/run-applescript/-/run-applescript-7.0.0.tgz#e5a553c2bffd620e169d276c1cd8f1b64778fbeb" integrity sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A== run-parallel@^1.1.9: From be4fa4e3e393fcbdf535aca883c050e00a235ccd Mon Sep 17 00:00:00 2001 From: Mohammed Abdul Sattar Date: Thu, 10 Jul 2025 16:30:49 -0400 Subject: [PATCH 3/3] fix: address feedback --- src/commands/lightning/dev/site.ts | 191 +++++++++++++++-------------- 1 file changed, 101 insertions(+), 90 deletions(-) diff --git a/src/commands/lightning/dev/site.ts b/src/commands/lightning/dev/site.ts index 7d5a32e7..d7f6a626 100644 --- a/src/commands/lightning/dev/site.ts +++ b/src/commands/lightning/dev/site.ts @@ -6,7 +6,7 @@ */ import fs from 'node:fs'; import { SfCommand, Flags } from '@salesforce/sf-plugins-core'; -import { Logger, Messages, SfProject } from '@salesforce/core'; +import { Connection, Logger, Messages, SfProject } from '@salesforce/core'; import { Platform } from '@salesforce/lwc-dev-mobile-core'; import { expDev, SitesLocalDevOptions, setupDev } from '@lwrjs/api'; import open from 'open'; @@ -74,99 +74,110 @@ export default class LightningDevSite extends SfCommand { const selectedSite = new ExperienceSite(org, siteName); if (!ssr) { - let sfdxProjectRootPath = ''; - try { - sfdxProjectRootPath = await SfProject.resolveProjectPath(); - } catch (error) { - throw new Error(sharedMessages.getMessage('error.no-project', [(error as Error)?.message ?? ''])); - } - const previewUrl = await selectedSite.getPreviewUrl(); - const username = connection.getUsername(); - if (!username) { - throw new Error(sharedMessages.getMessage('error.username')); - } - - this.log('Configuring local web server identity'); - const appServerIdentity = await PreviewUtils.getOrCreateAppServerIdentity(connection); - const ldpServerToken = appServerIdentity.identityToken; - const ldpServerId = appServerIdentity.usernameToServerEntityIdMap[username]; - if (!ldpServerId) { - throw new Error(sharedMessages.getMessage('error.identitydata.entityid')); - } - - this.log('Determining the next available port for Local Dev Server'); - const serverPorts = await PreviewUtils.getNextAvailablePorts(); - this.log(`Next available ports are http=${serverPorts.httpPort} , https=${serverPorts.httpsPort}`); - - this.log('Determining Local Dev Server url'); - const ldpServerUrl = PreviewUtils.generateWebSocketUrlForLocalDevServer(Platform.desktop, serverPorts); - this.log(`Local Dev Server url is ${ldpServerUrl}`); - - const logger = await Logger.child(this.ctor.name); - await startLWCServer(logger, sfdxProjectRootPath, ldpServerToken, Platform.desktop, serverPorts); - const url = new URL(previewUrl); - url.searchParams.set('aura.lwcDevServerUrl', ldpServerUrl); - url.searchParams.set('aura.lwcDevServerId', ldpServerId); - url.searchParams.set('lwc.mode', 'dev'); - await open(url.toString()); - return; - } - - let siteZip: string | undefined; - - // If the site is not setup / is not based on the current release / or get-latest is requested -> - // generate and download a new site bundle from the org based on latest builder metadata - if (!selectedSite.isSiteSetup() || getLatest) { - const startTime = Date.now(); - this.log(`[local-dev] Initializing: ${siteName}`); - this.spinner.start('[local-dev] Downloading site (this may take a few minutes)'); - siteZip = await selectedSite.downloadSite(); - - // delete oldSitePath recursive - const oldSitePath = selectedSite.getExtractDirectory(); - if (fs.existsSync(oldSitePath)) { - fs.rmSync(oldSitePath, { recursive: true }); - } - const endTime = Date.now(); - const duration = (endTime - startTime) / 1000; // Convert to seconds - this.spinner.stop('done.'); - this.log(`[local-dev] Site setup completed in ${duration.toFixed(2)} seconds.`); - } - - this.log(`[local-dev] launching browser preview for: ${siteName}`); - - // Establish a valid access token for this site - const authToken = guest ? '' : await selectedSite.setupAuth(); - - // Start the dev server - const port = parseInt(process.env.PORT ?? '3000', 10); - - // Internal vs external mode - const internalProject = !fs.existsSync('sfdx-project.json') && fs.existsSync('lwr.config.json'); - const logLevel = process.env.LOG_LEVEL ?? 'error'; - - const startupParams: SitesLocalDevOptions = { - sfCLI: !internalProject, - authToken, - open: process.env.OPEN_BROWSER === 'false' ? false : true, - port, - logLevel, - mode: 'dev', - siteZip, - siteDir: selectedSite.getSiteDirectory(), - }; - - // Environment variable used to setup the site rather than setup & start server - if (process.env.SETUP_ONLY === 'true') { - await setupDev(startupParams); - this.log('[local-dev] setup complete!'); - } else { - await expDev(startupParams); - this.log('[local-dev] watching for file changes... (CTRL-C to stop)'); + return await this.openPreviewUrl(selectedSite, connection); } + await this.serveSSRSite(selectedSite, getLatest, siteName, guest); } catch (e) { this.spinner.stop('failed.'); this.log('Local Development setup failed', e); } } + + private async serveSSRSite( + selectedSite: ExperienceSite, + getLatest: boolean, + siteName: string, + guest: boolean + ): Promise { + let siteZip: string | undefined; + + // If the site is not setup / is not based on the current release / or get-latest is requested -> + // generate and download a new site bundle from the org based on latest builder metadata + if (!selectedSite.isSiteSetup() || getLatest) { + const startTime = Date.now(); + this.log(`[local-dev] Initializing: ${siteName}`); + this.spinner.start('[local-dev] Downloading site (this may take a few minutes)'); + siteZip = await selectedSite.downloadSite(); + + // delete oldSitePath recursive + const oldSitePath = selectedSite.getExtractDirectory(); + if (fs.existsSync(oldSitePath)) { + fs.rmSync(oldSitePath, { recursive: true }); + } + const endTime = Date.now(); + const duration = (endTime - startTime) / 1000; // Convert to seconds + this.spinner.stop('done.'); + this.log(`[local-dev] Site setup completed in ${duration.toFixed(2)} seconds.`); + } + + this.log(`[local-dev] launching browser preview for: ${siteName}`); + + // Establish a valid access token for this site + const authToken = guest ? '' : await selectedSite.setupAuth(); + + // Start the dev server + const port = parseInt(process.env.PORT ?? '3000', 10); + + // Internal vs external mode + const internalProject = !fs.existsSync('sfdx-project.json') && fs.existsSync('lwr.config.json'); + const logLevel = process.env.LOG_LEVEL ?? 'error'; + + const startupParams: SitesLocalDevOptions = { + sfCLI: !internalProject, + authToken, + open: process.env.OPEN_BROWSER === 'false' ? false : true, + port, + logLevel, + mode: 'dev', + siteZip, + siteDir: selectedSite.getSiteDirectory(), + }; + + // Environment variable used to setup the site rather than setup & start server + if (process.env.SETUP_ONLY === 'true') { + await setupDev(startupParams); + this.log('[local-dev] setup complete!'); + } else { + await expDev(startupParams); + this.log('[local-dev] watching for file changes... (CTRL-C to stop)'); + } + } + + private async openPreviewUrl(selectedSite: ExperienceSite, connection: Connection): Promise { + let sfdxProjectRootPath = ''; + try { + sfdxProjectRootPath = await SfProject.resolveProjectPath(); + } catch (error) { + throw new Error(sharedMessages.getMessage('error.no-project', [(error as Error)?.message ?? ''])); + } + const previewUrl = await selectedSite.getPreviewUrl(); + const username = connection.getUsername(); + if (!username) { + throw new Error(sharedMessages.getMessage('error.username')); + } + + this.log('Configuring local web server identity'); + const appServerIdentity = await PreviewUtils.getOrCreateAppServerIdentity(connection); + const ldpServerToken = appServerIdentity.identityToken; + const ldpServerId = appServerIdentity.usernameToServerEntityIdMap[username]; + if (!ldpServerId) { + throw new Error(sharedMessages.getMessage('error.identitydata.entityid')); + } + + this.log('Determining the next available port for Local Dev Server'); + const serverPorts = await PreviewUtils.getNextAvailablePorts(); + this.log(`Next available ports are http=${serverPorts.httpPort} , https=${serverPorts.httpsPort}`); + + this.log('Determining Local Dev Server url'); + const ldpServerUrl = PreviewUtils.generateWebSocketUrlForLocalDevServer(Platform.desktop, serverPorts); + this.log(`Local Dev Server url is ${ldpServerUrl}`); + + const logger = await Logger.child(this.ctor.name); + await startLWCServer(logger, sfdxProjectRootPath, ldpServerToken, Platform.desktop, serverPorts); + const url = new URL(previewUrl); + url.searchParams.set('aura.lwcDevServerUrl', ldpServerUrl); + url.searchParams.set('aura.lwcDevServerId', ldpServerId); + url.searchParams.set('lwc.mode', 'dev'); + await open(url.toString()); + } }