name: Release on: push: tags: - 'v*' workflow_dispatch: inputs: tag: description: 'Existing release tag to update (e.g. v1.2.3)' required: true type: string remove_old_assets: description: 'Remove existing assets before uploading new ones' required: false default: true type: boolean jobs: build-android: name: Build Android runs-on: ubuntu-latest steps: #1 Checkout Repository - name: Checkout Repository uses: actions/checkout@v3 #2 Setup Java - name: Set Up Java uses: actions/setup-java@v3 with: distribution: 'zulu' java-version: '21' #3 Setup Flutter - name: Set Up Flutter uses: subosito/flutter-action@v2 with: channel: 'stable' #4 Install Dependencies - name: Install Dependencies run: flutter pub get - name: Generate Freezed Classes run: flutter pub run build_runner build --delete-conflicting-outputs - name: Determine Release Tag and Version id: meta shell: bash run: | if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then RELEASE_TAG="${{ inputs.tag }}" else RELEASE_TAG="${{ github.ref_name }}" fi echo "RELEASE_TAG=$RELEASE_TAG" >> $GITHUB_ENV # Derive VERSION_NAME (strip leading v) VERSION_NAME="${RELEASE_TAG#v}" echo "VERSION_NAME=$VERSION_NAME" >> $GITHUB_ENV # Extract current build number from pubspec.yaml if present, otherwise 1 CURRENT_VERSION_LINE=$(grep "^version:" pubspec.yaml || true) CURRENT_BUILD=$(echo "$CURRENT_VERSION_LINE" | awk -F'+' '{print $2}') if [[ -z "$CURRENT_BUILD" ]]; then CURRENT_BUILD=1; fi if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then # Rebuild mode: compute a unique, monotonic build number without committing # Use CI run number offset to avoid conflicts across multiple rebuilds BASE_BUILD=$CURRENT_BUILD RUN_NUM=${GITHUB_RUN_NUMBER:-1} BUILD_NUMBER=$((BASE_BUILD + RUN_NUM)) else # Tag build: honor the build number committed in pubspec, default to CURRENT_BUILD BUILD_NUMBER="$CURRENT_BUILD" fi echo "BUILD_NUMBER=$BUILD_NUMBER" >> $GITHUB_ENV echo "Using tag=$RELEASE_TAG version=$VERSION_NAME build=$BUILD_NUMBER" #5 Setup Keystore - name: Create Keystore run: | echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 --decode > app/conduit-keystore.jks echo "${{ secrets.ANDROID_KEY_PROPERTIES_BASE64 }}" | base64 --decode > key.properties env: ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} ANDROID_KEY_PROPERTIES_BASE64: ${{ secrets.ANDROID_KEY_PROPERTIES_BASE64 }} working-directory: android - name: Build APK run: flutter build apk --split-per-abi --release --build-name "$VERSION_NAME" --build-number "$BUILD_NUMBER" - name: Build appBundle run: flutter build appbundle --release --build-name "$VERSION_NAME" --build-number "$BUILD_NUMBER" - name: Upload Artifacts uses: actions/upload-artifact@v4 with: name: Releases path: | build/app/outputs/flutter-apk/app-arm64-v8a-release.apk build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk build/app/outputs/flutter-apk/app-x86_64-release.apk build/app/outputs/bundle/release/app-release.aab - name: Generate Release Notes id: release_notes run: | # Get the previous tag PREVIOUS_TAG=$(git tag --sort=-version:refname | head -2 | tail -1) # If no previous tag exists, use the first commit if [ -z "$PREVIOUS_TAG" ]; then PREVIOUS_TAG=$(git rev-list --max-parents=0 HEAD) fi # Generate commit messages since the previous tag echo "## What's Changed" > release_notes.md echo "" >> release_notes.md git log --pretty=format:"- %s (%h)" $PREVIOUS_TAG..${{ github.ref_name }} >> release_notes.md # Set the release notes as output echo "RELEASE_NOTES<> $GITHUB_OUTPUT cat release_notes.md >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT if: github.event_name == 'push' - name: Create or Update Release (tag push) if: github.event_name == 'push' uses: ncipollo/release-action@v1 with: artifacts: "build/app/outputs/flutter-apk/app-arm64-v8a-release.apk,build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk,build/app/outputs/flutter-apk/app-x86_64-release.apk,build/app/outputs/bundle/release/app-release.aab" tag: ${{ env.RELEASE_TAG }} token: ${{ secrets.GITHUB_TOKEN }} body: ${{ steps.release_notes.outputs.RELEASE_NOTES }} generateReleaseNotes: true allowUpdates: true makeLatest: true - name: Update Existing Release (manual rebuild) if: github.event_name == 'workflow_dispatch' uses: ncipollo/release-action@v1 with: artifacts: "build/app/outputs/flutter-apk/app-arm64-v8a-release.apk,build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk,build/app/outputs/flutter-apk/app-x86_64-release.apk,build/app/outputs/bundle/release/app-release.aab" tag: ${{ env.RELEASE_TAG }} token: ${{ secrets.GITHUB_TOKEN }} allowUpdates: true removeArtifacts: ${{ inputs.remove_old_assets }} makeLatest: true omitBodyDuringUpdate: true build-ios: name: Build iOS runs-on: macos-latest steps: #1 Checkout Repository - name: Checkout Repository uses: actions/checkout@v3 #2 Setup Flutter - name: Set Up Flutter uses: subosito/flutter-action@v2 with: channel: 'stable' #3 Install Dependencies - name: Install Dependencies run: flutter pub get - name: Generate Freezed Classes run: flutter pub run build_runner build --delete-conflicting-outputs - name: Determine Release Tag and Version id: meta shell: bash run: | if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then RELEASE_TAG="${{ inputs.tag }}" else RELEASE_TAG="${{ github.ref_name }}" fi echo "RELEASE_TAG=$RELEASE_TAG" >> $GITHUB_ENV # Derive VERSION_NAME (strip leading v) VERSION_NAME="${RELEASE_TAG#v}" echo "VERSION_NAME=$VERSION_NAME" >> $GITHUB_ENV # Extract current build number from pubspec.yaml if present, otherwise 1 CURRENT_VERSION_LINE=$(grep "^version:" pubspec.yaml || true) CURRENT_BUILD=$(echo "$CURRENT_VERSION_LINE" | awk -F'+' '{print $2}') if [[ -z "$CURRENT_BUILD" ]]; then CURRENT_BUILD=1; fi if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then # Rebuild mode: compute a unique, monotonic build number without committing # Use CI run number offset to avoid conflicts across multiple rebuilds BASE_BUILD=$CURRENT_BUILD RUN_NUM=${GITHUB_RUN_NUMBER:-1} BUILD_NUMBER=$((BASE_BUILD + RUN_NUM)) else # Tag build: honor the build number committed in pubspec, default to CURRENT_BUILD BUILD_NUMBER="$CURRENT_BUILD" fi echo "BUILD_NUMBER=$BUILD_NUMBER" >> $GITHUB_ENV echo "Using tag=$RELEASE_TAG version=$VERSION_NAME build=$BUILD_NUMBER" #4 Setup Xcode - name: Setup Xcode uses: maxim-lobanov/setup-xcode@v1 with: xcode-version: latest-stable #5 Install CocoaPods - name: Install CocoaPods run: | cd ios pod install #6 Build iOS App (without signing) - name: Build iOS App run: | flutter build ios --release --no-codesign --build-name "$VERSION_NAME" --build-number "$BUILD_NUMBER" #7 Create IPA from App Bundle - name: Create IPA run: | mkdir -p build/ios/ipa cd build/ios/Release-iphoneos mkdir Payload cp -r Runner.app Payload/ zip -r ../ipa/conduit-${{ env.VERSION_NAME }}.ipa Payload cd ../ipa shasum -a 256 conduit-${{ env.VERSION_NAME }}.ipa > conduit-${{ env.VERSION_NAME }}.ipa.sha256 #8 Upload Artifacts - name: Upload Artifacts uses: actions/upload-artifact@v4 with: name: iOS-Release path: | build/ios/ipa/*.ipa build/ios/ipa/*.sha256 #9 Update Release (tag push) - name: Update Release with IPA (tag push) if: github.event_name == 'push' uses: ncipollo/release-action@v1 with: artifacts: "build/ios/ipa/*.ipa,build/ios/ipa/*.sha256" tag: ${{ env.RELEASE_TAG }} token: ${{ secrets.GITHUB_TOKEN }} allowUpdates: true makeLatest: true #10 Update Release (manual rebuild) - name: Update Release with IPA (manual rebuild) if: github.event_name == 'workflow_dispatch' uses: ncipollo/release-action@v1 with: artifacts: "build/ios/ipa/*.ipa,build/ios/ipa/*.sha256" tag: ${{ env.RELEASE_TAG }} token: ${{ secrets.GITHUB_TOKEN }} allowUpdates: true removeArtifacts: ${{ inputs.remove_old_assets }} makeLatest: true omitBodyDuringUpdate: true