Add multi-platform build and release workflow

This commit is contained in:
Haitao Pan 2026-03-21 10:08:12 +08:00
parent e9e72b3fa5
commit 18f56ecd02
16 changed files with 739 additions and 3 deletions

View File

@ -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 }}"

194
.github/workflows/build-and-release.yml vendored Normal file
View File

@ -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 }}

4
.gitignore vendored
View File

@ -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/

View File

@ -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")
}
}
}
}

20
ios/ExportOptions.plist Normal file
View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>compileBitcode</key>
<false/>
<key>destination</key>
<string>export</string>
<key>method</key>
<string>${EXPORT_METHOD}</string>
<key>signingStyle</key>
<string>automatic</string>
<key>stripSwiftSymbols</key>
<true/>
<key>teamID</key>
<string>N3G9T67W78</string>
<key>thinning</key>
<string>&lt;none&gt;</string>
</dict>
</plist>

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
<Package
Name="XWorkmate"
Manufacturer="plus.svc"
Version="$(var.ProductVersion)"
UpgradeCode="B5BDB376-78C8-4988-B1F2-D3F7B4E0A4D7"
Language="1033">
<MediaTemplate EmbedCab="yes" />
<MajorUpgrade DowngradeErrorMessage="A newer version of XWorkmate is already installed." />
<StandardDirectory Id="ProgramFiles64Folder">
<Directory Id="INSTALLFOLDER" Name="XWorkmate" />
</StandardDirectory>
<Feature Id="MainFeature" Title="XWorkmate" Level="1">
<ComponentGroupRef Id="ApplicationFiles" />
</Feature>
<Icon Id="AppIcon.ico" SourceFile="$(var.SourceDir)\resources\app_icon.ico" />
<Property Id="ARPPRODUCTICON" Value="AppIcon.ico" />
</Package>
<Fragment>
<StandardDirectory Id="ProgramMenuFolder">
<Directory Id="ProgramMenuDir" Name="XWorkmate" />
</StandardDirectory>
<StandardDirectory Id="DesktopFolder" />
</Fragment>
<Fragment>
<ComponentGroup Id="ApplicationFiles" Directory="INSTALLFOLDER">
<Files Include="$(var.SourceDir)\**" />
<Component Id="AppShortcutComponent" Guid="D7AA0C43-123D-4749-A160-5169FA62CB44">
<Shortcut
Id="StartMenuShortcut"
Directory="ProgramMenuDir"
Name="XWorkmate"
Target="[INSTALLFOLDER]xworkmate.exe"
WorkingDirectory="INSTALLFOLDER" />
<Shortcut
Id="DesktopShortcut"
Directory="DesktopFolder"
Name="XWorkmate"
Target="[INSTALLFOLDER]xworkmate.exe"
WorkingDirectory="INSTALLFOLDER" />
<RemoveFolder Id="RemoveProgramMenuDir" Directory="ProgramMenuDir" On="uninstall" />
<RegistryValue Root="HKCU" Key="Software\plus.svc\XWorkmate" Name="installed" Type="integer" Value="1" KeyPath="yes" />
</Component>
</ComponentGroup>
</Fragment>
</Wix>

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,6 @@
#!/usr/bin/env bash
set -euo pipefail
flutter pub get
flutter analyze
flutter test

View File

@ -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 <<EOF
sdk.dir=$android_sdk_root
flutter.sdk=$flutter_root
flutter.buildMode=release
flutter.versionName=$app_version
flutter.versionCode=$version_code
EOF
;;
macos)
brew install cocoapods create-dmg
;;
ios)
brew install cocoapods
;;
windows)
dotnet tool install --global wix --version 4.0.4
echo "$HOME/.dotnet/tools" >> "$GITHUB_PATH"
;;
*)
echo "Unsupported platform: $platform" >&2
exit 1
;;
esac

30
scripts/package-android-apk.sh Executable file
View File

@ -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" <<EOF
storePassword=$ANDROID_KEYSTORE_PASSWORD
keyPassword=$ANDROID_KEY_PASSWORD
keyAlias=$ANDROID_KEY_ALIAS
storeFile=$keystore_path
EOF
else
echo "Android signing secrets are not fully set; using debug signing fallback for non-release builds."
fi
flutter pub get
flutter build apk --release
cp "$root_dir/build/app/outputs/flutter-apk/app-release.apk" "$dist_dir/xworkmate-android-arm64.apk"

73
scripts/package-ios-ipa.sh Executable file
View File

@ -0,0 +1,73 @@
#!/usr/bin/env bash
set -euo pipefail
root_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
dist_dir="$root_dir/dist/ios"
export_method="${APPLE_EXPORT_METHOD:-ad-hoc}"
mkdir -p "$dist_dir"
decode_base64() {
if base64 --help 2>&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

View File

@ -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"