diff --git a/lib/commands/upload/build.rb b/lib/commands/upload/build.rb new file mode 100644 index 0000000..bb5cbf9 --- /dev/null +++ b/lib/commands/upload/build.rb @@ -0,0 +1,140 @@ +require 'dry/cli' +require 'json' +require 'uri' +require 'async' +require 'async/barrier' +require 'async/semaphore' +require 'async/http/internet/instance' + +module EmergeCLI + module Commands + module Upload + class Build < EmergeCLI::Commands::GlobalOptions + desc 'Upload a build to Emerge' + + option :path, type: :string, required: true, desc: 'Path to the build artifact' + + # Optional options + option :api_token, type: :string, required: false, + desc: 'API token for authentication, defaults to ENV[EMERGE_API_TOKEN]' + option :sha, type: :string, required: false, desc: 'SHA of the commit' + option :branch, type: :string, required: false, desc: 'Branch name' + option :repo_name, type: :string, required: false, desc: 'Repository name' + option :base_sha, type: :string, required: false, desc: 'Base SHA' + option :previous_sha, type: :string, required: false, desc: 'Previous SHA' + option :pr_number, type: :string, required: false, desc: 'PR number' + + def initialize(network: nil, git_info_provider: nil) + @network = network + @git_info_provider = git_info_provider + end + + def call(**options) + @options = options + @profiler = EmergeCLI::Profiler.new(enabled: options[:profile]) + before(options) + + start_time = Time.now + + file_path = options[:path] + file_exists = File.exist?(file_path) + raise "File not found at path: #{file_path}" unless file_exists + + file_extension = File.extname(file_path) + raise "Unsupported file type: #{file_extension}" unless ['.ipa', '.apk', '.aab', + '.zip'].include?(file_extension) + + api_token = @options[:api_token] || ENV.fetch('EMERGE_API_TOKEN', nil) + raise 'API token is required and cannot be blank' if api_token.nil? || api_token.strip.empty? + + @network ||= EmergeCLI::Network.new(api_token:) + @git_info_provider ||= GitInfoProvider.new + + Sync do + upload_url, upload_id = fetch_upload_url + + file_size = File.size(file_path) + Logger.info("Uploading file... (#{file_size} bytes)") + + File.open(file_path, 'rb') do |file| + headers = { + 'Content-Type' => 'application/zip', + 'Content-Length' => file_size.to_s + } + + response = @network.put( + path: upload_url, + body: file.read, + headers: headers + ) + + unless response.status == 200 + Logger.error("Upload failed with status #{response.status}") + Logger.error("Response body: #{response.body}") + raise "Uploading file failed with status #{response.status}" + end + end + + Logger.info('Upload complete successfully!') + Logger.info "Time taken: #{(Time.now - start_time).round(2)} seconds" + Logger.info("✅ You can view the build analysis at https://emergetools.com/build/#{upload_id}") + end + end + + private + + def fetch_upload_url + git_result = @git_info_provider.fetch_git_info + sha = @options[:sha] || git_result.sha + branch = @options[:branch] || git_result.branch + base_sha = @options[:base_sha] || git_result.base_sha + previous_sha = @options[:previous_sha] || git_result.previous_sha + pr_number = @options[:pr_number] || git_result.pr_number + + # TODO: Make optional + raise 'SHA is required' unless sha + raise 'Branch is required' unless branch + + payload = { + sha:, + branch:, + repo_name: @options[:repo_name], + # Optional + base_sha:, + previous_sha:, + pr_number: pr_number&.to_s + }.compact + + upload_response = @network.post( + path: '/upload', + body: payload, + headers: { 'Content-Type' => 'application/json' } + ) + upload_json = parse_response(upload_response) + upload_id = upload_json.fetch('upload_id') + upload_url = upload_json.fetch('uploadURL') + Logger.debug("Got upload ID: #{upload_id}") + + warning = upload_json['warning'] + Logger.warn(warning) if warning + + [upload_url, upload_id] + end + + def parse_response(response) + case response.status + when 200 + JSON.parse(response.read) + when 400 + error_message = JSON.parse(response.read)['errorMessage'] + raise "Invalid parameters: #{error_message}" + when 401, 403 + raise 'Invalid API token' + else + raise "Creating upload failed with status #{response.status}" + end + end + end + end + end +end diff --git a/lib/emerge_cli.rb b/lib/emerge_cli.rb index 8c2a99e..8298b21 100644 --- a/lib/emerge_cli.rb +++ b/lib/emerge_cli.rb @@ -14,6 +14,7 @@ require_relative 'commands/order_files/download_order_files' require_relative 'commands/order_files/validate_linkmaps' require_relative 'commands/order_files/validate_xcode_project' +require_relative 'commands/upload/build' require_relative 'reaper/ast_parser' require_relative 'reaper/code_deleter' @@ -30,11 +31,13 @@ require_relative 'utils/version_check' require 'dry/cli' +require 'pry-byebug' module EmergeCLI extend Dry::CLI::Registry register 'upload', aliases: ['u'] do |prefix| + prefix.register 'build', Commands::Upload::Build prefix.register 'snapshots', Commands::Upload::Snapshots end diff --git a/lib/utils/git.rb b/lib/utils/git.rb index 36ddfc5..3223f10 100644 --- a/lib/utils/git.rb +++ b/lib/utils/git.rb @@ -3,6 +3,7 @@ module EmergeCLI module Git def self.branch + Logger.debug 'Getting current branch name' command = 'git rev-parse --abbrev-ref HEAD' Logger.debug command stdout, _, status = Open3.capture3(command) @@ -39,6 +40,7 @@ def self.branch end def self.sha + Logger.debug 'Getting current SHA' command = 'git rev-parse HEAD' Logger.debug command stdout, _, status = Open3.capture3(command) @@ -46,6 +48,7 @@ def self.sha end def self.base_sha + Logger.debug 'Getting base SHA' current_branch = branch remote_head = remote_head_branch return nil if current_branch.nil? || remote_head.nil? @@ -59,6 +62,7 @@ def self.base_sha end def self.previous_sha + Logger.debug 'Getting previous SHA' command = 'git rev-list --count HEAD' Logger.debug command count_stdout, _, count_status = Open3.capture3(command) @@ -78,12 +82,14 @@ def self.previous_sha end def self.primary_remote + Logger.debug 'Getting primary remote' remote = remote() return nil if remote.nil? remote.include?('origin') ? 'origin' : remote.first end def self.remote_head_branch(remote = primary_remote) + Logger.debug 'Getting remote head branch' return nil if remote.nil? command = "git remote show #{remote}" Logger.debug command @@ -98,6 +104,7 @@ def self.remote_head_branch(remote = primary_remote) end def self.remote_url(remote = primary_remote) + Logger.debug 'Getting remote URL' return nil if remote.nil? command = "git config --get remote.#{remote}.url" Logger.debug command @@ -106,6 +113,7 @@ def self.remote_url(remote = primary_remote) end def self.remote + Logger.debug 'Getting remote' command = 'git remote' Logger.debug command stdout, _, status = Open3.capture3(command)