Skip to content

Environnements de release et versioning

Le cœur conceptuel de l'outil. release et next-version partagent la même tuyauterie (commands/shared.ts:buildReleasePlan()) : racine du dépôt → loadConfig → préconditions → computeNextVersion → suffixe staging/feature → nom de branche → ReleasePlan.

Les trois environnements

ReleaseEnv = 'staging' | 'prod' | 'feature'. La source de vérité du parsing est src/version.ts ; parseReleaseSubject() reconnaît chore(release): X.Y.Z[-staging.N|-<slug>.N] et classe l'environnement depuis la forme du pré-release.

prod

La version vient des commits conventionnels depuis le dernier tag stable (computeNextVersion dans version.ts). C'est le seul environnement qui commite un CHANGELOG.md et qui ouvre une MR avec les notes de release.

  • parseCommit() parse les Conventional Commits (types reconnus dans KNOWN_TYPES : feat, fix, perf, refactor, docs, test, build, ci, chore, revert).
  • Un commit est breaking via ! après le type/scope ou via un pied de page BREAKING CHANGE:.
  • computeNextVersion() parcourt les commits depuis le dernier tag prod et choisit le bump (major / minor / patch).

staging

Même version de base prod, plus un suffixe -staging.N. Le compteur N vient de staging.ts:nextStagingCounter(), qui prend le max sur trois signaux :

  1. Les tags git (définitif : la version a été publiée).
  2. Les sujets chore(release): récents (une release a atterri sur le trunk mais le job tag/publish a échoué avant de pousser le tag).
  3. Opt-in : les branches orphelines release/X-staging.N sur origin — uniquement quand l'appelant l'active explicitement via --bump. Sinon on préfère échouer bruyamment sur une collision de nom plutôt que de rouler silencieusement en avant et d'orpheliner des branches.

Le staging saute le changelog : il est régénéré au cut prod, donc l'écrire à chaque itération de staging ne ferait que churner CHANGELOG.md avec des entrées réécrites de toute façon.

Le flag --bump

À utiliser quand une tentative échouée a laissé une branche release/X-staging.N sur origin. Avec --bump, release --env staging balaie les branches distantes, supprime les orphelines correspondant à la version prod planifiée, et fait avancer le compteur au-delà. Sans --bump, une collision de nom de branche fait échouer la commande au lieu de la contourner.

feature — previews

Coupé depuis une branche feature/<name>, pour produire un build de preview que la CI tague X.Y.Z-<slug>.NX.Y.Z est la version actuelle du trunk.

  • Slug : le nom après feature/ avec chaque - remplacé par _ (feature.ts:slugFromBranch()) — pour garder le slug comme un seul token de pré-release semver. Rejette les branches non feature/*, un slug vide, ou un slug contenant encore un / (les noms imbriqués casseraient les refs de tag).
  • Compteur : feature.ts:nextFeatureCounter(), analogue au compteur staging mais par slug.
  • Comportement : le preview commit la montée de version (sans changelog) directement sur la branche feature/<name> et la pousse en place — pas de nouvelle branche, pas de MR. La MR de feature (déjà ouverte) couvre la revue au moment du merge. Le job tag de la CI tague le HEAD.
  • Précondition : la branche doit être à jour avec le trunk — la fusionner d'abord, ne jamais rebaser.

Le démantèlement d'une feature (branche + tous ses tags *-<slug>.*) se fait avec release-util cleanup <slug>.

Le cas hotfix — pas un quatrième environnement

Un hotfix (release-util hotfix) ne rajoute pas d'environnement : son tag est un tag de production ordinaire (X.Y.Z), son commit chore(release): X.Y.Z est classé prod par parseReleaseSubject(), et detect le range dans le canal production. Ce qui change, c'est d'où la version se calcule :

  • Version : bump patch du tag prod le plus haut atteignable depuis la branche (hotfix.ts:computeHotfixTarget(), via git tag --list --merged HEAD) — et non du plus haut global comme computeNextVersion. C'est ce qui fait marcher la boucle de re-fix : après que --apply a tagué 1.2.4 sur la branche, le prochain --apply voit 1.2.4 comme base → 1.2.5.
  • Garde de péremption : avant de taguer, hotfix.ts:assertHotfixNotStale() vérifie qu'aucun tag prod global (local ∪ origin) n'est plus haut que la cible — taguer depuis une lignée périmée déploierait un rollback de la release plus récente.
  • Interaction avec le staging en cours : un hotfix livré (1.2.4) devient la nouvelle base globale de computeNextVersion. Si le trunk porte des commits feat:, la prochaine version prod visée reste 1.3.0 et la lignée 1.3.0-staging.N continue ; si le trunk ne porte que des fix:, la base passe de 1.2.4 à 1.2.5 et le compteur staging repart à 1 (les anciens tags 1.2.4-staging.N deviennent orphelins — inoffensif). Dans tous les cas, re-couper une staging après un hotfix pour re-valider le travail en cours.
  • Non-Node : même bascule que release — sans package.json, --apply crée le commit-marqueur via commitEmpty au lieu de release-it.

Préconditions

Pour staging/prod (src/preconditions.ts), dans l'ordre : sur le trunk · arbre propre · le trunk correspond au défaut distant · synchronisé avec origin. En retard / divergé → échec ; en avance seulement → avertissement mais succès (ces commits partiront via la MR de release). Pour feature, la précondition « sur une branche feature/* » remplace « sur le trunk ».

La CLI ne tire jamais et ne fusionne jamais à votre place : si une vérification échoue parce que le trunk est en retard, c'est à vous de décider de git pull --ff-only et de réessayer.

L'invariant à un seul commit

La MR de release ne porte qu'un seul commit : le chore(release): X.Y.Z[...]. C'est ce sujet de commit que le job tag de la CI lit pour décider quoi taguer. Conséquences pratiques :

  • Ne pas squasher les MR release/* au merge — le squash réécrit le sujet et casse le tag. Voir integration-ci.md.
  • Ne pas amender le commit chore(release): après le merge — cela corrompt le pipeline de publication. Pour corriger le changelog, lander un commit de suivi sur le trunk et re-couper.

Pour la politique complète (hotfix, correction en avant), voir RELEASING.md.

Projets sans package.json (non-Node)

Le commit de version est normalement créé par release-it, qui le matérialise en stageant les fichiers qu'il modifie : sur un projet Node il monte la version dans package.json, ce qui donne quelque chose à committer. Sur un projet sans package.json, et dans les environnements qui n'écrivent pas de changelog (staging et feature, où skipChangelog: true), release-it n'a alors rien à stager et le commit chore(release): ne se crée jamais.

release.ts détecte l'absence de package.json à la racine (hasPackageJson) et, dans ce cas précis, court-circuite release-it pour créer le commit directement via commitEmpty (git commit --allow-empty, dans src/git.ts). Un commit vide est correct ici : la version vit dans le sujet du commit (lu par les compteurs staging/feature) et dans le tag que la CI pose à ce commit — aucun fichier n'a besoin de changer pour l'enregistrer. Le sujet provient toujours de plan.commitSubject (le canonique chore(release): <version>), jamais d'un éventuel commitMessage personnalisé dans .release-it.json, car ce sont précisément ces sujets chore(release): que les compteurs scannent.

Matrice de décision (après le retour anticipé de feature, env ne vaut plus que staging ou prod) :

package.jsonenvaction
présentstagingrelease-it
présentprodrelease-it
absentprodrelease-it (le CHANGELOG.md est le fichier stagé)
absentstagingcommitEmpty
absentfeaturecommitEmpty

Le chemin Node (cas nominal) reste strictement inchangé.

Contributors

No contributors

Changelog

No recent changes