Skip to content

Commit 1131a65

Browse files
committed
Configure GitHub Actions release workflow, Gemini release notes generator, and RELEASE_GUIDE
1 parent 8e8a06f commit 1131a65

4 files changed

Lines changed: 257 additions & 0 deletions

File tree

‎.github/release-prompt-template.md‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
You are an expert release manager. Generate a concise, professional, and well-structured release changelog in English based on the following git commit messages since the last release.
2+
3+
Commit messages:
4+
{{COMMIT_LOG}}
5+
6+
Instructions:
7+
1. Write a brief friendly introduction/executive summary of this release.
8+
2. Group the changes logically into categories such as:
9+
- New Features
10+
- Bug Fixes
11+
- Improvements & Performance
12+
- Other Changes
13+
3. Use bullet points for changes.
14+
4. Do not include commit hashes or author names.
15+
5. Output using clean Markdown syntax without markdown code block wrappers around the entire response.
16+
6. Always append the following Markdown table at the end of the changelog to provide build artifact details, preserving the exact placeholders:
17+
18+
### Build Artifacts
19+
20+
| File Name | Format | Download Link |
21+
| --- | --- | --- |
22+
| app-release.apk | APK (Universal) | [Download](https://github.com/{{GITHUB_REPOSITORY}}/releases/download/{{RELEASE_VERSION}}/app-release.apk) |
23+
| app-release.aab | AAB (Google Play Bundle) | [Download](https://github.com/{{GITHUB_REPOSITORY}}/releases/download/{{RELEASE_VERSION}}/app-release.aab) |
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
const { execSync } = require('child_process');
4+
5+
async function run() {
6+
const apiKey = process.env.GEMINI_API_KEY;
7+
if (!apiKey) {
8+
console.error('Error: GEMINI_API_KEY environment variable is not set.');
9+
process.exit(1);
10+
}
11+
12+
// 1. Get commit history since last tag
13+
let commitLog = '';
14+
try {
15+
// Try to get the tag before the current one (which is HEAD)
16+
const lastTag = execSync('git describe --tags --abbrev=0 HEAD^ 2>/dev/null || git describe --tags --abbrev=0 HEAD~1 2>/dev/null').toString().trim();
17+
console.log(`Generating changelog since tag: ${lastTag}`);
18+
commitLog = execSync(`git log ${lastTag}..HEAD --oneline`).toString().trim();
19+
} catch (error) {
20+
console.log('No previous tag found or error fetching it. Fetching last 50 commits instead.');
21+
try {
22+
commitLog = execSync('git log --oneline -n 50').toString().trim();
23+
} catch (gitError) {
24+
console.error('Failed to retrieve git log:', gitError.message);
25+
commitLog = 'Initial release / No commit logs found.';
26+
}
27+
}
28+
29+
if (!commitLog) {
30+
commitLog = 'No commits found since the last release.';
31+
}
32+
33+
// 2. Read prompt template
34+
const templatePath = path.join(__dirname, '..', 'release-prompt-template.md');
35+
if (!fs.existsSync(templatePath)) {
36+
console.error(`Error: Template file not found at ${templatePath}`);
37+
process.exit(1);
38+
}
39+
40+
let prompt = fs.readFileSync(templatePath, 'utf8');
41+
prompt = prompt.replace('{{COMMIT_LOG}}', commitLog);
42+
43+
console.log('--- Sending Prompt to Gemini API ---');
44+
console.log(prompt);
45+
console.log('------------------------------------');
46+
47+
// 3. Request Gemini API
48+
const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=${apiKey}`;
49+
const requestBody = {
50+
contents: [
51+
{
52+
parts: [
53+
{
54+
text: prompt
55+
}
56+
]
57+
}
58+
]
59+
};
60+
61+
try {
62+
const response = await fetch(url, {
63+
method: 'POST',
64+
headers: {
65+
'Content-Type': 'application/json'
66+
},
67+
body: JSON.stringify(requestBody)
68+
});
69+
70+
if (!response.ok) {
71+
const errorText = await response.text();
72+
throw new Error(`Gemini API returned status ${response.status}: ${errorText}`);
73+
}
74+
75+
const data = await response.json();
76+
const generatedText = data.candidates?.[0]?.content?.parts?.[0]?.text;
77+
78+
if (!generatedText) {
79+
throw new Error('Invalid response structure or empty content from Gemini API');
80+
}
81+
82+
let finalNotes = generatedText;
83+
const githubRepository = process.env.GITHUB_REPOSITORY || 'owner/repo';
84+
const releaseVersion = process.env.GITHUB_REF_NAME || 'v1.0.0';
85+
86+
finalNotes = finalNotes
87+
.replace(/\{\{GITHUB_REPOSITORY\}\}/g, githubRepository)
88+
.replace(/\{\{RELEASE_VERSION\}\}/g, releaseVersion);
89+
90+
// 4. Output the release notes to a file for use in next steps
91+
const outputPath = path.join(process.cwd(), 'release_notes.md');
92+
fs.writeFileSync(outputPath, finalNotes, 'utf8');
93+
console.log(`Release notes successfully generated and written to ${outputPath}`);
94+
} catch (error) {
95+
console.error('Failed to generate release notes:', error.message);
96+
// Fallback release notes file to prevent workflow failure
97+
const outputPath = path.join(process.cwd(), 'release_notes.md');
98+
fs.writeFileSync(outputPath, `### Commits in this Release\n\n\`\`\`\n${commitLog}\n\`\`\``, 'utf8');
99+
console.log(`Fallback release notes written to ${outputPath}`);
100+
}
101+
}
102+
103+
run();
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
name: Android Release Build
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v*'
7+
workflow_dispatch:
8+
9+
permissions:
10+
contents: write
11+
12+
jobs:
13+
build-and-release:
14+
name: Build & Publish Release
15+
runs-on: ubuntu-latest
16+
17+
steps:
18+
- name: Checkout Source Code
19+
uses: actions/checkout@v4
20+
with:
21+
fetch-depth: 0 # Fetch all history so git logs/tags are available for Gemini notes
22+
23+
- name: Set up JDK 17
24+
uses: actions/setup-java@v4
25+
with:
26+
distribution: 'zulu'
27+
java-version: '17'
28+
29+
- name: Setup Gradle
30+
uses: gradle/actions/setup-gradle@v4
31+
32+
- name: Decode Android Keystore
33+
id: decode_keystore
34+
run: |
35+
if [ -n "${{ secrets.KEYSTORE_BASE64 }}" ]; then
36+
echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 --decode > my-upload-key.jks
37+
echo "keystore_decoded=true" >> $GITHUB_OUTPUT
38+
else
39+
echo "Keystore secret KEYSTORE_BASE64 not found. Proceeding without signing or using local defaults."
40+
echo "keystore_decoded=false" >> $GITHUB_OUTPUT
41+
fi
42+
43+
- name: Build Android Release Artifacts (APK & AAB)
44+
run: ./gradlew assembleRelease bundleRelease
45+
env:
46+
KEYSTORE_PATH: ${{ steps.decode_keystore.outputs.keystore_decoded == 'true' && './my-upload-key.jks' || '' }}
47+
STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}
48+
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
49+
50+
- name: Generate Release Notes via Gemini API
51+
run: node .github/scripts/generate_release_notes.js
52+
env:
53+
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
54+
55+
- name: Create GitHub Release
56+
uses: softprops/action-gh-release@v2
57+
with:
58+
body_path: release_notes.md
59+
files: |
60+
app/build/outputs/apk/release/*.apk
61+
app/build/outputs/bundle/release/*.aab
62+
env:
63+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

‎RELEASE_GUIDE.md‎

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Developer Release and Versioning Guide
2+
3+
This document outlines the versioning scheme, release workflows, and branch management strategies used in this project to ensure consistent and automated releases.
4+
5+
## Release Workflow Overview
6+
7+
The release pipeline is fully automated using GitHub Actions. The flow goes as follows:
8+
1. Developers commit and push code to the development branches.
9+
2. Changes are merged into the main branch.
10+
3. A developer creates and pushes a Git tag following the versioning rules described below.
11+
4. GitHub Actions intercepts the tag push, initiates the build process, invokes the Gemini API to generate release notes, and publishes the release on GitHub with the generated APK and AAB binaries attached.
12+
13+
## Versioning Scheme (Semantic Versioning)
14+
15+
We follow the Semantic Versioning (SemVer 2.0.0) standard with the format `vMAJOR.MINOR.PATCH` optionally appended with a pre-release suffix:
16+
17+
`v<MAJOR>.<MINOR>.<PATCH>[-<SUFFIX>.<N>]`
18+
19+
* **MAJOR**: Incremented when you make incompatible API changes or breaking app updates.
20+
* **MINOR**: Incremented when you add functionality in a backward-compatible manner (e.g. new features).
21+
* **PATCH**: Incremented when you make backward-compatible bug fixes.
22+
23+
### Release Type Suffixes
24+
25+
The type of release is determined by the suffix appended to the version tag.
26+
27+
| Release Type | Suffix Format | Example | Target Audience / Use Case |
28+
|---|---|---|---|
29+
| Stable | No suffix | `v1.0.0` | Production release, uploaded to Google Play store production track. |
30+
| Beta | `-beta.N` | `v1.0.0-beta.1` | Public pre-release, used for open or closed beta testing groups. |
31+
| Alpha | `-alpha.N` | `v1.0.0-alpha.2` | Internal pre-release, used for internal QA and developer testing. |
32+
| Test | `-test.N` | `v1.0.0-test.1` | System validation, used to test build pipelines and CI/CD behaviors. |
33+
34+
Note: `N` is an integer starting from 1 (e.g., `beta.1`, `beta.2`).
35+
36+
## Step-by-Step Release Instructions
37+
38+
### 1. Ensure all changes are merged
39+
Ensure that the `main` branch is up to date and contains all the changes you wish to release.
40+
41+
### 2. Create a Tag Locally
42+
Create a Git tag corresponding to the release version and type.
43+
44+
```bash
45+
# Example for a Stable release
46+
git tag v1.0.0
47+
48+
# Example for a Beta release
49+
git tag v1.0.0-beta.1
50+
```
51+
52+
### 3. Push the Tag to GitHub
53+
Pushing the tag to the remote repository triggers the release workflow.
54+
55+
```bash
56+
# Push a specific tag
57+
git push origin v1.0.0
58+
```
59+
60+
### 4. Monitor the Release Build
61+
Navigate to the "Actions" tab in the GitHub repository. Look for the "Android Release Build" run.
62+
63+
The workflow will:
64+
1. Verify the code base.
65+
2. Build signed/unsigned release APKs and AABs.
66+
3. Automatically gather commit logs since the last tag.
67+
4. Request the Gemini API to format and structure the changelog based on the prompt template.
68+
5. Create a GitHub Release page containing the changelog, a download link table, and the build binaries.

0 commit comments

Comments
 (0)