chore: release v1.0.18
Deploy / build-linux-web (push) Successful in 53s
Deploy / build-windows (push) Successful in 1m48s
Deploy / build-macos (push) Successful in 1m17s
Deploy / build-android (push) Successful in 4m13s
Deploy / build-ios (push) Successful in 9s
Deploy / release-deploy (push) Successful in 1m37s
Deploy / build-linux-web (push) Successful in 53s
Deploy / build-windows (push) Successful in 1m48s
Deploy / build-macos (push) Successful in 1m17s
Deploy / build-android (push) Successful in 4m13s
Deploy / build-ios (push) Successful in 9s
Deploy / release-deploy (push) Successful in 1m37s
移动端响应式适配(抽屉导航/列表卡片/弹窗自适应)、Android 正式签名与 APK 发布、 iOS(TestFlight) 工程与 CI、多平台构建流水线、相关文档同步。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Executable
+64
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env bash
|
||||
# compile-android.sh <tag> — build signed Flutter Android APK, package into dist/
|
||||
set -euo pipefail
|
||||
|
||||
# shellcheck source=scripts/ci/_env.sh
|
||||
. "$(dirname "$0")/_env.sh"
|
||||
|
||||
TAG="$1"
|
||||
VER="${TAG#v}"
|
||||
|
||||
# versionCode 必须单调递增,否则 Android 无法覆盖升级安装。
|
||||
# 由版本号推导:major*10000 + minor*100 + patch(如 1.0.17 -> 10017)。
|
||||
MAJOR="$(echo "$VER" | cut -d. -f1)"
|
||||
MINOR="$(echo "$VER" | cut -d. -f2)"
|
||||
PATCH="$(echo "$VER" | cut -d. -f3)"
|
||||
BUILD=$(( MAJOR * 10000 + MINOR * 100 + PATCH ))
|
||||
|
||||
echo "==> compile-android: version=${VER} build=${BUILD}"
|
||||
|
||||
# Sync Flutter pubspec version (BSD sed on macOS)
|
||||
sed -i '' "s/^version:.*/version: ${VER}+${BUILD}/" client/pubspec.yaml
|
||||
|
||||
# --- release 签名:从 CI secrets 还原 keystore 与 key.properties ---
|
||||
if [ -n "${ANDROID_KEYSTORE_BASE64:-}" ]; then
|
||||
echo "==> compile-android: configuring release signing"
|
||||
KEYSTORE_PATH="$(pwd)/client/android/jiu-release.jks"
|
||||
echo "${ANDROID_KEYSTORE_BASE64}" | base64 --decode > "${KEYSTORE_PATH}"
|
||||
cat > client/android/key.properties <<EOF
|
||||
storeFile=${KEYSTORE_PATH}
|
||||
storePassword=${ANDROID_KEYSTORE_PASSWORD}
|
||||
keyAlias=${ANDROID_KEY_ALIAS}
|
||||
keyPassword=${ANDROID_KEY_PASSWORD}
|
||||
EOF
|
||||
else
|
||||
echo "==> compile-android: WARNING — no ANDROID_KEYSTORE_BASE64, falling back to debug signing"
|
||||
fi
|
||||
|
||||
# Build APK (universal, all ABIs — simplest for sideload download)
|
||||
cd client
|
||||
flutter build apk --release \
|
||||
"--dart-define=BASE_URL=https://jiu.51yanmei.com" \
|
||||
"--dart-define=PUBLIC_URL=https://jiu.51yanmei.com" \
|
||||
"--dart-define=APP_VERSION=${TAG}"
|
||||
cd ..
|
||||
|
||||
# Locate the built APK (path differs across Flutter versions) and copy to dist/
|
||||
mkdir -p dist
|
||||
APK=""
|
||||
for cand in \
|
||||
client/build/app/outputs/flutter-apk/app-release.apk \
|
||||
client/build/app/outputs/apk/release/app-release.apk; do
|
||||
if [ -f "$cand" ]; then APK="$cand"; break; fi
|
||||
done
|
||||
if [ -z "$APK" ]; then
|
||||
echo "ERROR: built APK not found" >&2
|
||||
exit 1
|
||||
fi
|
||||
cp "$APK" dist/jiu-android.apk
|
||||
|
||||
# Clean up signing material so it never lingers on the runner workspace
|
||||
rm -f client/android/key.properties client/android/jiu-release.jks 2>/dev/null || true
|
||||
|
||||
echo "==> compile-android: done — dist/ contents:"
|
||||
ls -lh dist/
|
||||
Executable
+124
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env bash
|
||||
# compile-ios.sh <tag> — build signed iOS IPA and upload to TestFlight (App Store Connect)
|
||||
#
|
||||
# 需要的 CI secrets(缺任意一个则优雅跳过,不阻塞流水线):
|
||||
# IOS_DIST_CERT_P12_BASE64 Apple Distribution 证书 (.p12) 的 base64
|
||||
# IOS_DIST_CERT_PASSWORD .p12 导出密码
|
||||
# IOS_PROVISIONING_PROFILE_BASE64 App Store 类型 provisioning profile (.mobileprovision) 的 base64
|
||||
# IOS_TEAM_ID Apple Developer Team ID(10 位)
|
||||
# APPSTORE_API_KEY_ID App Store Connect API Key ID
|
||||
# APPSTORE_API_ISSUER_ID App Store Connect API Issuer ID
|
||||
# APPSTORE_API_KEY_P8_BASE64 API Key (.p8) 的 base64
|
||||
#
|
||||
# 详见 docs/ios-signing.md。
|
||||
set -euo pipefail
|
||||
|
||||
# shellcheck source=scripts/ci/_env.sh
|
||||
. "$(dirname "$0")/_env.sh"
|
||||
|
||||
TAG="$1"
|
||||
VER="${TAG#v}"
|
||||
|
||||
# CFBundleVersion(build 号)必须单调递增,否则 TestFlight 拒绝重复上传。
|
||||
MAJOR="$(echo "$VER" | cut -d. -f1)"
|
||||
MINOR="$(echo "$VER" | cut -d. -f2)"
|
||||
PATCH="$(echo "$VER" | cut -d. -f3)"
|
||||
BUILD=$(( MAJOR * 10000 + MINOR * 100 + PATCH ))
|
||||
|
||||
echo "==> compile-ios: version=${VER} build=${BUILD}"
|
||||
|
||||
# --- 凭证缺失则跳过(账号/证书尚未配置时不阻塞发版)---
|
||||
if [ -z "${IOS_DIST_CERT_P12_BASE64:-}" ] || \
|
||||
[ -z "${IOS_PROVISIONING_PROFILE_BASE64:-}" ] || \
|
||||
[ -z "${APPSTORE_API_KEY_P8_BASE64:-}" ]; then
|
||||
echo "==> compile-ios: SKIP — iOS signing secrets not configured (see docs/ios-signing.md)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
WORK="$(mktemp -d)"
|
||||
cleanup() {
|
||||
security delete-keychain "$KEYCHAIN" 2>/dev/null || true
|
||||
rm -rf "$WORK"
|
||||
rm -f "$HOME/Library/MobileDevice/Provisioning Profiles/jiu-ci.mobileprovision" 2>/dev/null || true
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# --- 1. 临时 keychain 导入发行证书 ---
|
||||
KEYCHAIN="$WORK/jiu-ci.keychain-db"
|
||||
KEYCHAIN_PWD="ci-temp-$$"
|
||||
security create-keychain -p "$KEYCHAIN_PWD" "$KEYCHAIN"
|
||||
security set-keychain-settings -lut 21600 "$KEYCHAIN"
|
||||
security unlock-keychain -p "$KEYCHAIN_PWD" "$KEYCHAIN"
|
||||
|
||||
echo "${IOS_DIST_CERT_P12_BASE64}" | base64 --decode > "$WORK/dist.p12"
|
||||
security import "$WORK/dist.p12" -k "$KEYCHAIN" \
|
||||
-P "${IOS_DIST_CERT_PASSWORD:-}" -T /usr/bin/codesign -T /usr/bin/security
|
||||
# 允许 codesign 非交互访问私钥
|
||||
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PWD" "$KEYCHAIN" >/dev/null
|
||||
# 把临时 keychain 加入搜索列表(保留默认 login keychain)
|
||||
security list-keychains -d user -s "$KEYCHAIN" login.keychain-db
|
||||
|
||||
# --- 2. 安装 provisioning profile ---
|
||||
PROFILES_DIR="$HOME/Library/MobileDevice/Provisioning Profiles"
|
||||
mkdir -p "$PROFILES_DIR"
|
||||
echo "${IOS_PROVISIONING_PROFILE_BASE64}" | base64 --decode > "$WORK/profile.mobileprovision"
|
||||
# 解出 profile 的 Name 与 UUID(ExportOptions 需要)
|
||||
security cms -D -i "$WORK/profile.mobileprovision" > "$WORK/profile.plist"
|
||||
PROFILE_NAME="$(/usr/libexec/PlistBuddy -c 'Print :Name' "$WORK/profile.plist")"
|
||||
PROFILE_UUID="$(/usr/libexec/PlistBuddy -c 'Print :UUID' "$WORK/profile.plist")"
|
||||
cp "$WORK/profile.mobileprovision" "$PROFILES_DIR/${PROFILE_UUID}.mobileprovision"
|
||||
echo "==> compile-ios: profile '${PROFILE_NAME}' (${PROFILE_UUID})"
|
||||
|
||||
# --- 3. App Store Connect API Key(供上传使用)---
|
||||
API_KEYS_DIR="$HOME/.appstoreconnect/private_keys"
|
||||
mkdir -p "$API_KEYS_DIR"
|
||||
echo "${APPSTORE_API_KEY_P8_BASE64}" | base64 --decode > "$API_KEYS_DIR/AuthKey_${APPSTORE_API_KEY_ID}.p8"
|
||||
|
||||
# --- 4. 同步版本号 ---
|
||||
sed -i '' "s/^version:.*/version: ${VER}+${BUILD}/" client/pubspec.yaml
|
||||
|
||||
# --- 5. 生成 ExportOptions.plist(manual 签名,app-store 分发)---
|
||||
BUNDLE_ID="com.yanmei.jiu"
|
||||
cat > "$WORK/ExportOptions.plist" <<EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>method</key><string>app-store</string>
|
||||
<key>teamID</key><string>${IOS_TEAM_ID}</string>
|
||||
<key>signingStyle</key><string>manual</string>
|
||||
<key>uploadBitcode</key><false/>
|
||||
<key>uploadSymbols</key><true/>
|
||||
<key>provisioningProfiles</key>
|
||||
<dict>
|
||||
<key>${BUNDLE_ID}</key><string>${PROFILE_NAME}</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
|
||||
# --- 6. 构建签名 IPA ---
|
||||
cd client
|
||||
flutter build ipa --release \
|
||||
--build-name="${VER}" \
|
||||
--build-number="${BUILD}" \
|
||||
--export-options-plist="$WORK/ExportOptions.plist" \
|
||||
"--dart-define=BASE_URL=https://jiu.51yanmei.com" \
|
||||
"--dart-define=PUBLIC_URL=https://jiu.51yanmei.com" \
|
||||
"--dart-define=APP_VERSION=${TAG}"
|
||||
cd ..
|
||||
|
||||
IPA="$(ls client/build/ios/ipa/*.ipa | head -1)"
|
||||
if [ -z "$IPA" ] || [ ! -f "$IPA" ]; then
|
||||
echo "ERROR: IPA not found" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "==> compile-ios: built ${IPA}"
|
||||
|
||||
# --- 7. 上传 TestFlight(App Store Connect)---
|
||||
echo "==> compile-ios: uploading to TestFlight"
|
||||
xcrun altool --upload-app -f "$IPA" -t ios \
|
||||
--apiKey "${APPSTORE_API_KEY_ID}" \
|
||||
--apiIssuer "${APPSTORE_API_ISSUER_ID}"
|
||||
|
||||
echo "==> compile-ios: done — uploaded build ${BUILD} to TestFlight"
|
||||
@@ -94,6 +94,7 @@ rsync -avz --delete -e "ssh -i ~/.ssh/ec2_deploy.pem -o StrictHostKeyChecking=no
|
||||
# Guarded: may be absent in a partial manual deploy.
|
||||
[ -f dist/jiu-windows-x64-setup.exe ] && ${SCP} dist/jiu-windows-x64-setup.exe "${EC2_USER}@${EC2_HOST}:/tmp/jiu-windows-x64-setup.exe" || true
|
||||
[ -f dist/jiu-macos-x64.zip ] && ${SCP} dist/jiu-macos-x64.zip "${EC2_USER}@${EC2_HOST}:/tmp/jiu-macos-x64.zip" || true
|
||||
[ -f dist/jiu-android.apk ] && ${SCP} dist/jiu-android.apk "${EC2_USER}@${EC2_HOST}:/tmp/jiu-android.apk" || true
|
||||
|
||||
echo "==> deploy: running remote deploy"
|
||||
|
||||
@@ -134,9 +135,10 @@ rm -rf /tmp/jiu-marketing-new
|
||||
# Publish desktop client installers to the nginx-served downloads dir.
|
||||
# Keep only the latest version: clear old installers, then drop in the new ones.
|
||||
mkdir -p /opt/jiu/downloads
|
||||
rm -f /opt/jiu/downloads/jiu-windows-* /opt/jiu/downloads/jiu-macos-* 2>/dev/null || true
|
||||
rm -f /opt/jiu/downloads/jiu-windows-* /opt/jiu/downloads/jiu-macos-* /opt/jiu/downloads/jiu-android-* /opt/jiu/downloads/jiu-android.apk 2>/dev/null || true
|
||||
[ -f /tmp/jiu-windows-x64-setup.exe ] && mv -f /tmp/jiu-windows-x64-setup.exe /opt/jiu/downloads/ || true
|
||||
[ -f /tmp/jiu-macos-x64.zip ] && mv -f /tmp/jiu-macos-x64.zip /opt/jiu/downloads/ || true
|
||||
[ -f /tmp/jiu-android.apk ] && mv -f /tmp/jiu-android.apk /opt/jiu/downloads/ || true
|
||||
|
||||
# Update jiu nginx config in the pangolin-edge reverse proxy.
|
||||
# nginx now runs inside the `pangolin-edge` container (host networking), not on
|
||||
|
||||
@@ -51,6 +51,7 @@ repo = sys.argv[5]
|
||||
base = "https://jiu.51yanmei.com/downloads"
|
||||
mac_url = f"{base}/jiu-macos-x64.zip"
|
||||
win_url = f"{base}/jiu-windows-x64-setup.exe"
|
||||
android_url = f"{base}/jiu-android.apk"
|
||||
|
||||
lines = []
|
||||
with open('backend/config/version.yaml') as f:
|
||||
@@ -69,11 +70,13 @@ with open('backend/config/version.yaml') as f:
|
||||
lines.append(' macos: "' + mac_url + '"\n')
|
||||
elif line.startswith(' windows:'):
|
||||
lines.append(' windows: "' + win_url + '"\n')
|
||||
elif line.startswith(' android:'):
|
||||
lines.append(' android: "' + android_url + '"\n')
|
||||
else:
|
||||
lines.append(line)
|
||||
with open('backend/config/version.yaml', 'w') as f:
|
||||
f.writelines(lines)
|
||||
print('version.yaml updated to', ver, '| macos:', mac_url, '| windows:', win_url)
|
||||
print('version.yaml updated to', ver, '| macos:', mac_url, '| windows:', win_url, '| android:', android_url)
|
||||
PYEOF
|
||||
|
||||
# Package configs.tar.gz (includes updated version.yaml)
|
||||
@@ -133,5 +136,6 @@ upload_asset dist/marketing.tar.gz
|
||||
upload_asset dist/configs.tar.gz
|
||||
upload_asset dist/jiu-macos-x64.zip
|
||||
upload_asset dist/jiu-windows-x64-setup.exe
|
||||
upload_asset dist/jiu-android.apk
|
||||
|
||||
echo "==> release: done — Release ${TAG} created"
|
||||
|
||||
Reference in New Issue
Block a user