@salesforce/afv-skills 1.46.0 → 1.48.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/agentforce-observe/SKILL.md +32 -4
- package/skills/agentforce-observe/references/ahm-alerts.md +719 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/SKILL.md +275 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/README.md +32 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/SetCommentValue.cls +75 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/SetCommentValue.cls-meta.xml +5 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/interview-answers.json +13 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/payloads/copy.json +10 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/payloads/create.json +20 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/payloads/update.json +16 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/references/conventions-and-payload-rules.md +273 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/references/generate-and-wire.md +236 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/references/reference-example-set-comment-value.md +132 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/references/smoke-and-verify.md +211 -0
- package/skills/dx-devops-project-manage/SKILL.md +197 -0
- package/skills/dx-devops-project-manage/examples/common-workflows.md +197 -0
- package/skills/dx-devops-project-manage/references/cli-commands.md +295 -0
- package/skills/dx-devops-project-manage/scripts/create-project.sh +48 -0
- package/skills/dx-devops-project-manage/scripts/list-projects.sh +51 -0
- package/skills/dx-devops-project-manage/scripts/update-project.sh +96 -0
- package/skills/education-cloud-academic-calendar-generate/SKILL.md +225 -0
- package/skills/education-cloud-academic-calendar-generate/examples/quarter-calendar.json +47 -0
- package/skills/education-cloud-academic-calendar-generate/examples/sample-output.md +57 -0
- package/skills/education-cloud-academic-calendar-generate/examples/semester-calendar.json +54 -0
- package/skills/education-cloud-academic-calendar-generate/references/calendar-systems.md +127 -0
- package/skills/education-cloud-academic-calendar-generate/references/date-validation.md +222 -0
- package/skills/education-cloud-academic-calendar-generate/references/foundation_prerequisites.md +40 -0
- package/skills/education-cloud-academic-calendar-generate/scripts/validate_calendar_dates.py +143 -0
- package/skills/education-cloud-course-catalog-migrate/SKILL.md +321 -0
- package/skills/education-cloud-course-catalog-migrate/references/gotchas-detail.md +16 -0
- package/skills/education-cloud-course-catalog-migrate/references/gotchas.md +16 -0
- package/skills/education-cloud-course-catalog-migrate/references/large-catalog-handling.md +42 -0
- package/skills/education-cloud-course-catalog-migrate/scripts/batch_courses.py +36 -0
- package/skills/education-cloud-course-catalog-migrate/scripts/detect_linked_courses.py +51 -0
- package/skills/education-cloud-course-catalog-migrate/scripts/detect_modality_variants.py +48 -0
- package/skills/education-cloud-course-catalog-migrate/scripts/resolve_api_version.py +43 -0
- package/skills/education-cloud-course-catalog-migrate/scripts/split_course_code.py +39 -0
- package/skills/education-cloud-course-catalog-migrate/scripts/validate_completeness.py +54 -0
- 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 +7 -0
- package/skills/education-cloud-multi-campus-configure/references/foundation_prerequisites.md +1 -2
- package/skills/education-cloud-student-recruitment-agent-configure/SKILL.md +177 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/agent-and-subagents.md +151 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/customer-narration.md +34 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/execution-model.md +54 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/flows.md +82 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/grounding.md +199 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/permissions.md +183 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/platform-enablement.md +82 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/prerequisites.md +158 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/routing.md +141 -0
- package/skills/experience-cms-brand-apply/SKILL.md +5 -5
- package/skills/experience-cms-brand-create/SKILL.md +2 -2
- package/skills/experience-cms-content-generate/SKILL.md +1 -0
- package/skills/experience-cms-content-render/SKILL.md +173 -0
- package/skills/experience-cms-content-render/assets/angular/DetailPage.component.ts +25 -0
- package/skills/experience-cms-content-render/assets/angular/MediaRenderer.component.ts +133 -0
- package/skills/experience-cms-content-render/assets/angular/TypeList.component.ts +38 -0
- package/skills/experience-cms-content-render/assets/angular/TypeRenderer.component.ts +90 -0
- package/skills/experience-cms-content-render/assets/angular/cms-content.component.ts +248 -0
- package/skills/experience-cms-content-render/assets/angular/cms-item.service.ts +100 -0
- package/skills/experience-cms-content-render/assets/react/DetailPage.tsx +20 -0
- package/skills/experience-cms-content-render/assets/react/MediaRenderer.tsx +129 -0
- package/skills/experience-cms-content-render/assets/react/TypeList.tsx +40 -0
- package/skills/experience-cms-content-render/assets/react/TypeRenderer.tsx +64 -0
- package/skills/experience-cms-content-render/assets/react/heuristicRenderer.tsx +310 -0
- package/skills/experience-cms-content-render/assets/react/useCmsItem.ts +129 -0
- package/skills/experience-cms-content-render/assets/shared/cmsContentType.ts +49 -0
- package/skills/experience-cms-content-render/assets/shared/cmsCore.types.ts +96 -0
- package/skills/experience-cms-content-render/assets/shared/externalRefs.ts +55 -0
- package/skills/experience-cms-content-render/references/bulk-loading.md +60 -0
- package/skills/experience-cms-content-render/references/codegen-guardrails.md +111 -0
- package/skills/experience-cms-content-render/references/detail-pages.md +87 -0
- package/skills/experience-cms-content-render/references/embed-recipes.md +127 -0
- package/skills/experience-cms-content-render/references/failure-modes.md +96 -0
- package/skills/experience-cms-content-render/references/heuristic-render-rules.md +131 -0
- package/skills/experience-cms-content-render/references/init-scaffold.md +122 -0
- package/skills/experience-cms-content-render/references/interaction-model.md +173 -0
- package/skills/experience-cms-content-render/references/package-api.md +106 -0
- package/skills/experience-cms-content-render/references/schema-sync.md +114 -0
- package/skills/experience-cms-content-render/references/styling-scopes.md +65 -0
- package/skills/experience-cms-content-render/references/verify.md +49 -0
- package/skills/experience-cms-content-type-generate/SKILL.md +2 -2
- package/skills/experience-content-media-stock-image-search/SKILL.md +5 -4
- package/skills/experience-search-coordinate/SKILL.md +198 -0
- package/skills/experience-search-coordinate/assets/search-payload-template.json +25 -0
- package/skills/experience-search-coordinate/references/content-route.md +313 -0
- package/skills/experience-search-coordinate/references/content-type-discovery.md +57 -0
- package/skills/experience-search-coordinate/references/media-route.md +172 -0
- package/skills/experience-search-coordinate/references/scope-resolution.md +14 -0
- package/skills/experience-ui-bundle-localize/SKILL.md +1 -1
- package/skills/experience-ui-bundle-localize/references/i18n-setup.md +5 -3
- package/skills/experience-ui-bundle-project-generate/SKILL.md +18 -14
- package/skills/experience-ui-bundle-project-generate/references/angular-project-generate.md +22 -0
- package/skills/experience-ui-bundle-project-generate/references/react-project-generate.md +20 -0
- package/skills/experience-ui-bundle-salesforce-data-access/SKILL.md +58 -54
- package/skills/experience-ui-bundle-salesforce-data-access/references/caching.md +6 -0
- package/skills/experience-ui-bundle-salesforce-data-access/references/graphiti-cli.md +2 -2
- package/skills/experience-ui-bundle-salesforce-data-access/references/migration.md +6 -0
- package/skills/experience-ui-bundle-salesforce-data-access/references/rest-and-integration.md +2 -1
- package/skills/experience-ui-bundle-salesforce-data-access/references/sdk-api.md +6 -0
- package/skills/experience-ui-bundle-site-generate/SKILL.md +59 -8
- package/skills/experience-ui-bundle-site-generate/references/configure-metadata-digital-experience.md +8 -3
- package/skills/experience-ui-bundle-site-generate/references/configure-metadata-language-settings.md +120 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/SKILL.md +336 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/orchestration-flow.md +143 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-application-flexipage-mapping.md +127 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-deploy-commands.md +116 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-lifesci-metadata-deploy.md +111 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-overview.md +312 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-profile-layout-assignments.md +171 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-state-tracking.md +64 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-trigger-handlers.md +122 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-4-user-provisioning-overview.md +335 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-4-user-provisioning-user-provisioning-details.md +140 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-execution-state-and-recovery.md +196 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-metadata-cache-generation.md +155 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-overview.md +307 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-visit-creation-data.md +211 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/state-machine-and-changes.md +108 -0
- package/skills/life-sciences-kam-coordinate/SKILL.md +241 -0
- package/skills/life-sciences-kam-coordinate/references/orchestration-flow.md +152 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-application-flexipage-mapping.md +79 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-deploy-commands.md +131 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-kam-config-records.md +85 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-lifesci-metadata-deploy.md +112 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-overview.md +202 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-profile-layout-assignments.md +67 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-state-tracking.md +65 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-trigger-handlers.md +123 -0
- package/skills/life-sciences-kam-coordinate/references/stage-4-participant-role-and-sprint.md +89 -0
- package/skills/life-sciences-kam-coordinate/references/stage-5-data-and-plan-templates-overview.md +337 -0
- package/skills/life-sciences-kam-coordinate/references/stage-5-data-creation-data.md +248 -0
- package/skills/life-sciences-kam-coordinate/references/stage-6-ipad-validation-script.md +35 -0
- package/skills/life-sciences-kam-coordinate/references/stage-6-metadata-cache-generation.md +155 -0
- package/skills/life-sciences-kam-coordinate/references/stage-6-user-provisioning-details.md +146 -0
- package/skills/life-sciences-kam-coordinate/references/stage-6-user-provisioning-overview.md +89 -0
- package/skills/life-sciences-kam-coordinate/references/state-machine-and-changes.md +114 -0
- package/skills/life-sciences-prerequisites-validate/SKILL.md +138 -0
- package/skills/life-sciences-prerequisites-validate/references/checks-org-settings.md +190 -0
- package/skills/life-sciences-prerequisites-validate/references/checks-user-and-package.md +211 -0
- package/skills/life-sciences-territory-configure/SKILL.md +217 -0
- package/skills/life-sciences-territory-configure/references/territory-metadata.md +262 -0
- package/skills/platform-dsar-policy-manage/SKILL.md +272 -0
- package/skills/platform-dsar-policy-manage/references/configure.md +106 -0
- package/skills/platform-dsar-policy-manage/references/export-and-history.md +123 -0
- package/skills/platform-dsar-policy-manage/references/gap-analysis-guide.md +150 -0
- package/skills/platform-dsar-policy-manage/references/gap-scan.md +129 -0
- package/skills/platform-dsar-policy-manage/references/headless-sor.md +59 -0
- package/skills/platform-dsar-policy-manage/references/report-format.md +59 -0
- package/skills/platform-dsar-policy-manage/scripts/tests/__init__.py +0 -0
- package/skills/platform-dsar-policy-manage/scripts/tests/test_validate_policy_tree.py +76 -0
- package/skills/platform-dsar-policy-manage/scripts/validate-policy-tree.py +130 -0
- package/skills/platform-salesforce-connect-adapter-generate/SKILL.md +359 -0
- package/skills/platform-salesforce-connect-adapter-generate/references/official-examples.md +69 -0
- package/skills/platform-salesforce-connect-adapter-generate/references/scenarios.md +187 -0
- 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-itsm-agentic-setup-cmdb-coordinate/SKILL.md +20 -27
- package/skills/service-native-voice-recording-transcription-configure/SKILL.md +47 -27
- package/skills/service-native-voice-recording-transcription-configure/references/thunderbird-voice-settings.md +13 -9
- package/skills/service-native-voice-recording-transcription-configure/scripts/enable-recording-transcription.sh +104 -45
- 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,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* heuristicRenderer — schema-less body renderer (Init → src/cms/react/heuristicRenderer.tsx).
|
|
3
|
+
* Picks fields by priority (title/image/excerpt), dispatches each value by shape via
|
|
4
|
+
* RenderField, and holds the ONE RichText injection site (Rule 4). Angular parallel:
|
|
5
|
+
* cms-content.component.ts. Rules: references/heuristic-render-rules.md.
|
|
6
|
+
*/
|
|
7
|
+
import { type ComponentType, type ReactNode } from 'react';
|
|
8
|
+
import {
|
|
9
|
+
resolveCmsImageUrl,
|
|
10
|
+
decodeRichHtmlEntities,
|
|
11
|
+
} from '@salesforce/ui-bundle-template-feature-cms-toolkit';
|
|
12
|
+
import type { CmsImageField } from '../shared/cmsCore.types';
|
|
13
|
+
|
|
14
|
+
const IMAGE_PRIORITY = ['bannerImage', 'featuredImage', 'heroImage', 'coverImage', 'thumbnail', 'image'];
|
|
15
|
+
const EXCERPT_PRIORITY = ['excerpt', 'summary', 'description', 'subtitle'];
|
|
16
|
+
|
|
17
|
+
const ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}/;
|
|
18
|
+
const URL_RE = /^https?:\/\//;
|
|
19
|
+
// Match an HTML tag-open (`<tag`, `</`, `<!`) AND entity-encoded HTML (`<`) — the
|
|
20
|
+
// encoded form has no literal `<`, so a `<`-only check would skip decode entirely, while
|
|
21
|
+
// a bare `<` in prose ("Q4 < projected") must NOT be treated as markup (Rule 4).
|
|
22
|
+
const RICH_TEXT_RE = /<[a-z!/]|<|&#(?:60|x3c);/i;
|
|
23
|
+
|
|
24
|
+
// ISO_DATE_RE only checks the PREFIX, so unparseable strings ("2024-13-99") and SKUs
|
|
25
|
+
// ("1234-56-78") slip through; `Intl.DateTimeFormat.format` throws RangeError on an
|
|
26
|
+
// Invalid Date, which would crash the whole render. Return null on invalid so the
|
|
27
|
+
// caller falls through to the next classifier instead of formatting.
|
|
28
|
+
function formatCmsDate(value: string, options: Intl.DateTimeFormatOptions): string | null {
|
|
29
|
+
const date = new Date(value);
|
|
30
|
+
if (Number.isNaN(date.getTime())) return null;
|
|
31
|
+
return new Intl.DateTimeFormat('en-US', options).format(date);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let sanitizer: (html: string) => string = defaultPassThroughSanitizer;
|
|
35
|
+
|
|
36
|
+
function defaultPassThroughSanitizer(html: string): string {
|
|
37
|
+
// Pass-through: Delivery RichText is trusted and already decoded to real HTML;
|
|
38
|
+
// escaping here would undo the decode (Rule 4). Apps add DOMPurify via
|
|
39
|
+
// setRichTextSanitizer for defense-in-depth.
|
|
40
|
+
return html;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Swap in DOMPurify (or similar) at app entry: setRichTextSanitizer(DOMPurify.sanitize).
|
|
44
|
+
export function setRichTextSanitizer(fn: (html: string) => string): void {
|
|
45
|
+
sanitizer = fn;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function richTextSanitizer(html: string): string {
|
|
49
|
+
return sanitizer(html);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Delivery image field → loadable `src` via the toolkit (resolves the relative
|
|
53
|
+
* Connect-API url; no options passed). Sync. */
|
|
54
|
+
export function resolveImageSrc(field: unknown): string | undefined {
|
|
55
|
+
return resolveCmsImageUrl(field as CmsImageField | undefined);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isImageObject(v: unknown): boolean {
|
|
59
|
+
if (!v || typeof v !== 'object') return false;
|
|
60
|
+
const o = v as { url?: unknown; source?: { type?: unknown } };
|
|
61
|
+
return typeof o.url === 'string' || (o.source != null && typeof o.source.type === 'string');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Delivery image field → authored alt text. Empty string when the field carries none —
|
|
65
|
+
* a decorative image (WCAG 1.1.1), not a missing attribute. */
|
|
66
|
+
function resolveImageAlt(field: unknown): string {
|
|
67
|
+
if (field && typeof field === 'object') {
|
|
68
|
+
const alt = (field as { altText?: unknown }).altText;
|
|
69
|
+
if (typeof alt === 'string') return alt;
|
|
70
|
+
}
|
|
71
|
+
return '';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// RenderField picks a rendering from the value's SHAPE (table:
|
|
75
|
+
// references/heuristic-render-rules.md) and holds the ONE RichText injection path
|
|
76
|
+
// (Rule 4). Do NOT inject RichText anywhere else.
|
|
77
|
+
interface RenderFieldProps {
|
|
78
|
+
name: string;
|
|
79
|
+
value: unknown;
|
|
80
|
+
layout: 'list' | 'detail';
|
|
81
|
+
/** Resolves a `references`-bag contentKey to its media field; undefined for foreign refs. */
|
|
82
|
+
resolveRef?: (contentKey: string) => CmsImageField | undefined;
|
|
83
|
+
TitleSlot?: ComponentType<{ value: string }>;
|
|
84
|
+
ImageSlot?: ComponentType<{ src: string; alt?: string }>;
|
|
85
|
+
RichTextSlot?: ComponentType<{ html: string }>;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function RenderField({
|
|
89
|
+
name,
|
|
90
|
+
value,
|
|
91
|
+
layout,
|
|
92
|
+
resolveRef,
|
|
93
|
+
TitleSlot,
|
|
94
|
+
ImageSlot,
|
|
95
|
+
RichTextSlot,
|
|
96
|
+
}: RenderFieldProps): ReactNode {
|
|
97
|
+
if (value == null) return null;
|
|
98
|
+
|
|
99
|
+
if (typeof value === 'string') {
|
|
100
|
+
if (RICH_TEXT_RE.test(value)) {
|
|
101
|
+
// Decode entity-encoded HTML FIRST, THEN sanitize (Rule 4).
|
|
102
|
+
const html = richTextSanitizer(decodeRichHtmlEntities(value));
|
|
103
|
+
if (RichTextSlot) return <RichTextSlot html={html} />;
|
|
104
|
+
// Trusted, decoded HTML → default sanitizer is pass-through (Rule 4).
|
|
105
|
+
return <div dangerouslySetInnerHTML={{ __html: html }} />;
|
|
106
|
+
}
|
|
107
|
+
if (ISO_DATE_RE.test(value)) {
|
|
108
|
+
const label = formatCmsDate(value, { dateStyle: 'medium' });
|
|
109
|
+
// Valid date → <time>; invalid (matched the prefix but unparseable) → fall through.
|
|
110
|
+
if (label) return <time dateTime={value}>{label}</time>;
|
|
111
|
+
}
|
|
112
|
+
if (URL_RE.test(value)) {
|
|
113
|
+
return <a href={value} target="_blank" rel="noopener noreferrer" aria-label={`External link: ${value}`}>{value}</a>;
|
|
114
|
+
}
|
|
115
|
+
if (/title/i.test(name)) {
|
|
116
|
+
return TitleSlot
|
|
117
|
+
? <TitleSlot value={value} />
|
|
118
|
+
: (layout === 'detail' ? <h1>{value}</h1> : <h2>{value}</h2>);
|
|
119
|
+
}
|
|
120
|
+
return <p>{value}</p>;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (typeof value === 'number') {
|
|
124
|
+
return <span>{new Intl.NumberFormat().format(value)}</span>;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (typeof value === 'boolean') {
|
|
128
|
+
return <span className="badge">{value ? 'Yes' : 'No'}</span>;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (Array.isArray(value)) {
|
|
132
|
+
return (
|
|
133
|
+
<>
|
|
134
|
+
{value.map((v, i) => (
|
|
135
|
+
<RenderField
|
|
136
|
+
key={i}
|
|
137
|
+
name={`${name}[${i}]`}
|
|
138
|
+
value={v}
|
|
139
|
+
layout={layout}
|
|
140
|
+
resolveRef={resolveRef}
|
|
141
|
+
TitleSlot={TitleSlot}
|
|
142
|
+
ImageSlot={ImageSlot}
|
|
143
|
+
RichTextSlot={RichTextSlot}
|
|
144
|
+
/>
|
|
145
|
+
))}
|
|
146
|
+
</>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (typeof value === 'object') {
|
|
151
|
+
const obj = value as Record<string, unknown>;
|
|
152
|
+
|
|
153
|
+
// dateTime shape
|
|
154
|
+
if (typeof obj.dateTime === 'string') {
|
|
155
|
+
const label = formatCmsDate(obj.dateTime, {
|
|
156
|
+
dateStyle: 'medium',
|
|
157
|
+
timeStyle: 'short',
|
|
158
|
+
timeZone: typeof obj.timeZone === 'string' ? obj.timeZone : undefined,
|
|
159
|
+
});
|
|
160
|
+
// Invalid dateTime → skip this field rather than crash the render.
|
|
161
|
+
return label ? <time dateTime={obj.dateTime}>{label}</time> : null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// image-shaped object — the toolkit resolves the src synchronously.
|
|
165
|
+
if (isImageObject(obj)) {
|
|
166
|
+
const src = resolveImageSrc(obj);
|
|
167
|
+
if (!src) return null;
|
|
168
|
+
const alt = resolveImageAlt(obj);
|
|
169
|
+
return ImageSlot
|
|
170
|
+
? <ImageSlot src={src} alt={alt} />
|
|
171
|
+
: <img src={src} alt={alt} loading="lazy" />;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// contentReference shape
|
|
175
|
+
const refObj = (obj as { ref?: { contentKey?: string } }).ref;
|
|
176
|
+
if (refObj && typeof refObj.contentKey === 'string' && resolveRef) {
|
|
177
|
+
const resolved = resolveRef(refObj.contentKey);
|
|
178
|
+
if (!resolved) return null;
|
|
179
|
+
return <span>{(resolved as { title?: string }).title ?? refObj.contentKey}</span>;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export interface HeuristicRendererProps {
|
|
189
|
+
body: Record<string, unknown> | undefined;
|
|
190
|
+
loading: boolean;
|
|
191
|
+
error: Error | undefined;
|
|
192
|
+
layout?: 'list' | 'detail' | string;
|
|
193
|
+
/** Optional whitelist — bypasses priority lists and renders only these fields in order. */
|
|
194
|
+
fields?: ReadonlyArray<string>;
|
|
195
|
+
components?: {
|
|
196
|
+
Title?: ComponentType<{ value: string }>;
|
|
197
|
+
Image?: ComponentType<{ src: string; alt?: string }>;
|
|
198
|
+
RichText?: ComponentType<{ html: string }>;
|
|
199
|
+
};
|
|
200
|
+
className?: string;
|
|
201
|
+
resolveRef?: (contentKey: string) => CmsImageField | undefined;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function heuristicRenderer({
|
|
205
|
+
body,
|
|
206
|
+
loading,
|
|
207
|
+
error,
|
|
208
|
+
layout = 'detail',
|
|
209
|
+
fields,
|
|
210
|
+
components,
|
|
211
|
+
className,
|
|
212
|
+
resolveRef,
|
|
213
|
+
}: HeuristicRendererProps): ReactNode {
|
|
214
|
+
if (loading) return <div role="status">Loading…</div>;
|
|
215
|
+
if (error) return <div role="alert">{error.message}</div>;
|
|
216
|
+
if (!body) return null;
|
|
217
|
+
|
|
218
|
+
const normalizedLayout: 'list' | 'detail' = layout === 'list' ? 'list' : 'detail';
|
|
219
|
+
const TitleSlot = components?.Title;
|
|
220
|
+
const ImageSlot = components?.Image;
|
|
221
|
+
const RichTextSlot = components?.RichText;
|
|
222
|
+
|
|
223
|
+
// Whitelist mode.
|
|
224
|
+
if (fields && fields.length > 0) {
|
|
225
|
+
return (
|
|
226
|
+
<article className={className}>
|
|
227
|
+
{fields.map((name) => {
|
|
228
|
+
const value = body[name];
|
|
229
|
+
if (value == null) return null;
|
|
230
|
+
return (
|
|
231
|
+
<RenderField
|
|
232
|
+
key={name}
|
|
233
|
+
name={name}
|
|
234
|
+
value={value}
|
|
235
|
+
layout={normalizedLayout}
|
|
236
|
+
resolveRef={resolveRef}
|
|
237
|
+
TitleSlot={TitleSlot}
|
|
238
|
+
ImageSlot={ImageSlot}
|
|
239
|
+
RichTextSlot={RichTextSlot}
|
|
240
|
+
/>
|
|
241
|
+
);
|
|
242
|
+
})}
|
|
243
|
+
</article>
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Heuristic mode.
|
|
248
|
+
const title = pickTitle(body);
|
|
249
|
+
const imageField = pickFirstField(body, IMAGE_PRIORITY, isImageObject);
|
|
250
|
+
const excerpt = pickFirstField(body, EXCERPT_PRIORITY, (v) => typeof v === 'string' && v.length > 0);
|
|
251
|
+
|
|
252
|
+
const consumed = new Set<string>();
|
|
253
|
+
if (title?.name) consumed.add(title.name);
|
|
254
|
+
if (imageField?.name) consumed.add(imageField.name);
|
|
255
|
+
if (excerpt?.name) consumed.add(excerpt.name);
|
|
256
|
+
|
|
257
|
+
return (
|
|
258
|
+
<article className={className}>
|
|
259
|
+
{title && (TitleSlot
|
|
260
|
+
? <TitleSlot value={title.value} />
|
|
261
|
+
: (normalizedLayout === 'detail' ? <h1>{title.value}</h1> : <h2>{title.value}</h2>))}
|
|
262
|
+
|
|
263
|
+
{imageField && (() => {
|
|
264
|
+
const src = resolveImageSrc(imageField.value);
|
|
265
|
+
if (!src) return null;
|
|
266
|
+
const alt = resolveImageAlt(imageField.value);
|
|
267
|
+
return ImageSlot
|
|
268
|
+
? <ImageSlot src={src} alt={alt} />
|
|
269
|
+
: <img src={src} alt={alt} loading="lazy" />;
|
|
270
|
+
})()}
|
|
271
|
+
|
|
272
|
+
{excerpt && <p>{excerpt.value as string}</p>}
|
|
273
|
+
|
|
274
|
+
{normalizedLayout === 'detail' && Object.entries(body)
|
|
275
|
+
.filter(([k]) => !consumed.has(k))
|
|
276
|
+
.map(([k, v]) => (
|
|
277
|
+
<RenderField
|
|
278
|
+
key={k}
|
|
279
|
+
name={k}
|
|
280
|
+
value={v}
|
|
281
|
+
layout={normalizedLayout}
|
|
282
|
+
resolveRef={resolveRef}
|
|
283
|
+
TitleSlot={TitleSlot}
|
|
284
|
+
ImageSlot={ImageSlot}
|
|
285
|
+
RichTextSlot={RichTextSlot}
|
|
286
|
+
/>
|
|
287
|
+
))}
|
|
288
|
+
</article>
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function pickTitle(body: Record<string, unknown>): { name: string; value: string } | undefined {
|
|
293
|
+
if (typeof body.title === 'string') return { name: 'title', value: body.title };
|
|
294
|
+
for (const [k, v] of Object.entries(body)) {
|
|
295
|
+
if (typeof v === 'string' && /title/i.test(k)) return { name: k, value: v };
|
|
296
|
+
}
|
|
297
|
+
return undefined;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function pickFirstField<T = unknown>(
|
|
301
|
+
body: Record<string, unknown>,
|
|
302
|
+
priority: string[],
|
|
303
|
+
predicate: (v: unknown) => boolean,
|
|
304
|
+
): { name: string; value: T } | undefined {
|
|
305
|
+
for (const k of priority) {
|
|
306
|
+
const v = body[k];
|
|
307
|
+
if (v != null && predicate(v)) return { name: k, value: v as T };
|
|
308
|
+
}
|
|
309
|
+
return undefined;
|
|
310
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useCmsItem — single-content React hook (Init → src/cms/react/useCmsItem.ts).
|
|
3
|
+
* Owns fetch state + a per-ref cache + inflight dedup (keyed by `ref.name`; never
|
|
4
|
+
* throws — errors → state). Dispatches on ref type: CmsExternalRef (`url`) →
|
|
5
|
+
* getCmsContentByUrl (CDN); CmsRef (`contentKey`) → getCmsContentByKey (Connect).
|
|
6
|
+
*
|
|
7
|
+
* StrictMode-safe (Rule 3): the request is SHARED, so cleanup only sets a local
|
|
8
|
+
* `cancelled` flag gating setState — it never aborts; shared requests get no
|
|
9
|
+
* AbortSignal; `inflight` self-cleans via `.finally(delete)`.
|
|
10
|
+
*/
|
|
11
|
+
import { useEffect, useState, useRef } from 'react';
|
|
12
|
+
import {
|
|
13
|
+
getCmsContentByUrl,
|
|
14
|
+
getCmsContentByKey,
|
|
15
|
+
CmsDeliveryError,
|
|
16
|
+
CmsDeliveryNotFoundError,
|
|
17
|
+
ConnectApiError,
|
|
18
|
+
CmsNotFoundError,
|
|
19
|
+
} from '@salesforce/ui-bundle-template-feature-cms-toolkit';
|
|
20
|
+
import type { AnyCmsRef, CmsRef } from '../shared/cmsCore.types';
|
|
21
|
+
import { CMS_CHANNEL_ID_FALLBACK } from '../shared/externalRefs';
|
|
22
|
+
|
|
23
|
+
interface UseCmsItemState<TBody> {
|
|
24
|
+
body: TBody | undefined;
|
|
25
|
+
loading: boolean;
|
|
26
|
+
error: Error | undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Module-scoped: shared across every consumer of the hook in the app.
|
|
30
|
+
const cache = new Map<string, unknown>();
|
|
31
|
+
const inflight = new Map<string, Promise<unknown>>();
|
|
32
|
+
|
|
33
|
+
/** Shared request for a ref — NO abort signal. `CMS_CHANNEL_ID_FALLBACK` is passed
|
|
34
|
+
* as `options.channelId` only when set (empty → catalog supplies it). */
|
|
35
|
+
function createItemRequest<TBody>(ref: AnyCmsRef): Promise<TBody> {
|
|
36
|
+
// Foreign ref — public delivery URL fetched AS-IS (Rule 1).
|
|
37
|
+
if ('url' in ref && typeof ref.url === 'string') {
|
|
38
|
+
return getCmsContentByUrl<TBody>(ref.url);
|
|
39
|
+
}
|
|
40
|
+
// uiBundle-space ref — toolkit remaps the contentKey (fail-open) and reads Connect.
|
|
41
|
+
const channelId = CMS_CHANNEL_ID_FALLBACK || undefined;
|
|
42
|
+
return getCmsContentByKey<TBody>((ref as CmsRef).contentKey, { channelId });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function useCmsItem<TBody = unknown>(
|
|
46
|
+
ref: AnyCmsRef,
|
|
47
|
+
): UseCmsItemState<TBody> {
|
|
48
|
+
const [state, setState] = useState<UseCmsItemState<TBody>>(() => {
|
|
49
|
+
const cached = cache.get(ref.name) as TBody | undefined;
|
|
50
|
+
return {
|
|
51
|
+
body: cached,
|
|
52
|
+
loading: cached === undefined,
|
|
53
|
+
error: undefined,
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Track the ref name so object-identity churn doesn't re-fetch.
|
|
58
|
+
const nameRef = useRef(ref.name);
|
|
59
|
+
nameRef.current = ref.name;
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
// Local guard only — cleanup suppresses stale setState, never the shared
|
|
63
|
+
// request (Rule 3).
|
|
64
|
+
let cancelled = false;
|
|
65
|
+
const name = ref.name;
|
|
66
|
+
|
|
67
|
+
const cached = cache.get(name) as TBody | undefined;
|
|
68
|
+
if (cached !== undefined) {
|
|
69
|
+
setState({ body: cached, loading: false, error: undefined });
|
|
70
|
+
return () => {
|
|
71
|
+
cancelled = true;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
setState((prev) => ({ ...prev, loading: true, error: undefined }));
|
|
76
|
+
|
|
77
|
+
// In-flight dedup: reuse a pending promise for this ref (shared, no signal).
|
|
78
|
+
let promise = inflight.get(name) as Promise<TBody> | undefined;
|
|
79
|
+
if (!promise) {
|
|
80
|
+
promise = createItemRequest<TBody>(ref);
|
|
81
|
+
inflight.set(name, promise);
|
|
82
|
+
// Self-cleaning: settled or failed, the entry is always removed so an
|
|
83
|
+
// unmounted consumer can't leave a dead promise to poison retries.
|
|
84
|
+
promise
|
|
85
|
+
.then((body) => {
|
|
86
|
+
cache.set(name, body);
|
|
87
|
+
})
|
|
88
|
+
.catch(() => {
|
|
89
|
+
/* surfaced per-consumer below; swallow to avoid unhandled rejection */
|
|
90
|
+
})
|
|
91
|
+
.finally(() => {
|
|
92
|
+
if (inflight.get(name) === promise) inflight.delete(name);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
promise
|
|
97
|
+
.then((body) => {
|
|
98
|
+
if (cancelled) return;
|
|
99
|
+
setState({ body, loading: false, error: undefined });
|
|
100
|
+
})
|
|
101
|
+
.catch((err: unknown) => {
|
|
102
|
+
if (cancelled) return;
|
|
103
|
+
// Pass the toolkit's typed errors through as-is; wrap anything else.
|
|
104
|
+
const error =
|
|
105
|
+
err instanceof CmsDeliveryNotFoundError ||
|
|
106
|
+
err instanceof CmsDeliveryError ||
|
|
107
|
+
err instanceof CmsNotFoundError ||
|
|
108
|
+
err instanceof ConnectApiError
|
|
109
|
+
? err
|
|
110
|
+
: err instanceof Error
|
|
111
|
+
? err
|
|
112
|
+
// Retain the original non-Error rejection as the cause for debugging.
|
|
113
|
+
: new Error('Unknown CMS fetch error', { cause: err });
|
|
114
|
+
setState({ body: undefined, loading: false, error });
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
return () => {
|
|
118
|
+
cancelled = true;
|
|
119
|
+
};
|
|
120
|
+
}, [ref.name]); // Ref-name is the identity — object churn is ignored.
|
|
121
|
+
|
|
122
|
+
return state;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Test-only — reset the module cache between runs. */
|
|
126
|
+
export function __resetCmsCacheForTests(): void {
|
|
127
|
+
cache.clear();
|
|
128
|
+
inflight.clear();
|
|
129
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* {{Type}} — per-type typed module (codegen-managed). ABOVE marker: user-editable
|
|
3
|
+
* (layouts, slots, className presets), preserved verbatim. BELOW marker: generated
|
|
4
|
+
* from the schema, rewritten on every Schema Sync. Missing/malformed markers → the
|
|
5
|
+
* skill HALTs (never auto-heals). Algorithm: references/schema-sync.md.
|
|
6
|
+
*
|
|
7
|
+
* Tokens: {{Type}} PascalCase · {{type}} camelCase · {{TYPE}} UPPER_SNAKE.
|
|
8
|
+
*/
|
|
9
|
+
import type { CmsFieldType, CmsImageField } from '../../shared/cmsCore.types';
|
|
10
|
+
|
|
11
|
+
// <experience-cms-content-render:above-begin>
|
|
12
|
+
// User-editable — layouts, slot overrides, className presets.
|
|
13
|
+
// Preserved across regeneration.
|
|
14
|
+
|
|
15
|
+
export const {{type}}Layouts = {
|
|
16
|
+
card: { fields: ['title', 'bannerImage', 'excerpt'] as const, className: 'cms-{{type}}-card' },
|
|
17
|
+
detail: { fields: undefined, className: 'cms-{{type}}-detail' },
|
|
18
|
+
} as const;
|
|
19
|
+
|
|
20
|
+
export type {{Type}}Layout = keyof typeof {{type}}Layouts;
|
|
21
|
+
|
|
22
|
+
export const {{type}}Slots: {
|
|
23
|
+
Title?: unknown;
|
|
24
|
+
Image?: unknown;
|
|
25
|
+
RichText?: unknown;
|
|
26
|
+
} = {};
|
|
27
|
+
|
|
28
|
+
// <experience-cms-content-render:above-end>
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
// <experience-cms-content-render:below-begin>
|
|
32
|
+
// GENERATED — do not edit. Regenerated on schema sync.
|
|
33
|
+
//
|
|
34
|
+
// One field per schema entry. Scalars are PLAIN PRIMITIVES (Rule 5) — Text/RichText/
|
|
35
|
+
// URL/Date/DateTime → string, Number → number, Boolean → boolean; only Image →
|
|
36
|
+
// CmsImageField. NEVER `{ value: T }`.
|
|
37
|
+
|
|
38
|
+
export interface {{Type}}Body {
|
|
39
|
+
// One typed property per schema field; the index keeps off-schema fields open.
|
|
40
|
+
[field: string]: string | number | boolean | CmsImageField | undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const {{type}}FieldTypes: Readonly<Record<string, CmsFieldType>> = {
|
|
44
|
+
// One entry per field, mapping lightningType → lowerCamel CmsFieldType literal
|
|
45
|
+
// (Text→'text', RichText→'richText', … — translate the casing, never copy the
|
|
46
|
+
// PascalCase token). E.g.: body: 'richText', excerpt: 'text', bannerImage: 'image'.
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// <experience-cms-content-render:below-end>
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CMS identity + field-type primitives (FRAMEWORK-AGNOSTIC). Scaffolded once at
|
|
3
|
+
* `src/cms/shared/cmsCore.types.ts`; safe to import, not regenerated. Owns ONLY the
|
|
4
|
+
* app-level ref identity and the codegen field-type enum — delivery/runtime shapes
|
|
5
|
+
* (CmsImageField, CmsContentBody, errors, …) live in the toolkit; do NOT redeclare.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Re-exported so generated `<type>.ts` import the image shape from one local path
|
|
9
|
+
// (relative to `<framework>/types/`) rather than the package specifier.
|
|
10
|
+
export type { CmsImageField } from "@salesforce/ui-bundle-template-feature-cms-toolkit";
|
|
11
|
+
|
|
12
|
+
/** Foreign-ref identity — content addressed by a public unauthenticatedUrl (CDN). */
|
|
13
|
+
export interface CmsExternalRef<K extends string = string> {
|
|
14
|
+
/** Unique catalog name. Convention: EXT_<TITLE_SNAKE>. */
|
|
15
|
+
readonly name: string;
|
|
16
|
+
/** Fully-formed unauthenticatedUrl; immutable across deploys. */
|
|
17
|
+
readonly url: string;
|
|
18
|
+
/** Content-type discriminator (the fqn DeveloperName). REQUIRED and populated on
|
|
19
|
+
* every entry — it binds the ref to its renderer, so a wrong-typed ref is a
|
|
20
|
+
* compile error. Optional/absent would erase K and defeat that guard. */
|
|
21
|
+
readonly cmsType: K;
|
|
22
|
+
/** MEDIA-ONLY, direct-URL path. For standalone media (`image`/`audio`/`video`/
|
|
23
|
+
* `document`) a foreign `url` is the ASSET itself, so MediaRenderer sets it as the
|
|
24
|
+
* element `src`/`href` WITHOUT fetching — there is no body to read `altText`/`title`
|
|
25
|
+
* from. The skill threads these off the search hand-off (or asks the user) at Ref
|
|
26
|
+
* Registration so the accessible name survives. Ignored on non-media refs (news
|
|
27
|
+
* reads its title from the fetched envelope). Both optional; `altText` absent → `''`
|
|
28
|
+
* (decorative image, WCAG 1.1.1); `title` names the document download link. */
|
|
29
|
+
readonly altText?: string;
|
|
30
|
+
readonly title?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* uiBundle-space identity — a `contentKey` read via the authenticated Connect API.
|
|
35
|
+
* The baked-in value is the `sourceContentKey`; the toolkit remaps it to the org's
|
|
36
|
+
* `targetContentKey` via content-metadata.json (fail-open to the baked-in key).
|
|
37
|
+
*/
|
|
38
|
+
export interface CmsRef<K extends string = string> {
|
|
39
|
+
/** Unique catalog name. Convention: EXT_<TITLE_SNAKE>. */
|
|
40
|
+
readonly name: string;
|
|
41
|
+
/** sourceContentKey baked into app code; the catalog's remap fallback. */
|
|
42
|
+
readonly contentKey: string;
|
|
43
|
+
/** Content-type discriminator (the fqn DeveloperName). REQUIRED and populated on
|
|
44
|
+
* every entry — it binds the ref to its renderer, so a wrong-typed ref is a
|
|
45
|
+
* compile error. Optional/absent would erase K and defeat that guard. */
|
|
46
|
+
readonly cmsType: K;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Union of every fetchable ref — branch on `url` (foreign) vs `contentKey`. */
|
|
50
|
+
export type AnyCmsRef<K extends string = string> = CmsExternalRef<K> | CmsRef<K>;
|
|
51
|
+
|
|
52
|
+
/** Codified Lightning field types — feeds the below-marker fieldTypes map per type. */
|
|
53
|
+
export type CmsFieldType =
|
|
54
|
+
| "text"
|
|
55
|
+
| "richText"
|
|
56
|
+
| "image"
|
|
57
|
+
| "date"
|
|
58
|
+
| "dateTime"
|
|
59
|
+
| "number"
|
|
60
|
+
| "boolean"
|
|
61
|
+
| "url"
|
|
62
|
+
| "reference"
|
|
63
|
+
| "unknown";
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Standalone-media discriminator — the fqn DeveloperName of a PREDEFINED CMS media
|
|
67
|
+
* item (`sfdc_cms__{image,audio,video,document}`), stored on the ref as `cmsType`.
|
|
68
|
+
* Media is a predefined type parallel to `news`: the ref binds to `MediaRenderer` at
|
|
69
|
+
* registration time, so dispatch is by `cmsType`, NEVER by body shape (SKILL.md §B1).
|
|
70
|
+
*/
|
|
71
|
+
export type CmsMediaType = "image" | "audio" | "video" | "document";
|
|
72
|
+
|
|
73
|
+
/** The `sfdc_cms:media` field every media body carries (key is LITERALLY
|
|
74
|
+
* "sfdc_cms:media", colon, across all four types). `url` is a loadable src; `source`
|
|
75
|
+
* describes the underlying file (`mimeType` picks finer intra-medium choices). */
|
|
76
|
+
export interface CmsMediaField {
|
|
77
|
+
readonly url?: string;
|
|
78
|
+
readonly source?: {
|
|
79
|
+
readonly mimeType?: string;
|
|
80
|
+
readonly ref?: string;
|
|
81
|
+
readonly type?: string;
|
|
82
|
+
readonly fileSize?: number;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Predefined body for standalone CMS media — UNIFORM across all four media types;
|
|
88
|
+
* they differ only by the envelope fqn (→ `cmsType`) and `source.mimeType`. `title`
|
|
89
|
+
* is envelope-level, lifted into the body by the toolkit (as news); `altText` is
|
|
90
|
+
* optional and absent on audio. The `<TBody>` for `getCmsContentByKey<CmsMediaBody>`.
|
|
91
|
+
*/
|
|
92
|
+
export interface CmsMediaBody {
|
|
93
|
+
readonly altText?: string;
|
|
94
|
+
readonly title?: string;
|
|
95
|
+
readonly "sfdc_cms:media": CmsMediaField;
|
|
96
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ref catalog for CMS content (FRAMEWORK-AGNOSTIC). Holds both ref types
|
|
3
|
+
* (CmsExternalRef by `url`, CmsRef by `contentKey`). Scaffolded once at
|
|
4
|
+
* src/cms/shared/externalRefs.ts; each embed appends one entry inside the marker block.
|
|
5
|
+
* Names are EXT_<TITLE_SNAKE>, unique — object-literal keys collide at compile time.
|
|
6
|
+
*
|
|
7
|
+
* ⚠ Do NOT edit the marker block by hand. Add entries via the skill.
|
|
8
|
+
*/
|
|
9
|
+
// Only `AnyCmsRef` is used in the empty template; Ref Registration adds
|
|
10
|
+
// CmsExternalRef/CmsRef when it appends an entry (importing them eagerly trips
|
|
11
|
+
// noUnusedLocals TS6196 on a fresh scaffold).
|
|
12
|
+
import type { AnyCmsRef } from './cmsCore.types';
|
|
13
|
+
|
|
14
|
+
// channelId OVERRIDE for CmsRef reads — CAUTION, opt-in only. Prefer the catalog
|
|
15
|
+
// (public/content-metadata.json), which is primary and travels per-org. This constant
|
|
16
|
+
// is baked into SOURCE, so it does NOT travel per-org and will point at the WRONG
|
|
17
|
+
// channel after a cross-org deploy — set it only at your discretion for a local
|
|
18
|
+
// override. The skill does NOT auto-fill it (leave ''); the catalog supplies the
|
|
19
|
+
// channel. Precedence: explicit option > this override > catalog channelId > toolkit
|
|
20
|
+
// throws. Passed as options.channelId only when non-empty.
|
|
21
|
+
// <experience-cms-content-render:channel-fallback-begin>
|
|
22
|
+
export const CMS_CHANNEL_ID_FALLBACK = '';
|
|
23
|
+
// <experience-cms-content-render:channel-fallback-end>
|
|
24
|
+
|
|
25
|
+
// <experience-cms-content-render:external-refs-begin>
|
|
26
|
+
// Skill-managed. Each embed appends one entry here of the form:
|
|
27
|
+
//
|
|
28
|
+
// Foreign ref (public unauthenticatedUrl):
|
|
29
|
+
// EXT_FOOD_BERRIES: {
|
|
30
|
+
// name: 'EXT_FOOD_BERRIES',
|
|
31
|
+
// url: 'https://orgfarm-XXX.cdn.example.salesforce-experience.com/cms/delivery/v64.0/0apSG.../contents/MCABC...?oid=00D...&language=en_US',
|
|
32
|
+
// cmsType: 'news',
|
|
33
|
+
// } satisfies CmsExternalRef<'news'>,
|
|
34
|
+
//
|
|
35
|
+
// uiBundle-space ref (contentKey — remapped via content-metadata.json):
|
|
36
|
+
// EXT_LAUNCH_NEWS: {
|
|
37
|
+
// name: 'EXT_LAUNCH_NEWS',
|
|
38
|
+
// contentKey: 'M3ASD4KHASDB73',
|
|
39
|
+
// cmsType: 'news',
|
|
40
|
+
// } satisfies CmsRef<'news'>,
|
|
41
|
+
//
|
|
42
|
+
// `cmsType` MUST match the `<K>` in `satisfies` — it binds the ref to a per-type
|
|
43
|
+
// renderer at compile time, so a wrong-typed ref is a compile error.
|
|
44
|
+
export const externalRefs = {} as const satisfies Record<string, AnyCmsRef>;
|
|
45
|
+
// <experience-cms-content-render:external-refs-end>
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Typed accessor — prefer over `externalRefs['EXT_X']`: the `cmsType` discriminator
|
|
49
|
+
* flows through, so a wrong-typed ref to a renderer fails at compile time.
|
|
50
|
+
*/
|
|
51
|
+
export function ref<Name extends keyof typeof externalRefs>(
|
|
52
|
+
name: Name,
|
|
53
|
+
): (typeof externalRefs)[Name] {
|
|
54
|
+
return externalRefs[name];
|
|
55
|
+
}
|