Skip to content

Commit 83ae4e9

Browse files
TMTrevisanmrtoddles11
authored andcommitted
feat: Add native Windows PowerShell rollout scripts and overhaul README
Resolves compatibility issues for Windows users unable to execute bash scripts smoothly. Overhaul README to provide clear instructions for multiple operating systems.
1 parent 86200d0 commit 83ae4e9

3 files changed

Lines changed: 317 additions & 12 deletions

File tree

README.md

Lines changed: 73 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,73 @@
1-
# README
2-
3-
# Prerequisites
4-
1. LSC4CE Org with 2GP package installed is available
5-
2. Org must have the permission set licenses "Health Cloud Starter" and "Life Science Commercial"
6-
7-
# Steps to load data into Org
8-
1. Clone the repo “https://github.com/SalesforceLabs/LSStarterConfig.git” to local on a laptop
9-
2. Open the SFDX project “LSStarterConfig” in Visual Studio Code
10-
3. Authorize the LSC4CE Org and connect to the Org.
11-
4. Open Terminal in Visual Studio Code and run the command “npm install” from "LSStarterConfig" folder
12-
5. From Terminal in Visual Studio Code run the command "sh Scripts/sh/data_load.sh" from "LSStarterConfig" folder
1+
# LS Starter Config
2+
3+
This repository contains the starter configuration records, profiles, and trigger handlers necessary to initialize a Life Sciences Cloud org or sandbox.
4+
5+
## Prerequisites
6+
7+
Before deploying this package to your org, ensure your environment is set up correctly:
8+
9+
1. **Target Org Requirements:**
10+
* A Life Sciences Cloud (LSC4CE) Org with the 2GP package installed.
11+
* The org MUST have the following permission set licenses available:
12+
* `Health Cloud Starter`
13+
* `Life Science Commercial`
14+
2. **Local Environment Requirements:**
15+
* [Salesforce CLI (`sf`)](https://developer.salesforce.com/tools/salesforcecli) installed.
16+
* [Node.js and npm](https://nodejs.org/en) installed.
17+
* *(Mac/Linux only)* `jq` installed (`brew install jq` or `apt-get install jq`).
18+
19+
---
20+
21+
## Deployment Steps
22+
23+
Choose the appropriate tab below depending on your operating system (Mac/Linux vs. Windows).
24+
25+
### 1. Authorize Your Org
26+
First, authenticate the Salesforce CLI with the exact org where you wish to deploy these configurations.
27+
28+
**For Production:**
29+
```bash
30+
sf org login web --set-default
31+
```
32+
33+
**For Sandboxes:**
34+
```bash
35+
sf org login web -r https://test.salesforce.com --set-default
36+
```
37+
*(Follow the browser prompts to log in and allow CLI access).*
38+
39+
### 2. Install Dependencies
40+
Install the local tooling required to deploy the project (like Prettier and testing frameworks):
41+
```bash
42+
npm install
43+
```
44+
45+
### 3. Run the Data Loader Script
46+
This script will deploy the custom profile, seed metadata framework records, push configuration records, and activate all necessary trigger handlers.
47+
48+
**Mac / Linux:**
49+
```bash
50+
sh Scripts/sh/data_load.sh
51+
```
52+
53+
**Windows (PowerShell):**
54+
```powershell
55+
.\Scripts\ps1\data_load.ps1
56+
```
57+
58+
---
59+
60+
## Troubleshooting
61+
62+
### Error: `Unknown user permission: EnableCommunityAppLauncher`
63+
If your deployment fails loudly on the `LSC Custom Profile` step stating that `EnableCommunityAppLauncher` is an unknown user permission, this means your specific org shape does not support this permission.
64+
65+
**Fix:**
66+
1. Open `PackageComponents/profiles/LSC Custom Profile.profile-meta.xml`.
67+
2. Locate the `<userPermissions>` block containing `<name>EnableCommunityAppLauncher</name>` (around line 93).
68+
3. Delete that entire block.
69+
4. Re-run the data loader script.
70+
71+
### Warnings: `DUPLICATE_VALUE` on tree import
72+
During step 3 (Data Loader), you may see a table of errors stating `duplicate value found: Name duplicates value on record with id...`.
73+
This is **safe to ignore**. It simply means that your org already contains the standard Life Sciences metadata categories provided by the package, so the CLI skipped recreating them.
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
<#
2+
.SYNOPSIS
3+
Activates/Deactivates LifeScienceTriggerHandler records by DeveloperName.
4+
5+
.DESCRIPTION
6+
- Uses the Tooling API for setup entity updates (no REST calls).
7+
- Accepts names via -Names or -File. If none provided, extracts DeveloperName values
8+
from TriggerHandlers.ts in the current directory.
9+
10+
# Requirements: Salesforce CLI (sf or sfdx)
11+
12+
.EXAMPLE
13+
.\activate_trigger_handlers.ps1 -org myAlias -names "HandlerA,HandlerB"
14+
.\activate_trigger_handlers.ps1 -org myAlias -file handlers.txt
15+
.\activate_trigger_handlers.ps1 -org myAlias
16+
#>
17+
18+
param (
19+
[string]$org = "",
20+
[string]$names = "",
21+
[string]$file = "",
22+
[string]$apiVersion = "65.0",
23+
[switch]$deactivate,
24+
[switch]$verboseOut
25+
)
26+
27+
$ErrorActionPreference = "Stop"
28+
29+
# Use sf if available, fallback to sfdx
30+
$sfCli = "sf"
31+
if (-not (Get-Command $sfCli -ErrorAction SilentlyContinue)) {
32+
$sfCli = "sfdx"
33+
if (-not (Get-Command $sfCli -ErrorAction SilentlyContinue)) {
34+
Write-Error "Salesforce CLI (sf or sfdx) not found"
35+
exit 1
36+
}
37+
}
38+
39+
# --- 1. Get Authentication Info ---
40+
function Get-OrgAuth {
41+
param([string]$orgAlias)
42+
43+
$authJson = ""
44+
if ($sfCli -eq "sf") {
45+
if ([string]::IsNullOrWhiteSpace($orgAlias)) {
46+
$authJson = sf org display --json
47+
} else {
48+
$authJson = sf org display --json --target-org "$orgAlias"
49+
}
50+
} else {
51+
if ([string]::IsNullOrWhiteSpace($orgAlias)) {
52+
$authJson = sfdx force:org:display --json
53+
} else {
54+
$authJson = sfdx force:org:display --json -u "$orgAlias"
55+
}
56+
}
57+
58+
return $authJson | ConvertFrom-Json
59+
}
60+
61+
Write-Host "Retrieving Org Authentication..."
62+
$authObj = Get-OrgAuth -orgAlias $org
63+
64+
$accessToken = $authObj.result.accessToken
65+
$instanceUrl = $authObj.result.instanceUrl
66+
67+
if ([string]::IsNullOrEmpty($accessToken) -or [string]::IsNullOrEmpty($instanceUrl)) {
68+
Write-Error "Could not retrieve access token or instance URL. Ensure you are logged into a default org or provide -org."
69+
exit 1
70+
}
71+
72+
if ($verboseOut) {
73+
Write-Host "Instance URL: $instanceUrl"
74+
Write-Host "API Version: $apiVersion"
75+
}
76+
77+
78+
# --- 2. Determine Developer Names to Process ---
79+
$developerNames = @()
80+
81+
if (-not [string]::IsNullOrWhiteSpace($names)) {
82+
$developerNames = $names -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' }
83+
}
84+
elseif (-not [string]::IsNullOrWhiteSpace($file)) {
85+
if (-not (Test-Path $file)) {
86+
Write-Error "File not found: $file"
87+
exit 1
88+
}
89+
$developerNames = Get-Content $file | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' }
90+
}
91+
else {
92+
# Default behavior: parse TriggerHandlers.ts
93+
Write-Host "No -names or -file provided. Parsing DeveloperName from TriggerHandlers.ts..."
94+
$tsPath = "TriggerHandlers.ts"
95+
if (-not (Test-Path $tsPath)) {
96+
# Assume it might be executed from the root, try that path.
97+
$tsPath = "TriggerHandlers\TriggerHandlers.ts"
98+
}
99+
100+
if (Test-Path $tsPath) {
101+
$content = Get-Content $tsPath
102+
# Regex to match DeveloperName: "value" or 'DeveloperName': "value"
103+
foreach ($line in $content) {
104+
if ($line -match "['""]?DeveloperName['""]?\s*:\s*""([^""]+)""") {
105+
$developerNames += $matches[1]
106+
}
107+
}
108+
$developerNames = $developerNames | Select-Object -Unique
109+
} else {
110+
Write-Error "TriggerHandlers.ts not found. Please specify -names or -file."
111+
exit 1
112+
}
113+
}
114+
115+
if ($developerNames.Count -eq 0) {
116+
Write-Error "No DeveloperName values found."
117+
exit 1
118+
}
119+
120+
# --- 3. Processing Handlers ---
121+
$headers = @{
122+
"Authorization" = "Bearer $accessToken"
123+
"Content-Type" = "application/json"
124+
}
125+
126+
$successCount = 0
127+
$skippedCount = 0
128+
$notFoundCount = 0
129+
$failedCount = 0
130+
131+
foreach ($devName in $developerNames) {
132+
if ($verboseOut) { Write-Host "Processing DeveloperName: $devName" }
133+
134+
$soql = "SELECT Id, IsActive, DeveloperName FROM LifeScienceTriggerHandler WHERE DeveloperName = '$devName'"
135+
$encodedSoql = [uri]::EscapeDataString($soql)
136+
137+
$record = $null
138+
139+
# Tooling API Query
140+
$toolingUrl = "$instanceUrl/services/data/v$apiVersion/tooling/query?q=$encodedSoql"
141+
try {
142+
$toolingResponse = Invoke-RestMethod -Uri $toolingUrl -Headers $headers -Method Get
143+
if ($toolingResponse.totalSize -gt 0) {
144+
$record = $toolingResponse.records[0]
145+
}
146+
} catch {
147+
# Fallback to standard REST API Query
148+
$restUrl = "$instanceUrl/services/data/v$apiVersion/query?q=$encodedSoql"
149+
try {
150+
$restResponse = Invoke-RestMethod -Uri $restUrl -Headers $headers -Method Get
151+
if ($restResponse.totalSize -gt 0) {
152+
$record = $restResponse.records[0]
153+
}
154+
} catch { }
155+
}
156+
157+
if ($null -eq $record) {
158+
if ($verboseOut) { Write-Host " Not found in org." }
159+
$notFoundCount++
160+
continue
161+
}
162+
163+
$id = $record.Id
164+
$isActive = $record.IsActive
165+
$targetActive = -not $deactivate
166+
167+
if ($targetActive -eq $isActive) {
168+
if ($verboseOut) { Write-Host " Already in desired state. Skipping." }
169+
$skippedCount++
170+
continue
171+
}
172+
173+
$bodyMap = @{ "IsActive" = $targetActive }
174+
$bodyJson = $bodyMap | ConvertTo-Json -Compress
175+
176+
$updateSuccess = $false
177+
178+
# Attempt Standard sObject REST Update
179+
$updateStdUrl = "$instanceUrl/services/data/v$apiVersion/sobjects/LifeScienceTriggerHandler/$id"
180+
try {
181+
$updateResp = Invoke-RestMethod -Uri $updateStdUrl -Headers $headers -Method Patch -Body $bodyJson
182+
$updateSuccess = $true
183+
if ($verboseOut) { Write-Host " Updated via Standard REST." }
184+
} catch {
185+
# Attempt Tooling API Update
186+
$updateToolUrl = "$instanceUrl/services/data/v$apiVersion/tooling/sobjects/LifeScienceTriggerHandler/$id"
187+
try {
188+
$updateResp = Invoke-RestMethod -Uri $updateToolUrl -Headers $headers -Method Patch -Body $bodyJson
189+
$updateSuccess = $true
190+
if ($verboseOut) { Write-Host " Updated via Tooling API." }
191+
} catch { }
192+
}
193+
194+
if ($updateSuccess) {
195+
$successCount++
196+
} else {
197+
if ($verboseOut) { Write-Host " Update failed." }
198+
$failedCount++
199+
}
200+
}
201+
202+
Write-Host "`nDone. Summary:"
203+
$successLabel = if ($deactivate) { "Deactivated" } else { "Activated" }
204+
$skippedLabel = if ($deactivate) { "Already Inactive" } else { "Already Active" }
205+
Write-Host " $successLabel : $successCount"
206+
Write-Host " $skippedLabel : $skippedCount"
207+
Write-Host " Not Found : $notFoundCount"
208+
Write-Host " Failed : $failedCount"
209+
210+
if ($failedCount -gt 0) {
211+
exit 1
212+
}
213+
exit 0

Scripts/ps1/data_load.ps1

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# data_load.ps1
2+
# Deploys LSC Starter Configurations
3+
$ErrorActionPreference = "Stop"
4+
5+
Write-Host "Deploying configurations..."
6+
7+
# Verify Salesforce CLI is installed
8+
if (-not (Get-Command "sf" -ErrorAction SilentlyContinue) -and -not (Get-Command "sfdx" -ErrorAction SilentlyContinue)) {
9+
Write-Error "Salesforce CLI (sf or sfdx) could not be found. Please install the Salesforce CLI to proceed."
10+
exit 1
11+
}
12+
13+
# 1. Deploy the LSC Custom Profile
14+
Write-Host "Deploying LSC Custom Profile..."
15+
sf project deploy start -d "PackageComponents/profiles/LSC Custom Profile.profile-meta.xml" --json *> $null
16+
17+
# 2. Import Metadata Categories
18+
Write-Host "Importing LifeSciMetadataCategories..."
19+
# Suppress output; duplicates will fail silently as intended by the original script
20+
sf data import tree --plan LSConfig/lifeSciMetadataRecord/LifeSciMetadataCategory-plan.json --json *> $null
21+
22+
# 3. Deploy configuration records
23+
Write-Host "Deploying Config Records..."
24+
sf project deploy start -d LSConfig/lifeSciConfigRecord --json *> $null
25+
26+
# 4. Activate Trigger Handlers
27+
Write-Host "Activating Trigger Handlers..."
28+
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
29+
& "$scriptDir\activate_trigger_handlers.ps1" -file "TriggerHandlers\TriggerHandlers.ts"
30+
31+
Write-Host "Deployment Completed Successfuly!"

0 commit comments

Comments
 (0)