postit: validate CHANGELOG section on tag, classify stable/preview/unstable
Rend le job publish-release dépendant d'un nouveau job validate-release qui : - parse le tag (format MAJOR.MINOR.PATCH[-SUFFIX]) - classifie le canal : pair=stable, impair=preview, suffixe=instable - fail-fast sur instable sauf opt-in explicite via workflow_dispatch - vérifie que CHANGELOG.md contient une section ## [<tag>] - <canal> - expose le body de la section via $GITHUB_ENV pour le job de publication Le tag trigger passe de 'v*' à '*' (pas de préfixe sur les tags), et le corps de release GitHub est désormais curé via CHANGELOG.md plutôt que généré automatiquement. Cette convention de parité est partagée avec le dépôt postit-debian pour la production des paquets .deb (alignement à traiter dans une PR séparée).
This commit is contained in:
parent
e3987d7890
commit
99a62ebf81
1 changed files with 118 additions and 8 deletions
128
.github/workflows/docker-publish-android.yml
vendored
128
.github/workflows/docker-publish-android.yml
vendored
|
|
@ -5,8 +5,14 @@ on:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
tags:
|
tags:
|
||||||
- 'v*'
|
- '*'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
force_unstable:
|
||||||
|
description: 'Publier une release avec suffixe (ex. 1.0.0-rc1) malgré le fail-fast par défaut.'
|
||||||
|
required: false
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
|
||||||
# softprops/action-gh-release a besoin de contents: write
|
# softprops/action-gh-release a besoin de contents: write
|
||||||
# pour publier une release + uploader un asset.
|
# pour publier une release + uploader un asset.
|
||||||
|
|
@ -41,11 +47,113 @@ jobs:
|
||||||
path: ./PostIt.Android.apk
|
path: ./PostIt.Android.apk
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
publish-release:
|
# Job de validation : parse le tag, vérifie le format, applique la règle
|
||||||
# Uniquement déclenché par un tag v*. Le job apk-deploy tourne en
|
# de parité du patch (pair=stable / impair=preview / suffixe=instable),
|
||||||
# parallèle, on partage l'artefact entre jobs.
|
# et s'assure que CHANGELOG.md contient une section cohérente.
|
||||||
|
# Sans ce job, le job publish-release peut être bypassé (un attaquant
|
||||||
|
# qui contrôle un tag ne peut pas publier de release sans une section
|
||||||
|
# changelog cohérente).
|
||||||
|
validate-release:
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
needs: apk-deploy
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout du code
|
||||||
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
|
- name: Valider le tag et la section CHANGELOG
|
||||||
|
env:
|
||||||
|
FORCE_UNSTABLE: ${{ inputs.force_unstable || github.event.inputs.force_unstable || 'false' }}
|
||||||
|
run: |
|
||||||
|
TAG="${GITHUB_REF_NAME}"
|
||||||
|
|
||||||
|
# Parse semver : MAJOR.MINOR.PATCH[-SUFFIX]
|
||||||
|
if [[ ! "$TAG" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)(-.*)?$ ]]; then
|
||||||
|
echo "::error::Tag '$TAG' does not match MAJOR.MINOR.PATCH[-SUFFIX] format."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
MAJOR="${BASH_REMATCH[1]}"
|
||||||
|
MINOR="${BASH_REMATCH[2]}"
|
||||||
|
PATCH="${BASH_REMATCH[3]}"
|
||||||
|
SUFFIX="${BASH_REMATCH[4]}"
|
||||||
|
|
||||||
|
# Classification du canal par parité du patch.
|
||||||
|
# Patch pair + pas de suffixe -> stable.
|
||||||
|
# Patch impair + pas de suffixe -> preview.
|
||||||
|
# Suffixe présent -> instable.
|
||||||
|
if [[ -n "$SUFFIX" ]]; then
|
||||||
|
CHANNEL="unstable"
|
||||||
|
elif (( PATCH % 2 == 0 )); then
|
||||||
|
CHANNEL="stable"
|
||||||
|
else
|
||||||
|
CHANNEL="preview"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Tag $TAG classifié comme channel=$CHANNEL"
|
||||||
|
|
||||||
|
# Fail-fast sur instable sauf opt-in explicite via workflow_dispatch.
|
||||||
|
if [[ "$CHANNEL" == "unstable" && "$FORCE_UNSTABLE" != "true" ]]; then
|
||||||
|
echo "::error::Tag '$TAG' is unstable (suffix '$SUFFIX'). Refusing to publish."
|
||||||
|
echo "Set force_unstable=true via workflow_dispatch to override."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Lecture du CHANGELOG.md (doit exister à la racine du repo).
|
||||||
|
if [[ ! -f CHANGELOG.md ]]; then
|
||||||
|
echo "::error::CHANGELOG.md not found at repo root."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extraction de la section [TAG]. On cherche la première ligne
|
||||||
|
# commençant par '## [' qui contient '[TAG]' (entre '## [' et
|
||||||
|
# la prochaine ligne '## [' ou fin de fichier). awk en mode
|
||||||
|
# paragraphe suffit et reste POSIX.
|
||||||
|
BODY=$(awk -v tag="[$TAG]" '
|
||||||
|
/^## \[/ {
|
||||||
|
if (in_section) exit
|
||||||
|
if (index($0, tag) > 0) in_section=1
|
||||||
|
next
|
||||||
|
}
|
||||||
|
in_section { print }
|
||||||
|
' CHANGELOG.md)
|
||||||
|
|
||||||
|
if [[ -z "$BODY" ]]; then
|
||||||
|
echo "::error::No section matching '## [$TAG]' found in CHANGELOG.md."
|
||||||
|
echo "Add a '## [$TAG] - $CHANNEL' section before tagging."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Vérification cohérence du canal déclaré dans le suffixe.
|
||||||
|
# Format attendu : "## [TAG] - stable" / "- preview" / "- unstable".
|
||||||
|
if [[ "$BODY" != *" - $CHANNEL"* ]]; then
|
||||||
|
echo "::error::Section '## [$TAG]' must declare suffix '- $CHANNEL' to match tag parity."
|
||||||
|
echo "Current section body (first 5 lines):"
|
||||||
|
echo "$BODY" | head -5
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Section CHANGELOG validée pour [$TAG] - $CHANNEL"
|
||||||
|
|
||||||
|
# Exposition aux étapes suivantes via $GITHUB_ENV.
|
||||||
|
# heredoc <<EOF pour le body multi-lignes (pattern GitHub Actions).
|
||||||
|
{
|
||||||
|
echo "RELEASE_BODY<<EOF"
|
||||||
|
echo "$BODY"
|
||||||
|
echo "EOF"
|
||||||
|
echo "RELEASE_CHANNEL=$CHANNEL"
|
||||||
|
if [[ "$CHANNEL" == "stable" ]]; then
|
||||||
|
echo "IS_PRERELEASE=false"
|
||||||
|
else
|
||||||
|
echo "IS_PRERELEASE=true"
|
||||||
|
fi
|
||||||
|
} >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
publish-release:
|
||||||
|
# Déclenché uniquement par un push de tag. Le job apk-deploy produit
|
||||||
|
# l'artefact ; validate-release garantit la cohérence du tag et du
|
||||||
|
# changelog avant publication.
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
needs: [apk-deploy, validate-release]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Récupérer l'APK depuis l'artefact
|
- name: Récupérer l'APK depuis l'artefact
|
||||||
|
|
@ -61,7 +169,9 @@ jobs:
|
||||||
# apparaîtra dans l'asset et donc dans le permalink :
|
# apparaîtra dans l'asset et donc dans le permalink :
|
||||||
# https://github.com/<owner>/<repo>/releases/latest/download/PostIt.Android.apk
|
# https://github.com/<owner>/<repo>/releases/latest/download/PostIt.Android.apk
|
||||||
files: ./PostIt.Android.apk
|
files: ./PostIt.Android.apk
|
||||||
# generate_release_notes: true -> évite d'avoir à maintenir
|
# Le body est extrait de la section CHANGELOG.md correspondant
|
||||||
# le corps de release à la main. Décommente si tu veux.
|
# au tag, exposée par validate-release via $GITHUB_ENV.
|
||||||
# generate_release_notes: true
|
body: ${{ env.RELEASE_BODY }}
|
||||||
|
# stable -> false (marque comme Latest).
|
||||||
|
# preview / unstable -> true (visible mais pas Latest).
|
||||||
|
prerelease: ${{ env.IS_PRERELEASE }}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue