name: Build and Push Yavsc Apk on: push: branches: - main tags: - '*' 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. permissions: contents: write jobs: apk-deploy: runs-on: ubuntu-latest steps: - name: Checkout du code uses: actions/checkout@v7 # 1. Votre étape de build actuelle (on nomme l'image "postit-android") # --target build-env : on ne veut que le stage de build (qui # contient les artefacts .apk). Sans --target, Docker ciblerait # le DERNIER stage du Dockerfile (blogs-runtime, qui est une # image ASP.NET runtime sans aucun APK à extraire). - name: Build de l'image Docker run: docker build --build-arg ANDROID_TARGET_RID=android-arm64 --target build-env -t postit-android . # 2. EXTRACTION : Créer un conteneur éphémère pour copier l'APK vers l'hôte GitHub - name: Extraire l'APK du conteneur Docker run: | docker create --name extractor postit-android docker cp extractor:/src/src/PostIt/PostIt.Android/bin/Release/net10.0-android/android-arm64/com.CompanyName.PostIt-Signed.apk ./PostIt.Android.apk docker rm extractor - name: Téléverser l'APK en tant qu'Artéfact GitHub uses: actions/upload-artifact@v7 with: name: application-apk-release path: ./PostIt.Android.apk retention-days: 7 # 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/') 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 uses: actions/download-artifact@v7 with: name: application-apk-release path: ./ - name: Publier la release GitHub et uploader l'APK uses: softprops/action-gh-release@v2 with: # Le nom de fichier final dans la release. C'est ce qui # apparaîtra dans l'asset et donc dans le permalink : # https://github.com///releases/latest/download/PostIt.Android.apk files: ./PostIt.Android.apk # 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 }}