Skip to content

Commit fb187c3

Browse files
graycreateclaude
andcommitted
feat: add Google Play Store automated deployment pipeline
- Enhanced release workflow with Play Store upload support - Added Fastlane configuration for flexible deployment options - Support for multiple release tracks (internal, alpha, beta, production) - Configurable release status (draft, completed) - Debug symbols upload support - Comprehensive deployment documentation - Security best practices with service account authentication 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 41070a1 commit fb187c3

File tree

8 files changed

+754
-21
lines changed

8 files changed

+754
-21
lines changed
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
name: Fastlane Deploy
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
track:
7+
description: 'Deployment track'
8+
required: true
9+
type: choice
10+
default: 'internal'
11+
options:
12+
- internal
13+
- alpha
14+
- beta
15+
- production
16+
release_status:
17+
description: 'Release status'
18+
required: false
19+
type: choice
20+
default: 'draft'
21+
options:
22+
- draft
23+
- completed
24+
25+
jobs:
26+
deploy:
27+
name: Deploy with Fastlane
28+
runs-on: ubuntu-latest
29+
30+
steps:
31+
- name: Checkout code
32+
uses: actions/checkout@v4
33+
34+
- name: Set up JDK 17
35+
uses: actions/setup-java@v4
36+
with:
37+
java-version: '17'
38+
distribution: 'temurin'
39+
cache: gradle
40+
41+
- name: Setup Ruby
42+
uses: ruby/setup-ruby@v1
43+
with:
44+
ruby-version: '3.2'
45+
bundler-cache: true
46+
47+
- name: Grant execute permission for gradlew
48+
run: chmod +x gradlew
49+
50+
- name: Decode Keystore
51+
env:
52+
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
53+
run: |
54+
echo "$KEYSTORE_BASE64" | base64 --decode > ${{ github.workspace }}/keystore.jks
55+
echo "KEYSTORE_PATH=${{ github.workspace }}/keystore.jks" >> $GITHUB_ENV
56+
57+
- name: Setup Play Store credentials
58+
env:
59+
PLAY_STORE_SERVICE_ACCOUNT_JSON: ${{ secrets.PLAY_STORE_SERVICE_ACCOUNT_JSON }}
60+
run: |
61+
echo "$PLAY_STORE_SERVICE_ACCOUNT_JSON" > ${{ github.workspace }}/fastlane/play-store-key.json
62+
echo "PLAY_STORE_JSON_KEY_PATH=${{ github.workspace }}/fastlane/play-store-key.json" >> $GITHUB_ENV
63+
64+
- name: Install Fastlane
65+
run: |
66+
bundle install
67+
bundle exec fastlane --version
68+
69+
- name: Deploy to Play Store
70+
env:
71+
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
72+
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
73+
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
74+
SUPPLY_RELEASE_STATUS: ${{ github.event.inputs.release_status }}
75+
run: |
76+
case "${{ github.event.inputs.track }}" in
77+
internal)
78+
bundle exec fastlane android deploy_internal
79+
;;
80+
alpha)
81+
bundle exec fastlane android deploy_alpha
82+
;;
83+
beta)
84+
bundle exec fastlane android deploy_beta
85+
;;
86+
production)
87+
bundle exec fastlane android deploy_production
88+
;;
89+
esac
90+
91+
- name: Clean up sensitive files
92+
if: always()
93+
run: |
94+
rm -f ${{ github.workspace }}/keystore.jks
95+
rm -f ${{ github.workspace }}/fastlane/play-store-key.json
96+
97+
- name: Deployment Summary
98+
if: success()
99+
run: |
100+
echo "## Fastlane Deployment Complete :rocket:" >> $GITHUB_STEP_SUMMARY
101+
echo "" >> $GITHUB_STEP_SUMMARY
102+
echo "- **Track**: ${{ github.event.inputs.track }}" >> $GITHUB_STEP_SUMMARY
103+
echo "- **Status**: ${{ github.event.inputs.release_status }}" >> $GITHUB_STEP_SUMMARY
104+
echo "- **Package**: me.ghui.v2er" >> $GITHUB_STEP_SUMMARY
105+
echo "" >> $GITHUB_STEP_SUMMARY
106+
echo "[View in Play Console](https://play.google.com/console)" >> $GITHUB_STEP_SUMMARY

.github/workflows/release.yml

Lines changed: 122 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,24 @@ on:
1010
description: 'Version to release (e.g., v1.0.0)'
1111
required: true
1212
type: string
13+
track:
14+
description: 'Play Store release track'
15+
required: false
16+
type: choice
17+
default: 'internal'
18+
options:
19+
- internal
20+
- alpha
21+
- beta
22+
- production
23+
status:
24+
description: 'Play Store release status'
25+
required: false
26+
type: choice
27+
default: 'draft'
28+
options:
29+
- draft
30+
- completed
1331

1432
permissions:
1533
contents: write
@@ -68,18 +86,20 @@ jobs:
6886
6987
- name: Build release APK
7088
env:
71-
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
72-
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
73-
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
74-
KEYSTORE_PATH: ${{ vars.ENABLE_SIGNING == 'true' && 'keystore.jks' || '' }}
89+
GHUI_KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
90+
GHUI_KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
7591
run: |
7692
if [ "${{ vars.ENABLE_SIGNING }}" = "true" ] && [ -f "app/keystore.jks" ]; then
7793
echo "Building signed release APK"
78-
echo "Using key alias: ${KEY_ALIAS:-ghui}"
79-
./gradlew assembleRelease --stacktrace
94+
echo "Using key alias: ${{ secrets.KEY_ALIAS }}"
95+
./gradlew assembleRelease \
96+
-Pandroid.injected.signing.store.file=${{ github.workspace }}/app/keystore.jks \
97+
-Pandroid.injected.signing.store.password=${{ secrets.KEYSTORE_PASSWORD }} \
98+
-Pandroid.injected.signing.key.alias=${{ secrets.KEY_ALIAS }} \
99+
-Pandroid.injected.signing.key.password=${{ secrets.KEY_PASSWORD }}
80100
else
81101
echo "Building unsigned release APK"
82-
./gradlew assembleRelease --stacktrace || ./gradlew assembleDebug --stacktrace
102+
./gradlew assembleRelease --stacktrace
83103
fi
84104
85105
- name: Clean up keystore
@@ -137,18 +157,28 @@ jobs:
137157
138158
- name: Build release bundle
139159
env:
140-
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
141-
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
142-
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
143-
KEYSTORE_PATH: ${{ vars.ENABLE_SIGNING == 'true' && 'keystore.jks' || '' }}
160+
GHUI_KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
161+
GHUI_KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
144162
run: |
145163
if [ "${{ vars.ENABLE_SIGNING }}" = "true" ] && [ -f "app/keystore.jks" ]; then
146164
echo "Building signed release bundle"
147-
./gradlew bundleRelease --stacktrace
165+
./gradlew bundleRelease \
166+
-Pandroid.injected.signing.store.file=${{ github.workspace }}/app/keystore.jks \
167+
-Pandroid.injected.signing.store.password=${{ secrets.KEYSTORE_PASSWORD }} \
168+
-Pandroid.injected.signing.key.alias=${{ secrets.KEY_ALIAS }} \
169+
-Pandroid.injected.signing.key.password=${{ secrets.KEY_PASSWORD }}
148170
else
149171
echo "Skipping bundle build - signing not configured"
150172
fi
151173
174+
- name: Generate debug symbols
175+
if: ${{ vars.ENABLE_SIGNING == 'true' }}
176+
run: |
177+
echo "Checking for debug symbols..."
178+
find app/build/outputs -name "*.zip" -type f | grep -i debug || echo "No debug symbol zips found"
179+
find app/build/outputs -name "*symbols*" -type f || echo "No symbol files found"
180+
ls -la app/build/outputs/bundle/release/ || true
181+
152182
- name: Clean up keystore
153183
if: always()
154184
run: |
@@ -242,24 +272,96 @@ jobs:
242272
runs-on: ubuntu-latest
243273

244274
steps:
275+
- name: Checkout code
276+
uses: actions/checkout@v4
277+
245278
- name: Download AAB artifact
246279
uses: actions/download-artifact@v4
247280
with:
248281
name: release-bundle
249282
path: release-artifacts/
250283

251-
- name: Find AAB file
252-
id: find-aab
284+
- name: Find bundle and symbols
285+
id: find-files
253286
run: |
254287
AAB_PATH=$(find release-artifacts -name "*.aab" | head -1)
255288
echo "aab_path=$AAB_PATH" >> $GITHUB_OUTPUT
256289
257-
- name: Upload to Play Store
290+
# Look for debug symbols
291+
SYMBOLS_PATH=$(find release-artifacts -name "native-debug-symbols.zip" 2>/dev/null | head -1)
292+
if [ -n "$SYMBOLS_PATH" ]; then
293+
echo "symbols_path=$SYMBOLS_PATH" >> $GITHUB_OUTPUT
294+
echo "Found debug symbols at: $SYMBOLS_PATH"
295+
else
296+
echo "No debug symbols found"
297+
fi
298+
299+
- name: Determine release track and status
300+
id: release-config
301+
run: |
302+
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
303+
TRACK="${{ github.event.inputs.track }}"
304+
STATUS="${{ github.event.inputs.status }}"
305+
else
306+
# Default for tag pushes
307+
TRACK="internal"
308+
STATUS="draft"
309+
fi
310+
echo "track=$TRACK" >> $GITHUB_OUTPUT
311+
echo "status=$STATUS" >> $GITHUB_OUTPUT
312+
echo "Deploying to track: $TRACK with status: $STATUS"
313+
314+
- name: Create whatsnew directory
315+
run: |
316+
mkdir -p whatsnew
317+
318+
# Generate release notes
319+
echo "Release ${{ needs.prepare.outputs.version }}" > whatsnew/whatsnew-en-US
320+
echo "" >> whatsnew/whatsnew-en-US
321+
322+
# Get recent commits
323+
git log --pretty=format:"• %s" -5 >> whatsnew/whatsnew-en-US
324+
325+
# Chinese version
326+
echo "版本 ${{ needs.prepare.outputs.version }}" > whatsnew/whatsnew-zh-CN
327+
echo "" >> whatsnew/whatsnew-zh-CN
328+
git log --pretty=format:"• %s" -5 >> whatsnew/whatsnew-zh-CN
329+
330+
- name: Upload to Play Store (with debug symbols)
331+
if: steps.find-files.outputs.symbols_path != ''
258332
uses: r0adkll/upload-google-play@v1
259333
with:
260-
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
334+
serviceAccountJsonPlainText: ${{ secrets.PLAY_STORE_SERVICE_ACCOUNT_JSON }}
261335
packageName: me.ghui.v2er
262-
releaseFiles: ${{ steps.find-aab.outputs.aab_path }}
263-
track: internal
264-
status: completed
265-
whatsNewDirectory: whatsnew/
336+
releaseFiles: ${{ steps.find-files.outputs.aab_path }}
337+
track: ${{ steps.release-config.outputs.track }}
338+
status: ${{ steps.release-config.outputs.status }}
339+
debugSymbols: ${{ steps.find-files.outputs.symbols_path }}
340+
whatsNewDirectory: whatsnew/
341+
changesNotSentForReview: true
342+
continue-on-error: true
343+
id: upload-with-symbols
344+
345+
- name: Upload to Play Store (without debug symbols)
346+
if: steps.find-files.outputs.symbols_path == '' || steps.upload-with-symbols.outcome == 'failure'
347+
uses: r0adkll/upload-google-play@v1
348+
with:
349+
serviceAccountJsonPlainText: ${{ secrets.PLAY_STORE_SERVICE_ACCOUNT_JSON }}
350+
packageName: me.ghui.v2er
351+
releaseFiles: ${{ steps.find-files.outputs.aab_path }}
352+
track: ${{ steps.release-config.outputs.track }}
353+
status: ${{ steps.release-config.outputs.status }}
354+
whatsNewDirectory: whatsnew/
355+
changesNotSentForReview: true
356+
357+
- name: Play Store Upload Summary
358+
if: success()
359+
run: |
360+
echo "## Play Store Upload Complete :rocket:" >> $GITHUB_STEP_SUMMARY
361+
echo "" >> $GITHUB_STEP_SUMMARY
362+
echo "- **Version**: ${{ needs.prepare.outputs.version }}" >> $GITHUB_STEP_SUMMARY
363+
echo "- **Track**: ${{ steps.release-config.outputs.track }}" >> $GITHUB_STEP_SUMMARY
364+
echo "- **Status**: ${{ steps.release-config.outputs.status }}" >> $GITHUB_STEP_SUMMARY
365+
echo "- **Package**: me.ghui.v2er" >> $GITHUB_STEP_SUMMARY
366+
echo "" >> $GITHUB_STEP_SUMMARY
367+
echo "[View in Play Console](https://play.google.com/console/u/0/developers/your-developer-id/app/me.ghui.v2er)" >> $GITHUB_STEP_SUMMARY

.gitignore

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,18 @@
1111
*.keystore
1212
*.base64.txt
1313
keystore.jks
14-
app/keystore.jks
14+
app/keystore.jks
15+
16+
# Fastlane
17+
fastlane/report.xml
18+
fastlane/Preview.html
19+
fastlane/screenshots
20+
fastlane/test_output
21+
fastlane/readme.md
22+
fastlane/play-store-key.json
23+
fastlane/*.json
24+
25+
# Bundle
26+
vendor/bundle/
27+
.bundle/
28+
Gemfile.lock

.gitignore.fastlane

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Fastlane specific
2+
fastlane/report.xml
3+
fastlane/Preview.html
4+
fastlane/screenshots
5+
fastlane/test_output
6+
fastlane/readme.md
7+
fastlane/play-store-key.json
8+
fastlane/*.json
9+
10+
# Bundle
11+
vendor/bundle/
12+
.bundle/
13+
14+
# Ruby
15+
*.gem
16+
*.rbc
17+
/.config
18+
/coverage/
19+
/InstalledFiles
20+
/pkg/
21+
/spec/reports/
22+
/spec/examples.txt
23+
/test/tmp/
24+
/test/version_tmp/
25+
/tmp/
26+
Gemfile.lock

Gemfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
source "https://rubygems.org"
2+
3+
gem "fastlane"
4+
gem "fastlane-plugin-firebase_app_distribution", "~> 0.7.0" # Optional: for Firebase distribution

0 commit comments

Comments
 (0)