diff --git a/.github/workflows/docker-publish-android.yml b/.github/workflows/docker-publish-android.yml index 317cda80..ebf52a6d 100644 --- a/.github/workflows/docker-publish-android.yml +++ b/.github/workflows/docker-publish-android.yml @@ -5,8 +5,14 @@ on: branches: - main tags: - - 'v*' + - '*' 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 # pour publier une release + uploader un asset. @@ -41,11 +47,113 @@ jobs: path: ./PostIt.Android.apk retention-days: 7 - publish-release: - # Uniquement déclenché par un tag v*. Le job apk-deploy tourne en - # parallèle, on partage l'artefact entre jobs. + # Job de validation : parse le tag, vérifie le format, applique la règle + # de parité du patch (pair=stable / impair=preview / suffixe=instable), + # 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/') - 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 <> "$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 steps: - name: Récupérer l'APK depuis l'artefact @@ -61,7 +169,9 @@ jobs: # apparaîtra dans l'asset et donc dans le permalink : # https://github.com///releases/latest/download/PostIt.Android.apk files: ./PostIt.Android.apk - # generate_release_notes: true -> évite d'avoir à maintenir - # le corps de release à la main. Décommente si tu veux. - # generate_release_notes: true - + # Le body est extrait de la section CHANGELOG.md correspondant + # au tag, exposée par validate-release via $GITHUB_ENV. + body: ${{ env.RELEASE_BODY }} + # stable -> false (marque comme Latest). + # preview / unstable -> true (visible mais pas Latest). + prerelease: ${{ env.IS_PRERELEASE }} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..eb596070 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,29 @@ +# Changelog + +Toutes les modifications notables de PostIt et de la plateforme Yavsc +sont documentées dans ce fichier. + +Le format suit [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/), +et ce projet adhère au [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +À noter : la **parité du numéro de patch** porte une signification de canal : + +- **patch pair** (ex. `1.0.0`, `1.0.2`) → **stable** +- **patch impair** (ex. `1.0.1`, `1.0.3`) → **preview** +- **suffixe** (ex. `1.0.0-rc1`, `1.0.0-alpha`) → **instable** + +Cette convention est partagée avec le dépôt +[`postit-debian`](https://forgejo.pschneider.fr/notazof/postit-debian) +pour la production des paquets `.deb`. + +## [Unreleased] + +### Added + +### Changed + +### Fixed + +### Removed + +[Unreleased]: https://github.com/pazof/yavsc/compare/HEAD