- Notifications
You must be signed in to change notification settings - Fork 0
feat: Implement automated release workflow for Android APKs, includin…#15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| name: Release Build | ||
| on: | ||
| push: | ||
| branches: | ||
| - main | ||
| jobs: | ||
| release: | ||
| name: Build and Release APK | ||
| if: github.actor != 'github-actions[bot]' | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: write # Required to create releases and push commits | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 # Fetch all history for proper versioning | ||
| token: ${{ secrets.GITHUB_TOKEN }} | ||
| - name: Set up Java | ||
| uses: actions/setup-java@v4 | ||
| with: | ||
| distribution: 'temurin' | ||
| java-version: '17' | ||
| - name: Set up Flutter | ||
| uses: subosito/flutter-action@v2 | ||
| with: | ||
| flutter-version: '3.27.2' | ||
| channel: 'stable' | ||
| cache: true | ||
| - name: Install dependencies | ||
| working-directory: ./workout-logger | ||
| run: flutter pub get | ||
| - name: Bump version | ||
| id: bump_version | ||
| run: | | ||
| dart scripts/bump_version.dart patch | ||
| echo "Version bumped successfully" | ||
Devasy marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| - name: Configure Git | ||
| run: | | ||
| git config --global user.name "github-actions[bot]" | ||
| git config --global user.email "github-actions[bot]@users.noreply.github.com" | ||
| - name: Commit version bump | ||
| id: commit_version | ||
| run: | | ||
| git add workout-logger/pubspec.yaml | ||
| if git diff --staged --quiet; then | ||
| echo "No changes to commit" | ||
| echo "committed=false" >> $GITHUB_OUTPUT | ||
| else | ||
| git commit -m "chore: bump version to ${{ steps.bump_version.outputs.version }}" | ||
| git push origin main | ||
| echo "committed=true" >> $GITHUB_OUTPUT | ||
| fi | ||
| - name: Create Git tag | ||
| if: steps.commit_version.outputs.committed == 'true' | ||
| run: | | ||
| git tag "v${{ steps.bump_version.outputs.version }}" | ||
| git push origin "v${{ steps.bump_version.outputs.version }}" | ||
coderabbitai[bot] marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| - name: Build APK | ||
| working-directory: ./workout-logger | ||
| run: flutter build apk --release | ||
| - name: Rename APK | ||
| run: | | ||
| mv workout-logger/build/app/outputs/flutter-apk/app-release.apk \ | ||
| workout-logger/build/app/outputs/flutter-apk/repforge-v${{ steps.bump_version.outputs.version }}.apk | ||
| - name: Create GitHub Release | ||
| uses: softprops/action-gh-release@v2 | ||
| with: | ||
| tag_name: v${{ steps.bump_version.outputs.version }} | ||
| name: RepForge v${{ steps.bump_version.outputs.version }} | ||
| body: | | ||
| ## RepForge v${{ steps.bump_version.outputs.version }} | ||
| ### 📱 Download | ||
| Download the APK file below to install on your Android device. | ||
| ### 🔄 Changes | ||
| This release was automatically generated from the latest changes merged to main. | ||
| ### 📊 Build Information | ||
| - **Version**: ${{ steps.bump_version.outputs.version }} | ||
| - **Build Date**: ${{ github.event.head_commit.timestamp }} | ||
| - **Commit**: ${{ github.sha }} | ||
| files: | | ||
| workout-logger/build/app/outputs/flutter-apk/repforge-v${{ steps.bump_version.outputs.version }}.apk | ||
| draft: false | ||
| prerelease: false | ||
| env: | ||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,168 @@ | ||
| # Automated Release Workflow | ||
| This repository uses GitHub Actions to automatically create releases when PRs are merged to the `main` branch. | ||
| ## 🚀 How It Works | ||
| 1. **PR Merge**: When a pull request is merged to `main`, the workflow triggers automatically | ||
| 2. **Version Bump**: The script increments the patch version and build number in `pubspec.yaml` | ||
| 3. **Commit & Tag**: Changes are committed and a new git tag is created (e.g., `v1.0.3`) | ||
| 4. **Build APK**: Flutter builds a release APK for Android | ||
| 5. **Create Release**: A GitHub release is created with the APK attached | ||
| ## 📋 Version Bumping Strategy | ||
| - **Automatic**: Patch version increments on every merge (e.g., `1.0.2` → `1.0.3`) | ||
| - **Build number**: Also increments automatically (e.g., `+3` → `+4`) | ||
| - **Manual bumps**: For minor/major version changes, edit `pubspec.yaml` manually before merging | ||
| ### Version Format | ||
| ```yaml | ||
| version: MAJOR.MINOR.PATCH+BUILD | ||
| # Example: 1.0.2+3 | ||
| ``` | ||
coderabbitai[bot] marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| ## 🔧 Setup Instructions | ||
| ### 1. Enable GitHub Actions | ||
| Ensure GitHub Actions is enabled in your repository settings: | ||
Devasy marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| - Go to **Settings** → **Actions** → **General** | ||
| - Under "Actions permissions", select "Allow all actions and reusable workflows" | ||
| ### 2. Configure Branch Protection (Optional but Recommended) | ||
| If you have branch protection on `main`: | ||
| - Go to **Settings** → **Branches** → **Branch protection rules** | ||
| - Edit the rule for `main` | ||
| - Under "Allow specified actors to bypass required pull requests", add `github-actions[bot]` | ||
| - This allows the workflow to push version bump commits | ||
| ### 3. Verify Workflow Permissions | ||
| The workflow needs write permissions to create releases: | ||
| - Go to **Settings** → **Actions** → **General** | ||
| - Under "Workflow permissions", ensure "Read and write permissions" is selected | ||
| - Check "Allow GitHub Actions to create and approve pull requests" (optional) | ||
| ## 📦 First Release | ||
| To trigger your first automated release: | ||
| 1. Create a feature branch: | ||
| ```bash | ||
| git checkout -b feature/test-release | ||
| ``` | ||
| 2. Make any change (or just update README): | ||
| ```bash | ||
| echo "# Test" >> README.md | ||
| git add README.md | ||
| git commit -m "test: trigger first automated release" | ||
| git push origin feature/test-release | ||
| ``` | ||
| 3. Create and merge a PR to `main` | ||
| 4. Check the **Actions** tab to see the workflow running | ||
| 5. Once complete, check the **Releases** section for your new release with APK | ||
| ## 📱 Installing the APK | ||
| After each release: | ||
| 1. Go to **Releases** in your GitHub repository | ||
| 2. Download the latest `repforge-vX.X.X.apk` file | ||
| 3. Transfer to your Android device | ||
| 4. Enable "Install from unknown sources" in Android settings | ||
| 5. Install the APK | ||
| ## 🔐 Production Signing (Recommended) | ||
| Currently, the APK is signed with debug keys. For production releases: | ||
| 1. Generate a release keystore: | ||
| ```bash | ||
| keytool -genkey -v -keystore release-keystore.jks -keyalg RSA -keysize 2048 -validity 10000 -alias release | ||
| ``` | ||
| 2. Add keystore to GitHub Secrets: | ||
| - Encode keystore: `base64 release-keystore.jks > keystore.txt` | ||
| - Add to **Settings** → **Secrets** → **Actions**: | ||
| - `KEYSTORE_BASE64`: Contents of `keystore.txt` | ||
| - `KEYSTORE_PASSWORD`: Your keystore password | ||
| - `KEY_ALIAS`: Your key alias (e.g., "release") | ||
| - `KEY_PASSWORD`: Your key password | ||
| 3. Update `android/app/build.gradle.kts` to use release signing | ||
| 4. Update the workflow to decode and use the keystore | ||
| ## 🛠️ Manual Version Bumping | ||
| To manually control version numbers: | ||
| ### Bump Minor Version (e.g., 1.0.3 → 1.1.0) | ||
| Edit `pubspec.yaml` before merging: | ||
| ```yaml | ||
| version: 1.1.0+5 | ||
| ``` | ||
| ### Bump Major Version (e.g., 1.1.0 → 2.0.0) | ||
| Edit `pubspec.yaml` before merging: | ||
| ```yaml | ||
| version: 2.0.0+6 | ||
| ``` | ||
| The workflow will still increment from whatever version you set. | ||
| ## 📊 Monitoring Releases | ||
| - **Actions Tab**: View workflow runs and logs | ||
| - **Releases Tab**: See all published releases | ||
| - **Tags**: View all version tags in the repository | ||
| ## 🐛 Troubleshooting | ||
| ### Workflow fails with "Permission denied" | ||
| - Check that workflow permissions are set to "Read and write" | ||
| - Verify branch protection settings allow `github-actions[bot]` to push | ||
| ### APK not attached to release | ||
| - Check the workflow logs in the Actions tab | ||
| - Verify the build step completed successfully | ||
| - Ensure the APK path in the workflow matches the actual build output | ||
| ### Version not bumping | ||
| - Check that `scripts/bump_version.dart` has execute permissions | ||
| - Verify the script can parse your `pubspec.yaml` format | ||
| - Review workflow logs for script errors | ||
| ## 📝 Files Created | ||
| - `.github/workflows/release.yml` - Main workflow configuration | ||
| - `scripts/bump_version.dart` - Version bumping script | ||
| - `docs/RELEASE_WORKFLOW.md` - This documentation | ||
| ## 🔄 Workflow Diagram | ||
| ```text | ||
| PR Merged to main | ||
| ↓ | ||
| Checkout code | ||
| ↓ | ||
| Setup Flutter & Java | ||
| ↓ | ||
| Bump version in pubspec.yaml | ||
| ↓ | ||
| Commit & push version change | ||
| ↓ | ||
| Create git tag (v1.0.3) | ||
| ↓ | ||
| Build release APK | ||
| ↓ | ||
| Create GitHub Release | ||
| ↓ | ||
| Upload APK to release | ||
| ↓ | ||
| ✅ Done! | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| import 'dart:io'; | ||
| /// Script to automatically bump version in pubspec.yaml | ||
| /// Increments build number by default. | ||
| /// Increments patch version ONLY if 'patch' argument is provided. | ||
| /// Usage: dart scripts/bump_version.dart [patch] | ||
| void main(List<String> args) async { | ||
| final pubspecFile = File('workout-logger/pubspec.yaml'); | ||
| if (!await pubspecFile.exists()) { | ||
| print('Error: pubspec.yaml not found'); | ||
| exit(1); | ||
| } | ||
| final content = await pubspecFile.readAsString(); | ||
| final lines = content.split('\n'); | ||
| String? newVersion; | ||
| final updatedLines = <String>[]; | ||
| // Check if patch bump is requested | ||
| final shouldBumpPatch = args.contains('patch'); | ||
| for (var line in lines) { | ||
| if (line.startsWith('version:')) { | ||
| // Extract current version (format: version: 1.0.2+3 or 1.0.2) | ||
| // Group 1: Major, Group 2: Minor, Group 3: Patch, Group 4: Build (optional) | ||
| final versionMatch = RegExp( | ||
| r'version:\s*(\d+)\.(\d+)\.(\d+)(?:\+(\d+))?', | ||
| ).firstMatch(line); | ||
coderabbitai[bot] marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| if (versionMatch == null) { | ||
| print('Error: Could not parse version from: $line'); | ||
| exit(1); | ||
| } | ||
| final major = int.parse(versionMatch.group(1)!); | ||
| final minor = int.parse(versionMatch.group(2)!); | ||
| final patch = int.parse(versionMatch.group(3)!); | ||
| final build = int.parse(versionMatch.group(4) ?? '0'); | ||
| // Calculate new versions | ||
| final newPatch = shouldBumpPatch ? patch + 1 : patch; | ||
| final newBuild = build + 1; | ||
| newVersion = '$major.$minor.$newPatch+$newBuild'; | ||
| updatedLines.add('version: $newVersion'); | ||
| final oldVersionStr = | ||
| '${versionMatch.group(1)}.${versionMatch.group(2)}.${versionMatch.group(3)}' + | ||
| (versionMatch.group(4) != null ? '+${versionMatch.group(4)}' : ''); | ||
| print('Bumping version: $oldVersionStr → $newVersion'); | ||
| } else { | ||
| updatedLines.add(line); | ||
| } | ||
| } | ||
| if (newVersion == null) { | ||
| print('Error: Version line not found in pubspec.yaml'); | ||
| exit(1); | ||
| } | ||
| // Write updated content back to file | ||
| await pubspecFile.writeAsString(updatedLines.join('\n')); | ||
| // Output new version for GitHub Actions to use | ||
| print('NEW_VERSION=$newVersion'); | ||
| // Also write to GitHub Actions output if running in CI | ||
| final githubOutput = Platform.environment['GITHUB_OUTPUT']; | ||
| if (githubOutput != null) { | ||
| try { | ||
| final outputFile = File(githubOutput); | ||
| await outputFile.writeAsString( | ||
| 'version=$newVersion\n', | ||
| mode: FileMode.append, | ||
| ); | ||
| } catch (e) { | ||
| print('Warning: Could not write to GITHUB_OUTPUT: $e'); | ||
| } | ||
| } | ||
| exit(0); | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.