From 747aa90d26f0af88ec2783b9d80058d9351aeaf0 Mon Sep 17 00:00:00 2001 From: reynold Date: Thu, 28 Aug 2025 16:34:16 +0800 Subject: [PATCH] added addl wrappers --- README.md | 200 +++++++++++++++++++++--- sf-apex-run | 173 +++++++++++++++++++++ sf-apex-run.ps1 | 244 +++++++++++++++++++++++++++++ sf-data-export | 321 ++++++++++++++++++++++++++++++++++++++ sf-data-export.ps1 | 290 ++++++++++++++++++++++++++++++++++ sf-data-import | 376 +++++++++++++++++++++++++++++++++++++++++++++ sf-data-import.ps1 | 345 +++++++++++++++++++++++++++++++++++++++++ sf-logs-tail | 313 +++++++++++++++++++++++++++++++++++++ sf-logs-tail.ps1 | 315 +++++++++++++++++++++++++++++++++++++ sf-org-create | 241 +++++++++++++++++++++++++++++ sf-org-create.ps1 | 253 ++++++++++++++++++++++++++++++ sf-org-info | 166 ++++++++++++++++++++ sf-org-info.ps1 | 204 ++++++++++++++++++++++++ sf-retrieve | 204 ++++++++++++++++++++++++ sf-retrieve.ps1 | 194 +++++++++++++++++++++++ sf-test-run | 218 ++++++++++++++++++++++++++ sf-test-run.ps1 | 229 +++++++++++++++++++++++++++ 17 files changed, 4265 insertions(+), 21 deletions(-) create mode 100755 sf-apex-run create mode 100644 sf-apex-run.ps1 create mode 100755 sf-data-export create mode 100644 sf-data-export.ps1 create mode 100755 sf-data-import create mode 100644 sf-data-import.ps1 create mode 100755 sf-logs-tail create mode 100644 sf-logs-tail.ps1 create mode 100755 sf-org-create create mode 100644 sf-org-create.ps1 create mode 100755 sf-org-info create mode 100644 sf-org-info.ps1 create mode 100755 sf-retrieve create mode 100644 sf-retrieve.ps1 create mode 100755 sf-test-run create mode 100644 sf-test-run.ps1 diff --git a/README.md b/README.md index 118032e..345481b 100644 --- a/README.md +++ b/README.md @@ -6,13 +6,28 @@ A collection of convenient wrapper scripts for common Salesforce CLI operations. ## Overview -This toolkit provides four main utilities: +This toolkit provides a suite of cross-platform wrappers to streamline common Salesforce CLI workflows: +Core: - **`sf-deploy`** - Streamlined wrapper for `sf project deploy start` - **`sf-dry-run`** - Validation wrapper for `sf project deploy start --dry-run` - **`sf-web-open`** - Quick org browser opener for `sf org open` - **`sf-check`** - Environment verification tool to check SF CLI installation and configuration +Org and metadata: +- **`sf-org-create` / `sf-org-create.ps1`** - Smart scratch org creation +- **`sf-org-info` / `sf-org-info.ps1`** - Quick org info, limits, and context +- **`sf-retrieve` / `sf-retrieve.ps1`** - Streamlined metadata retrieval (types, manifest, packages) + +Apex and tests: +- **`sf-test-run` / `sf-test-run.ps1`** - Focused Apex test execution with coverage +- **`sf-apex-run` / `sf-apex-run.ps1`** - Anonymous Apex execution (file or inline) + +Data and logs: +- **`sf-data-export` / `sf-data-export.ps1`** - Export data via SOQL to CSV/JSON, optional Bulk API +- **`sf-data-import` / `sf-data-import.ps1`** - Import CSV/JSON with insert/update/upsert +- **`sf-logs-tail` / `sf-logs-tail.ps1`** - Real-time debug logs tail with filtering + ## Installation ### Unix/Linux/macOS (Bash) @@ -20,19 +35,30 @@ This toolkit provides four main utilities: 1. Clone or download this repository to your preferred tools directory 2. Make the scripts executable: ```bash - chmod +x sf-deploy sf-dry-run sf-web-open sf-check + chmod +x \ + sf-deploy sf-dry-run sf-web-open sf-check \ + sf-org-create sf-org-info sf-retrieve sf-test-run sf-apex-run \ + sf-data-export sf-data-import sf-logs-tail ``` 3. Add the directory to your PATH or create symlinks in a directory that's already in your PATH: ```bash # Option 1: Add to PATH (add to your ~/.zshrc or ~/.bashrc) export PATH="$PATH:/path/to/sf-cli-wrapper" - # Option 2: Create symlinks - ln -s /path/to/sf-cli-wrapper/sf-deploy /usr/local/bin/sf-deploy - ln -s /path/to/sf-cli-wrapper/sf-dry-run /usr/local/bin/sf-dry-run - ln -s /path/to/sf-cli-wrapper/sf-web-open /usr/local/bin/sf-web-open - ln -s /path/to/sf-cli-wrapper/sf-check /usr/local/bin/sf-check - ``` +# Option 2: Create symlinks +ln -s /path/to/sf-cli-wrapper/sf-deploy /usr/local/bin/sf-deploy +ln -s /path/to/sf-cli-wrapper/sf-dry-run /usr/local/bin/sf-dry-run +ln -s /path/to/sf-cli-wrapper/sf-web-open /usr/local/bin/sf-web-open +ln -s /path/to/sf-cli-wrapper/sf-check /usr/local/bin/sf-check +ln -s /path/to/sf-cli-wrapper/sf-org-create /usr/local/bin/sf-org-create +ln -s /path/to/sf-cli-wrapper/sf-org-info /usr/local/bin/sf-org-info +ln -s /path/to/sf-cli-wrapper/sf-retrieve /usr/local/bin/sf-retrieve +ln -s /path/to/sf-cli-wrapper/sf-test-run /usr/local/bin/sf-test-run +ln -s /path/to/sf-cli-wrapper/sf-apex-run /usr/local/bin/sf-apex-run +ln -s /path/to/sf-cli-wrapper/sf-data-export /usr/local/bin/sf-data-export +ln -s /path/to/sf-cli-wrapper/sf-data-import /usr/local/bin/sf-data-import +ln -s /path/to/sf-cli-wrapper/sf-logs-tail /usr/local/bin/sf-logs-tail +``` ### Windows (PowerShell) @@ -46,15 +72,125 @@ This toolkit provides four main utilities: # Option 1: Add to PATH (System Environment Variables) # Add C:\path\to\sf-cli-wrapper to your system PATH - # Option 2: Create PowerShell aliases (add to your $PROFILE) - Set-Alias sf-deploy "C:\path\to\sf-cli-wrapper\sf-deploy.ps1" - Set-Alias sf-dry-run "C:\path\to\sf-cli-wrapper\sf-dry-run.ps1" - Set-Alias sf-web-open "C:\path\to\sf-cli-wrapper\sf-web-open.ps1" - Set-Alias sf-check "C:\path\to\sf-cli-wrapper\sf-check.ps1" - ``` +# Option 2: Create PowerShell aliases (add to your $PROFILE) +Set-Alias sf-deploy "C:\\path\\to\\sf-cli-wrapper\\sf-deploy.ps1" +Set-Alias sf-dry-run "C:\\path\\to\\sf-cli-wrapper\\sf-dry-run.ps1" +Set-Alias sf-web-open "C:\\path\\to\\sf-cli-wrapper\\sf-web-open.ps1" +Set-Alias sf-check "C:\\path\\to\\sf-cli-wrapper\\sf-check.ps1" +Set-Alias sf-org-create "C:\\path\\to\\sf-cli-wrapper\\sf-org-create.ps1" +Set-Alias sf-org-info "C:\\path\\to\\sf-cli-wrapper\\sf-org-info.ps1" +Set-Alias sf-retrieve "C:\\path\\to\\sf-cli-wrapper\\sf-retrieve.ps1" +Set-Alias sf-test-run "C:\\path\\to\\sf-cli-wrapper\\sf-test-run.ps1" +Set-Alias sf-apex-run "C:\\path\\to\\sf-cli-wrapper\\sf-apex-run.ps1" +Set-Alias sf-data-export "C:\\path\\to\\sf-cli-wrapper\\sf-data-export.ps1" +Set-Alias sf-data-import "C:\\path\\to\\sf-cli-wrapper\\sf-data-import.ps1" +Set-Alias sf-logs-tail "C:\\path\\to\\sf-cli-wrapper\\sf-logs-tail.ps1" +``` ## Scripts +### sf-org-create + +Smart scratch org creation with templates and intelligent defaults. + +Usage (bash): +```bash +sf-org-create [-a ALIAS] [-d DAYS] [-t TEMPLATE] [--no-namespace] +``` + +Usage (PowerShell): +```powershell +sf-org-create.ps1 -Alias "MySO" -DurationDays 7 -Template "config/project-scratch-def.json" +``` + +Examples: +- Create with alias and duration: `sf-org-create -a SO -d 7` +- Use a specific template: `sf-org-create -t config/project-scratch-def.json` + +### sf-org-info / sf-org-info.ps1 + +Quick org information display with optional limits and listing authenticated orgs. + +Usage: +```bash +sf-org-info [-o ORG] [--limits] [--list] +``` +```powershell +sf-org-info.ps1 -TargetOrg "myorg" -Limits -Verbose +``` + +### sf-retrieve / sf-retrieve.ps1 + +Streamlined metadata retrieval supporting metadata types, manifest, and package name. + +Usage: +```bash +sf-retrieve --types "ApexClass,CustomObject" | --manifest manifest/package.xml | --package-name MyPkg [--target-org ORG] [--output-dir DIR] +``` +```powershell +sf-retrieve.ps1 -MetadataTypes "ApexClass,CustomObject" -TargetOrg myorg -OutputDir retrieved +``` + +### sf-test-run / sf-test-run.ps1 + +Focused Apex test execution with better formatting, coverage, and wait control. + +Usage: +```bash +sf-test-run --classes "A,B" | --methods "A.m1,B.m2" | --level RunLocalTests [--coverage] [--wait 15] [--target-org ORG] +``` +```powershell +sf-test-run.ps1 -TestClasses "ApexTest1,ApexTest2" -Coverage -Wait 15 +``` + +### sf-apex-run / sf-apex-run.ps1 + +Execute anonymous Apex from file or inline code. + +Usage: +```bash +sf-apex-run --file scripts/setup.apex | --code "System.debug('hi');" [--target-org ORG] +``` +```powershell +sf-apex-run.ps1 -File "scripts/setup.apex" -TargetOrg dev +``` + +### sf-data-export / sf-data-export.ps1 + +Export data via SOQL to CSV/JSON with optional Bulk API. + +Usage: +```bash +sf-data-export --query "SELECT Id, Name FROM Account" | --file query.soql | --sobject Account [--format csv|json] [--bulk] [--output out.csv] +``` +```powershell +sf-data-export.ps1 -Query "SELECT Id FROM User" -Format json -Output users.json +``` + +### sf-data-import / sf-data-import.ps1 + +Import CSV/JSON with insert/update/upsert operations. + +Usage: +```bash +sf-data-import --file data.csv --sobject Account --operation insert|update|upsert [--external-id Field] [--bulk] +``` +```powershell +sf-data-import.ps1 -File data.json -SObject Contact -Operation upsert -ExternalId Email +``` + +### sf-logs-tail / sf-logs-tail.ps1 + +Real-time debug logs tail with filtering, levels, and Apex-only mode. + +Usage: +```bash +sf-logs-tail [--target-org ORG] [--user-id USER] [--level DEBUG] [--duration 60] [--filter PATTERN] [--apex-only] +``` +```powershell +sf-logs-tail.ps1 -TargetOrg sandbox -Level DEBUG -Duration 60 -ApexOnly -Filter "MyClass" +``` + ### sf-deploy Wrapper for `sf project deploy start` that simplifies deploying multiple source files with optional test execution. @@ -178,7 +314,7 @@ sf-check -v ## Automatic Environment Verification -All wrapper scripts (`sf-deploy`, `sf-dry-run`, `sf-web-open`) include built-in environment verification: +All wrapper scripts (deploy, dry-run, web-open, org-create, org-info, retrieve, test-run, apex-run, data-export, data-import, logs-tail) include built-in environment verification: ### ✅ **When SF CLI is installed (normal operation):** ```bash @@ -265,6 +401,28 @@ sf-web-open.ps1 ### Windows Common Workflows +Additional helpers (PowerShell): +```powershell +# Org creation +sf-org-create.ps1 -Alias "SO" -DurationDays 7 + +# Retrieve metadata +sf-retrieve.ps1 -Manifest "manifest/package.xml" -TargetOrg "dev" + +# Run tests with coverage +sf-test-run.ps1 -TestLevel RunLocalTests -Coverage -Wait 15 + +# Execute anonymous Apex +sf-apex-run.ps1 -Code "System.debug('Hello');" -TargetOrg dev + +# Data export and import +sf-data-export.ps1 -SObject Account -Format csv -Output accounts.csv +sf-data-import.ps1 -File accounts.csv -SObject Account -Operation insert + +# Tail logs +sf-logs-tail.ps1 -Level DEBUG -Duration 30 -ApexOnly +``` + ```powershell # 1. First, validate your deployment (specific file) sf-dry-run.ps1 -o "DEMO-ORG" -s "force-app/main/default/classes/MyClass.cls" @@ -328,13 +486,13 @@ sf-deploy -o DEMO-ORG \ ## Features - **Cross-Platform**: Available for both Unix/Linux/macOS (Bash) and Windows (PowerShell) -- **Automatic Environment Verification**: Scripts automatically check if SF CLI is available and run diagnostics if not -- **Error Handling**: All scripts include robust error handling -- **Help Documentation**: Each script includes comprehensive help accessible with `-h` (both Bash and PowerShell) -- **Flexible Input**: Support for both absolute and repository-relative paths +- **Automatic Environment Verification**: All wrappers auto-check SF CLI and run diagnostics when missing +- **Consistent UX**: Arguments and output styling are aligned across Bash and PowerShell +- **Error Handling**: Robust input validation and actionable errors +- **Help Documentation**: Each script includes comprehensive help (-h or -Help) +- **Flexible Input**: Supports absolute and repository-relative paths - **Command Echo**: Shows the actual `sf` command being executed for transparency -- **Multiple Sources**: Easy handling of multiple source directories and test classes -- **Parameter Validation**: Scripts validate required parameters and show helpful error messages +- **Focused Workflows**: Deploy, validate, retrieve, test, run Apex, manage orgs, data import/export, and tail logs ## Tips diff --git a/sf-apex-run b/sf-apex-run new file mode 100755 index 0000000..0e92b66 --- /dev/null +++ b/sf-apex-run @@ -0,0 +1,173 @@ +#!/usr/bin/env bash +set -euo pipefail + +show_help() { + cat <<'EOF' +sf-apex-run — wrapper for executing anonymous Apex code + +USAGE: + sf-apex-run -o (-f | -c ) [-h] + +OPTIONS: + -o Org alias or username to execute in (required) + -f Path to Apex file to execute + -c Apex code string to execute directly + -h Show this help + +EXAMPLES: + 1) Execute Apex from file: + sf-apex-run -o DEMO-ORG -f "scripts/debug.apex" + + 2) Execute inline Apex code: + sf-apex-run -o DEMO-ORG -c "System.debug('Hello World!');" + + 3) Execute complex Apex from file: + sf-apex-run -o DEMO-ORG -f "scripts/data-cleanup.apex" + +COMMON USE CASES: + - Running debug scripts + - Data manipulation and cleanup + - Testing code snippets + - One-time data fixes + - System diagnostics + +APEX FILE EXAMPLES: + Create a file like 'debug.apex': + ``` + List accounts = [SELECT Id, Name FROM Account LIMIT 5]; + for (Account acc : accounts) { + System.debug('Account: ' + acc.Name); + } + System.debug('Total accounts: ' + accounts.size()); + ``` + +Notes: +- Either -f (file) or -c (code) must be specified, but not both +- Apex files should contain valid Apex code +- Output will show execution results and any debug logs +- Be careful with DML operations - they will actually execute! +EOF +} + +# Default values +ORG="" +APEX_FILE="" +APEX_CODE="" + +if [[ $# -eq 0 ]]; then + show_help + exit 0 +fi + +while getopts ":o:f:c:h" opt; do + case "$opt" in + o) ORG="$OPTARG" ;; + f) APEX_FILE="$OPTARG" ;; + c) APEX_CODE="$OPTARG" ;; + h) show_help; exit 0 ;; + \?) echo "Unknown option: -$OPTARG" >&2; echo; show_help; exit 1 ;; + :) echo "Option -$OPTARG requires an argument." >&2; echo; show_help; exit 1 ;; + esac +done + +# Validate required parameters +if [[ -z "$ORG" ]]; then + echo "Error: Org (-o) is required." >&2 + echo + show_help + exit 1 +fi + +# Validate that either file or code is specified, but not both +if [[ -z "$APEX_FILE" && -z "$APEX_CODE" ]]; then + echo "Error: Must specify either -f (file) or -c (code)." >&2 + echo + show_help + exit 1 +fi + +if [[ -n "$APEX_FILE" && -n "$APEX_CODE" ]]; then + echo "Error: Cannot specify both -f (file) and -c (code). Use one method." >&2 + echo + show_help + exit 1 +fi + +# Validate file exists if specified +if [[ -n "$APEX_FILE" && ! -f "$APEX_FILE" ]]; then + echo "Error: Apex file '$APEX_FILE' not found." >&2 + exit 1 +fi + +# Silent environment check +if ! command -v sf >/dev/null 2>&1; then + echo "❌ Salesforce CLI (sf) not found!" + echo + echo "Running environment check to help you get started..." + echo + + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + if [[ -x "$SCRIPT_DIR/sf-check" ]]; then + "$SCRIPT_DIR/sf-check" + elif command -v sf-check >/dev/null 2>&1; then + sf-check + else + echo "sf-check not found. Please install the Salesforce CLI from:" + echo "https://developer.salesforce.com/tools/sfdxcli" + fi + exit 1 +fi + +# Build the command +CMD=(sf apex run) +CMD+=(--target-org "$ORG") + +# Create temporary file for code if needed +TEMP_FILE="" +if [[ -n "$APEX_CODE" ]]; then + TEMP_FILE=$(mktemp -t "apex-code-XXXXXX.apex") + echo "$APEX_CODE" > "$TEMP_FILE" + CMD+=(--file "$TEMP_FILE") +else + CMD+=(--file "$APEX_FILE") +fi + +# Show what we're executing +echo "⚡ Executing Apex code in org '$ORG'..." +if [[ -n "$APEX_FILE" ]]; then + echo " File: $APEX_FILE" + if [[ -f "$APEX_FILE" ]]; then + echo " Lines: $(wc -l < "$APEX_FILE")" + fi +elif [[ -n "$APEX_CODE" ]]; then + echo " Code: ${APEX_CODE:0:50}${#APEX_CODE > 50 ? '...' : ''}" +fi +echo + +echo ">>> Running: ${CMD[*]}" +echo + +# Execute the command +if "${CMD[@]}"; then + echo + echo "✅ Apex execution completed successfully!" + + # Show helpful next steps + echo + echo "💡 Next steps:" + echo " - Check debug logs: sf apex get log --target-org \"$ORG\"" + echo " - View recent logs: sf-logs-tail -o \"$ORG\" (if available)" + +else + RESULT=$? + echo + echo "❌ Apex execution failed" + echo "Check the output above for compilation or runtime errors." + + # Clean up temp file + [[ -n "$TEMP_FILE" && -f "$TEMP_FILE" ]] && rm -f "$TEMP_FILE" + exit $RESULT +fi + +# Clean up temp file +[[ -n "$TEMP_FILE" && -f "$TEMP_FILE" ]] && rm -f "$TEMP_FILE" diff --git a/sf-apex-run.ps1 b/sf-apex-run.ps1 new file mode 100644 index 0000000..3346141 --- /dev/null +++ b/sf-apex-run.ps1 @@ -0,0 +1,244 @@ +#!/usr/bin/env pwsh + +<# +.SYNOPSIS + Anonymous Apex execution wrapper for Salesforce CLI + +.DESCRIPTION + A user-friendly wrapper around 'sf apex run' that simplifies executing + anonymous Apex code from files or inline strings with better formatting + and error handling. + +.PARAMETER File + Path to Apex file to execute + +.PARAMETER Code + Inline Apex code to execute (alternative to -File) + +.PARAMETER TargetOrg + Target org username or alias (uses default if not specified) + +.PARAMETER Verbose + Enable verbose output showing execution details + +.PARAMETER Help + Show this help message + +.EXAMPLE + .\sf-apex-run.ps1 -File "scripts/setup.apex" + .\sf-apex-run.ps1 -Code "System.debug('Hello World');" + .\sf-apex-run.ps1 -File "test.apex" -TargetOrg "sandbox" + .\sf-apex-run.ps1 -Code "Database.insert(new Account(Name='Test'));" -Verbose + +.NOTES + This script automatically checks for Salesforce CLI installation and runs + diagnostics if the CLI is not found. +#> + +param( + [Parameter(ParameterSetName="File")] + [string]$File, + + [Parameter(ParameterSetName="Code")] + [string]$Code, + + [string]$TargetOrg, + [switch]$Verbose, + [switch]$Help +) + +# Show help if requested +if ($Help) { + Get-Help $MyInvocation.MyCommand.Path -Detailed + exit 0 +} + +# Function to check if Salesforce CLI is installed +function Test-SalesforceCLI { + try { + $null = Get-Command sf -ErrorAction Stop + return $true + } catch { + return $false + } +} + +# Function to run sf-check diagnostics +function Invoke-SalesforceCheck { + $checkScript = if (Test-Path "sf-check.ps1") { + ".\sf-check.ps1" + } elseif (Test-Path "sf-check.sh") { + "bash sf-check.sh" + } else { + $null + } + + if ($checkScript) { + Write-Host "Running Salesforce CLI diagnostics..." -ForegroundColor Yellow + Invoke-Expression $checkScript + } else { + Write-Host "Salesforce CLI not found and no diagnostic script available." -ForegroundColor Red + Write-Host "Please install the Salesforce CLI: https://developer.salesforce.com/tools/salesforcecli" -ForegroundColor Red + } +} + +# Function to display code preview +function Show-CodePreview { + param([string]$CodeContent, [string]$Source) + + Write-Host "📝 Apex Code ($Source):" -ForegroundColor Yellow + Write-Host "----------------------------------------" -ForegroundColor Gray + + # Show first few lines of code for preview + $lines = $CodeContent -split "`n" + $previewLines = if ($lines.Count -gt 10) { + $lines[0..9] + @("... (truncated, $($lines.Count - 10) more lines)") + } else { + $lines + } + + foreach ($line in $previewLines) { + Write-Host " $line" -ForegroundColor White + } + Write-Host "----------------------------------------" -ForegroundColor Gray +} + +# Silently check for Salesforce CLI +if (-not (Test-SalesforceCLI)) { + Invoke-SalesforceCheck + exit 1 +} + +# Validate that either file or code is provided +if (-not $File -and -not $Code) { + Write-Host "Error: Must specify either -File or -Code parameter" -ForegroundColor Red + Write-Host "" + Write-Host "Usage examples:" -ForegroundColor Yellow + Write-Host " .\sf-apex-run.ps1 -File `"scripts/setup.apex`"" -ForegroundColor Gray + Write-Host " .\sf-apex-run.ps1 -Code `"System.debug('Hello World');`"" -ForegroundColor Gray + Write-Host "" + Write-Host "Use -Help for detailed usage information." -ForegroundColor Yellow + exit 1 +} + +# Validate that both file and code aren't provided +if ($File -and $Code) { + Write-Host "Error: Cannot specify both -File and -Code parameters" -ForegroundColor Red + exit 1 +} + +# If file is specified, validate it exists and read content +if ($File) { + if (-not (Test-Path $File)) { + Write-Host "Error: Apex file not found: $File" -ForegroundColor Red + exit 1 + } + + try { + $apexContent = Get-Content -Path $File -Raw + Write-Host "Using Apex file: $File" -ForegroundColor Green + + if ($Verbose) { + Show-CodePreview $apexContent "from file: $File" + } + } catch { + Write-Host "Error reading Apex file: $($_.Exception.Message)" -ForegroundColor Red + exit 1 + } +} else { + $apexContent = $Code + Write-Host "Using inline Apex code" -ForegroundColor Green + + if ($Verbose) { + Show-CodePreview $apexContent "inline" + } +} + +# Build the sf command +$sfArgs = @("apex", "run") + +# Add target org if specified +if ($TargetOrg) { + $sfArgs += "--target-org" + $sfArgs += $TargetOrg + Write-Host "Target org: $TargetOrg" -ForegroundColor Cyan +} + +# Add verbose flag if requested +if ($Verbose) { + $sfArgs += "--verbose" +} + +# Display execution info +Write-Host "" +Write-Host "🚀 Executing Anonymous Apex" -ForegroundColor Blue +Write-Host "============================" -ForegroundColor Blue + +# Create a temporary file for the Apex content if needed +$tempFile = $null +if ($Code) { + $tempFile = [System.IO.Path]::GetTempFileName() + ".apex" + try { + Set-Content -Path $tempFile -Value $apexContent -Encoding UTF8 + $sfArgs += "--file" + $sfArgs += $tempFile + } catch { + Write-Host "Error creating temporary file: $($_.Exception.Message)" -ForegroundColor Red + exit 1 + } +} else { + $sfArgs += "--file" + $sfArgs += $File +} + +# Display the command being run (without showing temp file path) +$displayArgs = $sfArgs -replace [regex]::Escape($tempFile), '' +Write-Host "" +Write-Host "Executing: sf $($displayArgs -join ' ')" -ForegroundColor Gray +Write-Host "" + +# Execute the command +try { + $startTime = Get-Date + & sf @sfArgs + $exitCode = $LASTEXITCODE + $endTime = Get-Date + $duration = $endTime - $startTime + + Write-Host "" + Write-Host "⏱️ Execution completed in $($duration.TotalSeconds.ToString('F2')) seconds" -ForegroundColor Gray + + if ($exitCode -eq 0) { + Write-Host "" + Write-Host "✅ Anonymous Apex executed successfully!" -ForegroundColor Green + + if ($Verbose) { + Write-Host "💡 Check the output above for any System.debug() statements" -ForegroundColor Yellow + } + } else { + Write-Host "" + Write-Host "❌ Apex execution failed with exit code: $exitCode" -ForegroundColor Red + Write-Host "💡 Check compilation errors or runtime exceptions above" -ForegroundColor Yellow + + # Clean up temp file before exiting + if ($tempFile -and (Test-Path $tempFile)) { + Remove-Item $tempFile -Force -ErrorAction SilentlyContinue + } + + exit $exitCode + } +} catch { + Write-Host "Error executing sf command: $($_.Exception.Message)" -ForegroundColor Red + + # Clean up temp file before exiting + if ($tempFile -and (Test-Path $tempFile)) { + Remove-Item $tempFile -Force -ErrorAction SilentlyContinue + } + + exit 1 +} finally { + # Clean up temporary file + if ($tempFile -and (Test-Path $tempFile)) { + Remove-Item $tempFile -Force -ErrorAction SilentlyContinue + } +} diff --git a/sf-data-export b/sf-data-export new file mode 100755 index 0000000..b7ae3b1 --- /dev/null +++ b/sf-data-export @@ -0,0 +1,321 @@ +#!/bin/bash + +# Data export wrapper for Salesforce CLI +# Provides streamlined data export functionality with SOQL query support + +# Color codes for output formatting +readonly RED='\033[0;31m' +readonly GREEN='\033[0;32m' +readonly YELLOW='\033[0;33m' +readonly BLUE='\033[0;34m' +readonly CYAN='\033[0;36m' +readonly GRAY='\033[0;37m' +readonly NC='\033[0m' # No Color + +# Function to display usage information +show_usage() { + echo -e "${BLUE}sf-data-export - Data Export Wrapper for Salesforce CLI${NC}" + echo "" + echo "USAGE:" + echo " sf-data-export [OPTIONS]" + echo "" + echo "OPTIONS:" + echo " -q, --query QUERY SOQL query to export data" + echo " -f, --file FILE File containing SOQL query" + echo " -s, --sobject SOBJECT Standard object query (exports all records)" + echo " -o, --output FILE Output file path (default: export.csv)" + echo " -t, --target-org ORG Target org username or alias" + echo " --format FORMAT Output format: csv, json (default: csv)" + echo " --bulk Use bulk API for large datasets" + echo " --wait MINUTES Wait time in minutes (default: 10)" + echo " -v, --verbose Enable verbose output" + echo " -h, --help Show this help message" + echo "" + echo "EXAMPLES:" + echo " sf-data-export --query \"SELECT Id, Name FROM Account LIMIT 100\"" + echo " sf-data-export --sobject Account --format json --output accounts.json" + echo " sf-data-export --file queries/contacts.soql --bulk --wait 15" + echo " sf-data-export --query \"SELECT Id FROM User\" --target-org production" + echo "" + echo "This script automatically checks for Salesforce CLI installation." +} + +# Function to check if Salesforce CLI is installed +check_salesforce_cli() { + if ! command -v sf &> /dev/null; then + return 1 + fi + return 0 +} + +# Function to run sf-check diagnostics +run_salesforce_check() { + if [[ -f "sf-check" ]]; then + echo -e "${YELLOW}Running Salesforce CLI diagnostics...${NC}" + ./sf-check + elif [[ -f "sf-check.sh" ]]; then + echo -e "${YELLOW}Running Salesforce CLI diagnostics...${NC}" + bash sf-check.sh + elif [[ -f "sf-check.ps1" ]]; then + echo -e "${YELLOW}Running Salesforce CLI diagnostics...${NC}" + pwsh sf-check.ps1 + else + echo -e "${RED}Salesforce CLI not found and no diagnostic script available.${NC}" + echo -e "${RED}Please install the Salesforce CLI: https://developer.salesforce.com/tools/salesforcecli${NC}" + fi +} + +# Function to validate SOQL query +validate_query() { + local query="$1" + + # Basic validation - check if it starts with SELECT + if [[ ! "$query" =~ ^[[:space:]]*[Ss][Ee][Ll][Ee][Cc][Tt] ]]; then + echo -e "${RED}Error: Query must start with SELECT${NC}" + return 1 + fi + + return 0 +} + +# Function to build standard object query +build_sobject_query() { + local sobject="$1" + + # For common objects, use sensible field selections + case "$sobject" in + "Account") + echo "SELECT Id, Name, Type, Industry, Phone, Website, BillingCity, BillingState, BillingCountry FROM Account" + ;; + "Contact") + echo "SELECT Id, FirstName, LastName, Email, Phone, AccountId, Account.Name FROM Contact" + ;; + "Lead") + echo "SELECT Id, FirstName, LastName, Email, Phone, Company, Status, Source FROM Lead" + ;; + "Opportunity") + echo "SELECT Id, Name, AccountId, Account.Name, Amount, CloseDate, StageName, Probability FROM Opportunity" + ;; + "Case") + echo "SELECT Id, CaseNumber, Subject, Status, Priority, Origin, AccountId, Account.Name, ContactId, Contact.Name FROM Case" + ;; + "User") + echo "SELECT Id, Name, Email, Username, Profile.Name, IsActive, LastLoginDate FROM User" + ;; + *) + # Generic query for other objects + echo "SELECT Id, Name FROM $sobject" + ;; + esac +} + +# Initialize variables +QUERY="" +QUERY_FILE="" +SOBJECT="" +OUTPUT_FILE="export.csv" +TARGET_ORG="" +FORMAT="csv" +USE_BULK=false +WAIT_TIME="10" +VERBOSE=false + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case $1 in + -q|--query) + QUERY="$2" + shift 2 + ;; + -f|--file) + QUERY_FILE="$2" + shift 2 + ;; + -s|--sobject) + SOBJECT="$2" + shift 2 + ;; + -o|--output) + OUTPUT_FILE="$2" + shift 2 + ;; + -t|--target-org) + TARGET_ORG="$2" + shift 2 + ;; + --format) + FORMAT="$2" + shift 2 + ;; + --bulk) + USE_BULK=true + shift + ;; + --wait) + WAIT_TIME="$2" + shift 2 + ;; + -v|--verbose) + VERBOSE=true + shift + ;; + -h|--help) + show_usage + exit 0 + ;; + *) + echo -e "${RED}Unknown option: $1${NC}" + echo "" + show_usage + exit 1 + ;; + esac +done + +# Silently check for Salesforce CLI +if ! check_salesforce_cli; then + run_salesforce_check + exit 1 +fi + +# Validate that exactly one query method is specified +query_methods=0 +[[ -n "$QUERY" ]] && ((query_methods++)) +[[ -n "$QUERY_FILE" ]] && ((query_methods++)) +[[ -n "$SOBJECT" ]] && ((query_methods++)) + +if [[ $query_methods -eq 0 ]]; then + echo -e "${RED}Error: Must specify one of: --query, --file, or --sobject${NC}" + echo "" + echo -e "${YELLOW}Usage examples:${NC}" + echo -e "${GRAY} sf-data-export --query \"SELECT Id, Name FROM Account\"${NC}" + echo -e "${GRAY} sf-data-export --file queries/accounts.soql${NC}" + echo -e "${GRAY} sf-data-export --sobject Account${NC}" + echo "" + echo -e "${YELLOW}Use --help for detailed usage information.${NC}" + exit 1 +fi + +if [[ $query_methods -gt 1 ]]; then + echo -e "${RED}Error: Can only specify one of: --query, --file, or --sobject${NC}" + exit 1 +fi + +# Validate format +if [[ "$FORMAT" != "csv" && "$FORMAT" != "json" ]]; then + echo -e "${RED}Error: Format must be 'csv' or 'json'${NC}" + exit 1 +fi + +# Determine the final query +FINAL_QUERY="" +if [[ -n "$QUERY" ]]; then + FINAL_QUERY="$QUERY" + echo -e "${GREEN}Using inline query${NC}" +elif [[ -n "$QUERY_FILE" ]]; then + if [[ ! -f "$QUERY_FILE" ]]; then + echo -e "${RED}Error: Query file not found: $QUERY_FILE${NC}" + exit 1 + fi + FINAL_QUERY=$(cat "$QUERY_FILE") + echo -e "${GREEN}Using query from file: $QUERY_FILE${NC}" +elif [[ -n "$SOBJECT" ]]; then + FINAL_QUERY=$(build_sobject_query "$SOBJECT") + echo -e "${GREEN}Using standard query for $SOBJECT${NC}" +fi + +# Validate the query +if ! validate_query "$FINAL_QUERY"; then + exit 1 +fi + +# Build the sf command +SF_ARGS=("data" "export") + +# Add the query +SF_ARGS+=("--query" "$FINAL_QUERY") + +# Add optional parameters +if [[ -n "$TARGET_ORG" ]]; then + SF_ARGS+=("--target-org" "$TARGET_ORG") + echo -e "${CYAN}Target org: $TARGET_ORG${NC}" +fi + +if [[ "$USE_BULK" == true ]]; then + SF_ARGS+=("--bulk") + echo -e "${YELLOW}Using Bulk API${NC}" +fi + +if [[ "$WAIT_TIME" != "10" ]]; then + SF_ARGS+=("--wait" "$WAIT_TIME") +fi + +# Set output file and format +case "$FORMAT" in + "csv") + SF_ARGS+=("--output-file" "$OUTPUT_FILE") + ;; + "json") + SF_ARGS+=("--output-file" "$OUTPUT_FILE") + SF_ARGS+=("--json") + ;; +esac + +echo -e "${CYAN}Output format: $FORMAT${NC}" +echo -e "${CYAN}Output file: $OUTPUT_FILE${NC}" + +# Add verbose flag if requested +if [[ "$VERBOSE" == true ]]; then + SF_ARGS+=("--verbose") +fi + +# Display export information +echo "" +echo -e "${BLUE}📊 Starting Data Export${NC}" +echo -e "${BLUE}=======================${NC}" + +# Show query preview if verbose +if [[ "$VERBOSE" == true ]]; then + echo "" + echo -e "${YELLOW}📝 SOQL Query:${NC}" + echo -e "${GRAY}----------------------------------------${NC}" + echo -e "${GRAY}$FINAL_QUERY${NC}" + echo -e "${GRAY}----------------------------------------${NC}" +fi + +# Display the command being run +echo "" +echo -e "${GRAY}Executing: sf ${SF_ARGS[*]}${NC}" +echo "" + +# Execute the command +if sf "${SF_ARGS[@]}"; then + EXPORT_EXIT_CODE=0 +else + EXPORT_EXIT_CODE=$? +fi + +echo "" +if [[ $EXPORT_EXIT_CODE -eq 0 ]]; then + echo -e "${GREEN}✅ Data export completed successfully!${NC}" + + # Show file information if it exists + if [[ -f "$OUTPUT_FILE" ]]; then + FILE_SIZE=$(du -h "$OUTPUT_FILE" | cut -f1) + if [[ "$FORMAT" == "csv" ]]; then + # Count records (excluding header) + RECORD_COUNT=$(($(wc -l < "$OUTPUT_FILE") - 1)) + echo -e "${CYAN}📁 Exported $RECORD_COUNT records to $OUTPUT_FILE ($FILE_SIZE)${NC}" + else + echo -e "${CYAN}📁 Data exported to $OUTPUT_FILE ($FILE_SIZE)${NC}" + fi + fi + + if [[ "$VERBOSE" == true ]]; then + echo -e "${YELLOW}💡 Use a spreadsheet application or text editor to view the exported data${NC}" + fi +else + echo -e "${RED}❌ Data export failed with exit code: $EXPORT_EXIT_CODE${NC}" + echo -e "${YELLOW}💡 Check query syntax and permissions${NC}" + exit $EXPORT_EXIT_CODE +fi diff --git a/sf-data-export.ps1 b/sf-data-export.ps1 new file mode 100644 index 0000000..af54a55 --- /dev/null +++ b/sf-data-export.ps1 @@ -0,0 +1,290 @@ +#!/usr/bin/env pwsh + +<# +.SYNOPSIS + Data export wrapper for Salesforce CLI with SOQL query support + +.DESCRIPTION + A user-friendly wrapper around 'sf data export' that simplifies data export + from Salesforce orgs with SOQL query support, multiple formats, and intelligent defaults. + +.PARAMETER Query + SOQL query to export data + +.PARAMETER File + File containing SOQL query + +.PARAMETER SObject + Standard object query (exports common fields) + +.PARAMETER Output + Output file path (default: export.csv) + +.PARAMETER TargetOrg + Target org username or alias + +.PARAMETER Format + Output format: csv, json (default: csv) + +.PARAMETER Bulk + Use bulk API for large datasets + +.PARAMETER Wait + Wait time in minutes (default: 10) + +.PARAMETER Verbose + Enable verbose output + +.PARAMETER Help + Show this help message + +.EXAMPLE + .\sf-data-export.ps1 -Query "SELECT Id, Name FROM Account LIMIT 100" + .\sf-data-export.ps1 -SObject Account -Format json -Output accounts.json + .\sf-data-export.ps1 -File queries/contacts.soql -Bulk -Wait 15 + .\sf-data-export.ps1 -Query "SELECT Id FROM User" -TargetOrg production + +.NOTES + This script automatically checks for Salesforce CLI installation and runs + diagnostics if the CLI is not found. +#> + +param( + [Parameter(ParameterSetName="Query")] + [string]$Query, + + [Parameter(ParameterSetName="File")] + [string]$File, + + [Parameter(ParameterSetName="SObject")] + [string]$SObject, + + [string]$Output = "export.csv", + [string]$TargetOrg, + [ValidateSet("csv", "json")] + [string]$Format = "csv", + [switch]$Bulk, + [int]$Wait = 10, + [switch]$Verbose, + [switch]$Help +) + +# Show help if requested +if ($Help) { + Get-Help $MyInvocation.MyCommand.Path -Detailed + exit 0 +} + +# Function to check if Salesforce CLI is installed +function Test-SalesforceCLI { + try { + $null = Get-Command sf -ErrorAction Stop + return $true + } catch { + return $false + } +} + +# Function to run sf-check diagnostics +function Invoke-SalesforceCheck { + $checkScript = if (Test-Path "sf-check.ps1") { + ".\sf-check.ps1" + } elseif (Test-Path "sf-check.sh") { + "bash sf-check.sh" + } else { + $null + } + + if ($checkScript) { + Write-Host "Running Salesforce CLI diagnostics..." -ForegroundColor Yellow + Invoke-Expression $checkScript + } else { + Write-Host "Salesforce CLI not found and no diagnostic script available." -ForegroundColor Red + Write-Host "Please install the Salesforce CLI: https://developer.salesforce.com/tools/salesforcecli" -ForegroundColor Red + } +} + +# Function to build standard object query +function New-SObjectQuery { + param([string]$SObjectType) + + switch ($SObjectType) { + "Account" { + return "SELECT Id, Name, Type, Industry, Phone, Website, BillingCity, BillingState, BillingCountry FROM Account" + } + "Contact" { + return "SELECT Id, FirstName, LastName, Email, Phone, AccountId, Account.Name FROM Contact" + } + "Lead" { + return "SELECT Id, FirstName, LastName, Email, Phone, Company, Status, Source FROM Lead" + } + "Opportunity" { + return "SELECT Id, Name, AccountId, Account.Name, Amount, CloseDate, StageName, Probability FROM Opportunity" + } + "Case" { + return "SELECT Id, CaseNumber, Subject, Status, Priority, Origin, AccountId, Account.Name, ContactId, Contact.Name FROM Case" + } + "User" { + return "SELECT Id, Name, Email, Username, Profile.Name, IsActive, LastLoginDate FROM User" + } + default { + return "SELECT Id, Name FROM $SObjectType" + } + } +} + +# Function to validate SOQL query +function Test-SOQLQuery { + param([string]$QueryText) + + if ($QueryText -notmatch '^\s*SELECT\s+') { + Write-Host "Error: Query must start with SELECT" -ForegroundColor Red + return $false + } + + return $true +} + +# Silently check for Salesforce CLI +if (-not (Test-SalesforceCLI)) { + Invoke-SalesforceCheck + exit 1 +} + +# Validate that exactly one query method is specified +$queryMethods = @($Query, $File, $SObject | Where-Object { $_ }).Count +if ($queryMethods -eq 0) { + Write-Host "Error: Must specify one of: -Query, -File, or -SObject" -ForegroundColor Red + Write-Host "" + Write-Host "Usage examples:" -ForegroundColor Yellow + Write-Host " .\sf-data-export.ps1 -Query `"SELECT Id, Name FROM Account`"" -ForegroundColor Gray + Write-Host " .\sf-data-export.ps1 -File queries/accounts.soql" -ForegroundColor Gray + Write-Host " .\sf-data-export.ps1 -SObject Account" -ForegroundColor Gray + Write-Host "" + Write-Host "Use -Help for detailed usage information." -ForegroundColor Yellow + exit 1 +} + +if ($queryMethods -gt 1) { + Write-Host "Error: Can only specify one of: -Query, -File, or -SObject" -ForegroundColor Red + exit 1 +} + +# Determine the final query +$finalQuery = "" +if ($Query) { + $finalQuery = $Query + Write-Host "Using inline query" -ForegroundColor Green +} elseif ($File) { + if (-not (Test-Path $File)) { + Write-Host "Error: Query file not found: $File" -ForegroundColor Red + exit 1 + } + $finalQuery = Get-Content $File -Raw + Write-Host "Using query from file: $File" -ForegroundColor Green +} elseif ($SObject) { + $finalQuery = New-SObjectQuery $SObject + Write-Host "Using standard query for $SObject" -ForegroundColor Green +} + +# Validate the query +if (-not (Test-SOQLQuery $finalQuery)) { + exit 1 +} + +# Build the sf command +$sfArgs = @("data", "export", "--query", $finalQuery) + +# Add optional parameters +if ($TargetOrg) { + $sfArgs += "--target-org" + $sfArgs += $TargetOrg + Write-Host "Target org: $TargetOrg" -ForegroundColor Cyan +} + +if ($Bulk) { + $sfArgs += "--bulk" + Write-Host "Using Bulk API" -ForegroundColor Yellow +} + +if ($Wait -ne 10) { + $sfArgs += "--wait" + $sfArgs += $Wait.ToString() +} + +# Set output file and format +$sfArgs += "--output-file" +$sfArgs += $Output + +if ($Format -eq "json") { + $sfArgs += "--json" +} + +Write-Host "Output format: $Format" -ForegroundColor Cyan +Write-Host "Output file: $Output" -ForegroundColor Cyan + +# Add verbose flag if requested +if ($Verbose) { + $sfArgs += "--verbose" +} + +# Display export information +Write-Host "" +Write-Host "📊 Starting Data Export" -ForegroundColor Blue +Write-Host "=======================" -ForegroundColor Blue + +# Show query preview if verbose +if ($Verbose) { + Write-Host "" + Write-Host "📝 SOQL Query:" -ForegroundColor Yellow + Write-Host "----------------------------------------" -ForegroundColor Gray + Write-Host $finalQuery -ForegroundColor Gray + Write-Host "----------------------------------------" -ForegroundColor Gray +} + +# Display the command being run +Write-Host "" +Write-Host "Executing: sf $($sfArgs -join ' ')" -ForegroundColor Gray +Write-Host "" + +# Execute the command +try { + & sf @sfArgs + $exitCode = $LASTEXITCODE + + Write-Host "" + if ($exitCode -eq 0) { + Write-Host "✅ Data export completed successfully!" -ForegroundColor Green + + # Show file information if it exists + if (Test-Path $Output) { + $fileInfo = Get-Item $Output + $fileSize = if ($fileInfo.Length -gt 1MB) { + "{0:N1} MB" -f ($fileInfo.Length / 1MB) + } elseif ($fileInfo.Length -gt 1KB) { + "{0:N1} KB" -f ($fileInfo.Length / 1KB) + } else { + "$($fileInfo.Length) bytes" + } + + if ($Format -eq "csv") { + # Count records (excluding header) + $recordCount = (Get-Content $Output).Count - 1 + Write-Host "📁 Exported $recordCount records to $Output ($fileSize)" -ForegroundColor Cyan + } else { + Write-Host "📁 Data exported to $Output ($fileSize)" -ForegroundColor Cyan + } + } + + if ($Verbose) { + Write-Host "💡 Use a spreadsheet application or text editor to view the exported data" -ForegroundColor Yellow + } + } else { + Write-Host "❌ Data export failed with exit code: $exitCode" -ForegroundColor Red + Write-Host "💡 Check query syntax and permissions" -ForegroundColor Yellow + exit $exitCode + } +} catch { + Write-Host "Error executing sf command: $($_.Exception.Message)" -ForegroundColor Red + exit 1 +} diff --git a/sf-data-import b/sf-data-import new file mode 100755 index 0000000..762b89c --- /dev/null +++ b/sf-data-import @@ -0,0 +1,376 @@ +#!/bin/bash + +# Data import wrapper for Salesforce CLI +# Provides streamlined data import functionality with CSV/JSON support and upsert operations + +# Color codes for output formatting +readonly RED='\033[0;31m' +readonly GREEN='\033[0;32m' +readonly YELLOW='\033[0;33m' +readonly BLUE='\033[0;34m' +readonly CYAN='\033[0;36m' +readonly GRAY='\033[0;37m' +readonly NC='\033[0m' # No Color + +# Function to display usage information +show_usage() { + echo -e "${BLUE}sf-data-import - Data Import Wrapper for Salesforce CLI${NC}" + echo "" + echo "USAGE:" + echo " sf-data-import [OPTIONS]" + echo "" + echo "OPTIONS:" + echo " -f, --file FILE CSV or JSON file to import" + echo " -s, --sobject SOBJECT Target sObject type" + echo " -o, --operation OP Operation: insert, update, upsert (default: insert)" + echo " -e, --external-id FIELD External ID field for upsert/update operations" + echo " -t, --target-org ORG Target org username or alias" + echo " --bulk Use bulk API for large datasets" + echo " --wait MINUTES Wait time in minutes (default: 10)" + echo " --batch-size SIZE Batch size for bulk operations (default: 10000)" + echo " --ignore-errors Continue on errors (don't fail entire job)" + echo " -v, --verbose Enable verbose output" + echo " -h, --help Show this help message" + echo "" + echo "EXAMPLES:" + echo " sf-data-import --file accounts.csv --sobject Account" + echo " sf-data-import --file contacts.json --sobject Contact --operation upsert --external-id Email" + echo " sf-data-import --file leads.csv --sobject Lead --bulk --batch-size 5000" + echo " sf-data-import --file updates.csv --sobject Account --operation update --external-id AccountNumber" + echo "" + echo "SUPPORTED FORMATS:" + echo " • CSV files with header row" + echo " • JSON files (array of objects or newline-delimited JSON)" + echo "" + echo "This script automatically checks for Salesforce CLI installation." +} + +# Function to check if Salesforce CLI is installed +check_salesforce_cli() { + if ! command -v sf &> /dev/null; then + return 1 + fi + return 0 +} + +# Function to run sf-check diagnostics +run_salesforce_check() { + if [[ -f "sf-check" ]]; then + echo -e "${YELLOW}Running Salesforce CLI diagnostics...${NC}" + ./sf-check + elif [[ -f "sf-check.sh" ]]; then + echo -e "${YELLOW}Running Salesforce CLI diagnostics...${NC}" + bash sf-check.sh + elif [[ -f "sf-check.ps1" ]]; then + echo -e "${YELLOW}Running Salesforce CLI diagnostics...${NC}" + pwsh sf-check.ps1 + else + echo -e "${RED}Salesforce CLI not found and no diagnostic script available.${NC}" + echo -e "${RED}Please install the Salesforce CLI: https://developer.salesforce.com/tools/salesforcecli${NC}" + fi +} + +# Function to detect file format +detect_file_format() { + local file="$1" + local extension="${file##*.}" + + case "$extension" in + csv|CSV) + echo "csv" + ;; + json|JSON) + echo "json" + ;; + *) + # Try to detect by content + if head -n 1 "$file" | grep -q "^{.*}$\|^\["; then + echo "json" + else + echo "csv" + fi + ;; + esac +} + +# Function to validate CSV file +validate_csv_file() { + local file="$1" + + # Check if file has header + if [[ $(wc -l < "$file") -lt 2 ]]; then + echo -e "${RED}Error: CSV file must have at least a header and one data row${NC}" + return 1 + fi + + # Basic CSV validation - check for consistent field count + local header_fields=$(head -n 1 "$file" | tr ',' '\n' | wc -l) + local first_row_fields=$(sed -n '2p' "$file" | tr ',' '\n' | wc -l) + + if [[ $header_fields -ne $first_row_fields ]]; then + echo -e "${YELLOW}Warning: Header field count ($header_fields) differs from first row ($first_row_fields)${NC}" + fi + + return 0 +} + +# Function to validate JSON file +validate_json_file() { + local file="$1" + + # Try to parse JSON + if ! jq empty "$file" 2>/dev/null; then + echo -e "${RED}Error: Invalid JSON format in file${NC}" + return 1 + fi + + return 0 +} + +# Function to show file preview +show_file_preview() { + local file="$1" + local format="$2" + + echo -e "${YELLOW}📄 File Preview ($format):${NC}" + echo -e "${GRAY}----------------------------------------${NC}" + + case "$format" in + csv) + echo -e "${GRAY}Header: $(head -n 1 "$file")${NC}" + echo -e "${GRAY}Sample: $(sed -n '2p' "$file")${NC}" + echo -e "${GRAY}Records: $(($(wc -l < "$file") - 1))${NC}" + ;; + json) + if jq -e 'type == "array"' "$file" >/dev/null 2>&1; then + echo -e "${GRAY}Array format with $(jq '. | length' "$file") records${NC}" + echo -e "${GRAY}Sample keys: $(jq -r '.[0] | keys | join(", ")' "$file" 2>/dev/null || echo "N/A")${NC}" + else + echo -e "${GRAY}NDJSON format${NC}" + echo -e "${GRAY}Records: $(wc -l < "$file")${NC}" + fi + ;; + esac + + echo -e "${GRAY}File size: $(du -h "$file" | cut -f1)${NC}" + echo -e "${GRAY}----------------------------------------${NC}" +} + +# Initialize variables +FILE="" +SOBJECT="" +OPERATION="insert" +EXTERNAL_ID="" +TARGET_ORG="" +USE_BULK=false +WAIT_TIME="10" +BATCH_SIZE="10000" +IGNORE_ERRORS=false +VERBOSE=false + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case $1 in + -f|--file) + FILE="$2" + shift 2 + ;; + -s|--sobject) + SOBJECT="$2" + shift 2 + ;; + -o|--operation) + OPERATION="$2" + shift 2 + ;; + -e|--external-id) + EXTERNAL_ID="$2" + shift 2 + ;; + -t|--target-org) + TARGET_ORG="$2" + shift 2 + ;; + --bulk) + USE_BULK=true + shift + ;; + --wait) + WAIT_TIME="$2" + shift 2 + ;; + --batch-size) + BATCH_SIZE="$2" + shift 2 + ;; + --ignore-errors) + IGNORE_ERRORS=true + shift + ;; + -v|--verbose) + VERBOSE=true + shift + ;; + -h|--help) + show_usage + exit 0 + ;; + *) + echo -e "${RED}Unknown option: $1${NC}" + echo "" + show_usage + exit 1 + ;; + esac +done + +# Silently check for Salesforce CLI +if ! check_salesforce_cli; then + run_salesforce_check + exit 1 +fi + +# Validate required parameters +if [[ -z "$FILE" ]]; then + echo -e "${RED}Error: Must specify --file parameter${NC}" + echo "" + echo -e "${YELLOW}Usage examples:${NC}" + echo -e "${GRAY} sf-data-import --file data.csv --sobject Account${NC}" + echo -e "${GRAY} sf-data-import --file contacts.json --sobject Contact --operation upsert --external-id Email${NC}" + echo "" + echo -e "${YELLOW}Use --help for detailed usage information.${NC}" + exit 1 +fi + +if [[ -z "$SOBJECT" ]]; then + echo -e "${RED}Error: Must specify --sobject parameter${NC}" + exit 1 +fi + +# Validate file exists +if [[ ! -f "$FILE" ]]; then + echo -e "${RED}Error: File not found: $FILE${NC}" + exit 1 +fi + +# Validate operation +if [[ "$OPERATION" != "insert" && "$OPERATION" != "update" && "$OPERATION" != "upsert" ]]; then + echo -e "${RED}Error: Operation must be 'insert', 'update', or 'upsert'${NC}" + exit 1 +fi + +# Validate external ID for upsert/update operations +if [[ ("$OPERATION" == "upsert" || "$OPERATION" == "update") && -z "$EXTERNAL_ID" ]]; then + echo -e "${RED}Error: External ID field is required for $OPERATION operations${NC}" + exit 1 +fi + +# Detect and validate file format +FILE_FORMAT=$(detect_file_format "$FILE") +echo -e "${GREEN}Using file: $FILE${NC}" +echo -e "${CYAN}Detected format: $FILE_FORMAT${NC}" + +# Validate file content +case "$FILE_FORMAT" in + csv) + if ! validate_csv_file "$FILE"; then + exit 1 + fi + ;; + json) + if ! validate_json_file "$FILE"; then + exit 1 + fi + ;; +esac + +# Show file preview if verbose +if [[ "$VERBOSE" == true ]]; then + show_file_preview "$FILE" "$FILE_FORMAT" +fi + +# Build the sf command +SF_ARGS=("data" "$OPERATION") + +# Add the file and sobject +SF_ARGS+=("--file" "$FILE") +SF_ARGS+=("--sobject" "$SOBJECT") + +# Add optional parameters +if [[ -n "$TARGET_ORG" ]]; then + SF_ARGS+=("--target-org" "$TARGET_ORG") + echo -e "${CYAN}Target org: $TARGET_ORG${NC}" +fi + +if [[ -n "$EXTERNAL_ID" ]]; then + SF_ARGS+=("--external-id" "$EXTERNAL_ID") + echo -e "${CYAN}External ID field: $EXTERNAL_ID${NC}" +fi + +if [[ "$USE_BULK" == true ]]; then + SF_ARGS+=("--bulk") + echo -e "${YELLOW}Using Bulk API${NC}" +fi + +if [[ "$WAIT_TIME" != "10" ]]; then + SF_ARGS+=("--wait" "$WAIT_TIME") +fi + +if [[ "$USE_BULK" == true && "$BATCH_SIZE" != "10000" ]]; then + SF_ARGS+=("--batch-size" "$BATCH_SIZE") + echo -e "${CYAN}Batch size: $BATCH_SIZE${NC}" +fi + +if [[ "$IGNORE_ERRORS" == true ]]; then + SF_ARGS+=("--ignore-errors") + echo -e "${YELLOW}Ignoring individual record errors${NC}" +fi + +# Add verbose flag if requested +if [[ "$VERBOSE" == true ]]; then + SF_ARGS+=("--verbose") +fi + +# Display import information +echo "" +echo -e "${BLUE}📥 Starting Data Import${NC}" +echo -e "${BLUE}=======================${NC}" +echo -e "${CYAN}Operation: $OPERATION${NC}" +echo -e "${CYAN}sObject: $SOBJECT${NC}" + +# Display the command being run +echo "" +echo -e "${GRAY}Executing: sf ${SF_ARGS[*]}${NC}" +echo "" + +# Execute the command +if sf "${SF_ARGS[@]}"; then + IMPORT_EXIT_CODE=0 +else + IMPORT_EXIT_CODE=$? +fi + +echo "" +if [[ $IMPORT_EXIT_CODE -eq 0 ]]; then + echo -e "${GREEN}✅ Data import completed successfully!${NC}" + + case "$OPERATION" in + insert) + echo -e "${CYAN}📊 Records inserted into $SOBJECT${NC}" + ;; + update) + echo -e "${CYAN}📊 Records updated in $SOBJECT${NC}" + ;; + upsert) + echo -e "${CYAN}📊 Records upserted in $SOBJECT (using $EXTERNAL_ID as external ID)${NC}" + ;; + esac + + if [[ "$VERBOSE" == true ]]; then + echo -e "${YELLOW}💡 Check the output above for detailed results and any warnings${NC}" + fi +else + echo -e "${RED}❌ Data import failed with exit code: $IMPORT_EXIT_CODE${NC}" + echo -e "${YELLOW}💡 Check data format, field mappings, and validation rules${NC}" + exit $IMPORT_EXIT_CODE +fi diff --git a/sf-data-import.ps1 b/sf-data-import.ps1 new file mode 100644 index 0000000..08634c7 --- /dev/null +++ b/sf-data-import.ps1 @@ -0,0 +1,345 @@ +#!/usr/bin/env pwsh + +<# +.SYNOPSIS + Data import wrapper for Salesforce CLI with CSV and JSON support + +.DESCRIPTION + A user-friendly wrapper around 'sf data import' that simplifies data import + to Salesforce orgs with CSV/JSON support, upsert operations, and intelligent validation. + +.PARAMETER File + CSV or JSON file to import + +.PARAMETER SObject + Target sObject type + +.PARAMETER Operation + Operation: insert, update, upsert (default: insert) + +.PARAMETER ExternalId + External ID field for upsert/update operations + +.PARAMETER TargetOrg + Target org username or alias + +.PARAMETER Bulk + Use bulk API for large datasets + +.PARAMETER Wait + Wait time in minutes (default: 10) + +.PARAMETER BatchSize + Batch size for bulk operations (default: 10000) + +.PARAMETER IgnoreErrors + Continue on errors (don't fail entire job) + +.PARAMETER Verbose + Enable verbose output + +.PARAMETER Help + Show this help message + +.EXAMPLE + .\sf-data-import.ps1 -File accounts.csv -SObject Account + .\sf-data-import.ps1 -File contacts.json -SObject Contact -Operation upsert -ExternalId Email + .\sf-data-import.ps1 -File leads.csv -SObject Lead -Bulk -BatchSize 5000 + .\sf-data-import.ps1 -File updates.csv -SObject Account -Operation update -ExternalId AccountNumber + +.NOTES + This script automatically checks for Salesforce CLI installation and runs + diagnostics if the CLI is not found. + + Supported formats: + - CSV files with header row + - JSON files (array of objects or newline-delimited JSON) +#> + +param( + [Parameter(Mandatory)] + [string]$File, + + [Parameter(Mandatory)] + [string]$SObject, + + [ValidateSet("insert", "update", "upsert")] + [string]$Operation = "insert", + + [string]$ExternalId, + [string]$TargetOrg, + [switch]$Bulk, + [int]$Wait = 10, + [int]$BatchSize = 10000, + [switch]$IgnoreErrors, + [switch]$Verbose, + [switch]$Help +) + +# Show help if requested +if ($Help) { + Get-Help $MyInvocation.MyCommand.Path -Detailed + exit 0 +} + +# Function to check if Salesforce CLI is installed +function Test-SalesforceCLI { + try { + $null = Get-Command sf -ErrorAction Stop + return $true + } catch { + return $false + } +} + +# Function to run sf-check diagnostics +function Invoke-SalesforceCheck { + $checkScript = if (Test-Path "sf-check.ps1") { + ".\sf-check.ps1" + } elseif (Test-Path "sf-check.sh") { + "bash sf-check.sh" + } else { + $null + } + + if ($checkScript) { + Write-Host "Running Salesforce CLI diagnostics..." -ForegroundColor Yellow + Invoke-Expression $checkScript + } else { + Write-Host "Salesforce CLI not found and no diagnostic script available." -ForegroundColor Red + Write-Host "Please install the Salesforce CLI: https://developer.salesforce.com/tools/salesforcecli" -ForegroundColor Red + } +} + +# Function to detect file format +function Get-FileFormat { + param([string]$FilePath) + + $extension = [System.IO.Path]::GetExtension($FilePath).ToLower() + + switch ($extension) { + ".csv" { return "csv" } + ".json" { return "json" } + default { + # Try to detect by content + $firstLine = Get-Content $FilePath -First 1 + if ($firstLine -match '^\s*\{.*\}$|^\s*\[') { + return "json" + } else { + return "csv" + } + } + } +} + +# Function to validate CSV file +function Test-CSVFile { + param([string]$FilePath) + + $lines = Get-Content $FilePath + if ($lines.Count -lt 2) { + Write-Host "Error: CSV file must have at least a header and one data row" -ForegroundColor Red + return $false + } + + $headerFields = ($lines[0] -split ',').Count + $firstRowFields = ($lines[1] -split ',').Count + + if ($headerFields -ne $firstRowFields) { + Write-Host "Warning: Header field count ($headerFields) differs from first row ($firstRowFields)" -ForegroundColor Yellow + } + + return $true +} + +# Function to validate JSON file +function Test-JSONFile { + param([string]$FilePath) + + try { + $null = Get-Content $FilePath -Raw | ConvertFrom-Json + return $true + } catch { + Write-Host "Error: Invalid JSON format in file" -ForegroundColor Red + return $false + } +} + +# Function to show file preview +function Show-FilePreview { + param([string]$FilePath, [string]$Format) + + Write-Host "📄 File Preview ($Format):" -ForegroundColor Yellow + Write-Host "----------------------------------------" -ForegroundColor Gray + + switch ($Format) { + "csv" { + $lines = Get-Content $FilePath + Write-Host "Header: $($lines[0])" -ForegroundColor Gray + if ($lines.Count -gt 1) { + Write-Host "Sample: $($lines[1])" -ForegroundColor Gray + } + Write-Host "Records: $($lines.Count - 1)" -ForegroundColor Gray + } + "json" { + try { + $content = Get-Content $FilePath -Raw | ConvertFrom-Json + if ($content -is [array]) { + Write-Host "Array format with $($content.Count) records" -ForegroundColor Gray + if ($content.Count -gt 0) { + $keys = ($content[0] | Get-Member -MemberType NoteProperty).Name -join ", " + Write-Host "Sample keys: $keys" -ForegroundColor Gray + } + } else { + $recordCount = (Get-Content $FilePath).Count + Write-Host "NDJSON format" -ForegroundColor Gray + Write-Host "Records: $recordCount" -ForegroundColor Gray + } + } catch { + Write-Host "Unable to parse JSON preview" -ForegroundColor Gray + } + } + } + + $fileInfo = Get-Item $FilePath + $fileSize = if ($fileInfo.Length -gt 1MB) { + "{0:N1} MB" -f ($fileInfo.Length / 1MB) + } elseif ($fileInfo.Length -gt 1KB) { + "{0:N1} KB" -f ($fileInfo.Length / 1KB) + } else { + "$($fileInfo.Length) bytes" + } + + Write-Host "File size: $fileSize" -ForegroundColor Gray + Write-Host "----------------------------------------" -ForegroundColor Gray +} + +# Silently check for Salesforce CLI +if (-not (Test-SalesforceCLI)) { + Invoke-SalesforceCheck + exit 1 +} + +# Validate file exists +if (-not (Test-Path $File)) { + Write-Host "Error: File not found: $File" -ForegroundColor Red + exit 1 +} + +# Validate external ID for upsert/update operations +if (($Operation -eq "upsert" -or $Operation -eq "update") -and -not $ExternalId) { + Write-Host "Error: External ID field is required for $Operation operations" -ForegroundColor Red + exit 1 +} + +# Detect and validate file format +$fileFormat = Get-FileFormat $File +Write-Host "Using file: $File" -ForegroundColor Green +Write-Host "Detected format: $fileFormat" -ForegroundColor Cyan + +# Validate file content +switch ($fileFormat) { + "csv" { + if (-not (Test-CSVFile $File)) { + exit 1 + } + } + "json" { + if (-not (Test-JSONFile $File)) { + exit 1 + } + } +} + +# Show file preview if verbose +if ($Verbose) { + Show-FilePreview $File $fileFormat +} + +# Build the sf command +$sfArgs = @("data", $Operation, "--file", $File, "--sobject", $SObject) + +# Add optional parameters +if ($TargetOrg) { + $sfArgs += "--target-org" + $sfArgs += $TargetOrg + Write-Host "Target org: $TargetOrg" -ForegroundColor Cyan +} + +if ($ExternalId) { + $sfArgs += "--external-id" + $sfArgs += $ExternalId + Write-Host "External ID field: $ExternalId" -ForegroundColor Cyan +} + +if ($Bulk) { + $sfArgs += "--bulk" + Write-Host "Using Bulk API" -ForegroundColor Yellow +} + +if ($Wait -ne 10) { + $sfArgs += "--wait" + $sfArgs += $Wait.ToString() +} + +if ($Bulk -and $BatchSize -ne 10000) { + $sfArgs += "--batch-size" + $sfArgs += $BatchSize.ToString() + Write-Host "Batch size: $BatchSize" -ForegroundColor Cyan +} + +if ($IgnoreErrors) { + $sfArgs += "--ignore-errors" + Write-Host "Ignoring individual record errors" -ForegroundColor Yellow +} + +# Add verbose flag if requested +if ($Verbose) { + $sfArgs += "--verbose" +} + +# Display import information +Write-Host "" +Write-Host "📥 Starting Data Import" -ForegroundColor Blue +Write-Host "=======================" -ForegroundColor Blue +Write-Host "Operation: $Operation" -ForegroundColor Cyan +Write-Host "sObject: $SObject" -ForegroundColor Cyan + +# Display the command being run +Write-Host "" +Write-Host "Executing: sf $($sfArgs -join ' ')" -ForegroundColor Gray +Write-Host "" + +# Execute the command +try { + & sf @sfArgs + $exitCode = $LASTEXITCODE + + Write-Host "" + if ($exitCode -eq 0) { + Write-Host "✅ Data import completed successfully!" -ForegroundColor Green + + switch ($Operation) { + "insert" { + Write-Host "📊 Records inserted into $SObject" -ForegroundColor Cyan + } + "update" { + Write-Host "📊 Records updated in $SObject" -ForegroundColor Cyan + } + "upsert" { + Write-Host "📊 Records upserted in $SObject (using $ExternalId as external ID)" -ForegroundColor Cyan + } + } + + if ($Verbose) { + Write-Host "💡 Check the output above for detailed results and any warnings" -ForegroundColor Yellow + } + } else { + Write-Host "❌ Data import failed with exit code: $exitCode" -ForegroundColor Red + Write-Host "💡 Check data format, field mappings, and validation rules" -ForegroundColor Yellow + exit $exitCode + } +} catch { + Write-Host "Error executing sf command: $($_.Exception.Message)" -ForegroundColor Red + exit 1 +} diff --git a/sf-logs-tail b/sf-logs-tail new file mode 100755 index 0000000..99a116e --- /dev/null +++ b/sf-logs-tail @@ -0,0 +1,313 @@ +#!/bin/bash + +# Debug logs tail wrapper for Salesforce CLI +# Provides real-time debug log monitoring with filtering and formatting + +# Color codes for output formatting +readonly RED='\033[0;31m' +readonly GREEN='\033[0;32m' +readonly YELLOW='\033[0;33m' +readonly BLUE='\033[0;34m' +readonly CYAN='\033[0;36m' +readonly GRAY='\033[0;37m' +readonly MAGENTA='\033[0;35m' +readonly NC='\033[0m' # No Color + +# Function to display usage information +show_usage() { + echo -e "${BLUE}sf-logs-tail - Debug Logs Tail Wrapper for Salesforce CLI${NC}" + echo "" + echo "USAGE:" + echo " sf-logs-tail [OPTIONS]" + echo "" + echo "OPTIONS:" + echo " -t, --target-org ORG Target org username or alias" + echo " -u, --user-id USER Specific user ID to monitor (default: current user)" + echo " -l, --level LEVEL Log level: ERROR, WARN, INFO, DEBUG, FINE, FINER, FINEST" + echo " --duration MINUTES How long to tail logs in minutes (default: 30)" + echo " --filter PATTERN Filter log entries containing pattern" + echo " --apex-only Show only Apex-related log entries" + echo " --no-colors Disable colored output" + echo " -v, --verbose Enable verbose output with timestamps" + echo " -h, --help Show this help message" + echo "" + echo "EXAMPLES:" + echo " sf-logs-tail # Tail logs for default org" + echo " sf-logs-tail --level DEBUG --duration 60 # Debug level for 1 hour" + echo " sf-logs-tail --filter \"MyClass\" --apex-only # Filter Apex logs for MyClass" + echo " sf-logs-tail --target-org sandbox --user-id USER123 # Specific org and user" + echo "" + echo "KEYBOARD SHORTCUTS:" + echo " Ctrl+C Stop tailing logs and exit" + echo "" + echo "This script automatically checks for Salesforce CLI installation." +} + +# Function to check if Salesforce CLI is installed +check_salesforce_cli() { + if ! command -v sf &> /dev/null; then + return 1 + fi + return 0 +} + +# Function to run sf-check diagnostics +run_salesforce_check() { + if [[ -f "sf-check" ]]; then + echo -e "${YELLOW}Running Salesforce CLI diagnostics...${NC}" + ./sf-check + elif [[ -f "sf-check.sh" ]]; then + echo -e "${YELLOW}Running Salesforce CLI diagnostics...${NC}" + bash sf-check.sh + elif [[ -f "sf-check.ps1" ]]; then + echo -e "${YELLOW}Running Salesforce CLI diagnostics...${NC}" + pwsh sf-check.ps1 + else + echo -e "${RED}Salesforce CLI not found and no diagnostic script available.${NC}" + echo -e "${RED}Please install the Salesforce CLI: https://developer.salesforce.com/tools/salesforcecli${NC}" + fi +} + +# Function to colorize log level +colorize_log_level() { + local level="$1" + case "$level" in + *ERROR*) echo -e "${RED}$level${NC}" ;; + *WARN*) echo -e "${YELLOW}$level${NC}" ;; + *INFO*) echo -e "${GREEN}$level${NC}" ;; + *DEBUG*) echo -e "${CYAN}$level${NC}" ;; + *FINE*) echo -e "${BLUE}$level${NC}" ;; + *APEX*) echo -e "${MAGENTA}$level${NC}" ;; + *) echo "$level" ;; + esac +} + +# Function to format log entry +format_log_entry() { + local line="$1" + local show_colors="$2" + local show_timestamp="$3" + + if [[ "$show_colors" == true ]]; then + # Add timestamp if verbose + if [[ "$show_timestamp" == true ]]; then + local timestamp=$(date '+%H:%M:%S') + echo -e "${GRAY}[$timestamp]${NC} $line" + else + # Try to colorize based on content + if [[ "$line" =~ (ERROR|EXCEPTION|FATAL) ]]; then + echo -e "${RED}$line${NC}" + elif [[ "$line" =~ (WARN|WARNING) ]]; then + echo -e "${YELLOW}$line${NC}" + elif [[ "$line" =~ (DEBUG|FINE) ]]; then + echo -e "${CYAN}$line${NC}" + elif [[ "$line" =~ (APEX|USER_DEBUG) ]]; then + echo -e "${MAGENTA}$line${NC}" + else + echo "$line" + fi + fi + else + echo "$line" + fi +} + +# Function to filter log entry +should_show_log_entry() { + local line="$1" + local filter_pattern="$2" + local apex_only="$3" + + # Apply apex-only filter + if [[ "$apex_only" == true ]]; then + if [[ ! "$line" =~ (APEX|USER_DEBUG|EXCEPTION|METHOD_|CONSTRUCTOR_|DML_|SOQL_|VALIDATION_|FLOW_) ]]; then + return 1 + fi + fi + + # Apply custom filter + if [[ -n "$filter_pattern" ]]; then + if [[ ! "$line" =~ $filter_pattern ]]; then + return 1 + fi + fi + + return 0 +} + +# Function to setup signal handlers +setup_signal_handlers() { + trap 'echo -e "\n${YELLOW}Stopping log tail...${NC}"; exit 0' INT TERM +} + +# Initialize variables +TARGET_ORG="" +USER_ID="" +LOG_LEVEL="" +DURATION="30" +FILTER_PATTERN="" +APEX_ONLY=false +NO_COLORS=false +VERBOSE=false + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case $1 in + -t|--target-org) + TARGET_ORG="$2" + shift 2 + ;; + -u|--user-id) + USER_ID="$2" + shift 2 + ;; + -l|--level) + LOG_LEVEL="$2" + shift 2 + ;; + --duration) + DURATION="$2" + shift 2 + ;; + --filter) + FILTER_PATTERN="$2" + shift 2 + ;; + --apex-only) + APEX_ONLY=true + shift + ;; + --no-colors) + NO_COLORS=true + shift + ;; + -v|--verbose) + VERBOSE=true + shift + ;; + -h|--help) + show_usage + exit 0 + ;; + *) + echo -e "${RED}Unknown option: $1${NC}" + echo "" + show_usage + exit 1 + ;; + esac +done + +# Silently check for Salesforce CLI +if ! check_salesforce_cli; then + run_salesforce_check + exit 1 +fi + +# Validate log level if specified +if [[ -n "$LOG_LEVEL" ]]; then + case "$LOG_LEVEL" in + ERROR|WARN|INFO|DEBUG|FINE|FINER|FINEST) ;; + *) + echo -e "${RED}Error: Invalid log level '$LOG_LEVEL'${NC}" + echo -e "${YELLOW}Valid levels: ERROR, WARN, INFO, DEBUG, FINE, FINER, FINEST${NC}" + exit 1 + ;; + esac +fi + +# Validate duration +if ! [[ "$DURATION" =~ ^[0-9]+$ ]]; then + echo -e "${RED}Error: Duration must be a number (minutes)${NC}" + exit 1 +fi + +# Build the sf command +SF_ARGS=("apex" "tail" "log") + +# Add optional parameters +if [[ -n "$TARGET_ORG" ]]; then + SF_ARGS+=("--target-org" "$TARGET_ORG") +fi + +if [[ -n "$USER_ID" ]]; then + SF_ARGS+=("--user-id" "$USER_ID") +fi + +if [[ -n "$LOG_LEVEL" ]]; then + SF_ARGS+=("--debug-level" "$LOG_LEVEL") +fi + +# Set up signal handlers +setup_signal_handlers + +# Display log tail information +echo -e "${BLUE}📡 Starting Debug Log Tail${NC}" +echo -e "${BLUE}===========================${NC}" + +if [[ -n "$TARGET_ORG" ]]; then + echo -e "${CYAN}Target org: $TARGET_ORG${NC}" +fi + +if [[ -n "$USER_ID" ]]; then + echo -e "${CYAN}User ID: $USER_ID${NC}" +else + echo -e "${CYAN}User: Current user${NC}" +fi + +if [[ -n "$LOG_LEVEL" ]]; then + echo -e "${CYAN}Log level: $(colorize_log_level "$LOG_LEVEL")${NC}" +fi + +echo -e "${CYAN}Duration: $DURATION minutes${NC}" + +if [[ -n "$FILTER_PATTERN" ]]; then + echo -e "${CYAN}Filter: $FILTER_PATTERN${NC}" +fi + +if [[ "$APEX_ONLY" == true ]]; then + echo -e "${YELLOW}Mode: Apex-only logs${NC}" +fi + +if [[ "$VERBOSE" == true ]]; then + echo -e "${YELLOW}Verbose: Enabled (with timestamps)${NC}" +fi + +# Color settings +SHOW_COLORS=true +if [[ "$NO_COLORS" == true ]]; then + SHOW_COLORS=false + echo -e "${GRAY}Colors: Disabled${NC}" +fi + +echo "" +echo -e "${YELLOW}Press Ctrl+C to stop tailing logs${NC}" +echo "" + +# Display the command being run +if [[ "$VERBOSE" == true ]]; then + echo -e "${GRAY}Executing: sf ${SF_ARGS[*]}${NC}" + echo "" +fi + +# Start the log tailing with timeout +timeout "${DURATION}m" sf "${SF_ARGS[@]}" 2>/dev/null | while IFS= read -r line; do + if should_show_log_entry "$line" "$FILTER_PATTERN" "$APEX_ONLY"; then + format_log_entry "$line" "$SHOW_COLORS" "$VERBOSE" + fi +done + +# Check the exit status +EXIT_CODE=${PIPESTATUS[0]} + +echo "" +if [[ $EXIT_CODE -eq 124 ]]; then + echo -e "${YELLOW}⏰ Log tail timed out after $DURATION minutes${NC}" +elif [[ $EXIT_CODE -eq 0 ]]; then + echo -e "${GREEN}✅ Log tail completed successfully${NC}" +else + echo -e "${RED}❌ Log tail failed with exit code: $EXIT_CODE${NC}" + echo -e "${YELLOW}💡 Check org connectivity and user permissions${NC}" +fi + +echo -e "${GRAY}Tip: Use different filters to focus on specific log types${NC}" diff --git a/sf-logs-tail.ps1 b/sf-logs-tail.ps1 new file mode 100644 index 0000000..0f15e04 --- /dev/null +++ b/sf-logs-tail.ps1 @@ -0,0 +1,315 @@ +#!/usr/bin/env pwsh + +<# +.SYNOPSIS + Debug logs tail wrapper for Salesforce CLI with real-time monitoring + +.DESCRIPTION + A user-friendly wrapper around 'sf apex tail log' that provides real-time + debug log monitoring with filtering, formatting, and intelligent output colorization. + +.PARAMETER TargetOrg + Target org username or alias + +.PARAMETER UserId + Specific user ID to monitor (default: current user) + +.PARAMETER Level + Log level: ERROR, WARN, INFO, DEBUG, FINE, FINER, FINEST + +.PARAMETER Duration + How long to tail logs in minutes (default: 30) + +.PARAMETER Filter + Filter log entries containing pattern + +.PARAMETER ApexOnly + Show only Apex-related log entries + +.PARAMETER NoColors + Disable colored output + +.PARAMETER Verbose + Enable verbose output with timestamps + +.PARAMETER Help + Show this help message + +.EXAMPLE + .\sf-logs-tail.ps1 + .\sf-logs-tail.ps1 -Level DEBUG -Duration 60 + .\sf-logs-tail.ps1 -Filter "MyClass" -ApexOnly + .\sf-logs-tail.ps1 -TargetOrg sandbox -UserId USER123 + +.NOTES + This script automatically checks for Salesforce CLI installation and runs + diagnostics if the CLI is not found. + + Use Ctrl+C to stop tailing logs and exit. +#> + +param( + [string]$TargetOrg, + [string]$UserId, + [ValidateSet("ERROR", "WARN", "INFO", "DEBUG", "FINE", "FINER", "FINEST")] + [string]$Level, + [int]$Duration = 30, + [string]$Filter, + [switch]$ApexOnly, + [switch]$NoColors, + [switch]$Verbose, + [switch]$Help +) + +# Show help if requested +if ($Help) { + Get-Help $MyInvocation.MyCommand.Path -Detailed + exit 0 +} + +# Function to check if Salesforce CLI is installed +function Test-SalesforceCLI { + try { + $null = Get-Command sf -ErrorAction Stop + return $true + } catch { + return $false + } +} + +# Function to run sf-check diagnostics +function Invoke-SalesforceCheck { + $checkScript = if (Test-Path "sf-check.ps1") { + ".\sf-check.ps1" + } elseif (Test-Path "sf-check.sh") { + "bash sf-check.sh" + } else { + $null + } + + if ($checkScript) { + Write-Host "Running Salesforce CLI diagnostics..." -ForegroundColor Yellow + Invoke-Expression $checkScript + } else { + Write-Host "Salesforce CLI not found and no diagnostic script available." -ForegroundColor Red + Write-Host "Please install the Salesforce CLI: https://developer.salesforce.com/tools/salesforcecli" -ForegroundColor Red + } +} + +# Function to colorize log level +function Write-ColorizedLogLevel { + param([string]$Level) + + switch -Regex ($Level) { + "ERROR" { return Write-Host $Level -ForegroundColor Red -NoNewline; "" } + "WARN" { return Write-Host $Level -ForegroundColor Yellow -NoNewline; "" } + "INFO" { return Write-Host $Level -ForegroundColor Green -NoNewline; "" } + "DEBUG" { return Write-Host $Level -ForegroundColor Cyan -NoNewline; "" } + "FINE" { return Write-Host $Level -ForegroundColor Blue -NoNewline; "" } + "APEX" { return Write-Host $Level -ForegroundColor Magenta -NoNewline; "" } + default { return $Level } + } +} + +# Function to format log entry +function Write-FormattedLogEntry { + param([string]$Line, [bool]$ShowColors, [bool]$ShowTimestamp) + + if ($ShowColors) { + if ($ShowTimestamp) { + $timestamp = Get-Date -Format "HH:mm:ss" + Write-Host "[$timestamp] " -ForegroundColor Gray -NoNewline + } + + # Colorize based on content + switch -Regex ($Line) { + "(ERROR|EXCEPTION|FATAL)" { + Write-Host $Line -ForegroundColor Red + } + "(WARN|WARNING)" { + Write-Host $Line -ForegroundColor Yellow + } + "(DEBUG|FINE)" { + Write-Host $Line -ForegroundColor Cyan + } + "(APEX|USER_DEBUG)" { + Write-Host $Line -ForegroundColor Magenta + } + default { + Write-Host $Line + } + } + } else { + Write-Host $Line + } +} + +# Function to test if log entry should be shown +function Test-ShowLogEntry { + param([string]$Line, [string]$FilterPattern, [bool]$ApexOnlyMode) + + # Apply apex-only filter + if ($ApexOnlyMode) { + if ($Line -notmatch "(APEX|USER_DEBUG|EXCEPTION|METHOD_|CONSTRUCTOR_|DML_|SOQL_|VALIDATION_|FLOW_)") { + return $false + } + } + + # Apply custom filter + if ($FilterPattern) { + if ($Line -notmatch $FilterPattern) { + return $false + } + } + + return $true +} + +# Function to setup signal handlers +function Set-SignalHandlers { + # PowerShell equivalent of trap - handle Ctrl+C gracefully + $null = Register-EngineEvent PowerShell.Exiting -Action { + Write-Host "" + Write-Host "Stopping log tail..." -ForegroundColor Yellow + } +} + +# Silently check for Salesforce CLI +if (-not (Test-SalesforceCLI)) { + Invoke-SalesforceCheck + exit 1 +} + +# Validate duration +if ($Duration -lt 1) { + Write-Host "Error: Duration must be at least 1 minute" -ForegroundColor Red + exit 1 +} + +# Build the sf command +$sfArgs = @("apex", "tail", "log") + +# Add optional parameters +if ($TargetOrg) { + $sfArgs += "--target-org" + $sfArgs += $TargetOrg +} + +if ($UserId) { + $sfArgs += "--user-id" + $sfArgs += $UserId +} + +if ($Level) { + $sfArgs += "--debug-level" + $sfArgs += $Level +} + +# Set up signal handlers +Set-SignalHandlers + +# Display log tail information +Write-Host "📡 Starting Debug Log Tail" -ForegroundColor Blue +Write-Host "===========================" -ForegroundColor Blue + +if ($TargetOrg) { + Write-Host "Target org: $TargetOrg" -ForegroundColor Cyan +} + +if ($UserId) { + Write-Host "User ID: $UserId" -ForegroundColor Cyan +} else { + Write-Host "User: Current user" -ForegroundColor Cyan +} + +if ($Level) { + Write-Host "Log level: " -ForegroundColor Cyan -NoNewline + Write-ColorizedLogLevel $Level + Write-Host "" +} + +Write-Host "Duration: $Duration minutes" -ForegroundColor Cyan + +if ($Filter) { + Write-Host "Filter: $Filter" -ForegroundColor Cyan +} + +if ($ApexOnly) { + Write-Host "Mode: Apex-only logs" -ForegroundColor Yellow +} + +if ($Verbose) { + Write-Host "Verbose: Enabled (with timestamps)" -ForegroundColor Yellow +} + +# Color settings +$showColors = -not $NoColors +if ($NoColors) { + Write-Host "Colors: Disabled" -ForegroundColor Gray +} + +Write-Host "" +Write-Host "Press Ctrl+C to stop tailing logs" -ForegroundColor Yellow +Write-Host "" + +# Display the command being run +if ($Verbose) { + Write-Host "Executing: sf $($sfArgs -join ' ')" -ForegroundColor Gray + Write-Host "" +} + +# Start the log tailing with timeout +try { + $job = Start-Job -ScriptBlock { + param($sfArgs) + & sf @sfArgs 2>$null + } -ArgumentList $sfArgs + + $timeoutTime = (Get-Date).AddMinutes($Duration) + + while ((Get-Date) -lt $timeoutTime -and $job.State -eq "Running") { + $output = Receive-Job $job + + foreach ($line in $output) { + if (Test-ShowLogEntry $line $Filter $ApexOnly) { + Write-FormattedLogEntry $line $showColors $Verbose + } + } + + Start-Sleep -Milliseconds 100 + } + + # Get any remaining output + $finalOutput = Receive-Job $job + foreach ($line in $finalOutput) { + if (Test-ShowLogEntry $line $Filter $ApexOnly) { + Write-FormattedLogEntry $line $showColors $Verbose + } + } + + $exitCode = 0 + if ($job.State -eq "Running") { + Stop-Job $job + $exitCode = 124 # Timeout exit code + } elseif ($job.State -eq "Failed") { + $exitCode = 1 + } + + Remove-Job $job + + Write-Host "" + if ($exitCode -eq 124) { + Write-Host "⏰ Log tail timed out after $Duration minutes" -ForegroundColor Yellow + } elseif ($exitCode -eq 0) { + Write-Host "✅ Log tail completed successfully" -ForegroundColor Green + } else { + Write-Host "❌ Log tail failed with exit code: $exitCode" -ForegroundColor Red + Write-Host "💡 Check org connectivity and user permissions" -ForegroundColor Yellow + } + + Write-Host "Tip: Use different filters to focus on specific log types" -ForegroundColor Gray + +} catch { + Write-Host "Error during log tail execution: $($_.Exception.Message)" -ForegroundColor Red + exit 1 +} diff --git a/sf-org-create b/sf-org-create new file mode 100755 index 0000000..7fe10f5 --- /dev/null +++ b/sf-org-create @@ -0,0 +1,241 @@ +#!/usr/bin/env bash +set -euo pipefail + +show_help() { + cat <<'EOF' +sf-org-create — wrapper for smart scratch org creation + +USAGE: + sf-org-create -n [-d ] [-f ] [-a ] [-t