-
Notifications
You must be signed in to change notification settings - Fork 190
Expand file tree
/
Copy pathsalesforce-apps.ts
More file actions
167 lines (142 loc) · 5.65 KB
/
Copy pathsalesforce-apps.ts
File metadata and controls
167 lines (142 loc) · 5.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { resolve } from 'node:path'
import { tmpdir } from 'node:os'
import { parseArgs } from 'node:util'
import { printLog, runMain } from './lib/executionUtils.ts'
import { getSfLwcClientId, getSfLwcInstanceUrl, getSfLwcJwtPrivateKey, getSfLwcUsername } from './lib/secrets.ts'
import { command } from './lib/command.ts'
const repositoryRoot = resolve(import.meta.dirname, '..')
const TARGET_ORG = 'sf-lwc-ci'
type AppKey = 'lwc' | 'experience-cloud' | 'experience-cloud-headmarkup'
const APP_KEYS: AppKey[] = ['lwc', 'experience-cloud', 'experience-cloud-headmarkup']
const APPS: Record<AppKey, { dir: string; url: string; siteName?: string }> = {
lwc: {
dir: resolve(repositoryRoot, 'test/apps/sf-lwc-app'),
url: new URL('/lightning/app/c__SF_LWC_App/page/home', getSfLwcInstanceUrl()).href,
},
'experience-cloud': {
dir: resolve(repositoryRoot, 'test/apps/sf-experience-app'),
url: new URL('sfexperiencecloud/', getSalesforceSiteUrl()).href,
siteName: 'SF Experience Cloud App',
},
'experience-cloud-headmarkup': {
dir: resolve(repositoryRoot, 'test/apps/sf-experience-headmarkup-app'),
url: new URL('sfexperienceheadmarkup/', getSalesforceSiteUrl()).href,
siteName: 'SF Experience Cloud Head Markup',
},
}
// Name of the corresponding app in scripts/build/build-test-apps.ts, used to (re)build the app
// (and refresh its RUM Salesforce bundle static resource) before deploying it.
const BUILD_APP_NAME: Record<AppKey, string> = {
lwc: 'sf-lwc-app',
'experience-cloud': 'sf-experience-app',
'experience-cloud-headmarkup': 'sf-experience-headmarkup-app',
}
const SUPPORTED_COMMANDS = ['deploy-apps', 'get-urls']
runMain(() => {
const { values, positionals } = parseArgs({
allowPositionals: true,
options: {
help: {
type: 'boolean',
short: 'h',
},
app: {
type: 'string',
},
},
})
if (values.help) {
showUsageAndExit()
}
if (positionals.length !== 1) {
throw new Error(
`Usage: node scripts/salesforce-apps.ts <${SUPPORTED_COMMANDS.join('|')}> [--app ${APP_KEYS.join('|')}]`
)
}
const commandName = positionals[0]
const appKeys = resolveAppKeys(values.app)
switch (commandName) {
case 'deploy-apps':
deployApp(appKeys)
break
case 'get-urls':
printUrl(appKeys)
break
default:
throw new Error(`Unknown command "${commandName ?? ''}". Expected: ${SUPPORTED_COMMANDS.join('|')}`)
}
})
function showUsageAndExit() {
console.log(`Usage: node scripts/salesforce-apps.ts <command> [--app ${APP_KEYS.join('|')}]`)
console.log('')
console.log('Commands:')
console.log(' deploy-apps Deploy the app(s) to the Salesforce org. To be done only when an app is updated.')
console.log(' get-urls Get the authenticated URL of the app(s).')
process.exit(0)
}
function resolveAppKeys(appFlag: string | undefined): AppKey[] {
if (!appFlag) {
return APP_KEYS
}
if (!APP_KEYS.includes(appFlag as AppKey)) {
throw new Error(`Unknown --app "${appFlag}". Expected one of: ${APP_KEYS.join('|')}`)
}
return [appFlag as AppKey]
}
function authenticate(targetOrg: string, cwd: string) {
// Temporary directory holding the JWT private key for the duration of authentication.
// Using a unique temp dir avoids collisions when multiple CI jobs run in parallel.
const keyDirectory = mkdtempSync(resolve(tmpdir(), 'sf-lwc-jwt-'))
const serverKeyPath = resolve(keyDirectory, 'server.key')
try {
writeFileSync(serverKeyPath, Buffer.from(getSfLwcJwtPrivateKey(), 'base64').toString('utf8'), { mode: 0o600 })
printLog(`Authenticating Salesforce CLI alias ${targetOrg}...`)
command`sf org login jwt --client-id ${getSfLwcClientId()} --jwt-key-file ${serverKeyPath} --username ${getSfLwcUsername()} --instance-url ${getSfLwcInstanceUrl()} --alias ${targetOrg}`
.withCurrentWorkingDirectory(cwd)
.withLogs()
.run()
printLog(`Salesforce CLI authenticated as ${targetOrg}.`)
} finally {
rmSync(keyDirectory, { recursive: true, force: true })
}
}
function deployApp(appKeys: AppKey[]) {
printLog('Building RUM Salesforce bundle...')
command`yarn workspace @datadog/browser-rum-slim build:bundle`.withLogs().run()
printLog('Building Salesforce apps...')
command`yarn build:apps ${appKeys.flatMap((appKey) => ['--app', BUILD_APP_NAME[appKey]])}`
.withCurrentWorkingDirectory(repositoryRoot)
.withLogs()
.run()
for (const appKey of appKeys) {
const { dir, siteName } = APPS[appKey]
authenticate(TARGET_ORG, dir)
rmSync(resolve(dir, '.sf'), { recursive: true, force: true })
printLog(`Deploying Salesforce ${appKey} app to ${TARGET_ORG}...`)
command`sf project deploy start --target-org ${TARGET_ORG} --source-dir force-app --ignore-conflicts --concise`
.withCurrentWorkingDirectory(dir)
.withLogs()
.run()
printLog(`Salesforce ${appKey} app deployed.`)
if (siteName) {
// Metadata deploys only update the site's draft version; publishing is a separate step
// required to make the changes live on an Experience Builder / LWR site.
printLog(`Publishing Salesforce site "${siteName}"...`)
command`sf community publish --name ${siteName} --target-org ${TARGET_ORG}`
.withCurrentWorkingDirectory(dir)
.withLogs()
.run()
printLog(`Salesforce site "${siteName}" published.`)
}
}
}
function printUrl(appKeys: AppKey[]): void {
for (const appKey of appKeys) {
const { url } = APPS[appKey]
console.log(url)
}
}
function getSalesforceSiteUrl(): string {
return getSfLwcInstanceUrl().replace('.my.salesforce.com', '.my.site.com')
}