-
-
Notifications
You must be signed in to change notification settings - Fork 265
182 lines (157 loc) · 8.14 KB
/
Copy pathrelease.yml
File metadata and controls
182 lines (157 loc) · 8.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
name: Build and Release Android
# NOTE: The iOS workflow (release-ios.yml) triggers via workflow_run on this workflow.
# If you rename this workflow, update the workflow_run trigger in release-ios.yml.
on:
# Manual only. Releases are cut by hand (scripts/release.sh locally, or this dispatch) —
# merging to main must NOT auto-publish to the stores.
workflow_dispatch: {}
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
# Lift store secrets to job-level env so the `if:` guards on the Play steps can test
# for their presence (if: conditions can read env, not secrets directly). Absent
# secrets => empty => Play upload steps skip, GitHub Release path still runs.
env:
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
PLAY_STORE_JSON_KEY: ${{ secrets.PLAY_STORE_JSON_KEY }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0 # Fetch all history for changelog
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
- name: Setup Android SDK
uses: android-actions/setup-android@v3
- name: Setup Ruby (fastlane)
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3'
bundler-cache: true
- name: Cache Gradle
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: gradle-
- name: Install dependencies
run: npm ci
- name: Bump patch version
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
npm version patch --no-git-tag-version
NEW_VERSION=$(node -p "require('./package.json').version")
echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_ENV
# Update Android versionCode and versionName
VERSION_CODE=$(date +%s)
echo "VERSION_CODE=$VERSION_CODE" >> $GITHUB_ENV
# Update build.gradle
sed -i "s/versionCode .*/versionCode $VERSION_CODE/" android/app/build.gradle
sed -i "s/versionName .*/versionName \"$NEW_VERSION\"/" android/app/build.gradle
git add package.json package-lock.json android/app/build.gradle
git commit -m "chore: bump version to $NEW_VERSION [skip ci]"
git push
- name: Generate release notes
run: |
# Get commits since last tag (or all commits if no tags)
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
if [ -z "$LAST_TAG" ]; then
COMMITS=$(git log --pretty=format:"- %s (%h)" --no-merges -20)
else
COMMITS=$(git log ${LAST_TAG}..HEAD --pretty=format:"- %s (%h)" --no-merges)
fi
# Write release notes
echo "## What's Changed" > release-notes.md
echo "" >> release-notes.md
echo "$COMMITS" >> release-notes.md
echo "" >> release-notes.md
echo "**Full Changelog**: https://github.com/${{ github.repository }}/compare/${LAST_TAG:-v0.0.0}...v${{ env.NEW_VERSION }}" >> release-notes.md
cat release-notes.md
# Decode the keystore BEFORE building the APK so the GitHub-release APK is RELEASE-signed,
# not debug-signed. Gated on the KEYSTORE ALONE (not the Play key) — APK signing only needs
# the keystore; coupling it to PLAY_STORE_JSON_KEY would debug-sign the APK in keystore-only
# setups. Only the keystore FILE PATH goes into $GITHUB_ENV (a path, so newline-safe); the
# secret-derived props are passed as static step env below (no env-file parsing). build.gradle
# gates release signing on project.hasProperty('OFFGRID_UPLOAD_STORE_FILE'), so when the
# keystore is absent the prop is unset and the build falls back to debug signing.
- name: Decode keystore
if: ${{ env.ANDROID_KEYSTORE_BASE64 != '' }}
run: |
echo "$ANDROID_KEYSTORE_BASE64" | base64 --decode > "$RUNNER_TEMP/release.keystore"
echo "ORG_GRADLE_PROJECT_OFFGRID_UPLOAD_STORE_FILE=$RUNNER_TEMP/release.keystore" >> "$GITHUB_ENV"
- name: Build Android Release APK
env:
# Secret-derived signing props: static step env (never $GITHUB_ENV) so a newline in a
# secret can't corrupt the env file. build.gradle only reads these inside the
# hasProperty(STORE_FILE) block, so when the keystore is absent they're simply unused.
ORG_GRADLE_PROJECT_OFFGRID_UPLOAD_STORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
ORG_GRADLE_PROJECT_OFFGRID_UPLOAD_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
ORG_GRADLE_PROJECT_OFFGRID_UPLOAD_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
run: |
cd android
./gradlew assembleRelease
- name: Rename APK
run: |
mv android/app/build/outputs/apk/release/app-release.apk \
android/app/build/outputs/apk/release/OffgridMobile-v${{ env.NEW_VERSION }}.apk
- name: Create GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create v${{ env.NEW_VERSION }} \
android/app/build/outputs/apk/release/OffgridMobile-v${{ env.NEW_VERSION }}.apk \
--title "Off Grid v${{ env.NEW_VERSION }}" \
--notes-file release-notes.md
- name: Upload APK artifact
uses: actions/upload-artifact@v4
with:
name: OffgridMobile-v${{ env.NEW_VERSION }}
# No product flavors, so the APK lives under apk/release/ (not apk/standard/release/).
path: android/app/build/outputs/apk/release/OffgridMobile-v${{ env.NEW_VERSION }}.apk
if-no-files-found: error
# ── Play Store upload (in addition to the GitHub Release above) ──────────
# Decodes the Play service-account key, then builds the signed AAB and uploads it to the
# Play production track as a DRAFT (a human confirms rollout in the Play Console). Needs
# BOTH the keystore (decoded above → STORE_FILE prop) and the Play key, so it's gated on
# both; skipped when either is absent, and the GitHub Release path above still works.
- name: Decode Play service-account key
if: ${{ env.ANDROID_KEYSTORE_BASE64 != '' && env.PLAY_STORE_JSON_KEY != '' }}
run: |
# PLAY_STORE_JSON_KEY may be stored raw-JSON or base64 — handle both (matches uat-dev.yml).
echo "$PLAY_STORE_JSON_KEY" | base64 --decode > "$RUNNER_TEMP/play-key.json" \
|| printf '%s' "$PLAY_STORE_JSON_KEY" > "$RUNNER_TEMP/play-key.json"
echo "PLAY_UPLOAD=1" >> "$GITHUB_ENV"
- name: Build signed AAB + upload to Play (draft)
if: ${{ env.PLAY_UPLOAD == '1' }}
env:
PLAY_STORE_JSON_KEY_PATH: ${{ runner.temp }}/play-key.json
# Same static signing props as the APK build (STORE_FILE comes from $GITHUB_ENV).
ORG_GRADLE_PROJECT_OFFGRID_UPLOAD_STORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
ORG_GRADLE_PROJECT_OFFGRID_UPLOAD_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
ORG_GRADLE_PROJECT_OFFGRID_UPLOAD_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
run: bundle exec fastlane android release
# Announce the release in Slack: the GitHub release link + the notes generated above.
# Fail-soft (notify script always exits 0), so a Slack outage never fails a release.
# Needs secret SLACK_WEBHOOK_URL (an Incoming Webhook, channel-bound). No secret => no-op.
- name: Announce release in Slack
if: success()
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
PRODUCT: Off Grid AI Mobile (Android)
VERSION: ${{ env.NEW_VERSION }}
NOTES_FILE: release-notes.md
run: node scripts/notify-slack-release.mjs