-
Notifications
You must be signed in to change notification settings - Fork 12
feat: [sites] add get-latest
cli parameter / update lwr -> v0.16.2 (includes latest local-dev fixes)
#311
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
feat: [sites] add get-latest
cli parameter / update lwr -> v0.16.2 (includes latest local-dev fixes)
#311
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
48b4c7c
feat: add flag for getting the latest version of a experience site / …
nrkruk ee0a3dc
chore: update snapshots
nrkruk 94108b3
feat: add getLatest flag / delete unused code
nrkruk 86db846
feat: get-latest flag / update site setup / track download time / tmp…
nrkruk b7dd210
fix: tests
nrkruk File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,7 @@ import axios from 'axios'; | |
export type SiteMetadata = { | ||
bundleName: string; | ||
bundleLastModified: string; | ||
coreVersion: string; | ||
}; | ||
|
||
export type SiteMetadataCache = { | ||
|
@@ -41,44 +42,6 @@ export class ExperienceSite { | |
this.siteName = this.siteName.replace(/[^a-zA-Z0-9]/g, '_'); | ||
} | ||
|
||
/** | ||
* Get an experience site bundle by site name. | ||
* | ||
* @param conn - Salesforce connection object. | ||
* @param siteName - The name of the experience site. | ||
* @returns - The experience site. | ||
* | ||
* @param siteName | ||
* @returns | ||
*/ | ||
public static getLocalExpSite(siteName: string): ExperienceSite { | ||
const siteJsonPath = path.join('.localdev', siteName.trim().replace(' ', '_'), 'site.json'); | ||
const siteJson = fs.readFileSync(siteJsonPath, 'utf8'); | ||
const site = JSON.parse(siteJson) as ExperienceSite; | ||
return site; | ||
} | ||
|
||
/** | ||
* Fetches all experience site bundles that are published to MRT. | ||
* | ||
* @param {Connection} conn - Salesforce connection object. | ||
* @returns {Promise<ExperienceSite[]>} - List of experience sites. | ||
*/ | ||
public static async getAllPublishedExpSites(org: Org): Promise<ExperienceSite[]> { | ||
const result = await org | ||
.getConnection() | ||
.query<{ Id: string; Name: string; LastModifiedDate: string }>( | ||
"SELECT Id, Name, LastModifiedDate FROM StaticResource WHERE Name LIKE 'MRT%_'" | ||
); | ||
|
||
// Example of creating ExperienceSite instances | ||
const experienceSites: ExperienceSite[] = result.records.map( | ||
(record) => new ExperienceSite(org, getSiteNameFromStaticResource(record.Name)) | ||
); | ||
|
||
return experienceSites; | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unused code |
||
/** | ||
* Fetches all current experience sites | ||
* | ||
|
@@ -135,7 +98,10 @@ export class ExperienceSite { | |
|
||
// Is the site extracted locally | ||
public isSiteSetup(): boolean { | ||
return fs.existsSync(path.join(this.getExtractDirectory(), 'ssr.js')); | ||
if (fs.existsSync(path.join(this.getExtractDirectory(), 'ssr.js'))) { | ||
return this.getLocalMetadata()?.coreVersion === '254'; | ||
} | ||
return false; | ||
} | ||
|
||
// Is the static resource available on the server | ||
|
@@ -193,6 +159,7 @@ export class ExperienceSite { | |
this.metadataCache.remoteMetadata = { | ||
bundleName: staticResource.Name, | ||
bundleLastModified: staticResource.LastModifiedDate, | ||
coreVersion: '254', | ||
}; | ||
return this.metadataCache.remoteMetadata; | ||
} | ||
|
@@ -226,42 +193,23 @@ export class ExperienceSite { | |
* @returns path of downloaded site zip | ||
*/ | ||
public async downloadSite(): Promise<string> { | ||
let retVal; | ||
if (process.env.STATIC_MODE !== 'true') { | ||
const retVal = await this.downloadSiteV2(); | ||
return retVal; | ||
// Use sites API to download the site bundle on demand | ||
retVal = await this.downloadSiteApi(); | ||
} else { | ||
const remoteMetadata = await this.getRemoteMetadata(); | ||
if (!remoteMetadata) { | ||
throw new SfError(`No published site found for: ${this.siteDisplayName}`); | ||
} | ||
|
||
// Download the site from static resources | ||
// eslint-disable-next-line no-console | ||
console.log('[local-dev] Downloading site...'); // TODO spinner | ||
const resourcePath = this.getSiteZipPath(remoteMetadata); | ||
const staticresource = await this.org.getConnection().metadata.read('StaticResource', remoteMetadata.bundleName); | ||
if (staticresource?.content) { | ||
// Save the static resource | ||
fs.mkdirSync(this.getSiteDirectory(), { recursive: true }); | ||
const buffer = Buffer.from(staticresource.content, 'base64'); | ||
fs.writeFileSync(resourcePath, buffer); | ||
|
||
// Save the site's metadata | ||
this.saveMetadata(remoteMetadata); | ||
} else { | ||
throw new SfError(`Error occurred downloading your site: ${this.siteDisplayName}`); | ||
} | ||
|
||
return resourcePath; | ||
// This is for testing purposes only now - not an officially supported external path | ||
retVal = await this.downloadSiteStaticResources(); | ||
} | ||
return retVal; | ||
} | ||
|
||
/** | ||
* Generate a site bundle on demand and download it | ||
* | ||
* @returns path of downloaded site zip | ||
*/ | ||
public async downloadSiteV2(): Promise<string> { | ||
public async downloadSiteApi(): Promise<string> { | ||
const remoteMetadata = await this.org | ||
.getConnection() | ||
.query<{ Id: string; Name: string; LastModifiedDate: string; MasterLabel: string }>( | ||
|
@@ -273,30 +221,31 @@ export class ExperienceSite { | |
const theSite = remoteMetadata.records[0]; | ||
|
||
// Download the site via API | ||
// eslint-disable-next-line no-console | ||
console.log('[local-dev] Downloading site...'); // TODO spinner | ||
const conn = this.org.getConnection(); | ||
const metadata = { | ||
bundleName: theSite.Name, | ||
bundleLastModified: theSite.LastModifiedDate, | ||
coreVersion: '254', | ||
}; | ||
const siteId = theSite.Id; | ||
const siteIdMinus3 = siteId.substring(0, siteId.length - 3); | ||
const accessToken = conn.accessToken; | ||
const instanceUrl = conn.instanceUrl; // Org URL | ||
if (!accessToken) { | ||
throw new SfError(`Error occurred downloading your site: ${this.siteDisplayName}`); | ||
throw new SfError(`Invalid access token, unable to download site: ${this.siteDisplayName}`); | ||
} | ||
const resourcePath = this.getSiteZipPath(metadata); | ||
try { | ||
const apiUrl = `${instanceUrl}/services/data/v63.0/sites/${siteIdMinus3}/preview`; | ||
// Limit API to published sites for now until we have a patch for the issues with unpublished sites | ||
// TODO switch api back to preview mode after issues are addressed | ||
const apiUrl = `${instanceUrl}/services/data/v63.0/sites/${siteIdMinus3}/preview?published`; | ||
const response = await axios.get(apiUrl, { | ||
headers: { | ||
Authorization: `Bearer ${accessToken}`, | ||
}, | ||
responseType: 'stream', | ||
}); | ||
fs.mkdirSync(this.getSiteDirectory(), { recursive: true }); | ||
if (response.statusText) fs.mkdirSync(this.getSiteDirectory(), { recursive: true }); | ||
|
||
const fileStream = fs.createWriteStream(resourcePath); | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access | ||
|
@@ -307,15 +256,51 @@ export class ExperienceSite { | |
fileStream.on('error', reject); | ||
}); | ||
this.saveMetadata(metadata); | ||
} catch (e) { | ||
// eslint-disable-next-line no-console | ||
console.error('failed to download site', e); | ||
} catch (error) { | ||
// Handle axios errors | ||
if (axios.isAxiosError(error)) { | ||
if (error.response) { | ||
// Server responded with non-200 status | ||
throw new SfError( | ||
`Failed to download site: 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 download site: No response received from server'); | ||
} | ||
} | ||
throw new SfError(`Failed to download site: ${this.siteDisplayName}`); | ||
} | ||
|
||
// Save the site's metadata | ||
return resourcePath; | ||
} | ||
|
||
// Deprecated. Only used internally now for testing. Customer sites will no longer be stored in static resources | ||
// and are only available via the API. | ||
public async downloadSiteStaticResources(): Promise<string> { | ||
const remoteMetadata = await this.getRemoteMetadata(); | ||
if (!remoteMetadata) { | ||
throw new SfError(`No published site found for: ${this.siteDisplayName}`); | ||
} | ||
|
||
// Download the site from static resources | ||
const resourcePath = this.getSiteZipPath(remoteMetadata); | ||
const staticresource = await this.org.getConnection().metadata.read('StaticResource', remoteMetadata.bundleName); | ||
if (staticresource?.content) { | ||
// Save the static resource | ||
fs.mkdirSync(this.getSiteDirectory(), { recursive: true }); | ||
const buffer = Buffer.from(staticresource.content, 'base64'); | ||
fs.writeFileSync(resourcePath, buffer); | ||
|
||
// Save the site's metadata | ||
this.saveMetadata(remoteMetadata); | ||
} else { | ||
throw new SfError(`Error occurred downloading your site: ${this.siteDisplayName}`); | ||
} | ||
return resourcePath; | ||
} | ||
|
||
private async getNetworkId(): Promise<string> { | ||
const conn = this.org.getConnection(); | ||
// Query the Network object for the network with the given site name | ||
|
@@ -332,6 +317,7 @@ export class ExperienceSite { | |
} | ||
} | ||
|
||
// TODO need to get auth tokens for the builder preview also once API issues are addressed | ||
private async getNewSidToken(networkId: string): Promise<string> { | ||
// Get the connection and access token from the org | ||
const conn = this.org.getConnection(); | ||
|
@@ -344,9 +330,6 @@ export class ExperienceSite { | |
|
||
// Make the GET request without following redirects | ||
if (accessToken) { | ||
// TODO should we try and refresh auth here? | ||
// await conn.refreshAuth(); | ||
|
||
// Call out to the switcher servlet to establish a session | ||
const switchUrl = `${instanceUrl}/servlet/networks/switch?networkId=${networkId}`; | ||
const cookies = [`sid=${accessToken}`, `oid=${orgIdMinus3}`].join('; ').trim(); | ||
|
@@ -409,17 +392,3 @@ export class ExperienceSite { | |
return ''; | ||
} | ||
} | ||
|
||
/** | ||
* Return the site name given the name of its static resource bundle | ||
* | ||
* @param staticResourceName the static resource bundle name | ||
* @returns the name of the site | ||
*/ | ||
function getSiteNameFromStaticResource(staticResourceName: string): string { | ||
const parts = staticResourceName.split('_'); | ||
if (parts.length < 5) { | ||
throw new Error(`Unexpected static resource name: ${staticResourceName}`); | ||
} | ||
return parts.slice(4).join(' '); | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,7 +21,7 @@ const messages = Messages.loadMessages('@salesforce/plugin-lightning-dev', 'prom | |
|
||
export class PromptUtils { | ||
public static async promptUserToSelectSite(sites: string[]): Promise<string> { | ||
const choices = sites.map((site) => ({ value: site })); | ||
const choices = sites.sort((a, b) => a.localeCompare(b)).map((site) => ({ value: site })); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Order alphabetically |
||
const response = await select({ | ||
message: messages.getMessage('site.select'), | ||
choices, | ||
|
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Published sites are no longer tracked in an orgs static resources, so we will always use our local cache of initialized sites unless the user specifically requests the latest version of a site from the org