diff --git a/apps/amiapp_flutter/Gemfile b/apps/amiapp_flutter/Gemfile index 5a29778..017f017 100644 --- a/apps/amiapp_flutter/Gemfile +++ b/apps/amiapp_flutter/Gemfile @@ -1,11 +1,9 @@ -# Autogenerated by fastlane -# -# Ensure this file is checked in to source control! - source "https://rubygems.org" +git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } + gem 'fastlane' -gem 'cocoapods', '1.11.3' +gem 'cocoapods' plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') eval_gemfile(plugins_path) if File.exist?(plugins_path) diff --git a/apps/amiapp_flutter/Gemfile.lock b/apps/amiapp_flutter/Gemfile.lock index f12fcdc..f1a6ef9 100644 --- a/apps/amiapp_flutter/Gemfile.lock +++ b/apps/amiapp_flutter/Gemfile.lock @@ -1,48 +1,56 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.6) + CFPropertyList (3.0.7) + base64 + nkf rexml - activesupport (6.1.7.2) + activesupport (7.1.3.4) + base64 + bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) + connection_pool (>= 2.2.5) + drb i18n (>= 1.6, < 2) minitest (>= 5.1) + mutex_m tzinfo (~> 2.0) - zeitwerk (~> 2.3) - addressable (2.8.1) - public_suffix (>= 2.0.2, < 6.0) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) algoliasearch (1.27.5) httpclient (~> 2.8, >= 2.8.3) json (>= 1.5.1) - artifactory (3.0.15) + artifactory (3.0.17) atomos (0.1.3) - aws-eventstream (1.2.0) - aws-partitions (1.711.0) - aws-sdk-core (3.170.0) - aws-eventstream (~> 1, >= 1.0.2) + aws-eventstream (1.3.0) + aws-partitions (1.962.0) + aws-sdk-core (3.201.4) + aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) - aws-sigv4 (~> 1.5) + aws-sigv4 (~> 1.8) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.62.0) - aws-sdk-core (~> 3, >= 3.165.0) - aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.119.1) - aws-sdk-core (~> 3, >= 3.165.0) + aws-sdk-kms (1.88.0) + aws-sdk-core (~> 3, >= 3.201.0) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.157.0) + aws-sdk-core (~> 3, >= 3.201.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.4) - aws-sigv4 (1.5.2) + aws-sigv4 (~> 1.5) + aws-sigv4 (1.9.1) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) + base64 (0.2.0) + bigdecimal (3.1.8) claide (1.1.0) - cocoapods (1.11.3) + cocoapods (1.15.2) addressable (~> 2.8) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.11.3) + cocoapods-core (= 1.15.2) cocoapods-deintegrate (>= 1.0.3, < 2.0) - cocoapods-downloader (>= 1.4.0, < 2.0) + cocoapods-downloader (>= 2.1, < 3.0) cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-search (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.4.0, < 2.0) + cocoapods-trunk (>= 1.6.0, < 2.0) cocoapods-try (>= 1.1.0, < 2.0) colored2 (~> 3.1) escape (~> 0.0.4) @@ -50,10 +58,10 @@ GEM gh_inspector (~> 1.0) molinillo (~> 0.8.0) nap (~> 1.0) - ruby-macho (>= 1.0, < 3.0) - xcodeproj (>= 1.21.0, < 2.0) - cocoapods-core (1.11.3) - activesupport (>= 5.0, < 7) + ruby-macho (>= 2.3.0, < 3.0) + xcodeproj (>= 1.23.0, < 2.0) + cocoapods-core (1.15.2) + activesupport (>= 5.0, < 8) addressable (~> 2.8) algoliasearch (~> 1.0) concurrent-ruby (~> 1.1) @@ -63,7 +71,7 @@ GEM public_suffix (~> 4.0) typhoeus (~> 1.0) cocoapods-deintegrate (1.0.5) - cocoapods-downloader (1.6.3) + cocoapods-downloader (2.1) cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.1) @@ -75,18 +83,19 @@ GEM colored2 (3.1.2) commander (4.6.0) highline (~> 2.0.0) - concurrent-ruby (1.2.2) + concurrent-ruby (1.3.3) + connection_pool (2.4.1) declarative (0.0.20) - digest-crc (0.6.4) + digest-crc (0.6.5) rake (>= 12.0.0, < 14.0.0) - domain_name (0.5.20190701) - unf (>= 0.0.5, < 1.0.0) + domain_name (0.6.20240107) dotenv (2.8.1) + drb (2.2.1) emoji_regex (3.2.3) escape (0.0.4) ethon (0.16.0) ffi (>= 1.15.0) - excon (0.99.0) + excon (0.111.0) faraday (1.10.3) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) @@ -108,22 +117,22 @@ GEM faraday-httpclient (1.0.1) faraday-multipart (1.0.4) multipart-post (~> 2) - faraday-net_http (1.0.1) + faraday-net_http (1.0.2) faraday-net_http_persistent (1.2.0) faraday-patron (1.0.0) faraday-rack (1.0.0) faraday-retry (1.0.3) faraday_middleware (1.2.0) faraday (~> 1.0) - fastimage (2.2.6) - fastlane (2.211.0) + fastimage (2.3.1) + fastlane (2.222.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) aws-sdk-s3 (~> 1.0) babosa (>= 1.0.3, < 2.0.0) bundler (>= 1.12.0, < 3.0.0) - colored + colored (~> 1.2) commander (~> 4.6) dotenv (>= 2.1.1, < 3.0.0) emoji_regex (>= 0.1, < 4.0) @@ -135,35 +144,41 @@ GEM gh_inspector (>= 1.1.2, < 2.0.0) google-apis-androidpublisher_v3 (~> 0.3) google-apis-playcustomapp_v1 (~> 0.1) + google-cloud-env (>= 1.6.0, < 2.0.0) google-cloud-storage (~> 1.31) highline (~> 2.0) + http-cookie (~> 1.0.5) json (< 3.0.0) jwt (>= 2.1.0, < 3) mini_magick (>= 4.9.4, < 5.0.0) - multipart-post (~> 2.0.0) + multipart-post (>= 2.0.0, < 3.0.0) naturally (~> 2.2) - optparse (~> 0.1.1) + optparse (>= 0.1.1, < 1.0.0) plist (>= 3.1.0, < 4.0.0) rubyzip (>= 2.0.0, < 3.0.0) - security (= 0.1.3) + security (= 0.1.5) simctl (~> 1.6.3) terminal-notifier (>= 2.0.0, < 3.0.0) - terminal-table (>= 1.4.5, < 2.0.0) + terminal-table (~> 3) tty-screen (>= 0.6.3, < 1.0.0) tty-spinner (>= 0.8.0, < 1.0.0) word_wrap (~> 1.0.0) xcodeproj (>= 1.13.0, < 2.0.0) xcpretty (~> 0.3.0) - xcpretty-travis-formatter (>= 0.0.3) - fastlane-plugin-firebase_app_distribution (0.5.0) + xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) + fastlane-plugin-find_firebase_app_id (0.1.2) + fastlane-plugin-firebase_app_distribution (0.9.1) + google-apis-firebaseappdistribution_v1 (~> 0.3.0) + google-apis-firebaseappdistribution_v1alpha (~> 0.2.0) fastlane-plugin-versioning_android (0.1.1) - ffi (1.15.5) + fastlane-plugin-versioning_ios (0.1.0) + ffi (1.17.0-arm64-darwin) fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.34.0) - google-apis-core (>= 0.9.1, < 2.a) - google-apis-core (0.11.0) + google-apis-androidpublisher_v3 (0.54.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-core (0.11.3) addressable (~> 2.5, >= 2.5.1) googleauth (>= 0.16.2, < 2.a) httpclient (>= 2.8.1, < 3.a) @@ -171,71 +186,76 @@ GEM representable (~> 3.0) retriable (>= 2.0, < 4.a) rexml - webrick - google-apis-iamcredentials_v1 (0.16.0) - google-apis-core (>= 0.9.1, < 2.a) - google-apis-playcustomapp_v1 (0.12.0) - google-apis-core (>= 0.9.1, < 2.a) - google-apis-storage_v1 (0.19.0) - google-apis-core (>= 0.9.0, < 2.a) - google-cloud-core (1.6.0) - google-cloud-env (~> 1.0) + google-apis-firebaseappdistribution_v1 (0.3.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-firebaseappdistribution_v1alpha (0.2.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-iamcredentials_v1 (0.17.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-playcustomapp_v1 (0.13.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-storage_v1 (0.31.0) + google-apis-core (>= 0.11.0, < 2.a) + google-cloud-core (1.7.1) + google-cloud-env (>= 1.0, < 3.a) google-cloud-errors (~> 1.0) google-cloud-env (1.6.0) faraday (>= 0.17.3, < 3.0) - google-cloud-errors (1.3.0) - google-cloud-storage (1.44.0) + google-cloud-errors (1.4.0) + google-cloud-storage (1.47.0) addressable (~> 2.8) digest-crc (~> 0.4) google-apis-iamcredentials_v1 (~> 0.1) - google-apis-storage_v1 (~> 0.19.0) + google-apis-storage_v1 (~> 0.31.0) google-cloud-core (~> 1.6) googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) - googleauth (1.3.0) + googleauth (1.8.1) faraday (>= 0.17.3, < 3.a) jwt (>= 1.4, < 3.0) - memoist (~> 0.16) multi_json (~> 1.11) os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) highline (2.0.3) - http-cookie (1.0.5) + http-cookie (1.0.6) domain_name (~> 0.5) httpclient (2.8.3) - i18n (1.12.0) + i18n (1.14.5) concurrent-ruby (~> 1.0) jmespath (1.6.2) - json (2.6.3) - jwt (2.7.0) - memoist (0.16.2) - mini_magick (4.12.0) - mini_mime (1.1.2) - minitest (5.17.0) + json (2.7.2) + jwt (2.8.2) + base64 + mini_magick (4.13.2) + mini_mime (1.1.5) + minitest (5.24.1) molinillo (0.8.0) multi_json (1.15.0) - multipart-post (2.0.0) + multipart-post (2.4.1) + mutex_m (0.2.0) nanaimo (0.3.0) nap (1.1.0) naturally (2.2.1) netrc (0.11.0) - optparse (0.1.1) + nkf (0.2.0) + optparse (0.5.0) os (1.1.4) - plist (3.6.0) + plist (3.7.1) public_suffix (4.0.7) - rake (13.0.6) + rake (13.2.1) representable (3.2.0) declarative (< 0.1.0) trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) retriable (3.1.2) - rexml (3.2.5) + rexml (3.3.4) + strscan rouge (2.0.7) ruby-macho (2.5.1) ruby2_keywords (0.0.5) rubyzip (2.3.2) - security (0.1.3) - signet (0.17.0) + security (0.1.5) + signet (0.19.0) addressable (~> 2.8) faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) @@ -243,48 +263,44 @@ GEM simctl (1.6.10) CFPropertyList naturally + strscan (3.1.0) terminal-notifier (2.0.0) - terminal-table (1.8.0) - unicode-display_width (~> 1.1, >= 1.1.1) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) trailblazer-option (0.1.2) tty-cursor (0.7.1) - tty-screen (0.8.1) + tty-screen (0.8.2) tty-spinner (0.9.3) tty-cursor (~> 0.7) - typhoeus (1.4.0) + typhoeus (1.4.1) ethon (>= 0.9.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) uber (0.1.0) - unf (0.1.4) - unf_ext - unf_ext (0.0.8.2) - unicode-display_width (1.8.0) - webrick (1.8.1) + unicode-display_width (2.5.0) word_wrap (1.0.0) - xcodeproj (1.22.0) + xcodeproj (1.25.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) nanaimo (~> 0.3.0) - rexml (~> 3.2.4) + rexml (>= 3.3.2, < 4.0) xcpretty (0.3.0) rouge (~> 2.0.7) xcpretty-travis-formatter (1.0.1) xcpretty (~> 0.2, >= 0.0.7) - zeitwerk (2.6.7) PLATFORMS - universal-darwin-22 - x86_64-darwin-19 - x86_64-linux + arm64-darwin-21 DEPENDENCIES - cocoapods (= 1.11.3) + cocoapods fastlane + fastlane-plugin-find_firebase_app_id fastlane-plugin-firebase_app_distribution fastlane-plugin-versioning_android + fastlane-plugin-versioning_ios BUNDLED WITH - 2.4.7 + 2.4.22 diff --git a/apps/amiapp_flutter/fastlane/Appfile b/apps/amiapp_flutter/fastlane/Appfile index bf97ff2..f8d8f5f 100644 --- a/apps/amiapp_flutter/fastlane/Appfile +++ b/apps/amiapp_flutter/fastlane/Appfile @@ -1,8 +1,8 @@ team_id "2YC97BQN3N" app_identifier([ - "io.customer.amiapp.flutter", - "io.customer.amiapp.flutter.NotificationServiceExtension", + "io.customer.amiapp.flutter", + "io.customer.amiapp.flutter.NotificationServiceExtension", ]) package_name("io.customer.amiapp_flutter") \ No newline at end of file diff --git a/apps/amiapp_flutter/fastlane/Fastfile b/apps/amiapp_flutter/fastlane/Fastfile index af9fa7c..f12bd39 100644 --- a/apps/amiapp_flutter/fastlane/Fastfile +++ b/apps/amiapp_flutter/fastlane/Fastfile @@ -1,341 +1,2 @@ -# Import reusable functions that can used by all iOS apps in the team -# https://docs.fastlane.tools/actions/import_from_git/ -import_from_git( - url: "https://github.com/customerio/apple-code-signing.git", - branch: "main", - path: "fastlane/Fastfile" -) - -# This file contains the fastlane.tools configuration -# You can find the documentation at https://docs.fastlane.tools -# -# For a list of all available actions, check out -# -# https://docs.fastlane.tools/actions -# -# For a list of all available plugins, check out -# -# https://docs.fastlane.tools/plugins/available-plugins -# - -# Uncomment the line if you want fastlane to automatically update itself -# update_fastlane - -require 'json' - -# Fastlane reacts differently by when it gets executed in GitHub Actions. -class GitHub - # `event` payload for pushes: https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#push - # `event` payload for pull requests: https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#pull_request - - def initialize(github_context) - @github_context = github_context - end - - def is_push - @github_context["event"]["commits"] != nil - end - - def is_pull_request - @github_context["event"]["pull_request"] != nil - end - - def commit_hash - if is_push - return @github_context["event"]["head_commit"]["id"][0..8] - else - return @github_context["sha"][0..8] - end - end - - def pr_title - @github_context["event"]["pull_request"]["title"] - end - - def pr_number - @github_context["event"]["pull_request"]["number"] - end - - def source_branch - @github_context["head_ref"] - end - - def destination_branch - @github_context["base_ref"] - end - - def author - if is_push - return @github_context["event"]["head_commit"]["committer"]["username"] - else - return @github_context["event"]["pull_request"]["user"]["login"] - end - end - - def commit_message - @github_context["event"]["head_commit"]["message"] - end - - def branch_name - @github_context["event"]["ref"].split("/").last # getting the last part of `refs/heads/test-dump` is the branch name - end -end - -platform :ios do - - is_enterprise_app = false - info_plist_file_path = "ios/Runner/Info.plist" - google_service_plist_file_path = "ios/Runner/GoogleService-Info.plist" - - before_all do |lane, options| - if ENV['CI'] - setup_ci - - # authenticate with apple account so all lanes are able to authenticate correctly - # https://docs.fastlane.tools/app-store-connect-api/ - app_store_connect_api_key( - key_id: ENV["APP_STORE_CONNECT_API_KEY_ID"], - issuer_id: ENV["APP_STORE_CONNECT_API_ISSUER_ID"], - key_content: ENV["APP_STORE_CONNECT_API_KEY_CONTENT_B64"], - is_key_content_base64: true, - in_house: is_enterprise_app - ) - end - end - - # example for main builds: `bundle exec fastlane android deploy_app version:1.0.0"` - # example for develoment builds (pull request, push): `bundle exec fastlane deploy_app` - lane :deploy_app do |values| - # download provisioning profile/certs to be able to build and sign app. - # automatically creates new profile if new test devices have been added to account - sync_code_signing( - type: "adhoc", - force_for_new_devices: true, - readonly: false - ) - - name_of_app = get_info_plist_value(path: info_plist_file_path, key: "CFBundleDisplayName") # get from xcode project - new_app_version = values[:version] # pass in new app version as parameter because we can get it from new semantic version - is_main_build = new_app_version != nil && new_app_version != "" - release_notes = ["app: #{name_of_app}"] - groups = ['all-builds'] # default - always send to these groups. - - github = GitHub.new(JSON.parse(ENV["GITHUB_CONTEXT"])) # context is a JSON string - new_build_number = github.commit_hash - - UI.message("TESTING COMMIT HASH #{github.commit_hash}") - - if is_main_build - UI.message("Deploying a main build of app. Version: #{new_app_version}") - - groups.append("stable-builds") - - release_notes.append( - "build type: main", - "version: #{new_app_version}" - ) - else - UI.message("Deploying a development build of app.") - - # At first, we made a fancy app version string with branch name and other things but the version string is limited on the valid characters in it. - # therefore, we are using the commit hash as that's enough to identify the build. - - if github.is_pull_request - UI.message("I see this is a pull request. Build metadata will include helpful PR info.") - - new_app_version = "pr.#{github.pr_number}" - - release_notes.append( - "build type: pull request", - "title: #{github.pr_title} (#{github.pr_number})", - "author: #{github.author}", - "source branch: #{github.source_branch}", - "destination branch: #{github.destination_branch}" - ) - elsif github.is_push - UI.message("I see this is a git commit push. Build metadata will include helpful commit info.") - - new_app_version = "push.#{github.commit_hash}" - - release_notes.append( - "build type: push", - "message: #{github.commit_message}", - "author: #{github.author}", - "branch: #{github.branch_name}" - ) - else - UI.message("This is not a pull request or push. Going to ignore the event.") - return - end - end - - release_notes.append( - "commit hash: #{github.commit_hash}", - "build number: #{new_build_number}" - ) - - release_notes = release_notes.join("\n") - groups = groups.join(", ") - - UI.important("Release notes:\n#{release_notes}") - UI.important("New app version: #{new_app_version}") - UI.important("Firebase App testing groups: #{groups}") - - set_info_plist_value(path: info_plist_file_path, key: "CFBundleVersion", value: new_build_number) # make sure unique build number to avoid clashing - set_info_plist_value(path: info_plist_file_path, key: "CFBundleShortVersionString", value: new_app_version) - - if ENV["XCODE_VERSION"] != nil - xcode_select "/Applications/Xcode_#{ENV["XCODE_VERSION"]}.app" - end - - build_ios_app - - upload_symbols_to_crashlytics( - gsp_path: google_service_plist_file_path - ) - - firebase_app_distribution( - app: ENV["FIREBASE_APP_ID"], - service_credentials_file: './ami_app_ci_server-google_cloud_service_account.json', - groups: groups, - release_notes: release_notes - ) - end - - lane :dev_setup do - match( - type: "development", - readonly: true, - google_cloud_keys_file: ENV["FASTLANE_GC_KEYS_FILE"] - ) - match( - type: "adhoc", - readonly: true, - google_cloud_keys_file: ENV["FASTLANE_GC_KEYS_FILE"] - ) - end - - lane :delete_code_signing_development do - match_nuke( - type: "development", - google_cloud_keys_file: ENV["FASTLANE_GC_KEYS_FILE"], - api_key_path: ENV["FASTLANE_APP_STORE_CONNECT_FILE"] || './app_store_connect_creds.json', - readonly: false - ) - end - - lane :delete_code_signing_release do - match_nuke( - type: "adhoc", - google_cloud_keys_file: ENV["FASTLANE_GC_KEYS_FILE"], - api_key_path: ENV["FASTLANE_APP_STORE_CONNECT_FILE"] || './app_store_connect_creds.json', - readonly: false - ) - end - - lane :create_code_signing_development do - match( - type: "development", - google_cloud_keys_file: ENV["FASTLANE_GC_KEYS_FILE"], - api_key_path: ENV["FASTLANE_APP_STORE_CONNECT_FILE"] || './app_store_connect_creds.json', - readonly: false - ) - end - - lane :create_code_signing_release do - match( - type: "adhoc", - google_cloud_keys_file: ENV["FASTLANE_GC_KEYS_FILE"], - api_key_path: ENV["FASTLANE_APP_STORE_CONNECT_FILE"] || './app_store_connect_creds.json', - readonly: false - ) - end -end - -platform :android do - - # example for main builds: `bundle exec fastlane android deploy_app version:1.0.0"` - # example for develoment builds (pull request, push): `bundle exec fastlane deploy_app` - lane :deploy_app do |values| - - gradle_file_path = "android/app/build.gradle" - project_dir = "android" - - name_of_app = "Ami App" - new_app_version = values[:version] # pass in new app version as parameter because we can get it from new semantic version - is_main_build = new_app_version != nil && new_app_version != "" - release_notes = ["app: #{name_of_app}"] - groups = ['all-builds'] # default - always send to these groups. - - if is_main_build - UI.message("Deploying a main build of app. Version: #{new_app_version}") - - groups.append("stable-builds") - - release_notes.append( - "build type: main", - "version: #{new_app_version}" - ) - else - UI.message("Deploying a development build of app.") - - github = GitHub.new(JSON.parse(ENV["GITHUB_CONTEXT"])) # context is a JSON string - # Not aligned with iOS as this has to integer and unique for Android - new_build_number = Time.now.to_i - - if github.is_pull_request - UI.message("I see this is a pull request. Build metadata will include helpful PR info.") - - new_app_version = "pr.#{github.pr_number}" - - release_notes.append( - "build type: pull request", - "title: #{github.pr_title} (#{github.pr_number})", - "author: #{github.author}", - "source branch: #{github.source_branch}", - "destination branch: #{github.destination_branch}" - ) - elsif github.is_push - UI.message("I see this is a git commit push. Build metadata will include helpful commit info.") - - new_app_version = "push.#{github.commit_hash}" - - release_notes.append( - "build type: push", - "message: #{github.commit_message}", - "author: #{github.author}", - "branch: #{github.branch_name}" - ) - end - end - - release_notes.append( - "commit hash: #{github.commit_hash}", - "build number: #{new_build_number}" - ) - - release_notes = release_notes.join("\n") - groups = groups.join(", ") - - UI.important("Release notes:\n#{release_notes}") - UI.important("New app version: #{new_app_version}") - UI.important("Firebase App testing groups: #{groups}") - - android_set_version_name(version_name: new_app_version, gradle_file: gradle_file_path) - android_set_version_code(version_code: new_build_number, gradle_file: gradle_file_path) - - build_android_app( - tasks: 'assembleRelease', - project_dir: project_dir - ) - - firebase_app_distribution( - app: ENV["FIREBASE_ANDROID_APP_ID"], - service_credentials_file: './ami_app_ci_server-google_cloud_service_account.json', - groups: groups, - android_artifact_type: "APK", - android_artifact_path: "build/app/outputs/apk/release/app-release.apk", - release_notes: release_notes - ) - end -end +# Import the Fastfile from common fastlane directory +import "../../fastlane/Fastfile" diff --git a/apps/amiapp_flutter/fastlane/Gymfile b/apps/amiapp_flutter/fastlane/Gymfile index 0faa2c9..d9bbc96 100644 --- a/apps/amiapp_flutter/fastlane/Gymfile +++ b/apps/amiapp_flutter/fastlane/Gymfile @@ -1,4 +1,8 @@ -configuration 'Release' -export_method 'ad-hoc' -scheme "Runner" # scheme in XCode workspace of Flutter generated iOS app -workspace "ios/Runner.xcworkspace" \ No newline at end of file +# For more information about this configuration run `fastlane gym --help` or check +# out the documentation at https://docs.fastlane.tools/actions/gym/#gymfile + +configuration("Release") +export_method("ad-hoc") +# scheme in XCode workspace of Flutter generated iOS app +scheme("Runner") +workspace("ios/Runner.xcworkspace") diff --git a/apps/amiapp_flutter/fastlane/Matchfile b/apps/amiapp_flutter/fastlane/Matchfile index 9cf545d..d6d38f5 100644 --- a/apps/amiapp_flutter/fastlane/Matchfile +++ b/apps/amiapp_flutter/fastlane/Matchfile @@ -3,4 +3,4 @@ type "development" readonly true # IMPORTANT: use the same gcloud bucket name for *all* iOS apps we use in the company. # fastlane is smart enough to share certificates while creating separate provisioning profiles for each app. -google_cloud_bucket_name "remote-habits-ios-signing" \ No newline at end of file +google_cloud_bucket_name "remote-habits-ios-signing" diff --git a/apps/amiapp_flutter/fastlane/Pluginfile b/apps/amiapp_flutter/fastlane/Pluginfile index a97e994..3b6a915 100644 --- a/apps/amiapp_flutter/fastlane/Pluginfile +++ b/apps/amiapp_flutter/fastlane/Pluginfile @@ -1,6 +1,6 @@ -# Autogenerated by fastlane -# -# Ensure this file is checked in to source control! +# Gemfile containing all the necessary plugins used by fastlane in the project gem 'fastlane-plugin-firebase_app_distribution' gem 'fastlane-plugin-versioning_android' +gem 'fastlane-plugin-versioning_ios' +gem 'fastlane-plugin-find_firebase_app_id' diff --git a/apps/fastlane/Fastfile b/apps/fastlane/Fastfile new file mode 100644 index 0000000..6e8993b --- /dev/null +++ b/apps/fastlane/Fastfile @@ -0,0 +1,48 @@ +# import Fastfile from the apple-code-signing repo to reuse signing lanes +import_from_git( + url: "https://github.com/customerio/apple-code-signing.git", + branch: "main", + path: "fastlane/Fastfile" +) +# Use expand_path to import code so it automatically resolves the path based on the current file +import File.expand_path('helpers/build_helper.rb', __dir__) +import File.expand_path('helpers/version_helper.rb', __dir__) + +# Helper method to update pubspec version +def update_pubspec_version(project_path, new_version) + pubspec_path = File.join(project_path, 'pubspec.yaml') + + unless File.exist?(pubspec_path) + UI.user_error!("pubspec.yaml not found at #{pubspec_path}") + end + + pubspec_content = File.read(pubspec_path) + new_pubspec_content = pubspec_content.gsub(/^version: .*/, "version: #{new_version}") + + File.write(pubspec_path, new_pubspec_content) + UI.message("Updated version to #{new_version} in #{pubspec_path}") +end + +# Lane to update Flutter app version +lane :update_flutter_sdk_version do |options| + project_path = options[:project_path] || File.join(Dir.pwd, '../../..') + version_name = options[:version_name] || ENV['SDK_VERSION_NAME'] + + UI.message("Updating sdk version to #{version_name} in #{project_path}") + update_pubspec_version(project_path, version_name) +end + +# Lane to update Flutter app version +lane :update_flutter_app_version do |options| + project_path = options[:project_path] || File.join(Dir.pwd, '..') + version_name = options[:version_name] || ENV['APP_VERSION_NAME'] + version_code = options[:version_code] || ENV['APP_VERSION_CODE'] + + # To match version format with flutter apps e.g. 1.0.0+1 + new_version = "#{version_name}+#{version_code}" + + UI.message("Updating app versions to #{new_version} in #{project_path}") + update_pubspec_version(project_path, new_version) + update_android_version(project_path, version_name, version_code) + update_ios_version(project_path, version_name, version_code) +end diff --git a/apps/fastlane/helpers/build_helper.rb b/apps/fastlane/helpers/build_helper.rb new file mode 100644 index 0000000..8c4d4d8 --- /dev/null +++ b/apps/fastlane/helpers/build_helper.rb @@ -0,0 +1,133 @@ +require 'json' +require 'xcodeproj' +require_relative 'github_helper.rb' + +platform :android do + lane :build do |values| + build_notes = get_build_notes() + test_groups = get_build_test_groups() + app_package_name = CredentialsManager::AppfileConfig.try_fetch_value(:package_name) # get package_name from Appfile + + # Firebase app id is required. Fetch it from google-services.json file using app_package_name + UI.important("Firebase app id: #{find_firebase_app_id(app_identifier: app_package_name)} for app package name: #{app_package_name}") + + # Build release APK using Flutter CLI + sh("flutter build apk --release") + + # Path to the APK generated by Flutter (relative to the Flutter project root) + distribution_apk_path = "build/app/outputs/flutter-apk/app-release.apk" + + # Adjusted path for checking the APK from the Fastlane directory + relative_apk_path = "../#{distribution_apk_path}" + + UI.message("Current working directory: #{Dir.pwd}, Looking for APK at: #{relative_apk_path}") + # Check if the APK exists + unless File.exist?(relative_apk_path) + UI.user_error!("Couldn't find the APK at #{relative_apk_path}") + end + + # function 'setup_google_bucket_access' is a re-usable function inside of apple-code-signing Fastfile that we imported. + # This allows you to create a temporary file from a GitHub secret for added convenience. + # When uploading the build to Firebase App Distribution, the CI server needs to authenticate with Firebase. This is done with a + # Google Cloud Service Account json creds file. The base64 encoded value of this service account file is stored as this secret. + service_credentials_file_path = setup_google_bucket_access( + environment_variable_key: "FIREBASE_APP_DISTRIBUTION_SERVICE_ACCOUNT_CREDS_B64" + ) + + firebase_app_distribution( + app: find_firebase_app_id(app_identifier: app_package_name), # Firebase app id is required. Get it from google-services.json file + apk_path: distribution_apk_path, + service_credentials_file: service_credentials_file_path, + groups: test_groups, + release_notes: build_notes + ) + end +end + +platform :ios do + lane :build do |arguments| + if ENV["CI"] + download_ci_code_signing_files + else + download_development_code_signing + end + + # prevents builds from being flaky. As app sizes get bigger, it takes fastlane longer to initialize the build process. Increase this value to compensate for that. + ENV["FASTLANE_XCODEBUILD_SETTINGS_RETRIES"] = "10" + + # Build IPA using gym (Fastlane's built-in tool for building iOS apps) + gym(scheme: "Runner") + + are_environment_variables_set_for_build_uploading = ENV["FIREBASE_APP_DISTRIBUTION_SERVICE_ACCOUNT_CREDS_B64"] + + environment_variables_required_for_build_uploading = ["FIREBASE_APP_DISTRIBUTION_SERVICE_ACCOUNT_CREDS_B64"] # array of keys + are_environment_variables_set_for_build_uploading = environment_variables_required_for_build_uploading.all? { |key| ENV[key] != nil && !ENV[key].empty? } # check if all environment variables are set and not empty + if !are_environment_variables_set_for_build_uploading + UI.important("Environment variables required for uploading QA builds are not set. Therefore, not uploading build to Firebase App Distribution.") + else + # function 'setup_google_bucket_access' is a re-usable function inside of apple-code-signing Fastfile that we imported. + # This allows you to create a temporary file from a GitHub secret for added convenience. + # When uploading the build to Firebase App Distribution, the CI server needs to authenticate with Firebase. This is done with a + # Google Cloud Service Account json creds file. The base64 encoded value of this service account file is stored as this secret. + service_credentials_file_path = setup_google_bucket_access( + environment_variable_key: "FIREBASE_APP_DISTRIBUTION_SERVICE_ACCOUNT_CREDS_B64" + ) + + firebase_app_distribution( + service_credentials_file: service_credentials_file_path, + groups: get_build_test_groups(), + release_notes: get_build_notes() + ) + end + end +end + +# Firebase App Distribution allows you to attach notes to each build uploaded. These notes are searchable so we use the notes +# field to allow QA to quickly find builds they should install. We populate the notes with metadata from GitHub. +# GitHub Actions is the CI product we use to create builds of our apps. GitHub Actions provides metadata about the build +# via a JSON file. We parse this JSON file and pull out fields from it to populate the notes. +lane :get_build_notes do + build_notes = [] + github = GitHub.new() + + if github.is_pull_request + build_notes.append( + "build type: pull request", + "pr title: #{github.pr_title}", + "pr number: #{github.pr_number}", + "pr author: #{github.pr_author}", + "commit hash: #{github.pr_commit_hash}", + "source branch: #{github.pr_source_branch}", + "destination branch: #{github.pr_destination_branch}" + ) + elsif github.is_commit_pushed + build_notes.append( + "build type: commit pushed to branch", + "branch: #{github.push_branch}", + "commit hash: #{github.push_commit_hash}" + ) + end + + build_notes = build_notes.join("\n") + + UI.important("Build notes for this build:\n#{build_notes}") + + build_notes # return value +end + +lane :get_build_test_groups do + test_groups = ['all-builds'] # send all builds to group 'all-builds'. Therefore, set it here and we will not remove it. + github = GitHub.new() + + # To avoid giving potentially unstable builds of our sample apps to certain members of the organization, we only send builds to "stable" group uncertain certain situations. + # If a commit is merged into main, it's considered stable because we deploy to production on merges to main. + if github.is_commit_pushed && github.push_branch == "main" + test_groups.append("stable-builds") + end + + test_groups = test_groups.join(", ") + + UI.important("Test group names that will be added to this build: #{test_groups}") + + test_groups # return value +end diff --git a/apps/fastlane/helpers/github_helper.rb b/apps/fastlane/helpers/github_helper.rb new file mode 100644 index 0000000..8ce1b25 --- /dev/null +++ b/apps/fastlane/helpers/github_helper.rb @@ -0,0 +1,61 @@ +# Parse JSON out of GitHub Context JSON when being executed on GitHub Actions. +class GitHub + # payload for releases: https://docs.github.com/en/webhooks-and-events/webhooks/webhook-events-and-payloads?actionType=published#release + # payload for pull requests: https://docs.github.com/en/webhooks-and-events/webhooks/webhook-events-and-payloads?actionType=synchronize#pull_request + + def initialize() + github_actions_metadata_path = ENV["GITHUB_EVENT_PATH"] # the path to the JSON file is the value of this environment variable. + # Read the GitHub provided JSON file > Parse the JSON into a Ruby Hash > Construct a GitHub class instance to easily pull out metadata for the notes. + @github_context = JSON.parse(File.open(github_actions_metadata_path).read) + end + + def is_commit_pushed + @github_context["head_commit"] != nil + end + + def is_pull_request + @github_context["pull_request"] != nil + end + + # Functions below only meant for when a github actions event is a push event + + def push_branch + # the branch name is: "refs/heads/". We use gsub to string replace and remove "refs/heads/" part to only get the branch name + return @github_context["ref"].gsub!('refs/heads/', '') + end + + def push_commit_hash + return @github_context["head_commit"]["id"] + end + + # Functions below only meant for when a github actions event is a pull request + + def pr_author + return @github_context["pull_request"]["user"]["login"] + end + + def pr_commit_hash + # Unfortunately, the git commit hash isn't included in the GitHub Actions metadata JSON for a release. We have to get that value manually. + return @github_context["pull_request"]["head"]["sha"][0..8] + end + + def pr_commits + return @github_context["pull_request"]["commits"] + end + + def pr_title + @github_context["pull_request"]["title"] + end + + def pr_number + @github_context["pull_request"]["number"] + end + + def pr_source_branch + return @github_context["pull_request"]["head"]["ref"] + end + + def pr_destination_branch + return @github_context["pull_request"]["base"]["ref"] + end +end diff --git a/apps/fastlane/helpers/version_helper.rb b/apps/fastlane/helpers/version_helper.rb new file mode 100644 index 0000000..7efd25f --- /dev/null +++ b/apps/fastlane/helpers/version_helper.rb @@ -0,0 +1,71 @@ +require_relative 'github_helper.rb' + +# Lane to generate new version +lane :generate_new_version do |options| + current_time = Time.now + github = GitHub.new() + + if github.is_pull_request + branch_name = github.pr_source_branch + elsif github.is_commit_pushed + branch_name = github.push_branch + end + + # Replace '/' with '-' to avoid issues with unsupported characters in version name + branch_name = branch_name.gsub('/', '-') + timestamp = current_time.strftime("%Y%m%d.%H%M%S") + + sdk_version_name = "#{timestamp}.0-#{branch_name}" + + if github.is_pull_request + app_version_name = "#{github.pr_number}.#{github.pr_commits}.0" + else + app_version_name = sdk_version_name + end + app_version_code = (current_time.to_f / 60).to_i + + UI.message("Generated new versions => SDK: #{sdk_version_name}, App: #{app_version_name} (#{app_version_code})") + sh("echo SDK_VERSION_NAME=#{sdk_version_name} >> $GITHUB_ENV") + sh("echo APP_VERSION_NAME=#{app_version_name} >> $GITHUB_ENV") + sh("echo APP_VERSION_CODE=#{app_version_code} >> $GITHUB_ENV") +end + +# Helper method to update Android version +def update_android_version(project_path, version_name, version_code) + build_gradle_path = File.join(project_path, 'android/app/build.gradle') + + unless File.exist?(build_gradle_path) + UI.user_error!("build.gradle not found at #{build_gradle_path}") + end + + android_set_version_name( + gradle_file: build_gradle_path, + version_name: version_name + ) + android_set_version_code( + gradle_file: build_gradle_path, + version_code: version_code + ) + UI.message("Updated versionName and versionCode to #{version_name} and #{version_code} in #{build_gradle_path}") +end + +# Helper method to update iOS version +def update_ios_version(project_path, version_name, build_number) + xcodeproj_name = 'Runner.xcodeproj' + xcodeproj_path = File.join(project_path, 'ios', xcodeproj_name) + + UI.message("Checking for #{xcodeproj_name} at #{xcodeproj_path} with version #{version_name} and build number #{build_number}") + unless File.exist?(xcodeproj_path) + UI.user_error!("#{xcodeproj_name} not found at #{xcodeproj_path}") + end + + ios_set_version( + xcodeproj: xcodeproj_path, + version: version_name + ) + ios_set_build_number( + xcodeproj: xcodeproj_path, + build_number: build_number + ) + UI.message("Updated version and build number to #{version_name} and #{build_number} in #{xcodeproj_path}") +end