From 18f56ecd0252056ee08b051a32a9ebd5d369aaf0 Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Sat, 21 Mar 2026 10:08:12 +0800 Subject: [PATCH] Add multi-platform build and release workflow --- .github/actions/setup-flutter-sdk/action.yml | 20 ++ .github/workflows/build-and-release.yml | 194 +++++++++++++++++++ .gitignore | 4 + android/app/build.gradle.kts | 28 ++- ios/ExportOptions.plist | 20 ++ packaging/windows/main.wxs | 51 +++++ scripts/ci/build_matrix_artifacts.sh | 45 +++++ scripts/ci/compute_release_metadata.sh | 27 +++ scripts/ci/github_release_upload.sh | 29 +++ scripts/ci/install_flutter_sdk.ps1 | 27 +++ scripts/ci/install_flutter_sdk.sh | 47 +++++ scripts/ci/run_code_analysis.sh | 6 + scripts/ci/setup_platform_deps.sh | 81 ++++++++ scripts/package-android-apk.sh | 30 +++ scripts/package-ios-ipa.sh | 73 +++++++ scripts/package-windows-msi.ps1 | 60 ++++++ 16 files changed, 739 insertions(+), 3 deletions(-) create mode 100644 .github/actions/setup-flutter-sdk/action.yml create mode 100644 .github/workflows/build-and-release.yml create mode 100644 ios/ExportOptions.plist create mode 100644 packaging/windows/main.wxs create mode 100755 scripts/ci/build_matrix_artifacts.sh create mode 100755 scripts/ci/compute_release_metadata.sh create mode 100755 scripts/ci/github_release_upload.sh create mode 100644 scripts/ci/install_flutter_sdk.ps1 create mode 100755 scripts/ci/install_flutter_sdk.sh create mode 100755 scripts/ci/run_code_analysis.sh create mode 100755 scripts/ci/setup_platform_deps.sh create mode 100755 scripts/package-android-apk.sh create mode 100755 scripts/package-ios-ipa.sh create mode 100644 scripts/package-windows-msi.ps1 diff --git a/.github/actions/setup-flutter-sdk/action.yml b/.github/actions/setup-flutter-sdk/action.yml new file mode 100644 index 00000000..8fa593ab --- /dev/null +++ b/.github/actions/setup-flutter-sdk/action.yml @@ -0,0 +1,20 @@ +name: Setup Flutter SDK +description: Install the pinned Flutter SDK on Linux, macOS, or Windows runners + +inputs: + flutter-version: + description: Flutter SDK version to install + required: true + +runs: + using: composite + steps: + - name: Install Flutter SDK on Linux/macOS + if: ${{ runner.os != 'Windows' }} + shell: bash + run: bash ./scripts/ci/install_flutter_sdk.sh "${{ inputs.flutter-version }}" + + - name: Install Flutter SDK on Windows + if: ${{ runner.os == 'Windows' }} + shell: pwsh + run: pwsh -File ./scripts/ci/install_flutter_sdk.ps1 -FlutterVersion "${{ inputs.flutter-version }}" diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml new file mode 100644 index 00000000..b159f2a6 --- /dev/null +++ b/.github/workflows/build-and-release.yml @@ -0,0 +1,194 @@ +name: Build and Release XWorkmate Packages + +on: + push: + branches: + - main + tags: + - "v*" + paths-ignore: + - "README.md" + pull_request: + branches: + - main + paths: + - "lib/**" + - "assets/**" + - "android/**" + - "ios/**" + - "macos/**" + - "linux/**" + - "windows/**" + - "rust/**" + - "scripts/**" + - "pubspec.*" + - "Makefile" + - ".github/actions/setup-flutter-sdk/action.yml" + - ".github/workflows/build-and-release.yml" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: build-and-release-${{ github.ref }} + cancel-in-progress: true + +env: + FLUTTER_VERSION: 3.41.4 + +jobs: + prepare: + runs-on: ubuntu-22.04 + permissions: + contents: write + outputs: + should_release: ${{ steps.flags.outputs.should_release }} + release_tag: ${{ steps.meta.outputs.release_tag }} + release_title: ${{ steps.meta.outputs.release_title }} + release_notes: ${{ steps.meta.outputs.release_notes }} + steps: + - name: Checkout source + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Determine release mode + id: flags + shell: bash + run: | + if [[ "${GITHUB_REF:-}" == refs/tags/v* || "${GITHUB_EVENT_NAME:-}" == "workflow_dispatch" ]]; then + echo "should_release=true" >> "$GITHUB_OUTPUT" + else + echo "should_release=false" >> "$GITHUB_OUTPUT" + fi + + - name: Compute release metadata + id: meta + shell: bash + run: bash ./scripts/ci/compute_release_metadata.sh + + verify: + if: ${{ github.event_name == 'pull_request' || github.ref == 'refs/heads/main' }} + runs-on: ubuntu-22.04 + steps: + - name: Checkout source + uses: actions/checkout@v4 + + - name: Set up Flutter SDK + uses: ./.github/actions/setup-flutter-sdk + with: + flutter-version: ${{ env.FLUTTER_VERSION }} + + - name: Install Linux dependencies + shell: bash + run: bash ./scripts/ci/setup_platform_deps.sh linux + + - name: Run analysis and tests + shell: bash + run: bash ./scripts/ci/run_code_analysis.sh + + build: + strategy: + fail-fast: false + matrix: + include: + - platform: linux + arch: amd64 + runs_on: ubuntu-22.04 + - platform: windows + arch: amd64 + runs_on: windows-2022 + - platform: macos + arch: arm64 + runs_on: macos-14 + - platform: ios + arch: arm64 + runs_on: macos-14 + - platform: android + arch: arm64 + runs_on: ubuntu-22.04 + runs-on: ${{ matrix.runs_on }} + needs: + - prepare + - verify + env: + PLATFORM: ${{ matrix.platform }} + ARCH: ${{ matrix.arch }} + SHOULD_RELEASE: ${{ needs.prepare.outputs.should_release }} + XWORKMATE_SIGN_IDENTITY: ${{ secrets.XWORKMATE_SIGN_IDENTITY }} + WINDOWS_PFX_BASE64: ${{ secrets.WINDOWS_PFX_BASE64 }} + WINDOWS_PFX_PASSWORD: ${{ secrets.WINDOWS_PFX_PASSWORD }} + WINDOWS_CODESIGN_SUBJECT: ${{ secrets.WINDOWS_CODESIGN_SUBJECT }} + APPLE_CERT_P12_BASE64: ${{ secrets.APPLE_CERT_P12_BASE64 }} + APPLE_CERT_PASSWORD: ${{ secrets.APPLE_CERT_PASSWORD }} + APPLE_PROVISION_PROFILE_BASE64: ${{ secrets.APPLE_PROVISION_PROFILE_BASE64 }} + APPLE_KEYCHAIN_PASSWORD: ${{ secrets.APPLE_KEYCHAIN_PASSWORD }} + APPLE_EXPORT_METHOD: ${{ secrets.APPLE_EXPORT_METHOD }} + ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} + ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} + ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} + ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} + steps: + - name: Checkout source + uses: actions/checkout@v4 + + - name: Set up Flutter SDK + uses: ./.github/actions/setup-flutter-sdk + with: + flutter-version: ${{ env.FLUTTER_VERSION }} + + - name: Install Go + if: ${{ matrix.platform == 'macos' }} + uses: actions/setup-go@v5 + with: + go-version: "1.24.1" + + - name: Install platform dependencies + shell: bash + run: bash ./scripts/ci/setup_platform_deps.sh "$PLATFORM" + + - name: Build platform artifacts + shell: bash + run: bash ./scripts/ci/build_matrix_artifacts.sh "$PLATFORM" "$ARCH" "$SHOULD_RELEASE" + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: build-${{ matrix.platform }}-${{ matrix.arch }} + path: | + dist/linux/*.deb + dist/linux/*.rpm + dist/macos/*.dmg + dist/windows/*.msi + dist/windows/*.zip + dist/ios/*.ipa + dist/ios/*.zip + dist/android/*.apk + if-no-files-found: error + + release: + if: ${{ needs.prepare.outputs.should_release == 'true' }} + runs-on: ubuntu-22.04 + permissions: + contents: write + needs: + - prepare + - build + steps: + - name: Checkout source + uses: actions/checkout@v4 + + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: release-artifacts + + - name: Upload assets to GitHub Release + shell: bash + run: bash ./scripts/ci/github_release_upload.sh release-artifacts + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASE_TAG: ${{ needs.prepare.outputs.release_tag }} + RELEASE_TITLE: ${{ needs.prepare.outputs.release_title }} + RELEASE_NOTES: ${{ needs.prepare.outputs.release_notes }} diff --git a/.gitignore b/.gitignore index 0f16cb03..d87aa0df 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,10 @@ app.*.map.json /android/app/debug /android/app/profile /android/app/release +/android/key.properties +/*.p12 +/*.mobileprovision +/*.keystore # Rust / FFI artifacts /rust/target/ diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 911a2976..4e0fb86d 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -5,6 +5,14 @@ plugins { id("dev.flutter.flutter-gradle-plugin") } +import java.util.Properties + +val keystoreProperties = Properties() +val keystorePropertiesFile = rootProject.file("key.properties") +if (keystorePropertiesFile.exists()) { + keystorePropertiesFile.inputStream().use { keystoreProperties.load(it) } +} + android { namespace = "plus.svc.xworkmate" compileSdk = flutter.compileSdkVersion @@ -30,11 +38,25 @@ android { versionName = flutter.versionName } + signingConfigs { + create("release") { + val storeFilePath = keystoreProperties.getProperty("storeFile") + if (!storeFilePath.isNullOrBlank()) { + storeFile = file(storeFilePath) + storePassword = keystoreProperties.getProperty("storePassword") + keyAlias = keystoreProperties.getProperty("keyAlias") + keyPassword = keystoreProperties.getProperty("keyPassword") + } + } + } + buildTypes { release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig = signingConfigs.getByName("debug") + signingConfig = if (keystorePropertiesFile.exists()) { + signingConfigs.getByName("release") + } else { + signingConfigs.getByName("debug") + } } } } diff --git a/ios/ExportOptions.plist b/ios/ExportOptions.plist new file mode 100644 index 00000000..6119752b --- /dev/null +++ b/ios/ExportOptions.plist @@ -0,0 +1,20 @@ + + + + + compileBitcode + + destination + export + method + ${EXPORT_METHOD} + signingStyle + automatic + stripSwiftSymbols + + teamID + N3G9T67W78 + thinning + <none> + + diff --git a/packaging/windows/main.wxs b/packaging/windows/main.wxs new file mode 100644 index 00000000..5aeca4bc --- /dev/null +++ b/packaging/windows/main.wxs @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/ci/build_matrix_artifacts.sh b/scripts/ci/build_matrix_artifacts.sh new file mode 100755 index 00000000..c2dfac65 --- /dev/null +++ b/scripts/ci/build_matrix_artifacts.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +set -euo pipefail + +platform="${1:?platform is required}" +arch="${2:?arch is required}" +should_release="${3:-false}" + +flutter pub get + +case "$platform" in + linux) + 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/ \; + ;; + windows) + flutter build windows --release + pwsh -File ./scripts/package-windows-msi.ps1 -Arch "$arch" + ;; + ios) + if [[ "$should_release" == "true" ]]; then + bash ./scripts/package-ios-ipa.sh + else + echo "Release secrets not required for non-release runs; building unsigned iOS app bundle." + flutter build ios --release --no-codesign + mkdir -p dist/ios + ( + cd build/ios/iphoneos + rm -f XWorkmate.app.zip + zip -qry XWorkmate.app.zip Runner.app + mv XWorkmate.app.zip ../../../dist/ios/ + ) + fi + ;; + android) + bash ./scripts/package-android-apk.sh + ;; + *) + echo "Unsupported platform: $platform" >&2 + exit 1 + ;; +esac diff --git a/scripts/ci/compute_release_metadata.sh b/scripts/ci/compute_release_metadata.sh new file mode 100755 index 00000000..751ee055 --- /dev/null +++ b/scripts/ci/compute_release_metadata.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ -z "${GITHUB_OUTPUT:-}" ]]; then + echo "GITHUB_OUTPUT is required" >&2 + exit 1 +fi + +if [[ "${GITHUB_REF_TYPE:-}" == "tag" ]]; then + release_tag="${GITHUB_REF_NAME}" + release_title="Release ${GITHUB_REF_NAME}" + release_notes="Automated release for ${GITHUB_REF_NAME}" +elif [[ "${GITHUB_EVENT_NAME:-}" == "workflow_dispatch" ]]; then + release_tag="manual-${GITHUB_RUN_NUMBER:-0}" + release_title="Manual Build ${GITHUB_RUN_NUMBER:-0}" + release_notes="Automated manual build from ${GITHUB_SHA:-unknown}" +else + release_tag="main-${GITHUB_RUN_NUMBER:-0}" + release_title="Main Build ${GITHUB_RUN_NUMBER:-0}" + release_notes="Automated build from ${GITHUB_SHA:-unknown}" +fi + +{ + echo "release_tag=${release_tag}" + echo "release_title=${release_title}" + echo "release_notes=${release_notes}" +} >> "$GITHUB_OUTPUT" diff --git a/scripts/ci/github_release_upload.sh b/scripts/ci/github_release_upload.sh new file mode 100755 index 00000000..c6abe520 --- /dev/null +++ b/scripts/ci/github_release_upload.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -euo pipefail + +artifact_dir="${1:-release-artifacts}" +tag="${RELEASE_TAG:-manual-${GITHUB_RUN_NUMBER:-0}}" +title="${RELEASE_TITLE:-Manual Build ${GITHUB_RUN_NUMBER:-0}}" +notes="${RELEASE_NOTES:-Automated build}" + +if ! command -v gh >/dev/null 2>&1; then + echo "GitHub CLI is required to upload release artifacts." >&2 + exit 1 +fi + +if ! gh release view "$tag" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then + gh release create "$tag" --repo "${GITHUB_REPOSITORY}" --title "$title" --notes "$notes" +else + gh release edit "$tag" --repo "${GITHUB_REPOSITORY}" --title "$title" --notes "$notes" +fi + +mapfile -d '' files < <(find "$artifact_dir" -type f -print0) + +if [[ "${#files[@]}" -eq 0 ]]; then + echo "No release artifacts found in $artifact_dir" >&2 + exit 1 +fi + +for file in "${files[@]}"; do + gh release upload "$tag" "$file" --repo "${GITHUB_REPOSITORY}" --clobber +done diff --git a/scripts/ci/install_flutter_sdk.ps1 b/scripts/ci/install_flutter_sdk.ps1 new file mode 100644 index 00000000..793ef485 --- /dev/null +++ b/scripts/ci/install_flutter_sdk.ps1 @@ -0,0 +1,27 @@ +param( + [Parameter(Mandatory = $true)] + [string]$FlutterVersion +) + +$ErrorActionPreference = "Stop" + +$installRoot = Join-Path ($env:RUNNER_TEMP ?? $env:TEMP) "flutter-sdk" +$flutterRoot = Join-Path $installRoot "flutter" +$archiveName = "flutter_windows_${FlutterVersion}-stable.zip" +$archivePath = Join-Path ($env:RUNNER_TEMP ?? $env:TEMP) $archiveName +$downloadUrl = "https://storage.googleapis.com/flutter_infra_release/releases/stable/windows/$archiveName" + +New-Item -ItemType Directory -Path $installRoot -Force | Out-Null + +if (-not (Test-Path (Join-Path $flutterRoot "bin/flutter.bat"))) { + Invoke-WebRequest -Uri $downloadUrl -OutFile $archivePath + if (Test-Path $flutterRoot) { + Remove-Item -Recurse -Force $flutterRoot + } + Expand-Archive -Path $archivePath -DestinationPath $installRoot -Force +} + +"$flutterRoot\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append +& "$flutterRoot\bin\flutter.bat" --disable-analytics +& "$flutterRoot\bin\flutter.bat" config --no-analytics +& "$flutterRoot\bin\flutter.bat" --version diff --git a/scripts/ci/install_flutter_sdk.sh b/scripts/ci/install_flutter_sdk.sh new file mode 100755 index 00000000..6ec3b974 --- /dev/null +++ b/scripts/ci/install_flutter_sdk.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +set -euo pipefail + +version="${1:?flutter version is required}" +runner_temp="${RUNNER_TEMP:-/tmp}" +install_root="$runner_temp/flutter-sdk" +archive_name="flutter_linux_${version}-stable.tar.xz" + +case "${RUNNER_OS:-$(uname -s)}" in + Linux) + archive_name="flutter_linux_${version}-stable.tar.xz" + download_url="https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/${archive_name}" + ;; + macOS|Darwin) + if [[ "$(uname -m)" == "arm64" ]]; then + archive_name="flutter_macos_arm64_${version}-stable.zip" + else + archive_name="flutter_macos_${version}-stable.zip" + fi + download_url="https://storage.googleapis.com/flutter_infra_release/releases/stable/macos/${archive_name}" + ;; + *) + echo "Unsupported OS for Flutter install: ${RUNNER_OS:-$(uname -s)}" >&2 + exit 1 + ;; +esac + +mkdir -p "$install_root" + +if [[ ! -x "$install_root/flutter/bin/flutter" ]]; then + archive_path="$runner_temp/$archive_name" + curl -fsSL "$download_url" -o "$archive_path" + rm -rf "$install_root/flutter" + case "$archive_name" in + *.tar.xz) + tar -xJf "$archive_path" -C "$install_root" + ;; + *.zip) + unzip -q "$archive_path" -d "$install_root" + ;; + esac +fi + +echo "$install_root/flutter/bin" >> "$GITHUB_PATH" +"$install_root/flutter/bin/flutter" --disable-analytics +"$install_root/flutter/bin/flutter" config --no-analytics +"$install_root/flutter/bin/flutter" --version diff --git a/scripts/ci/run_code_analysis.sh b/scripts/ci/run_code_analysis.sh new file mode 100755 index 00000000..51846902 --- /dev/null +++ b/scripts/ci/run_code_analysis.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -euo pipefail + +flutter pub get +flutter analyze +flutter test diff --git a/scripts/ci/setup_platform_deps.sh b/scripts/ci/setup_platform_deps.sh new file mode 100755 index 00000000..86e77ed2 --- /dev/null +++ b/scripts/ci/setup_platform_deps.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +set -euo pipefail + +platform="${1:?platform is required}" + +case "$platform" in + linux) + sudo apt-get update + sudo apt-get install -y \ + clang \ + cmake \ + ninja-build \ + libgtk-3-dev \ + pkg-config \ + libx11-dev \ + libgl1-mesa-dev \ + libayatana-appindicator3-dev \ + dpkg-dev \ + rpm \ + imagemagick + ;; + android) + sudo apt-get update + sudo apt-get install -y clang cmake ninja-build libgtk-3-dev pkg-config libx11-dev libgl1-mesa-dev + + android_sdk_root="${ANDROID_SDK_ROOT:-${ANDROID_HOME:-/usr/local/lib/android/sdk}}" + export ANDROID_SDK_ROOT="$android_sdk_root" + export ANDROID_HOME="$android_sdk_root" + + { + echo "ANDROID_SDK_ROOT=$android_sdk_root" + echo "ANDROID_HOME=$android_sdk_root" + } >> "$GITHUB_ENV" + + for candidate in \ + "$android_sdk_root/cmdline-tools/latest/bin/sdkmanager" \ + "$android_sdk_root/cmdline-tools/bin/sdkmanager" \ + "$android_sdk_root/tools/bin/sdkmanager"; do + if [[ -x "$candidate" ]]; then + sdkmanager="$candidate" + break + fi + done + + if [[ -z "${sdkmanager:-}" ]]; then + echo "Android sdkmanager not found under $android_sdk_root" >&2 + exit 1 + fi + + yes | "$sdkmanager" --licenses >/dev/null 2>&1 || true + "$sdkmanager" "platform-tools" "platforms;android-35" "build-tools;35.0.0" "ndk;27.1.12297006" + + flutter_bin="$(command -v flutter)" + flutter_root="$(cd "$(dirname "$flutter_bin")/.." && pwd)" + app_version="$(sed -n 's/^version:[[:space:]]*//p' pubspec.yaml | head -n 1)" + app_version="${app_version%%+*}" + version_code="${GITHUB_RUN_NUMBER:-1}" + + cat > android/local.properties <> "$GITHUB_PATH" + ;; + *) + echo "Unsupported platform: $platform" >&2 + exit 1 + ;; +esac diff --git a/scripts/package-android-apk.sh b/scripts/package-android-apk.sh new file mode 100755 index 00000000..32b0bbcb --- /dev/null +++ b/scripts/package-android-apk.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +set -euo pipefail + +root_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +dist_dir="$root_dir/dist/android" +key_properties="$root_dir/android/key.properties" +keystore_path="$root_dir/android/upload-keystore.jks" + +mkdir -p "$dist_dir" + +cleanup() { + rm -f "$key_properties" "$keystore_path" +} +trap cleanup EXIT + +if [[ -n "${ANDROID_KEYSTORE_BASE64:-}" && -n "${ANDROID_KEYSTORE_PASSWORD:-}" && -n "${ANDROID_KEY_ALIAS:-}" && -n "${ANDROID_KEY_PASSWORD:-}" ]]; then + printf '%s' "$ANDROID_KEYSTORE_BASE64" | base64 --decode > "$keystore_path" + cat > "$key_properties" <&1 | grep -q -- '--decode'; then + base64 --decode + else + base64 -D + fi +} + +required_vars=( + APPLE_CERT_P12_BASE64 + APPLE_CERT_PASSWORD + APPLE_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 iOS signing secrets: ${missing[*]}" >&2 + exit 1 +fi + +tmp_dir="$(mktemp -d "${RUNNER_TEMP:-/tmp}/xworkmate-ios.XXXXXX")" +keychain_name="xworkmate-build.keychain-db" +keychain_path="$HOME/Library/Keychains/$keychain_name" +cert_path="$tmp_dir/dist-cert.p12" +profile_path="$tmp_dir/profile.mobileprovision" +export_options_path="$tmp_dir/ExportOptions.plist" + +cleanup() { + security delete-keychain "$keychain_path" >/dev/null 2>&1 || true + rm -rf "$tmp_dir" +} +trap cleanup EXIT + +printf '%s' "$APPLE_CERT_P12_BASE64" | decode_base64 > "$cert_path" +printf '%s' "$APPLE_PROVISION_PROFILE_BASE64" | decode_base64 > "$profile_path" + +security create-keychain -p "$APPLE_KEYCHAIN_PASSWORD" "$keychain_name" +security set-keychain-settings -lut 21600 "$keychain_path" +security unlock-keychain -p "$APPLE_KEYCHAIN_PASSWORD" "$keychain_path" +security import "$cert_path" -P "$APPLE_CERT_PASSWORD" -A -t cert -f pkcs12 -k "$keychain_path" +security list-keychains -d user -s "$keychain_path" +security set-key-partition-list -S apple-tool:,apple: -s -k "$APPLE_KEYCHAIN_PASSWORD" "$keychain_path" + +mkdir -p "$HOME/Library/MobileDevice/Provisioning Profiles" +cp "$profile_path" "$HOME/Library/MobileDevice/Provisioning Profiles/xworkmate.mobileprovision" + +sed "s|\${EXPORT_METHOD}|$export_method|g" "$root_dir/ios/ExportOptions.plist" > "$export_options_path" + +flutter pub get +flutter build ipa --release --export-options-plist="$export_options_path" + +find "$root_dir/build/ios/ipa" -maxdepth 1 -name '*.ipa' -exec cp {} "$dist_dir/" \; + +if ! compgen -G "$dist_dir/*.ipa" >/dev/null; then + echo "No IPA was produced under $dist_dir" >&2 + exit 1 +fi diff --git a/scripts/package-windows-msi.ps1 b/scripts/package-windows-msi.ps1 new file mode 100644 index 00000000..126cbaa3 --- /dev/null +++ b/scripts/package-windows-msi.ps1 @@ -0,0 +1,60 @@ +param( + [string]$Arch = "amd64" +) + +$ErrorActionPreference = "Stop" + +$root = Resolve-Path (Join-Path $PSScriptRoot "..") +$sourceDir = Join-Path $root "build\windows\x64\runner\Release" +$distDir = Join-Path $root "dist\windows" +$wxsPath = Join-Path $root "packaging\windows\main.wxs" +$versionLine = (Get-Content (Join-Path $root "pubspec.yaml") | Select-String '^version:\s*').ToString() +$versionValue = ($versionLine -replace '^version:\s*', '').Split('+')[0] +$msiPath = Join-Path $distDir "xworkmate-$versionValue-$Arch.msi" +$zipPath = Join-Path $distDir "xworkmate-windows-$Arch.zip" + +if (-not (Test-Path $sourceDir)) { + throw "Expected Windows release bundle not found: $sourceDir" +} + +New-Item -ItemType Directory -Path $distDir -Force | Out-Null + +if (Test-Path $zipPath) { + Remove-Item -Force $zipPath +} +Compress-Archive -Path (Join-Path $sourceDir '*') -DestinationPath $zipPath + +$wixVersion = $versionValue +if ($wixVersion -notmatch '^\d+\.\d+\.\d+$') { + $wixVersion = "0.0.0" +} + +& wix build $wxsPath ` + -arch x64 ` + -d SourceDir=$sourceDir ` + -d ProductVersion=$wixVersion ` + -o $msiPath + +if ($env:WINDOWS_PFX_BASE64 -and $env:WINDOWS_PFX_PASSWORD) { + $certDir = Join-Path $env:RUNNER_TEMP "windows-signing" + $pfxPath = Join-Path $certDir "codesign.pfx" + New-Item -ItemType Directory -Path $certDir -Force | Out-Null + [IO.File]::WriteAllBytes($pfxPath, [Convert]::FromBase64String($env:WINDOWS_PFX_BASE64)) + + $signtool = Get-ChildItem "C:\Program Files (x86)\Windows Kits\10\bin" -Recurse -Filter signtool.exe | + Sort-Object FullName -Descending | + Select-Object -First 1 + if (-not $signtool) { + throw "signtool.exe not found after Windows SDK discovery." + } + + $subjectArgs = @() + if ($env:WINDOWS_CODESIGN_SUBJECT) { + $subjectArgs = @("/n", $env:WINDOWS_CODESIGN_SUBJECT) + } + + & $signtool.FullName sign /fd SHA256 /f $pfxPath /p $env:WINDOWS_PFX_PASSWORD /tr http://timestamp.digicert.com /td SHA256 @subjectArgs $msiPath +} + +Write-Host "MSI: $msiPath" +Write-Host "ZIP: $zipPath"