Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

Chapitre 1316 minIntermédiaire

Publier une version : SemVer, tags et releases

Par Josselyn C., prof de BTS SIO. Mis à jour le .

À la fin de ce chapitre, tu sauras

  • choisir le prochain numéro de version avec SemVer ;
  • déduire la version des messages Conventional Commits ;
  • publier une release automatiquement à partir d'un tag ;
  • protéger les versions publiées.

4 exercices corrigés

Publier une version, c'est figer un état du code sous un numéro (un tag Git comme v2.5.0), produire les artefacts correspondants (image Docker, archive, exécutable) et documenter ce qui a changé (une release et un CHANGELOG). Dans un pipeline, le tag déclenche tout le reste automatiquement. Ce chapitre explique le versionnage sémantique, les messages de commit normalisés, le workflow de publication sur un tag, l'automatisation du numéro et du CHANGELOG, et la protection des versions publiées.

Le versionnage sémantique (SemVer)

Le versionnage sémantique (Semantic Versioning, SemVer) donne un sens à chaque chiffre d'un numéro MAJEUR.MINEUR.CORRECTIF (tags et versions dans Git) :

On incrémente…QuandExemple depuis 2.4.1Les chiffres à droite
CORRECTIFUne correction de bug, compatible2.4.2—
MINEURUne nouvelle fonctionnalité, compatible avec l'existant2.5.0Remis à 0
MAJEURUn changement incompatible : une route d'API supprimée, un format de réponse modifié3.0.0Remis à 0

Quelques règles complètent le système :

  • une pré-version s'écrit avec un tiret : 2.5.0-rc.1 (release candidate), 3.0.0-beta.2 ; elle précède la version finale (2.5.0-rc.1 < 2.5.0) ;
  • en version 0.x, tout peut changer : l'API n'est pas encore considérée comme stable ; par convention (npm, Cargo), un changement incompatible incrémente alors le chiffre MINEUR et une fonctionnalité ou une correction le CORRECTIF, mais beaucoup de projets incrémentent aussi le MINEUR pour une fonctionnalité ;
  • un numéro publié ne se réutilise jamais : si la 2.5.0 contient un bug, on publie la 2.5.1, on ne remplace pas la 2.5.0.

Pour une API comme sorbier-frais, « incompatible » se juge du point de vue de ceux qui l'appellent : l'application mobile et l'interface Web. Ajouter un champ à une réponse JSON est compatible ; renommer un champ ne l'est pas.

Des messages de commit normalisés

La convention Conventional Commits structure les messages de commit avec un type (Conventional Commits) :

text
feat: ajoute l'export CSV des notes de frais
fix: arrondit les indemnités au centime
docs: complète le README avec la procédure de déploiement
feat!: renomme le champ montant en montant_ttc dans l'API

Le type indique directement l'incrément de version :

Commits depuis la dernière versionIncrément
Uniquement fix: (et des docs:, test:, chore:, ci:, qui ne changent pas le numéro)CORRECTIF
Au moins un feat:MINEUR
Au moins un ! après le type, ou un pied de message BREAKING CHANGE:MAJEUR

C'est ce qui permet à un outil de calculer seul le prochain numéro et de rédiger le CHANGELOG.

Tenir un CHANGELOG

Le fichier CHANGELOG.md résume, version par version, ce qui change pour ceux qui utilisent le logiciel. Le format le plus répandu, Keep a Changelog, range les changements par catégorie (Ajouté, Modifié, Corrigé, Supprimé, Sécurité), la version la plus récente en haut, avec sa date.

C'est aussi ce que demande l'indicateur de la compétence « Intégrer en continu les versions d'une solution applicative » de l'E6 SLAM : « une documentation des versions accompagne l'intégration ». Un CHANGELOG tenu à jour, des tags et des releases en sont la preuve directe (les compétences de l'E6 SLAM).

Le workflow de publication sur un tag

Pour publier la version 2.5.0, Lina met à jour le numéro, crée le tag et pousse :

bash
npm version minor          # 2.4.1 → 2.5.0 dans package.json et package-lock.json, commit « 2.5.0 » et tag v2.5.0
git push --follow-tags     # pousse le commit et le tag

Le tag v2.5.0 déclenche ce workflow :

yaml
name: Publication

on:
  push:
    tags: ['v*.*.*']

permissions:
  contents: read

jobs:
  verifier:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - name: Vérifier que le tag correspond à package.json
        run: test "v$(node -p "require('./package.json').version")" = "$GITHUB_REF_NAME"
      - uses: actions/setup-node@v7
        with:
          node-version: 24
          package-manager-cache: false
      - run: npm ci
      - run: npm test -- --ci

  release:
    needs: verifier
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v7
      - name: Construire l'archive
        run: git archive --format=zip --prefix="sorbier-frais-$GITHUB_REF_NAME/" -o "sorbier-frais-$GITHUB_REF_NAME.zip" HEAD
      - name: Créer la release
        env:
          GH_TOKEN: ${{ github.token }}
        run: |
          gh release create "$GITHUB_REF_NAME" \
            --title "sorbier-frais $GITHUB_REF_NAME" \
            --generate-notes \
            --verify-tag \
            "sorbier-frais-$GITHUB_REF_NAME.zip"
ÉlémentRôle
tags: ['v*.*.*']Seuls les tags de la forme v2.5.0 déclenchent la publication
La vérification du tagRefuse de publier si le tag et le numéro de package.json ne correspondent pas
package-manager-cache: falsePas de cache dans un workflow de publication (l'empoisonnement du cache)
contents: writeNécessaire pour créer la release, accordé au seul job release
gh release createL'outil en ligne de commande de GitHub, préinstallé sur les runners : il crée la release, y joint l'archive, et --generate-notes rédige les notes à partir des pull requests fusionnées depuis la version précédente
--verify-tagÉchoue si le tag n'existe pas sur GitHub, au lieu d'en créer un

En parallèle, le workflow Docker du chapitre 9, déclenché lui aussi par les tags v*, publie les images 2.5.0 et 2.5. La production peut alors déployer 2.5.0, une étiquette qui parle à tout le monde.

Automatiser le numéro et le CHANGELOG

Choisir le numéro et écrire le CHANGELOG à la main est source d'oublis. Deux outils s'appuient sur Conventional Commits pour le faire :

  • release-please (de Google) tient en permanence une pull request de publication, mise à jour à chaque fusion sur main : elle contient le nouveau numéro et les nouvelles entrées du CHANGELOG. Quand l'équipe décide de publier, elle fusionne cette pull request ; l'outil crée alors le tag et la release.
  • semantic-release publie automatiquement à chaque fusion sur main qui contient un feat ou un fix, sans pull request intermédiaire.
yaml
name: Release Please

on:
  push:
    branches: [main]

permissions:
  contents: write
  pull-requests: write

jobs:
  release-please:
    runs-on: ubuntu-latest
    outputs:
      publiee: ${{ steps.release.outputs.release_created }}
      tag: ${{ steps.release.outputs.tag_name }}
    steps:
      # Action d'un éditeur tiers : dans un vrai projet, épingle-la sur une empreinte de commit
      - id: release
        uses: googleapis/release-please-action@v5
        with:
          release-type: node

  image:
    needs: release-please
    if: needs.release-please.outputs.publiee == 'true'
    runs-on: ubuntu-latest
    steps:
      - run: echo "Construction de l'image pour ${{ needs.release-please.outputs.tag }}"

Protéger les versions publiées

Une version publiée doit rester ce qu'elle était. Sur GitHub :

  • un ruleset de tags (Settings, Rules, Rulesets, New tag ruleset) sur le motif v* réserve la création des tags de version aux mainteneurs et interdit leur suppression et leur déplacement ;
  • les releases immuables, une option du dépôt, empêchent de modifier le tag et les fichiers d'une release une fois publiée ;
  • un tag signé (git tag -s v2.5.0) prouve qui a créé la version (signer ses tags) ;
  • une attestation de provenance des fichiers publiés, produite dans le workflow par l'action officielle actions/attest-build-provenance (avec les permissions id-token: write et attestations: write), permet à quiconque de vérifier avec gh attestation verify qu'un fichier a bien été construit par ce workflow, à partir de ce commit.

C'est exactement ce que la règle de l'action épinglée demande aux autres : des versions qui ne bougent pas.

Avec GitLab

yaml
publier-release:
  stage: deploy
  image: registry.gitlab.com/gitlab-org/cli:latest
  rules:
    - if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
  script:
    - echo "Publication de la version $CI_COMMIT_TAG"
  release:
    tag_name: $CI_COMMIT_TAG
    name: "sorbier-frais $CI_COMMIT_TAG"
    description: "Voir CHANGELOG.md pour le détail des changements."

Le mot-clé release crée la release GitLab à partir du tag ; l'image cli contient l'outil glab qui s'en charge. Les tags protégés (Settings, Repository, Protected tags) réservent la création des tags v* aux mainteneurs, et les variables protégées ne sont transmises qu'à leurs pipelines.

Exercices

Exercice 1 sur 4 Le prochain numéro Réussi À revoir

La version actuelle de sorbier-frais est 2.4.1. Donne le numéro de la prochaine version dans chaque cas.

  1. Depuis la 2.4.1, on a corrigé l'arrondi des indemnités.
  2. On a ajouté l'export CSV et corrigé deux bugs.
  3. La route GET /notes renvoie désormais une liste paginée { "elements": [...], "total": 87 } au lieu d'un simple tableau.
  4. Un autre projet est en 0.9.3 ; on ajoute une fonctionnalité.
  5. Avant la 3.0.0, l'équipe veut publier une première version de test.
Voir la réponseMasquer la réponse de l'exercice 1
  1. Correction compatible : 2.4.2.
  2. Au moins une fonctionnalité compatible : 2.5.0 (le correctif repart à 0, les corrections sont incluses).
  3. Le format de la réponse change, les applications qui lisent un tableau cassent : changement incompatible, 3.0.0.
  4. En 0.x, une fonctionnalité incrémente le chiffre MINEUR : 0.10.0 (et non 1.0.0 : le passage en 1.0.0 est une décision de stabilité, pas un calcul).
  5. Une pré-version : 3.0.0-rc.1 (ou 3.0.0-beta.1), qui précède 3.0.0 dans l'ordre des versions.

Tu avais trouvé ?

Exercice 2 sur 4 Calculer la version à partir des commits Réussi À revoir

Voici les commits fusionnés sur main depuis le tag v2.4.1 :

text
fix: arrondit les indemnités au centime
docs: complète le README
feat: ajoute l'export CSV des notes de frais
chore: met à jour Jest
ci: ajoute l'analyse CodeQL
  1. Quel numéro release-please proposera-t-il ?
  2. Et si l'un des messages avait été feat!: supprime la route /notes/export-ancien ?
Voir la réponseMasquer la réponse de l'exercice 2
  1. Il y a un feat : incrément MINEUR, 2.5.0. Le CHANGELOG aura une section « Fonctionnalités » (l'export CSV) et une section « Corrections » (l'arrondi) ; docs, chore et ci n'y figurent pas par défaut.
  2. Le ! signale un changement incompatible : incrément MAJEUR, 3.0.0.

Tu avais trouvé ?

Exercice 3 sur 4 Une publication refusée Réussi À revoir

Hugo crée le tag v2.5.0 à la main avec git tag v2.5.0 puis git push origin v2.5.0. Le job verifier du workflow « Publication » échoue :

text
Run test "v$(node -p "require('./package.json').version")" = "$GITHUB_REF_NAME"
Error: Process completed with exit code 1.
  1. Que s'est-il passé ?
  2. Que doit faire Hugo pour publier correctement ?
Voir la réponseMasquer la réponse de l'exercice 3
  1. La commande compare v suivi de la version de package.json au nom du tag. Hugo n'a pas mis à jour package.json, qui indique toujours 2.4.1 : v2.4.1 est différent de v2.5.0, test renvoie 1, l'étape échoue, et la release n'est pas créée. C'est exactement le rôle de cette vérification : sans elle, l'application publiée en 2.5.0 afficherait 2.4.1 dans sa route /version.
  2. Supprimer le tag erroné, en local et sur GitHub (git tag -d v2.5.0 puis git push origin --delete v2.5.0), ce qui est possible ici parce que rien n'a été publié. Puis lancer npm version minor, qui met à jour package.json, commite et crée le tag v2.5.0 cohérent, et git push --follow-tags. Une fois une version vraiment publiée, on ne supprime plus son tag : on publie un correctif.

Tu avais trouvé ?

Exercice 4 sur 4 Écrire un workflow de publication Réussi À revoir

Pour l'application C# GestStock, écris un workflow qui, sur un tag v*.*.* : compile en Release sous Windows avec .NET 10, publie l'application dans un dossier publication, le compresse en GestStock-<tag>.zip, et crée une release GitHub avec ce fichier.

Voir la réponseMasquer la réponse de l'exercice 4
yaml
name: Publication

on:
  push:
    tags: ['v*.*.*']

permissions:
  contents: read

jobs:
  release:
    runs-on: windows-latest
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-dotnet@v6
        with:
          dotnet-version: '10.0.x'
      - run: dotnet publish GestStock/GestStock.csproj --configuration Release --output publication
      - name: Compresser
        shell: pwsh
        run: Compress-Archive -Path publication/* -DestinationPath "GestStock-$env:GITHUB_REF_NAME.zip"
      - name: Créer la release
        shell: pwsh
        env:
          GH_TOKEN: ${{ github.token }}
        run: gh release create $env:GITHUB_REF_NAME "GestStock-$env:GITHUB_REF_NAME.zip" --generate-notes --verify-tag

Sous Windows, le shell par défaut est PowerShell : les variables d'environnement s'y lisent avec $env:NOM. dotnet publish restaure et compile avant de publier. contents: write n'est donné qu'au job qui crée la release.

Tu avais trouvé ?

Chapitre terminé !

Fais les 4 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Chapitre suivant : Exercices corrigés CI/CD : workflows et journaux d'erreursRevoir le sommaire du cours