|
6 | 6 | */ |
7 | 7 | import fs from 'node:fs'; |
8 | 8 | import path from 'node:path'; |
9 | | -// import zlib from 'node:zlib'; |
10 | | -// import { pipeline } from 'node:stream'; |
11 | | -// import { promisify } from 'node:util'; |
12 | | -// eslint-disable-next-line import/no-extraneous-dependencies |
13 | | -import * as tar from 'tar'; |
14 | 9 | import { SfCommand, Flags } from '@salesforce/sf-plugins-core'; |
15 | | -import { Messages } from '@salesforce/core'; |
16 | | -import { expDev } from '@lwrjs/api'; |
| 10 | +import { Messages, SfError } from '@salesforce/core'; |
| 11 | +import { expDev, setupDev } from '@lwrjs/api'; |
| 12 | +import { PromptUtils } from '../../../shared/prompt.js'; |
| 13 | +import { OrgUtils } from '../../../shared/orgUtils.js'; |
17 | 14 |
|
18 | 15 | Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); |
19 | 16 | const messages = Messages.loadMessages('@salesforce/plugin-lightning-dev', 'lightning.preview.site'); |
@@ -42,113 +39,79 @@ export default class LightningPreviewSite extends SfCommand<LightningPreviewSite |
42 | 39 |
|
43 | 40 | public async run(): Promise<LightningPreviewSiteResult> { |
44 | 41 | const { flags } = await this.parse(LightningPreviewSite); |
45 | | - |
46 | | - // 1. Collect Flags |
47 | | - let siteName = flags.name ?? 'B2C_CodeCept'; |
| 42 | + // Connect to Org |
| 43 | + const connection = flags['target-org'].getConnection(); |
| 44 | + |
| 45 | + // If we don't have a site to use, promp the user for one |
| 46 | + let siteName = flags.name; |
| 47 | + if (!siteName) { |
| 48 | + this.log('No site name was specified, pick one'); |
| 49 | + // Query for the list of possible sites |
| 50 | + const siteList = await OrgUtils.retrieveSites(connection); |
| 51 | + siteName = await PromptUtils.promptUserToSelectSite(siteList); |
| 52 | + } |
48 | 53 | this.log(`Setting up local development for: ${siteName}`); |
49 | | - siteName = siteName.trim().replace(' ', '_'); |
50 | | - // TODO don't redownload the app |
51 | | - if (!fs.existsSync('app')) { |
52 | | - // this.log('getting org connection'); |
53 | 54 |
|
54 | | - // 2. Connect to Org |
55 | | - const connection = flags['target-org'].getConnection(); |
56 | | - // 3. Check if the site exists |
57 | | - // this.log('checking site exists'); |
58 | | - // TODO cleanup query |
| 55 | + siteName = siteName.trim().replace(' ', '_'); |
| 56 | + const siteDir = path.join('__local_dev__', siteName); |
| 57 | + if (!fs.existsSync(path.join(siteDir, 'ssr.js'))) { |
| 58 | + // Ensure local dev dir is created |
| 59 | + fs.mkdirSync('__local_dev__'); |
| 60 | + // 3. Check if the site has been published |
59 | 61 | const result = await connection.query<{ Id: string; Name: string; LastModifiedDate: string }>( |
60 | | - "SELECT Id, Name, LastModifiedDate FROM StaticResource WHERE Name LIKE 'MRT%" + siteName + "' LIMIT 1" |
| 62 | + "SELECT Id, Name, LastModifiedDate FROM StaticResource WHERE Name LIKE 'MRT%" + siteName + "'" |
61 | 63 | ); |
62 | 64 |
|
63 | | - // 4. Download the static resource |
64 | | - if (result.records[0]) { |
65 | | - const resourceName = result.records[0].Name; |
66 | | - // this.log(`Found Site: ${resourceName}`); |
67 | | - |
68 | | - this.log('Downloading Site...'); |
69 | | - const staticresource = await connection.metadata.read('StaticResource', resourceName); |
70 | | - |
71 | | - if (staticresource?.content) { |
72 | | - // 5a. Save the resource |
73 | | - // const { contentType } = staticresource; |
74 | | - const buffer = Buffer.from(staticresource.content, 'base64'); |
75 | | - // // const path = `${resourceName}.${contentType.split('/')[1]}`; |
76 | | - const resourcePath = `${resourceName}.gz`; |
77 | | - this.log(`Extracting -> '${resourcePath}'`); |
78 | | - // this.log(`Writing file to path: ${resourcePath}`); |
79 | | - fs.writeFileSync(resourcePath, buffer); |
80 | | - |
81 | | - // Cleanup old directories |
82 | | - fs.rmSync('app', { recursive: true, force: true }); |
83 | | - fs.rmSync('bld', { recursive: true, force: true }); |
84 | | - |
85 | | - // Extract to specific directory |
86 | | - // Ensure output directory exists |
87 | | - // fs.mkdirSync('app', { recursive: true }); |
88 | | - |
89 | | - // 5b. Extracting static resource |
90 | | - await tar.x({ |
91 | | - file: resourcePath, |
92 | | - }); |
93 | | - |
94 | | - fs.renameSync('bld', 'app'); |
95 | | - // fs.unlinkSync(tempPath); // Clean up the temporary file |
96 | | - |
97 | | - // Setup the stream pipeline for unzipping |
98 | | - // const pipe = promisify(pipeline); |
99 | | - // const gunzip = zlib.createGunzip(); |
100 | | - // const inputStream = fs.createReadStream(resourcePath); |
101 | | - // const output = fs.createWriteStream(path.join('app', 'bld')); |
102 | | - // await pipe(inputStream, gunzip, output); |
103 | | - |
104 | | - // 5c. Temp - copy a proxy file |
105 | | - // TODO query for the url if we need to |
106 | | - // const newResult = await connection.query<{ Name: string; UrlPathPrefix: string }>( |
107 | | - // `SELECT Name, UrlPathPrefix FROM Network WHERE Name = '${siteName}'` |
108 | | - // ); |
| 65 | + let resourceName; |
| 66 | + // Pick the site you want if there is more than one |
| 67 | + if (result?.totalSize > 1) { |
| 68 | + const chooseFromList = result.records.map((record) => record.Name); |
| 69 | + resourceName = await PromptUtils.promptUserToSelectSite(chooseFromList); |
| 70 | + } else if (result?.totalSize === 1) { |
| 71 | + resourceName = result.records[0].Name; |
| 72 | + } else { |
| 73 | + throw new SfError( |
| 74 | + `Couldnt find your site: ${siteName}. Please navigate to the builder and publish your site with the Local Development preference enabled in your org.` |
| 75 | + ); |
| 76 | + } |
109 | 77 |
|
110 | | - // TODO should be included with bundle |
111 | | - const proxyPath = path.join('app', 'config', '_proxy'); |
112 | | - // fs.writeFileSync( |
113 | | - // proxyPath, |
114 | | - // '/services https://dsg000007tzqk2ak.test1.my.pc-rnd.site.com' + |
115 | | - // '\n/sfsites https://dsg000007tzqk2ak.test1.my.pc-rnd.site.com' + |
116 | | - // '\n/webruntime https://dsg000007tzqk2ak.test1.my.pc-rnd.site.com' |
117 | | - // ); |
118 | | - // Temp write proxy file |
119 | | - fs.writeFileSync( |
120 | | - proxyPath, |
121 | | - '/services https://dsg00000ayyw92ap.test1.my.pc-rnd.site.com' + |
122 | | - '\n/sfsites https://dsg00000ayyw92ap.test1.my.pc-rnd.site.com' + |
123 | | - '\n/webruntime https://dsg00000ayyw92ap.test1.my.pc-rnd.site.com' + |
124 | | - '\n/mobify/proxy/core https://dsg00000ayyw92ap.test1.my.pc-rnd.site.com' |
125 | | - ); |
126 | | - } else { |
127 | | - this.error(`Static Resource for ${siteName} not found.`); |
128 | | - } |
| 78 | + // Download the static resource |
| 79 | + this.log('Downloading Site...'); |
| 80 | + const staticresource = await connection.metadata.read('StaticResource', resourceName); |
| 81 | + const resourcePath = path.join('__local_dev__', `${resourceName}.gz`); |
| 82 | + if (staticresource?.content) { |
| 83 | + // Save the static resource |
| 84 | + const buffer = Buffer.from(staticresource.content, 'base64'); |
| 85 | + this.log(`Writing file to path: ${resourcePath}`); |
| 86 | + fs.writeFileSync(resourcePath, buffer); |
129 | 87 | } else { |
130 | | - throw new Error(`Couldnt find your site: ${siteName}`); |
| 88 | + throw new SfError(`Error occured downloading your site: ${siteName}`); |
131 | 89 | } |
| 90 | + |
| 91 | + const domains = await OrgUtils.getDomains(connection); |
| 92 | + const domain = await PromptUtils.promptUserToSelectDomain(domains); |
| 93 | + const urlPrefix = await OrgUtils.getSitePathPrefix(connection, siteName); |
| 94 | + const fullProxyUrl = `https://${domain}${urlPrefix}`; |
| 95 | + |
| 96 | + // Setup Local Dev |
| 97 | + await setupDev({ mrtBundle: resourcePath, mrtDir: siteDir, proxyUrl: fullProxyUrl, npmInstall: false }); |
| 98 | + this.log('Setup Complete!'); |
132 | 99 | } else { |
133 | | - // this.log('Site already configured!'); |
| 100 | + // If we do have the site setup already, don't do anything / TODO prompt the user if they want to get latest? |
134 | 101 | } |
135 | | - // Demo: Temp write Live Reload CSP |
136 | | - // const filepath = './app/experience/site-metadata.json'; |
137 | | - // const csp = fs.readFileSync(filepath, 'utf-8'); |
138 | | - // if (!csp.includes('ws://127.0.0.1:35729/livereload')) { |
139 | | - // const newContent = csp.replace( |
140 | | - // /https:\/\/js\.stripe\.com;/g, |
141 | | - // 'https://js.stripe.com ws://127.0.0.1:35729/livereload;' |
142 | | - // ); |
143 | | - // fs.writeFileSync(filepath, newContent); |
144 | | - // } |
145 | | - this.log('Setup Complete!'); |
146 | 102 |
|
147 | 103 | // 6. Start the dev server |
148 | 104 | this.log('Starting local development server...'); |
149 | 105 | // TODO add additional args |
150 | 106 | // eslint-disable-next-line unicorn/numeric-separators-style |
151 | | - await expDev({ open: false, port: 3000, timeout: 30000, sandbox: false, logLevel: 'error' }); |
| 107 | + await expDev({ |
| 108 | + open: false, |
| 109 | + port: 3000, |
| 110 | + timeout: 30000, |
| 111 | + sandbox: false, |
| 112 | + logLevel: 'error', |
| 113 | + mrtBundleRoot: siteDir, |
| 114 | + }); |
152 | 115 | // const name = flags.name ?? 'world'; |
153 | 116 | // this.log(`hello ${name} from /Users/nkruk/git/plugin-lightning-dev/src/commands/lightning/preview/site.ts`); |
154 | 117 | return { |
|
0 commit comments