# rubocop:disable Metrics/MethodLength require 'net/http' require 'uri' require 'json' module Fastlane module Actions class PolideaStoreAction < Action def self.run(params) platform = Actions.lane_context[Actions::SharedValues::PLATFORM_NAME].to_sym validate(platform, params) config = get_config(platform, params) app_name, app_identifier, version = notify(platform, config) UI.success("Successfully uploaded #{app_name} (#{app_identifier})") UI.success("version: #{version}") UI.success("using icon from #{params[:icon_url]}") if params[:icon_url] UI.success("using plist from #{params[:plist_url]}") if platform == :ios UI.success("with release notes: #{params[:release_notes]}") if params[:release_notes] end ##################################################### # @!group Documentation ##################################################### def self.description "Polidea Store upload action" end def self.details "Notify Polidea Store about new app version" end def self.author ["Piotrek Dubiel"] end def self.available_options [ FastlaneCore::ConfigItem.new(key: :api_token, env_name: "POLIDEA_STORE_API_TOKEN", description: "API Token for Polidea Store", verify_block: proc do |value| UI.user_error!("No API token for Polidea Store given, pass using `api_token: 'token'`") unless value and !value.empty? end), FastlaneCore::ConfigItem.new(key: :ipa, env_name: "", description: ".ipa file for the build ", optional: true, default_value: Actions.lane_context[SharedValues::IPA_OUTPUT_PATH]), FastlaneCore::ConfigItem.new(key: :apk, env_name: "", description: ".apk file for the build ", optional: true, default_value: Actions.lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH]), FastlaneCore::ConfigItem.new(key: :plist_url, env_name: "POLIDEA_STORE_PLIST_URL", description: "Url to uploaded plist", default_value: Actions.lane_context[SharedValues::S3_PLIST_OUTPUT_PATH], verify_block: proc do |value| UI.user_error!("No plist url given, pass using `plist_url: 'url'` or make sure s3 action succeded and exposed S3_PLIST_OUTPUT_PATH shared value") unless value and !value.empty? end), FastlaneCore::ConfigItem.new(key: :apk_url, env_name: "POLIDEA_STORE_APK_URL", description: "Url to uploaded apk", default_value: Actions.lane_context[SharedValues::S3_APK_OUTPUT_PATH], verify_block: proc do |value| UI.user_error!("No apk url given, pass using `apk_url: 'url'` or make sure s3 action succeded and exposed S3_APK_OUTPUT_PATH shared value") unless value and !value.empty? end), FastlaneCore::ConfigItem.new(key: :icon_url, env_name: "POLIDEA_STORE_ICON_URL", description: "Url to uploaded app icon", default_value: Actions.lane_context[SharedValues::S3_ICON_OUTPUT_PATH], optional: true), FastlaneCore::ConfigItem.new(key: :release_notes, env_name: "", description: "Release notes to be attached to uploaded version", is_string: true, optional: true, default_value: Actions.lane_context[SharedValues::RELEASE_NOTES]), FastlaneCore::ConfigItem.new(key: :app_name, env_name: "", description: "Application name (defaults to results of extract_app_name action)", is_string: true, optional: true), FastlaneCore::ConfigItem.new(key: :bundle_id, env_name: "", description: "Bundle id, used if platform equals :ios", is_string: true, optional: true), FastlaneCore::ConfigItem.new(key: :bundle_version, env_name: "", description: "Bundle version, used if platform equals :ios", is_string: true, optional: true), FastlaneCore::ConfigItem.new(key: :package_name, env_name: "", description: "Package name, used if platform equals :android", is_string: true, optional: true), FastlaneCore::ConfigItem.new(key: :version_name, env_name: "", description: "Version name, used if platform equals :android", is_string: true, optional: true), FastlaneCore::ConfigItem.new(key: :version_code, env_name: "", description: "Version code, used if platform equals :android", type: Integer, optional: true), FastlaneCore::ConfigItem.new(key: :binary_size, env_name: "", description: ".ipa/.apk binary size in bytes", type: Integer, optional: true) ] end def self.is_supported?(platform) [:ios, :android].include? platform end def self.validate(platform, params) binary_size = params[:binary_size] || Actions.lane_context[Actions::SharedValues::BINARY_SIZE] UI.user_error!("No binary size given, pass using `binary_size: bytes` or make sure get_binary_size action succeded and exposed BINARY_SIZE shared value") unless binary_size and !binary_size.nil? case platform when :android UI.user_error!("No .apk file given, pass using `apk: 'apk path'` or make sure gradle action succeded and exposed GRADLE_APK_OUTPUT_PATH shared value") unless params[:apk] and !params[:apk].empty? when :ios UI.user_error!("No .ipa file given, pass using `ipa: 'ipa path'` or make sure gym action succeded and exposed IPA_OUTPUT_PATH shared value") unless params[:ipa] and !params[:ipa].empty? end end def self.get_config(platform, params) binary_size = params[:binary_size] || Actions.lane_context[Actions::SharedValues::BINARY_SIZE] api_token = params[:api_token] icon_url = params[:icon_url] release_notes = params[:release_notes] case platform when :ios info = FastlaneCore::IpaFileAnalyser.fetch_info_plist_file(params[:ipa]) app_name = params[:app_name] || info['CFBundleName'] app_identifier = params[:bundle_id] || info['CFBundleIdentifier'] version = params[:bundle_version] || info['CFBundleShortVersionString'] href = itms_href(params[:plist_url]) url_scheme = get_url_scheme(info) UI.user_error!("No prefix scheme found in Info.plist. Make sure `add_prefix_schema` action succeded before build action") if url_scheme.nil? when :android manifest = Android::Apk.new(params[:apk]).manifest app_name = params[:app_name] || manifest.label app_identifier = params[:package_name] || manifest.package_name version = params[:version_name] || manifest.version_name version_code = params[:version_code] || manifest.version_code href = params[:apk_url] end config = { api_token: api_token, app_name: app_name, app_identifier: app_identifier, version: version, version_code: version_code, icon_url: icon_url, href: href, release_notes: release_notes, binary_size: binary_size, url_scheme: url_scheme } return config end def self.notify(platform, params) uri = URI.parse(url(platform, params[:app_identifier])) version = { version: params[:version], releaseNotes: params[:release_notes], href: params[:href], bytes: params[:binary_size] } version[:prefixSchema] = params[:url_scheme] if platform == :ios version[:versionCode] = params[:version_code] if platform == :android http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true req = create_request(uri, params[:api_token], { name: params[:app_name], icon: { href: params[:icon_url] }, versions: [version] }) response = http.request(req) unless response.code == "204" UI.user_error! "Polidea Store backend responded with [#{response.code}] #{response.body}" end return params[:app_name], params[:app_identifier], params[:version] end private_class_method :notify def self.url(platform, app_identifier) "#{base_url}/#{platform}/apps/#{app_identifier}/versions" end private_class_method :url def self.base_url "https://polideastore.herokuapp.com" # "https://polideastore-staging.herokuapp.com" end private_class_method :base_url def self.create_request(uri, upload_token, params) req = Net::HTTP::Post.new(uri.request_uri) req['Content-Type'] = 'application/json' req['Upload-Token'] = upload_token req.body = JSON.generate(params) req end private_class_method :create_request def self.itms_href(plist_url) "itms-services://?action=download-manifest&url=#{URI.encode_www_form_component(plist_url)}" end private_class_method :itms_href def self.get_url_scheme(plist) url_scheme = nil url_types = plist['CFBundleURLTypes'] unless url_types.nil? url_type = url_types[0] url_schemes = url_type['CFBundleURLSchemes'] unless url_schemes.nil? url_scheme = url_schemes[0] end end url_scheme end private_class_method :get_url_scheme end end end # rubocop:enable Metrics/MethodLength