@salesforce/afv-skills 1.47.0 → 1.49.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/package.json +1 -1
- package/skills/education-cloud-domain-configure/SKILL.md +234 -0
- package/skills/education-cloud-domain-configure/references/dependent-features.md +198 -0
- package/skills/education-cloud-domain-configure/references/domain-features/academic-operations.md +386 -0
- package/skills/education-cloud-domain-configure/references/domain-features/alumni-relations.md +155 -0
- package/skills/education-cloud-domain-configure/references/domain-features/cross-domain-dependencies.md +18 -0
- package/skills/education-cloud-domain-configure/references/domain-features/industriessettings-toggle-reference.md +43 -0
- package/skills/education-cloud-domain-configure/references/domain-features/mentoring.md +198 -0
- package/skills/education-cloud-domain-configure/references/domain-features/recruitment-admissions.md +315 -0
- package/skills/education-cloud-domain-configure/references/domain-features/student-management.md +155 -0
- package/skills/education-cloud-domain-configure/references/domain-features/student-success.md +249 -0
- package/skills/education-cloud-domain-configure/references/domain-features.md +27 -0
- package/skills/education-cloud-domain-configure/references/gotchas-extended.md +23 -0
- package/skills/education-cloud-domain-configure/references/toggle-write-path.md +67 -0
- package/skills/education-cloud-domain-configure/scripts/check_license_provisioned.py +62 -0
- package/skills/education-cloud-domain-configure/scripts/check_org_edition.py +44 -0
- package/skills/education-cloud-domain-configure/scripts/check_permission_assigned.py +39 -0
- package/skills/education-cloud-multi-campus-configure/SKILL.md +2 -0
- package/skills/education-cloud-multi-campus-configure/references/foundation_prerequisites.md +4 -3
- package/skills/service-agentforce-channel-configure/SKILL.md +33 -28
- package/skills/service-agentforce-human-escalation-configure/SKILL.md +205 -0
- package/skills/service-agentforce-human-escalation-configure/assets/escalation-thresholds.instructions.md +37 -0
- package/skills/service-agentforce-human-escalation-configure/assets/force-app/main/default/flows/Human_Escalation_Outbound_Flow.flow-meta.xml +173 -0
- package/skills/service-agentforce-human-escalation-configure/assets/package.xml +8 -0
- package/skills/service-agentforce-human-escalation-configure/assets/sfdx-project.json +5 -0
- package/skills/service-agentforce-human-escalation-configure/references/classifier-contracts.md +103 -0
- package/skills/service-agentforce-human-escalation-configure/references/escalation-mechanism.md +63 -0
- package/skills/service-agentforce-human-escalation-configure/references/failure-policy.md +44 -0
- package/skills/service-agentforce-human-escalation-configure/references/nga-escalation.md +57 -0
- package/skills/service-agentforce-human-escalation-configure/references/runtime-verification.md +72 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/classify-agent-active.mjs +49 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/classify-nga-escalation.mjs +166 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/extract-outbound-route.mjs +67 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/patch-escalation-surfaces.mjs +131 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/tests/__init__.py +0 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/tests/_bootstrap.py +249 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/tests/test_escalation_contracts.py +899 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/verify-and-configure.sh +705 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/verify-escalation-config.mjs +89 -0
- package/skills/service-de-channel-routing-configure/SKILL.md +13 -5
- package/skills/service-de-channel-routing-configure/scripts/tests/test_skill_contract.py +25 -0
- package/skills/service-itsm-agentic-setup-agentforce-coordinate/SKILL.md +23 -10
- package/skills/service-itsm-agentic-setup-agentforce-coordinate/examples/output-templates.md +63 -48
- package/skills/service-itsm-agentic-setup-agentforce-coordinate/scripts/verify-child-verdict.mjs +6 -2
- package/skills/service-omni-agent-users-create/SKILL.md +96 -0
- package/skills/service-omni-agent-users-create/assets/create-users.apex.template +80 -0
- package/skills/service-omni-agent-users-create/references/apex-patterns.md +135 -0
- package/skills/service-omni-agent-users-create/references/apex-template-notes.md +40 -0
- package/skills/service-omni-agent-users-create/scripts/detect-and-create.sh +206 -0
- package/skills/service-omni-agent-users-create/scripts/detect-existing.sh +122 -0
- package/skills/service-omni-agent-users-create/scripts/run-create.sh +298 -0
- package/skills/service-omni-agent-users-create/scripts/tests/test_user_create_security.py +171 -0
- package/skills/service-omni-agent-work-sharing-configure/SKILL.md +78 -0
- package/skills/service-omni-agent-work-sharing-configure/references/api-notes.md +31 -0
- package/skills/service-omni-agent-work-sharing-configure/scripts/configure-and-report.sh +186 -0
- package/skills/service-omni-agent-work-sharing-configure/scripts/tests/_bootstrap.py +120 -0
- package/skills/service-omni-agent-work-sharing-configure/scripts/tests/test_agent_work_sharing_contracts.py +93 -0
- package/skills/service-omni-base-settings-configure/SKILL.md +77 -0
- package/skills/service-omni-base-settings-configure/assets/force-app/main/default/settings/OmniChannel.settings-meta.xml +8 -0
- package/skills/service-omni-base-settings-configure/assets/package.xml +8 -0
- package/skills/service-omni-base-settings-configure/assets/sfdx-project.json +12 -0
- package/skills/service-omni-base-settings-configure/references/api-notes.md +121 -0
- package/skills/service-omni-base-settings-configure/scripts/configure-and-report.sh +164 -0
- package/skills/service-omni-base-settings-configure/scripts/detect-existing.sh +69 -0
- package/skills/service-omni-channel-setup-coordinate/SKILL.md +168 -0
- package/skills/service-omni-channel-setup-coordinate/examples/output-templates.md +161 -0
- package/skills/service-omni-channel-setup-coordinate/references/gap-catalog.md +99 -0
- package/skills/service-omni-channel-setup-coordinate/scripts/integration-driver.sh +1542 -0
- package/skills/service-omni-channel-setup-coordinate/scripts/tests/__init__.py +0 -0
- package/skills/service-omni-channel-setup-coordinate/scripts/tests/_bootstrap.py +223 -0
- package/skills/service-omni-channel-setup-coordinate/scripts/tests/test_omni_contracts.py +804 -0
- package/skills/service-omni-command-center-analyze/SKILL.md +84 -0
- package/skills/service-omni-command-center-analyze/references/api-notes.md +54 -0
- package/skills/service-omni-command-center-analyze/scripts/analyze.sh +198 -0
- package/skills/service-omni-command-center-analyze/scripts/tests/_bootstrap.py +115 -0
- package/skills/service-omni-command-center-analyze/scripts/tests/test_command_center_analyze_contracts.py +165 -0
- package/skills/service-omni-permission-set-assign/SKILL.md +88 -0
- package/skills/service-omni-permission-set-assign/assets/package.xml +8 -0
- package/skills/service-omni-permission-set-assign/references/api-notes.md +143 -0
- package/skills/service-omni-permission-set-assign/scripts/verify-and-assign.sh +397 -0
- package/skills/service-omni-presence-status-deploy/SKILL.md +87 -0
- package/skills/service-omni-presence-status-deploy/assets/package.xml +12 -0
- package/skills/service-omni-presence-status-deploy/references/api-notes.md +115 -0
- package/skills/service-omni-presence-status-deploy/scripts/deploy-and-report.sh +422 -0
- package/skills/service-omni-presence-user-config-deploy/SKILL.md +80 -0
- package/skills/service-omni-presence-user-config-deploy/references/api-notes.md +60 -0
- package/skills/service-omni-presence-user-config-deploy/scripts/deploy-and-report.sh +261 -0
- package/skills/service-omni-presence-user-config-deploy/scripts/tests/_bootstrap.py +106 -0
- package/skills/service-omni-presence-user-config-deploy/scripts/tests/test_presence_user_config_contracts.py +142 -0
- package/skills/service-omni-queue-deploy/SKILL.md +91 -0
- package/skills/service-omni-queue-deploy/references/api-notes.md +140 -0
- package/skills/service-omni-queue-deploy/scripts/verify-and-align.sh +470 -0
- package/skills/service-omni-queue-members-assign/SKILL.md +93 -0
- package/skills/service-omni-queue-members-assign/references/api-notes.md +144 -0
- package/skills/service-omni-queue-members-assign/scripts/tests/_bootstrap.py +67 -0
- package/skills/service-omni-queue-members-assign/scripts/tests/test_queue_members_contracts.py +73 -0
- package/skills/service-omni-queue-members-assign/scripts/verify-and-bind.sh +362 -0
- package/skills/service-omni-queue-routing-config-deploy/SKILL.md +101 -0
- package/skills/service-omni-queue-routing-config-deploy/references/api-notes.md +127 -0
- package/skills/service-omni-queue-routing-config-deploy/scripts/tests/_bootstrap.py +90 -0
- package/skills/service-omni-queue-routing-config-deploy/scripts/tests/test_queue_routing_config_contracts.py +70 -0
- package/skills/service-omni-queue-routing-config-deploy/scripts/upsert-and-report.sh +538 -0
- package/skills/service-omni-routing-flow-deploy/SKILL.md +111 -0
- package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_Case_Trigger.SkillsBased.flow-meta.xml +94 -0
- package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_Case_Trigger.flow-meta.xml +88 -0
- package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_Cases.flow-meta.xml +144 -0
- package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_VoiceCall_Trigger.SkillsBased.flow-meta.xml +94 -0
- package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_VoiceCall_Trigger.flow-meta.xml +88 -0
- package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_VoiceCalls.flow-meta.xml +144 -0
- package/skills/service-omni-routing-flow-deploy/assets/package.xml +11 -0
- package/skills/service-omni-routing-flow-deploy/assets/sfdx-project.json +11 -0
- package/skills/service-omni-routing-flow-deploy/references/api-notes.md +265 -0
- package/skills/service-omni-routing-flow-deploy/scripts/deploy-and-report.sh +684 -0
- package/skills/service-omni-service-channel-configure/SKILL.md +83 -0
- package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/Cases.serviceChannel-meta.xml +9 -0
- package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/Incidents.serviceChannel-meta.xml +9 -0
- package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/MessagingSessions.serviceChannel-meta.xml +9 -0
- package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/VoiceCalls.serviceChannel-meta.xml +9 -0
- package/skills/service-omni-service-channel-configure/assets/package.xml +11 -0
- package/skills/service-omni-service-channel-configure/assets/sfdx-project.json +12 -0
- package/skills/service-omni-service-channel-configure/references/api-notes.md +84 -0
- package/skills/service-omni-service-channel-configure/scripts/deploy-and-report.sh +383 -0
- package/skills/service-omni-sidebar-configure/SKILL.md +77 -0
- package/skills/service-omni-sidebar-configure/references/api-notes.md +51 -0
- package/skills/service-omni-sidebar-configure/scripts/enable-and-report.sh +315 -0
- package/skills/service-omni-sidebar-configure/scripts/tests/_bootstrap.py +142 -0
- package/skills/service-omni-sidebar-configure/scripts/tests/test_sidebar_contracts.py +139 -0
- package/skills/service-omni-skills-based-routing-configure/SKILL.md +81 -0
- package/skills/service-omni-skills-based-routing-configure/references/api-notes.md +50 -0
- package/skills/service-omni-skills-based-routing-configure/scripts/configure-and-report.sh +270 -0
- package/skills/service-omni-skills-based-routing-configure/scripts/tests/_bootstrap.py +120 -0
- package/skills/service-omni-skills-based-routing-configure/scripts/tests/test_sbr_contracts.py +129 -0
- package/skills/service-omni-supervisor-config-deploy/SKILL.md +97 -0
- package/skills/service-omni-supervisor-config-deploy/assets/force-app/main/default/omniSupervisorConfigs/Omni_Supervisor.omniSupervisorConfig-meta.xml +9 -0
- package/skills/service-omni-supervisor-config-deploy/assets/package.xml +8 -0
- package/skills/service-omni-supervisor-config-deploy/assets/sfdx-project.json +12 -0
- package/skills/service-omni-supervisor-config-deploy/references/api-notes.md +205 -0
- package/skills/service-omni-supervisor-config-deploy/scripts/deploy-and-report.sh +586 -0
- package/skills/service-omni-supervisor-config-deploy/scripts/tests/_bootstrap.py +153 -0
- package/skills/service-omni-supervisor-config-deploy/scripts/tests/test_supervisor_config_contracts.py +116 -0
- package/skills/service-omni-supervisor-permset-assign/SKILL.md +88 -0
- package/skills/service-omni-supervisor-permset-assign/references/api-notes.md +48 -0
- package/skills/service-omni-supervisor-permset-assign/scripts/verify-and-assign.sh +316 -0
- package/skills/service-omni-supervisor-surface-deploy/SKILL.md +85 -0
- package/skills/service-omni-supervisor-surface-deploy/references/api-notes.md +47 -0
- package/skills/service-omni-supervisor-surface-deploy/scripts/deploy-and-report.sh +216 -0
- package/skills/service-omni-supervisor-surface-deploy/scripts/tests/_bootstrap.py +95 -0
- package/skills/service-omni-supervisor-surface-deploy/scripts/tests/test_supervisor_surface_contracts.py +154 -0
- package/skills/service-omni-supervisor-users-create/SKILL.md +92 -0
- package/skills/service-omni-supervisor-users-create/assets/create-supervisors.apex.template +72 -0
- package/skills/service-omni-supervisor-users-create/references/apex-patterns.md +73 -0
- package/skills/service-omni-supervisor-users-create/references/apex-template-notes.md +41 -0
- package/skills/service-omni-supervisor-users-create/scripts/detect-and-create.sh +197 -0
- package/skills/service-omni-supervisor-users-create/scripts/detect-existing.sh +112 -0
- package/skills/service-omni-supervisor-users-create/scripts/run-create.sh +296 -0
- package/skills/service-omni-work-skill-routing-configure/SKILL.md +80 -0
- package/skills/service-omni-work-skill-routing-configure/references/api-notes.md +67 -0
- package/skills/service-omni-work-skill-routing-configure/scripts/configure-and-report.sh +262 -0
- package/skills/service-omni-work-skill-routing-configure/scripts/tests/_bootstrap.py +121 -0
- package/skills/service-omni-work-skill-routing-configure/scripts/tests/test_work_skill_routing_contracts.py +161 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: service-omni-sidebar-configure
|
|
3
|
+
description: "Use to enable or verify the Omni-Channel sidebar on a Lightning console app. Triggers: enable the Omni sidebar, pin Omni-Channel in a console app, configure the Omni sidebar, turn on the Omni-Channel utility region, verify the pinned Omni widget. Do not use for Aura apps, standard apps, utility-bar-only Omni widgets, or to create a console app."
|
|
4
|
+
allowed-tools: Bash Read Write Edit Glob Grep
|
|
5
|
+
metadata:
|
|
6
|
+
version: "1.0"
|
|
7
|
+
domains: ["Service"]
|
|
8
|
+
minApiVersion: "66.0"
|
|
9
|
+
relatedSkills:
|
|
10
|
+
- "service-omni-base-settings-configure"
|
|
11
|
+
- "service-omni-channel-setup-coordinate"
|
|
12
|
+
accessCheck:
|
|
13
|
+
- type: license
|
|
14
|
+
value: ServiceCloud
|
|
15
|
+
cliTools:
|
|
16
|
+
- tool: ["jq"]
|
|
17
|
+
semver: ">=1.6"
|
|
18
|
+
- tool: ["python3"]
|
|
19
|
+
semver: ">=3.8"
|
|
20
|
+
- tool: ["sf"]
|
|
21
|
+
semver: ">=2.139.6"
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
# service-omni-sidebar-configure
|
|
25
|
+
|
|
26
|
+
Enable the Omni-Channel **sidebar** — the pinned Omni utility region that docks to the side of a Lightning console app — by setting `isOmniPinnedViewEnabled=true` on that app's `CustomApplication` metadata and confirming the flag round-trips. This is the recommended Omni surface for console/demo orgs: reps see incoming work, presence, and the work list docked beside the record, rather than only in the collapsible utility bar. It runs once per console app and is invoked by `service-omni-channel-setup-coordinate` as the final rep-experience step.
|
|
27
|
+
|
|
28
|
+
## Inputs
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
bash scripts/enable-and-report.sh <org-alias> [app_developer_name]
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
- `org-alias` (required).
|
|
35
|
+
- `app_developer_name` (optional). The `CustomApplication` DeveloperName of the Lightning console app. When omitted, the skill queries `AppDefinition` for Lightning console apps: exactly one → adopt it; zero or many → `blocked` (name it explicitly).
|
|
36
|
+
|
|
37
|
+
## Preconditions and safety
|
|
38
|
+
|
|
39
|
+
- Target org authenticated via `sf` CLI, Service Cloud license, `sf` CLI ≥ 2.139.6.
|
|
40
|
+
- Omni-Channel base settings enabled (`service-omni-base-settings-configure`).
|
|
41
|
+
- The target must be a **Lightning console** app that is deployable as `CustomApplication` metadata. Standard apps and Aura apps are out of scope.
|
|
42
|
+
- The three-way `safe_to_write` production guard applies.
|
|
43
|
+
|
|
44
|
+
## Run
|
|
45
|
+
|
|
46
|
+
`enable-and-report.sh` resolves the target app, retrieves its `CustomApplication`, and reads the current `isOmniPinnedViewEnabled` value:
|
|
47
|
+
|
|
48
|
+
- already `true` → `reused` (no deploy).
|
|
49
|
+
- `false` or absent → sets it to `true` and deploys the single `CustomApplication` in one atomic Metadata API call, then **re-retrieves** to confirm the flag is `true` before reporting success.
|
|
50
|
+
|
|
51
|
+
The deploy uses explicit `--metadata "CustomApplication:<name>"` (never `--source-dir`) and is done on its own — Metadata deploys are atomic, so the app is left unchanged on any failure.
|
|
52
|
+
|
|
53
|
+
## Behavior
|
|
54
|
+
|
|
55
|
+
**Idempotent + non-destructive.** The skill only flips the single boolean; it never rewrites tabs, brand, or nav config. When the field is missing it is inserted in its XSD-ordered position (immediately before `<label>`); when present its value is replaced in place.
|
|
56
|
+
|
|
57
|
+
**Auto-detect is conservative.** It adopts an app automatically only when exactly one Lightning console app exists, so it can never silently pin the wrong app on an org with several.
|
|
58
|
+
|
|
59
|
+
## Output contract
|
|
60
|
+
|
|
61
|
+
A single JSON object: `status` ∈ `enabled` | `reused` | `blocked`, `app_developer_name`, `app_label`, `before` (bool), `after` (bool), `deploy_id`, `manual_actions`, `blocking_issue`.
|
|
62
|
+
|
|
63
|
+
- `enabled` — the flag was `false`/absent and is now verified `true`.
|
|
64
|
+
- `reused` — the flag was already `true`.
|
|
65
|
+
- `blocked` — no/many console apps found, retrieve/deploy failure, or the post-verify did not confirm `true`; `blocking_issue` explains and `manual_actions` names the fix or prerequisite skill.
|
|
66
|
+
|
|
67
|
+
## Limitations
|
|
68
|
+
|
|
69
|
+
- One app per invocation; run again for each console app that needs the sidebar.
|
|
70
|
+
- Lightning console `CustomApplication` only — not Aura, not standard apps, not the utility-bar Omni widget.
|
|
71
|
+
- Does not create or lay out the console app, its tabs, or the Omni utility item.
|
|
72
|
+
|
|
73
|
+
## References
|
|
74
|
+
|
|
75
|
+
| File | When to read |
|
|
76
|
+
|---|---|
|
|
77
|
+
| `references/api-notes.md` | CustomApplication `isOmniPinnedViewEnabled` schema/order, AppDefinition detection query, and retrieve/deploy notes |
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# API notes — service-omni-sidebar-configure
|
|
2
|
+
|
|
3
|
+
## The setting
|
|
4
|
+
|
|
5
|
+
The Omni-Channel **sidebar** (the pinned Omni utility region docked to the side of a console
|
|
6
|
+
workspace) is controlled by a single boolean on the `CustomApplication` metadata:
|
|
7
|
+
|
|
8
|
+
```xml
|
|
9
|
+
<isOmniPinnedViewEnabled>true</isOmniPinnedViewEnabled>
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
- It is a **per-app** setting on a Lightning **console** app — not an org-level `OmniChannelSettings`
|
|
13
|
+
flag. Enabling Omni-Channel org-wide (`service-omni-base-settings-configure`) is a prerequisite,
|
|
14
|
+
but does not pin the sidebar on any app.
|
|
15
|
+
- When absent, the platform treats it as `false`. This skill therefore inserts the element when it
|
|
16
|
+
is missing rather than assuming a default.
|
|
17
|
+
|
|
18
|
+
## XSD ordering
|
|
19
|
+
|
|
20
|
+
`CustomApplication` enforces element order. The `is*` booleans
|
|
21
|
+
(`isNavAutoTempTabsDisabled`, `isNavPersonalizationDisabled`, `isNavTabPersistenceDisabled`,
|
|
22
|
+
`isOmniPinnedViewEnabled`) precede `<label>`. The skill inserts `<isOmniPinnedViewEnabled>`
|
|
23
|
+
immediately before the first top-level `<label>`, which keeps the payload schema-valid whether or
|
|
24
|
+
not the other booleans are present. If a future release reorders the schema and the deploy fails
|
|
25
|
+
with an XSD-order error, adjust the insertion anchor here.
|
|
26
|
+
|
|
27
|
+
## Detecting console apps (auto-detect path)
|
|
28
|
+
|
|
29
|
+
When no app name is supplied, candidates come from the Tooling API:
|
|
30
|
+
|
|
31
|
+
```sql
|
|
32
|
+
SELECT DeveloperName, Label FROM AppDefinition WHERE UiType='Lightning' AND NavType='Console'
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`AppDefinition.DeveloperName` matches the `CustomApplication` DeveloperName used for retrieve/deploy
|
|
36
|
+
for custom apps. The skill adopts a target automatically **only** when exactly one row is returned;
|
|
37
|
+
zero or many → `blocked` so it can never pin the wrong app. Standard apps surfaced by this query are
|
|
38
|
+
generally not deployable as `CustomApplication`; if one is named explicitly the retrieve returns no
|
|
39
|
+
`.app-meta.xml` and the skill blocks with a clear message.
|
|
40
|
+
|
|
41
|
+
## Retrieve / deploy
|
|
42
|
+
|
|
43
|
+
- Retrieve and deploy use explicit `--metadata "CustomApplication:<name>"` (never `--source-dir`) so
|
|
44
|
+
no sibling metadata can piggyback.
|
|
45
|
+
- The deploy runs `--async` and polls `sf project deploy report` to a terminal state — a synchronous
|
|
46
|
+
watched deploy can raise `ClientTimeoutError` under org load even when the server-side deploy
|
|
47
|
+
succeeds (same false-red class handled by the other Omni leaves).
|
|
48
|
+
- Metadata deploys are atomic, so a failed deploy leaves the app unchanged; the skill then reports
|
|
49
|
+
`blocked` and never claims partial success.
|
|
50
|
+
- Success is confirmed by a **re-retrieve** that reads `isOmniPinnedViewEnabled` back as `true`; a
|
|
51
|
+
green deploy that does not round-trip the flag is reported `blocked`, not `enabled`.
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# enable-and-report.sh - enable the Omni-Channel sidebar on a Lightning console app (CustomApplication.isOmniPinnedViewEnabled=true) and verify round-trip; idempotent, non-destructive (only the one boolean). Args, exit codes: SKILL.md.
|
|
3
|
+
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
if [ $# -lt 1 ]; then
|
|
7
|
+
echo '{"error":"Usage: bash enable-and-report.sh <org-alias> [app_developer_name]"}' >&2
|
|
8
|
+
exit 1
|
|
9
|
+
fi
|
|
10
|
+
|
|
11
|
+
ORG="$1"
|
|
12
|
+
APP_DN_ARG="${2:-}"
|
|
13
|
+
|
|
14
|
+
# App DeveloperName grammar (when supplied): leading letter, then letters/digits/underscore, max 80.
|
|
15
|
+
# A namespace prefix (ns__Name) is permitted for packaged apps.
|
|
16
|
+
if [ -n "$APP_DN_ARG" ] && ! [[ "$APP_DN_ARG" =~ ^([A-Za-z][A-Za-z0-9]{0,14}__)?[A-Za-z][A-Za-z0-9_]{0,79}$ ]]; then
|
|
17
|
+
echo "{\"error\":\"Invalid app_developer_name '$APP_DN_ARG'. Must be a CustomApplication DeveloperName (letters, digits, underscores; optional ns__ prefix).\"}" >&2
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
if ! sf org display --target-org "$ORG" --json >/dev/null 2>&1; then
|
|
22
|
+
echo "{\"error\":\"Org alias '$ORG' is not authenticated. Run: sf org login web -a $ORG -r <my-domain-url>\"}" >&2
|
|
23
|
+
exit 1
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
# Helpers
|
|
27
|
+
emit_blocked() {
|
|
28
|
+
local msg="$1"
|
|
29
|
+
local click_path="${2:-}"
|
|
30
|
+
local app_dn="${3:-}"
|
|
31
|
+
local target_skill="${4:-}"
|
|
32
|
+
|
|
33
|
+
local manual_actions='[]'
|
|
34
|
+
if [ -n "$target_skill" ]; then
|
|
35
|
+
manual_actions=$(jq -n --arg ts "$target_skill" '[{id: "MISSING_PREREQ", title: "Run prerequisite skill", target_skill: $ts}]')
|
|
36
|
+
elif [ -n "$click_path" ]; then
|
|
37
|
+
manual_actions=$(jq -n --arg cp "$click_path" '[{id: "MANUAL_FIX", title: "Manual fix required", click_path: $cp}]')
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
EMITTED=1
|
|
41
|
+
jq -n \
|
|
42
|
+
--arg msg "$msg" \
|
|
43
|
+
--arg dn "$app_dn" \
|
|
44
|
+
--argjson ma "$manual_actions" \
|
|
45
|
+
'{
|
|
46
|
+
skill: "service-omni-sidebar-configure",
|
|
47
|
+
status: "blocked",
|
|
48
|
+
app_developer_name: (if $dn == "" then null else $dn end),
|
|
49
|
+
app_label: null,
|
|
50
|
+
before: null,
|
|
51
|
+
after: null,
|
|
52
|
+
deploy_id: null,
|
|
53
|
+
manual_actions: $ma,
|
|
54
|
+
blocking_issue: $msg
|
|
55
|
+
}'
|
|
56
|
+
exit 1
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
emit_plan() {
|
|
60
|
+
local status="$1"
|
|
61
|
+
local detail="$2"
|
|
62
|
+
local app_dn="$3"
|
|
63
|
+
EMITTED=1
|
|
64
|
+
jq -n \
|
|
65
|
+
--arg status "$status" \
|
|
66
|
+
--arg detail "$detail" \
|
|
67
|
+
--arg dn "$app_dn" \
|
|
68
|
+
'{
|
|
69
|
+
skill: "service-omni-sidebar-configure",
|
|
70
|
+
status: $status,
|
|
71
|
+
plan_mode: true,
|
|
72
|
+
plan_detail: $detail,
|
|
73
|
+
app_developer_name: (if $dn == "" then null else $dn end),
|
|
74
|
+
manual_actions: [],
|
|
75
|
+
blocking_issue: null
|
|
76
|
+
}'
|
|
77
|
+
exit 0
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# Fail-safe: the leaf runs under `set -euo pipefail` with several unguarded jq/sed/awk calls, so a
|
|
81
|
+
# transient failure (e.g. a flaky Metadata API) could otherwise kill it with empty stdout. Emit a
|
|
82
|
+
# structured blocked JSON on any exit that happens before a real result, so the caller never parses
|
|
83
|
+
# empty output. EMITTED is flipped to 1 immediately before each intentional emission below.
|
|
84
|
+
EMITTED=0
|
|
85
|
+
WORK=""
|
|
86
|
+
cleanup_work() { [ -n "${WORK:-}" ] && rm -rf "$WORK" 2>/dev/null || true; }
|
|
87
|
+
on_exit() {
|
|
88
|
+
cleanup_work
|
|
89
|
+
if [ "$EMITTED" != "1" ]; then
|
|
90
|
+
jq -n --arg dn "${APP_DN:-${APP_DN_ARG:-}}" \
|
|
91
|
+
'{skill:"service-omni-sidebar-configure", status:"blocked",
|
|
92
|
+
app_developer_name:(if $dn=="" then null else $dn end),
|
|
93
|
+
app_label:null, before:null, after:null, deploy_id:null, manual_actions:[],
|
|
94
|
+
blocking_issue:"Sidebar enablement aborted before completing (unexpected error; see stderr). Re-run, optionally with an explicit app_developer_name."}'
|
|
95
|
+
fi
|
|
96
|
+
}
|
|
97
|
+
trap on_exit EXIT
|
|
98
|
+
|
|
99
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
100
|
+
|
|
101
|
+
retrieve_app_xml() {
|
|
102
|
+
# Retrieve CustomApplication:<member> into a fresh temp DX project and echo the .app-meta.xml path
|
|
103
|
+
# (empty if none returned). Caller owns cleanup of the printed dir's parent.
|
|
104
|
+
local member="${1:-$APP_DN}"
|
|
105
|
+
local work; work="$(mktemp -d)"
|
|
106
|
+
cp "$SCRIPT_DIR/../assets/sfdx-project.json" "$work/sfdx-project.json" 2>/dev/null || echo '{"packageDirectories":[{"path":"force-app","default":true}],"sourceApiVersion":"66.0"}' > "$work/sfdx-project.json"
|
|
107
|
+
mkdir -p "$work/force-app/main/default"
|
|
108
|
+
( cd "$work" && sf project retrieve start --target-org "$ORG" \
|
|
109
|
+
--metadata "CustomApplication:${member}" --json >/dev/null 2>&1 || true )
|
|
110
|
+
local xml; xml=$(find "$work" -name "*.app-meta.xml" 2>/dev/null | head -1)
|
|
111
|
+
echo "$work|$xml"
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
# Step 1 - Organization + safe_to_write guard
|
|
115
|
+
ORG_GUARD_JSON=$(sf data query --target-org "$ORG" \
|
|
116
|
+
--query "SELECT IsSandbox, TrialExpirationDate, OrganizationType FROM Organization LIMIT 1" \
|
|
117
|
+
--json 2>/dev/null) || emit_blocked "Failed to query Organization for the safe_to_write guard."
|
|
118
|
+
IS_SANDBOX=$(echo "$ORG_GUARD_JSON" | jq -r '.result.records[0].IsSandbox')
|
|
119
|
+
TRIAL_EXP=$(echo "$ORG_GUARD_JSON" | jq -r '.result.records[0].TrialExpirationDate // "null"')
|
|
120
|
+
ORG_TYPE=$(echo "$ORG_GUARD_JSON" | jq -r '.result.records[0].OrganizationType')
|
|
121
|
+
|
|
122
|
+
SAFE_TO_WRITE=false
|
|
123
|
+
if [ "$IS_SANDBOX" = "true" ] \
|
|
124
|
+
|| [ "$TRIAL_EXP" != "null" ] \
|
|
125
|
+
|| [ "$ORG_TYPE" = "Developer Edition" ] \
|
|
126
|
+
|| [ "$ORG_TYPE" = "Base Edition" ]; then
|
|
127
|
+
SAFE_TO_WRITE=true
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
if [ "$SAFE_TO_WRITE" != "true" ]; then
|
|
131
|
+
emit_blocked "Refusing to modify an app on a real production customer org (IsSandbox=$IS_SANDBOX, TrialExpirationDate=$TRIAL_EXP, OrganizationType=$ORG_TYPE). Target a sandbox, trial CDO, or Developer Edition org."
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
# Step 2 - Resolve the target Lightning console app
|
|
135
|
+
APP_DN="$APP_DN_ARG"
|
|
136
|
+
APP_LABEL=""
|
|
137
|
+
if [ -z "$APP_DN" ]; then
|
|
138
|
+
# Standard apps are frequently read-only even though AppDefinition reports them as console apps.
|
|
139
|
+
# Enumerate actual CustomApplication members and inspect only non-standard metadata so automatic
|
|
140
|
+
# selection cannot pick an undeployable platform app.
|
|
141
|
+
APP_METADATA_JSON=$(sf org list metadata --metadata-type CustomApplication \
|
|
142
|
+
--target-org "$ORG" --json 2>/dev/null) \
|
|
143
|
+
|| emit_blocked "Could not list CustomApplication metadata to auto-detect a deployable Lightning console app. Re-run with a reachable org, or pass an explicit app_developer_name."
|
|
144
|
+
echo "$APP_METADATA_JSON" | jq -e '.result | arrays' >/dev/null 2>&1 \
|
|
145
|
+
|| emit_blocked "CustomApplication metadata discovery returned no parseable result (inconclusive). Re-run, or pass an explicit app_developer_name."
|
|
146
|
+
|
|
147
|
+
CANDIDATES='[]'
|
|
148
|
+
CUSTOM_MEMBERS=$(echo "$APP_METADATA_JSON" | jq -r '.result[]?.fullName | select(startswith("standard__") | not)')
|
|
149
|
+
for APP_MEMBER in $CUSTOM_MEMBERS; do
|
|
150
|
+
CANDIDATE_RET=$(retrieve_app_xml "$APP_MEMBER")
|
|
151
|
+
CANDIDATE_WORK="${CANDIDATE_RET%%|*}"
|
|
152
|
+
CANDIDATE_XML="${CANDIDATE_RET#*|}"
|
|
153
|
+
if [ -n "$CANDIDATE_XML" ] && [ -f "$CANDIDATE_XML" ] \
|
|
154
|
+
&& grep -q '<uiType>Lightning</uiType>' "$CANDIDATE_XML" \
|
|
155
|
+
&& grep -q '<navType>Console</navType>' "$CANDIDATE_XML"; then
|
|
156
|
+
CANDIDATES=$(echo "$CANDIDATES" | jq --arg dn "$APP_MEMBER" '. + [$dn]')
|
|
157
|
+
fi
|
|
158
|
+
rm -rf "$CANDIDATE_WORK"
|
|
159
|
+
done
|
|
160
|
+
|
|
161
|
+
APP_COUNT=$(echo "$CANDIDATES" | jq 'length')
|
|
162
|
+
if [ "$APP_COUNT" = "0" ]; then
|
|
163
|
+
emit_blocked "No deployable custom Lightning console app found on this org. Create one first, then pass its DeveloperName." \
|
|
164
|
+
"Setup → App Manager → New Lightning App → Navigation Style: Console → Save"
|
|
165
|
+
elif [ "$APP_COUNT" != "1" ]; then
|
|
166
|
+
emit_blocked "Found $APP_COUNT deployable custom Lightning console apps ($CANDIDATES). Re-run with an explicit app_developer_name so the sidebar is enabled on the intended app."
|
|
167
|
+
fi
|
|
168
|
+
APP_DN=$(echo "$CANDIDATES" | jq -r '.[0]')
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
# Step 3 - Retrieve the app's CustomApplication metadata
|
|
172
|
+
RET=$(retrieve_app_xml "$APP_DN")
|
|
173
|
+
WORK="${RET%%|*}"
|
|
174
|
+
APP_XML="${RET#*|}"
|
|
175
|
+
|
|
176
|
+
# Standard apps expose AppDefinition.DeveloperName without the standard__ prefix, but the
|
|
177
|
+
# CustomApplication metadata member requires it (e.g. standard__LightningService). If the plain
|
|
178
|
+
# member did not resolve and the name is unprefixed, retry with standard__ and adopt whichever
|
|
179
|
+
# member actually retrieves, so the later deploy targets the correct component.
|
|
180
|
+
if { [ -z "$APP_XML" ] || [ ! -f "$APP_XML" ]; } && [[ "$APP_DN" != *"__"* ]]; then
|
|
181
|
+
cleanup_work
|
|
182
|
+
RET=$(retrieve_app_xml "standard__$APP_DN")
|
|
183
|
+
WORK="${RET%%|*}"
|
|
184
|
+
APP_XML="${RET#*|}"
|
|
185
|
+
{ [ -n "$APP_XML" ] && [ -f "$APP_XML" ]; } && APP_DN="standard__$APP_DN"
|
|
186
|
+
fi
|
|
187
|
+
|
|
188
|
+
if [ -z "$APP_XML" ] || [ ! -f "$APP_XML" ]; then
|
|
189
|
+
emit_blocked "Could not retrieve CustomApplication '$APP_DN' (it may not exist, may be a standard/Aura app that is not deployable as CustomApplication, or the retrieve returned no metadata)." \
|
|
190
|
+
"Setup → App Manager → confirm '$APP_DN' is a custom Lightning console app"
|
|
191
|
+
fi
|
|
192
|
+
|
|
193
|
+
# Current value of the boolean (true only when the element is present AND set true).
|
|
194
|
+
current_flag() {
|
|
195
|
+
if grep -oE "<isOmniPinnedViewEnabled>[^<]*</isOmniPinnedViewEnabled>" "$1" 2>/dev/null \
|
|
196
|
+
| grep -qi ">true<"; then
|
|
197
|
+
echo true
|
|
198
|
+
else
|
|
199
|
+
echo false
|
|
200
|
+
fi
|
|
201
|
+
}
|
|
202
|
+
BEFORE=$(current_flag "$APP_XML")
|
|
203
|
+
|
|
204
|
+
# PLAN_ONLY - all reads above are read-only; report intent and stop before any write.
|
|
205
|
+
if [ "${PLAN_ONLY:-}" = "1" ]; then
|
|
206
|
+
if [ "$BEFORE" = "true" ]; then
|
|
207
|
+
emit_plan "reused" "Omni sidebar already enabled (isOmniPinnedViewEnabled=true) on '$APP_DN'." "$APP_DN"
|
|
208
|
+
else
|
|
209
|
+
emit_plan "action_needed" "Would set isOmniPinnedViewEnabled=true on Lightning console app '$APP_DN'." "$APP_DN"
|
|
210
|
+
fi
|
|
211
|
+
fi
|
|
212
|
+
|
|
213
|
+
# Step 4 - Already enabled → reused (no deploy)
|
|
214
|
+
if [ "$BEFORE" = "true" ]; then
|
|
215
|
+
EMITTED=1
|
|
216
|
+
jq -n --arg dn "$APP_DN" --arg lbl "$APP_LABEL" \
|
|
217
|
+
'{
|
|
218
|
+
skill: "service-omni-sidebar-configure",
|
|
219
|
+
status: "reused",
|
|
220
|
+
app_developer_name: $dn,
|
|
221
|
+
app_label: (if $lbl == "" then null else $lbl end),
|
|
222
|
+
before: true,
|
|
223
|
+
after: true,
|
|
224
|
+
deploy_id: null,
|
|
225
|
+
manual_actions: [],
|
|
226
|
+
blocking_issue: null
|
|
227
|
+
}'
|
|
228
|
+
exit 0
|
|
229
|
+
fi
|
|
230
|
+
|
|
231
|
+
# Step 5 - Set the flag (replace in place, or insert in XSD order before <label>)
|
|
232
|
+
if grep -q "<isOmniPinnedViewEnabled>" "$APP_XML"; then
|
|
233
|
+
sed -E "s|<isOmniPinnedViewEnabled>[^<]*</isOmniPinnedViewEnabled>|<isOmniPinnedViewEnabled>true</isOmniPinnedViewEnabled>|" \
|
|
234
|
+
"$APP_XML" > "$APP_XML.tmp" && mv "$APP_XML.tmp" "$APP_XML"
|
|
235
|
+
else
|
|
236
|
+
# Insert immediately before the first top-level <label> - its XSD-ordered position in
|
|
237
|
+
# CustomApplication (the is* booleans precede <label>). Only the first match is edited.
|
|
238
|
+
awk 'BEGIN{done=0}
|
|
239
|
+
/<label>/ && !done { print " <isOmniPinnedViewEnabled>true</isOmniPinnedViewEnabled>"; done=1 }
|
|
240
|
+
{ print }' "$APP_XML" > "$APP_XML.tmp" && mv "$APP_XML.tmp" "$APP_XML"
|
|
241
|
+
fi
|
|
242
|
+
|
|
243
|
+
if [ "$(current_flag "$APP_XML")" != "true" ]; then
|
|
244
|
+
emit_blocked "Failed to set isOmniPinnedViewEnabled in the retrieved CustomApplication payload for '$APP_DN' (unexpected app metadata shape)." "" "$APP_DN"
|
|
245
|
+
fi
|
|
246
|
+
|
|
247
|
+
# Step 6 - Deploy the single CustomApplication (async + poll to a terminal state)
|
|
248
|
+
START_JSON=$(cd "$WORK" && sf project deploy start --target-org "$ORG" \
|
|
249
|
+
--metadata "CustomApplication:${APP_DN}" --async --json 2>/dev/null || true)
|
|
250
|
+
DEPLOY_ID=$(echo "$START_JSON" | jq -r '.result.id // ""' 2>/dev/null)
|
|
251
|
+
DEPLOY_JSON="$START_JSON"
|
|
252
|
+
|
|
253
|
+
if [ -n "$DEPLOY_ID" ] && [ "$DEPLOY_ID" != "null" ]; then
|
|
254
|
+
POLL_STATUS=""
|
|
255
|
+
for _ in $(seq 1 40); do
|
|
256
|
+
REPORT_JSON=$(cd "$WORK" && sf project deploy report --job-id "$DEPLOY_ID" --target-org "$ORG" --json 2>/dev/null || true)
|
|
257
|
+
CUR_STATUS=$(echo "$REPORT_JSON" | jq -r '.result.status // ""' 2>/dev/null)
|
|
258
|
+
case "$CUR_STATUS" in
|
|
259
|
+
Succeeded|Failed|SucceededPartial|Canceled) DEPLOY_JSON="$REPORT_JSON"; POLL_STATUS="$CUR_STATUS"; break ;;
|
|
260
|
+
esac
|
|
261
|
+
sleep 15
|
|
262
|
+
done
|
|
263
|
+
if [ -z "$POLL_STATUS" ]; then
|
|
264
|
+
emit_blocked "CustomApplication deploy job $DEPLOY_ID for '$APP_DN' did not reach a terminal state within the poll budget. Not reporting success." "" "$APP_DN"
|
|
265
|
+
fi
|
|
266
|
+
fi
|
|
267
|
+
|
|
268
|
+
DEPLOY_SUCCESS=$(echo "$DEPLOY_JSON" | jq -r '.result.success // false' 2>/dev/null)
|
|
269
|
+
DEPLOY_ID=$(echo "$DEPLOY_JSON" | jq -r '.result.id // ""' 2>/dev/null)
|
|
270
|
+
|
|
271
|
+
if [ "$DEPLOY_SUCCESS" != "true" ]; then
|
|
272
|
+
FAILURES=$(echo "$DEPLOY_JSON" | jq -r '.result.details.componentFailures // [] | map(.problem) | join("; ")' 2>/dev/null)
|
|
273
|
+
if [ -z "$FAILURES" ] || [ "$FAILURES" = "null" ]; then
|
|
274
|
+
FAILURES=$(echo "$DEPLOY_JSON" | jq -r '.message // .result.errorMessage // "Unknown deploy error"' 2>/dev/null)
|
|
275
|
+
fi
|
|
276
|
+
# Standard console apps (standard__*) are frequently read-only via the Metadata API, so the
|
|
277
|
+
# fallback deploy can be rejected. Give a targeted remediation instead of only the raw error.
|
|
278
|
+
if [[ "$APP_DN" == standard__* ]]; then
|
|
279
|
+
emit_blocked "Deploy of the standard console app '$APP_DN' failed: $FAILURES. Standard apps are often not modifiable via the Metadata API - enable the Omni sidebar in Setup, or re-run this skill against a custom Lightning console app (pass its DeveloperName)." \
|
|
280
|
+
"Setup → App Manager → ${APP_DN#standard__} → Edit → enable the Omni-Channel pinned/utility sidebar" "$APP_DN"
|
|
281
|
+
fi
|
|
282
|
+
emit_blocked "Deploy of CustomApplication '$APP_DN' failed: $FAILURES" "" "$APP_DN"
|
|
283
|
+
fi
|
|
284
|
+
|
|
285
|
+
# Step 7 - Post-verify: re-retrieve and confirm the flag is true on the org
|
|
286
|
+
AFTER="unverified"
|
|
287
|
+
for verify_attempt in 1 2 3; do
|
|
288
|
+
VRET=$(retrieve_app_xml "$APP_DN")
|
|
289
|
+
VWORK="${VRET%%|*}"; VXML="${VRET#*|}"
|
|
290
|
+
if [ -n "$VXML" ] && [ -f "$VXML" ]; then
|
|
291
|
+
AFTER=$(current_flag "$VXML")
|
|
292
|
+
rm -rf "$VWORK"
|
|
293
|
+
break
|
|
294
|
+
fi
|
|
295
|
+
rm -rf "$VWORK"
|
|
296
|
+
[ "$verify_attempt" -lt 3 ] && sleep 10
|
|
297
|
+
done
|
|
298
|
+
|
|
299
|
+
if [ "$AFTER" != "true" ]; then
|
|
300
|
+
emit_blocked "Deploy reported success but isOmniPinnedViewEnabled is not true for '$APP_DN' on post-verify (retrieved metadata does not confirm the sidebar is pinned)." "" "$APP_DN"
|
|
301
|
+
fi
|
|
302
|
+
|
|
303
|
+
EMITTED=1
|
|
304
|
+
jq -n --arg dn "$APP_DN" --arg lbl "$APP_LABEL" --arg deploy_id "$DEPLOY_ID" \
|
|
305
|
+
'{
|
|
306
|
+
skill: "service-omni-sidebar-configure",
|
|
307
|
+
status: "enabled",
|
|
308
|
+
app_developer_name: $dn,
|
|
309
|
+
app_label: (if $lbl == "" then null else $lbl end),
|
|
310
|
+
before: false,
|
|
311
|
+
after: true,
|
|
312
|
+
deploy_id: (if $deploy_id == "" then null else $deploy_id end),
|
|
313
|
+
manual_actions: [],
|
|
314
|
+
blocking_issue: null
|
|
315
|
+
}'
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""Shared paths + fake-`sf` harness for the sidebar-configure contract suite (org-free, network-free): drives enable-and-report.sh via subprocess with scrubbed-PATH and production/sandbox fake-sf fixtures (un-pinned/pinned app, console-app auto-detect cardinality)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import subprocess
|
|
6
|
+
import tempfile
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
SKILLS_ROOT = Path(__file__).resolve().parents[3]
|
|
10
|
+
SKILL_DIR = "service-omni-sidebar-configure"
|
|
11
|
+
SCRIPT = f"{SKILL_DIR}/scripts/enable-and-report.sh"
|
|
12
|
+
|
|
13
|
+
# PRODUCTION org - the safe_to_write guard must refuse before any write.
|
|
14
|
+
_FAKE_SF_PROD = r"""#!/usr/bin/env bash
|
|
15
|
+
sub="${1:-} ${2:-}"; args="$*"
|
|
16
|
+
case "$sub" in
|
|
17
|
+
"org display") echo '{"status":0,"result":{"id":"00Dxx0000000000EAA"}}'; exit 0;;
|
|
18
|
+
"data query")
|
|
19
|
+
if printf '%s' "$args" | grep -q "FROM Organization"; then
|
|
20
|
+
echo '{"status":0,"result":{"records":[{"IsSandbox":false,"TrialExpirationDate":null,"OrganizationType":"Enterprise Edition"}]}}'
|
|
21
|
+
else
|
|
22
|
+
echo '{"status":0,"result":{"records":[],"totalSize":0}}'
|
|
23
|
+
fi
|
|
24
|
+
exit 0;;
|
|
25
|
+
"project deploy") echo '{"status":1,"result":{"success":false},"message":"FAKE_SF_DEPLOY_SHOULD_NOT_BE_REACHED"}'; exit 1;;
|
|
26
|
+
*) echo '{"status":0,"result":{}}'; exit 0;;
|
|
27
|
+
esac
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
# SANDBOX org. Tunables:
|
|
31
|
+
# FAKE_APP_PINNED=1 → the retrieved CustomApplication already has isOmniPinnedViewEnabled=true
|
|
32
|
+
# (reused path). Default 0 → the element is absent (insert-before-label path).
|
|
33
|
+
# FAKE_CONSOLE_APPS=N → number of deployable custom Lightning console apps returned by metadata
|
|
34
|
+
# discovery (0 → blocked, 1 → adopt, >1 → blocked). Default 1.
|
|
35
|
+
# A successful deploy touches a marker next to the fake sf; the subsequent re-retrieve then reports
|
|
36
|
+
# the flag as true, so the enable → deploy → post-verify path converges statefully.
|
|
37
|
+
_FAKE_SF_SANDBOX = r"""#!/usr/bin/env bash
|
|
38
|
+
sub="${1:-} ${2:-}"; args="$*"
|
|
39
|
+
SELF_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
40
|
+
APP_PINNED="${FAKE_APP_PINNED:-0}"
|
|
41
|
+
CONSOLE_APPS="${FAKE_CONSOLE_APPS:-1}"
|
|
42
|
+
if [ -n "${FAKE_SF_CALLLOG:-}" ]; then printf '%s\n' "$args" >> "$FAKE_SF_CALLLOG"; fi
|
|
43
|
+
case "$sub" in
|
|
44
|
+
"org display") echo '{"status":0,"result":{"id":"00Dxx0000000000EAA"}}'; exit 0;;
|
|
45
|
+
"org list")
|
|
46
|
+
case "$CONSOLE_APPS" in
|
|
47
|
+
0) echo '{"status":0,"result":[{"fullName":"standard__LightningService"}]}' ;;
|
|
48
|
+
1) echo '{"status":0,"result":[{"fullName":"standard__LightningService"},{"fullName":"Demo_Console"}]}' ;;
|
|
49
|
+
*) echo '{"status":0,"result":[{"fullName":"standard__LightningService"},{"fullName":"Demo_Console"},{"fullName":"Other_Console"}]}' ;;
|
|
50
|
+
esac
|
|
51
|
+
exit 0;;
|
|
52
|
+
"data query")
|
|
53
|
+
if printf '%s' "$args" | grep -q "FROM Organization"; then
|
|
54
|
+
echo '{"status":0,"result":{"records":[{"IsSandbox":true,"TrialExpirationDate":null,"OrganizationType":"Developer Edition"}]}}'
|
|
55
|
+
elif printf '%s' "$args" | grep -q "FROM AppDefinition"; then
|
|
56
|
+
if printf '%s' "$args" | grep -q -- "--use-tooling-api"; then
|
|
57
|
+
echo '{"status":1,"name":"INVALID_TYPE","message":"AppDefinition is not supported by Tooling API"}'
|
|
58
|
+
exit 1
|
|
59
|
+
fi
|
|
60
|
+
case "$CONSOLE_APPS" in
|
|
61
|
+
0) echo '{"status":0,"result":{"records":[],"totalSize":0}}';;
|
|
62
|
+
1) echo '{"status":0,"result":{"records":[{"DeveloperName":"Demo_Console","Label":"Demo Console"}],"totalSize":1}}';;
|
|
63
|
+
*) echo '{"status":0,"result":{"records":[{"DeveloperName":"Demo_Console","Label":"Demo Console"},{"DeveloperName":"Other_Console","Label":"Other Console"}],"totalSize":2}}';;
|
|
64
|
+
esac
|
|
65
|
+
else
|
|
66
|
+
echo '{"status":0,"result":{"records":[],"totalSize":0}}'
|
|
67
|
+
fi
|
|
68
|
+
exit 0;;
|
|
69
|
+
"project retrieve")
|
|
70
|
+
appname=$(printf '%s' "$args" | sed -n 's/.*CustomApplication:\([A-Za-z0-9_]*\).*/\1/p'); appname="${appname:-Demo_Console}"
|
|
71
|
+
pinned=false
|
|
72
|
+
if [ "$APP_PINNED" = "1" ] || [ -f "$SELF_DIR/.deployed" ]; then pinned=true; fi
|
|
73
|
+
if [ "$pinned" = "true" ]; then
|
|
74
|
+
cat > "./${appname}.app-meta.xml" <<XML
|
|
75
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
76
|
+
<CustomApplication xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
77
|
+
<isOmniPinnedViewEnabled>true</isOmniPinnedViewEnabled>
|
|
78
|
+
<label>${appname}</label>
|
|
79
|
+
<navType>Console</navType>
|
|
80
|
+
<uiType>Lightning</uiType>
|
|
81
|
+
</CustomApplication>
|
|
82
|
+
XML
|
|
83
|
+
else
|
|
84
|
+
cat > "./${appname}.app-meta.xml" <<XML
|
|
85
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
86
|
+
<CustomApplication xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
87
|
+
<label>${appname}</label>
|
|
88
|
+
<navType>Console</navType>
|
|
89
|
+
<uiType>Lightning</uiType>
|
|
90
|
+
</CustomApplication>
|
|
91
|
+
XML
|
|
92
|
+
fi
|
|
93
|
+
echo '{"status":0,"result":{"success":true}}'; exit 0;;
|
|
94
|
+
"project deploy")
|
|
95
|
+
if printf '%s' "$args" | grep -q "report"; then
|
|
96
|
+
echo '{"status":0,"result":{"success":true,"status":"Succeeded","id":"0AfXX0000000001"}}'
|
|
97
|
+
else
|
|
98
|
+
touch "$SELF_DIR/.deployed"
|
|
99
|
+
echo '{"status":0,"result":{"success":true,"status":"Succeeded","id":"0AfXX0000000001"}}'
|
|
100
|
+
fi
|
|
101
|
+
exit 0;;
|
|
102
|
+
*) echo '{"status":0,"result":{}}'; exit 0;;
|
|
103
|
+
esac
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def run(args, path_prefix=None, scrub_sf=False, extra_env=None):
|
|
108
|
+
"""Run the orchestrator; return (returncode, merged stdout+stderr)."""
|
|
109
|
+
env = dict(os.environ)
|
|
110
|
+
if scrub_sf:
|
|
111
|
+
kept = [d for d in env.get("PATH", "").split(os.pathsep)
|
|
112
|
+
if d and not os.path.exists(os.path.join(d, "sf"))]
|
|
113
|
+
env["PATH"] = os.pathsep.join(kept)
|
|
114
|
+
if path_prefix:
|
|
115
|
+
env["PATH"] = path_prefix + os.pathsep + env.get("PATH", "")
|
|
116
|
+
if extra_env:
|
|
117
|
+
env.update({k: str(v) for k, v in extra_env.items()})
|
|
118
|
+
proc = subprocess.run(
|
|
119
|
+
["bash", str(SKILLS_ROOT / SCRIPT), *args],
|
|
120
|
+
capture_output=True, text=True, env=env, cwd=str(SKILLS_ROOT),
|
|
121
|
+
)
|
|
122
|
+
return proc.returncode, (proc.stdout or "") + (proc.stderr or "")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class FakeSf:
|
|
126
|
+
"""Context manager: a temp dir holding an executable fake `sf` first on PATH."""
|
|
127
|
+
|
|
128
|
+
def __init__(self, body=_FAKE_SF_PROD):
|
|
129
|
+
self._body = body
|
|
130
|
+
self._tmp = None
|
|
131
|
+
|
|
132
|
+
def __enter__(self):
|
|
133
|
+
self._tmp = tempfile.mkdtemp(prefix="sidebar-fake-sf-")
|
|
134
|
+
sf = Path(self._tmp) / "sf"
|
|
135
|
+
sf.write_text(self._body)
|
|
136
|
+
sf.chmod(0o755)
|
|
137
|
+
return self._tmp
|
|
138
|
+
|
|
139
|
+
def __exit__(self, *exc):
|
|
140
|
+
import shutil
|
|
141
|
+
if self._tmp:
|
|
142
|
+
shutil.rmtree(self._tmp, ignore_errors=True)
|