added addl wrappers
This commit is contained in:
200
README.md
200
README.md
@@ -6,13 +6,28 @@ A collection of convenient wrapper scripts for common Salesforce CLI operations.
|
||||
|
||||
## Overview
|
||||
|
||||
This toolkit provides four main utilities:
|
||||
This toolkit provides a suite of cross-platform wrappers to streamline common Salesforce CLI workflows:
|
||||
|
||||
Core:
|
||||
- **`sf-deploy`** - Streamlined wrapper for `sf project deploy start`
|
||||
- **`sf-dry-run`** - Validation wrapper for `sf project deploy start --dry-run`
|
||||
- **`sf-web-open`** - Quick org browser opener for `sf org open`
|
||||
- **`sf-check`** - Environment verification tool to check SF CLI installation and configuration
|
||||
|
||||
Org and metadata:
|
||||
- **`sf-org-create` / `sf-org-create.ps1`** - Smart scratch org creation
|
||||
- **`sf-org-info` / `sf-org-info.ps1`** - Quick org info, limits, and context
|
||||
- **`sf-retrieve` / `sf-retrieve.ps1`** - Streamlined metadata retrieval (types, manifest, packages)
|
||||
|
||||
Apex and tests:
|
||||
- **`sf-test-run` / `sf-test-run.ps1`** - Focused Apex test execution with coverage
|
||||
- **`sf-apex-run` / `sf-apex-run.ps1`** - Anonymous Apex execution (file or inline)
|
||||
|
||||
Data and logs:
|
||||
- **`sf-data-export` / `sf-data-export.ps1`** - Export data via SOQL to CSV/JSON, optional Bulk API
|
||||
- **`sf-data-import` / `sf-data-import.ps1`** - Import CSV/JSON with insert/update/upsert
|
||||
- **`sf-logs-tail` / `sf-logs-tail.ps1`** - Real-time debug logs tail with filtering
|
||||
|
||||
## Installation
|
||||
|
||||
### Unix/Linux/macOS (Bash)
|
||||
@@ -20,19 +35,30 @@ This toolkit provides four main utilities:
|
||||
1. Clone or download this repository to your preferred tools directory
|
||||
2. Make the scripts executable:
|
||||
```bash
|
||||
chmod +x sf-deploy sf-dry-run sf-web-open sf-check
|
||||
chmod +x \
|
||||
sf-deploy sf-dry-run sf-web-open sf-check \
|
||||
sf-org-create sf-org-info sf-retrieve sf-test-run sf-apex-run \
|
||||
sf-data-export sf-data-import sf-logs-tail
|
||||
```
|
||||
3. Add the directory to your PATH or create symlinks in a directory that's already in your PATH:
|
||||
```bash
|
||||
# Option 1: Add to PATH (add to your ~/.zshrc or ~/.bashrc)
|
||||
export PATH="$PATH:/path/to/sf-cli-wrapper"
|
||||
|
||||
# Option 2: Create symlinks
|
||||
ln -s /path/to/sf-cli-wrapper/sf-deploy /usr/local/bin/sf-deploy
|
||||
ln -s /path/to/sf-cli-wrapper/sf-dry-run /usr/local/bin/sf-dry-run
|
||||
ln -s /path/to/sf-cli-wrapper/sf-web-open /usr/local/bin/sf-web-open
|
||||
ln -s /path/to/sf-cli-wrapper/sf-check /usr/local/bin/sf-check
|
||||
```
|
||||
# Option 2: Create symlinks
|
||||
ln -s /path/to/sf-cli-wrapper/sf-deploy /usr/local/bin/sf-deploy
|
||||
ln -s /path/to/sf-cli-wrapper/sf-dry-run /usr/local/bin/sf-dry-run
|
||||
ln -s /path/to/sf-cli-wrapper/sf-web-open /usr/local/bin/sf-web-open
|
||||
ln -s /path/to/sf-cli-wrapper/sf-check /usr/local/bin/sf-check
|
||||
ln -s /path/to/sf-cli-wrapper/sf-org-create /usr/local/bin/sf-org-create
|
||||
ln -s /path/to/sf-cli-wrapper/sf-org-info /usr/local/bin/sf-org-info
|
||||
ln -s /path/to/sf-cli-wrapper/sf-retrieve /usr/local/bin/sf-retrieve
|
||||
ln -s /path/to/sf-cli-wrapper/sf-test-run /usr/local/bin/sf-test-run
|
||||
ln -s /path/to/sf-cli-wrapper/sf-apex-run /usr/local/bin/sf-apex-run
|
||||
ln -s /path/to/sf-cli-wrapper/sf-data-export /usr/local/bin/sf-data-export
|
||||
ln -s /path/to/sf-cli-wrapper/sf-data-import /usr/local/bin/sf-data-import
|
||||
ln -s /path/to/sf-cli-wrapper/sf-logs-tail /usr/local/bin/sf-logs-tail
|
||||
```
|
||||
|
||||
### Windows (PowerShell)
|
||||
|
||||
@@ -46,15 +72,125 @@ This toolkit provides four main utilities:
|
||||
# Option 1: Add to PATH (System Environment Variables)
|
||||
# Add C:\path\to\sf-cli-wrapper to your system PATH
|
||||
|
||||
# Option 2: Create PowerShell aliases (add to your $PROFILE)
|
||||
Set-Alias sf-deploy "C:\path\to\sf-cli-wrapper\sf-deploy.ps1"
|
||||
Set-Alias sf-dry-run "C:\path\to\sf-cli-wrapper\sf-dry-run.ps1"
|
||||
Set-Alias sf-web-open "C:\path\to\sf-cli-wrapper\sf-web-open.ps1"
|
||||
Set-Alias sf-check "C:\path\to\sf-cli-wrapper\sf-check.ps1"
|
||||
```
|
||||
# Option 2: Create PowerShell aliases (add to your $PROFILE)
|
||||
Set-Alias sf-deploy "C:\\path\\to\\sf-cli-wrapper\\sf-deploy.ps1"
|
||||
Set-Alias sf-dry-run "C:\\path\\to\\sf-cli-wrapper\\sf-dry-run.ps1"
|
||||
Set-Alias sf-web-open "C:\\path\\to\\sf-cli-wrapper\\sf-web-open.ps1"
|
||||
Set-Alias sf-check "C:\\path\\to\\sf-cli-wrapper\\sf-check.ps1"
|
||||
Set-Alias sf-org-create "C:\\path\\to\\sf-cli-wrapper\\sf-org-create.ps1"
|
||||
Set-Alias sf-org-info "C:\\path\\to\\sf-cli-wrapper\\sf-org-info.ps1"
|
||||
Set-Alias sf-retrieve "C:\\path\\to\\sf-cli-wrapper\\sf-retrieve.ps1"
|
||||
Set-Alias sf-test-run "C:\\path\\to\\sf-cli-wrapper\\sf-test-run.ps1"
|
||||
Set-Alias sf-apex-run "C:\\path\\to\\sf-cli-wrapper\\sf-apex-run.ps1"
|
||||
Set-Alias sf-data-export "C:\\path\\to\\sf-cli-wrapper\\sf-data-export.ps1"
|
||||
Set-Alias sf-data-import "C:\\path\\to\\sf-cli-wrapper\\sf-data-import.ps1"
|
||||
Set-Alias sf-logs-tail "C:\\path\\to\\sf-cli-wrapper\\sf-logs-tail.ps1"
|
||||
```
|
||||
|
||||
## Scripts
|
||||
|
||||
### sf-org-create
|
||||
|
||||
Smart scratch org creation with templates and intelligent defaults.
|
||||
|
||||
Usage (bash):
|
||||
```bash
|
||||
sf-org-create [-a ALIAS] [-d DAYS] [-t TEMPLATE] [--no-namespace]
|
||||
```
|
||||
|
||||
Usage (PowerShell):
|
||||
```powershell
|
||||
sf-org-create.ps1 -Alias "MySO" -DurationDays 7 -Template "config/project-scratch-def.json"
|
||||
```
|
||||
|
||||
Examples:
|
||||
- Create with alias and duration: `sf-org-create -a SO -d 7`
|
||||
- Use a specific template: `sf-org-create -t config/project-scratch-def.json`
|
||||
|
||||
### sf-org-info / sf-org-info.ps1
|
||||
|
||||
Quick org information display with optional limits and listing authenticated orgs.
|
||||
|
||||
Usage:
|
||||
```bash
|
||||
sf-org-info [-o ORG] [--limits] [--list]
|
||||
```
|
||||
```powershell
|
||||
sf-org-info.ps1 -TargetOrg "myorg" -Limits -Verbose
|
||||
```
|
||||
|
||||
### sf-retrieve / sf-retrieve.ps1
|
||||
|
||||
Streamlined metadata retrieval supporting metadata types, manifest, and package name.
|
||||
|
||||
Usage:
|
||||
```bash
|
||||
sf-retrieve --types "ApexClass,CustomObject" | --manifest manifest/package.xml | --package-name MyPkg [--target-org ORG] [--output-dir DIR]
|
||||
```
|
||||
```powershell
|
||||
sf-retrieve.ps1 -MetadataTypes "ApexClass,CustomObject" -TargetOrg myorg -OutputDir retrieved
|
||||
```
|
||||
|
||||
### sf-test-run / sf-test-run.ps1
|
||||
|
||||
Focused Apex test execution with better formatting, coverage, and wait control.
|
||||
|
||||
Usage:
|
||||
```bash
|
||||
sf-test-run --classes "A,B" | --methods "A.m1,B.m2" | --level RunLocalTests [--coverage] [--wait 15] [--target-org ORG]
|
||||
```
|
||||
```powershell
|
||||
sf-test-run.ps1 -TestClasses "ApexTest1,ApexTest2" -Coverage -Wait 15
|
||||
```
|
||||
|
||||
### sf-apex-run / sf-apex-run.ps1
|
||||
|
||||
Execute anonymous Apex from file or inline code.
|
||||
|
||||
Usage:
|
||||
```bash
|
||||
sf-apex-run --file scripts/setup.apex | --code "System.debug('hi');" [--target-org ORG]
|
||||
```
|
||||
```powershell
|
||||
sf-apex-run.ps1 -File "scripts/setup.apex" -TargetOrg dev
|
||||
```
|
||||
|
||||
### sf-data-export / sf-data-export.ps1
|
||||
|
||||
Export data via SOQL to CSV/JSON with optional Bulk API.
|
||||
|
||||
Usage:
|
||||
```bash
|
||||
sf-data-export --query "SELECT Id, Name FROM Account" | --file query.soql | --sobject Account [--format csv|json] [--bulk] [--output out.csv]
|
||||
```
|
||||
```powershell
|
||||
sf-data-export.ps1 -Query "SELECT Id FROM User" -Format json -Output users.json
|
||||
```
|
||||
|
||||
### sf-data-import / sf-data-import.ps1
|
||||
|
||||
Import CSV/JSON with insert/update/upsert operations.
|
||||
|
||||
Usage:
|
||||
```bash
|
||||
sf-data-import --file data.csv --sobject Account --operation insert|update|upsert [--external-id Field] [--bulk]
|
||||
```
|
||||
```powershell
|
||||
sf-data-import.ps1 -File data.json -SObject Contact -Operation upsert -ExternalId Email
|
||||
```
|
||||
|
||||
### sf-logs-tail / sf-logs-tail.ps1
|
||||
|
||||
Real-time debug logs tail with filtering, levels, and Apex-only mode.
|
||||
|
||||
Usage:
|
||||
```bash
|
||||
sf-logs-tail [--target-org ORG] [--user-id USER] [--level DEBUG] [--duration 60] [--filter PATTERN] [--apex-only]
|
||||
```
|
||||
```powershell
|
||||
sf-logs-tail.ps1 -TargetOrg sandbox -Level DEBUG -Duration 60 -ApexOnly -Filter "MyClass"
|
||||
```
|
||||
|
||||
### sf-deploy
|
||||
|
||||
Wrapper for `sf project deploy start` that simplifies deploying multiple source files with optional test execution.
|
||||
@@ -178,7 +314,7 @@ sf-check -v
|
||||
|
||||
## Automatic Environment Verification
|
||||
|
||||
All wrapper scripts (`sf-deploy`, `sf-dry-run`, `sf-web-open`) include built-in environment verification:
|
||||
All wrapper scripts (deploy, dry-run, web-open, org-create, org-info, retrieve, test-run, apex-run, data-export, data-import, logs-tail) include built-in environment verification:
|
||||
|
||||
### ✅ **When SF CLI is installed (normal operation):**
|
||||
```bash
|
||||
@@ -265,6 +401,28 @@ sf-web-open.ps1
|
||||
|
||||
### Windows Common Workflows
|
||||
|
||||
Additional helpers (PowerShell):
|
||||
```powershell
|
||||
# Org creation
|
||||
sf-org-create.ps1 -Alias "SO" -DurationDays 7
|
||||
|
||||
# Retrieve metadata
|
||||
sf-retrieve.ps1 -Manifest "manifest/package.xml" -TargetOrg "dev"
|
||||
|
||||
# Run tests with coverage
|
||||
sf-test-run.ps1 -TestLevel RunLocalTests -Coverage -Wait 15
|
||||
|
||||
# Execute anonymous Apex
|
||||
sf-apex-run.ps1 -Code "System.debug('Hello');" -TargetOrg dev
|
||||
|
||||
# Data export and import
|
||||
sf-data-export.ps1 -SObject Account -Format csv -Output accounts.csv
|
||||
sf-data-import.ps1 -File accounts.csv -SObject Account -Operation insert
|
||||
|
||||
# Tail logs
|
||||
sf-logs-tail.ps1 -Level DEBUG -Duration 30 -ApexOnly
|
||||
```
|
||||
|
||||
```powershell
|
||||
# 1. First, validate your deployment (specific file)
|
||||
sf-dry-run.ps1 -o "DEMO-ORG" -s "force-app/main/default/classes/MyClass.cls"
|
||||
@@ -328,13 +486,13 @@ sf-deploy -o DEMO-ORG \
|
||||
## Features
|
||||
|
||||
- **Cross-Platform**: Available for both Unix/Linux/macOS (Bash) and Windows (PowerShell)
|
||||
- **Automatic Environment Verification**: Scripts automatically check if SF CLI is available and run diagnostics if not
|
||||
- **Error Handling**: All scripts include robust error handling
|
||||
- **Help Documentation**: Each script includes comprehensive help accessible with `-h` (both Bash and PowerShell)
|
||||
- **Flexible Input**: Support for both absolute and repository-relative paths
|
||||
- **Automatic Environment Verification**: All wrappers auto-check SF CLI and run diagnostics when missing
|
||||
- **Consistent UX**: Arguments and output styling are aligned across Bash and PowerShell
|
||||
- **Error Handling**: Robust input validation and actionable errors
|
||||
- **Help Documentation**: Each script includes comprehensive help (-h or -Help)
|
||||
- **Flexible Input**: Supports absolute and repository-relative paths
|
||||
- **Command Echo**: Shows the actual `sf` command being executed for transparency
|
||||
- **Multiple Sources**: Easy handling of multiple source directories and test classes
|
||||
- **Parameter Validation**: Scripts validate required parameters and show helpful error messages
|
||||
- **Focused Workflows**: Deploy, validate, retrieve, test, run Apex, manage orgs, data import/export, and tail logs
|
||||
|
||||
## Tips
|
||||
|
||||
|
||||
173
sf-apex-run
Executable file
173
sf-apex-run
Executable 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
244
sf-apex-run.ps1
Normal 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
321
sf-data-export
Executable 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
290
sf-data-export.ps1
Normal 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
376
sf-data-import
Executable 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
345
sf-data-import.ps1
Normal 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
313
sf-logs-tail
Executable 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
315
sf-logs-tail.ps1
Normal 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
241
sf-org-create
Executable 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
253
sf-org-create.ps1
Normal 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
166
sf-org-info
Executable 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
204
sf-org-info.ps1
Normal 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
204
sf-retrieve
Executable 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
194
sf-retrieve.ps1
Normal 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
218
sf-test-run
Executable 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
229
sf-test-run.ps1
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user