Last active
June 10, 2024 22:15
-
-
Save simplenotezy/f52b470293edafa919584d911cc5e6b9 to your computer and use it in GitHub Desktop.
Bootstraps a flutter project with Fastlane and Github Action to deploy on release
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ignore_for_file: avoid_print | |
import 'dart:io'; | |
void main() { | |
// Read the PRODUCT_BUNDLE_IDENTIFIER from ios/Runner.xcodeproj/project.pbxproj | |
// or fallback to com.example.app | |
String? appBundleId = 'com.example.app'; | |
File iosProjectFile = File('ios/Runner.xcodeproj/project.pbxproj'); | |
if (iosProjectFile.existsSync()) { | |
String content = iosProjectFile.readAsStringSync(); | |
RegExp regExp = RegExp(r'PRODUCT_BUNDLE_IDENTIFIER = (.+);'); | |
Match? match = regExp.firstMatch(content); | |
if (match != null) { | |
appBundleId = match.group(1); | |
} | |
} | |
// read applicationId = "com.hejtech.flutter_fastlane_tutorial" from android/app/build.gradle | |
// or fallback to com.example.app | |
String? appPackage = 'com.example.app'; | |
File androidBuildFile = File('android/app/build.gradle'); | |
if (androidBuildFile.existsSync()) { | |
String content = androidBuildFile.readAsStringSync(); | |
RegExp regExp = RegExp(r'applicationId = "(.+)"'); | |
Match? match = regExp.firstMatch(content); | |
if (match != null) { | |
appPackage = match.group(1); | |
} | |
} | |
Map<String, String> files = { | |
'android/fastlane/.env': ''' | |
# This file should be added to your .gitignore file | |
# It contains sensitive information that should not be versioned | |
# https://docs.fastlane.tools/best-practices/keys/#where-do-i-store-my-keys | |
FIREBASE_APP_ID= | |
APP_PACKAGE_NAME=$appPackage | |
# Optional: Override the path to your Firebase service account JSON file (defaults to root of your project) | |
# GOOGLE_SERVICE_ACCOUNT_JSON_PATH= | |
''', | |
'android/fastlane/Appfile': ''' | |
package_name(ENV["APP_PACKAGE_NAME"]) # e.g. com.krausefx.app | |
''', | |
'android/fastlane/Fastfile': ''' | |
import "../../Fastfile" | |
default_platform(:android) | |
platform :android do | |
# Build flutter Android app | |
lane :build do |options| | |
verify_env(envs: [ | |
"APP_PACKAGE_NAME" | |
]) | |
# Verify 'firebase_app_distribution_service_account.json' file exists | |
unless File.exist?(google_service_account_json_path) | |
UI.user_error!("google_service_account.json file not found. Please add it to the root of the flutter project. See https://docs.fastlane.tools/actions/supply/") | |
end | |
# Verify version number is correct | |
if !is_ci && (!options[:version_number]) | |
version_number = get_version_from_pubspec() | |
continue = UI.confirm("Deploying version #{version_number} (from pubspec.yaml) to Play Store. Continue?") | |
unless continue | |
UI.user_error!("Aborted") | |
end | |
end | |
build_flutter_app( | |
type: options[:type] || "appbundle", | |
no_codesign: options[:no_codesign], | |
config_only: options[:config_only], | |
build_number: options[:build_number], | |
version_number: options[:version_number], | |
store: "playstore" | |
) | |
end | |
# Release to Play Store using Fastlane Supply (https://docs.fastlane.tools/actions/supply/) | |
desc "Release to Play Store" | |
lane :release_play_store do |options| | |
begin | |
build( | |
no_codesign: options[:no_codesign], | |
config_only: options[:config_only], | |
build_number: options[:build_number], | |
version_number: options[:version_number] | |
) | |
supply( | |
track: 'internal', | |
# Uncomment this if getting error "Only releases with status draft may be created on draft app." | |
# release_status: 'draft', | |
aab: "../build/app/outputs/bundle/release/app-release.aab", | |
json_key: google_service_account_json_path, | |
skip_upload_apk: true, # Upload the aab instead of apk | |
skip_upload_metadata: true, | |
skip_upload_changelogs: true, | |
skip_upload_images: true, | |
skip_upload_screenshots: true | |
) | |
end | |
end | |
# Release to Play Store using Firebase App Distribution (https://docs.fastlane.tools/actions/firebase_app_distribution/) | |
desc "Release to Play Store using Firebase App Distribution" | |
lane :release_play_store_using_firebase do |options| | |
begin | |
build( | |
type: 'apk', | |
no_codesign: options[:no_codesign], | |
config_only: options[:config_only], | |
build_number: options[:build_number], | |
version_number: options[:version_number] | |
) | |
firebase_app_distribution( | |
app: ENV["FIREBASE_APP_ID"], | |
android_artifact_path: "#{root_path}/build/app/outputs/flutter-apk/app-release.apk", | |
service_credentials_file: google_service_account_json_path, | |
# Use the following to enable debug mode | |
debug: true | |
) | |
end | |
end | |
end''', | |
'android/fastlane/Pluginfile': ''' | |
# Autogenerated by fastlane | |
# | |
# Ensure this file is checked in to source control! | |
gem 'fastlane-plugin-firebase_app_distribution' | |
gem 'fastlane-plugin-get_new_build_number' | |
''', | |
'android/Gemfile': ''' | |
source "https://rubygems.org" | |
gem "fastlane" | |
# takes version from flutter (pubspec.yaml) | |
gem "fastlane-plugin-flutter_version", git: "https://github.com/tianhaoz95/fastlane-plugin-flutter-version" | |
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') | |
eval_gemfile(plugins_path) if File.exist?(plugins_path) | |
''', | |
/// IOS | |
'ios/fastlane/.env': ''' | |
ASC_KEY_ID="" | |
ASC_ISSUER_ID="" | |
ASC_KEY_P8_BASE64="" | |
MATCH_PASSWORD="" | |
MATCH_GIT_BASIC_AUTHORIZATION="" | |
APP_BUNDLE_ID=$appBundleId | |
FIREBASE_APP_ID= | |
# Optional: Override the path to your Firebase service account JSON file (defaults to root of your project) | |
# GOOGLE_SERVICE_ACCOUNT_JSON_PATH= | |
''', | |
'ios/fastlane/Appfile': ''' | |
app_identifier(ENV["DEVELOPER_APP_IDENTIFIER"]) | |
apple_id(ENV["FASTLANE_APPLE_ID"]) | |
itc_team_id(ENV["APP_STORE_CONNECT_TEAM_ID"]) | |
team_id(ENV["DEVELOPER_PORTAL_TEAM_ID"]) | |
''', | |
'ios/fastlane/Fastfile': ''' | |
import "../../Fastfile" | |
default_platform(:ios) | |
platform :ios do | |
# Authenticate with Apple Store | |
private_lane :authenticate_apple_store do | |
app_store_connect_api_key( | |
key_id: ENV["ASC_KEY_ID"], | |
issuer_id: ENV["ASC_ISSUER_ID"], | |
key_content: ENV["ASC_KEY_P8_BASE64"], | |
is_key_content_base64: true, | |
in_house: false | |
) | |
end | |
# Build iOS app | |
lane :build_ipa do |options| | |
authenticate_apple_store | |
build_flutter_app( | |
type: "ipa", | |
no_codesign: options[:no_codesign] || false, | |
config_only: options[:config_only] || false, | |
build_number: options[:build_number] || get_build_number('appstore'), | |
version_number: options[:version_number] || get_version_from_pubspec(), | |
store: "appstore" | |
) | |
end | |
desc "Release a new build to Apple Store" | |
lane :release_app_store do |options| | |
verify_env(envs: [ | |
"ASC_KEY_ID", | |
"ASC_ISSUER_ID", | |
"ASC_KEY_P8_BASE64", | |
"APP_BUNDLE_ID", | |
"MATCH_PASSWORD", | |
"MATCH_GIT_BASIC_AUTHORIZATION", | |
]) | |
authenticate_apple_store | |
build_number = options.fetch(:build_number, get_build_number('appstore')) | |
version_number = options.fetch(:version_number, get_version_from_pubspec()) | |
# Verify version number is correct | |
if !is_ci && (!options[:version_number]) | |
continue = UI.confirm("Deploying version #{version_number} (from pubspec.yaml) to App Store. Continue?") | |
unless continue | |
UI.user_error!("Aborted") | |
end | |
end | |
# Sync certificates and profiles using match | |
UI.message("Syncing certificates and profiles") | |
if is_ci | |
UI.message("CI detected. Setting up CI environment") | |
setup_ci | |
end | |
sync_code_signing( | |
type: "appstore", | |
readonly: is_ci, | |
) | |
build_ipa( | |
build_number: build_number, | |
version_number: version_number | |
) | |
build_app( | |
skip_build_archive: true, | |
archive_path: "../build/ios/archive/Runner.xcarchive", | |
) | |
# If GoogleService-Info.plist exists and Pods/FirebaseCrashlytics exists | |
# Upload symbols to Firebase Crashlytics | |
if File.file?("../ios/Runner/GoogleService-Info.plist") && File.directory?("../ios/Pods/FirebaseCrashlytics") | |
upload_symbols_to_crashlytics( | |
gsp_path: "../ios/Runner/GoogleService-Info.plist" | |
) | |
end | |
upload_to_testflight( | |
skip_waiting_for_build_processing: true | |
) | |
end | |
# This is a work in progress, requiring ad-hoc export method | |
# desc "Release to Play Store using Firebase App Distribution" | |
# lane :release_to_firebase do |options| | |
# begin | |
# build_ipa | |
# firebase_app_distribution( | |
# app: ENV["FIREBASE_APP_ID"], | |
# service_credentials_file: google_service_account_json_path, | |
# ipa_path: "../build/ios/Runner.ipa" | |
# ) | |
# end | |
# end | |
end | |
''', | |
'ios/fastlane/Matchfile': ''' | |
git_url("https://github.com/your/app-certificates-and-profiles.git") | |
storage_mode("git") | |
type("appstore") # The default type, can be: appstore, adhoc, enterprise or development | |
app_identifier([ENV["APP_BUNDLE_ID"]]) | |
''', | |
'ios/fastlane/Pluginfile': ''' | |
# Autogenerated by fastlane | |
# | |
# Ensure this file is checked in to source control! | |
gem 'fastlane-plugin-firebase_app_distribution' | |
gem 'fastlane-plugin-get_new_build_number' | |
''', | |
'ios/Gemfile': ''' | |
source "https://rubygems.org" | |
gem "fastlane" | |
# takes version from flutter (pubspec.yaml) | |
gem "fastlane-plugin-flutter_version", git: "https://github.com/tianhaoz95/fastlane-plugin-flutter-version" | |
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') | |
eval_gemfile(plugins_path) if File.exist?(plugins_path) | |
''', | |
/// Generics | |
'Fastfile': ''' | |
opt_out_usage | |
# Have an easy way to get the root of the project | |
def root_path | |
Dir.pwd.sub(/.*\\Kfastlane/, '').sub(/.*\\Kandroid/, '').sub(/.*\\Kios/, '').sub(/.*\\K\\/\\//, '') | |
end | |
# Have an easy way to run flutter tasks on the root of the project | |
lane :sh_on_root do |options| | |
command = options[:command] | |
sh("cd #{root_path} && #{command}") | |
end | |
# Tasks to be reused on each platform flow | |
lane :fetch_dependencies do | |
sh_on_root(command: "flutter pub get --suppress-analytics") | |
end | |
# Tasks to be reused on each platform flow | |
lane :build_autogenerated_code do | |
sh_on_root(command: "flutter pub run build_runner build --delete-conflicting-outputs") | |
end | |
# Tasks to be reused on each platform flow | |
lane :lint do | |
sh_on_root(command: "flutter format --suppress-analytics --set-exit-if-changed -n lib/main.dart lib/src/ test/") | |
end | |
lane :build_flutter_app do |options| | |
pubspec_version_number = get_version_from_pubspec() | |
type = options[:type] | |
build_number = options[:build_number] || get_build_number(options[:store]) | |
version_number = options[:version_number] || pubspec_version_number | |
no_codesign = options[:no_codesign] || false | |
config_only = options[:config_only] || false | |
commit = last_git_commit | |
command = "flutter build #{type} --release --no-pub --suppress-analytics" | |
command += " --build-number=#{build_number}" if build_number.to_s != "" | |
command += " --build-name=#{version_number}" if version_number.to_s != "" | |
command += " --no-codesign" if no_codesign | |
command += " --config-only" if config_only | |
UI.message("Building #{type} - version: #{version_number} - build: #{build_number} - commit: #{commit[:abbreviated_commit_hash]}") | |
fetch_dependencies | |
# Check if build_runner exists in pubspec.yaml | |
# If it does, run the build_runner command | |
if File.exist?("#{root_path}/pubspec.yaml") | |
pubspec_content = File.read("#{root_path}/pubspec.yaml") | |
if pubspec_content.include?("build_runner:") | |
build_autogenerated_code | |
end | |
end | |
sh_on_root(command: command) | |
end | |
# Tasks to be reused on each platform flow | |
lane :test do |options| | |
sh_on_root(command: "flutter test --no-pub --coverage --suppress-analytics") | |
end | |
# Private lane to verify all environment variables are set | |
private_lane :verify_env do |options| | |
# array of ENVS to check | |
envs = options.fetch(:envs, []) | |
envs.each do |env| | |
if ENV[env].nil? || ENV[env].empty? | |
UI.user_error!("ENV \\"#{env}\\" is not set. Please set it in your environment variables (e.g. ios/fastlane/.env)") | |
end | |
end | |
end | |
# A helper method to get the path to the firebase service account json file | |
def google_service_account_json_path | |
root_path + '/' + (ENV["GOOGLE_SERVICE_ACCOUNT_JSON_PATH"] || 'google_service_account.json') | |
end | |
# Build number is a unique identifier for each build that is uploaded to the app store. | |
# This method will get the latest build number from the app store and increment it by 1. | |
# Ensure authenticate_apple_store is called before this method | |
def get_build_number(store) | |
return get_new_build_number( | |
bundle_identifier: store == "appstore" ? ENV["APP_BUNDLE_ID"] : nil, | |
package_name: store == "playstore" ? ENV["APP_PACKAGE_NAME"] : nil, | |
google_play_json_key_path: google_service_account_json_path | |
).to_s | |
end | |
def get_version_from_pubspec | |
require 'yaml' | |
# Define the correct path to pubspec.yaml relative to the Fastlane directory | |
pubspec_path = File.expand_path("#{root_path}/pubspec.yaml") | |
# Check if the file exists to avoid errors | |
unless File.exist?(pubspec_path) | |
UI.error("pubspec.yaml file not found at path: #{pubspec_path}") | |
return nil | |
end | |
# Parse the pubspec.yaml file | |
pubspec_content = File.read(pubspec_path) | |
# Use regex to find the version number line and extract both version number and build number | |
version_line = pubspec_content.match(/version:\\s*(\\d+\\.\\d+\\.\\d+)\\+(\\d+)/) | |
if version_line | |
version_number = version_line[1] | |
else | |
UI.error("Version number not found in pubspec.yaml") | |
return nil | |
end | |
return version_number | |
end | |
''', | |
'google_service_account.json': ''' | |
Follow this guide to create a service account JSON file: https://docs.fastlane.tools/actions/supply/#setup | |
''', | |
'android/key.properties': ''' | |
storePassword=<the password you used when generating the key> | |
keyPassword=<the password you used when generating the key> | |
keyAlias=release | |
storeFile=../key.jks | |
''', | |
'''.github/workflows/deploy-with-fastlane.yaml''': r''' | |
name: Publish iOS and Android release | |
on: | |
release: | |
types: [published] | |
env: | |
FLUTTER_CHANNEL: "stable" | |
RUBY_VERSION: "3.2.2" | |
jobs: | |
build_ios: | |
name: Build iOS | |
# You can upgrade to more powerful machines if you need to | |
# See https://docs.github.com/en/actions/using-github-hosted-runners/about-larger-runners/about-larger-runners#about-macos-larger-runners | |
runs-on: macos-latest | |
# Depending on how long your build takes, you might want to increase the timeou | |
timeout-minutes: 20 | |
steps: | |
- name: Checkout | |
uses: actions/checkout@v4 | |
- name: Set up Ruby | |
uses: ruby/setup-ruby@v1 | |
with: | |
ruby-version: ${{ env.RUBY_VERSION }} | |
bundler-cache: true | |
working-directory: 'ios' | |
- name: Run Flutter tasks | |
uses: subosito/flutter-action@v2.16.0 | |
with: | |
# Remember to specify flutter version in pubspec.yaml under environment | |
# https://github.com/subosito/flutter-action?tab=readme-ov-file#use-version-from-pubspecyaml | |
flutter-version-file: 'pubspec.yaml' | |
channel: ${{ env.FLUTTER_CHANNEL }} | |
cache: true | |
- uses: maierj/fastlane-action@v3.1.0 | |
with: | |
lane: 'release_app_store' | |
subdirectory: ios | |
options: '{ "version_number": "${{ github.ref_name }}" }' | |
env: | |
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }} | |
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }} | |
ASC_KEY_P8_BASE64: ${{ secrets.ASC_KEY_P8_BASE64 }} | |
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} | |
MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_BASIC_AUTHORIZATION }} | |
APP_BUNDLE_ID: ${{ secrets.APP_BUNDLE_ID }} | |
notify_ios: | |
name: Send Slack Notification about iOS build | |
needs: [build_ios] | |
runs-on: ubuntu-latest | |
timeout-minutes: 2 | |
steps: | |
- name: Send Slack Notification about iOS build | |
uses: rtCamp/action-slack-notify@v2 | |
if: ${{ !cancelled() && (success() || failure()) && env.SLACK_LOGS_WEBHOOK_PRESENT == 'true' }} | |
env: | |
SLACK_LOGS_WEBHOOK_PRESENT: ${{ secrets.SLACK_LOGS_WEBHOOK && 'true' || 'false' }} | |
SLACK_WEBHOOK: ${{ secrets.SLACK_LOGS_WEBHOOK }} | |
SLACK_CHANNEL: logs | |
SLACK_USERNAME: "${{ github.repository_owner }}" | |
SLACK_ICON: "https://github.com/${{ github.repository_owner }}.png?size=250" | |
SLACK_COLOR: "${{ contains(needs.*.result, 'success') && 'good' || 'danger' }}" | |
SLACK_TITLE: "${{ contains(needs.*.result, 'success') && 'Successfully released' || 'Error during release of' }} ${{ github.ref_name }} for iOS to TestFlight" | |
SLACK_FOOTER: "DevOps" | |
SLACK_MESSAGE: "${{ contains(needs.*.result, 'success') && 'Released:' || 'Release failed:' }} ${{github.event.head_commit.message}}" | |
build_android: | |
name: Build Android | |
runs-on: ubuntu-latest | |
timeout-minutes: 20 | |
steps: | |
- name: Checkout | |
uses: actions/checkout@v4 | |
- name: Set up Ruby | |
uses: ruby/setup-ruby@v1 | |
with: | |
ruby-version: ${{ env.RUBY_VERSION }} | |
bundler-cache: true | |
working-directory: 'android' | |
- name: Run Flutter tasks | |
uses: subosito/flutter-action@v2.16.0 | |
with: | |
flutter-version-file: 'pubspec.yaml' | |
channel: ${{ env.FLUTTER_CHANNEL }} | |
cache: true | |
- name: Create google_service_account.json | |
run: | | |
echo "${{ secrets.FIREBASE_SERVICE_ACCOUNT_BASE64 }}" | base64 --decode > google_service_account.json | |
- name: Create key.jks | |
run: | | |
echo "${{ secrets.ANDROID_KEYSTORE_FILE_BASE64 }}" | base64 --decode > android/key.jks | |
- name: Create key.properties | |
run: | | |
cat <<EOF > android/key.properties | |
storePassword=${{ secrets.ANDROID_KEY_STORE_PASSWORD }} | |
keyPassword=${{ secrets.ANDROID_KEY_STORE_PASSWORD }} | |
keyAlias=release | |
storeFile=../key.jks | |
EOF | |
env: | |
ANDROID_KEY_STORE_PASSWORD: ${{ secrets.ANDROID_KEY_STORE_PASSWORD }} | |
- uses: maierj/fastlane-action@v3.1.0 | |
with: | |
lane: 'release_play_store' | |
subdirectory: android | |
options: '{ "version_number": "${{ github.ref_name }}" }' | |
env: | |
APP_PACKAGE_NAME: ${{ secrets.APP_PACKAGE_NAME }} | |
notify_android: | |
name: Send Slack Notification about Android build | |
needs: [build_android] | |
runs-on: ubuntu-latest | |
timeout-minutes: 2 | |
steps: | |
- name: Send Slack Notification about Android build | |
uses: rtCamp/action-slack-notify@v2 | |
if: ${{ !cancelled() && (success() || failure()) && env.SLACK_LOGS_WEBHOOK_PRESENT == 'true' }} | |
env: | |
SLACK_LOGS_WEBHOOK_PRESENT: ${{ secrets.SLACK_LOGS_WEBHOOK && 'true' || 'false' }} | |
SLACK_WEBHOOK: ${{ secrets.SLACK_LOGS_WEBHOOK }} | |
SLACK_CHANNEL: logs | |
SLACK_USERNAME: "${{ github.repository_owner }}" | |
SLACK_ICON: "https://github.com/${{ github.repository_owner }}.png?size=250" | |
SLACK_COLOR: "${{ contains(needs.*.result, 'success') && 'good' || 'danger' }}" | |
SLACK_TITLE: "${{ contains(needs.*.result, 'success') && 'Successfully released' || 'Error during release of' }} ${{ github.ref_name }} for Android to Play Store" | |
SLACK_FOOTER: "DevOps" | |
SLACK_MESSAGE: "${{ contains(needs.*.result, 'success') && 'Released:' || 'Release failed:' }} ${{github.event.head_commit.message}}" | |
''', | |
}; | |
// verify if the files already exist | |
List<String> existingFiles = []; | |
files.forEach((path, content) { | |
if (File.fromUri(Uri.file(path)).existsSync()) { | |
existingFiles.add(path); | |
} | |
}); | |
bool override = false; | |
// ask if he wish to override the file | |
if (existingFiles.isNotEmpty) { | |
print('The following files already exist:'); | |
existingFiles.forEach((file) { | |
print(file); | |
}); | |
stdout.write('Do you wish to override the files? (y/n): '); | |
String? answer = stdin.readLineSync(); | |
override = answer?.toLowerCase() == 'y'; | |
} | |
files.forEach((path, content) { | |
// if it exists and the user doesn't want to override, skip | |
if (File.fromUri(Uri.file(path)).existsSync() && !override) { | |
return; | |
} | |
File file = File(path); | |
file.createSync(recursive: true); | |
file.writeAsStringSync(content); | |
print('File $path created successfully.'); | |
}); | |
// Check if .gitignore contains '*.jks*, key.properties, google_service_account.json' | |
// and if not, append them | |
File gitignore = File('.gitignore'); | |
if (gitignore.existsSync()) { | |
String content = gitignore.readAsStringSync(); | |
if (!content.contains('.env')) { | |
gitignore.writeAsStringSync('.env\n', mode: FileMode.append); | |
} | |
if (!content.contains('*.jks')) { | |
gitignore.writeAsStringSync('*.jks\n', mode: FileMode.append); | |
} | |
if (!content.contains('key.properties')) { | |
gitignore.writeAsStringSync('key.properties\n', mode: FileMode.append); | |
} | |
if (!content.contains('google_service_account.json')) { | |
gitignore.writeAsStringSync('google_service_account.json\n', | |
mode: FileMode.append); | |
} | |
if (!content.contains('**/fastlane/report.xml')) { | |
gitignore.writeAsStringSync('**/fastlane/report.xml\n', | |
mode: FileMode.append); | |
} | |
if (!content.contains('**/fastlane/Preview.html')) { | |
gitignore.writeAsStringSync('**/fastlane/Preview.html\n', | |
mode: FileMode.append); | |
} | |
if (!content.contains('**/fastlane/screenshots')) { | |
gitignore.writeAsStringSync('**/fastlane/screenshots\n', | |
mode: FileMode.append); | |
} | |
if (!content.contains('**/fastlane/test_output')) { | |
gitignore.writeAsStringSync('**/fastlane/test_output\n', | |
mode: FileMode.append); | |
} | |
} | |
// Modify `android/app/build.gradle` and ensure it contains the following, | |
// ```gradle | |
// def keystoreProperties = new Properties() | |
// def keystorePropertiesFile = rootProject.file('key.properties') | |
// if (keystorePropertiesFile.exists()) { | |
// keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) | |
// } | |
// ``` | |
File androidBuildGradle = File('android/app/build.gradle'); | |
if (androidBuildGradle.existsSync()) { | |
// Ask if user wants to automatically add the required configurations | |
print( | |
'Do you want to automatically add the required configurations to android/app/build.gradle? (y/n): '); | |
String? answer = stdin.readLineSync(); | |
if (answer?.toLowerCase() == 'y') { | |
// Read the content of the file | |
String content = androidBuildGradle.readAsStringSync(); | |
if (!content.contains('def keystoreProperties = new Properties()')) { | |
print('Adding keystoreProperties to android/app/build.gradle'); | |
// add it above the `android {` block | |
content = content.replaceFirst('android {', ''' | |
def keystoreProperties = new Properties() | |
def keystorePropertiesFile = rootProject.file('key.properties') | |
if (keystorePropertiesFile.exists()) { | |
keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) | |
} | |
android {'''); | |
} | |
// If it does not contain signingConfigs { | |
// then add it above the buildTypes block | |
if (!content.contains('signingConfigs {')) { | |
print('Adding signingConfigs to android/app/build.gradle'); | |
content = content.replaceFirst('buildTypes {', ''' | |
signingConfigs { | |
release { | |
keyAlias keystoreProperties['keyAlias'] | |
keyPassword keystoreProperties['keyPassword'] | |
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null | |
storePassword keystoreProperties['storePassword'] | |
} | |
} | |
buildTypes {'''); | |
} | |
// Set signingConfig signingConfigs.release | |
print('Ensuring signingConfig is set to signingConfigs.release'); | |
content = content.replaceFirst( | |
RegExp(r'buildTypes \{[\s\S]*?release \{[\s\S]*?\}[\s\S]*?\}'), ''' | |
buildTypes { | |
release { | |
signingConfig signingConfigs.release | |
} | |
}'''); | |
// Write the content back to the file | |
androidBuildGradle.writeAsStringSync(content); | |
} | |
} | |
print('Fastlane setup completed successfully.'); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment