@salesforce/ui-bundle-template-feature-react-agentforce-conversation-client 11.68.6 → 11.69.1
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/dist/CHANGELOG.md +18 -0
- package/dist/force-app/main/default/uiBundles/feature-react-agentforce-conversation-client/package.json +4 -4
- package/dist/package-lock.json +2 -2
- package/dist/package.json +1 -1
- package/dist/scripts/org-setup-config-schema.mjs +12 -0
- package/dist/scripts/org-setup-dev.mjs +27 -0
- package/dist/scripts/org-setup-url.mjs +108 -0
- package/dist/scripts/org-setup-xml.mjs +96 -0
- package/dist/scripts/org-setup.mjs +154 -0
- package/package.json +2 -2
package/dist/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,24 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [11.69.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v11.69.0...v11.69.1) (2026-08-25)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* @W-23919045 note local-preview limitation on working with MFA & IDP locally ([#912](https://github.com/salesforce-experience-platform-emu/webapps/issues/912)) ([087e217](https://github.com/salesforce-experience-platform-emu/webapps/commit/087e2173625641c157b1a0630b96b13c950faab7))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
## [11.69.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v11.68.6...v11.69.0) (2026-08-25)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
* **org-setup:** resolve and apply site logout URL during setup ([#920](https://github.com/salesforce-experience-platform-emu/webapps/issues/920)) ([82b016e](https://github.com/salesforce-experience-platform-emu/webapps/commit/82b016e07f26938e19740d7943ae0f1c04c8e861))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
6
24
|
## [11.68.6](https://github.com/salesforce-experience-platform-emu/webapps/compare/v11.68.5...v11.68.6) (2026-08-24)
|
|
7
25
|
|
|
8
26
|
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
"graphql:schema": "node scripts/get-graphql-schema.mjs"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@salesforce/platform-sdk": "^11.
|
|
22
|
-
"@salesforce/ui-bundle": "^11.
|
|
21
|
+
"@salesforce/platform-sdk": "^11.69.1",
|
|
22
|
+
"@salesforce/ui-bundle": "^11.69.1",
|
|
23
23
|
"@tailwindcss/vite": "^4.1.17",
|
|
24
24
|
"class-variance-authority": "^0.7.1",
|
|
25
25
|
"clsx": "^2.1.1",
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
"@graphql-eslint/eslint-plugin": "^4.1.0",
|
|
46
46
|
"@graphql-tools/utils": "^11.0.0",
|
|
47
47
|
"@playwright/test": "^1.49.0",
|
|
48
|
-
"@salesforce/graphiti": "^11.
|
|
49
|
-
"@salesforce/vite-plugin-ui-bundle": "^11.
|
|
48
|
+
"@salesforce/graphiti": "^11.69.1",
|
|
49
|
+
"@salesforce/vite-plugin-ui-bundle": "^11.69.1",
|
|
50
50
|
"@testing-library/jest-dom": "^6.6.3",
|
|
51
51
|
"@testing-library/react": "^16.1.0",
|
|
52
52
|
"@testing-library/user-event": "^14.5.2",
|
package/dist/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforce/ui-bundle-template-base-sfdx-project",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.69.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@salesforce/ui-bundle-template-base-sfdx-project",
|
|
9
|
-
"version": "11.
|
|
9
|
+
"version": "11.69.1",
|
|
10
10
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"fast-xml-parser": "^5.9.3",
|
package/dist/package.json
CHANGED
|
@@ -79,6 +79,18 @@ export const ConfigSchema = z
|
|
|
79
79
|
})
|
|
80
80
|
.strict()
|
|
81
81
|
.optional(),
|
|
82
|
+
// URL members are redirected to after logging out, written to the site's
|
|
83
|
+
// <logoutUrl> network metadata (a post-deploy step). Steers logout back to THIS
|
|
84
|
+
// site so members aren't dropped on the org default-site login (which, in an
|
|
85
|
+
// org hosting multiple sites, can be a different community). Prefer a
|
|
86
|
+
// site-relative path (e.g. "/myapp/"): it is domain-independent, so the same
|
|
87
|
+
// shipped value is valid on every org. The platform REQUIRES an absolute logout
|
|
88
|
+
// URL, so org-setup resolves a relative path against the site's Experience Cloud
|
|
89
|
+
// origin (discovered from the org at deploy time) before writing it; an
|
|
90
|
+
// already-absolute value is used as-is. Kept a permissive non-empty string (not
|
|
91
|
+
// z.string().url()) so relative paths are valid; character-safety for raw XML
|
|
92
|
+
// interpolation is enforced at write time.
|
|
93
|
+
logoutUrl: z.string().min(1).optional(),
|
|
82
94
|
})
|
|
83
95
|
.strict();
|
|
84
96
|
|
|
@@ -60,6 +60,31 @@ Options:
|
|
|
60
60
|
return { targetOrg, uiBundleName };
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Print a heads-up about auth surfaces that only work on the deployed Experience
|
|
65
|
+
* site. In local preview, `sdk.fetch` is proxied to the org outside the site's
|
|
66
|
+
* guest/community context, so social login and password/MFA login cannot be
|
|
67
|
+
* exercised on localhost. This mirrors the "Local preview limitations" sections
|
|
68
|
+
* in the feature-react-authentication / feature-react-mfa READMEs — surfaced here
|
|
69
|
+
* so it is seen even when the READMEs are not.
|
|
70
|
+
*/
|
|
71
|
+
function printLocalPreviewWarning() {
|
|
72
|
+
console.log(
|
|
73
|
+
[
|
|
74
|
+
'',
|
|
75
|
+
'⚠️ Local preview limitation — some auth surfaces only work on the DEPLOYED site',
|
|
76
|
+
' Local preview proxies sdk.fetch to the org outside the Experience site guest',
|
|
77
|
+
' context (Site.getBaseUrl() is blank, no site session), so on localhost:',
|
|
78
|
+
' • Social Login buttons do not render (/auth/social-login-config returns no providers)',
|
|
79
|
+
' • Password / MFA login fails with "Your login attempt has failed…" (Site.login() cannot',
|
|
80
|
+
' establish the site session)',
|
|
81
|
+
' This is expected, not a misconfiguration. Test social login and MFA on the published',
|
|
82
|
+
' site login page: https://<domain>.site.com/<site-path>/login',
|
|
83
|
+
'',
|
|
84
|
+
].join('\n'),
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
63
88
|
async function main() {
|
|
64
89
|
const parsed = parseArgs();
|
|
65
90
|
|
|
@@ -76,6 +101,8 @@ async function main() {
|
|
|
76
101
|
});
|
|
77
102
|
run('GraphQL codegen', 'npm', ['run', 'graphql:codegen'], { cwd: uiBundleDir });
|
|
78
103
|
|
|
104
|
+
printLocalPreviewWarning();
|
|
105
|
+
|
|
79
106
|
console.log('\n--- Launching dev server (Ctrl+C to stop) ---\n');
|
|
80
107
|
const devResult = run('Dev server', 'npm', ['run', 'dev'], { cwd: uiBundleDir, optional: true });
|
|
81
108
|
process.exit(devResult.status ?? 0);
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure URL helpers for org-setup.mjs logout-URL productization.
|
|
3
|
+
*
|
|
4
|
+
* The platform requires an ABSOLUTE logout URL on Network metadata — a relative
|
|
5
|
+
* value is rejected at deploy time ("The logout page URL must be an absolute
|
|
6
|
+
* URL."). Apps ship a domain-INDEPENDENT, site-relative path in
|
|
7
|
+
* org-setup.config.json (so the same value is valid on every org the app deploys
|
|
8
|
+
* to); these helpers resolve it to an absolute URL at deploy time, against the
|
|
9
|
+
* site's Experience Cloud origin discovered from the target org's communities.
|
|
10
|
+
*
|
|
11
|
+
* Pure (no fs / no process / no network): org-setup.mjs fetches the community list
|
|
12
|
+
* (Connect API) and owns the fail modes; these helpers only classify + resolve
|
|
13
|
+
* strings, so they carry automated coverage in the consuming test package
|
|
14
|
+
* (org-setup-tests/test/org-setup-url.spec.ts). Companion to org-setup-xml.mjs.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** True when `value` is an absolute http(s) URL — already deployable as-is. */
|
|
18
|
+
export function isAbsoluteLogoutUrl(value) {
|
|
19
|
+
return /^https?:\/\//i.test(String(value).trim());
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Leading path segment of a URL or path:
|
|
24
|
+
* "/propertyrentalapp/" -> "propertyrentalapp"
|
|
25
|
+
* "https://h.site.com/propertyrentalapp" -> "propertyrentalapp"
|
|
26
|
+
* "/" -> ""
|
|
27
|
+
*/
|
|
28
|
+
export function firstPathSegment(pathOrUrl) {
|
|
29
|
+
let pathname;
|
|
30
|
+
try {
|
|
31
|
+
pathname = new URL(pathOrUrl).pathname; // absolute URL
|
|
32
|
+
} catch {
|
|
33
|
+
pathname = String(pathOrUrl).split(/[?#]/)[0]; // relative path
|
|
34
|
+
}
|
|
35
|
+
return pathname.replace(/^\/+/, '').split('/')[0];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Choose the Experience Cloud community whose site URL should anchor a relative
|
|
40
|
+
* logout path, and return its siteUrl (or null when none matches).
|
|
41
|
+
*
|
|
42
|
+
* Matches on the community's siteUrl PATH first — robust even when a community's
|
|
43
|
+
* `urlPathPrefix` differs from its public path (e.g. a React "site container" site
|
|
44
|
+
* whose companion ChatterNetwork carries a "…vforcesite" prefix), and correct on
|
|
45
|
+
* custom domains where each site can have its own origin. Falls back to a
|
|
46
|
+
* community whose `name` equals the derived site name. Does NOT fall back to an
|
|
47
|
+
* arbitrary community: resolving against the wrong origin would silently produce a
|
|
48
|
+
* valid-but-wrong absolute URL, so an unmatched relative path is left for the
|
|
49
|
+
* caller to surface loudly.
|
|
50
|
+
*
|
|
51
|
+
* @param {Array<{name?: string, siteUrl?: string}>} communities
|
|
52
|
+
* @param {string} configLogoutUrl the site-relative path being resolved
|
|
53
|
+
* @param {string} [siteName]
|
|
54
|
+
* @returns {string|null} the matched community siteUrl
|
|
55
|
+
*/
|
|
56
|
+
export function pickCommunityBaseUrl(communities, configLogoutUrl, siteName) {
|
|
57
|
+
const list = Array.isArray(communities) ? communities.filter((c) => c && c.siteUrl) : [];
|
|
58
|
+
const seg = firstPathSegment(configLogoutUrl);
|
|
59
|
+
|
|
60
|
+
if (seg) {
|
|
61
|
+
const byPath = list.find((c) => firstPathSegment(c.siteUrl) === seg);
|
|
62
|
+
if (byPath) return byPath.siteUrl;
|
|
63
|
+
}
|
|
64
|
+
if (siteName) {
|
|
65
|
+
const byName = list.find((c) => c.name === siteName);
|
|
66
|
+
if (byName) return byName.siteUrl;
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Resolve a shipped logout-URL config value to an absolute URL.
|
|
73
|
+
* - already absolute -> returned unchanged (trimmed).
|
|
74
|
+
* - site-relative path -> resolved against `baseUrl` (a community siteUrl).
|
|
75
|
+
* Because the config path is root-relative ("/app/"), only baseUrl's ORIGIN is
|
|
76
|
+
* used, so any siteUrl on the same Experience Cloud domain yields the same
|
|
77
|
+
* result.
|
|
78
|
+
*
|
|
79
|
+
* Throws when a relative value has no baseUrl to resolve against, or when
|
|
80
|
+
* resolution fails to produce an absolute http(s) URL.
|
|
81
|
+
*
|
|
82
|
+
* @param {string} configLogoutUrl
|
|
83
|
+
* @param {string|null} baseUrl
|
|
84
|
+
* @returns {string} an absolute URL
|
|
85
|
+
*/
|
|
86
|
+
export function resolveLogoutUrl(configLogoutUrl, baseUrl) {
|
|
87
|
+
const value = String(configLogoutUrl).trim();
|
|
88
|
+
if (isAbsoluteLogoutUrl(value)) return value;
|
|
89
|
+
|
|
90
|
+
if (!baseUrl) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
`logout URL "${configLogoutUrl}" is site-relative but no Experience Cloud ` +
|
|
93
|
+
`community site URL was found to resolve it into an absolute URL`,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
let resolved;
|
|
97
|
+
try {
|
|
98
|
+
resolved = new URL(value, baseUrl).href;
|
|
99
|
+
} catch (e) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`could not resolve logout URL "${configLogoutUrl}" against "${baseUrl}": ${e.message}`,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
if (!isAbsoluteLogoutUrl(resolved)) {
|
|
105
|
+
throw new Error(`resolved logout URL "${resolved}" is not an absolute http(s) URL`);
|
|
106
|
+
}
|
|
107
|
+
return resolved;
|
|
108
|
+
}
|
|
@@ -180,3 +180,99 @@ export function enableSelfRegInXml(xml, selfRegProfile) {
|
|
|
180
180
|
);
|
|
181
181
|
return { xml: updated, changed: true };
|
|
182
182
|
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Indentation of the first indented child element, so an inserted node matches
|
|
186
|
+
* the file's own formatting — byte-faithful on 2-space and 4-space files alike.
|
|
187
|
+
* Falls back to 4 spaces when the document has no indented child to sample.
|
|
188
|
+
*/
|
|
189
|
+
function firstChildIndent(xml) {
|
|
190
|
+
const m = xml.match(/\n([ \t]+)</);
|
|
191
|
+
return m ? m[1] : ' ';
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Assert a logout URL is safe to interpolate into the network metadata as-is.
|
|
196
|
+
*
|
|
197
|
+
* Mirrors assertSafeProfileName: the value is developer-authored config, so a
|
|
198
|
+
* character that would need XML entity-escaping (& < > " ') signals a mistake to
|
|
199
|
+
* surface loudly rather than silently rewrite. A logout landing URL is a site
|
|
200
|
+
* home path/URL and legitimately needs none of these. Throws NetworkXmlError
|
|
201
|
+
* (mapped by callers to their fail mode) otherwise.
|
|
202
|
+
*/
|
|
203
|
+
export function assertSafeLogoutUrl(url) {
|
|
204
|
+
const s = String(url);
|
|
205
|
+
if (XML_SPECIAL_CHARS.test(s)) {
|
|
206
|
+
throw new NetworkXmlError(
|
|
207
|
+
`logoutUrl "${url}" contains an XML-special character (& < > " '); ` +
|
|
208
|
+
`use a URL without these characters in org-setup.config.json`,
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
return s;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Set <logoutUrl> so members land on THIS site after logging out, instead of the
|
|
216
|
+
* org's default-site logout — which, in an org hosting multiple Experience Cloud
|
|
217
|
+
* sites, can drop a member on a different community's login page.
|
|
218
|
+
*
|
|
219
|
+
* `url` MUST already be an absolute URL: the platform rejects a relative value at
|
|
220
|
+
* deploy time ("The logout page URL must be an absolute URL."). A shipped
|
|
221
|
+
* site-relative config path is resolved to an absolute URL before this is called
|
|
222
|
+
* (see org-setup-url.mjs, driven by ensureLogoutUrl in org-setup.mjs).
|
|
223
|
+
*
|
|
224
|
+
* Asserts this is a Network document (throws NetworkXmlError otherwise). Returns
|
|
225
|
+
* `{ xml, changed }`:
|
|
226
|
+
* - `changed: false` (input returned unchanged) when <logoutUrl> already equals
|
|
227
|
+
* `url` — a configured re-run is a true byte-for-byte no-op.
|
|
228
|
+
* - present-but-different: the value is replaced in place (position preserved).
|
|
229
|
+
* - absent: a <logoutUrl> node is inserted in the CANONICAL position the Metadata
|
|
230
|
+
* API emits. Network's top-level elements serialize in alphabetical order, so
|
|
231
|
+
* the node is placed immediately before the first existing sibling that sorts
|
|
232
|
+
* after "logoutUrl" (or as the last child, before </Network>, when none does).
|
|
233
|
+
* Inserting where a later retrieve would place it keeps this org-deployed file
|
|
234
|
+
* free of reorder churn. The node adopts the file's own indentation and every
|
|
235
|
+
* untouched region is preserved byte-for-byte.
|
|
236
|
+
*
|
|
237
|
+
* Assumes an existing <logoutUrl> is a paired, non-empty node (the shipped sparse
|
|
238
|
+
* network omits it entirely; a deployed org carries a paired value) — the case
|
|
239
|
+
* this productizes.
|
|
240
|
+
*/
|
|
241
|
+
export function setLogoutUrl(xml, url) {
|
|
242
|
+
const network = parseNetwork(xml);
|
|
243
|
+
// Reject XML-special characters rather than escaping them (see
|
|
244
|
+
// assertSafeLogoutUrl) before interpolating raw.
|
|
245
|
+
const safeUrl = assertSafeLogoutUrl(url);
|
|
246
|
+
const current = 'logoutUrl' in network ? String(network.logoutUrl) : null;
|
|
247
|
+
if (current === safeUrl) {
|
|
248
|
+
return { xml, changed: false };
|
|
249
|
+
}
|
|
250
|
+
const node = `<logoutUrl>${safeUrl}</logoutUrl>`;
|
|
251
|
+
// Present-but-different — replace the value in place (preserves position).
|
|
252
|
+
if (/<logoutUrl>[^<]*<\/logoutUrl>/.test(xml)) {
|
|
253
|
+
return { xml: xml.replace(/<logoutUrl>[^<]*<\/logoutUrl>/, node), changed: true };
|
|
254
|
+
}
|
|
255
|
+
// Absent — insert in alphabetical order among the Network's top-level children to
|
|
256
|
+
// match the canonical Metadata API serialization. parseNetwork's keys are the
|
|
257
|
+
// direct child element names; drop fast-xml-parser's attribute (@_*) and text
|
|
258
|
+
// (#*) pseudo-keys.
|
|
259
|
+
const siblings = Object.keys(network).filter((k) => !k.startsWith('@_') && !k.startsWith('#'));
|
|
260
|
+
// The first sibling that sorts AFTER "logoutUrl" — the node goes just before it.
|
|
261
|
+
const successor = siblings.filter((name) => name > 'logoutUrl').sort()[0];
|
|
262
|
+
if (successor) {
|
|
263
|
+
const beforeSuccessor = new RegExp(`(\\n[ \\t]*)(<${successor}\\b)`);
|
|
264
|
+
if (beforeSuccessor.test(xml)) {
|
|
265
|
+
// Reuse the successor's own leading whitespace so the node adopts its indent.
|
|
266
|
+
return { xml: xml.replace(beforeSuccessor, `$1${node}$1$2`), changed: true };
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
// No later-sorting sibling (or its tag wasn't found) — insert as the last child,
|
|
270
|
+
// right before </Network>, at the file's own indent.
|
|
271
|
+
const indent = firstChildIndent(xml);
|
|
272
|
+
const beforeClose = /(\n)([ \t]*<\/Network>)/;
|
|
273
|
+
if (beforeClose.test(xml)) {
|
|
274
|
+
return { xml: xml.replace(beforeClose, `\n${indent}${node}$1$2`), changed: true };
|
|
275
|
+
}
|
|
276
|
+
// Degenerate single-line / rootless form — fall back to just after the open tag.
|
|
277
|
+
return { xml: xml.replace(/(<Network\b[^>]*>)/, `$1\n${indent}${node}`), changed: true };
|
|
278
|
+
}
|
|
@@ -58,8 +58,14 @@ import { validateConfig } from './org-setup-config-schema.mjs';
|
|
|
58
58
|
import {
|
|
59
59
|
addProfileToMemberGroups,
|
|
60
60
|
enableSelfRegInXml,
|
|
61
|
+
setLogoutUrl,
|
|
61
62
|
NetworkXmlError,
|
|
62
63
|
} from './org-setup-xml.mjs';
|
|
64
|
+
import {
|
|
65
|
+
isAbsoluteLogoutUrl,
|
|
66
|
+
pickCommunityBaseUrl,
|
|
67
|
+
resolveLogoutUrl,
|
|
68
|
+
} from './org-setup-url.mjs';
|
|
63
69
|
import {
|
|
64
70
|
discoverAllUIBundleDirs as discoverAllUIBundleDirsIn,
|
|
65
71
|
discoverUIBundleDir as discoverUIBundleDirIn,
|
|
@@ -554,6 +560,15 @@ function loadSocialLoginConfig(config) {
|
|
|
554
560
|
};
|
|
555
561
|
}
|
|
556
562
|
|
|
563
|
+
/**
|
|
564
|
+
* Logout URL config, read from the already-validated config. Returns the string
|
|
565
|
+
* (a site-relative path like "/myapp/", or an absolute URL) or null when no
|
|
566
|
+
* "logoutUrl" is set (the step is then a no-op).
|
|
567
|
+
*/
|
|
568
|
+
function loadLogoutUrlConfig(config) {
|
|
569
|
+
return config.logoutUrl ?? null;
|
|
570
|
+
}
|
|
571
|
+
|
|
557
572
|
/**
|
|
558
573
|
* Enable the "Allow using standard external profiles for self-registration,
|
|
559
574
|
* user creation, and login" org setting via Metadata API deploy.
|
|
@@ -998,6 +1013,123 @@ function ensureNetworkMemberProfile(selfRegConfig, siteName) {
|
|
|
998
1013
|
console.log(` Added profile "${selfRegProfile}" to networkMemberGroups in ${siteName}.network-meta.xml`);
|
|
999
1014
|
}
|
|
1000
1015
|
|
|
1016
|
+
const CONNECT_API_VERSION = '62.0';
|
|
1017
|
+
|
|
1018
|
+
/**
|
|
1019
|
+
* Fetch the org's Experience Cloud communities via the Connect API and return the
|
|
1020
|
+
* `communities` array (possibly empty). Used to discover a site's public origin so
|
|
1021
|
+
* a shipped, site-relative logout path can be resolved to the absolute URL the
|
|
1022
|
+
* platform requires. Throws on transport/parse failure so the caller degrades to a
|
|
1023
|
+
* loud skip. (v62.0 is a safe floor — the /connect/communities resource is stable
|
|
1024
|
+
* across API versions.)
|
|
1025
|
+
*/
|
|
1026
|
+
function fetchCommunities(targetOrg) {
|
|
1027
|
+
const res = spawnSync('sf', [
|
|
1028
|
+
'api', 'request', 'rest',
|
|
1029
|
+
`/services/data/v${CONNECT_API_VERSION}/connect/communities`,
|
|
1030
|
+
'--target-org', targetOrg,
|
|
1031
|
+
], { cwd: ROOT, encoding: 'utf8', timeout: 60000 });
|
|
1032
|
+
if (res.status !== 0) {
|
|
1033
|
+
throw new Error(`Connect communities query failed (sf exit ${res.status ?? 1})`);
|
|
1034
|
+
}
|
|
1035
|
+
let json;
|
|
1036
|
+
try {
|
|
1037
|
+
json = JSON.parse(res.stdout);
|
|
1038
|
+
} catch {
|
|
1039
|
+
throw new Error('could not parse the Connect communities response as JSON');
|
|
1040
|
+
}
|
|
1041
|
+
return Array.isArray(json.communities) ? json.communities : [];
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
/**
|
|
1045
|
+
* Set the site's <logoutUrl> in the network metadata AFTER the initial deploy, then
|
|
1046
|
+
* re-deploy just the network — mirroring the self-registration step.
|
|
1047
|
+
*
|
|
1048
|
+
* Post-deploy (not folded into the main deploy) because the value must be an
|
|
1049
|
+
* ABSOLUTE URL: the platform rejects a relative logout URL at deploy ("The logout
|
|
1050
|
+
* page URL must be an absolute URL."). Apps ship a domain-independent, site-relative
|
|
1051
|
+
* path in org-setup.config.json; it is resolved here against the site's Experience
|
|
1052
|
+
* Cloud origin, which is only discoverable (via the Connect communities API) once
|
|
1053
|
+
* the site exists — i.e. after the main deploy. An already-absolute config value is
|
|
1054
|
+
* used as-is (no lookup).
|
|
1055
|
+
*
|
|
1056
|
+
* Best-effort, mirroring ensureNetworkMemberProfile: a missing network file, a
|
|
1057
|
+
* failure to resolve the absolute URL, a malformed-XML NetworkXmlError, or a failed
|
|
1058
|
+
* network re-deploy each log a LOUD console.error but do NOT throw — the logout URL
|
|
1059
|
+
* is a convenience that must not abort the whole setup. In particular, if the org's
|
|
1060
|
+
* Network emailSenderAddress has drifted from the shipped value, that field blocks
|
|
1061
|
+
* the network deploy; the message then points the operator at the site's
|
|
1062
|
+
* Administration settings. The helper's idempotency check makes a configured re-run
|
|
1063
|
+
* a byte-for-byte no-op with no deploy.
|
|
1064
|
+
*/
|
|
1065
|
+
function ensureLogoutUrl(logoutUrl, siteName, targetOrg) {
|
|
1066
|
+
if (!siteName || !logoutUrl) return;
|
|
1067
|
+
|
|
1068
|
+
const networkXmlPath = resolve(SFDX_SOURCE, 'networks', `${siteName}.network-meta.xml`);
|
|
1069
|
+
if (!existsSync(networkXmlPath)) {
|
|
1070
|
+
console.log(` Network metadata not found: ${networkXmlPath}; skipping logout URL update.`);
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
// Resolve the shipped (site-relative or absolute) config value to the absolute
|
|
1075
|
+
// URL the platform requires. A relative value needs the site's Experience Cloud
|
|
1076
|
+
// origin, discovered from the org's communities.
|
|
1077
|
+
let absoluteUrl;
|
|
1078
|
+
try {
|
|
1079
|
+
let baseUrl = null;
|
|
1080
|
+
if (!isAbsoluteLogoutUrl(logoutUrl)) {
|
|
1081
|
+
baseUrl = pickCommunityBaseUrl(fetchCommunities(targetOrg), logoutUrl, siteName);
|
|
1082
|
+
}
|
|
1083
|
+
absoluteUrl = resolveLogoutUrl(logoutUrl, baseUrl);
|
|
1084
|
+
} catch (e) {
|
|
1085
|
+
console.error(
|
|
1086
|
+
` ERROR: cannot resolve an absolute logout URL for "${siteName}" — ${e.message}. ` +
|
|
1087
|
+
`Skipping; set the logout URL manually in the site's Administration settings.`,
|
|
1088
|
+
);
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
const xml = readFileSync(networkXmlPath, 'utf8');
|
|
1093
|
+
let result;
|
|
1094
|
+
try {
|
|
1095
|
+
result = setLogoutUrl(xml, absoluteUrl);
|
|
1096
|
+
} catch (e) {
|
|
1097
|
+
if (e instanceof NetworkXmlError) {
|
|
1098
|
+
console.error(
|
|
1099
|
+
` ERROR: cannot set logout URL in ${siteName}.network-meta.xml — ${e.message}. ` +
|
|
1100
|
+
`Skipping; set <logoutUrl> manually in the site's Administration settings.`,
|
|
1101
|
+
);
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
throw e;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
if (!result.changed) {
|
|
1108
|
+
console.log(` Logout URL already set to "${absoluteUrl}" in ${siteName}.network-meta.xml; no update needed.`);
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
writeFileSync(networkXmlPath, result.xml);
|
|
1112
|
+
console.log(` Set <logoutUrl>${absoluteUrl}</logoutUrl> in ${siteName}.network-meta.xml`);
|
|
1113
|
+
|
|
1114
|
+
// Re-deploy ONLY the network file (mirrors enableSelfRegistration). Best-effort:
|
|
1115
|
+
// a non-zero exit (e.g. the org's emailSenderAddress differs from the shipped
|
|
1116
|
+
// value and can't be updated) logs loudly and continues.
|
|
1117
|
+
const deployResult = spawnSync('sf', [
|
|
1118
|
+
'project', 'deploy', 'start',
|
|
1119
|
+
'--target-org', targetOrg,
|
|
1120
|
+
'--source-dir', networkXmlPath,
|
|
1121
|
+
], { cwd: ROOT, stdio: 'inherit', shell: true, timeout: 120000 });
|
|
1122
|
+
if (deployResult.status !== 0) {
|
|
1123
|
+
console.error(
|
|
1124
|
+
` ERROR: failed to deploy <logoutUrl> for "${siteName}" (sf exit ${deployResult.status ?? 1}). ` +
|
|
1125
|
+
`If the org's Network emailSenderAddress differs from the shipped value it blocks this deploy — ` +
|
|
1126
|
+
`set the logout URL manually in the site's Administration settings.`,
|
|
1127
|
+
);
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
console.log(` Deployed <logoutUrl> for "${siteName}".`);
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1001
1133
|
/**
|
|
1002
1134
|
* Enable self-registration for an Experience Cloud network.
|
|
1003
1135
|
*
|
|
@@ -1788,6 +1920,7 @@ async function main() {
|
|
|
1788
1920
|
const hasSelfRegConfig = selfRegConfig !== null;
|
|
1789
1921
|
const socialLoginConfig = loadSocialLoginConfig(config);
|
|
1790
1922
|
const hasSocialLoginConfig = socialLoginConfig !== null;
|
|
1923
|
+
const logoutUrl = loadLogoutUrlConfig(config);
|
|
1791
1924
|
|
|
1792
1925
|
// Validate the selfRegProfile name for SOQL-safety up front, alongside the
|
|
1793
1926
|
// config validation and BEFORE any org mutation (login/deploy). A quote /
|
|
@@ -1955,6 +2088,27 @@ async function main() {
|
|
|
1955
2088
|
recordSkipped(deployStep, 'not selected');
|
|
1956
2089
|
}
|
|
1957
2090
|
|
|
2091
|
+
// Set the site's logout URL AFTER deploy so members land back on THIS site after
|
|
2092
|
+
// logging out (not the org default-site login, which in a multi-site org can be a
|
|
2093
|
+
// different community). Post-deploy because the deployed value must be an ABSOLUTE
|
|
2094
|
+
// URL (the platform rejects a relative one) and the shipped site-relative path is
|
|
2095
|
+
// resolved against the site's community origin — which only exists once the site
|
|
2096
|
+
// is deployed. Independent of self-reg; best-effort (a failure logs loudly, does
|
|
2097
|
+
// not abort). An ambiguous multi-network app can't auto-target a single site, so
|
|
2098
|
+
// derivation failure skips it.
|
|
2099
|
+
if (!skipDeploy && logoutUrl) {
|
|
2100
|
+
let logoutSiteName = null;
|
|
2101
|
+
try {
|
|
2102
|
+
logoutSiteName = deriveSiteName();
|
|
2103
|
+
} catch {
|
|
2104
|
+
// ambiguous derivation (multiple network files) — skip logout URL prep
|
|
2105
|
+
}
|
|
2106
|
+
if (logoutSiteName) {
|
|
2107
|
+
console.log('\n--- Ensure logout URL (post-deploy) ---');
|
|
2108
|
+
ensureLogoutUrl(logoutUrl, logoutSiteName, targetOrg);
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
|
|
1958
2112
|
const permsetStep = stepDefs.find((s) => s.key === 'permset');
|
|
1959
2113
|
if (!skipPermset) {
|
|
1960
2114
|
await runStep(permsetStep, targetOrg, async () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforce/ui-bundle-template-feature-react-agentforce-conversation-client",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.69.1",
|
|
4
4
|
"description": "Embedded Agentforce conversation client feature for UI Bundles",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
6
|
"author": "",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"clean": "rm -rf dist"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@salesforce/agentforce-conversation-client": "^11.
|
|
29
|
+
"@salesforce/agentforce-conversation-client": "^11.69.1"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/react": "^19.2.7",
|