diff --git a/.prettierignore b/.prettierignore index 0dc60f5c7c..5e8679b302 100644 --- a/.prettierignore +++ b/.prettierignore @@ -13,6 +13,7 @@ yarn.lock /generated-docs /developer-extension/.output /developer-extension/.wxt +/test/apps/sf-lwc-app/* # Symlinks (prettier would otherwise resolve and overwrite the target file or replace the symlink with a regular file) /packages/browser-rum-slim/BROWSER_SUPPORT.md diff --git a/eslint.config.ts b/eslint.config.ts index 1502cb2a8b..f72c71aa57 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -52,6 +52,7 @@ export default defineConfig( 'test/apps/vue-router-v4-app', 'test/apps/nuxt-app', 'test/apps/nuxt-vue-router-v4-app', + 'test/apps/sf-lwc-app', 'sandbox', 'coverage', 'rum-events-format', diff --git a/test/apps/sf-lwc-app/.gitignore b/test/apps/sf-lwc-app/.gitignore new file mode 100644 index 0000000000..a8488dce86 --- /dev/null +++ b/test/apps/sf-lwc-app/.gitignore @@ -0,0 +1,3 @@ +.sf +.sfdx +/force-app/main/default/staticresources/datadog_rum_slim.js \ No newline at end of file diff --git a/test/apps/sf-lwc-app/README.md b/test/apps/sf-lwc-app/README.md new file mode 100644 index 0000000000..735bc85ea9 --- /dev/null +++ b/test/apps/sf-lwc-app/README.md @@ -0,0 +1,24 @@ +# SF LWC App + +Salesforce DX project used to exercise browser-sdk behavior in a real Lightning Web Components runtime. + +This app is Lightning-only. + +## What It Contains + +- A Lightning app named `SF LWC App` +- A trimmed Home page with Datadog test controls +- A `Product Explorer` app page with three hardcoded editable products +- `c:datadogInit` in the utility bar, backed by the `datadog_rum_slim` static resource + +## Deploy + +From this directory: + +```sh +npm run setup -- -o engrumdev --ignore-conflicts +``` + +The setup script copies the local RUM slim bundle into the static resource, deploys the app, assigns the app permission set to the target user, and prints the app-specific Home URL. + +If prompted for a user. Log into 1Password and use `beltran.bulbarella@datadoghq.com.engrumdev` diff --git a/test/apps/sf-lwc-app/config/project-scratch-def.json b/test/apps/sf-lwc-app/config/project-scratch-def.json new file mode 100644 index 0000000000..09a39a14e0 --- /dev/null +++ b/test/apps/sf-lwc-app/config/project-scratch-def.json @@ -0,0 +1,10 @@ +{ + "orgName": "SF LWC App", + "edition": "Developer", + "features": ["EnableSetPasswordInApi"], + "settings": { + "lightningExperienceSettings": { + "enableS1DesktopEnabled": true + } + } +} diff --git a/test/apps/sf-lwc-app/force-app/main/default/applications/SF_LWC_App.app-meta.xml b/test/apps/sf-lwc-app/force-app/main/default/applications/SF_LWC_App.app-meta.xml new file mode 100644 index 0000000000..0ba716cab3 --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/applications/SF_LWC_App.app-meta.xml @@ -0,0 +1,24 @@ + + + + Tab + SF_LWC_App_Home + Large + false + Flexipage + standard-home + + Small + Large + false + false + false + false + + Standard + all + standard-home + Product_Explorer + Lightning + SF_LWC_App_UtilityBar + diff --git a/test/apps/sf-lwc-app/force-app/main/default/cspTrustedSites/browser_intake_datadoghq_com.cspTrustedSite-meta.xml b/test/apps/sf-lwc-app/force-app/main/default/cspTrustedSites/browser_intake_datadoghq_com.cspTrustedSite-meta.xml new file mode 100644 index 0000000000..ecf051c561 --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/cspTrustedSites/browser_intake_datadoghq_com.cspTrustedSite-meta.xml @@ -0,0 +1,15 @@ + + + false + false + All + Datadog browser RUM intake for US1 + https://browser-intake-datadoghq.com + true + true + false + false + false + false + false + diff --git a/test/apps/sf-lwc-app/force-app/main/default/flexipages/Product_Explorer.flexipage-meta.xml b/test/apps/sf-lwc-app/force-app/main/default/flexipages/Product_Explorer.flexipage-meta.xml new file mode 100644 index 0000000000..142b4f2274 --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/flexipages/Product_Explorer.flexipage-meta.xml @@ -0,0 +1,18 @@ + + + + + + productExplorer + productExplorer + + + main + Region + + Product Explorer + + AppPage + diff --git a/test/apps/sf-lwc-app/force-app/main/default/flexipages/SF_LWC_App_Home.flexipage-meta.xml b/test/apps/sf-lwc-app/force-app/main/default/flexipages/SF_LWC_App_Home.flexipage-meta.xml new file mode 100644 index 0000000000..109a44e956 --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/flexipages/SF_LWC_App_Home.flexipage-meta.xml @@ -0,0 +1,18 @@ + + + + + + customActionButtons + customActionButtons + + + top + Region + + SF LWC App Home + + HomePage + diff --git a/test/apps/sf-lwc-app/force-app/main/default/flexipages/SF_LWC_App_UtilityBar.flexipage-meta.xml b/test/apps/sf-lwc-app/force-app/main/default/flexipages/SF_LWC_App_UtilityBar.flexipage-meta.xml new file mode 100644 index 0000000000..a933d40747 --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/flexipages/SF_LWC_App_UtilityBar.flexipage-meta.xml @@ -0,0 +1,76 @@ + + + + + + + applicationId + 1397744d-34f4-4a6a-a735-801e31c18221 + + + clientToken + pub2ad3fe2578f01b9f329bd0ea4a2f08c5 + + + site + datadoghq.com + + + service + browser-sdk-sandbox + + + env + dev + + + eager + decorator + true + + + height + decorator + 240 + + + icon + decorator + metrics + + + label + decorator + Datadog Init + + + scrollable + decorator + false + + + width + decorator + 320 + + datadogInit + datadogInit + + + utilityItems + Region + + + backgroundComponents + Background + + SF LWC App UtilityBar + + UtilityBar + diff --git a/test/apps/sf-lwc-app/force-app/main/default/lwc/customActionButtons/customActionButtons.css b/test/apps/sf-lwc-app/force-app/main/default/lwc/customActionButtons/customActionButtons.css new file mode 100644 index 0000000000..d3b9b307e9 --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/lwc/customActionButtons/customActionButtons.css @@ -0,0 +1,49 @@ +.action-card { + background: #ffffff; + border: 1px solid #d8dde6; + border-radius: 0.75rem; + box-shadow: 0 6px 18px rgba(24, 24, 24, 0.08); + margin: 1.5rem auto; + max-width: 48rem; + padding: 1.5rem; +} + +.action-card__header h2 { + color: #032d60; + font-size: 1.25rem; + margin: 0 0 0.25rem; +} + +.action-card__header p { + color: #5c5c5c; + margin: 0; +} + +.action-card__buttons { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; + margin-top: 1rem; +} + +.action-card__status { + color: #032d60; + font-size: 0.875rem; + margin-top: 1rem; +} + +.selector-probe { + background: #0176d3; + border: 1px solid #0176d3; + border-radius: 0.25rem; + color: #ffffff; + cursor: pointer; + font: inherit; + min-height: 2rem; + padding: 0 1rem; +} + +.selector-probe:hover { + background: #014486; + border-color: #014486; +} diff --git a/test/apps/sf-lwc-app/force-app/main/default/lwc/customActionButtons/customActionButtons.html b/test/apps/sf-lwc-app/force-app/main/default/lwc/customActionButtons/customActionButtons.html new file mode 100644 index 0000000000..538a60b3fd --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/lwc/customActionButtons/customActionButtons.html @@ -0,0 +1,135 @@ + \ No newline at end of file diff --git a/test/apps/sf-lwc-app/force-app/main/default/lwc/customActionButtons/customActionButtons.js b/test/apps/sf-lwc-app/force-app/main/default/lwc/customActionButtons/customActionButtons.js new file mode 100644 index 0000000000..43ab0391d2 --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/lwc/customActionButtons/customActionButtons.js @@ -0,0 +1,123 @@ +import { LightningElement, track } from 'lwc' + +export default class CustomActionButtons extends LightningElement { + @track lastActionName + @track lastErrorName + @track lastResourceName + @track lastSelectorProbe + + handleActionClick(event) { + const actionName = event.currentTarget.dataset.actionName + this.lastActionName = actionName + + window.DD_RUM?.addAction(actionName, { + source: 'salesforce-home-button', + pathname: window.location?.pathname, + }) + } + + handleErrorClick(event) { + const errorName = event.currentTarget.dataset.errorName + this.lastErrorName = errorName + + window.DD_RUM?.addError(new Error(errorName), { + source: 'salesforce-home-button', + pathname: window.location?.pathname, + }) + } + + handleRuntimeErrorClick() { + throw new Error('salesforce direct runtime error test') + } + + handleLongTaskClick() { + window.setTimeout(() => { + const start = performance.now() + while (performance.now() - start < 750) { + /* spin */ + } + }, 0) + } + + // Vitals: report a completed duration vital directly. + handleAddDurationVitalClick() { + window.DD_RUM?.addDurationVital('salesforce.duration_vital_test', { + startTime: Date.now() - 250, + duration: 250, + description: 'salesforce duration vital test', + context: { + source: 'salesforce-home-button', + }, + }) + } + + // Vitals: measure a live 300ms window. + handleStartStopVitalClick() { + const vital = window.DD_RUM?.startDurationVital('salesforce.start_stop_vital_test', { + description: 'salesforce start-stop vital test', + context: { + source: 'salesforce-home-button', + }, + }) + + setTimeout(() => window.DD_RUM?.stopDurationVital(vital), 300) + } + + async handleFetchResourceClick() { + const token = `dd-fetch-test-${Date.now()}` + const url = this.getResourceTestUrl(token) + this.lastResourceName = `fetch: ${token}` + + try { + const response = await window.fetch(url, { cache: 'no-store' }) + await response.text() + } catch (error) { + window.DD_RUM?.addError(error, { + source: 'salesforce-resource-button', + resourceType: 'fetch', + url, + }) + } + } + + handleXhrResourceClick() { + const token = `dd-xhr-test-${Date.now()}` + const url = this.getResourceTestUrl(token) + this.lastResourceName = `xhr: ${token}` + + const xhr = new window.XMLHttpRequest() + xhr.open('GET', url) + xhr.onerror = () => { + window.DD_RUM?.addError(new Error('salesforce xhr resource test failed'), { + source: 'salesforce-resource-button', + resourceType: 'xhr', + url, + }) + } + xhr.send() + } + + handleSelectorProbeClick(event) { + const innerActionName = event.currentTarget.getAttribute('data-dd-action-name') + this.lastSelectorProbe = innerActionName + + window.DD_RUM?.addAction('selector probe internal target', { + source: 'salesforce-selector-probe', + currentTargetActionName: innerActionName, + currentTargetTagName: event.currentTarget.tagName, + targetTagName: event.target.tagName, + composedPath: this.getComposedPathNames(event), + }) + } + + getResourceTestUrl(token) { + return `https://sample-json-api.com/products/1?${token}` + } + + getComposedPathNames(event) { + return event + .composedPath() + .slice(0, 6) + .map((target) => target.tagName || target.nodeName || String(target)) + } +} diff --git a/test/apps/sf-lwc-app/force-app/main/default/lwc/customActionButtons/customActionButtons.js-meta.xml b/test/apps/sf-lwc-app/force-app/main/default/lwc/customActionButtons/customActionButtons.js-meta.xml new file mode 100644 index 0000000000..a183d52180 --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/lwc/customActionButtons/customActionButtons.js-meta.xml @@ -0,0 +1,9 @@ + + + 65.0 + true + Custom Action Buttons + + lightning__HomePage + + diff --git a/test/apps/sf-lwc-app/force-app/main/default/lwc/datadogInit/datadogInit.html b/test/apps/sf-lwc-app/force-app/main/default/lwc/datadogInit/datadogInit.html new file mode 100644 index 0000000000..cc340bc4c9 --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/lwc/datadogInit/datadogInit.html @@ -0,0 +1 @@ + diff --git a/test/apps/sf-lwc-app/force-app/main/default/lwc/datadogInit/datadogInit.js b/test/apps/sf-lwc-app/force-app/main/default/lwc/datadogInit/datadogInit.js new file mode 100644 index 0000000000..a944a40c1e --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/lwc/datadogInit/datadogInit.js @@ -0,0 +1,74 @@ +import { LightningElement, api, wire } from 'lwc' +import { NavigationMixin, CurrentPageReference } from 'lightning/navigation' +import datadogRumSlim from '@salesforce/resourceUrl/datadog_rum_slim' +import { loadScript } from 'lightning/platformResourceLoader' + +let datadogInitialization +let lastStartedUrl + +export default class DatadogInit extends NavigationMixin(LightningElement) { + @api applicationId = '1397744d-34f4-4a6a-a735-801e31c18221' + @api clientToken = 'pub2ad3fe2578f01b9f329bd0ea4a2f08c5' + @api site = 'datadoghq.com' + @api service = 'my-salesforce-app' + @api env = 'dev' + @api trackViewsManually + + connectedCallback() { + this.initialize() + } + + @wire(CurrentPageReference) + handleCurrentPageReference(pageReference) { + if (!pageReference) { + return + } + + this.initialize() + + if (window.DD_RUM) { + this.startViewForPageReference(pageReference) + } + } + + startViewForPageReference(pageReference) { + const urlPromise = this[NavigationMixin.GenerateUrl](pageReference) + urlPromise.then((url) => { + if (url === lastStartedUrl) { + return + } + lastStartedUrl = url + const absoluteUrl = new URL(url, window.location.origin).href + window.DD_RUM.startView({ name: url, url: absoluteUrl }) + }) + } + + initialize() { + if (!datadogInitialization) { + datadogInitialization = this.loadDatadogRum() + } + } + + loadDatadogRum() { + return loadScript(this, datadogRumSlim).then(() => { + const initConfig = { + applicationId: this.applicationId, + clientToken: this.clientToken, + env: this.env, + service: this.service, + site: this.site, + trackViewsManually: true, + trackEarlyRequests: true, + trackLongTasks: true, + trackResources: true, + trackUserInteractions: true, + } + window.DD_RUM.init(initConfig) + lastStartedUrl = window.location.pathname + window.location.search + window.location.hash + window.DD_RUM.startView({ + name: lastStartedUrl, + url: window.location.href, + }) + }) + } +} diff --git a/test/apps/sf-lwc-app/force-app/main/default/lwc/datadogInit/datadogInit.js-meta.xml b/test/apps/sf-lwc-app/force-app/main/default/lwc/datadogInit/datadogInit.js-meta.xml new file mode 100644 index 0000000000..89e7497b8b --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/lwc/datadogInit/datadogInit.js-meta.xml @@ -0,0 +1,19 @@ + + + 64.0 + true + Datadog Init + + lightning__UtilityBar + + + + + + + + + + + + diff --git a/test/apps/sf-lwc-app/force-app/main/default/lwc/jsconfig.json b/test/apps/sf-lwc-app/force-app/main/default/lwc/jsconfig.json new file mode 100644 index 0000000000..febe5fd90e --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/lwc/jsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "experimentalDecorators": true, + "baseUrl": ".", + "paths": { + "c/*": ["*"] + } + }, + "include": ["**/*", "../../../../.sfdx/typings/lwc/**/*.d.ts"], + "typeAcquisition": { + "include": ["jest"] + } +} diff --git a/test/apps/sf-lwc-app/force-app/main/default/lwc/productExplorer/productExplorer.css b/test/apps/sf-lwc-app/force-app/main/default/lwc/productExplorer/productExplorer.css new file mode 100644 index 0000000000..47363fbc8c --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/lwc/productExplorer/productExplorer.css @@ -0,0 +1,86 @@ +.explorer { + display: grid; + gap: 1rem; + grid-template-columns: minmax(14rem, 18rem) minmax(0, 1fr); + padding: 1.5rem; +} + +.product-list { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.product-button { + background: #ffffff; + border: 1px solid #d8dde6; + border-radius: 0.5rem; + color: #181818; + cursor: pointer; + display: grid; + gap: 0.25rem; + padding: 1rem; + text-align: left; +} + +.product-button:hover, +.product-button_selected { + border-color: #0176d3; + box-shadow: 0 0 0 1px #0176d3 inset; +} + +.product-name { + color: #032d60; + font-size: 1rem; + font-weight: 700; +} + +.product-meta, +.product-price { + color: #5c5c5c; + font-size: 0.875rem; +} + +.editor { + background: #ffffff; + border: 1px solid #d8dde6; + border-radius: 0.5rem; + padding: 1rem; +} + +.editor-header { + align-items: flex-start; + display: flex; + justify-content: space-between; + gap: 1rem; + margin-bottom: 1rem; +} + +.editor-header h2 { + color: #032d60; + font-size: 1.25rem; + margin: 0 0 0.25rem; +} + +.editor-header p { + color: #5c5c5c; + margin: 0; +} + +.editor-grid { + display: grid; + gap: 0.75rem; + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +lightning-textarea { + display: block; + margin-top: 0.75rem; +} + +@media (max-width: 48rem) { + .explorer, + .editor-grid { + grid-template-columns: 1fr; + } +} diff --git a/test/apps/sf-lwc-app/force-app/main/default/lwc/productExplorer/productExplorer.html b/test/apps/sf-lwc-app/force-app/main/default/lwc/productExplorer/productExplorer.html new file mode 100644 index 0000000000..4dff067cf1 --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/lwc/productExplorer/productExplorer.html @@ -0,0 +1,104 @@ + \ No newline at end of file diff --git a/test/apps/sf-lwc-app/force-app/main/default/lwc/productExplorer/productExplorer.js b/test/apps/sf-lwc-app/force-app/main/default/lwc/productExplorer/productExplorer.js new file mode 100644 index 0000000000..4f66655b99 --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/lwc/productExplorer/productExplorer.js @@ -0,0 +1,90 @@ +import { LightningElement } from 'lwc' + +const INITIAL_PRODUCTS = [ + { + id: 'dynamo-x2', + name: 'Dynamo X2', + category: 'Mountain', + level: 'Expert', + material: 'Carbon', + msrp: 7200, + motor: 'High torque 252 watt', + battery: '702Wh', + brakes: 'Hydraulic disc', + description: 'A high-output trail bike for technical climbs and fast descents.', + }, + { + id: 'electra-x2', + name: 'Electra X2', + category: 'Road', + level: 'Intermediate', + material: 'Aluminum', + msrp: 4300, + motor: 'Balanced 250 watt', + battery: '502Wh', + brakes: 'Dual-pivot caliper', + description: 'A lightweight commuter with fast handling and comfortable range.', + }, + { + id: 'fuse-x2', + name: 'Fuse X2', + category: 'Commuter', + level: 'Beginner', + material: 'Aluminum', + msrp: 2600, + motor: 'Efficient 250 watt', + battery: '402Wh', + brakes: 'Mechanical disc', + description: 'An approachable city bike for everyday testing workflows.', + }, +] + +export default class ProductExplorer extends LightningElement { + products = cloneProducts(INITIAL_PRODUCTS) + selectedProductId = INITIAL_PRODUCTS[0].id + + categoryOptions = toOptions(['Commuter', 'Mountain', 'Road']) + levelOptions = toOptions(['Beginner', 'Intermediate', 'Expert']) + materialOptions = toOptions(['Aluminum', 'Carbon', 'Steel']) + + get productsForView() { + return this.products.map((product) => ({ + ...product, + buttonClass: product.id === this.selectedProductId ? 'product-button product-button_selected' : 'product-button', + })) + } + + get selectedProduct() { + return this.products.find((product) => product.id === this.selectedProductId) + } + + handleSelectProduct(event) { + this.selectedProductId = event.currentTarget.dataset.id + } + + handleFieldChange(event) { + const field = event.currentTarget.dataset.field + const value = field === 'msrp' ? Number(event.detail.value) : event.detail.value + + this.products = this.products.map((product) => + product.id === this.selectedProductId + ? { + ...product, + [field]: value, + } + : product + ) + } + + handleReset() { + this.products = cloneProducts(INITIAL_PRODUCTS) + } +} + +function cloneProducts(products) { + return products.map((product) => ({ ...product })) +} + +function toOptions(values) { + return values.map((value) => ({ label: value, value })) +} diff --git a/test/apps/sf-lwc-app/force-app/main/default/lwc/productExplorer/productExplorer.js-meta.xml b/test/apps/sf-lwc-app/force-app/main/default/lwc/productExplorer/productExplorer.js-meta.xml new file mode 100644 index 0000000000..27e42c077d --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/lwc/productExplorer/productExplorer.js-meta.xml @@ -0,0 +1,9 @@ + + + 65.0 + true + Product Explorer + + lightning__AppPage + + diff --git a/test/apps/sf-lwc-app/force-app/main/default/permissionsets/SF_LWC_App.permissionset-meta.xml b/test/apps/sf-lwc-app/force-app/main/default/permissionsets/SF_LWC_App.permissionset-meta.xml new file mode 100644 index 0000000000..8fa0e908a5 --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/permissionsets/SF_LWC_App.permissionset-meta.xml @@ -0,0 +1,13 @@ + + + + SF_LWC_App + true + + false + + + Product_Explorer + Visible + + diff --git a/test/apps/sf-lwc-app/force-app/main/default/staticresources/datadog_rum_slim.resource-meta.xml b/test/apps/sf-lwc-app/force-app/main/default/staticresources/datadog_rum_slim.resource-meta.xml new file mode 100644 index 0000000000..811f7d8899 --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/staticresources/datadog_rum_slim.resource-meta.xml @@ -0,0 +1,5 @@ + + + Public + application/javascript + diff --git a/test/apps/sf-lwc-app/force-app/main/default/tabs/Product_Explorer.tab-meta.xml b/test/apps/sf-lwc-app/force-app/main/default/tabs/Product_Explorer.tab-meta.xml new file mode 100644 index 0000000000..e8104b8cc4 --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/tabs/Product_Explorer.tab-meta.xml @@ -0,0 +1,7 @@ + + + Created by Lightning App Builder + Product_Explorer + + Custom3: Sun + diff --git a/test/apps/sf-lwc-app/package.json b/test/apps/sf-lwc-app/package.json new file mode 100644 index 0000000000..3e4c9820d2 --- /dev/null +++ b/test/apps/sf-lwc-app/package.json @@ -0,0 +1,8 @@ +{ + "name": "sf-lwc-app", + "private": true, + "description": "Salesforce Lightning app for browser-sdk Salesforce testing", + "scripts": { + "setup": "node scripts/setup.mjs" + } +} diff --git a/test/apps/sf-lwc-app/scripts/setup.mjs b/test/apps/sf-lwc-app/scripts/setup.mjs new file mode 100644 index 0000000000..0154302639 --- /dev/null +++ b/test/apps/sf-lwc-app/scripts/setup.mjs @@ -0,0 +1,92 @@ +import { copyFileSync, existsSync } from 'node:fs' +import { dirname, resolve } from 'node:path' +import { spawnSync } from 'node:child_process' +import { fileURLToPath } from 'node:url' + +const args = process.argv.slice(2) +const orgArgs = getOrgArgs(args) + +syncDatadogBundle() +run('sf', ['project', 'deploy', 'start', ...args]) +assignPermissionSet() +printAppUrl() + +// Copies the Datadog RUM slim bundle to the app's staticresource +function syncDatadogBundle() { + const appDir = resolve(dirname(fileURLToPath(import.meta.url)), '..') + const browserSdkDir = resolve(appDir, '..', '..', '..') + const sourceBundle = resolve(browserSdkDir, 'packages/browser-rum-slim/bundle/datadog-rum-slim.js') + const targetBundle = resolve(appDir, 'force-app/main/default/staticresources/datadog_rum_slim.js') + + if (!existsSync(sourceBundle)) { + throw new Error( + `Missing Datadog RUM slim bundle at ${sourceBundle}. Run from a browser-sdk checkout with the bundle built.` + ) + } + + copyFileSync(sourceBundle, targetBundle) + console.log(`Synced ${targetBundle}`) +} + +function run(command, args) { + const result = spawnSync(command, args, { stdio: 'inherit' }) + if (result.status !== 0) { + process.exit(result.status ?? 1) + } +} + +function assignPermissionSet() { + const result = spawnSync('sf', ['org', 'assign', 'permset', '-n', 'SF_LWC_App', ...orgArgs], { + encoding: 'utf8', + }) + + if (result.status === 0) { + console.log('Assigned SF_LWC_App permission set') + return + } + + const output = `${result.stdout}\n${result.stderr}` + if (output.includes('Duplicate PermissionSetAssignment')) { + console.log('SF_LWC_App permission set already assigned') + return + } + + process.stdout.write(result.stdout) + process.stderr.write(result.stderr) + process.exit(result.status ?? 1) +} + +function printAppUrl() { + const result = spawnSync( + 'sf', + ['org', 'open', '-p', '/lightning/app/c__SF_LWC_App/page/home', '--url-only', '--json', ...orgArgs], + { + encoding: 'utf8', + } + ) + + if (result.status !== 0) { + process.stderr.write(result.stderr) + process.exit(result.status ?? 1) + } + + const { result: openResult } = JSON.parse(result.stdout) + console.log(`Home: ${openResult.url}`) +} + +function getOrgArgs(args) { + const orgArgs = [] + + for (let index = 0; index < args.length; index += 1) { + const arg = args[index] + + if ((arg === '-o' || arg === '--target-org') && args[index + 1]) { + orgArgs.push(arg, args[index + 1]) + index += 1 + } else if (arg.startsWith('--target-org=')) { + orgArgs.push(arg) + } + } + + return orgArgs +} diff --git a/test/apps/sf-lwc-app/sfdx-project.json b/test/apps/sf-lwc-app/sfdx-project.json new file mode 100644 index 0000000000..a68e6367f3 --- /dev/null +++ b/test/apps/sf-lwc-app/sfdx-project.json @@ -0,0 +1,10 @@ +{ + "packageDirectories": [ + { + "path": "force-app", + "default": true + } + ], + "namespace": "", + "sourceApiVersion": "65.0" +}