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.

  1. Le code source (format DX, dossier force-app) vit dans Git ; chaque évolution passe par une pull request.
  2. À chaque pull request : calcul des métadonnées modifiées, validation dans une sandbox d’intégration, tests Apex.
  3. À 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 : RunLocalTests par défaut ; RunSpecifiedTests accé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).
🍪 Gestion des cookies
Ce site utilise des cookies pour mesurer l'audience (Google Analytics) et lire des vidéos (YouTube). Vous pouvez personnaliser votre choix.
Mentions légales
Réglages des cookies
Essentiels Nécessaires au fonctionnement du site — ne peuvent pas être désactivés.
Google Analytics Analyse d'audience (pages vues, sources de trafic). Aucune donnée personnelle identifiante.
YouTube Permet de lire les vidéos YouTube intégrées sur cette page.