Files
frontend/.gitea/workflows/build.yml
lafay c46260e4c0
Some checks failed
Frontend CI / ota (ios) (push) Has been cancelled
Frontend CI / ota (android) (push) Has been cancelled
Frontend CI / build-and-push-web (push) Failing after 3m58s
Frontend CI / build-android-apk (push) Failing after 45m17s
chore(plugins,ci): remove livekit audio switch patch plugin and simplify build config
Remove the LiveKit audio switch patch plugin as it's no longer needed after the JitPack
repository was restricted to GitHub packages only. Simplify CI by removing npm caching and
Android NDK caching/installation steps. Update privacy policy and terms of service to
reflect JPush SDK naming changes.
2026-06-16 08:38:07 +08:00

383 lines
14 KiB
YAML

name: Frontend CI
on:
push:
branches:
- main
- master
- develop
- dev
pull_request:
branches:
- main
- master
workflow_dispatch:
inputs:
publish_ota:
description: 'Publish Android OTA'
required: false
default: 'true'
env:
REGISTRY: code.littlelan.cn
IMAGE_NAME: carrot_bbs/frontend-web
OTA_PUBLISH_URL: https://updates.littlelan.cn/admin/publish
OTA_MANIFEST_URL: https://updates.littlelan.cn/api/manifest
jobs:
ota:
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' && (github.event_name != 'workflow_dispatch' || github.event.inputs.publish_ota == 'true')
permissions:
contents: read
strategy:
matrix:
platform: [android, ios]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: '25.6.1'
- name: Install dependencies
run: npm ci
- name: Resolve runtime version
id: runtime
run: |
RUNTIME_VERSION="$(node -p "require('./app.json').expo.version")"
echo "runtime_version=${RUNTIME_VERSION}" >> "$GITHUB_OUTPUT"
echo "Resolved runtimeVersion: ${RUNTIME_VERSION}"
- name: Export update bundle
run: |
rm -rf dist-${{ matrix.platform }} dist-${{ matrix.platform }}-update.zip
npx expo export --platform ${{ matrix.platform }} --output-dir dist-${{ matrix.platform }}
npx expo config --type public --json > dist-${{ matrix.platform }}/expoConfig.json
- name: Archive update bundle
run: |
python -c "
import os, zipfile
p = '${{ matrix.platform }}'
dist = f'dist-{p}'
with zipfile.ZipFile(f'dist-{p}-update.zip', 'w', zipfile.ZIP_DEFLATED) as zf:
for root, _, files in os.walk(dist):
for name in files:
src = os.path.join(root, name)
arc = os.path.relpath(src, dist)
zf.write(src, arc)
"
- name: Publish OTA
env:
OTA_AUTH_TOKEN: ${{ secrets.OTA_AUTH_TOKEN }}
run: |
test -n "${OTA_AUTH_TOKEN}" || (echo "Missing secret OTA_AUTH_TOKEN" && exit 1)
curl -fSs -X POST "${OTA_PUBLISH_URL}?runtimeVersion=${{ steps.runtime.outputs.runtime_version }}&platform=${{ matrix.platform }}" \
-H "Authorization: Bearer ${OTA_AUTH_TOKEN}" \
-H "Content-Type: application/zip" \
--data-binary @"dist-${{ matrix.platform }}-update.zip"
- name: Verify OTA manifest
run: |
REMOTE="$(curl -sS "${OTA_MANIFEST_URL}" \
-H "expo-platform: ${{ matrix.platform }}" \
-H "expo-runtime-version: ${{ steps.runtime.outputs.runtime_version }}" \
-H "expo-protocol-version: 1" \
| python -c "import sys, json, re; s=sys.stdin.read(); m=re.search(r'\\{\"id\":.*\"extra\":\{.*\}\}', s); j=json.loads(m.group(0)); print(j['launchAsset']['url'].split('/')[-1].split('&')[0])")"
LOCAL="$(python -c "import json; m=json.load(open('dist-${{ matrix.platform }}/metadata.json')); print(m['fileMetadata']['${{ matrix.platform }}']['bundle'].split('/')[-1])")"
echo "Remote bundle: ${REMOTE}"
echo "Local bundle: ${LOCAL}"
test "${REMOTE}" = "${LOCAL}"
build-android-apk:
runs-on: android-builder
container:
image: reactnativecommunity/react-native-android:latest
env:
GRADLE_OPTS: "-Dorg.gradle.daemon=false -Dorg.gradle.parallel=true -Dorg.gradle.workers.max=4 -Xmx8g -XX:MaxMetaspaceSize=1g -XX:+UseG1GC -XX:SoftRefLRUPolicyMSPerMB=0 -XX:ReservedCodeCacheSize=512m"
_JAVA_OPTIONS: "-Xmx8g -XX:MaxMetaspaceSize=1g -XX:+UseG1GC -XX:SoftRefLRUPolicyMSPerMB=0 -XX:ReservedCodeCacheSize=512m"
NODE_OPTIONS: "--max-old-space-size=8192"
NODE_ENV: "production"
NDK_NUM_JOBS: "4"
CMAKE_BUILD_PARALLEL_LEVEL: "8"
GRADLE_USER_HOME: /root/.gradle
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: '25.6.1'
- name: Install dependencies
run: npm ci
- name: Cache Gradle
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
~/.android/build-cache
key: ${{ runner.os }}-gradle-${{ hashFiles('package-lock.json') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Generate Android native project
run: npx expo prebuild --platform android
- name: Configure Gradle with signing
run: |
cd android
# Decode Android signing keystore
echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 -d > app/withyou-release-key.keystore
# Update settings.gradle
cat > settings.gradle << 'SETTINGS_EOF'
pluginManagement {
repositories {
maven { url 'https://developer.huawei.com/repo/' }
google()
mavenCentral()
gradlePluginPortal()
}
def reactNativeGradlePlugin = new File(
providers.exec {
workingDir(rootDir)
commandLine("node", "--print", "require.resolve('@react-native/gradle-plugin/package.json', { paths: [require.resolve('react-native/package.json')] })")
}.standardOutput.asText.get().trim()
).getParentFile().absolutePath
includeBuild(reactNativeGradlePlugin)
def expoPluginsPath = new File(
providers.exec {
workingDir(rootDir)
commandLine("node", "--print", "require.resolve('expo-modules-autolinking/package.json', { paths: [require.resolve('expo/package.json')] })")
}.standardOutput.asText.get().trim(),
"../android/expo-gradle-plugin"
).absolutePath
includeBuild(expoPluginsPath)
}
plugins {
id("com.facebook.react.settings")
id("expo-autolinking-settings")
id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0"
}
dependencyResolutionManagement {
repositories {
maven { url 'https://developer.huawei.com/repo/' }
google()
mavenCentral()
maven { url 'https://www.jitpack.io' }
}
}
extensions.configure(com.facebook.react.ReactSettingsExtension) { ex ->
if (System.getenv('EXPO_USE_COMMUNITY_AUTOLINKING') == '1') {
ex.autolinkLibrariesFromCommand()
} else {
ex.autolinkLibrariesFromCommand(expoAutolinking.rnConfigCommand)
}
}
expoAutolinking.useExpoModules()
rootProject.name = '威友'
expoAutolinking.useExpoVersionCatalog()
include ':app'
includeBuild(expoAutolinking.reactNativeGradlePlugin)
SETTINGS_EOF
# Update build.gradle
cat > build.gradle << 'BUILD_EOF'
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
maven { url 'https://developer.huawei.com/repo/' }
google()
mavenCentral()
}
dependencies {
classpath('com.huawei.agconnect:agcp:1.9.1.301') {
exclude group: 'com.huawei.agconnect', module: 'agconnect-apms-plugin'
}
classpath('com.google.gms:google-services:4.4.4')
classpath('com.android.tools.build:gradle:8.12.0')
classpath('com.facebook.react:react-native-gradle-plugin')
classpath('org.jetbrains.kotlin:kotlin-gradle-plugin')
}
}
allprojects {
repositories {
maven { url 'https://developer.huawei.com/repo/' }
google()
mavenCentral()
maven { url 'https://www.jitpack.io' }
}
}
apply plugin: "expo-root-project"
apply plugin: "com.facebook.react.rootproject"
BUILD_EOF
# Update gradle.properties (without secrets for better cache hit rate)
cat > gradle.properties << 'PROPS_EOF'
org.gradle.daemon=false
org.gradle.parallel=true
org.gradle.configureondemand=true
org.gradle.workers.max=4
org.gradle.jvmargs=-Xmx8g -XX:MaxMetaspaceSize=1g -XX:+UseG1GC -XX:SoftRefLRUPolicyMSPerMB=0 -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.enableJetifier=false
android.useAndroidX=true
hermesEnabled=true
reactNativeArchitectures=arm64-v8a
newArchEnabled=true
edgeToEdgeEnabled=true
expo.gif.enabled=true
expo.webp.enabled=true
expo.webp.animated=false
ndkVersion=27.1.12297006
expo.useLegacyPackaging=false
systemProp.org.gradle.internal.http.connectionTimeout=30000
systemProp.org.gradle.internal.http.socketTimeout=30000
PROPS_EOF
# Append signing properties (secrets appended, not cached)
cat >> gradle.properties << SIGNING_PROPS
MYAPP_UPLOAD_STORE_FILE=withyou-release-key.keystore
MYAPP_UPLOAD_STORE_PASSWORD=${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
MYAPP_UPLOAD_KEY_ALIAS=withyou-key
MYAPP_UPLOAD_KEY_PASSWORD=${{ secrets.ANDROID_KEY_PASSWORD }}
SIGNING_PROPS
# Verify signing config in app/build.gradle
echo "=== app/build.gradle signing section ==="
grep -A 20 'signingConfigs' app/build.gradle || echo "signingConfigs not found"
- name: Build Android release APK (arm64 only)
run: |
cd android
chmod +x gradlew
./gradlew :app:assembleRelease -PreactNativeArchitectures=arm64-v8a --max-workers=4 --parallel
- name: Upload APK artifact
uses: actions/upload-artifact@v3
with:
name: withyou-android-release-apk
path: android/app/build/outputs/apk/release/app-release.apk
- name: Resolve runtime version
id: runtime
run: |
RUNTIME_VERSION="$(node -p "require('./app.json').expo.version")"
echo "runtime_version=${RUNTIME_VERSION}" >> "$GITHUB_OUTPUT"
echo "Resolved runtimeVersion: ${RUNTIME_VERSION}"
- name: Upload APK to Updates Server
env:
OTA_AUTH_TOKEN: ${{ secrets.OTA_AUTH_TOKEN }}
UPDATES_SERVER_URL: https://updates.littlelan.cn
run: |
test -n "${OTA_AUTH_TOKEN}" || (echo "Missing secret OTA_AUTH_TOKEN" && exit 1)
APK_PATH="android/app/build/outputs/apk/release/app-release.apk"
RUNTIME_VERSION="${{ steps.runtime.outputs.runtime_version }}"
echo "Uploading APK for runtime version: ${RUNTIME_VERSION}"
HTTP_CODE=$(curl -s -o response.json -w "%{http_code}" \
-X POST \
"${UPDATES_SERVER_URL}/admin/apk/upload?runtimeVersion=${RUNTIME_VERSION}" \
-H "Authorization: Bearer ${OTA_AUTH_TOKEN}" \
-H "Content-Type: application/vnd.android.package-archive" \
--data-binary @"${APK_PATH}")
echo "HTTP Response Code: ${HTTP_CODE}"
cat response.json
if [ "${HTTP_CODE}" -ne 200 ]; then
echo "Failed to upload APK"
exit 1
fi
echo "APK uploaded successfully!"
build-and-push-web:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Log in to Gitea Container Registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.GIT_USERNAME }}
password: ${{ secrets.GIT_TOKEN }}
- name: Extract metadata for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=raw,value=latest,enable={{is_default_branch}}
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: Dockerfile.web
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64
provenance: false
- name: Show image tags
run: |
echo "Built image tags:"
echo "${{ steps.meta.outputs.tags }}"
echo "Digest: ${{ steps.meta.outputs.digest }}"
- name: Summary
if: always()
run: |
echo "## Frontend CI Summary" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "**Web image:** ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}" >> "$GITHUB_STEP_SUMMARY"
echo "**Tags:**" >> "$GITHUB_STEP_SUMMARY"
echo "${{ steps.meta.outputs.tags }}" >> "$GITHUB_STEP_SUMMARY"
echo "**Digest:** ${{ steps.meta.outputs.digest }}" >> "$GITHUB_STEP_SUMMARY"