From salesforce-pack
Set up Salesforce CI/CD pipelines with GitHub Actions, SFDX deployments, JWT auth, and Apex testing. For automating metadata validation and tests in Salesforce repos.
npx claudepluginhub jeremylongshore/claude-code-plugins-plus-skills --plugin salesforce-packThis skill is limited to using the following tools:
Set up CI/CD pipelines for Salesforce using GitHub Actions with JWT-based authentication, automated Apex testing, and metadata deployment.
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Guides building MCP servers enabling LLMs to interact with external services via tools. Covers best practices, TypeScript/Node (MCP SDK), Python (FastMCP).
Generates original PNG/PDF visual art via design philosophy manifestos for posters, graphics, and static designs on user request.
Set up CI/CD pipelines for Salesforce using GitHub Actions with JWT-based authentication, automated Apex testing, and metadata deployment.
Create .github/workflows/salesforce-ci.yml:
name: Salesforce CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install Salesforce CLI
run: npm install -g @salesforce/cli
- name: Authenticate to Salesforce (JWT)
run: |
echo "${{ secrets.SF_JWT_KEY }}" > server.key
sf org login jwt \
--client-id ${{ secrets.SF_CLIENT_ID }} \
--jwt-key-file server.key \
--username ${{ secrets.SF_USERNAME }} \
--set-default \
--alias ci-org
rm server.key
- name: Validate Metadata Deployment (dry run)
run: |
sf project deploy start \
--target-org ci-org \
--dry-run \
--wait 30
- name: Run Apex Tests
run: |
sf apex run test \
--target-org ci-org \
--result-format human \
--code-coverage \
--wait 20
- name: Check API Limits
run: |
sf limits api display --target-org ci-org --json | \
jq '.result[] | select(.name == "DailyApiRequests") | "\(.name): \(.remaining)/\(.max)"'
integration-tests:
runs-on: ubuntu-latest
needs: validate
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- name: Run jsforce Integration Tests
env:
SF_LOGIN_URL: https://test.salesforce.com
SF_USERNAME: ${{ secrets.SF_USERNAME }}
SF_PASSWORD: ${{ secrets.SF_PASSWORD }}
SF_SECURITY_TOKEN: ${{ secrets.SF_SECURITY_TOKEN }}
run: npm run test:integration
# JWT private key (from your RSA key pair)
gh secret set SF_JWT_KEY < server.key
# Connected App consumer key
gh secret set SF_CLIENT_ID --body "3MVG9..."
# Integration user credentials
gh secret set SF_USERNAME --body "ci-user@yourcompany.com"
gh secret set SF_PASSWORD --body "password"
gh secret set SF_SECURITY_TOKEN --body "token"
# Generate RSA key pair for JWT Bearer flow
openssl genrsa -out server.key 2048
openssl req -new -x509 -key server.key -out server.crt -days 365 \
-subj "/CN=Salesforce CI/O=YourCompany"
# Upload server.crt to Connected App in Salesforce Setup:
# Setup > App Manager > Your App > Edit > Use Digital Signatures > Choose File
import { describe, it, expect } from 'vitest';
import jsforce from 'jsforce';
describe('Salesforce Integration', () => {
const conn = new jsforce.Connection({
loginUrl: process.env.SF_LOGIN_URL || 'https://test.salesforce.com',
});
beforeAll(async () => {
await conn.login(
process.env.SF_USERNAME!,
process.env.SF_PASSWORD! + process.env.SF_SECURITY_TOKEN!
);
});
it('should query Accounts via SOQL', async () => {
const result = await conn.query('SELECT Id, Name FROM Account LIMIT 1');
expect(result.totalSize).toBeGreaterThanOrEqual(0);
expect(result.done).toBe(true);
});
it('should check API limits are not exhausted', async () => {
const limits = await conn.request('/services/data/v59.0/limits/');
const remaining = limits.DailyApiRequests.Remaining;
const max = limits.DailyApiRequests.Max;
expect(remaining / max).toBeGreaterThan(0.1); // At least 10% remaining
});
it('should describe Account sObject', async () => {
const meta = await conn.sobject('Account').describe();
expect(meta.name).toBe('Account');
expect(meta.fields.length).toBeGreaterThan(0);
expect(meta.fields.find(f => f.name === 'Name')).toBeDefined();
});
});
# Deploy on merge to main
deploy:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
needs: [validate, integration-tests]
steps:
- uses: actions/checkout@v4
- name: Install Salesforce CLI
run: npm install -g @salesforce/cli
- name: Authenticate
run: |
echo "${{ secrets.SF_JWT_KEY_PROD }}" > server.key
sf org login jwt \
--client-id ${{ secrets.SF_CLIENT_ID_PROD }} \
--jwt-key-file server.key \
--username ${{ secrets.SF_USERNAME_PROD }} \
--set-default
rm server.key
- name: Deploy to Production
run: |
sf project deploy start \
--target-org ${{ secrets.SF_USERNAME_PROD }} \
--test-level RunLocalTests \
--wait 30
| Issue | Cause | Solution |
|---|---|---|
INVALID_GRANT | JWT cert not uploaded or user not pre-authorized | Upload cert to Connected App; add user to pre-authorized profiles |
| Apex test failures | Test data dependencies | Use @testSetup methods for test isolation |
| Deploy validation errors | Missing dependencies | Check component dependencies with sf project deploy report |
| API limit in CI | Too many test runs/day | Use sandbox instead of production for CI |
For deployment patterns, see salesforce-deploy-integration.