#!/bin/bash # Enable all prerequisite settings for Pipeline Management # Usage: ./enable-prerequisites.sh set -euo pipefail # sf CLI can emit ANSI color codes inside --json stdout, breaking jq parsing. # 2>/dev/null only strips stderr; these env vars are the reliable fix (see PM notes). export NO_COLOR=1 export FORCE_COLOR=0 # Source shared SOAP parsing library SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/shared/soap.sh" ORG_ALIAS="${1:-}" if [[ -z "$ORG_ALIAS" ]]; then echo "❌ Error: Missing org alias" echo "Usage: $0 " exit 1 fi if [[ ! -f "sfdx-project.json" ]]; then echo "❌ Error: sfdx-project.json not found in current directory" echo "Copy from ../assets/sfdx-project.json first" exit 1 fi echo "🚀 Enabling Pipeline Management prerequisites for org: $ORG_ALIAS" echo "" # Get org details ORG_INFO=$(sf org display --target-org "$ORG_ALIAS" --json 2>/dev/null) ORG_ID=$(echo "$ORG_INFO" | jq -r '.result.id') INSTANCE_URL=$(echo "$ORG_INFO" | jq -r '.result.instanceUrl') ACCESS_TOKEN=$(echo "$ORG_INFO" | jq -r '.result.accessToken') # Newer CLI versions redact accessToken — use show-access-token fallback if [[ -z "$ACCESS_TOKEN" || "$ACCESS_TOKEN" == "null" || "$ACCESS_TOKEN" == *"REDACTED"* ]]; then ACCESS_TOKEN=$(echo "y" | sf org auth show-access-token --target-org "$ORG_ALIAS" --no-prompt --json 2>/dev/null | jq -r '.result.accessToken // empty') fi if [[ -z "$ORG_ID" || -z "$INSTANCE_URL" || -z "$ACCESS_TOKEN" || "$ACCESS_TOKEN" == "null" ]]; then echo "Error: Could not retrieve org details. Is the org authenticated?" exit 1 fi echo "Org ID: $ORG_ID" echo "Instance URL: $INSTANCE_URL" echo "" # Function to enable setting via SOAP API enable_via_soap() { local SETTING_NAME="$1" local DISPLAY_NAME="$2" local FIELD_NAME="$3" local API_VERSION="${4:-62.0}" echo -n "Enabling ${DISPLAY_NAME}... " # Check if already enabled local QUERY="SELECT ${FIELD_NAME} FROM ${SETTING_NAME}" local CURRENT_VALUE CURRENT_VALUE=$(sf data query --query "$QUERY" --target-org "$ORG_ALIAS" --json 2>/dev/null | jq -r ".result.records[0].${FIELD_NAME}") if [[ "$CURRENT_VALUE" == "true" ]]; then echo "✅ (already enabled)" return 0 fi # Build SOAP request local SOAP_REQUEST SOAP_REQUEST=$(cat < ${ACCESS_TOKEN} ${SETTING_NAME} true EOF ) # Send SOAP request local RESPONSE RESPONSE=$(curl -s -X POST "${INSTANCE_URL}/services/Soap/m/${API_VERSION}" \ -H "Content-Type: text/xml; charset=utf-8" \ -H "SOAPAction: update" \ -d "$SOAP_REQUEST") if echo "$RESPONSE" | grep -q "true"; then echo "✅" return 0 else echo "❌" echo "SOAP Response: $RESPONSE" return 1 fi } # Function to enable setting via CLI retrieve+modify+deploy enable_via_cli() { local SETTING_NAME="$1" local DISPLAY_NAME="$2" local FIELD_NAME="$3" local FIELD_VALUE="${4:-true}" echo -n "Enabling ${DISPLAY_NAME}... " # Check if already enabled local QUERY="SELECT ${FIELD_NAME} FROM ${SETTING_NAME}" local CURRENT_VALUE CURRENT_VALUE=$(sf data query --query "$QUERY" --target-org "$ORG_ALIAS" --json 2>/dev/null | jq -r ".result.records[0].${FIELD_NAME}" 2>/dev/null || echo "null") if [[ "$CURRENT_VALUE" == "$FIELD_VALUE" || "$CURRENT_VALUE" == "true" ]]; then echo "✅ (already enabled)" return 0 fi # Retrieve current settings local SETTINGS_DIR="force-app/main/default/settings" mkdir -p "$SETTINGS_DIR" if ! sf project retrieve start --metadata "Settings:${SETTING_NAME}" --target-org "$ORG_ALIAS" --json 2>/dev/null | jq -e '.status == 0' >/dev/null 2>&1; then echo "❌ (retrieve failed)" return 1 fi local SETTINGS_FILE="${SETTINGS_DIR}/${SETTING_NAME}.settings-meta.xml" if [[ ! -f "$SETTINGS_FILE" ]]; then echo "❌ (file not found after retrieve)" return 1 fi # Modify setting using sed (non-greedy) if grep -q "<${FIELD_NAME}>" "$SETTINGS_FILE"; then # Update existing field sed -i.bak "0,/<${FIELD_NAME}>[^<]*<\/${FIELD_NAME}>/s|<${FIELD_NAME}>[^<]*|<${FIELD_NAME}>${FIELD_VALUE}|" "$SETTINGS_FILE" else # Insert field before closing tag sed -i.bak "/<\/.*Settings>/i\\ <${FIELD_NAME}>${FIELD_VALUE}" "$SETTINGS_FILE" fi # Deploy if sf project deploy start --source-dir "$SETTINGS_DIR" --target-org "$ORG_ALIAS" --json 2>/dev/null | jq -e '.status == 0' >/dev/null 2>&1; then echo "✅" rm -f "${SETTINGS_FILE}.bak" return 0 else echo "❌ (deploy failed)" mv "${SETTINGS_FILE}.bak" "$SETTINGS_FILE" 2>/dev/null || true return 1 fi } # Read a settings boolean via SOAP readMetadata and classify the response using # the shared shared/soap.sh parser. Distinguishes a genuine "false" from an # auth/SOAP fault or a network/service error instead of collapsing all three to # empty (the W-23215519 bug). Captures the HTTP status so 5xx/HTML error pages # are not mistaken for a disabled setting. # Args: $1 = metadata type, $2 = fullName, $3 = field name # Echoes: "true" | "false" | "AUTH_ERROR:..." | "TYPE_UNAVAILABLE:..." | "NETWORK_ERROR" read_setting_status() { local type="$1" local full_name="$2" local field="$3" local response http_status xml response=$(curl -s -w "\n%{http_code}" "${INSTANCE_URL}/services/Soap/m/64.0" \ -H "Content-Type: text/xml; charset=UTF-8" \ -H "SOAPAction: readMetadata" \ -d " ${ACCESS_TOKEN} ${type}${full_name} " 2>/dev/null || echo "") http_status=$(echo "$response" | tail -1) xml=$(echo "$response" | sed '$d') parse_soap_response "$xml" "$field" "$http_status" } # Read OpportunitySettings.enableOpportunityTeam via SOAP readMetadata. # OpportunitySettings is NOT queryable via SOQL, so SOAP is the only option # (the fullName for OpportunitySettings is "Opportunity", not the type name). # Echoes a classification string (see read_setting_status). get_opp_team_status() { read_setting_status "OpportunitySettings" "Opportunity" "enableOpportunityTeam" } # Step 1: Enable EinsteinGptSettings (MUST succeed for Copilot) # NOTE: The critical field for Pipeline Management is enableEinsteinGptPlatform. # We enable it via SOAP with the correct fullName pattern (fullName = type name for settings). echo "Step 1: Enabling Einstein Generative AI" echo -n " Checking EinsteinGptSettings... " GENAI_CURRENT=$(read_setting_status "EinsteinGptSettings" "EinsteinGpt" "enableEinsteinGptPlatform") if [[ "$GENAI_CURRENT" == "true" ]]; then echo "✅ (already enabled)" else echo "enabling..." RESULT=$(curl -s -X POST "${INSTANCE_URL}/services/Soap/m/64.0" \ -H "Content-Type: text/xml; charset=utf-8" \ -H "SOAPAction: update" \ -d " ${ACCESS_TOKEN} EinsteinGpt true ") if echo "$RESULT" | grep -q "true"; then echo " ✅ Einstein Generative AI enabled (SOAP)" else echo " ⚠️ SOAP failed — trying CLI deploy fallback..." enable_via_cli "EinsteinGptSettings" "Einstein Generative AI (CLI)" "enableEinsteinGptPlatform" GENAI_VERIFY=$(read_setting_status "EinsteinGptSettings" "EinsteinGpt" "enableEinsteinGptPlatform") if [[ "$GENAI_VERIFY" != "true" ]]; then echo " ❌ Critical failure: Cannot enable Einstein Generative AI (all approaches failed)" echo "$RESULT" | grep -o '[^<]*' | sed 's/<[^>]*>//g' | head -1 || true exit 1 fi echo " ✅ Einstein Generative AI enabled (CLI fallback)" fi sleep 3 fi echo "" # Step 2: Enable EinsteinCopilotSettings (depends on GPT) echo "Step 2: Enabling Agentforce Agent (Einstein Copilot)" echo -n " Checking EinsteinCopilotSettings... " COPILOT_CURRENT=$(read_setting_status "EinsteinCopilotSettings" "EinsteinCopilot" "enableEinsteinGptCopilot") if [[ "$COPILOT_CURRENT" == "true" ]]; then echo "✅ (already enabled)" else echo "enabling..." RESULT=$(curl -s -X POST "${INSTANCE_URL}/services/Soap/m/64.0" \ -H "Content-Type: text/xml; charset=utf-8" \ -H "SOAPAction: update" \ -d " ${ACCESS_TOKEN} EinsteinCopilot true ") if echo "$RESULT" | grep -q "true"; then echo " ✅ Agentforce Agent enabled (SOAP)" else echo " ⚠️ SOAP failed — trying CLI deploy fallback..." enable_via_cli "EinsteinCopilotSettings" "Agentforce Agent (CLI)" "enableEinsteinGptCopilot" COPILOT_VERIFY=$(read_setting_status "EinsteinCopilotSettings" "EinsteinCopilot" "enableEinsteinGptCopilot") if [[ "$COPILOT_VERIFY" != "true" ]]; then echo " ⚠️ Warning: Agentforce Agent enable failed (all approaches — continuing)" echo "$RESULT" | grep -o '[^<]*' | sed 's/<[^>]*>//g' | head -1 || true else echo " ✅ Agentforce Agent enabled (CLI fallback)" fi fi sleep 3 fi echo "" # Step 2b: Enable AgentPlatformSettings (Agentforce Studio — gates Deal Agent) # AgentPlatformSettings.enableAgentPlatform (OrgPreference AgentPlatformEnabled) # must be ON before SalesDealAgentSettings can be enabled; core rejects Deal Agent # enablement otherwise. Depends on Einstein GPT + Copilot (Steps 1–2). echo "Step 2b: Enabling Agentforce Studio (Agent Platform)" echo -n " Checking AgentPlatformSettings... " AGENT_PLATFORM_CURRENT=$(read_setting_status "AgentPlatformSettings" "AgentPlatform" "enableAgentPlatform") if [[ "$AGENT_PLATFORM_CURRENT" == "true" ]]; then echo "✅ (already enabled)" else echo "enabling..." RESULT=$(curl -s -X POST "${INSTANCE_URL}/services/Soap/m/64.0" \ -H "Content-Type: text/xml; charset=utf-8" \ -H "SOAPAction: update" \ -d " ${ACCESS_TOKEN} AgentPlatform true ") if echo "$RESULT" | grep -q "true"; then echo " ✅ Agentforce Studio (Agent Platform) enabled (SOAP)" else echo " ⚠️ SOAP failed — trying CLI deploy fallback..." enable_via_cli "AgentPlatformSettings" "Agentforce Studio (CLI)" "enableAgentPlatform" AGENT_PLATFORM_VERIFY=$(read_setting_status "AgentPlatformSettings" "AgentPlatform" "enableAgentPlatform") if [[ "$AGENT_PLATFORM_VERIFY" != "true" ]]; then echo " ⚠️ Warning: Agentforce Studio enable failed (all approaches — continuing)" echo "$RESULT" | grep -o '[^<]*' | sed 's/<[^>]*>//g' | head -1 || true else echo " ✅ Agentforce Studio (Agent Platform) enabled (CLI fallback)" fi fi sleep 3 fi echo "" # Step 3: Enable Enhanced Notes (required for Pipeline Management data sources) echo "Step 3: Enabling Enhanced Notes" echo -n " Checking EnhancedNotesSettings... " NOTES_CURRENT=$(read_setting_status "EnhancedNotesSettings" "EnhancedNotes" "enableEnhancedNotes") if [[ "$NOTES_CURRENT" == "true" ]]; then echo "✅ (already enabled)" else echo "enabling..." RESULT=$(curl -s -X POST "${INSTANCE_URL}/services/Soap/m/64.0" \ -H "Content-Type: text/xml; charset=utf-8" \ -H "SOAPAction: update" \ -d " ${ACCESS_TOKEN} EnhancedNotes true ") if echo "$RESULT" | grep -q "true"; then echo " ✅ Enhanced Notes enabled" else echo " ⚠️ SOAP failed — trying CLI deploy fallback..." enable_via_cli "EnhancedNotesSettings" "Enhanced Notes" "enableEnhancedNotes" || echo " ❌ Enhanced Notes failed (Pipeline Management may not activate)" fi sleep 2 fi echo "" # Step 4: Enable Email Administration (CLI is OK, handle compliance field removal) echo "Step 4: Enabling Email Administration" if ! enable_via_cli "EmailAdministrationSettings" "Email Administration" "enableInternalNotesInEmailSnippet"; then echo "⚠️ Retrying without enableEmailSenderIdCompliance..." # Retrieve again sf project retrieve start --metadata "Settings:EmailAdministrationSettings" --target-org "$ORG_ALIAS" >/dev/null 2>&1 || true SETTINGS_FILE="force-app/main/default/settings/EmailAdministrationSettings.settings-meta.xml" if [[ -f "$SETTINGS_FILE" ]]; then # Remove problematic field if present sed -i.bak '//d' "$SETTINGS_FILE" # Ensure enableInternalNotesInEmailSnippet is true if grep -q "" "$SETTINGS_FILE"; then sed -i.bak2 "0,/[^<]*<\/enableInternalNotesInEmailSnippet>/s|[^<]*|true|" "$SETTINGS_FILE" fi sf project deploy start --source-dir "force-app/main/default/settings" --target-org "$ORG_ALIAS" >/dev/null 2>&1 || true rm -f "${SETTINGS_FILE}.bak" "${SETTINGS_FILE}.bak2" 2>/dev/null || true fi fi echo "" # Step 5: Enable Opportunity Team (CRITICAL — flow uses OpportunityTeamMember) # Without this, the Pipeline Management flow deploy fails with an OppTeamMemRec error. # OpportunitySettings is NOT queryable via SOQL, so we use SOAP readMetadata/update # (matching Steps 1-3 and setup-all.sh). The fullName for OpportunitySettings is # "Opportunity", not the type name. echo "Step 5: Enabling Opportunity Team" echo -n " Checking OpportunitySettings.enableOpportunityTeam... " OPP_TEAM_CURRENT=$(get_opp_team_status) if [[ "$OPP_TEAM_CURRENT" == "true" ]]; then echo "✅ (already enabled)" else echo "enabling..." RESULT=$(curl -s -X POST "${INSTANCE_URL}/services/Soap/m/64.0" \ -H "Content-Type: text/xml; charset=utf-8" \ -H "SOAPAction: update" \ -d " ${ACCESS_TOKEN} Opportunity true " 2>/dev/null) if echo "$RESULT" | grep -q "true"; then echo " ✅ Opportunity Team enabled" else echo " ❌ Opportunity Team enable failed — verifying below..." echo "$RESULT" | grep -o '[^<]*' | sed 's/<[^>]*>//g' | head -1 || true echo " Note: cannot coexist with Opportunity Splits — disable Splits first if enabled." fi sleep 2 fi echo "" # Step 6: Enable Opportunity settings (Find Similar Opportunities — optional) echo "Step 6: Enabling Opportunity Settings" enable_via_cli "OpportunitySettings" "Opportunity Settings" "enableFindSimilarOpportunities" || echo "⚠️ Warning: Opportunity Settings failed but continuing..." echo "" # Verification — use SOAP readMetadata (matches enable-deal-agent.sh pre-flight checks) echo "🔍 Verifying prerequisites via SOAP readMetadata..." echo "" VERIFICATION_FAILED=false # Report a verification status classified by shared/soap.sh. Distinguishes a # genuine disabled setting from an auth fault or a network/service error so the # operator knows whether to re-auth, check connectivity, or actually enable the # setting (the W-23215519 fix). Args: $1=label, $2=status string, $3=severity # severity "critical" → sets VERIFICATION_FAILED on a real "false" # severity "warn" → reports but never blocks report_status() { local label="$1" local status="$2" local severity="$3" case "$status" in true) echo " ✅ ${label}: enabled" ;; false) if [[ "$severity" == "critical" ]]; then echo " ❌ ${label}: NOT enabled (CRITICAL)" VERIFICATION_FAILED=true else echo " ⚠️ ${label}: NOT enabled" fi ;; AUTH_ERROR:*) echo " ❌ ${label}: cannot verify — authentication/SOAP fault (${status#AUTH_ERROR:})" echo " Re-authenticate: sf org login web --instance-url $INSTANCE_URL --alias $ORG_ALIAS" # if/then/fi (not `[[ ]] && ...`): a bare `&&` would return 1 when severity # is "warn", and as the case arm's last statement that makes report_status # return 1 — aborting the script at the bare warn call sites under set -e. if [[ "$severity" == "critical" ]]; then VERIFICATION_FAILED=true; fi ;; TYPE_UNAVAILABLE:*) echo " ❌ ${label}: not available in this org edition (${status#TYPE_UNAVAILABLE:})" if [[ "$severity" == "critical" ]]; then VERIFICATION_FAILED=true; fi ;; NETWORK_ERROR) echo " ❌ ${label}: cannot verify — network/service error (check instance URL and connectivity)" if [[ "$severity" == "critical" ]]; then VERIFICATION_FAILED=true; fi ;; *) echo " ⚠️ ${label}: unknown status ($status)" ;; esac } # Verify Einstein GPT (critical — checked by enable-deal-agent.sh pre-flight) VERIFY_GENAI=$(read_setting_status "EinsteinGptSettings" "EinsteinGpt" "enableEinsteinGptPlatform") report_status "Einstein Generative AI (enableEinsteinGptPlatform)" "$VERIFY_GENAI" "critical" # Verify Einstein Copilot (checked by enable-deal-agent.sh pre-flight) VERIFY_COPILOT=$(read_setting_status "EinsteinCopilotSettings" "EinsteinCopilot" "enableEinsteinGptCopilot") report_status "Agentforce Agent (enableEinsteinGptCopilot)" "$VERIFY_COPILOT" "warn" # Verify Agentforce Studio / Agent Platform (gates Deal Agent; enable-deal-agent.sh # pre-flight hard-blocks on this — a disabled state here explains a downstream # Deal Agent activation failure). VERIFY_AGENT_PLATFORM=$(read_setting_status "AgentPlatformSettings" "AgentPlatform" "enableAgentPlatform") report_status "Agentforce Studio / Agent Platform (enableAgentPlatform)" "$VERIFY_AGENT_PLATFORM" "warn" # Verify Enhanced Notes (checked by enable-deal-agent.sh pre-flight) VERIFY_NOTES=$(read_setting_status "EnhancedNotesSettings" "EnhancedNotes" "enableEnhancedNotes") report_status "Enhanced Notes (enableEnhancedNotes)" "$VERIFY_NOTES" "warn" # Verify Opportunity Team (CRITICAL — PM flow deploy fails with OppTeamMemRec error # without it). OpportunitySettings is not queryable via SOQL — use SOAP readMetadata. VERIFY_OPP_TEAM=$(get_opp_team_status) if [[ "$VERIFY_OPP_TEAM" != "true" ]]; then report_status "Opportunity Team (enableOpportunityTeam)" "$VERIFY_OPP_TEAM" "critical" echo " The Pipeline Management flow requires OpportunityTeamMember access;" echo " without it, flow deployment fails with an OppTeamMemRec error." echo " Note: cannot coexist with Opportunity Splits — disable Splits first if enabled." else report_status "Opportunity Team (enableOpportunityTeam)" "$VERIFY_OPP_TEAM" "critical" fi # Verify Email Administration (best-effort, not blocking) VERIFY_EMAIL=$(sf data query --query "SELECT enableEnhancedEmailEnabled FROM EmailAdministrationSettings" --target-org "$ORG_ALIAS" --json 2>/dev/null | jq -r '.result.records[0].enableEnhancedEmailEnabled // "unknown"' 2>/dev/null || echo "unknown") if [[ "$VERIFY_EMAIL" == "true" ]]; then echo " ✅ Enhanced Email: enabled" else echo " ⚠️ Enhanced Email: not enabled (optional — email body indexing)" fi # Verify Opportunity Settings (best-effort, not blocking) VERIFY_OPP=$(sf data query --query "SELECT enableFindSimilarOpportunities FROM OpportunitySettings" --target-org "$ORG_ALIAS" --json 2>/dev/null | jq -r '.result.records[0].enableFindSimilarOpportunities // "unknown"' 2>/dev/null || echo "unknown") if [[ "$VERIFY_OPP" == "true" ]]; then echo " ✅ Opportunity Settings: enabled" else echo " ⚠️ Opportunity Settings: not enabled (optional)" fi echo "" if [[ "$VERIFICATION_FAILED" == "true" ]]; then echo "❌ Critical prerequisites failed. Cannot proceed to Deal Agent setup." exit 1 fi echo "✅ All prerequisites enabled successfully" echo "" echo "Next step: Run ./enable-deal-agent.sh $ORG_ALIAS"