66 */
77import fs from 'node:fs' ;
88import path from 'node:path' ;
9- import { Connection , Org , SfError } from '@salesforce/core' ;
9+ import { Org , SfError } from '@salesforce/core' ;
10+
11+ export type SiteMetadata = {
12+ bundleName : string ;
13+ bundleLastModified : string ;
14+ } ;
15+
16+ export type SiteMetadataCache = {
17+ [ key : string ] : SiteMetadata ;
18+ } ;
1019
1120/**
1221 * Experience Site class.
22+ * https://developer.salesforce.com/docs/platform/lwc/guide/get-started-test-components.html#enable-local-dev
1323 *
1424 * @param {string } siteName - The name of the experience site.
1525 * @param {string } status - The status of the experience site.
@@ -19,19 +29,13 @@ import { Connection, Org, SfError } from '@salesforce/core';
1929export class ExperienceSite {
2030 public siteDisplayName : string ;
2131 public siteName : string ;
22- public status : string ;
23-
2432 private org : Org ;
25- private bundleName : string ;
26- private bundleLastModified : string ;
33+ private metadataCache : SiteMetadataCache = { } ;
2734
28- public constructor ( org : Org , siteName : string , status ?: string , bundleName ?: string , bundleLastModified ?: string ) {
35+ public constructor ( org : Org , siteName : string ) {
2936 this . org = org ;
3037 this . siteDisplayName = siteName . trim ( ) ;
3138 this . siteName = this . siteDisplayName . replace ( ' ' , '_' ) ;
32- this . status = status ?? '' ;
33- this . bundleName = bundleName ?? '' ;
34- this . bundleLastModified = bundleLastModified ?? '' ;
3539 }
3640
3741 /**
@@ -45,7 +49,6 @@ export class ExperienceSite {
4549 * @returns
4650 */
4751 public static getLocalExpSite ( siteName : string ) : ExperienceSite {
48- // TODO cleanup
4952 const siteJsonPath = path . join ( '.localdev' , siteName . trim ( ) . replace ( ' ' , '_' ) , 'site.json' ) ;
5053 const siteJson = fs . readFileSync ( siteJsonPath , 'utf8' ) ;
5154 const site = JSON . parse ( siteJson ) as ExperienceSite ;
@@ -67,14 +70,7 @@ export class ExperienceSite {
6770
6871 // Example of creating ExperienceSite instances
6972 const experienceSites : ExperienceSite [ ] = result . records . map (
70- ( record ) =>
71- new ExperienceSite (
72- org ,
73- getSiteNameFromStaticResource ( record . Name ) ,
74- 'live' ,
75- record . Name ,
76- record . LastModifiedDate
77- )
73+ ( record ) => new ExperienceSite ( org , getSiteNameFromStaticResource ( record . Name ) )
7874 ) ;
7975
8076 return experienceSites ;
@@ -86,8 +82,8 @@ export class ExperienceSite {
8682 * @param {Connection } conn - Salesforce connection object.
8783 * @returns {Promise<string[]> } - List of experience sites.
8884 */
89- public static async getAllExpSites ( conn : Connection ) : Promise < string [ ] > {
90- const result = await conn . query < {
85+ public static async getAllExpSites ( org : Org ) : Promise < string [ ] > {
86+ const result = await org . getConnection ( ) . query < {
9187 Id : string ;
9288 Name : string ;
9389 LastModifiedDate : string ;
@@ -98,42 +94,84 @@ export class ExperienceSite {
9894 return experienceSites ;
9995 }
10096
101- public isSiteSetup ( ) : boolean {
102- return fs . existsSync ( path . join ( this . getExtractDirectory ( ) , 'ssr.js' ) ) ;
97+ public async isUpdateAvailable ( ) : Promise < boolean > {
98+ const localMetadata = this . getLocalMetadata ( ) ;
99+ if ( ! localMetadata ) {
100+ return true ; // If no local metadata, assume update is available
101+ }
102+
103+ const remoteMetadata = await this . getRemoteMetadata ( ) ;
104+ if ( ! remoteMetadata ) {
105+ return false ; // If no org bundle found, no update available
106+ }
107+
108+ return new Date ( remoteMetadata . bundleLastModified ) > new Date ( localMetadata . bundleLastModified ) ;
103109 }
104110
105- public isSitePublished ( ) : boolean {
106- // TODO
111+ // Is the site extracted locally
112+ public isSiteSetup ( ) : boolean {
107113 return fs . existsSync ( path . join ( this . getExtractDirectory ( ) , 'ssr.js' ) ) ;
108114 }
109115
110- public async getBundleName ( ) : Promise < string > {
111- if ( ! this . bundleName ) {
112- await this . initBundle ( ) ;
116+ // Is the static resource available on the server
117+ public async isSitePublished ( ) : Promise < boolean > {
118+ const remoteMetadata = await this . getRemoteMetadata ( ) ;
119+ if ( ! remoteMetadata ) {
120+ return false ;
113121 }
114-
115- return this . bundleName ;
122+ return true ;
116123 }
117124
118- public async getBundleLastModified ( ) : Promise < string > {
119- if ( ! this . bundleLastModified ) {
120- await this . initBundle ( ) ;
125+ // Is there a local gz file of the site
126+ public isSiteDownloaded ( ) : boolean {
127+ const metadata = this . getLocalMetadata ( ) ;
128+ if ( ! metadata ) {
129+ return false ;
121130 }
122- return this . bundleLastModified ;
131+ return fs . existsSync ( this . getSiteZipPath ( metadata ) ) ;
123132 }
124133
125- /**
126- * Save the site metadata to the file system.
127- */
128- public save ( ) : void {
134+ public saveMetadata ( metadata : SiteMetadata ) : void {
129135 const siteJsonPath = path . join ( this . getSiteDirectory ( ) , 'site.json' ) ;
130- const siteJson = JSON . stringify ( this , null , 4 ) ;
131-
132- // write out the site metadata
133- fs . mkdirSync ( this . getSiteDirectory ( ) , { recursive : true } ) ;
136+ const siteJson = JSON . stringify ( metadata , null , 2 ) ;
134137 fs . writeFileSync ( siteJsonPath , siteJson ) ;
135138 }
136139
140+ public getLocalMetadata ( ) : SiteMetadata | undefined {
141+ if ( this . metadataCache . localMetadata ) return this . metadataCache . localMetadata ;
142+ const siteJsonPath = path . join ( this . getSiteDirectory ( ) , 'site.json' ) ;
143+ let siteJson ;
144+ if ( fs . existsSync ( siteJsonPath ) ) {
145+ try {
146+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
147+ siteJson = JSON . parse ( fs . readFileSync ( siteJsonPath , 'utf-8' ) ) as SiteMetadata ;
148+ this . metadataCache . localMetadata = siteJson ;
149+ } catch ( error ) {
150+ // eslint-disable-next-line no-console
151+ console . error ( 'Error reading site.json file' , error ) ;
152+ }
153+ }
154+ return siteJson ;
155+ }
156+
157+ public async getRemoteMetadata ( ) : Promise < SiteMetadata | undefined > {
158+ if ( this . metadataCache . remoteMetadata ) return this . metadataCache . remoteMetadata ;
159+ const result = await this . org
160+ . getConnection ( )
161+ . query < { Name : string ; LastModifiedDate : string } > (
162+ `SELECT Name, LastModifiedDate FROM StaticResource WHERE Name LIKE 'MRT_experience_%_${ this . siteName } '`
163+ ) ;
164+ if ( result . records . length === 0 ) {
165+ return undefined ;
166+ }
167+ const staticResource = result . records [ 0 ] ;
168+ this . metadataCache . remoteMetadata = {
169+ bundleName : staticResource . Name ,
170+ bundleLastModified : staticResource . LastModifiedDate ,
171+ } ;
172+ return this . metadataCache . remoteMetadata ;
173+ }
174+
137175 /**
138176 * Get the local site directory path
139177 *
@@ -147,47 +185,45 @@ export class ExperienceSite {
147185 return path . join ( '.localdev' , this . siteName , 'app' ) ;
148186 }
149187
188+ public getSiteZipPath ( metadata : SiteMetadata ) : string {
189+ const lastModifiedDate = new Date ( metadata . bundleLastModified ) ;
190+ const timestamp = `${
191+ lastModifiedDate . getMonth ( ) + 1
192+ } -${ lastModifiedDate . getDate ( ) } _${ lastModifiedDate . getHours ( ) } -${ lastModifiedDate . getMinutes ( ) } `;
193+ const fileName = `${ metadata . bundleName } _${ timestamp } .gz` ;
194+ const resourcePath = path . join ( this . getSiteDirectory ( ) , fileName ) ;
195+ return resourcePath ;
196+ }
197+
150198 /**
151199 * Download and return the site resource bundle
152200 *
153201 * @returns path of downloaded site zip
154202 */
155203 public async downloadSite ( ) : Promise < string > {
156- // 3a. Locate the site bundle
157- const bundleName = await this . getBundleName ( ) ;
158-
159- // 3b. Download the site from static resources
160- const resourcePath = path . join ( this . getSiteDirectory ( ) , `${ bundleName } .gz` ) ;
161-
162- // TODO configure redownloading
163- if ( ! fs . existsSync ( resourcePath ) ) {
164- const staticresource = await this . org . getConnection ( ) . metadata . read ( 'StaticResource' , bundleName ) ;
165- if ( staticresource ?. content ) {
166- fs . mkdirSync ( this . getSiteDirectory ( ) , { recursive : true } ) ;
167- // Save the static resource
168- const buffer = Buffer . from ( staticresource . content , 'base64' ) ;
169- // this.log(`Writing file to path: ${resourcePath}`);
170- fs . writeFileSync ( resourcePath , buffer ) ;
171- } else {
172- throw new SfError ( `Error occured downloading your site: ${ this . siteDisplayName } ` ) ;
173- }
204+ const remoteMetadata = await this . getRemoteMetadata ( ) ;
205+ if ( ! remoteMetadata ) {
206+ throw new SfError ( `No published site found for: ${ this . siteDisplayName } ` ) ;
174207 }
175- return resourcePath ;
176- }
177208
178- private async initBundle ( ) : Promise < void > {
179- const result = await this . org
180- . getConnection ( )
181- . query < { Id : string ; Name : string ; LastModifiedDate : string } > (
182- "SELECT Id, Name, LastModifiedDate FROM StaticResource WHERE Name LIKE 'MRT_experience_%_" + this . siteName + "'"
183- ) ;
184- if ( result . records . length === 0 ) {
185- throw new Error ( `No experience site found for siteName: ${ this . siteDisplayName } ` ) ;
209+ // Download the site from static resources
210+ // eslint-disable-next-line no-console
211+ console . log ( '[local-dev] Downloading site...' ) ; // TODO spinner
212+ const resourcePath = this . getSiteZipPath ( remoteMetadata ) ;
213+ const staticresource = await this . org . getConnection ( ) . metadata . read ( 'StaticResource' , remoteMetadata . bundleName ) ;
214+ if ( staticresource ?. content ) {
215+ // Save the static resource
216+ fs . mkdirSync ( this . getSiteDirectory ( ) , { recursive : true } ) ;
217+ const buffer = Buffer . from ( staticresource . content , 'base64' ) ;
218+ fs . writeFileSync ( resourcePath , buffer ) ;
219+
220+ // Save the site's metadata
221+ this . saveMetadata ( remoteMetadata ) ;
222+ } else {
223+ throw new SfError ( `Error occurred downloading your site: ${ this . siteDisplayName } ` ) ;
186224 }
187225
188- const staticResource = result . records [ 0 ] ;
189- this . bundleName = staticResource . Name ;
190- this . bundleLastModified = staticResource . LastModifiedDate ;
226+ return resourcePath ;
191227 }
192228}
193229
0 commit comments