@salesforce/ui-bundle-template-feature-react-agentforce-conversation-client 11.68.5 → 11.69.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/dist/CHANGELOG.md CHANGED
@@ -3,6 +3,23 @@
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.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v11.68.6...v11.69.0) (2026-08-25)
7
+
8
+
9
+ ### Features
10
+
11
+ * **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))
12
+
13
+
14
+
15
+ ## [11.68.6](https://github.com/salesforce-experience-platform-emu/webapps/compare/v11.68.5...v11.68.6) (2026-08-24)
16
+
17
+ **Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
18
+
19
+
20
+
21
+
22
+
6
23
  ## [11.68.5](https://github.com/salesforce-experience-platform-emu/webapps/compare/v11.68.4...v11.68.5) (2026-08-24)
7
24
 
8
25
  **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.68.5",
22
- "@salesforce/ui-bundle": "^11.68.5",
21
+ "@salesforce/platform-sdk": "^11.69.0",
22
+ "@salesforce/ui-bundle": "^11.69.0",
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.68.5",
49
- "@salesforce/vite-plugin-ui-bundle": "^11.68.5",
48
+ "@salesforce/graphiti": "^11.69.0",
49
+ "@salesforce/vite-plugin-ui-bundle": "^11.69.0",
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",
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@salesforce/ui-bundle-template-base-sfdx-project",
3
- "version": "11.68.5",
3
+ "version": "11.69.0",
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.68.5",
9
+ "version": "11.69.0",
10
10
  "license": "SEE LICENSE IN LICENSE.txt",
11
11
  "dependencies": {
12
12
  "fast-xml-parser": "^5.9.3",
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/ui-bundle-template-base-sfdx-project",
3
- "version": "11.68.5",
3
+ "version": "11.69.0",
4
4
  "description": "Base SFDX project template",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "publishConfig": {
@@ -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
 
@@ -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.68.5",
3
+ "version": "11.69.0",
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.68.5"
29
+ "@salesforce/agentforce-conversation-client": "^11.69.0"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@types/react": "^19.2.7",