require 'net/http' require 'uri' require 'json' module Fastlane module Actions class ShuttleAction < Action def self.run(params) Fastlane::Polidea.session.action_launched("shuttle", params) platform = Actions.lane_context[Actions::SharedValues::PLATFORM_NAME].to_sym options = { api_token: params[:api_token], plist_url: params[:plist_url], prefix_schema: params[:prefix_schema], apk_url: params[:apk_url], app_identifier: params[:app_identifier], app_version: params[:app_version], build_number: params[:build_number], binary_size: params[:binary_size], release_notes: params[:release_notes], releaser: params[:releaser], environment: params[:environment] } validate(platform, options) config = get_config(platform, options) notify(platform, config) UI.success("Successfully uploaded to #{params[:app_identifier]} #{params[:app_version]}.#{params[:build_number]} to Shuttle") Fastlane::Polidea.session.action_completed("shuttle") end ##################################################### # @!group Documentation ##################################################### def self.description "Shuttle upload action" end def self.details "Notify Shuttle about new app version" end def self.author ["Piotrek Dubiel"] end def self.available_options [ FastlaneCore::ConfigItem.new(key: :api_token, env_name: "SHUTTLE_API_TOKEN", description: "API Token for Shuttle", verify_block: proc do |api_token| UI.user_error!("No API token for Shuttle given, pass using `api_token: 'token'`") unless api_token and !api_token.empty? end), FastlaneCore::ConfigItem.new(key: :plist_url, env_name: "SHUTTLE_PLIST_URL", description: "Url to uploaded plist", default_value: Actions.lane_context[SharedValues::S3_PLIST_OUTPUT_PATH], optional: true), FastlaneCore::ConfigItem.new(key: :prefix_schema, env_name: "SHUTTLE_PREFIX_SCHEMA", description: "Prefix schema in uploaded app", default_value: Actions.lane_context[SharedValues::PREFIX_SCHEMA], optional: true), FastlaneCore::ConfigItem.new(key: :apk_url, env_name: "SHUTTLE_APK_URL", description: "Url to uploaded apk", default_value: Actions.lane_context[SharedValues::S3_APK_OUTPUT_PATH], optional: true), FastlaneCore::ConfigItem.new(key: :app_identifier, description: "App identifier, either bundle id or package name", type: String, default_value: Actions.lane_context[SharedValues::APP_IDENTIFIER]), FastlaneCore::ConfigItem.new(key: :app_version, description: "App version, eg. '1.0.0''", type: String, default_value: Actions.lane_context[SharedValues::APP_VERSION]), FastlaneCore::ConfigItem.new(key: :build_number, description: "Build number, eg. 1337", is_string: false, default_value: Actions.lane_context[SharedValues::BUILD_NUMBER], verify_block: proc do |build_number| UI.user_error!("No value found for 'build_number'") unless build_number and build_number.kind_of? Integer end), FastlaneCore::ConfigItem.new(key: :binary_size, description: ".ipa/.apk binary size in bytes", is_string: false, default_value: Actions.lane_context[SharedValues::BINARY_SIZE], verify_block: proc do |binary_size| UI.user_error!("No value found for 'binary_size'") unless binary_size and binary_size.kind_of? Integer end), FastlaneCore::ConfigItem.new(key: :release_notes, description: "Release notes to be attached to uploaded version", type: String, optional: true, default_value: Actions.lane_context[SharedValues::RELEASE_NOTES]), FastlaneCore::ConfigItem.new(key: :releaser, description: "Releaser email", type: String, optional: true), FastlaneCore::ConfigItem.new(key: :environment, description: "Select environment, defaults to :production", type: Symbol, default_value: :production, optional: true) ] end def self.is_supported?(platform) [:ios, :android].include? platform end def self.validate(platform, params) case platform when :android apk_url = params[:apk_url] 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 apk_url and !apk_url.empty? when :ios plist_url = params[:plist_url] 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 plist_url and !plist_url.empty? url_scheme = params[:prefix_schema] UI.user_error!("No prefix scheme given. Make sure `add_prefix_schema` action succeded before build action") if url_scheme.nil? end end def self.get_config(platform, params) api_token = params[:api_token] app_identifier = params[:app_identifier] app_version = params[:app_version] build_number = params[:build_number] binary_size = params[:binary_size] release_notes = params[:release_notes] releaser = params[:releaser] || commit_author case platform when :ios href = itms_href(params[:plist_url]) prefix_schema = params[:prefix_schema] when :android href = params[:apk_url] end { api_token: api_token, app_identifier: app_identifier, app_version: app_version, build_number: build_number, href: href, release_notes: release_notes, releaser: releaser, binary_size: binary_size, prefix_schema: prefix_schema, environment: params[:environment] } end def self.notify(platform, params) build = { href: params[:href], version: params[:app_version], releaseNotes: params[:release_notes], bytes: params[:binary_size], releaserEmail: params[:releaser] } build[:prefixSchema] = params[:prefix_schema] if platform == :ios build[:versionCode] = params[:build_number] if platform == :android client = Shuttle::Client.new(base_url(params[:environment]), params[:api_token]) client.create_build(platform, params[:app_identifier], build) end private_class_method :notify def self.base_url(environment) case environment when :production "https://shuttle.polidea.com" when :testing "https://shuttle-testing.polidea.com" end end private_class_method :base_url 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.commit_author sh("git --no-pager show -s --format='%ae'", print_command: false, print_command_output: false).strip end private_class_method :commit_author end end end