Files
life-echo/.github/workflows/app-expo-deploy.yml
2026-03-23 13:21:07 +08:00

232 lines
8.4 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# App ExpoCI 内生成 Android 签名 APKexpo prebuild + Gradle assembleRelease
# 使用 app-expo/plugins/withAndroidReleaseSigning在 android/app 放置 keystore.properties + jks 后打 release 包。
#
# 产物说明:
# - 以下为「真机向」release APKGoogle Play 上架更推荐 AAB按设备 ABI 下发)。
# - 构建步骤会将 reactNativeArchitectures 设为仅 arm64-v8a更小不含 x86/x86_64多数模拟器无法安装
# - 若需兼容旧 32 位 ARM 设备,可将该属性改为 armeabi-v7a,arm64-v8a体积会增大
# - 若需用 x86 模拟器验证此 APK需改回含 x86 的 ABI 或另建 job。
#
# 环境映射(与后端 api 一致main → 预发 stagingtag → 生产 production
# push main → stage → node scripts/use-env.js staging → .env.staging
# push v*.*.* → prod → node scripts/use-env.js production → .env.production
#
# 手动触发 workflow_dispatch可选 dev / stage / proddev 用 .env.development便于打内部测试包
#
# Repository secrets与 android-release.yml 共用同一套即可):
# ANDROID_KEYSTORE_BASE64 / ANDROID_STORE_PASSWORD / ANDROID_KEY_ALIAS / ANDROID_KEY_PASSWORD
name: App Expo Deploy
on:
push:
branches: [main]
tags: ['v*.*.*']
paths:
- "app-expo/**"
- ".github/workflows/app-expo-deploy.yml"
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
type: choice
options:
- dev
- stage
- prod
default: dev
version:
description: '版本号 (prod 手动发版时使用,如 1.0.0)'
required: false
type: string
concurrency:
group: app-expo-deploy-${{ github.ref }}
cancel-in-progress: true
env:
APP_SLUG: life-echo
APP_DISPLAY_NAME: Life Echo
EXPO_NO_TELEMETRY: 1
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
deploy:
name: "Android APK"
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v5
with:
fetch-depth: ${{ startsWith(github.ref, 'refs/tags/') && '0' || '1' }}
- name: Determine environment
id: env
run: |
if [[ "${{ github.ref }}" == refs/tags/v* ]]; then
echo "env=prod"
elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
echo "env=${{ github.event.inputs.environment }}"
else
# push 到 main本 workflow 仅监听 main 与 tag
echo "env=stage"
fi >> $GITHUB_OUTPUT
- name: Set up Node.js
uses: actions/setup-node@v5
with:
node-version: 22.22.1
cache: npm
cache-dependency-path: app-expo/package-lock.json
- name: Install dependencies
working-directory: app-expo
run: npm ci
- name: Quality checks (non-prod)
if: steps.env.outputs.env != 'prod'
working-directory: app-expo
run: |
npm run format:check
npm run lint
npm run test:ci
- name: Set API environment
working-directory: app-expo
run: |
case "${{ steps.env.outputs.env }}" in
prod) node scripts/use-env.js production ;;
stage) node scripts/use-env.js staging ;;
*) node scripts/use-env.js development ;;
esac
- name: Determine version
id: version
working-directory: app-expo
run: |
if [[ "${{ github.ref }}" == refs/tags/v* ]]; then
VERSION="${GITHUB_REF#refs/tags/v}"
TAG_NAME="${GITHUB_REF#refs/tags/}"
elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]] && [[ -n "${{ github.event.inputs.version }}" ]]; then
VERSION="${{ github.event.inputs.version }}"
TAG_NAME="v${VERSION}"
else
VERSION=$(node -p "require('./package.json').version")
TAG_NAME="v${VERSION}"
fi
VERSION_CODE="${GITHUB_RUN_NUMBER}"
echo "version=${VERSION}" >> $GITHUB_OUTPUT
echo "tag_name=${TAG_NAME}" >> $GITHUB_OUTPUT
echo "version_code=${VERSION_CODE}" >> $GITHUB_OUTPUT
echo "apk_name=${{ env.APP_SLUG }}-v${VERSION}-release.apk" >> $GITHUB_OUTPUT
echo "版本名: ${VERSION}, versionCode: ${VERSION_CODE}, tag: ${TAG_NAME}"
- name: Set up JDK
uses: actions/setup-java@v5
with:
java-version: '17'
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
- name: Expo prebuild (Android)
working-directory: app-expo
run: npx expo prebuild --platform android --clean
- name: Release APK — arm64 only (real devices; smaller)
working-directory: app-expo/android
run: |
set -euo pipefail
if grep -q '^reactNativeArchitectures=' gradle.properties; then
sed -i 's/^reactNativeArchitectures=.*/reactNativeArchitectures=arm64-v8a/' gradle.properties
else
echo 'reactNativeArchitectures=arm64-v8a' >> gradle.properties
fi
grep -E '^reactNativeArchitectures=' gradle.properties
- name: Decode keystore
working-directory: app-expo/android/app
run: |
echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 --decode > release-keystore.jks
cat > keystore.properties <<EOF
storeFile=release-keystore.jks
storePassword=${{ secrets.ANDROID_STORE_PASSWORD }}
keyAlias=${{ secrets.ANDROID_KEY_ALIAS }}
keyPassword=${{ secrets.ANDROID_KEY_PASSWORD }}
EOF
sed -i 's/^[[:space:]]*//' keystore.properties
- name: Build release APK
working-directory: app-expo/android
env:
GRADLE_OPTS: '-Xmx4g -Dorg.gradle.daemon=false'
run: |
chmod +x gradlew
./gradlew assembleRelease --no-daemon \
-PversionName="${{ steps.version.outputs.version }}" \
-PversionCode="${{ steps.version.outputs.version_code }}"
- name: Locate APK
id: apk
working-directory: app-expo/android
run: |
APK_PATH=$(find app/build/outputs/apk/release -name "*.apk" | head -1)
if [ -z "$APK_PATH" ]; then
echo "未找到 Release APK"
exit 1
fi
FINAL_NAME="${{ steps.version.outputs.apk_name }}"
FINAL_PATH="app/build/outputs/apk/release/${FINAL_NAME}"
mv "$APK_PATH" "$FINAL_PATH"
echo "apk_path=${FINAL_PATH}" >> $GITHUB_OUTPUT
du -h "$FINAL_PATH"
- name: Upload APK artifact
uses: actions/upload-artifact@v6
with:
name: ${{ steps.version.outputs.apk_name }}
path: app-expo/android/${{ steps.apk.outputs.apk_path }}
retention-days: ${{ steps.env.outputs.env == 'prod' && '90' || (steps.env.outputs.env == 'dev' && '14' || '30') }}
- name: Generate Release Notes (prod)
if: steps.env.outputs.env == 'prod' && startsWith(github.ref, 'refs/tags/')
id: release_notes
run: |
TAG_NAME="${{ steps.version.outputs.tag_name }}"
PREV_TAG=$(git tag --sort=-creatordate | grep '^v' | sed -n '2p')
if [ -n "$PREV_TAG" ]; then
CHANGES=$(git log "${PREV_TAG}..HEAD" --pretty=format:"- %s (%h)" --no-merges)
else
CHANGES=$(git log --pretty=format:"- %s (%h)" --no-merges -20)
fi
{
echo "notes<<EOF"
echo "## ${{ env.APP_DISPLAY_NAME }} ${TAG_NAME}"
echo ""
echo "### 更新内容"
echo ""
echo "${CHANGES}"
echo ""
echo "---"
echo "构建编号: #${GITHUB_RUN_NUMBER}"
echo "EOF"
} >> $GITHUB_OUTPUT
- name: Create GitHub Release (prod)
if: steps.env.outputs.env == 'prod'
uses: softprops/action-gh-release@v2.5.0
with:
tag_name: ${{ steps.version.outputs.tag_name }}
name: "${{ env.APP_DISPLAY_NAME }} ${{ steps.version.outputs.tag_name }}"
body: ${{ steps.release_notes.outputs.notes || 'Release' }}
draft: false
prerelease: false
files: app-expo/android/${{ steps.apk.outputs.apk_path }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}