Tutoriel · Salesforce · DevOps
CI/CD Salesforce avec GitHub Actions : valider et déployer en delta
Connexion JWT, delta avec sfdx-git-delta, validation et tests Apex à chaque pull request, puis déploiement en production derrière une approbation : une chaîne complète et reproductible.
La chaîne cible
Déployer à la main depuis un poste (ou par change sets) fonctionne jusqu’au jour où deux développeurs écrasent mutuellement leur travail, ou où un test cassé n’est découvert qu’en production. Une chaîne CI/CD rend chaque étape automatique, identique et tracée.
- Le code source (format DX, dossier
force-app) vit dans Git ; chaque évolution passe par une pull request. - À chaque pull request : calcul des métadonnées modifiées, validation dans une sandbox d’intégration, tests Apex.
- À la fusion dans
main: déploiement en production après approbation d’un responsable.
Fil rouge : le dépôt Salesforce d’une compagnie fictive, Acme Assurances, avec une sandbox d’intégration et une production.
Connexion sans mot de passe : JWT
Le pipeline se connecte aux orgs avec le flux OAuth JWT Bearer : une application externe dédiée « CI GitHub », un certificat, une clé privée rangée dans les secrets GitHub et un utilisateur d’intégration par org (détails dans Authentifier une intégration Salesforce avec OAuth 2.0).
Secrets GitHub (Settings → Secrets and variables → Actions) :
SF_CLIENT_ID consumer key de l'application externe « CI GitHub »
SF_JWT_KEY clé privée PEM associée au certificat déposé dans l'application
SF_USERNAME_UAT utilisateur d'intégration de la sandbox d'intégration
SF_USERNAME_PROD utilisateur d'intégration de la production L’utilisateur d’intégration de production n’a que les droits de déploiement nécessaires ; personne ne connaît son mot de passe, et révoquer le pipeline revient à désactiver l’application.
Ne déployer que ce qui change
Déployer tout force-app à chaque fois devient lent sur une grosse org, et peut écraser une modification faite directement dans l’org. Le plugin sfdx-git-delta compare deux commits et produit :
delta/package/package.xml: les métadonnées ajoutées ou modifiées ;delta/destructiveChanges/destructiveChanges.xml: celles qui ont été supprimées du dépôt, à supprimer de l’org.
Le calcul a besoin de l’historique Git : d’où fetch-depth: 0 au checkout.
Valider chaque pull request
sf project deploy validate joue le déploiement et les tests sans rien enregistrer dans l’org. Une pull request qui casse la compilation, un test ou la couverture de code est refusée avant même la relecture.
name: Validation Salesforce
on:
pull_request:
branches: [main]
paths: ["force-app/**"]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # historique complet : nécessaire au calcul du delta
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Installer la Salesforce CLI et sfdx-git-delta
run: |
npm install --global @salesforce/cli
echo y | sf plugins install sfdx-git-delta
- name: Connexion à la sandbox d'intégration (JWT)
env:
SF_JWT_KEY: ${{ secrets.SF_JWT_KEY }}
run: |
echo "$SF_JWT_KEY" > server.key
sf org login jwt --client-id "${{ secrets.SF_CLIENT_ID }}" --jwt-key-file server.key \
--username "${{ secrets.SF_USERNAME_UAT }}" --instance-url https://test.salesforce.com --alias uat
rm server.key
- name: Calculer le delta
run: sf sgd source delta --from "origin/${{ github.base_ref }}" --to HEAD --output-dir delta
- name: Valider sans déployer (tests Apex locaux)
run: |
sf project deploy validate --manifest delta/package/package.xml \
--post-destructive-changes delta/destructiveChanges/destructiveChanges.xml \
--test-level RunLocalTests --target-org uat --wait 60 On ajoute utilement une étape d’analyse statique (Salesforce Code Analyzer, PMD) et le lancement des tests Jest des composants LWC.
Déployer en production avec approbation
Le déploiement se déclenche à la fusion dans main. L’environnement GitHub production porte une règle de protection : le job attend qu’un responsable approuve, et ses secrets ne sont accessibles qu’après cette approbation.
name: Déploiement Salesforce
on:
push:
branches: [main]
paths: ["force-app/**"]
jobs:
deploy:
runs-on: ubuntu-latest
environment: production # règle de protection GitHub : approbation manuelle obligatoire
concurrency: salesforce-production # jamais deux déploiements en parallèle
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 20
- run: |
npm install --global @salesforce/cli
echo y | sf plugins install sfdx-git-delta
- name: Connexion à la production (JWT)
env:
SF_JWT_KEY: ${{ secrets.SF_JWT_KEY }}
run: |
echo "$SF_JWT_KEY" > server.key
sf org login jwt --client-id "${{ secrets.SF_CLIENT_ID }}" --jwt-key-file server.key \
--username "${{ secrets.SF_USERNAME_PROD }}" --instance-url https://login.salesforce.com --alias prod
rm server.key
- name: Delta depuis le dernier état de main
run: sf sgd source delta --from "${{ github.event.before }}" --to HEAD --output-dir delta
- name: Déployer
run: |
sf project deploy start --manifest delta/package/package.xml \
--post-destructive-changes delta/destructiveChanges/destructiveChanges.xml \
--test-level RunLocalTests --target-org prod --wait 120 Variante plus rapide : valider sur la production elle-même pendant la pull request, puis lancer sf project deploy quick --job-id <id> après la fusion. Les tests ne sont alors pas rejoués, à condition que la validation date de moins de dix jours et que les composants concernés n’aient pas changé dans l’org entre-temps.
Bonnes pratiques
- Git comme source de vérité : une modification faite directement en production est reportée dans le dépôt, sinon le prochain déploiement l’écrase.
- Tests fiables : chaque test crée ses propres données (
@TestSetup), simule les appels externes et vérifie un résultat. Un test instable bloque tout le pipeline. - Niveau de tests :
RunLocalTestspar défaut ;RunSpecifiedTestsaccélère, mais impose 75 % de couverture sur chaque classe déployée. - Profils et permissions : préférer les permission sets, bien plus simples à déployer en delta que les profils.
- Données et paramétrage : les enregistrements de métadonnées personnalisées se déploient ; les données métier, non. Les prévoir dans un script de reprise séparé.
- Agent IA dans la boucle : un agent peut préparer la pull request, mais la validation et le déploiement restent dans le pipeline, avec une règle qui lui interdit de déployer lui-même (voir les hooks).