@salesforce/templates 66.7.13 → 66.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/generators/lightningEmbeddingGenerator.d.ts +6 -0
- package/lib/generators/lightningEmbeddingGenerator.js +68 -0
- package/lib/generators/lightningEmbeddingGenerator.js.map +1 -0
- package/lib/i18n/i18n.d.ts +6 -0
- package/lib/i18n/i18n.js +6 -0
- package/lib/i18n/i18n.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/templates/lightningembedding/default/default.css +5 -0
- package/lib/templates/lightningembedding/default/default.html +7 -0
- package/lib/templates/lightningembedding/default/default.js +5 -0
- package/lib/templates/lightningembedding/default/default.js-meta.xml +12 -0
- package/lib/templates/project/reactexternalapp/AGENT.md +3 -3
- package/lib/templates/project/reactexternalapp/CHANGELOG.md +428 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/package-lock.json +792 -2031
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/package.json +4 -4
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/AccountObjectDetailPage.tsx +7 -9
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/AccountSearch.tsx +7 -15
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/api/objectSearchService.ts +14 -19
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/NumericRangeFilter.tsx +9 -5
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/SearchFilter.tsx +5 -3
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/TextFilter.tsx +5 -3
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/hooks/useAsyncData.ts +11 -4
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/api/userProfileApi.ts +12 -11
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/authenticationConfig.ts +9 -9
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/context/AuthContext.tsx +21 -4
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/forms/auth-form.tsx +15 -1
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/hooks/form.tsx +1 -1
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/layouts/privateRouteLayout.tsx +2 -11
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/pages/ChangePassword.tsx +21 -5
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/pages/ForgotPassword.tsx +20 -5
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/pages/Login.tsx +20 -5
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/pages/Profile.tsx +80 -43
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/pages/Register.tsx +16 -5
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/pages/ResetPassword.tsx +20 -5
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/utils/helpers.ts +15 -52
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/api/graphqlClient.ts +13 -13
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/components/alerts/status-alert.tsx +11 -8
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/components/ui/input.tsx +1 -1
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/hooks/useAsyncData.ts +67 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/pages/AccountObjectDetailPage.tsx +7 -9
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/pages/AccountSearch.tsx +7 -15
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/routes.tsx +19 -25
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/tsconfig.json +4 -6
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/vite-env.d.ts +0 -3
- package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleAuthUtils.cls-meta.xml +1 -1
- package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleChangePassword.cls-meta.xml +1 -1
- package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleForgotPassword.cls-meta.xml +1 -1
- package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleLogin.cls-meta.xml +1 -1
- package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleRegistration.cls-meta.xml +1 -1
- package/lib/templates/project/reactexternalapp/_p_/_m_/package.xml +1 -1
- package/lib/templates/project/reactexternalapp/package.json +1 -1
- package/lib/templates/project/reactexternalapp/scripts/org-setup.config.json +0 -1
- package/lib/templates/project/reactexternalapp/scripts/org-setup.mjs +528 -44
- package/lib/templates/project/reactexternalapp/sfdx-project.json +1 -1
- package/lib/templates/project/reactinternalapp/AGENT.md +3 -3
- package/lib/templates/project/reactinternalapp/CHANGELOG.md +428 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/package-lock.json +784 -2036
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/package.json +5 -5
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/reactinternalapp.uibundle-meta.xml +1 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/AccountObjectDetailPage.tsx +7 -9
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/AccountSearch.tsx +7 -15
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/api/objectSearchService.ts +14 -19
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/NumericRangeFilter.tsx +9 -5
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/SearchFilter.tsx +5 -3
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/TextFilter.tsx +5 -3
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/hooks/useAsyncData.ts +11 -4
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/api/graphqlClient.ts +13 -13
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/components/AgentforceConversationClient.tsx +40 -44
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/components/alerts/status-alert.tsx +11 -8
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/components/ui/input.tsx +1 -1
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/hooks/useAsyncData.ts +67 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/pages/AccountObjectDetailPage.tsx +7 -9
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/pages/AccountSearch.tsx +7 -15
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/types/conversation.ts +9 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/types/globals.d.ts +13 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/tsconfig.json +4 -6
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/vite-env.d.ts +0 -3
- package/lib/templates/project/reactinternalapp/_p_/_m_/applications/reactinternalapp.app-meta.xml +17 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/permissionsets/reactinternalapp_Access.permissionset-meta.xml +9 -0
- package/lib/templates/project/reactinternalapp/package.json +1 -1
- package/lib/templates/project/reactinternalapp/scripts/org-setup.config.json +6 -3
- package/lib/templates/project/reactinternalapp/scripts/org-setup.mjs +528 -44
- package/lib/templates/project/reactinternalapp/sfdx-project.json +1 -1
- package/lib/templates/uiBundles/reactbasic/package-lock.json +1040 -593
- package/lib/templates/uiBundles/reactbasic/package.json +3 -3
- package/lib/templates/uiBundles/reactbasic/src/api/graphqlClient.ts +13 -13
- package/lib/templates/uiBundles/reactbasic/src/components/alerts/status-alert.tsx +11 -8
- package/lib/templates/uiBundles/reactbasic/src/components/ui/input.tsx +1 -1
- package/lib/templates/uiBundles/reactbasic/src/hooks/useAsyncData.ts +67 -0
- package/lib/templates/uiBundles/reactbasic/tsconfig.json +4 -6
- package/lib/templates/uiBundles/reactbasic/vite-env.d.ts +0 -3
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/utils/lightningEmbedding.d.ts +12 -0
- package/lib/utils/lightningEmbedding.js +50 -0
- package/lib/utils/lightningEmbedding.js.map +1 -0
- package/lib/utils/types.d.ts +15 -6
- package/lib/utils/types.js +8 -5
- package/lib/utils/types.js.map +1 -1
- package/package.json +6 -6
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/hooks/useCachedAsyncData.ts +0 -188
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/layout/card-skeleton.tsx +0 -38
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/layouts/authenticationRouteLayout.tsx +0 -21
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/hooks/useCachedAsyncData.ts +0 -188
|
@@ -22,17 +22,18 @@
|
|
|
22
22
|
* Permset assignment config (scripts/org-setup.config.json):
|
|
23
23
|
* {
|
|
24
24
|
* "permsetAssignments": {
|
|
25
|
-
* "defaultAssignee": "currentUser",
|
|
26
25
|
* "assignments": {
|
|
27
|
-
* "
|
|
26
|
+
* "My_Permset": { "assignee": "currentUser" },
|
|
27
|
+
* "Guest_Permset": { "assignee": "guestUser", "siteName": "mysite" },
|
|
28
28
|
* "Internal_Only": { "assignee": "skip" }
|
|
29
29
|
* }
|
|
30
30
|
* }
|
|
31
31
|
* }
|
|
32
|
-
* Assignee values: "currentUser"
|
|
32
|
+
* Assignee values: "currentUser", "skip", "guestUser" (requires siteName), or a specific username.
|
|
33
|
+
* Only permsets explicitly listed in assignments are assigned; unlisted permsets are skipped.
|
|
33
34
|
*/
|
|
34
35
|
|
|
35
|
-
import { spawnSync } from 'node:child_process';
|
|
36
|
+
import { spawnSync, spawn as nodeSpawn } from 'node:child_process';
|
|
36
37
|
import { resolve, dirname } from 'node:path';
|
|
37
38
|
import { fileURLToPath } from 'node:url';
|
|
38
39
|
import { readdirSync, existsSync, readFileSync, writeFileSync, unlinkSync } from 'node:fs';
|
|
@@ -97,9 +98,11 @@ function parseArgs() {
|
|
|
97
98
|
skipLogin: false,
|
|
98
99
|
skipDeploy: false,
|
|
99
100
|
skipPermset: false,
|
|
101
|
+
skipRole: false,
|
|
100
102
|
skipData: false,
|
|
101
103
|
skipGraphql: false,
|
|
102
104
|
skipUIBundleBuild: false,
|
|
105
|
+
skipSelfReg: false,
|
|
103
106
|
skipDev: false,
|
|
104
107
|
};
|
|
105
108
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -112,7 +115,9 @@ function parseArgs() {
|
|
|
112
115
|
} else if (args[i] === '--skip-login') flags.skipLogin = true;
|
|
113
116
|
else if (args[i] === '--skip-deploy') flags.skipDeploy = true;
|
|
114
117
|
else if (args[i] === '--skip-permset') flags.skipPermset = true;
|
|
118
|
+
else if (args[i] === '--skip-role') flags.skipRole = true;
|
|
115
119
|
else if (args[i] === '--skip-data') flags.skipData = true;
|
|
120
|
+
else if (args[i] === '--skip-self-reg') flags.skipSelfReg = true;
|
|
116
121
|
else if (args[i] === '--skip-graphql') flags.skipGraphql = true;
|
|
117
122
|
else if (args[i] === '--skip-ui-bundle-build') flags.skipUIBundleBuild = true;
|
|
118
123
|
else if (args[i] === '--skip-dev') flags.skipDev = true;
|
|
@@ -144,15 +149,15 @@ Permset config (scripts/org-setup.config.json):
|
|
|
144
149
|
Control per-permset assignment via a config file. Example:
|
|
145
150
|
{
|
|
146
151
|
"permsetAssignments": {
|
|
147
|
-
"defaultAssignee": "currentUser",
|
|
148
152
|
"assignments": {
|
|
149
|
-
"
|
|
153
|
+
"My_Permset": { "assignee": "currentUser" },
|
|
154
|
+
"Guest_Permset": { "assignee": "guestUser", "siteName": "mysite" },
|
|
150
155
|
"Internal_Only": { "assignee": "skip" }
|
|
151
156
|
}
|
|
152
157
|
}
|
|
153
158
|
}
|
|
154
|
-
Assignee values: "currentUser"
|
|
155
|
-
|
|
159
|
+
Assignee values: "currentUser", "skip", "guestUser" (requires siteName), or a specific username.
|
|
160
|
+
Only permsets explicitly listed in assignments are assigned; unlisted permsets are skipped.
|
|
156
161
|
`);
|
|
157
162
|
process.exit(0);
|
|
158
163
|
}
|
|
@@ -213,31 +218,33 @@ function discoverPermissionSetNames() {
|
|
|
213
218
|
* Config shape:
|
|
214
219
|
* {
|
|
215
220
|
* "permsetAssignments": {
|
|
216
|
-
* "defaultAssignee": "currentUser", // "currentUser" (default) or "skip"
|
|
217
221
|
* "assignments": {
|
|
218
|
-
* "
|
|
222
|
+
* "My_Permset": { "assignee": "currentUser" },
|
|
223
|
+
* "My_Guest_Permset": { "assignee": "guestUser", "siteName": "mysite" },
|
|
219
224
|
* "Internal_Only": { "assignee": "skip" }
|
|
220
225
|
* }
|
|
221
226
|
* }
|
|
222
227
|
* }
|
|
223
228
|
*
|
|
224
229
|
* Assignee values:
|
|
225
|
-
* "currentUser" — assign to the user running the script
|
|
230
|
+
* "currentUser" — assign to the user running the script
|
|
226
231
|
* "skip" — do not assign this permset
|
|
232
|
+
* "guestUser" — resolve the site guest user automatically (requires siteName)
|
|
227
233
|
* "<username>" — assign to a specific user via --on-behalf-of
|
|
228
234
|
*
|
|
229
|
-
*
|
|
235
|
+
* Only permsets explicitly listed in assignments are assigned; unlisted permsets are skipped.
|
|
236
|
+
*
|
|
237
|
+
* Returns { assignments: Record<string, { assignee: string, siteName?: string }> }
|
|
230
238
|
*/
|
|
231
239
|
function loadPermsetConfig() {
|
|
232
240
|
const configPath = resolve(__dirname, 'org-setup.config.json');
|
|
233
|
-
const defaults = {
|
|
241
|
+
const defaults = { assignments: {} };
|
|
234
242
|
if (!existsSync(configPath)) return defaults;
|
|
235
243
|
try {
|
|
236
244
|
const raw = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
237
245
|
const section = raw?.permsetAssignments;
|
|
238
246
|
if (!section) return defaults;
|
|
239
247
|
return {
|
|
240
|
-
defaultAssignee: section.defaultAssignee || 'currentUser',
|
|
241
248
|
assignments: section.assignments || {},
|
|
242
249
|
};
|
|
243
250
|
} catch (err) {
|
|
@@ -246,10 +253,375 @@ function loadPermsetConfig() {
|
|
|
246
253
|
}
|
|
247
254
|
}
|
|
248
255
|
|
|
249
|
-
/** Resolve the effective
|
|
250
|
-
function
|
|
256
|
+
/** Resolve the effective assignment config for a given permset name. */
|
|
257
|
+
function resolveAssignment(permsetName, permsetConfig) {
|
|
251
258
|
const override = permsetConfig.assignments[permsetName];
|
|
252
|
-
return
|
|
259
|
+
if (!override) return { assignee: 'skip' };
|
|
260
|
+
return { assignee: override.assignee || 'skip', siteName: override.siteName };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Load role assignment config from org-setup.config.json.
|
|
265
|
+
*
|
|
266
|
+
* Config shape:
|
|
267
|
+
* { "role": { "assignee": "currentUser", "roleName": "Admin" } }
|
|
268
|
+
*
|
|
269
|
+
* Returns null if no "role" section exists in config (the step is hidden).
|
|
270
|
+
*/
|
|
271
|
+
function loadRoleConfig() {
|
|
272
|
+
const configPath = resolve(__dirname, 'org-setup.config.json');
|
|
273
|
+
if (!existsSync(configPath)) return null;
|
|
274
|
+
try {
|
|
275
|
+
const raw = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
276
|
+
const section = raw?.role;
|
|
277
|
+
if (!section) return null;
|
|
278
|
+
return {
|
|
279
|
+
assignee: section.assignee || 'currentUser',
|
|
280
|
+
roleName: section.roleName || null,
|
|
281
|
+
};
|
|
282
|
+
} catch {
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Load self-registration config from org-setup.config.json.
|
|
289
|
+
*
|
|
290
|
+
* Config shape:
|
|
291
|
+
* {
|
|
292
|
+
* "selfRegistration": {
|
|
293
|
+
* "siteName": "myapp",
|
|
294
|
+
* "selfRegProfile": "myapp Profile",
|
|
295
|
+
* "accountName": "My Self-Reg Account"
|
|
296
|
+
* }
|
|
297
|
+
* }
|
|
298
|
+
*
|
|
299
|
+
* Returns null if no "selfRegistration" section exists in config (the step is hidden).
|
|
300
|
+
*/
|
|
301
|
+
function loadSelfRegConfig() {
|
|
302
|
+
const configPath = resolve(__dirname, 'org-setup.config.json');
|
|
303
|
+
if (!existsSync(configPath)) return null;
|
|
304
|
+
try {
|
|
305
|
+
const raw = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
306
|
+
const section = raw?.selfRegistration;
|
|
307
|
+
if (!section) return null;
|
|
308
|
+
return {
|
|
309
|
+
siteName: section.siteName || null,
|
|
310
|
+
selfRegProfile: section.selfRegProfile || null,
|
|
311
|
+
accountName: section.accountName || null,
|
|
312
|
+
};
|
|
313
|
+
} catch {
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Ensure the self-registration profile is listed in networkMemberGroups.
|
|
320
|
+
* This must happen BEFORE the initial deploy so that the profile is a recognised
|
|
321
|
+
* site member when subsequent steps (selfRegProfile, selfRegistration=true) are deployed.
|
|
322
|
+
*/
|
|
323
|
+
function ensureNetworkMemberProfile(selfRegConfig) {
|
|
324
|
+
const { siteName, selfRegProfile } = selfRegConfig;
|
|
325
|
+
if (!siteName || !selfRegProfile) return;
|
|
326
|
+
|
|
327
|
+
const networkXmlPath = resolve(SFDX_SOURCE, 'networks', `${siteName}.network-meta.xml`);
|
|
328
|
+
if (!existsSync(networkXmlPath)) {
|
|
329
|
+
console.log(` Network metadata not found: ${networkXmlPath}; skipping member group update.`);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
const xml = readFileSync(networkXmlPath, 'utf8');
|
|
333
|
+
|
|
334
|
+
// Check if profile is already in networkMemberGroups
|
|
335
|
+
const profileEscaped = selfRegProfile.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
336
|
+
const profileRegex = new RegExp(`<profile>\\s*${profileEscaped}\\s*</profile>`);
|
|
337
|
+
if (profileRegex.test(xml)) {
|
|
338
|
+
console.log(` Profile "${selfRegProfile}" already in networkMemberGroups; no update needed.`);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Add the profile to networkMemberGroups
|
|
343
|
+
const updatedXml = xml.replace(
|
|
344
|
+
/(<networkMemberGroups>)/,
|
|
345
|
+
`$1\n <profile>${selfRegProfile}</profile>`
|
|
346
|
+
);
|
|
347
|
+
writeFileSync(networkXmlPath, updatedXml);
|
|
348
|
+
console.log(` Added profile "${selfRegProfile}" to networkMemberGroups in ${siteName}.network-meta.xml`);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Enable self-registration for an Experience Cloud network.
|
|
353
|
+
*
|
|
354
|
+
* 1. Modify the network metadata XML to set selfRegistration=true and add selfRegProfile.
|
|
355
|
+
* 2. Re-deploy the modified network metadata.
|
|
356
|
+
* 3. Create an Account record (idempotent).
|
|
357
|
+
* 4. Create a NetworkSelfRegistration record linking the Account to the Network (idempotent).
|
|
358
|
+
*/
|
|
359
|
+
function enableSelfRegistration(selfRegConfig, targetOrg) {
|
|
360
|
+
const { siteName, selfRegProfile, accountName } = selfRegConfig;
|
|
361
|
+
|
|
362
|
+
// 1. Modify network metadata XML
|
|
363
|
+
const networkXmlPath = resolve(SFDX_SOURCE, 'networks', `${siteName}.network-meta.xml`);
|
|
364
|
+
if (!existsSync(networkXmlPath)) {
|
|
365
|
+
console.error(` Network metadata not found: ${networkXmlPath}`);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
const xml = readFileSync(networkXmlPath, 'utf8');
|
|
369
|
+
|
|
370
|
+
// Skip network modification and deploy if self-registration is already configured
|
|
371
|
+
const alreadyEnabled = /<selfRegistration>true<\/selfRegistration>/.test(xml);
|
|
372
|
+
const alreadyHasProfile = /<selfRegProfile>/.test(xml);
|
|
373
|
+
if (alreadyEnabled || alreadyHasProfile) {
|
|
374
|
+
console.log(` Network "${siteName}" already has self-registration configured; skipping metadata update and deploy.`);
|
|
375
|
+
} else {
|
|
376
|
+
// Set selfRegistration to true and add selfRegProfile
|
|
377
|
+
let updatedXml = xml.replace(
|
|
378
|
+
/<selfRegistration>false<\/selfRegistration>/,
|
|
379
|
+
'<selfRegistration>true</selfRegistration>'
|
|
380
|
+
);
|
|
381
|
+
updatedXml = updatedXml.replace(
|
|
382
|
+
/(\s*)(<selfRegistration>)/,
|
|
383
|
+
`$1<selfRegProfile>${selfRegProfile}</selfRegProfile>\n$1$2`
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
writeFileSync(networkXmlPath, updatedXml);
|
|
387
|
+
console.log(` Updated ${siteName}.network-meta.xml: selfRegistration=true, selfRegProfile=${selfRegProfile}`);
|
|
388
|
+
|
|
389
|
+
// Re-deploy only the network file
|
|
390
|
+
const deployResult = spawnSync('sf', [
|
|
391
|
+
'project', 'deploy', 'start',
|
|
392
|
+
'--target-org', targetOrg,
|
|
393
|
+
'--source-dir', networkXmlPath,
|
|
394
|
+
], { cwd: ROOT, stdio: 'inherit', shell: true, timeout: 120000 });
|
|
395
|
+
if (deployResult.status !== 0) {
|
|
396
|
+
console.error(' Failed to deploy updated network metadata.');
|
|
397
|
+
process.exit(deployResult.status ?? 1);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// 3. Create Account (idempotent)
|
|
402
|
+
const acctQuery = `SELECT Id FROM Account WHERE Name = '${accountName.replace(/'/g, "\\'")}' LIMIT 1`;
|
|
403
|
+
const acctQueryResult = spawnSync('sf', [
|
|
404
|
+
'data', 'query',
|
|
405
|
+
'--query', acctQuery,
|
|
406
|
+
'--target-org', targetOrg,
|
|
407
|
+
'--json',
|
|
408
|
+
], { cwd: ROOT, encoding: 'utf8' });
|
|
409
|
+
let accountId = null;
|
|
410
|
+
if (acctQueryResult.status === 0) {
|
|
411
|
+
try {
|
|
412
|
+
const json = JSON.parse(acctQueryResult.stdout);
|
|
413
|
+
accountId = json.result?.records?.[0]?.Id || null;
|
|
414
|
+
} catch { /* proceed to create */ }
|
|
415
|
+
}
|
|
416
|
+
if (accountId) {
|
|
417
|
+
console.log(` Account "${accountName}" already exists (${accountId}); skipping creation.`);
|
|
418
|
+
} else {
|
|
419
|
+
const createResult = spawnSync('sf', [
|
|
420
|
+
'data', 'create', 'record',
|
|
421
|
+
'--sobject', 'Account',
|
|
422
|
+
'--values', `Name='${accountName}'`,
|
|
423
|
+
'--target-org', targetOrg,
|
|
424
|
+
'--json',
|
|
425
|
+
], { cwd: ROOT, encoding: 'utf8' });
|
|
426
|
+
if (createResult.status !== 0) {
|
|
427
|
+
console.error(` Failed to create Account "${accountName}".`);
|
|
428
|
+
if (createResult.stderr) console.error(createResult.stderr);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
try {
|
|
432
|
+
const json = JSON.parse(createResult.stdout);
|
|
433
|
+
accountId = json.result?.id;
|
|
434
|
+
console.log(` Created Account "${accountName}" (${accountId}).`);
|
|
435
|
+
} catch {
|
|
436
|
+
console.error(' Failed to parse Account creation result.');
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// 4. Query Network Id
|
|
442
|
+
const netQuery = `SELECT Id FROM Network WHERE Name = '${siteName}'`;
|
|
443
|
+
const netResult = spawnSync('sf', [
|
|
444
|
+
'data', 'query',
|
|
445
|
+
'--query', netQuery,
|
|
446
|
+
'--target-org', targetOrg,
|
|
447
|
+
'--json',
|
|
448
|
+
], { cwd: ROOT, encoding: 'utf8' });
|
|
449
|
+
let networkId = null;
|
|
450
|
+
if (netResult.status === 0) {
|
|
451
|
+
try {
|
|
452
|
+
const json = JSON.parse(netResult.stdout);
|
|
453
|
+
networkId = json.result?.records?.[0]?.Id || null;
|
|
454
|
+
} catch { /* fall through */ }
|
|
455
|
+
}
|
|
456
|
+
if (!networkId) {
|
|
457
|
+
console.error(` Could not find Network "${siteName}" in org.`);
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
console.log(` Found Network "${siteName}" (${networkId}).`);
|
|
461
|
+
|
|
462
|
+
// 5. Create NetworkSelfRegistration (idempotent)
|
|
463
|
+
const nsrQuery = `SELECT Id FROM NetworkSelfRegistration WHERE NetworkId = '${networkId}'`;
|
|
464
|
+
const nsrResult = spawnSync('sf', [
|
|
465
|
+
'data', 'query',
|
|
466
|
+
'--query', nsrQuery,
|
|
467
|
+
'--target-org', targetOrg,
|
|
468
|
+
'--json',
|
|
469
|
+
], { cwd: ROOT, encoding: 'utf8' });
|
|
470
|
+
let nsrExists = false;
|
|
471
|
+
if (nsrResult.status === 0) {
|
|
472
|
+
try {
|
|
473
|
+
const json = JSON.parse(nsrResult.stdout);
|
|
474
|
+
nsrExists = (json.result?.records?.length || 0) > 0;
|
|
475
|
+
} catch { /* proceed to create */ }
|
|
476
|
+
}
|
|
477
|
+
if (nsrExists) {
|
|
478
|
+
console.log(' NetworkSelfRegistration record already exists; skipping.');
|
|
479
|
+
} else {
|
|
480
|
+
const tmpApex = resolve(ROOT, '.tmp-setup-selfreg.apex');
|
|
481
|
+
const apex = [
|
|
482
|
+
`Account acct = [SELECT Id FROM Account WHERE Id = '${accountId}' LIMIT 1];`,
|
|
483
|
+
`NetworkSelfRegistration nsr = new NetworkSelfRegistration();`,
|
|
484
|
+
`nsr.AccountId = acct.Id;`,
|
|
485
|
+
`nsr.NetworkId = '${networkId}';`,
|
|
486
|
+
`insert nsr;`,
|
|
487
|
+
`System.debug('NSR_CREATED:' + nsr.Id);`,
|
|
488
|
+
].join('\n');
|
|
489
|
+
writeFileSync(tmpApex, apex);
|
|
490
|
+
const apexResult = spawnSync('sf', [
|
|
491
|
+
'apex', 'run', '--target-org', targetOrg, '--file', tmpApex,
|
|
492
|
+
], { cwd: ROOT, stdio: 'pipe', shell: true, timeout: 60000 });
|
|
493
|
+
const apexOut = apexResult.stdout?.toString() || '';
|
|
494
|
+
if (existsSync(tmpApex)) unlinkSync(tmpApex);
|
|
495
|
+
if (apexResult.status !== 0 && !apexOut.includes('Compiled successfully')) {
|
|
496
|
+
console.error(' Failed to create NetworkSelfRegistration record.');
|
|
497
|
+
process.stderr.write(apexResult.stderr?.toString() || apexOut);
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
const nsrMatch = apexOut.match(/NSR_CREATED:(\w+)/);
|
|
501
|
+
if (nsrMatch) {
|
|
502
|
+
console.log(` Created NetworkSelfRegistration (${nsrMatch[1]}).`);
|
|
503
|
+
} else {
|
|
504
|
+
console.log(' NetworkSelfRegistration creation executed.');
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Assign a role to the current user so that Experience Cloud self-registration
|
|
511
|
+
* works correctly.
|
|
512
|
+
*/
|
|
513
|
+
function assignRoleToCurrentUser(roleName, targetOrg) {
|
|
514
|
+
const roleQuery = `SELECT Id FROM UserRole WHERE Name = '${roleName}'`;
|
|
515
|
+
const roleResult = spawnSync('sf', [
|
|
516
|
+
'data', 'query',
|
|
517
|
+
'--query', roleQuery,
|
|
518
|
+
'--target-org', targetOrg,
|
|
519
|
+
'--json',
|
|
520
|
+
], { cwd: ROOT, encoding: 'utf8' });
|
|
521
|
+
if (roleResult.status !== 0) {
|
|
522
|
+
console.error(` Failed to query role "${roleName}" in org.`);
|
|
523
|
+
if (roleResult.stderr) console.error(roleResult.stderr);
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
let roleId;
|
|
527
|
+
try {
|
|
528
|
+
const json = JSON.parse(roleResult.stdout);
|
|
529
|
+
const records = json.result?.records;
|
|
530
|
+
if (!records || records.length === 0) {
|
|
531
|
+
console.error(` Role "${roleName}" not found in org; skipping.`);
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
roleId = records[0].Id;
|
|
535
|
+
} catch {
|
|
536
|
+
console.error(` Failed to parse role query result for "${roleName}".`);
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const orgResult = spawnSync('sf', [
|
|
541
|
+
'org', 'display',
|
|
542
|
+
'--target-org', targetOrg,
|
|
543
|
+
'--json',
|
|
544
|
+
], { cwd: ROOT, encoding: 'utf8' });
|
|
545
|
+
if (orgResult.status !== 0) {
|
|
546
|
+
console.error(' Failed to resolve current user from org.');
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
let username;
|
|
550
|
+
try {
|
|
551
|
+
const json = JSON.parse(orgResult.stdout);
|
|
552
|
+
username = json.result?.username;
|
|
553
|
+
if (!username) {
|
|
554
|
+
console.error(' Could not determine current username from org display.');
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
} catch {
|
|
558
|
+
console.error(' Failed to parse org display result.');
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const userQuery = `SELECT Id, UserRoleId FROM User WHERE Username = '${username}'`;
|
|
563
|
+
const userResult = spawnSync('sf', [
|
|
564
|
+
'data', 'query',
|
|
565
|
+
'--query', userQuery,
|
|
566
|
+
'--target-org', targetOrg,
|
|
567
|
+
'--json',
|
|
568
|
+
], { cwd: ROOT, encoding: 'utf8' });
|
|
569
|
+
if (userResult.status === 0) {
|
|
570
|
+
try {
|
|
571
|
+
const json = JSON.parse(userResult.stdout);
|
|
572
|
+
const userRecord = json.result?.records?.[0];
|
|
573
|
+
if (userRecord?.UserRoleId) {
|
|
574
|
+
console.log(` User ${username} already has a role assigned; skipping to avoid overriding.`);
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
} catch { /* continue */ }
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
const updateResult = spawnSync('sf', [
|
|
581
|
+
'data', 'update', 'record',
|
|
582
|
+
'--sobject', 'User',
|
|
583
|
+
'--where', `Username='${username}'`,
|
|
584
|
+
'--values', `UserRoleId='${roleId}'`,
|
|
585
|
+
'--target-org', targetOrg,
|
|
586
|
+
'--json',
|
|
587
|
+
], { cwd: ROOT, encoding: 'utf8' });
|
|
588
|
+
if (updateResult.status === 0) {
|
|
589
|
+
console.log(` Role "${roleName}" assigned to ${username}.`);
|
|
590
|
+
} else {
|
|
591
|
+
const out = (updateResult.stderr?.toString() || '') + (updateResult.stdout?.toString() || '');
|
|
592
|
+
console.error(` Failed to assign role "${roleName}" to ${username}.`);
|
|
593
|
+
if (out) console.error(out);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Query the org for a guest user whose profile name matches the given site name.
|
|
599
|
+
*/
|
|
600
|
+
function resolveGuestUsername(siteName, targetOrg) {
|
|
601
|
+
const query = `SELECT Username FROM User WHERE Profile.Name LIKE '%${siteName}%' AND UserType = 'Guest'`;
|
|
602
|
+
const result = spawnSync('sf', [
|
|
603
|
+
'data', 'query',
|
|
604
|
+
'--query', query,
|
|
605
|
+
'--target-org', targetOrg,
|
|
606
|
+
'--json',
|
|
607
|
+
], { cwd: ROOT, encoding: 'utf8' });
|
|
608
|
+
if (result.status !== 0) {
|
|
609
|
+
console.error(` Failed to query guest user for site "${siteName}".`);
|
|
610
|
+
if (result.stderr) console.error(result.stderr);
|
|
611
|
+
return null;
|
|
612
|
+
}
|
|
613
|
+
try {
|
|
614
|
+
const json = JSON.parse(result.stdout);
|
|
615
|
+
const records = json.result?.records;
|
|
616
|
+
if (!records || records.length === 0) {
|
|
617
|
+
console.error(` No guest user found for site "${siteName}".`);
|
|
618
|
+
return null;
|
|
619
|
+
}
|
|
620
|
+
return records[0].Username;
|
|
621
|
+
} catch {
|
|
622
|
+
console.error(` Failed to parse guest user query result for site "${siteName}".`);
|
|
623
|
+
return null;
|
|
624
|
+
}
|
|
253
625
|
}
|
|
254
626
|
|
|
255
627
|
function isOrgConnected(targetOrg) {
|
|
@@ -312,6 +684,22 @@ async function promptSteps(steps) {
|
|
|
312
684
|
const CYAN = '\x1B[36m';
|
|
313
685
|
const GREEN = '\x1B[32m';
|
|
314
686
|
|
|
687
|
+
/** Strip ANSI escape sequences to get visible character count. */
|
|
688
|
+
function visibleLength(str) {
|
|
689
|
+
return str.replace(/\x1B\[[0-9;]*m/g, '').length;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/** Count how many terminal rows a set of lines occupies (accounting for wrapping). */
|
|
693
|
+
function terminalRows(lines) {
|
|
694
|
+
const cols = process.stdout.columns || 80;
|
|
695
|
+
let rows = 0;
|
|
696
|
+
for (const line of lines) {
|
|
697
|
+
const len = visibleLength(line);
|
|
698
|
+
rows += len === 0 ? 1 : Math.ceil(len / cols);
|
|
699
|
+
}
|
|
700
|
+
return rows;
|
|
701
|
+
}
|
|
702
|
+
|
|
315
703
|
function render() {
|
|
316
704
|
return steps.map((s, i) => {
|
|
317
705
|
const ptr = i === cursor ? `${CYAN}❯${RST}` : ' ';
|
|
@@ -321,17 +709,23 @@ async function promptSteps(steps) {
|
|
|
321
709
|
});
|
|
322
710
|
}
|
|
323
711
|
|
|
712
|
+
let prevRows = 0;
|
|
713
|
+
|
|
324
714
|
return new Promise((resolve) => {
|
|
325
715
|
process.stdin.setRawMode(true);
|
|
326
716
|
process.stdin.resume();
|
|
327
717
|
process.stdin.setEncoding('utf8');
|
|
328
718
|
process.stdout.write('\x1B[?25l');
|
|
329
719
|
console.log('\nSelect steps (↑↓ move, space toggle, a all, enter confirm):\n');
|
|
330
|
-
|
|
720
|
+
const initialLines = render();
|
|
721
|
+
prevRows = terminalRows(initialLines);
|
|
722
|
+
process.stdout.write(initialLines.join('\n') + '\n');
|
|
331
723
|
|
|
332
724
|
function redraw() {
|
|
333
|
-
process.stdout.write(`\x1B[${
|
|
334
|
-
|
|
725
|
+
process.stdout.write(`\x1B[${prevRows}A`);
|
|
726
|
+
const lines = render();
|
|
727
|
+
for (const line of lines) process.stdout.write(`\x1B[2K${line}\n`);
|
|
728
|
+
prevRows = terminalRows(lines);
|
|
335
729
|
}
|
|
336
730
|
|
|
337
731
|
process.stdin.on('data', (key) => {
|
|
@@ -388,6 +782,37 @@ function run(name, cmd, args, opts = {}) {
|
|
|
388
782
|
return result;
|
|
389
783
|
}
|
|
390
784
|
|
|
785
|
+
/** Promise-based spawn for parallel execution. Always uses stdio: 'pipe'. */
|
|
786
|
+
function spawnAsync(cmd, args, opts = {}) {
|
|
787
|
+
return new Promise((resolve, reject) => {
|
|
788
|
+
const proc = nodeSpawn(cmd, args, {
|
|
789
|
+
cwd: opts.cwd || ROOT,
|
|
790
|
+
stdio: 'pipe',
|
|
791
|
+
shell: true,
|
|
792
|
+
...(opts.timeout && { timeout: opts.timeout }),
|
|
793
|
+
});
|
|
794
|
+
let stdout = '';
|
|
795
|
+
let stderr = '';
|
|
796
|
+
proc.stdout.on('data', (d) => { stdout += d.toString(); });
|
|
797
|
+
proc.stderr.on('data', (d) => { stderr += d.toString(); });
|
|
798
|
+
proc.on('close', (code) => resolve({ status: code, stdout, stderr }));
|
|
799
|
+
proc.on('error', reject);
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/** Async version of run() for parallel steps. Captures output and prints on failure. */
|
|
804
|
+
async function runAsync(name, cmd, args, opts = {}) {
|
|
805
|
+
const { cwd = ROOT, optional = false } = opts;
|
|
806
|
+
const result = await spawnAsync(cmd, args, { cwd, ...(opts.timeout && { timeout: opts.timeout }) });
|
|
807
|
+
if (result.status !== 0 && !optional) {
|
|
808
|
+
if (result.stdout) process.stdout.write(result.stdout);
|
|
809
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
810
|
+
console.error(`\nSetup failed at step: ${name}`);
|
|
811
|
+
process.exit(result.status ?? 1);
|
|
812
|
+
}
|
|
813
|
+
return result;
|
|
814
|
+
}
|
|
815
|
+
|
|
391
816
|
async function main() {
|
|
392
817
|
// Ensure .gitignore files exist (npm strips them from published packages).
|
|
393
818
|
const gitignoreTemplates = loadGitignoreTemplates();
|
|
@@ -410,6 +835,8 @@ async function main() {
|
|
|
410
835
|
skipLogin: argSkipLogin,
|
|
411
836
|
skipDeploy: argSkipDeploy,
|
|
412
837
|
skipPermset: argSkipPermset,
|
|
838
|
+
skipRole: argSkipRole,
|
|
839
|
+
skipSelfReg: argSkipSelfReg,
|
|
413
840
|
skipData: argSkipData,
|
|
414
841
|
skipGraphql: argSkipGraphql,
|
|
415
842
|
skipUIBundleBuild: argSkipUIBundleBuild,
|
|
@@ -426,12 +853,18 @@ async function main() {
|
|
|
426
853
|
: `Permset — assign ${permsetNames.length} permission sets`;
|
|
427
854
|
|
|
428
855
|
const hasDataPlan = existsSync(DATA_PLAN) && existsSync(DATA_DIR);
|
|
856
|
+
const roleConfig = loadRoleConfig();
|
|
857
|
+
const hasRoleConfig = roleConfig !== null;
|
|
858
|
+
const selfRegConfig = loadSelfRegConfig();
|
|
859
|
+
const hasSelfRegConfig = selfRegConfig !== null;
|
|
429
860
|
|
|
430
861
|
const stepDefs = [
|
|
431
862
|
{ key: 'login', label: 'Login — org authentication', enabled: !argSkipLogin, available: true },
|
|
432
863
|
{ key: 'uiBundleBuild', label: 'UI Bundle Build — npm install + build (pre-deploy)', enabled: !argSkipUIBundleBuild, available: true },
|
|
433
864
|
{ key: 'deploy', label: 'Deploy — sf project deploy start', enabled: !argSkipDeploy, available: true },
|
|
434
865
|
{ key: 'permset', label: permsetStepLabel, enabled: !argSkipPermset, available: true },
|
|
866
|
+
{ key: 'role', label: `Role — assign "${roleConfig?.roleName ?? '?'}" to current user`, enabled: !argSkipRole && hasRoleConfig, available: hasRoleConfig },
|
|
867
|
+
{ key: 'selfReg', label: `Self-Registration — enable for "${selfRegConfig?.siteName ?? '?'}"`, enabled: !argSkipSelfReg && hasSelfRegConfig, available: hasSelfRegConfig },
|
|
435
868
|
{ key: 'data', label: 'Data — delete + import records via Apex', enabled: !argSkipData && hasDataPlan, available: hasDataPlan },
|
|
436
869
|
{ key: 'graphql', label: 'GraphQL — schema introspect + codegen', enabled: !argSkipGraphql, available: true },
|
|
437
870
|
{ key: 'dev', label: 'Dev — launch dev server', enabled: !argSkipDev, available: true },
|
|
@@ -447,6 +880,8 @@ async function main() {
|
|
|
447
880
|
const skipUIBundleBuild = !on.uiBundleBuild;
|
|
448
881
|
const skipDeploy = !on.deploy;
|
|
449
882
|
const skipPermset = !on.permset;
|
|
883
|
+
const skipRole = !on.role;
|
|
884
|
+
const skipSelfReg = !on.selfReg;
|
|
450
885
|
const skipData = !on.data;
|
|
451
886
|
const skipGraphql = !on.graphql;
|
|
452
887
|
const skipDev = !on.dev;
|
|
@@ -457,10 +892,12 @@ async function main() {
|
|
|
457
892
|
|
|
458
893
|
console.log('Setup — target org:', targetOrg, '| UI bundle:', uiBundleDir ?? '(none)');
|
|
459
894
|
console.log(
|
|
460
|
-
'Steps: login=%s deploy=%s permset=%s data=%s graphql=%s uiBundle=%s dev=%s',
|
|
895
|
+
'Steps: login=%s deploy=%s permset=%s role=%s selfReg=%s data=%s graphql=%s uiBundle=%s dev=%s',
|
|
461
896
|
!skipLogin,
|
|
462
897
|
!skipDeploy,
|
|
463
898
|
!skipPermset,
|
|
899
|
+
!skipRole,
|
|
900
|
+
!skipSelfReg,
|
|
464
901
|
doData,
|
|
465
902
|
!skipGraphql,
|
|
466
903
|
!skipUIBundleBuild,
|
|
@@ -476,6 +913,13 @@ async function main() {
|
|
|
476
913
|
}
|
|
477
914
|
}
|
|
478
915
|
|
|
916
|
+
// Ensure the self-reg profile is in networkMemberGroups before deploy so that
|
|
917
|
+
// subsequent selfRegProfile / selfRegistration updates don't fail.
|
|
918
|
+
if (!skipDeploy && selfRegConfig) {
|
|
919
|
+
console.log('\n--- Ensure network member profile (pre-deploy) ---');
|
|
920
|
+
ensureNetworkMemberProfile(selfRegConfig);
|
|
921
|
+
}
|
|
922
|
+
|
|
479
923
|
// Build all UI Bundles before deploy so dist exists for entity deployment
|
|
480
924
|
if (!skipDeploy && !skipUIBundleBuild) {
|
|
481
925
|
const allUIBundleDirs = discoverAllUIBundleDirs(uiBundleName);
|
|
@@ -499,42 +943,84 @@ async function main() {
|
|
|
499
943
|
console.log('No permission sets found under permissionsets/ and none passed via --permset-name; skipping.');
|
|
500
944
|
} else {
|
|
501
945
|
console.log('\n--- Assign permission sets ---');
|
|
946
|
+
|
|
947
|
+
// Resolve assignments (guest user lookups etc.) then run all sf assign calls in parallel.
|
|
948
|
+
const assignmentJobs = [];
|
|
502
949
|
for (const permsetName of permsetNames) {
|
|
503
|
-
const
|
|
504
|
-
if (assignee === 'skip') {
|
|
950
|
+
const assignment = resolveAssignment(permsetName, permsetConfig);
|
|
951
|
+
if (assignment.assignee === 'skip') {
|
|
505
952
|
console.log(`Permission set "${permsetName}" — skipped (config).`);
|
|
506
953
|
continue;
|
|
507
954
|
}
|
|
955
|
+
let effectiveUsername = null;
|
|
956
|
+
if (assignment.assignee === 'guestUser') {
|
|
957
|
+
if (!assignment.siteName) {
|
|
958
|
+
console.error(`Permission set "${permsetName}" — assignee is "guestUser" but no "siteName" configured; skipping.`);
|
|
959
|
+
continue;
|
|
960
|
+
}
|
|
961
|
+
effectiveUsername = resolveGuestUsername(assignment.siteName, targetOrg);
|
|
962
|
+
if (!effectiveUsername) {
|
|
963
|
+
console.error(`Permission set "${permsetName}" — could not resolve guest user for site "${assignment.siteName}"; skipping.`);
|
|
964
|
+
continue;
|
|
965
|
+
}
|
|
966
|
+
console.log(` Resolved guest user for site "${assignment.siteName}": ${effectiveUsername}`);
|
|
967
|
+
} else if (assignment.assignee !== 'currentUser') {
|
|
968
|
+
effectiveUsername = assignment.assignee;
|
|
969
|
+
}
|
|
970
|
+
assignmentJobs.push({ permsetName, effectiveUsername });
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// Run all permset assignment calls in parallel.
|
|
974
|
+
const assignResults = await Promise.all(assignmentJobs.map(async ({ permsetName, effectiveUsername }) => {
|
|
508
975
|
const sfArgs = ['org', 'assign', 'permset', '--name', permsetName, '--target-org', targetOrg];
|
|
509
|
-
if (
|
|
510
|
-
sfArgs.push('--on-behalf-of',
|
|
976
|
+
if (effectiveUsername) {
|
|
977
|
+
sfArgs.push('--on-behalf-of', effectiveUsername);
|
|
511
978
|
}
|
|
512
|
-
const assigneeLabel =
|
|
513
|
-
const
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
if (
|
|
979
|
+
const assigneeLabel = effectiveUsername || 'current user';
|
|
980
|
+
const result = await spawnAsync('sf', sfArgs);
|
|
981
|
+
return { permsetName, assigneeLabel, result };
|
|
982
|
+
}));
|
|
983
|
+
|
|
984
|
+
for (const { permsetName, assigneeLabel, result } of assignResults) {
|
|
985
|
+
if (result.status === 0) {
|
|
519
986
|
console.log(`Permission set "${permsetName}" assigned to ${assigneeLabel}.`);
|
|
520
987
|
} else {
|
|
521
|
-
const out =
|
|
522
|
-
(permsetResult.stderr?.toString() || '') + (permsetResult.stdout?.toString() || '');
|
|
988
|
+
const out = (result.stderr || '') + (result.stdout || '');
|
|
523
989
|
if (out.includes('Duplicate') && out.includes('PermissionSet')) {
|
|
524
990
|
console.log(`Permission set "${permsetName}" already assigned to ${assigneeLabel}; skipping.`);
|
|
525
991
|
} else if (out.includes('not found') && out.includes('target org')) {
|
|
526
992
|
console.log(`Permission set "${permsetName}" not in org; skipping.`);
|
|
527
993
|
} else {
|
|
528
|
-
process.stdout.write(
|
|
529
|
-
process.stderr.write(
|
|
994
|
+
if (result.stdout) process.stdout.write(result.stdout);
|
|
995
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
530
996
|
console.error(`\nSetup failed at step: Assign permission set (${permsetName})`);
|
|
531
|
-
process.exit(
|
|
997
|
+
process.exit(result.status ?? 1);
|
|
532
998
|
}
|
|
533
999
|
}
|
|
534
1000
|
}
|
|
535
1001
|
}
|
|
536
1002
|
}
|
|
537
1003
|
|
|
1004
|
+
if (!skipRole) {
|
|
1005
|
+
console.log('\n--- Assign role ---');
|
|
1006
|
+
if (roleConfig?.assignee !== 'currentUser') {
|
|
1007
|
+
console.error(`Role assignee "${roleConfig?.assignee}" is not supported; only "currentUser" is allowed. Skipping.`);
|
|
1008
|
+
} else if (!roleConfig?.roleName) {
|
|
1009
|
+
console.error('Role step enabled but no "roleName" specified in org-setup.config.json; skipping.');
|
|
1010
|
+
} else {
|
|
1011
|
+
assignRoleToCurrentUser(roleConfig.roleName, targetOrg);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
if (!skipSelfReg) {
|
|
1016
|
+
console.log('\n--- Enable self-registration ---');
|
|
1017
|
+
if (!selfRegConfig?.siteName || !selfRegConfig?.selfRegProfile || !selfRegConfig?.accountName) {
|
|
1018
|
+
console.error('Self-registration config is incomplete (need siteName, selfRegProfile, accountName); skipping.');
|
|
1019
|
+
} else {
|
|
1020
|
+
enableSelfRegistration(selfRegConfig, targetOrg);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
|
|
538
1024
|
if (doData) {
|
|
539
1025
|
// Prepare data for uniqueness (run before import so repeat imports don't conflict)
|
|
540
1026
|
const prepareScript = resolve(__dirname, 'prepare-import-unique-fields.js');
|
|
@@ -652,17 +1138,15 @@ async function main() {
|
|
|
652
1138
|
if (existsSync(tmpApex)) unlinkSync(tmpApex);
|
|
653
1139
|
}
|
|
654
1140
|
|
|
655
|
-
if (!skipGraphql || !skipUIBundleBuild) {
|
|
656
|
-
run('UI Bundle npm install', 'npm', ['install'], { cwd: uiBundleDir });
|
|
657
|
-
}
|
|
658
|
-
|
|
659
1141
|
if (!skipGraphql) {
|
|
1142
|
+
run('UI Bundle npm install', 'npm', ['install'], { cwd: uiBundleDir });
|
|
660
1143
|
run('Set default org for schema', 'sf', ['config', 'set', 'target-org', targetOrg, '--global']);
|
|
661
1144
|
run('GraphQL schema (introspect)', 'npm', ['run', 'graphql:schema'], { cwd: uiBundleDir });
|
|
662
1145
|
run('GraphQL codegen', 'npm', ['run', 'graphql:codegen'], { cwd: uiBundleDir });
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
1146
|
+
run('UI Bundle build (post-codegen)', 'npm', ['run', 'build'], { cwd: uiBundleDir });
|
|
1147
|
+
} else if (!skipUIBundleBuild && skipDeploy) {
|
|
1148
|
+
// Only build here if the pre-deploy build didn't already run
|
|
1149
|
+
run('UI Bundle npm install', 'npm', ['install'], { cwd: uiBundleDir });
|
|
666
1150
|
run('UI Bundle build', 'npm', ['run', 'build'], { cwd: uiBundleDir });
|
|
667
1151
|
}
|
|
668
1152
|
|