ci(release): add TestFlight release matrix

* chore(release): bump version to 1.1.5+2

* chore(release): bump build metadata for 1.1.5+2

* ci(release): add TestFlight release matrix

---------

Co-authored-by: Haitao Pan <manbuzhe2009@qq.com>
This commit is contained in:
Haitao Pan 2026-06-29 14:27:49 +08:00 committed by GitHub
parent 40ce8a95de
commit a9e7a6fa9e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 277 additions and 8 deletions

View File

@ -122,6 +122,14 @@ jobs:
artifact_name: build-macos-arm64-dmg
artifact_paths: |
dist/macos/*.dmg
- platform: macos
arch: arm64
package: app-store-pkg
release_only: true
runs_on: macos-14
artifact_name: build-macos-arm64-pkg
artifact_paths: |
dist/macos-app-store/*.pkg
- platform: ios
arch: arm64
package: ipa
@ -166,8 +174,12 @@ jobs:
kv/data/github-actions/xworkmate-app APPLE_CERT_P12_BASE64 | APPLE_CERT_P12_BASE64 ;
kv/data/github-actions/xworkmate-app APPLE_CERT_PASSWORD | APPLE_CERT_PASSWORD ;
kv/data/github-actions/xworkmate-app APPLE_PROVISION_PROFILE_BASE64 | APPLE_PROVISION_PROFILE_BASE64 ;
kv/data/github-actions/xworkmate-app APPLE_MAC_PROVISION_PROFILE_BASE64 | APPLE_MAC_PROVISION_PROFILE_BASE64 ;
kv/data/github-actions/xworkmate-app APPLE_KEYCHAIN_PASSWORD | APPLE_KEYCHAIN_PASSWORD ;
kv/data/github-actions/xworkmate-app APPLE_EXPORT_METHOD | APPLE_EXPORT_METHOD ;
kv/data/github-actions/xworkmate-app APP_STORE_CONNECT_API_KEY_ID | APP_STORE_CONNECT_API_KEY_ID ;
kv/data/github-actions/xworkmate-app APP_STORE_CONNECT_ISSUER_ID | APP_STORE_CONNECT_ISSUER_ID ;
kv/data/github-actions/xworkmate-app APP_STORE_CONNECT_API_KEY_P8_BASE64 | APP_STORE_CONNECT_API_KEY_P8_BASE64 ;
kv/data/github-actions/xworkmate-app ANDROID_KEYSTORE_BASE64 | ANDROID_KEYSTORE_BASE64 ;
kv/data/github-actions/xworkmate-app ANDROID_KEYSTORE_PASSWORD | ANDROID_KEYSTORE_PASSWORD ;
kv/data/github-actions/xworkmate-app ANDROID_KEY_ALIAS | ANDROID_KEY_ALIAS ;
@ -183,8 +195,12 @@ jobs:
echo "APPLE_CERT_P12_BASE64=${{ steps.vault.outputs.APPLE_CERT_P12_BASE64 }}"
echo "APPLE_CERT_PASSWORD=${{ steps.vault.outputs.APPLE_CERT_PASSWORD }}"
echo "APPLE_PROVISION_PROFILE_BASE64=${{ steps.vault.outputs.APPLE_PROVISION_PROFILE_BASE64 }}"
echo "APPLE_MAC_PROVISION_PROFILE_BASE64=${{ steps.vault.outputs.APPLE_MAC_PROVISION_PROFILE_BASE64 }}"
echo "APPLE_KEYCHAIN_PASSWORD=${{ steps.vault.outputs.APPLE_KEYCHAIN_PASSWORD }}"
echo "APPLE_EXPORT_METHOD=${{ steps.vault.outputs.APPLE_EXPORT_METHOD }}"
echo "APP_STORE_CONNECT_API_KEY_ID=${{ steps.vault.outputs.APP_STORE_CONNECT_API_KEY_ID }}"
echo "APP_STORE_CONNECT_ISSUER_ID=${{ steps.vault.outputs.APP_STORE_CONNECT_ISSUER_ID }}"
echo "APP_STORE_CONNECT_API_KEY_P8_BASE64=${{ steps.vault.outputs.APP_STORE_CONNECT_API_KEY_P8_BASE64 }}"
echo "ANDROID_KEYSTORE_BASE64=${{ steps.vault.outputs.ANDROID_KEYSTORE_BASE64 }}"
echo "ANDROID_KEYSTORE_PASSWORD=${{ steps.vault.outputs.ANDROID_KEYSTORE_PASSWORD }}"
echo "ANDROID_KEY_ALIAS=${{ steps.vault.outputs.ANDROID_KEY_ALIAS }}"
@ -220,12 +236,12 @@ jobs:
go-version: "1.24.1"
- name: Build platform artifacts
if: ${{ steps.preflight.outputs.should_build_platform == 'true' }}
if: ${{ steps.preflight.outputs.should_build_platform == 'true' && (matrix.release_only != 'true' || env.SHOULD_RELEASE == 'true') }}
shell: bash
run: bash ./scripts/ci/build_matrix_artifacts.sh "$PLATFORM" "$ARCH" "$SHOULD_RELEASE"
run: bash ./scripts/ci/build_matrix_artifacts.sh "$PLATFORM" "$ARCH" "${{ matrix.package }}" "$SHOULD_RELEASE"
- name: Upload build artifacts
if: ${{ steps.preflight.outputs.should_build_platform == 'true' }}
if: ${{ steps.preflight.outputs.should_build_platform == 'true' && (matrix.release_only != 'true' || env.SHOULD_RELEASE == 'true') }}
uses: actions/upload-artifact@v7
with:
name: ${{ matrix.artifact_name }}
@ -273,7 +289,23 @@ jobs:
# never blocked by it being skipped (e.g. push events) or failing.
# build/prepare must still genuinely succeed.
if: ${{ always() && needs.prepare.outputs.should_release == 'true' && needs.prepare.result == 'success' && needs.build.result == 'success' }}
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
include:
- target: github_release
runs_on: ubuntu-22.04
- target: testflight_ios
runs_on: macos-14
artifact_name: build-ios-arm64-ipa
artifact_path: release-artifacts/build-ios-arm64-ipa
testflight_platform: ios
- target: testflight_macos
runs_on: macos-14
artifact_name: build-macos-arm64-pkg
artifact_path: release-artifacts/build-macos-arm64-pkg
testflight_platform: macos
runs-on: ${{ matrix.runs_on }}
permissions:
contents: write
needs:
@ -284,12 +316,46 @@ jobs:
- name: Checkout source
uses: actions/checkout@v7
- name: Load App Store Connect secrets
id: vault
if: ${{ matrix.target != 'github_release' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }}
uses: hashicorp/vault-action@v4
with:
url: ${{ env.VAULT_ADDR }}
method: jwt
role: github-actions-xworkmate-app
jwtGithubAudience: vault
ignoreNotFound: true
secrets: |
kv/data/github-actions/xworkmate-app APPLE_CERT_P12_BASE64 | APPLE_CERT_P12_BASE64 ;
kv/data/github-actions/xworkmate-app APPLE_CERT_PASSWORD | APPLE_CERT_PASSWORD ;
kv/data/github-actions/xworkmate-app APPLE_MAC_PROVISION_PROFILE_BASE64 | APPLE_MAC_PROVISION_PROFILE_BASE64 ;
kv/data/github-actions/xworkmate-app APPLE_KEYCHAIN_PASSWORD | APPLE_KEYCHAIN_PASSWORD ;
kv/data/github-actions/xworkmate-app APP_STORE_CONNECT_API_KEY_ID | APP_STORE_CONNECT_API_KEY_ID ;
kv/data/github-actions/xworkmate-app APP_STORE_CONNECT_ISSUER_ID | APP_STORE_CONNECT_ISSUER_ID ;
kv/data/github-actions/xworkmate-app APP_STORE_CONNECT_API_KEY_P8_BASE64 | APP_STORE_CONNECT_API_KEY_P8_BASE64
- name: Export App Store Connect secrets
if: ${{ matrix.target != 'github_release' }}
run: |
{
echo "APPLE_CERT_P12_BASE64=${{ steps.vault.outputs.APPLE_CERT_P12_BASE64 }}"
echo "APPLE_CERT_PASSWORD=${{ steps.vault.outputs.APPLE_CERT_PASSWORD }}"
echo "APPLE_MAC_PROVISION_PROFILE_BASE64=${{ steps.vault.outputs.APPLE_MAC_PROVISION_PROFILE_BASE64 }}"
echo "APPLE_KEYCHAIN_PASSWORD=${{ steps.vault.outputs.APPLE_KEYCHAIN_PASSWORD }}"
echo "APP_STORE_CONNECT_API_KEY_ID=${{ steps.vault.outputs.APP_STORE_CONNECT_API_KEY_ID }}"
echo "APP_STORE_CONNECT_ISSUER_ID=${{ steps.vault.outputs.APP_STORE_CONNECT_ISSUER_ID }}"
echo "APP_STORE_CONNECT_API_KEY_P8_BASE64=${{ steps.vault.outputs.APP_STORE_CONNECT_API_KEY_P8_BASE64 }}"
} >> "$GITHUB_ENV"
- name: Download all artifacts
if: ${{ matrix.target == 'github_release' }}
uses: actions/download-artifact@v8
with:
path: release-artifacts
- name: Upload assets to GitHub Release
if: ${{ matrix.target == 'github_release' }}
shell: bash
run: bash ./scripts/ci/github_release_upload.sh release-artifacts
env:
@ -297,3 +363,15 @@ jobs:
RELEASE_TAG: ${{ needs.prepare.outputs.release_tag }}
RELEASE_TITLE: ${{ needs.prepare.outputs.release_title }}
RELEASE_NOTES: ${{ needs.prepare.outputs.release_notes }}
- name: Download TestFlight artifact
if: ${{ matrix.target != 'github_release' }}
uses: actions/download-artifact@v8
with:
name: ${{ matrix.artifact_name }}
path: ${{ matrix.artifact_path }}
- name: Upload to TestFlight
if: ${{ matrix.target != 'github_release' }}
shell: bash
run: bash ./scripts/ci/testflight_upload.sh "${{ matrix.testflight_platform }}" "${{ matrix.artifact_path }}"

View File

@ -6,7 +6,8 @@ cd "$repo_root"
eval "$(python3 "$repo_root/scripts/ci/build_version.py" --format shell)"
platform="${1:?platform is required}"
arch="${2:?arch is required}"
should_release="${3:-false}"
package_kind="${3:-}"
should_release="${4:-false}"
flutter pub get
@ -15,9 +16,22 @@ case "$platform" in
bash ./scripts/package-linux.sh
;;
macos)
bash ./scripts/package-flutter-mac-app.sh
mkdir -p dist/macos
find dist -maxdepth 1 -name '*.dmg' -exec mv {} dist/macos/ \;
case "$package_kind" in
dmg)
bash ./scripts/package-flutter-mac-app.sh
mkdir -p dist/macos
find dist -maxdepth 1 -name '*.dmg' -exec mv {} dist/macos/ \;
;;
app-store-pkg)
bash ./scripts/package-macos-app-store-pkg.sh
mkdir -p dist/macos-app-store
find dist -maxdepth 1 -name '*.pkg' -exec mv {} dist/macos-app-store/ \;
;;
*)
echo "Unsupported macOS package kind: $package_kind" >&2
exit 1
;;
esac
;;
windows)
flutter build windows --release \

82
scripts/ci/testflight_upload.sh Executable file
View File

@ -0,0 +1,82 @@
#!/usr/bin/env bash
set -euo pipefail
platform="${1:?platform is required}"
artifact_root="${2:?artifact root is required}"
required_vars=(
APP_STORE_CONNECT_API_KEY_ID
APP_STORE_CONNECT_ISSUER_ID
APP_STORE_CONNECT_API_KEY_P8_BASE64
)
missing=()
for var_name in "${required_vars[@]}"; do
if [[ -z "${!var_name:-}" ]]; then
missing+=("$var_name")
fi
done
if [[ "${#missing[@]}" -gt 0 ]]; then
echo "Missing App Store Connect secrets: ${missing[*]}" >&2
exit 1
fi
if ! command -v xcrun >/dev/null 2>&1; then
echo "xcrun is required to upload TestFlight artifacts." >&2
exit 1
fi
apple_decode_base64() {
if base64 --help 2>&1 | grep -q -- '--decode'; then
base64 --decode
else
base64 -D
fi
}
tmp_dir="$(mktemp -d "${RUNNER_TEMP:-/tmp}/xworkmate-testflight.XXXXXX")"
cleanup() {
rm -rf "$tmp_dir"
}
trap cleanup EXIT
private_keys_dir="$tmp_dir/private_keys"
mkdir -p "$private_keys_dir"
p8_path="$private_keys_dir/AuthKey_${APP_STORE_CONNECT_API_KEY_ID}.p8"
printf '%s' "$APP_STORE_CONNECT_API_KEY_P8_BASE64" | apple_decode_base64 > "$p8_path"
case "$platform" in
ios)
artifact_file="$(find "$artifact_root" -type f -name '*.ipa' | head -n 1)"
;;
macos)
artifact_file="$(find "$artifact_root" -type f -name '*.pkg' | head -n 1)"
;;
*)
echo "Unsupported TestFlight platform: $platform" >&2
exit 1
;;
esac
if [[ -z "$artifact_file" ]]; then
echo "No ipa/pkg artifact found under $artifact_root" >&2
exit 1
fi
export API_PRIVATE_KEYS_DIR="$private_keys_dir"
if [[ "$platform" == "ios" ]]; then
xcrun altool \
--upload-app \
-f "$artifact_file" \
--api-key "$APP_STORE_CONNECT_API_KEY_ID" \
--api-issuer "$APP_STORE_CONNECT_ISSUER_ID" \
--show-progress
else
xcrun altool \
--upload-package "$artifact_file" \
--api-key "$APP_STORE_CONNECT_API_KEY_ID" \
--api-issuer "$APP_STORE_CONNECT_ISSUER_ID" \
--show-progress
fi

View File

@ -0,0 +1,95 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
DIST_DIR="$ROOT_DIR/dist/macos-app-store"
APP_NAME="${APP_NAME:-XWorkmate}"
APP_STORE_DEFINE="${APP_STORE_DEFINE:---dart-define=XWORKMATE_APP_STORE=${XWORKMATE_APP_STORE:-true}}"
source "$ROOT_DIR/scripts/ci/apple_signing.sh"
APPLE_SIGNING_CLEANUP_COMMANDS=()
trap apple_run_cleanup EXIT
required_vars=(
APPLE_CERT_P12_BASE64
APPLE_CERT_PASSWORD
APPLE_MAC_PROVISION_PROFILE_BASE64
APPLE_KEYCHAIN_PASSWORD
)
missing=()
for var_name in "${required_vars[@]}"; do
if [[ -z "${!var_name:-}" ]]; then
missing+=("$var_name")
fi
done
if [[ "${#missing[@]}" -gt 0 ]]; then
echo "Missing macOS TestFlight signing secrets: ${missing[*]}" >&2
exit 1
fi
eval "$(python3 "$ROOT_DIR/scripts/ci/build_version.py" --format shell)"
app_version="$DISPLAY_VERSION"
app_build="$BUILD_NUMBER"
BUILD_DATE_LINE="$(sed -n 's/^build-date:[[:space:]]*//p' "$ROOT_DIR/pubspec.yaml" | head -n 1)"
BUILD_ID_LINE="$(sed -n 's/^build-id:[[:space:]]*//p' "$ROOT_DIR/pubspec.yaml" | head -n 1)"
GIT_BUILD_DATE="$(cd "$ROOT_DIR" && git show -s --format=%cs HEAD 2>/dev/null || true)"
GIT_BUILD_COMMIT="$(cd "$ROOT_DIR" && git rev-parse --short HEAD 2>/dev/null || true)"
app_build_date="${GIT_BUILD_DATE:-${BUILD_DATE_LINE:-unknown}}"
app_build_commit="${GIT_BUILD_COMMIT:-${BUILD_ID_LINE:-unknown}}"
tmp_dir="$(mktemp -d "${RUNNER_TEMP:-/tmp}/xworkmate-macos-app-store.XXXXXX")"
cleanup() {
rm -rf "$tmp_dir"
}
trap cleanup EXIT
apple_setup_signing_keychain
apple_decode_base64() {
if base64 --help 2>&1 | grep -q -- '--decode'; then
base64 --decode
else
base64 -D
fi
}
profile_dir="$HOME/Library/MobileDevice/Provisioning Profiles"
profile_path="$profile_dir/xworkmate-macos.mobileprovision"
mkdir -p "$profile_dir"
printf '%s' "$APPLE_MAC_PROVISION_PROFILE_BASE64" | apple_decode_base64 > "$profile_path"
apple_register_cleanup "rm -f \"$profile_path\""
mkdir -p "$DIST_DIR"
archive_path="$tmp_dir/$APP_NAME.xcarchive"
export_options_path="$tmp_dir/ExportOptions.plist"
sed "s|\${EXPORT_METHOD}|app-store|g" "$ROOT_DIR/ios/ExportOptions.plist" > "$export_options_path"
flutter pub get
flutter build macos --release \
--build-name="$PLATFORM_RELEASE_VERSION" \
--build-number="$app_build" \
--dart-define="XWORKMATE_DISPLAY_VERSION=$app_version" \
--dart-define="XWORKMATE_BUILD_NUMBER=$app_build" \
--dart-define="XWORKMATE_BUILD_DATE=$app_build_date" \
--dart-define="XWORKMATE_BUILD_COMMIT=$app_build_commit" \
"$APP_STORE_DEFINE"
xcodebuild archive \
-workspace "$ROOT_DIR/macos/Runner.xcworkspace" \
-scheme Runner \
-configuration Release \
-archivePath "$archive_path" \
DEVELOPMENT_TEAM="N3G9T67W78"
xcodebuild -exportArchive \
-archivePath "$archive_path" \
-exportPath "$DIST_DIR" \
-exportOptionsPlist "$export_options_path"
if ! compgen -G "$DIST_DIR/*.pkg" >/dev/null; then
echo "No macOS TestFlight pkg was produced under $DIST_DIR" >&2
exit 1
fi
echo "macOS TestFlight pkg: $(find "$DIST_DIR" -maxdepth 1 -name '*.pkg' | head -n 1)"