added addl wrappers

This commit is contained in:
reynold
2025-08-28 16:34:16 +08:00
parent a385484a69
commit 747aa90d26
17 changed files with 4265 additions and 21 deletions

184
README.md
View File

@@ -6,13 +6,28 @@ A collection of convenient wrapper scripts for common Salesforce CLI operations.
## Overview ## 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-deploy`** - Streamlined wrapper for `sf project deploy start`
- **`sf-dry-run`** - Validation wrapper for `sf project deploy start --dry-run` - **`sf-dry-run`** - Validation wrapper for `sf project deploy start --dry-run`
- **`sf-web-open`** - Quick org browser opener for `sf org open` - **`sf-web-open`** - Quick org browser opener for `sf org open`
- **`sf-check`** - Environment verification tool to check SF CLI installation and configuration - **`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 ## Installation
### Unix/Linux/macOS (Bash) ### Unix/Linux/macOS (Bash)
@@ -20,7 +35,10 @@ This toolkit provides four main utilities:
1. Clone or download this repository to your preferred tools directory 1. Clone or download this repository to your preferred tools directory
2. Make the scripts executable: 2. Make the scripts executable:
```bash ```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: 3. Add the directory to your PATH or create symlinks in a directory that's already in your PATH:
```bash ```bash
@@ -32,6 +50,14 @@ This toolkit provides four main utilities:
ln -s /path/to/sf-cli-wrapper/sf-dry-run /usr/local/bin/sf-dry-run 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-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-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) ### Windows (PowerShell)
@@ -47,14 +73,124 @@ This toolkit provides four main utilities:
# Add C:\path\to\sf-cli-wrapper to your system PATH # Add C:\path\to\sf-cli-wrapper to your system PATH
# Option 2: Create PowerShell aliases (add to your $PROFILE) # 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-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-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-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-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 ## 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 ### sf-deploy
Wrapper for `sf project deploy start` that simplifies deploying multiple source files with optional test execution. 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 ## 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):** ### ✅ **When SF CLI is installed (normal operation):**
```bash ```bash
@@ -265,6 +401,28 @@ sf-web-open.ps1
### Windows Common Workflows ### 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 ```powershell
# 1. First, validate your deployment (specific file) # 1. First, validate your deployment (specific file)
sf-dry-run.ps1 -o "DEMO-ORG" -s "force-app/main/default/classes/MyClass.cls" 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 ## Features
- **Cross-Platform**: Available for both Unix/Linux/macOS (Bash) and Windows (PowerShell) - **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 - **Automatic Environment Verification**: All wrappers auto-check SF CLI and run diagnostics when missing
- **Error Handling**: All scripts include robust error handling - **Consistent UX**: Arguments and output styling are aligned across Bash and PowerShell
- **Help Documentation**: Each script includes comprehensive help accessible with `-h` (both Bash and PowerShell) - **Error Handling**: Robust input validation and actionable errors
- **Flexible Input**: Support for both absolute and repository-relative paths - **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 - **Command Echo**: Shows the actual `sf` command being executed for transparency
- **Multiple Sources**: Easy handling of multiple source directories and test classes - **Focused Workflows**: Deploy, validate, retrieve, test, run Apex, manage orgs, data import/export, and tail logs
- **Parameter Validation**: Scripts validate required parameters and show helpful error messages
## Tips ## Tips

173
sf-apex-run Executable file
View File

@@ -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 <ORG_ALIAS> (-f <FILE> | -c <CODE>) [-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<Account> 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"

244
sf-apex-run.ps1 Normal file
View File

@@ -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), '<temp-file>'
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
}
}

321
sf-data-export Executable file
View File

@@ -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

290
sf-data-export.ps1 Normal file
View File

@@ -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
}

376
sf-data-import Executable file
View File

@@ -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

345
sf-data-import.ps1 Normal file
View File

@@ -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
}

313
sf-logs-tail Executable file
View File

@@ -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}"

315
sf-logs-tail.ps1 Normal file
View File

@@ -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
}

241
sf-org-create Executable file
View File

@@ -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 <ORG_NAME> [-d <DAYS>] [-f <CONFIG_FILE>] [-a <ALIAS>] [-t <TEMPLATE>] [-h]
OPTIONS:
-n Name/alias for the new scratch org (required)
-d Duration in days (default: 7, max: 30)
-f Path to scratch org definition file (default: config/project-scratch-def.json)
-a Set as default org alias after creation
-t Use predefined template (standard, testing, minimal, full)
-h Show this help
EXAMPLES:
1) Create basic scratch org:
sf-org-create -n "MyDevOrg"
2) Create testing org for 1 day:
sf-org-create -n "QuickTest" -d 1 -t testing
3) Create with custom config and set as default:
sf-org-create -n "MainDev" -d 14 -f "config/custom-scratch-def.json" -a
4) Create full-featured org:
sf-org-create -n "FullEnv" -t full -d 30
TEMPLATES:
- standard: Basic scratch org with common features
- testing: Optimized for running tests (minimal data)
- minimal: Bare-bones org for quick tasks
- full: All available features enabled
Notes:
- If no config file is specified, will look for config/project-scratch-def.json
- Templates override config file settings
- Org will be automatically opened in browser after creation
EOF
}
# Default values
ORG_NAME=""
DURATION=7
CONFIG_FILE=""
SET_DEFAULT=false
TEMPLATE=""
if [[ $# -eq 0 ]]; then
show_help
exit 0
fi
while getopts ":n:d:f:at:h" opt; do
case "$opt" in
n) ORG_NAME="$OPTARG" ;;
d) DURATION="$OPTARG" ;;
f) CONFIG_FILE="$OPTARG" ;;
a) SET_DEFAULT=true ;;
t) TEMPLATE="$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_NAME" ]]; then
echo "Error: Org name (-n) is required." >&2
echo
show_help
exit 1
fi
# Validate duration
if ! [[ "$DURATION" =~ ^[0-9]+$ ]] || [[ "$DURATION" -lt 1 ]] || [[ "$DURATION" -gt 30 ]]; then
echo "Error: Duration must be a number between 1 and 30." >&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
# Create temporary config file for templates
create_template_config() {
local template="$1"
local temp_config="/tmp/sf-org-create-$$.json"
case "$template" in
"standard")
cat > "$temp_config" <<EOF
{
"orgName": "Standard Scratch Org",
"edition": "Developer",
"features": ["EnableSetPasswordInApi", "MultiCurrency", "AuthorApex"],
"settings": {
"lightningExperienceSettings": {
"enableS1DesktopEnabled": true
},
"mobileSettings": {
"enableS1EncryptedStoragePref2": false
}
}
}
EOF
;;
"testing")
cat > "$temp_config" <<EOF
{
"orgName": "Testing Scratch Org",
"edition": "Developer",
"features": ["EnableSetPasswordInApi", "AuthorApex"],
"settings": {
"lightningExperienceSettings": {
"enableS1DesktopEnabled": true
}
}
}
EOF
;;
"minimal")
cat > "$temp_config" <<EOF
{
"orgName": "Minimal Scratch Org",
"edition": "Developer",
"features": ["AuthorApex"]
}
EOF
;;
"full")
cat > "$temp_config" <<EOF
{
"orgName": "Full-Featured Scratch Org",
"edition": "Enterprise",
"features": [
"EnableSetPasswordInApi", "MultiCurrency", "AuthorApex",
"ContactsToMultipleAccounts", "ServiceCloud", "SalesCloud",
"Communities", "Sites", "PersonAccounts"
],
"settings": {
"lightningExperienceSettings": {
"enableS1DesktopEnabled": true
},
"mobileSettings": {
"enableS1EncryptedStoragePref2": false
},
"enhancedEmailSettings": {
"enableRestrictTlsAndRequireSSL": true
}
}
}
EOF
;;
*)
echo "Error: Unknown template '$template'. Available: standard, testing, minimal, full" >&2
exit 1
;;
esac
echo "$temp_config"
}
# Determine config file to use
FINAL_CONFIG=""
if [[ -n "$TEMPLATE" ]]; then
echo "📝 Creating scratch org definition from template: $TEMPLATE"
FINAL_CONFIG=$(create_template_config "$TEMPLATE")
elif [[ -n "$CONFIG_FILE" ]]; then
if [[ ! -f "$CONFIG_FILE" ]]; then
echo "Error: Config file '$CONFIG_FILE' not found." >&2
exit 1
fi
FINAL_CONFIG="$CONFIG_FILE"
elif [[ -f "config/project-scratch-def.json" ]]; then
echo "📝 Using default config: config/project-scratch-def.json"
FINAL_CONFIG="config/project-scratch-def.json"
else
echo "⚠️ No config file found, creating minimal scratch org"
FINAL_CONFIG=$(create_template_config "minimal")
fi
# Build the command
CMD=(sf org create scratch)
CMD+=(--definition-file "$FINAL_CONFIG")
CMD+=(--duration-days "$DURATION")
CMD+=(--alias "$ORG_NAME")
# Set as default if requested
if [[ "$SET_DEFAULT" == "true" ]]; then
CMD+=(--set-default)
fi
echo "🚀 Creating scratch org '$ORG_NAME' for $DURATION days..."
echo ">>> Running: ${CMD[*]}"
echo
if "${CMD[@]}"; then
echo
echo "✅ Scratch org '$ORG_NAME' created successfully!"
# Clean up temp config if we created one
if [[ -n "$TEMPLATE" ]]; then
rm -f "$FINAL_CONFIG"
fi
# Open the org
echo "🌐 Opening org in browser..."
sf org open --target-org "$ORG_NAME" || echo "⚠️ Could not open org in browser"
# Show org info
echo
echo "📊 Org Information:"
sf org display --target-org "$ORG_NAME" || echo "Could not display org info"
else
# Clean up temp config if we created one
if [[ -n "$TEMPLATE" ]]; then
rm -f "$FINAL_CONFIG"
fi
echo "❌ Failed to create scratch org"
exit 1
fi

253
sf-org-create.ps1 Normal file
View File

@@ -0,0 +1,253 @@
param(
[string]$n = "",
[int]$d = 7,
[string]$f = "",
[switch]$a,
[string]$t = "",
[switch]$h
)
function Show-Help {
@"
sf-org-create.ps1 wrapper for smart scratch org creation
USAGE:
sf-org-create.ps1 -n <ORG_NAME> [-d <DAYS>] [-f <CONFIG_FILE>] [-a] [-t <TEMPLATE>] [-h]
OPTIONS:
-n Name/alias for the new scratch org (required)
-d Duration in days (default: 7, max: 30)
-f Path to scratch org definition file (default: config/project-scratch-def.json)
-a Set as default org alias after creation
-t Use predefined template (standard, testing, minimal, full)
-h Show this help
EXAMPLES:
1) Create basic scratch org:
sf-org-create.ps1 -n "MyDevOrg"
2) Create testing org for 1 day:
sf-org-create.ps1 -n "QuickTest" -d 1 -t testing
3) Create with custom config and set as default:
sf-org-create.ps1 -n "MainDev" -d 14 -f "config/custom-scratch-def.json" -a
4) Create full-featured org:
sf-org-create.ps1 -n "FullEnv" -t full -d 30
TEMPLATES:
- standard: Basic scratch org with common features
- testing: Optimized for running tests (minimal data)
- minimal: Bare-bones org for quick tasks
- full: All available features enabled
Notes:
- If no config file is specified, will look for config/project-scratch-def.json
- Templates override config file settings
- Org will be automatically opened in browser after creation
"@
}
# Show help if requested or no parameters
if ($h -or ($n -eq "" -and $f -eq "" -and $t -eq "")) {
Show-Help
exit 0
}
# Validate required parameters
if ($n -eq "") {
Write-Error "Error: Org name (-n) is required."
Write-Host ""
Show-Help
exit 1
}
# Validate duration
if ($d -lt 1 -or $d -gt 30) {
Write-Error "Error: Duration must be between 1 and 30 days."
exit 1
}
# Silent environment check
try {
Get-Command sf -ErrorAction Stop | Out-Null
}
catch {
Write-Host "❌ Salesforce CLI (sf) not found!" -ForegroundColor Red
Write-Host ""
Write-Host "Running environment check to help you get started..."
Write-Host ""
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$sfCheckPath = Join-Path $scriptDir "sf-check.ps1"
if (Test-Path $sfCheckPath) {
& $sfCheckPath
}
elseif (Get-Command sf-check.ps1 -ErrorAction SilentlyContinue) {
sf-check.ps1
}
else {
Write-Host "sf-check.ps1 not found. Please install the Salesforce CLI from:"
Write-Host "https://developer.salesforce.com/tools/sfdxcli"
}
exit 1
}
# Function to create template configs
function New-TemplateConfig {
param([string]$Template)
$tempConfig = Join-Path $env:TEMP "sf-org-create-$(Get-Random).json"
switch ($Template) {
"standard" {
@{
orgName = "Standard Scratch Org"
edition = "Developer"
features = @("EnableSetPasswordInApi", "MultiCurrency", "AuthorApex")
settings = @{
lightningExperienceSettings = @{
enableS1DesktopEnabled = $true
}
mobileSettings = @{
enableS1EncryptedStoragePref2 = $false
}
}
} | ConvertTo-Json -Depth 4 | Out-File $tempConfig -Encoding UTF8
}
"testing" {
@{
orgName = "Testing Scratch Org"
edition = "Developer"
features = @("EnableSetPasswordInApi", "AuthorApex")
settings = @{
lightningExperienceSettings = @{
enableS1DesktopEnabled = $true
}
}
} | ConvertTo-Json -Depth 4 | Out-File $tempConfig -Encoding UTF8
}
"minimal" {
@{
orgName = "Minimal Scratch Org"
edition = "Developer"
features = @("AuthorApex")
} | ConvertTo-Json -Depth 4 | Out-File $tempConfig -Encoding UTF8
}
"full" {
@{
orgName = "Full-Featured Scratch Org"
edition = "Enterprise"
features = @(
"EnableSetPasswordInApi", "MultiCurrency", "AuthorApex",
"ContactsToMultipleAccounts", "ServiceCloud", "SalesCloud",
"Communities", "Sites", "PersonAccounts"
)
settings = @{
lightningExperienceSettings = @{
enableS1DesktopEnabled = $true
}
mobileSettings = @{
enableS1EncryptedStoragePref2 = $false
}
enhancedEmailSettings = @{
enableRestrictTlsAndRequireSSL = $true
}
}
} | ConvertTo-Json -Depth 4 | Out-File $tempConfig -Encoding UTF8
}
default {
Write-Error "Error: Unknown template '$Template'. Available: standard, testing, minimal, full"
exit 1
}
}
return $tempConfig
}
# Determine config file to use
$finalConfig = ""
$tempConfigCreated = $false
if ($t -ne "") {
Write-Host "📝 Creating scratch org definition from template: $t"
$finalConfig = New-TemplateConfig -Template $t
$tempConfigCreated = $true
}
elseif ($f -ne "") {
if (-not (Test-Path $f)) {
Write-Error "Error: Config file '$f' not found."
exit 1
}
$finalConfig = $f
}
elseif (Test-Path "config/project-scratch-def.json") {
Write-Host "📝 Using default config: config/project-scratch-def.json"
$finalConfig = "config/project-scratch-def.json"
}
else {
Write-Host "⚠️ No config file found, creating minimal scratch org"
$finalConfig = New-TemplateConfig -Template "minimal"
$tempConfigCreated = $true
}
# Build the command
$cmd = @("sf", "org", "create", "scratch")
$cmd += "--definition-file"
$cmd += $finalConfig
$cmd += "--duration-days"
$cmd += $d.ToString()
$cmd += "--alias"
$cmd += $n
# Set as default if requested
if ($a) {
$cmd += "--set-default"
}
Write-Host "🚀 Creating scratch org '$n' for $d days..."
Write-Host ">>> Running: $($cmd -join ' ')" -ForegroundColor Yellow
Write-Host ""
try {
& $cmd[0] $cmd[1..($cmd.Length-1)]
if ($LASTEXITCODE -eq 0) {
Write-Host ""
Write-Host "✅ Scratch org '$n' created successfully!" -ForegroundColor Green
# Open the org
Write-Host "🌐 Opening org in browser..."
try {
& sf org open --target-org $n
}
catch {
Write-Host "⚠️ Could not open org in browser" -ForegroundColor Yellow
}
# Show org info
Write-Host ""
Write-Host "📊 Org Information:"
try {
& sf org display --target-org $n
}
catch {
Write-Host "Could not display org info"
}
}
else {
Write-Host "❌ Failed to create scratch org" -ForegroundColor Red
exit 1
}
}
catch {
Write-Error "Failed to execute sf command: $_"
exit 1
}
finally {
# Clean up temp config if we created one
if ($tempConfigCreated -and (Test-Path $finalConfig)) {
Remove-Item $finalConfig -Force
}
}

166
sf-org-info Executable file
View File

@@ -0,0 +1,166 @@
#!/usr/bin/env bash
set -euo pipefail
show_help() {
cat <<'EOF'
sf-org-info — wrapper for quick org information display
USAGE:
sf-org-info [-o <ORG_ALIAS>] [-v] [-l] [-h]
OPTIONS:
-o Org alias or username (if not provided, uses default org)
-v Verbose output (show detailed information)
-l List all authenticated orgs
-h Show this help
EXAMPLES:
1) Show default org info:
sf-org-info
2) Show specific org info:
sf-org-info -o DEMO-ORG
3) Show detailed org information:
sf-org-info -o DEMO-ORG -v
4) List all authenticated orgs:
sf-org-info -l
DISPLAYED INFORMATION:
- Org name and ID
- Username and user info
- Instance URL and login URL
- Org type and edition
- API version and features
- Limits and usage (verbose mode)
- Connected app info (verbose mode)
Notes:
- If no org is specified, uses the default org
- Verbose mode shows limits, features, and additional details
- List mode shows all orgs you're authenticated to
EOF
}
# Default values
ORG=""
VERBOSE=false
LIST_ORGS=false
while getopts ":o:vlh" opt; do
case "$opt" in
o) ORG="$OPTARG" ;;
v) VERBOSE=true ;;
l) LIST_ORGS=true ;;
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
# 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
# Function to display org list
show_org_list() {
echo "📋 Authenticated Organizations:"
echo "=============================="
if sf org list --json >/dev/null 2>&1; then
sf org list 2>/dev/null || {
echo "No authenticated orgs found."
echo "Run 'sf org login web' to authenticate to an org."
return 1
}
else
echo "❌ Unable to list orgs"
return 1
fi
}
# Function to display org information
show_org_info() {
local target_org="$1"
local cmd_args=()
if [[ -n "$target_org" ]]; then
cmd_args+=(--target-org "$target_org")
echo "🏢 Organization Information: $target_org"
else
echo "🏢 Default Organization Information:"
fi
echo "======================================="
# Get basic org info
if sf org display "${cmd_args[@]}" --json >/dev/null 2>&1; then
echo
echo "📊 Basic Information:"
sf org display "${cmd_args[@]}" 2>/dev/null || {
echo "❌ Unable to retrieve org information"
return 1
}
else
echo "❌ Unable to retrieve org information"
echo "Make sure you're authenticated and the org exists."
return 1
fi
# Show verbose information if requested
if [[ "$VERBOSE" == "true" ]]; then
echo
echo "📈 Org Limits:"
echo "-------------"
if sf org list limits "${cmd_args[@]}" >/dev/null 2>&1; then
sf org list limits "${cmd_args[@]}" 2>/dev/null | head -20 || echo "Unable to retrieve org limits"
else
echo "Unable to retrieve org limits"
fi
echo
echo "⚙️ Org Shape (if available):"
echo "-----------------------------"
if sf org list shape "${cmd_args[@]}" >/dev/null 2>&1; then
sf org list shape "${cmd_args[@]}" 2>/dev/null || echo "No org shapes available"
else
echo "No org shapes available"
fi
fi
}
# Handle list orgs option
if [[ "$LIST_ORGS" == "true" ]]; then
show_org_list
exit $?
fi
# Show org information
if ! show_org_info "$ORG"; then
exit 1
fi
echo
echo "✅ Org information displayed successfully!"
# Show helpful commands
echo
echo "💡 Helpful commands:"
echo " - Open this org: sf org open${ORG:+ --target-org \"$ORG\"}"
echo " - List all orgs: sf-org-info -l"
echo " - Detailed info: sf-org-info${ORG:+ -o \"$ORG\"} -v"

204
sf-org-info.ps1 Normal file
View File

@@ -0,0 +1,204 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Quick org information display wrapper for Salesforce CLI
.DESCRIPTION
A user-friendly wrapper around Salesforce CLI org commands that provides
quick access to org information, limits, and authentication status with
clean, formatted output.
.PARAMETER TargetOrg
Target org username or alias (uses default if not specified)
.PARAMETER Limits
Show detailed org limits information
.PARAMETER ListOrgs
List all authenticated orgs
.PARAMETER Verbose
Enable verbose output with additional details
.PARAMETER Help
Show this help message
.EXAMPLE
.\sf-org-info.ps1
.\sf-org-info.ps1 -TargetOrg "production"
.\sf-org-info.ps1 -Limits
.\sf-org-info.ps1 -ListOrgs
.NOTES
This script automatically checks for Salesforce CLI installation and runs
diagnostics if the CLI is not found.
#>
param(
[string]$TargetOrg,
[switch]$Limits,
[switch]$ListOrgs,
[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 execute sf commands with error handling
function Invoke-SafeSfCommand {
param([string[]]$Arguments)
try {
Write-Host "Executing: sf $($Arguments -join ' ')" -ForegroundColor Gray
Write-Host ""
& sf @Arguments
$exitCode = $LASTEXITCODE
if ($exitCode -ne 0) {
Write-Host ""
Write-Host "❌ Command failed with exit code: $exitCode" -ForegroundColor Red
return $false
}
return $true
} catch {
Write-Host "Error executing sf command: $($_.Exception.Message)" -ForegroundColor Red
return $false
}
}
# Function to display section header
function Write-SectionHeader {
param([string]$Title)
Write-Host ""
Write-Host "🔍 $Title" -ForegroundColor Blue
Write-Host ("=" * (4 + $Title.Length)) -ForegroundColor Blue
}
# Silently check for Salesforce CLI
if (-not (Test-SalesforceCLI)) {
Invoke-SalesforceCheck
exit 1
}
# If ListOrgs is specified, show all authenticated orgs and exit
if ($ListOrgs) {
Write-SectionHeader "Authenticated Orgs"
$success = Invoke-SafeSfCommand @("org", "list")
if ($success) {
Write-Host ""
Write-Host "✅ Listed all authenticated orgs" -ForegroundColor Green
}
exit 0
}
# Build the base sf command for org display
$sfArgs = @("org", "display")
# 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 org information
Write-SectionHeader "Organization Information"
$success = Invoke-SafeSfCommand $sfArgs
if (-not $success) {
exit 1
}
# If limits are requested, show org limits
if ($Limits) {
Write-SectionHeader "Organization Limits"
# Build limits command
$limitsArgs = @("org", "list", "limits")
if ($TargetOrg) {
$limitsArgs += "--target-org"
$limitsArgs += $TargetOrg
}
$success = Invoke-SafeSfCommand $limitsArgs
if (-not $success) {
Write-Host "Warning: Could not retrieve org limits" -ForegroundColor Yellow
}
}
# Show additional org context if verbose
if ($Verbose) {
Write-SectionHeader "Additional Context"
# Show all authenticated orgs for context
Write-Host "📋 All Authenticated Orgs:" -ForegroundColor Yellow
$success = Invoke-SafeSfCommand @("org", "list")
if ($success) {
Write-Host ""
}
# Try to show current user info
Write-Host "👤 Current User Info:" -ForegroundColor Yellow
$userArgs = @("org", "display", "user")
if ($TargetOrg) {
$userArgs += "--target-org"
$userArgs += $TargetOrg
}
$success = Invoke-SafeSfCommand $userArgs
}
# Final success message
Write-Host ""
Write-Host "✅ Organization information retrieved successfully!" -ForegroundColor Green
if (-not $Limits -and -not $Verbose) {
Write-Host ""
Write-Host "💡 Tip: Use -Limits to see org limits, -Verbose for more details, or -ListOrgs to see all authenticated orgs" -ForegroundColor Yellow
}

204
sf-retrieve Executable file
View File

@@ -0,0 +1,204 @@
#!/usr/bin/env bash
set -euo pipefail
show_help() {
cat <<'EOF'
sf-retrieve — wrapper for streamlined metadata retrieval
USAGE:
sf-retrieve -o <ORG_ALIAS> (-t <TYPES> | -m <MANIFEST> | -p <PACKAGE>) [-n <NAMES>] [-d <DIR>] [-h]
OPTIONS:
-o Org alias or username to retrieve from (required)
-t Comma-separated metadata types (ApexClass,CustomObject,Flow,etc.)
-n Comma-separated component names (optional, works with -t)
-m Path to manifest file (package.xml)
-p Package name to retrieve
-d Target directory for retrieved metadata (default: force-app)
-h Show this help
EXAMPLES:
1) Retrieve all Apex classes:
sf-retrieve -o PROD-ORG -t "ApexClass"
2) Retrieve specific classes:
sf-retrieve -o PROD-ORG -t "ApexClass" -n "MyClass,AnotherClass"
3) Retrieve multiple metadata types:
sf-retrieve -o PROD-ORG -t "ApexClass,CustomObject,Flow"
4) Retrieve using manifest:
sf-retrieve -o PROD-ORG -m "manifest/package.xml"
5) Retrieve to specific directory:
sf-retrieve -o PROD-ORG -t "ApexClass" -d "retrieved-metadata"
6) Retrieve installed package:
sf-retrieve -o PROD-ORG -p "MyPackage"
COMMON METADATA TYPES:
- ApexClass, ApexTrigger, ApexComponent, ApexPage
- CustomObject, CustomField, CustomTab
- Flow, ProcessBuilder, WorkflowRule
- Layout, Profile, PermissionSet
- StaticResource, ContentAsset
- CustomApplication, CustomLabel
Notes:
- Use -t with -n to retrieve specific components of a type
- Use -m for complex retrievals with manifest files
- Use -p to retrieve entire packages
- Retrieved metadata will be in source format
EOF
}
# Default values
ORG=""
TYPES=""
NAMES=""
MANIFEST=""
PACKAGE=""
TARGET_DIR="force-app"
if [[ $# -eq 0 ]]; then
show_help
exit 0
fi
while getopts ":o:t:n:m:p:d:h" opt; do
case "$opt" in
o) ORG="$OPTARG" ;;
t) TYPES="$OPTARG" ;;
n) NAMES="$OPTARG" ;;
m) MANIFEST="$OPTARG" ;;
p) PACKAGE="$OPTARG" ;;
d) TARGET_DIR="$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 at least one retrieval method is specified
if [[ -z "$TYPES" && -z "$MANIFEST" && -z "$PACKAGE" ]]; then
echo "Error: Must specify -t (types), -m (manifest), or -p (package)." >&2
echo
show_help
exit 1
fi
# Validate that only one retrieval method is used
method_count=0
[[ -n "$TYPES" ]] && ((method_count++))
[[ -n "$MANIFEST" ]] && ((method_count++))
[[ -n "$PACKAGE" ]] && ((method_count++))
if [[ $method_count -gt 1 ]]; then
echo "Error: Cannot use -t, -m, and -p together. Choose one method." >&2
echo
show_help
exit 1
fi
# Validate manifest file exists
if [[ -n "$MANIFEST" && ! -f "$MANIFEST" ]]; then
echo "Error: Manifest file '$MANIFEST' 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 project retrieve start)
CMD+=(--target-org "$ORG")
# Add retrieval method
if [[ -n "$TYPES" ]]; then
# Split types by comma and add each as separate --metadata parameter
IFS=',' read -ra TYPES_ARR <<< "$TYPES"
for TYPE in "${TYPES_ARR[@]}"; do
TYPE=$(echo "$TYPE" | xargs) # Trim whitespace
if [[ -n "$TYPE" ]]; then
if [[ -n "$NAMES" ]]; then
# If names are specified, add each name for this type
IFS=',' read -ra NAMES_ARR <<< "$NAMES"
for NAME in "${NAMES_ARR[@]}"; do
NAME=$(echo "$NAME" | xargs) # Trim whitespace
if [[ -n "$NAME" ]]; then
CMD+=(--metadata "$TYPE:$NAME")
fi
done
else
# No names specified, retrieve all of this type
CMD+=(--metadata "$TYPE")
fi
fi
done
elif [[ -n "$MANIFEST" ]]; then
CMD+=(--manifest "$MANIFEST")
elif [[ -n "$PACKAGE" ]]; then
CMD+=(--package-name "$PACKAGE")
fi
# Add target directory
CMD+=(--output-dir "$TARGET_DIR")
# Show what we're retrieving
echo "🔄 Retrieving metadata from org '$ORG'..."
if [[ -n "$TYPES" ]]; then
echo " Types: $TYPES"
[[ -n "$NAMES" ]] && echo " Names: $NAMES"
elif [[ -n "$MANIFEST" ]]; then
echo " Manifest: $MANIFEST"
elif [[ -n "$PACKAGE" ]]; then
echo " Package: $PACKAGE"
fi
echo " Target: $TARGET_DIR"
echo
echo ">>> Running: ${CMD[*]}"
echo
if "${CMD[@]}"; then
echo
echo "✅ Metadata retrieved successfully!"
echo "📁 Check the '$TARGET_DIR' directory for retrieved components."
# Show summary of what was retrieved
if command -v find >/dev/null 2>&1 && [[ -d "$TARGET_DIR" ]]; then
echo
echo "📊 Retrieved components summary:"
find "$TARGET_DIR" -type f -name "*.xml" -o -name "*.cls" -o -name "*.trigger" -o -name "*.js" -o -name "*.html" | \
sed 's|.*/||' | sort | uniq -c | sort -nr | head -10
fi
else
echo
echo "❌ Failed to retrieve metadata"
exit 1
fi

194
sf-retrieve.ps1 Normal file
View File

@@ -0,0 +1,194 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Streamlined metadata retrieval wrapper for Salesforce CLI
.DESCRIPTION
A user-friendly wrapper around 'sf project retrieve' commands that simplifies
metadata retrieval from Salesforce orgs with intelligent defaults and validation.
.PARAMETER MetadataTypes
Comma-separated list of metadata types to retrieve (e.g., "ApexClass,CustomObject")
.PARAMETER Manifest
Path to package.xml or manifest file for retrieval
.PARAMETER Package
Package name for package-based retrieval
.PARAMETER OutputDir
Output directory for retrieved metadata (default: current directory)
.PARAMETER TargetOrg
Target org username or alias (uses default if not specified)
.PARAMETER Wait
Wait time in minutes for the retrieve operation (default: 10)
.PARAMETER Verbose
Enable verbose output
.PARAMETER Help
Show this help message
.EXAMPLE
.\sf-retrieve.ps1 -MetadataTypes "ApexClass,CustomObject"
.\sf-retrieve.ps1 -Manifest "manifest/package.xml"
.\sf-retrieve.ps1 -Package "MyPackage" -TargetOrg "myorg"
.\sf-retrieve.ps1 -MetadataTypes "Flow" -OutputDir "./retrieved" -Verbose
.NOTES
This script automatically checks for Salesforce CLI installation and runs
diagnostics if the CLI is not found.
#>
param(
[Parameter(ParameterSetName="Types")]
[string]$MetadataTypes,
[Parameter(ParameterSetName="Manifest")]
[string]$Manifest,
[Parameter(ParameterSetName="Package")]
[string]$Package,
[string]$OutputDir,
[string]$TargetOrg,
[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
}
}
# Silently check for Salesforce CLI
if (-not (Test-SalesforceCLI)) {
Invoke-SalesforceCheck
exit 1
}
# Validate that exactly one retrieval method is specified
$methodCount = @($MetadataTypes, $Manifest, $Package | Where-Object { $_ }).Count
if ($methodCount -eq 0) {
Write-Host "Error: Must specify one of: -MetadataTypes, -Manifest, or -Package" -ForegroundColor Red
Write-Host ""
Write-Host "Usage examples:" -ForegroundColor Yellow
Write-Host " .\sf-retrieve.ps1 -MetadataTypes `"ApexClass,CustomObject`"" -ForegroundColor Gray
Write-Host " .\sf-retrieve.ps1 -Manifest `"manifest/package.xml`"" -ForegroundColor Gray
Write-Host " .\sf-retrieve.ps1 -Package `"MyPackage`"" -ForegroundColor Gray
Write-Host ""
Write-Host "Use -Help for detailed usage information." -ForegroundColor Yellow
exit 1
}
if ($methodCount -gt 1) {
Write-Host "Error: Can only specify one of: -MetadataTypes, -Manifest, or -Package" -ForegroundColor Red
exit 1
}
# Validate manifest file exists if specified
if ($Manifest -and -not (Test-Path $Manifest)) {
Write-Host "Error: Manifest file not found: $Manifest" -ForegroundColor Red
exit 1
}
# Build the sf command
$sfArgs = @("project", "retrieve", "start")
# Add retrieval method
if ($MetadataTypes) {
$sfArgs += "--metadata"
$sfArgs += $MetadataTypes
Write-Host "Retrieving metadata types: $MetadataTypes" -ForegroundColor Green
} elseif ($Manifest) {
$sfArgs += "--manifest"
$sfArgs += $Manifest
Write-Host "Retrieving from manifest: $Manifest" -ForegroundColor Green
} elseif ($Package) {
$sfArgs += "--package-name"
$sfArgs += $Package
Write-Host "Retrieving package: $Package" -ForegroundColor Green
}
# Add optional parameters
if ($OutputDir) {
if (-not (Test-Path $OutputDir)) {
Write-Host "Creating output directory: $OutputDir" -ForegroundColor Yellow
New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
}
$sfArgs += "--output-dir"
$sfArgs += $OutputDir
}
if ($TargetOrg) {
$sfArgs += "--target-org"
$sfArgs += $TargetOrg
Write-Host "Target org: $TargetOrg" -ForegroundColor Cyan
}
if ($Wait -ne 10) {
$sfArgs += "--wait"
$sfArgs += $Wait.ToString()
}
# Add verbose flag if requested
if ($Verbose) {
$sfArgs += "--verbose"
}
# 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
if ($exitCode -eq 0) {
Write-Host ""
Write-Host "✅ Metadata retrieval completed successfully!" -ForegroundColor Green
} else {
Write-Host ""
Write-Host "❌ Metadata retrieval failed with exit code: $exitCode" -ForegroundColor Red
exit $exitCode
}
} catch {
Write-Host "Error executing sf command: $($_.Exception.Message)" -ForegroundColor Red
exit 1
}

218
sf-test-run Executable file
View File

@@ -0,0 +1,218 @@
#!/usr/bin/env bash
set -euo pipefail
show_help() {
cat <<'EOF'
sf-test-run — wrapper for focused Apex test execution
USAGE:
sf-test-run -o <ORG_ALIAS> (-c <CLASSES> | -s <SUITES> | -a) [-l <LEVEL>] [-r] [-w <WAIT>] [-h]
OPTIONS:
-o Org alias or username to run tests in (required)
-c Comma-separated test class names
-s Comma-separated test suite names
-a Run all tests in the org
-l Test level (RunLocalTests, RunAllTestsInOrg, RunSpecifiedTests)
-r Generate code coverage report
-w Wait time in minutes (default: 10)
-h Show this help
EXAMPLES:
1) Run specific test classes:
sf-test-run -o DEMO-ORG -c "MyTestClass,AnotherTestClass"
2) Run test suites:
sf-test-run -o DEMO-ORG -s "UnitTests,IntegrationTests"
3) Run all local tests with coverage:
sf-test-run -o DEMO-ORG -l RunLocalTests -r
4) Run all tests in org (be careful!):
sf-test-run -o DEMO-ORG -a -w 30
5) Quick test with custom wait time:
sf-test-run -o DEMO-ORG -c "QuickTest" -w 5
TEST LEVELS:
- RunLocalTests: Run all tests in your org except managed package tests
- RunAllTestsInOrg: Run all tests including managed package tests
- RunSpecifiedTests: Run only specified test classes/suites
Notes:
- Default wait time is 10 minutes
- Use -r to generate detailed coverage reports
- RunAllTestsInOrg can take a very long time
- Test results will be displayed in formatted output
EOF
}
# Default values
ORG=""
CLASSES=""
SUITES=""
ALL_TESTS=false
TEST_LEVEL=""
COVERAGE=false
WAIT_TIME=10
if [[ $# -eq 0 ]]; then
show_help
exit 0
fi
while getopts ":o:c:s:al:rw:h" opt; do
case "$opt" in
o) ORG="$OPTARG" ;;
c) CLASSES="$OPTARG" ;;
s) SUITES="$OPTARG" ;;
a) ALL_TESTS=true ;;
l) TEST_LEVEL="$OPTARG" ;;
r) COVERAGE=true ;;
w) WAIT_TIME="$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 at least one test method is specified
if [[ -z "$CLASSES" && -z "$SUITES" && "$ALL_TESTS" != "true" && -z "$TEST_LEVEL" ]]; then
echo "Error: Must specify -c (classes), -s (suites), -a (all tests), or -l (test level)." >&2
echo
show_help
exit 1
fi
# Validate wait time
if ! [[ "$WAIT_TIME" =~ ^[0-9]+$ ]] || [[ "$WAIT_TIME" -lt 1 ]]; then
echo "Error: Wait time must be a positive number." >&2
exit 1
fi
# Validate test level if specified
if [[ -n "$TEST_LEVEL" ]]; then
case "$TEST_LEVEL" in
"RunLocalTests"|"RunAllTestsInOrg"|"RunSpecifiedTests")
# Valid test levels
;;
*)
echo "Error: Invalid test level. Use RunLocalTests, RunAllTestsInOrg, or RunSpecifiedTests." >&2
exit 1
;;
esac
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 test)
CMD+=(--target-org "$ORG")
CMD+=(--wait "$WAIT_TIME")
CMD+=(--result-format human)
# Determine test execution method
if [[ -n "$TEST_LEVEL" ]]; then
CMD+=(--test-level "$TEST_LEVEL")
elif [[ "$ALL_TESTS" == "true" ]]; then
CMD+=(--test-level "RunAllTestsInOrg")
elif [[ -n "$CLASSES" ]]; then
IFS=',' read -ra CLASSES_ARR <<< "$CLASSES"
for CLASS in "${CLASSES_ARR[@]}"; do
CLASS=$(echo "$CLASS" | xargs) # Trim whitespace
[[ -n "$CLASS" ]] && CMD+=(--tests "$CLASS")
done
elif [[ -n "$SUITES" ]]; then
IFS=',' read -ra SUITES_ARR <<< "$SUITES"
for SUITE in "${SUITES_ARR[@]}"; do
SUITE=$(echo "$SUITE" | xargs) # Trim whitespace
[[ -n "$SUITE" ]] && CMD+=(--suites "$SUITE")
done
fi
# Add code coverage if requested
if [[ "$COVERAGE" == "true" ]]; then
CMD+=(--code-coverage)
fi
# Show what we're running
echo "🧪 Running Apex tests in org '$ORG'..."
if [[ -n "$TEST_LEVEL" ]]; then
echo " Level: $TEST_LEVEL"
elif [[ "$ALL_TESTS" == "true" ]]; then
echo " Level: RunAllTestsInOrg (all tests)"
elif [[ -n "$CLASSES" ]]; then
echo " Classes: $CLASSES"
elif [[ -n "$SUITES" ]]; then
echo " Suites: $SUITES"
fi
echo " Wait time: ${WAIT_TIME} minutes"
[[ "$COVERAGE" == "true" ]] && echo " Code coverage: enabled"
echo
echo ">>> Running: ${CMD[*]}"
echo
# Create a temporary file to capture output
TEMP_OUTPUT=$(mktemp)
if "${CMD[@]}" 2>&1 | tee "$TEMP_OUTPUT"; then
echo
echo "✅ Test execution completed!"
# Parse and display summary from output
if grep -q "Test Results" "$TEMP_OUTPUT"; then
echo
echo "📊 Test Summary:"
grep -A 20 "Test Results" "$TEMP_OUTPUT" | head -20
fi
# Show coverage summary if coverage was requested
if [[ "$COVERAGE" == "true" ]] && grep -q "Coverage" "$TEMP_OUTPUT"; then
echo
echo "📈 Coverage Summary:"
grep -A 10 -B 2 "Coverage" "$TEMP_OUTPUT"
fi
# Check for failures
if grep -q "FAIL" "$TEMP_OUTPUT" || grep -q "Error" "$TEMP_OUTPUT"; then
echo
echo "⚠️ Some tests failed. Check the output above for details."
rm -f "$TEMP_OUTPUT"
exit 1
fi
else
echo
echo "❌ Test execution failed"
rm -f "$TEMP_OUTPUT"
exit 1
fi
# Clean up
rm -f "$TEMP_OUTPUT"

229
sf-test-run.ps1 Normal file
View File

@@ -0,0 +1,229 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Focused Apex test execution wrapper for Salesforce CLI
.DESCRIPTION
A user-friendly wrapper around 'sf apex run test' that simplifies test execution
with better formatting, intelligent defaults, and comprehensive reporting options.
.PARAMETER TestClasses
Comma-separated list of test class names to run
.PARAMETER TestMethods
Comma-separated list of specific test methods to run (format: ClassName.methodName)
.PARAMETER TestLevel
Test level to run (RunLocalTests, RunAllTestsInOrg, RunSpecifiedTests)
.PARAMETER Suite
Test suite name to run
.PARAMETER Coverage
Generate code coverage report
.PARAMETER Wait
Wait time in minutes for test execution (default: 10)
.PARAMETER TargetOrg
Target org username or alias (uses default if not specified)
.PARAMETER OutputDir
Directory to store test results and reports
.PARAMETER Verbose
Enable verbose test output
.PARAMETER Help
Show this help message
.EXAMPLE
.\sf-test-run.ps1 -TestClasses "AccountTest,ContactTest"
.\sf-test-run.ps1 -TestLevel "RunLocalTests" -Coverage
.\sf-test-run.ps1 -TestMethods "AccountTest.testCreate,ContactTest.testUpdate"
.\sf-test-run.ps1 -Suite "AllTests" -Wait 15 -TargetOrg "staging"
.NOTES
This script automatically checks for Salesforce CLI installation and runs
diagnostics if the CLI is not found.
#>
param(
[Parameter(ParameterSetName="Classes")]
[string]$TestClasses,
[Parameter(ParameterSetName="Methods")]
[string]$TestMethods,
[Parameter(ParameterSetName="Level")]
[ValidateSet("RunLocalTests", "RunAllTestsInOrg", "RunSpecifiedTests")]
[string]$TestLevel,
[Parameter(ParameterSetName="Suite")]
[string]$Suite,
[switch]$Coverage,
[int]$Wait = 10,
[string]$TargetOrg,
[string]$OutputDir,
[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
}
}
# Silently check for Salesforce CLI
if (-not (Test-SalesforceCLI)) {
Invoke-SalesforceCheck
exit 1
}
# Validate that at least one test specification is provided
$testSpecCount = @($TestClasses, $TestMethods, $TestLevel, $Suite | Where-Object { $_ }).Count
if ($testSpecCount -eq 0) {
Write-Host "Error: Must specify one of: -TestClasses, -TestMethods, -TestLevel, or -Suite" -ForegroundColor Red
Write-Host ""
Write-Host "Usage examples:" -ForegroundColor Yellow
Write-Host " .\sf-test-run.ps1 -TestClasses `"AccountTest,ContactTest`"" -ForegroundColor Gray
Write-Host " .\sf-test-run.ps1 -TestLevel `"RunLocalTests`" -Coverage" -ForegroundColor Gray
Write-Host " .\sf-test-run.ps1 -TestMethods `"AccountTest.testCreate`"" -ForegroundColor Gray
Write-Host " .\sf-test-run.ps1 -Suite `"AllTests`"" -ForegroundColor Gray
Write-Host ""
Write-Host "Use -Help for detailed usage information." -ForegroundColor Yellow
exit 1
}
# Build the sf command
$sfArgs = @("apex", "run", "test")
# Add test specification
if ($TestClasses) {
$sfArgs += "--class-names"
$sfArgs += $TestClasses
Write-Host "Running test classes: $TestClasses" -ForegroundColor Green
} elseif ($TestMethods) {
$sfArgs += "--tests"
$sfArgs += $TestMethods
Write-Host "Running test methods: $TestMethods" -ForegroundColor Green
} elseif ($TestLevel) {
$sfArgs += "--test-level"
$sfArgs += $TestLevel
Write-Host "Running tests at level: $TestLevel" -ForegroundColor Green
} elseif ($Suite) {
$sfArgs += "--suite-names"
$sfArgs += $Suite
Write-Host "Running test suite: $Suite" -ForegroundColor Green
}
# Add optional parameters
if ($TargetOrg) {
$sfArgs += "--target-org"
$sfArgs += $TargetOrg
Write-Host "Target org: $TargetOrg" -ForegroundColor Cyan
}
if ($Wait -ne 10) {
$sfArgs += "--wait"
$sfArgs += $Wait.ToString()
}
if ($Coverage) {
$sfArgs += "--code-coverage"
Write-Host "Code coverage: Enabled" -ForegroundColor Yellow
}
if ($OutputDir) {
if (-not (Test-Path $OutputDir)) {
Write-Host "Creating output directory: $OutputDir" -ForegroundColor Yellow
New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
}
$sfArgs += "--output-dir"
$sfArgs += $OutputDir
}
# Always use detailed output for better reporting
$sfArgs += "--detailed-coverage"
$sfArgs += "--result-format"
$sfArgs += "human"
# Add verbose flag if requested
if ($Verbose) {
$sfArgs += "--verbose"
}
# Display test execution info
Write-Host ""
Write-Host "🧪 Starting Apex Test Execution" -ForegroundColor Blue
Write-Host "================================" -ForegroundColor Blue
# Display the command being run
Write-Host ""
Write-Host "Executing: sf $($sfArgs -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 "⏱️ Test execution completed in $($duration.TotalSeconds.ToString('F1')) seconds" -ForegroundColor Gray
if ($exitCode -eq 0) {
Write-Host ""
Write-Host "✅ Tests completed successfully!" -ForegroundColor Green
if ($Coverage) {
Write-Host "📊 Code coverage report generated" -ForegroundColor Yellow
}
if ($OutputDir) {
Write-Host "📁 Results saved to: $OutputDir" -ForegroundColor Cyan
}
} else {
Write-Host ""
Write-Host "❌ Test execution failed with exit code: $exitCode" -ForegroundColor Red
Write-Host "💡 Check test failures above for details" -ForegroundColor Yellow
exit $exitCode
}
} catch {
Write-Host "Error executing sf command: $($_.Exception.Message)" -ForegroundColor Red
exit 1
}