chore: remove empty v1.2.1 changelog entry before re-release #5
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| push: | |
| tags: | |
| - 'v[0-9]+.[0-9]+.[0-9]+' # Only match semver tags (v1.2.3), NOT v1.2.3+456 | |
| permissions: | |
| contents: write | |
| jobs: | |
| build-and-release: | |
| name: Build Android & Create Release | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| fetch-tags: true | |
| - name: Extract version from tag | |
| id: version | |
| run: | | |
| FULL_TAG=${GITHUB_REF#refs/tags/} | |
| # Strip 'v' prefix and any build metadata (+NNN) | |
| TAG=$(echo "${FULL_TAG#v}" | cut -d'+' -f1) | |
| echo "tag=$TAG" >> $GITHUB_OUTPUT | |
| echo "full_tag=$FULL_TAG" >> $GITHUB_OUTPUT | |
| # Get the previous tag for changelog range | |
| PREV_TAG=$(git tag --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | sed -n '2p') | |
| if [ -z "$PREV_TAG" ]; then | |
| echo "prev_tag=" >> $GITHUB_OUTPUT | |
| echo "is_first=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "prev_tag=$PREV_TAG" >> $GITHUB_OUTPUT | |
| echo "is_first=false" >> $GITHUB_OUTPUT | |
| fi | |
| echo "Parsed: tag=$TAG full_tag=$FULL_TAG prev_tag=$PREV_TAG" | |
| - name: Generate changelog and release notes | |
| id: changelog | |
| run: | | |
| TAG=${{ steps.version.outputs.full_tag }} | |
| PREV_TAG=${{ steps.version.outputs.prev_tag }} | |
| IS_FIRST=${{ steps.version.outputs.is_first }} | |
| if [ "$IS_FIRST" = "true" ]; then | |
| COMMITS=$(git log --pretty=format:"- %s (%h)" --no-merges) | |
| else | |
| COMMITS=$(git log --pretty=format:"- %s (%h)" --no-merges "${PREV_TAG}..${TAG}") | |
| fi | |
| # Categorize commits | |
| FEATURES=$(echo "$COMMITS" | grep -iE "^- (feat|add|new)" || true) | |
| FIXES=$(echo "$COMMITS" | grep -iE "^- (fix|bug|patch|hotfix)" || true) | |
| DOCS=$(echo "$COMMITS" | grep -iE "^- (doc|docs|readme)" || true) | |
| OTHER=$(echo "$COMMITS" | grep -ivE "^- (feat|add|new|fix|bug|patch|hotfix|doc|docs|readme|chore: update version|chore: update changelog)" || true) | |
| # --- RELEASE_NOTES.md: categorized notes for internal use --- | |
| { | |
| echo "## What's New in $TAG" | |
| echo "" | |
| if [ -n "$FEATURES" ]; then | |
| echo "### β¨ New Features" | |
| echo "$FEATURES" | |
| echo "" | |
| fi | |
| if [ -n "$FIXES" ]; then | |
| echo "### π Bug Fixes" | |
| echo "$FIXES" | |
| echo "" | |
| fi | |
| if [ -n "$DOCS" ]; then | |
| echo "### π Documentation" | |
| echo "$DOCS" | |
| echo "" | |
| fi | |
| if [ -n "$OTHER" ]; then | |
| echo "### π§ Other Changes" | |
| echo "$OTHER" | |
| echo "" | |
| fi | |
| if [ "$IS_FIRST" != "true" ]; then | |
| echo "**Full Changelog**: https://github.com/${{ github.repository }}/compare/${PREV_TAG}...${TAG}" | |
| fi | |
| } > RELEASE_NOTES.md | |
| echo "=== RELEASE_NOTES.md ===" | |
| cat RELEASE_NOTES.md | |
| - name: Generate release description | |
| run: | | |
| TAG=${{ steps.version.outputs.full_tag }} | |
| # Read categorized sections from RELEASE_NOTES.md | |
| FEATURES=$(sed -n '/### β¨/,/^###\|^$/p' RELEASE_NOTES.md | grep "^-" || true) | |
| FIXES=$(sed -n '/### π/,/^###\|^$/p' RELEASE_NOTES.md | grep "^-" || true) | |
| { | |
| echo "# β‘ Mostro Mobile ${TAG}" | |
| echo "" | |
| echo "A new version of the Mostro P2P Bitcoin trading app is here!" | |
| echo "" | |
| if [ -n "$FEATURES" ]; then | |
| FEATURE_COUNT=$(echo "$FEATURES" | wc -l) | |
| echo "This release brings **${FEATURE_COUNT} new feature(s)**:" | |
| echo "" | |
| echo "$FEATURES" | |
| echo "" | |
| fi | |
| if [ -n "$FIXES" ]; then | |
| FIX_COUNT=$(echo "$FIXES" | wc -l) | |
| echo "We also squashed **${FIX_COUNT} bug(s)** π:" | |
| echo "" | |
| echo "$FIXES" | |
| echo "" | |
| fi | |
| echo "---" | |
| echo "" | |
| echo "### π₯ Install" | |
| echo "Download the APK for your device architecture below:" | |
| echo "- \`mostro-${TAG}-arm64-v8a.apk\` β most modern Android phones (64-bit)" | |
| echo "- \`mostro-${TAG}-armeabi-v7a.apk\` β older Android phones (32-bit)" | |
| echo "" | |
| echo "> The \`.aab\` (Android App Bundle) is for Google Play Store distribution." | |
| echo "" | |
| echo "### π₯οΈ Desktop" | |
| echo "Linux and macOS builds are triggered separately and will be attached to this release shortly." | |
| echo "" | |
| echo "### π Full Changelog" | |
| echo "See [CHANGELOG.md](https://github.com/${{ github.repository }}/blob/main/CHANGELOG.md) for the complete history." | |
| } > RELEASE_BODY.md | |
| echo "=== RELEASE_BODY.md ===" | |
| cat RELEASE_BODY.md | |
| - name: Commit changelog and version bump to main | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git fetch origin main | |
| git checkout main | |
| git merge --ff-only ${{ steps.version.outputs.full_tag }} | |
| # Apply version bump β use clean semver (no build metadata) | |
| VERSION=${{ steps.version.outputs.tag }} | |
| BUILD=$(git rev-list --count HEAD) | |
| sed -i "s/^version: .*/version: ${VERSION}+${BUILD}/" pubspec.yaml | |
| echo "Updated pubspec.yaml to version: ${VERSION}+${BUILD}" | |
| # Generate CHANGELOG.md on main (where the existing CHANGELOG lives) | |
| DATESTAMP=$(date -u +"%Y-%m-%d") | |
| TAG=${{ steps.version.outputs.full_tag }} | |
| # Read categorized commits from RELEASE_NOTES.md (generated earlier, still in working tree) | |
| FEATURES=$(sed -n '/### β¨/,/^###\|^$/p' RELEASE_NOTES.md | grep "^-" || true) | |
| FIXES=$(sed -n '/### π/,/^###\|^$/p' RELEASE_NOTES.md | grep "^-" || true) | |
| DOCS=$(sed -n '/### π/,/^###\|^$/p' RELEASE_NOTES.md | grep "^-" || true) | |
| OTHER=$(sed -n '/### π§/,/^###\|^$/p' RELEASE_NOTES.md | grep "^-" || true) | |
| { | |
| echo "# Changelog" | |
| echo "" | |
| echo "All notable changes to this project will be documented in this file." | |
| echo "" | |
| echo "The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)," | |
| echo "and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html)." | |
| echo "" | |
| echo "## [$TAG] - $DATESTAMP" | |
| echo "" | |
| if [ -n "$FEATURES" ]; then | |
| echo "### Added" | |
| echo "$FEATURES" | |
| echo "" | |
| fi | |
| if [ -n "$FIXES" ]; then | |
| echo "### Fixed" | |
| echo "$FIXES" | |
| echo "" | |
| fi | |
| if [ -n "$DOCS" ]; then | |
| echo "### Documentation" | |
| echo "$DOCS" | |
| echo "" | |
| fi | |
| if [ -n "$OTHER" ]; then | |
| echo "### Changed" | |
| echo "$OTHER" | |
| echo "" | |
| fi | |
| # Append previous changelog content (skip the header block β first 6 lines) | |
| if [ -f CHANGELOG.md ]; then | |
| tail -n +7 CHANGELOG.md | |
| fi | |
| } > CHANGELOG_NEW.md | |
| mv CHANGELOG_NEW.md CHANGELOG.md | |
| git add pubspec.yaml CHANGELOG.md | |
| git diff --cached --quiet || git commit -m "chore: update changelog and version for ${{ steps.version.outputs.full_tag }}" | |
| git push origin main | |
| - name: Checkout tag for build | |
| run: git checkout ${{ steps.version.outputs.full_tag }} | |
| - name: Verify signing secrets | |
| run: | | |
| missing="" | |
| [ -z "${{ secrets.ANDROID_KEYSTORE_FILE }}" ] && missing="$missing ANDROID_KEYSTORE_FILE" | |
| [ -z "${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" ] && missing="$missing ANDROID_KEYSTORE_PASSWORD" | |
| [ -z "${{ secrets.ANDROID_KEY_PASSWORD }}" ] && missing="$missing ANDROID_KEY_PASSWORD" | |
| [ -z "${{ secrets.ANDROID_KEY_ALIAS }}" ] && missing="$missing ANDROID_KEY_ALIAS" | |
| if [ -n "$missing" ]; then | |
| echo "::error::Missing signing secrets:$missing" | |
| exit 1 | |
| fi | |
| - name: Setup Java | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: 'temurin' | |
| java-version: '17' | |
| - name: Setup Android keystore | |
| run: | | |
| echo "${{ secrets.ANDROID_KEYSTORE_FILE }}" | base64 --decode > android/app/upload-keystore.jks | |
| echo "storePassword=${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" > android/key.properties | |
| echo "keyPassword=${{ secrets.ANDROID_KEY_PASSWORD }}" >> android/key.properties | |
| echo "keyAlias=${{ secrets.ANDROID_KEY_ALIAS }}" >> android/key.properties | |
| echo "storeFile=upload-keystore.jks" >> android/key.properties | |
| - name: Setup Flutter | |
| uses: subosito/flutter-action@v2 | |
| with: | |
| flutter-version: '3.32.2' | |
| channel: stable | |
| cache: true | |
| - name: Configure Flutter minSdkVersion | |
| run: | | |
| set -euo pipefail | |
| mkdir -p android | |
| touch android/local.properties | |
| if grep -qE '^flutter\.minSdkVersion=' android/local.properties; then | |
| sed -i 's/^flutter\.minSdkVersion=.*/flutter.minSdkVersion=23/' android/local.properties | |
| else | |
| echo "flutter.minSdkVersion=23" >> android/local.properties | |
| fi | |
| - name: Install dependencies | |
| run: flutter pub get | |
| - name: Generate localization and other required files | |
| run: dart run build_runner build -d | |
| - name: Analyze | |
| run: flutter analyze | |
| - name: Run tests | |
| run: flutter test | |
| - name: Update version in pubspec for build | |
| run: | | |
| # Use clean semver from tag β never read from pubspec (avoids double +BUILD) | |
| VERSION=${{ steps.version.outputs.tag }} | |
| BUILD=$(git rev-list --count HEAD) | |
| sed -i "s/^version: .*/version: ${VERSION}+${BUILD}/" pubspec.yaml | |
| echo "Building with version: ${VERSION}+${BUILD}" | |
| - name: Build APK (split per ABI) | |
| run: flutter build apk --split-per-abi --dart-define=APP_VERSION=${{ steps.version.outputs.tag }} --dart-define=GIT_COMMIT=${{ github.sha }} | |
| env: | |
| ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} | |
| ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} | |
| ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} | |
| ANDROID_KEYSTORE_FILE: upload-keystore.jks | |
| - name: Build appBundle | |
| run: flutter build appbundle --release --dart-define=APP_VERSION=${{ steps.version.outputs.tag }} --dart-define=GIT_COMMIT=${{ github.sha }} | |
| env: | |
| ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} | |
| ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} | |
| ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} | |
| ANDROID_KEYSTORE_FILE: upload-keystore.jks | |
| - name: Rename split APKs | |
| run: | | |
| set -euo pipefail | |
| VERSION=${{ steps.version.outputs.full_tag }} | |
| mv build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk \ | |
| build/app/outputs/flutter-apk/mostro-${VERSION}-armeabi-v7a.apk | |
| mv build/app/outputs/flutter-apk/app-arm64-v8a-release.apk \ | |
| build/app/outputs/flutter-apk/mostro-${VERSION}-arm64-v8a.apk | |
| ls -lh build/app/outputs/flutter-apk/mostro-${VERSION}-*.apk | |
| - name: Verify APK signing | |
| run: | | |
| set -euo pipefail | |
| VERSION=${{ steps.version.outputs.full_tag }} | |
| SDK_ROOT="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}" | |
| APKSIGNER=$(find "$SDK_ROOT"/build-tools -name apksigner -type f 2>/dev/null | sort -V | tail -1 || true) | |
| for ARCH in armeabi-v7a arm64-v8a; do | |
| APK="build/app/outputs/flutter-apk/mostro-${VERSION}-${ARCH}.apk" | |
| echo "Verifying ${ARCH}..." | |
| jarsigner -verify "$APK" > /dev/null | |
| if [ -n "$APKSIGNER" ]; then | |
| "$APKSIGNER" verify --print-certs "$APK" | |
| fi | |
| echo "β ${ARCH} verified" | |
| done | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: Releases | |
| path: | | |
| build/app/outputs/flutter-apk/mostro-${{ steps.version.outputs.full_tag }}-armeabi-v7a.apk | |
| build/app/outputs/flutter-apk/mostro-${{ steps.version.outputs.full_tag }}-arm64-v8a.apk | |
| build/app/outputs/bundle/release/app-release.aab | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| body_path: RELEASE_BODY.md | |
| files: | | |
| build/app/outputs/flutter-apk/mostro-${{ steps.version.outputs.full_tag }}-armeabi-v7a.apk | |
| build/app/outputs/flutter-apk/mostro-${{ steps.version.outputs.full_tag }}-arm64-v8a.apk | |
| build/app/outputs/bundle/release/app-release.aab | |
| generate_release_notes: false | |
| draft: false | |
| prerelease: false | |
| - name: Trigger desktop builds | |
| uses: peter-evans/repository-dispatch@v3 | |
| with: | |
| event-type: build-desktop | |
| client-payload: '{"version": "${{ steps.version.outputs.tag }}"}' | |
| - name: Cleanup keystore | |
| if: always() | |
| run: rm -f android/app/upload-keystore.jks android/key.properties |