feat: add reusable Flutter build actions for android, ios, macos, windows, and linux
Update README Actions Table / update-readme (push) Successful in 7s

This commit is contained in:
2026-10-18 13:55:08 -07:00
parent 56dd7d58b3
commit 7a12bb1cc6
11 changed files with 1669 additions and 0 deletions
+244
View File
@@ -0,0 +1,244 @@
name: 'Flutter Build iOS'
description: 'Installs Apple signing certificates and provisioning profiles in an isolated keychain, builds an iOS release IPA, and optionally uploads to TestFlight and Shorebird.'
inputs:
working_directory:
description: 'Path to the Flutter app package directory (e.g., packages/hfw_flutter or .)'
required: false
default: '.'
export_options_plist:
description: 'Relative path inside working_directory to ExportOptions.plist'
required: false
default: 'ios/ExportOptions.plist'
build_args:
description: 'Extra arguments passed to flutter build ipa / ios (e.g., --dart-define=...)'
required: false
default: ''
disable_spm:
description: 'Disable Flutter Swift Package Manager before building'
required: false
default: 'true'
artifact_prefix:
description: 'Optional prefix for copied versioned IPA in output_dir (e.g., hfw-ios -> hfw-ios-v1.0.0.ipa)'
required: false
default: ''
output_dir:
description: 'Directory to copy versioned IPA into when artifact_prefix is set'
required: false
default: 'artifacts'
certificate_base64:
description: 'Base64-encoded Apple iOS distribution .p12 certificate'
required: false
default: ''
certificate_password:
description: 'Password for the Apple .p12 certificate'
required: false
default: ''
provisioning_profile_base64:
description: 'Base64-encoded .mobileprovision profile'
required: false
default: ''
asc_key_id:
description: 'App Store Connect API Key ID'
required: false
default: ''
asc_issuer_id:
description: 'App Store Connect API Issuer ID'
required: false
default: ''
asc_private_key:
description: 'App Store Connect API Private Key (.p8 content)'
required: false
default: ''
apple_id_username:
description: 'Fallback Apple ID email for altool upload'
required: false
default: ''
apple_id_password:
description: 'Fallback Apple ID app-specific password for altool upload'
required: false
default: ''
upload_to_testflight:
description: 'Whether to upload signed IPA to TestFlight when credentials are provided'
required: false
default: 'true'
shorebird_token:
description: 'Shorebird token for optional Shorebird iOS release'
required: false
default: ''
outputs:
version:
description: 'Resolved package version from pubspec.yaml'
value: ${{ steps.build.outputs.version }}
ipa_path:
description: 'Path to the built or packaged IPA file'
value: ${{ steps.build.outputs.ipa_path }}
signed:
description: 'Whether a signed IPA was exported by Xcode'
value: ${{ steps.build.outputs.signed }}
runs:
using: 'composite'
steps:
- name: Install Apple Certificate and Provisioning Profile
shell: bash
env:
IOS_CERT_BASE64: ${{ inputs.certificate_base64 }}
IOS_CERT_PWD: ${{ inputs.certificate_password }}
IOS_PROFILE_BASE64: ${{ inputs.provisioning_profile_base64 }}
run: |
if [ -n "$IOS_CERT_BASE64" ]; then
echo "Setting up temporary keychain for iOS signing..."
security delete-keychain signing_temp.keychain 2>/dev/null || true
rm -f "$HOME/Library/Keychains/signing_temp.keychain-db" || true
KEYCHAIN="$HOME/Library/Keychains/signing_temp.keychain-db"
KEYCHAIN_PWD="temp_password_123"
security create-keychain -p "$KEYCHAIN_PWD" "$KEYCHAIN" || true
security unlock-keychain -p "$KEYCHAIN_PWD" "$KEYCHAIN" || true
security set-keychain-settings -lut 21600 "$KEYCHAIN" || true
security list-keychains -d user -s "$KEYCHAIN" "$HOME/Library/Keychains/login.keychain-db" || true
security default-keychain -s "$HOME/Library/Keychains/login.keychain-db" || true
echo "$IOS_CERT_BASE64" | base64 --decode > /tmp/certificate.p12
security import "/tmp/certificate.p12" -k "$KEYCHAIN" -P "$IOS_CERT_PWD" -T /usr/bin/codesign -T /usr/bin/security -A || true
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PWD" "$KEYCHAIN" || true
if [ -n "$IOS_PROFILE_BASE64" ]; then
mkdir -p "$HOME/Library/MobileDevice/Provisioning Profiles" "$HOME/Library/Developer/Xcode/UserData/Provisioning Profiles"
echo "$IOS_PROFILE_BASE64" | base64 --decode > /tmp/profile.mobileprovision
PROFILE_UUID=$(security cms -D -i /tmp/profile.mobileprovision 2>/dev/null | plutil -extract UUID raw - 2>/dev/null || echo "profile")
cp /tmp/profile.mobileprovision "$HOME/Library/MobileDevice/Provisioning Profiles/${PROFILE_UUID}.mobileprovision"
cp /tmp/profile.mobileprovision "$HOME/Library/Developer/Xcode/UserData/Provisioning Profiles/${PROFILE_UUID}.mobileprovision"
fi
echo "iOS signing credentials installed successfully."
else
echo "Notice: certificate_base64 not configured."
fi
- name: Build iOS IPA
id: build
shell: bash
env:
WORK_DIR: ${{ inputs.working_directory }}
EXPORT_PLIST: ${{ inputs.export_options_plist }}
BUILD_ARGS: ${{ inputs.build_args }}
DISABLE_SPM: ${{ inputs.disable_spm }}
ARTIFACT_PREFIX: ${{ inputs.artifact_prefix }}
OUTPUT_DIR: ${{ inputs.output_dir }}
IOS_CERT_BASE64: ${{ inputs.certificate_base64 }}
ASC_KEY_ID: ${{ inputs.asc_key_id }}
ASC_ISSUER_ID: ${{ inputs.asc_issuer_id }}
ASC_KEY_CONTENT: ${{ inputs.asc_private_key }}
run: |
export PATH="/usr/bin:/bin:/usr/sbin:/sbin:$PATH"
ROOT_DIR="$(pwd)"
VERSION=$(grep '^version: ' "$WORK_DIR/pubspec.yaml" | head -n 1 | cut -d ' ' -f 2 | tr -d '\r')
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
if [ -n "$ASC_KEY_ID" ] && [ -n "$ASC_KEY_CONTENT" ]; then
mkdir -p "$HOME/.appstoreconnect/private_keys"
echo "$ASC_KEY_CONTENT" > "$HOME/.appstoreconnect/private_keys/AuthKey_${ASC_KEY_ID}.p8"
fi
cd "$WORK_DIR"
if [ "$DISABLE_SPM" = "true" ]; then
flutter config --no-enable-swift-package-manager
fi
if [ -f "$EXPORT_PLIST" ] && [ -n "$IOS_CERT_BASE64" ]; then
# shellcheck disable=SC2086
flutter build ipa --release --export-options-plist="$EXPORT_PLIST" $BUILD_ARGS || flutter build ios --release --no-codesign $BUILD_ARGS
else
# shellcheck disable=SC2086
flutter build ios --release --no-codesign $BUILD_ARGS
fi
cd "$ROOT_DIR"
SIGNED_IPA=$(ls "$WORK_DIR"/build/ios/ipa/*.ipa 2>/dev/null | head -n 1 || true)
if [ -n "$SIGNED_IPA" ]; then
echo "signed=true" >> "$GITHUB_OUTPUT"
else
echo "signed=false" >> "$GITHUB_OUTPUT"
fi
IPA_OUT="${SIGNED_IPA#./}"
if [ -n "$ARTIFACT_PREFIX" ]; then
mkdir -p "$OUTPUT_DIR"
TARGET_IPA="$OUTPUT_DIR/${ARTIFACT_PREFIX}-v${VERSION}.ipa"
if [ -n "$SIGNED_IPA" ]; then
cp "$SIGNED_IPA" "$TARGET_IPA"
elif [ -d "$WORK_DIR/build/ios/iphoneos/Runner.app" ]; then
rm -rf /tmp/Payload
mkdir -p /tmp/Payload
cp -r "$WORK_DIR/build/ios/iphoneos/Runner.app" /tmp/Payload/
(cd /tmp && zip -qr "$ROOT_DIR/$TARGET_IPA" Payload)
rm -rf /tmp/Payload
else
touch "$TARGET_IPA"
fi
IPA_OUT="$TARGET_IPA"
echo "Collected iOS artifact at $IPA_OUT"
fi
echo "ipa_path=$IPA_OUT" >> "$GITHUB_OUTPUT"
- name: Push to TestFlight via App Store Connect API
if: inputs.upload_to_testflight == 'true'
shell: bash
env:
WORK_DIR: ${{ inputs.working_directory }}
ASC_KEY_ID: ${{ inputs.asc_key_id }}
ASC_ISSUER_ID: ${{ inputs.asc_issuer_id }}
ASC_KEY_CONTENT: ${{ inputs.asc_private_key }}
AC_USERNAME: ${{ inputs.apple_id_username }}
AC_PASSWORD: ${{ inputs.apple_id_password }}
run: |
IPA_FILE=$(ls "$WORK_DIR"/build/ios/ipa/*.ipa 2>/dev/null | head -n 1 || true)
if [ -z "$IPA_FILE" ]; then
echo "Skipping TestFlight upload: no signed App Store IPA was exported in $WORK_DIR/build/ios/ipa/."
exit 0
fi
if [ -n "$ASC_KEY_ID" ] && [ -n "$ASC_ISSUER_ID" ] && [ -n "$ASC_KEY_CONTENT" ]; then
echo "Uploading signed IPA ($IPA_FILE) to TestFlight via App Store Connect API Key..."
mkdir -p "$HOME/.appstoreconnect/private_keys"
echo "$ASC_KEY_CONTENT" > "$HOME/.appstoreconnect/private_keys/AuthKey_${ASC_KEY_ID}.p8"
xcrun altool --upload-app \
-f "$IPA_FILE" \
--type ios \
--apiKey "$ASC_KEY_ID" \
--apiIssuer "$ASC_ISSUER_ID"
elif [ -n "$AC_USERNAME" ] && [ -n "$AC_PASSWORD" ]; then
echo "Uploading signed IPA ($IPA_FILE) to TestFlight via Apple ID App-Specific Password..."
xcrun altool --upload-app \
-f "$IPA_FILE" \
--type ios \
--u "$AC_USERNAME" \
--p "$AC_PASSWORD"
else
echo "Skipping TestFlight upload (App Store Connect credentials not configured)."
fi
- name: Release via Shorebird (Optional)
if: inputs.shorebird_token != ''
shell: bash
env:
WORK_DIR: ${{ inputs.working_directory }}
SHOREBIRD_TOKEN: ${{ inputs.shorebird_token }}
run: |
if [ -n "$SHOREBIRD_TOKEN" ]; then
ROOT_DIR="$(pwd)"
cd "$WORK_DIR"
flutter pub global activate shorebird_cli
shorebird release ios --artifact=ipa
cd "$ROOT_DIR"
fi
- name: Clean up keychain
if: always()
shell: bash
run: |
security list-keychains -d user -s "$HOME/Library/Keychains/login.keychain-db" || true
security default-keychain -s "$HOME/Library/Keychains/login.keychain-db" || true
security delete-keychain "$HOME/Library/Keychains/signing_temp.keychain-db" 2>/dev/null || true
rm -f "$HOME/Library/Keychains/signing_temp.keychain-db" /tmp/certificate.p12 /tmp/profile.mobileprovision || true