ci: make release preparation workflow robust (#76) #56
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: Build NexaNote Android APK | |
| # Releasing (the pubspec is the single source of truth for the version): | |
| # 1. Bump `version:` in app/pubspec.yaml (e.g. 1.0.1+3 — versionName+versionCode). | |
| # 2. Commit, then tag the matching versionName: `git tag v1.0.1 && git push --tags`. | |
| # A `vX.Y.Z` tag builds a release-mode APK, attaches it to the GitHub Release as | |
| # a stable-named asset (NexaNote-Android.apk) so Obtainium can track updates, and | |
| # titles the release "NexaNote vX.Y.Z". The job fails if the tag does not match | |
| # the pubspec versionName, or if the APK is missing. | |
| on: | |
| push: | |
| branches: ["main"] | |
| tags: ["v*"] | |
| workflow_dispatch: | |
| jobs: | |
| build-android: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| # On a vX.Y.Z tag the release version must match the app's versionName | |
| # (the part of pubspec `version:` before the `+build` suffix). Fail fast | |
| # so we never publish a release whose tag and in-app version disagree. | |
| - name: Verify tag matches pubspec version | |
| if: github.ref_type == 'tag' | |
| run: | | |
| set -euo pipefail | |
| tag_version="${GITHUB_REF_NAME#v}" | |
| pubspec_version="$(sed -n 's/^version:[[:space:]]*\([0-9][0-9.]*\).*/\1/p' app/pubspec.yaml | head -n1)" | |
| echo "Tag versionName: ${tag_version}" | |
| echo "pubspec versionName: ${pubspec_version}" | |
| if [ "${tag_version}" != "${pubspec_version}" ]; then | |
| echo "::error::Release tag/pubspec version mismatch — refusing to publish." | |
| echo " current tag version: v${tag_version} (the tag you pushed)" | |
| echo " current pubspec version: ${pubspec_version} (app/pubspec.yaml)" | |
| echo "Expected next action: tag the commit on main where app/pubspec.yaml already reads ${tag_version}, or run the Prepare release workflow to bump app/pubspec.yaml to ${tag_version} first, then re-create tag v${tag_version}." | |
| exit 1 | |
| fi | |
| - name: Setup Java | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: "21" | |
| - name: Setup Flutter | |
| uses: subosito/flutter-action@v2 | |
| with: | |
| channel: stable | |
| cache: true | |
| - name: Install dependencies | |
| run: | | |
| cd app | |
| flutter pub get | |
| # `flutter analyze` and `flutter test` are intentionally non-blocking | |
| # for the APK build job: an analyze warning or a flaky test must not | |
| # prevent shipping a tagged release. The Python test workflow is the | |
| # gating signal for backend changes; static analysis still runs here | |
| # so its output is visible in the job log, just without `set -e` | |
| # promoting a warning to a build failure. | |
| - name: Analyze (non-blocking — see comment above) | |
| run: | | |
| cd app | |
| flutter analyze || true | |
| - name: Run tests (non-blocking — see comment above) | |
| run: | | |
| cd app | |
| flutter test || true | |
| - name: Build NexaNote APK | |
| run: | | |
| cd app | |
| flutter build apk --release | |
| # Publish a stable asset name (NexaNote-Android.apk) so Obtainium can | |
| # match the APK across releases. A versioned copy is kept alongside it | |
| # for the workflow artifact / human download. Fail loudly if the build | |
| # did not actually produce an APK. | |
| - name: Prepare APK asset | |
| run: | | |
| set -euo pipefail | |
| src="app/build/app/outputs/flutter-apk/app-release.apk" | |
| if [ ! -f "${src}" ]; then | |
| echo "::error::Expected APK not found at ${src} — the release build did not produce an APK." | |
| exit 1 | |
| fi | |
| mkdir -p release | |
| cp "${src}" "release/NexaNote-Android.apk" | |
| if [ "${GITHUB_REF_TYPE}" = "tag" ]; then | |
| cp "${src}" "release/NexaNote-Android-${GITHUB_REF_NAME}.apk" | |
| else | |
| cp "${src}" "release/NexaNote-Android-dev-${GITHUB_SHA::7}.apk" | |
| fi | |
| ls -l release | |
| - name: Upload APK artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: NexaNote-Android | |
| path: release/*.apk | |
| if-no-files-found: error | |
| # Attach only the stable-named asset to the GitHub Release so Obtainium | |
| # always finds NexaNote-Android.apk. `fail_on_unmatched_files` turns a | |
| # missing APK into a failed release instead of an empty one. | |
| - name: Create GitHub Release and attach APK | |
| if: github.ref_type == 'tag' | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ github.ref_name }} | |
| name: NexaNote ${{ github.ref_name }} | |
| files: release/NexaNote-Android.apk | |
| fail_on_unmatched_files: true |