66 */
77import fs from 'node:fs' ;
88import { SfCommand , Flags } from '@salesforce/sf-plugins-core' ;
9- import { Messages } from '@salesforce/core' ;
9+ import { Connection , Logger , Messages , SfProject } from '@salesforce/core' ;
10+ import { Platform } from '@salesforce/lwc-dev-mobile-core' ;
1011import { expDev , SitesLocalDevOptions , setupDev } from '@lwrjs/api' ;
12+ import open from 'open' ;
1113import { OrgUtils } from '../../../shared/orgUtils.js' ;
1214import { PromptUtils } from '../../../shared/promptUtils.js' ;
1315import { ExperienceSite } from '../../../shared/experience/expSite.js' ;
16+ import { PreviewUtils } from '../../../shared/previewUtils.js' ;
17+ import { startLWCServer } from '../../../lwc-dev-server/index.js' ;
1418
1519Messages . importMessagesDirectoryFromMetaUrl ( import . meta. url ) ;
1620const messages = Messages . loadMessages ( '@salesforce/plugin-lightning-dev' , 'lightning.dev.site' ) ;
@@ -36,6 +40,10 @@ export default class LightningDevSite extends SfCommand<void> {
3640 summary : messages . getMessage ( 'flags.guest.summary' ) ,
3741 default : false ,
3842 } ) ,
43+ ssr : Flags . boolean ( {
44+ summary : messages . getMessage ( 'flags.ssr.summary' ) ,
45+ default : false ,
46+ } ) ,
3947 } ;
4048
4149 public async run ( ) : Promise < void > {
@@ -45,6 +53,7 @@ export default class LightningDevSite extends SfCommand<void> {
4553 const org = flags [ 'target-org' ] ;
4654 const getLatest = flags [ 'get-latest' ] ;
4755 const guest = flags . guest ;
56+ const ssr = flags . ssr ;
4857 let siteName = flags . name ;
4958
5059 const connection = org . getConnection ( undefined ) ;
@@ -63,61 +72,112 @@ export default class LightningDevSite extends SfCommand<void> {
6372 }
6473
6574 const selectedSite = new ExperienceSite ( org , siteName ) ;
66- let siteZip : string | undefined ;
67-
68- // If the site is not setup / is not based on the current release / or get-latest is requested ->
69- // generate and download a new site bundle from the org based on latest builder metadata
70- if ( ! selectedSite . isSiteSetup ( ) || getLatest ) {
71- const startTime = Date . now ( ) ;
72- this . log ( `[local-dev] Initializing: ${ siteName } ` ) ;
73- this . spinner . start ( '[local-dev] Downloading site (this may take a few minutes)' ) ;
74- siteZip = await selectedSite . downloadSite ( ) ;
75-
76- // delete oldSitePath recursive
77- const oldSitePath = selectedSite . getExtractDirectory ( ) ;
78- if ( fs . existsSync ( oldSitePath ) ) {
79- fs . rmSync ( oldSitePath , { recursive : true } ) ;
80- }
81- const endTime = Date . now ( ) ;
82- const duration = ( endTime - startTime ) / 1000 ; // Convert to seconds
83- this . spinner . stop ( 'done.' ) ;
84- this . log ( `[local-dev] Site setup completed in ${ duration . toFixed ( 2 ) } seconds.` ) ;
85- }
8675
87- this . log ( `[local-dev] launching browser preview for: ${ siteName } ` ) ;
88-
89- // Establish a valid access token for this site
90- const authToken = guest ? '' : await selectedSite . setupAuth ( ) ;
91-
92- // Start the dev server
93- const port = parseInt ( process . env . PORT ?? '3000' , 10 ) ;
94-
95- // Internal vs external mode
96- const internalProject = ! fs . existsSync ( 'sfdx-project.json' ) && fs . existsSync ( 'lwr.config.json' ) ;
97- const logLevel = process . env . LOG_LEVEL ?? 'error' ;
98-
99- const startupParams : SitesLocalDevOptions = {
100- sfCLI : ! internalProject ,
101- authToken,
102- open : process . env . OPEN_BROWSER === 'false' ? false : true ,
103- port,
104- logLevel,
105- mode : 'dev' ,
106- siteZip,
107- siteDir : selectedSite . getSiteDirectory ( ) ,
108- } ;
109-
110- // Environment variable used to setup the site rather than setup & start server
111- if ( process . env . SETUP_ONLY === 'true' ) {
112- await setupDev ( startupParams ) ;
113- this . log ( '[local-dev] setup complete!' ) ;
114- } else {
115- await expDev ( startupParams ) ;
116- this . log ( '[local-dev] watching for file changes... (CTRL-C to stop)' ) ;
76+ if ( ! ssr ) {
77+ return await this . openPreviewUrl ( selectedSite , connection ) ;
11778 }
79+ await this . serveSSRSite ( selectedSite , getLatest , siteName , guest ) ;
11880 } catch ( e ) {
11981 this . spinner . stop ( 'failed.' ) ;
12082 this . log ( 'Local Development setup failed' , e ) ;
12183 }
12284 }
85+
86+ private async serveSSRSite (
87+ selectedSite : ExperienceSite ,
88+ getLatest : boolean ,
89+ siteName : string ,
90+ guest : boolean
91+ ) : Promise < void > {
92+ let siteZip : string | undefined ;
93+
94+ // If the site is not setup / is not based on the current release / or get-latest is requested ->
95+ // generate and download a new site bundle from the org based on latest builder metadata
96+ if ( ! selectedSite . isSiteSetup ( ) || getLatest ) {
97+ const startTime = Date . now ( ) ;
98+ this . log ( `[local-dev] Initializing: ${ siteName } ` ) ;
99+ this . spinner . start ( '[local-dev] Downloading site (this may take a few minutes)' ) ;
100+ siteZip = await selectedSite . downloadSite ( ) ;
101+
102+ // delete oldSitePath recursive
103+ const oldSitePath = selectedSite . getExtractDirectory ( ) ;
104+ if ( fs . existsSync ( oldSitePath ) ) {
105+ fs . rmSync ( oldSitePath , { recursive : true } ) ;
106+ }
107+ const endTime = Date . now ( ) ;
108+ const duration = ( endTime - startTime ) / 1000 ; // Convert to seconds
109+ this . spinner . stop ( 'done.' ) ;
110+ this . log ( `[local-dev] Site setup completed in ${ duration . toFixed ( 2 ) } seconds.` ) ;
111+ }
112+
113+ this . log ( `[local-dev] launching browser preview for: ${ siteName } ` ) ;
114+
115+ // Establish a valid access token for this site
116+ const authToken = guest ? '' : await selectedSite . setupAuth ( ) ;
117+
118+ // Start the dev server
119+ const port = parseInt ( process . env . PORT ?? '3000' , 10 ) ;
120+
121+ // Internal vs external mode
122+ const internalProject = ! fs . existsSync ( 'sfdx-project.json' ) && fs . existsSync ( 'lwr.config.json' ) ;
123+ const logLevel = process . env . LOG_LEVEL ?? 'error' ;
124+
125+ const startupParams : SitesLocalDevOptions = {
126+ sfCLI : ! internalProject ,
127+ authToken,
128+ open : process . env . OPEN_BROWSER === 'false' ? false : true ,
129+ port,
130+ logLevel,
131+ mode : 'dev' ,
132+ siteZip,
133+ siteDir : selectedSite . getSiteDirectory ( ) ,
134+ } ;
135+
136+ // Environment variable used to setup the site rather than setup & start server
137+ if ( process . env . SETUP_ONLY === 'true' ) {
138+ await setupDev ( startupParams ) ;
139+ this . log ( '[local-dev] setup complete!' ) ;
140+ } else {
141+ await expDev ( startupParams ) ;
142+ this . log ( '[local-dev] watching for file changes... (CTRL-C to stop)' ) ;
143+ }
144+ }
145+
146+ private async openPreviewUrl ( selectedSite : ExperienceSite , connection : Connection ) : Promise < void > {
147+ let sfdxProjectRootPath = '' ;
148+ try {
149+ sfdxProjectRootPath = await SfProject . resolveProjectPath ( ) ;
150+ } catch ( error ) {
151+ throw new Error ( sharedMessages . getMessage ( 'error.no-project' , [ ( error as Error ) ?. message ?? '' ] ) ) ;
152+ }
153+ const previewUrl = await selectedSite . getPreviewUrl ( ) ;
154+ const username = connection . getUsername ( ) ;
155+ if ( ! username ) {
156+ throw new Error ( sharedMessages . getMessage ( 'error.username' ) ) ;
157+ }
158+
159+ this . log ( 'Configuring local web server identity' ) ;
160+ const appServerIdentity = await PreviewUtils . getOrCreateAppServerIdentity ( connection ) ;
161+ const ldpServerToken = appServerIdentity . identityToken ;
162+ const ldpServerId = appServerIdentity . usernameToServerEntityIdMap [ username ] ;
163+ if ( ! ldpServerId ) {
164+ throw new Error ( sharedMessages . getMessage ( 'error.identitydata.entityid' ) ) ;
165+ }
166+
167+ this . log ( 'Determining the next available port for Local Dev Server' ) ;
168+ const serverPorts = await PreviewUtils . getNextAvailablePorts ( ) ;
169+ this . log ( `Next available ports are http=${ serverPorts . httpPort } , https=${ serverPorts . httpsPort } ` ) ;
170+
171+ this . log ( 'Determining Local Dev Server url' ) ;
172+ const ldpServerUrl = PreviewUtils . generateWebSocketUrlForLocalDevServer ( Platform . desktop , serverPorts ) ;
173+ this . log ( `Local Dev Server url is ${ ldpServerUrl } ` ) ;
174+
175+ const logger = await Logger . child ( this . ctor . name ) ;
176+ await startLWCServer ( logger , sfdxProjectRootPath , ldpServerToken , Platform . desktop , serverPorts ) ;
177+ const url = new URL ( previewUrl ) ;
178+ url . searchParams . set ( 'aura.lwcDevServerUrl' , ldpServerUrl ) ;
179+ url . searchParams . set ( 'aura.lwcDevServerId' , ldpServerId ) ;
180+ url . searchParams . set ( 'lwc.mode' , 'dev' ) ;
181+ await open ( url . toString ( ) ) ;
182+ }
123183}
0 commit comments