Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions .github/workflows/release.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
name: Release Build

on:
push:
branches:
- main
Comment thread
coderabbitai[bot] marked this conversation as resolved.

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"
Comment thread
Devasy marked this conversation as resolved.

- 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 }}"
Comment thread
coderabbitai[bot] marked this conversation as resolved.

- 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 }}
168 changes: 168 additions & 0 deletions docs/RELEASE_WORKFLOW.md
Original file line numberDiff line numberDiff 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
```
Comment thread
coderabbitai[bot] marked this conversation as resolved.

## 🔧 Setup Instructions

### 1. Enable GitHub Actions

Ensure GitHub Actions is enabled in your repository settings:
Comment thread
Devasy marked this conversation as resolved.
- 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!
```
85 changes: 85 additions & 0 deletions scripts/bump_version.dart
Original file line numberDiff line numberDiff 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);
Comment thread
coderabbitai[bot] marked this conversation as resolved.

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);
}