module Fastlane module Actions module SharedValues APPETIZE_PUBLIC_KEY = :APPETIZE_PUBLIC_KEY APPETIZE_APP_URL = :APPETIZE_APP_URL APPETIZE_MANAGE_URL = :APPETIZE_MANAGE_URL APPETIZE_API_HOST = :APPETIZE_API_HOST end class AppetizeAction < Action def self.is_supported?(platform) [:ios, :android].include?(platform) end def self.run(options) require 'net/http' require 'net/http/post/multipart' require 'uri' require 'json' params = { platform: options[:platform] } if options[:path] params[:file] = UploadIO.new(options[:path], 'application/zip') else UI.user_error!('url parameter is required if no file path is specified') if options[:url].nil? params[:url] = options[:url] end params[:note] = options[:note] if options[:note].to_s.length > 0 uri = URI.parse(appetize_url(options)) req = create_request(uri, params) req.basic_auth(options[:api_token], nil) http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true if params[:platform] == 'ios' UI.message("Uploading ipa to appetize... this might take a while") else UI.message("Uploading apk to appetize... this might take a while") end response = http.request(req) parse_response(response) # this will raise an exception if something goes wrong UI.message("App URL: #{Actions.lane_context[SharedValues::APPETIZE_APP_URL]}") UI.message("Manage URL: #{Actions.lane_context[SharedValues::APPETIZE_MANAGE_URL]}") UI.message("Public Key: #{Actions.lane_context[SharedValues::APPETIZE_PUBLIC_KEY]}") UI.success("Build successfully uploaded to Appetize.io") end def self.appetize_url(options) Actions.lane_context[SharedValues::APPETIZE_API_HOST] = options[:api_host] "https://#{options[:api_host]}/v1/apps/#{options[:public_key]}" end private_class_method :appetize_url def self.create_request(uri, params) if params[:url] req = Net::HTTP::Post.new(uri.request_uri, { 'Content-Type' => 'application/json' }) req.body = JSON.generate(params) else req = Net::HTTP::Post::Multipart.new(uri.path, params) end req end private_class_method :create_request def self.parse_response(response) body = JSON.parse(response.body) app_url = body['appURL'] manage_url = body['manageURL'] public_key = body['publicKey'] Actions.lane_context[SharedValues::APPETIZE_PUBLIC_KEY] = public_key Actions.lane_context[SharedValues::APPETIZE_APP_URL] = app_url Actions.lane_context[SharedValues::APPETIZE_MANAGE_URL] = manage_url return true rescue => ex UI.error(ex) UI.user_error!("Error uploading to Appetize.io: #{response.body}") end private_class_method :parse_response def self.description "Upload your app to [Appetize.io](https://appetize.io/) to stream it in browser" end def self.details [ "If you provide a `public_key`, this will overwrite an existing application. If you want to have this build as a new app version, you shouldn't provide this value.", "", "To integrate appetize into your GitHub workflow check out the [device_grid guide](https://github.com/fastlane/fastlane/blob/master/fastlane/lib/fastlane/actions/device_grid/README.md)." ].join("\n") end def self.available_options [ FastlaneCore::ConfigItem.new(key: :api_host, env_name: "APPETIZE_API_HOST", description: "Appetize API host", is_string: true, default_value: 'api.appetize.io', verify_block: proc do |value| UI.user_error!("API host should not contain the scheme e.g. `https`") if value.start_with?('https') end), FastlaneCore::ConfigItem.new(key: :api_token, env_name: "APPETIZE_API_TOKEN", sensitive: true, description: "Appetize.io API Token", is_string: true, verify_block: proc do |value| UI.user_error!("No API Token for Appetize.io given, pass using `api_token: 'token'`") unless value.to_s.length > 0 end), FastlaneCore::ConfigItem.new(key: :url, env_name: "APPETIZE_URL", description: "URL from which the ipa file can be fetched. Alternative to :path", is_string: true, optional: true), FastlaneCore::ConfigItem.new(key: :platform, env_name: "APPETIZE_PLATFORM", description: "Platform. Either `ios` or `android`", is_string: true, default_value: 'ios'), FastlaneCore::ConfigItem.new(key: :path, env_name: "APPETIZE_FILE_PATH", description: "Path to zipped build on the local filesystem. Either this or `url` must be specified", is_string: true, optional: true), FastlaneCore::ConfigItem.new(key: :public_key, env_name: "APPETIZE_PUBLICKEY", description: "If not provided, a new app will be created. If provided, the existing build will be overwritten", is_string: true, optional: true, verify_block: proc do |value| if value.start_with?("private_") UI.user_error!("You provided a private key to appetize, please provide the public key") end end), FastlaneCore::ConfigItem.new(key: :note, env_name: "APPETIZE_NOTE", description: "Notes you wish to add to the uploaded app", is_string: true, optional: true) ] end def self.output [ ['APPETIZE_API_HOST', 'Appetize API host.'], ['APPETIZE_PUBLIC_KEY', 'a public identifier for your app. Use this to update your app after it has been initially created.'], ['APPETIZE_APP_URL', 'a page to test and share your app.'], ['APPETIZE_MANAGE_URL', 'a page to manage your app.'] ] end def self.authors ["klundberg", "giginet", "steprescott"] end def self.category :beta end def self.example_code [ 'appetize( path: "./MyApp.zip", api_token: "yourapitoken", # get it from https://appetize.io/docs#request-api-token public_key: "your_public_key" # get it from https://appetize.io/dashboard )', 'appetize( path: "./MyApp.zip", api_host: "company.appetize.io", # only needed for enterprise hosted solution api_token: "yourapitoken", # get it from https://appetize.io/docs#request-api-token public_key: "your_public_key" # get it from https://appetize.io/dashboard )' ] end end end end