From e17278f7ba578853b3fb04e5fb0f3efaab41aa7f Mon Sep 17 00:00:00 2001 From: eelco Date: Fri, 5 Jun 2026 18:49:48 +0200 Subject: [PATCH] Add progress indicator for all providers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Files mode shows `⠋ 1 of 10` - Bytes mode shows `⠙ 0B of 1.0MB` --- lib/beam_up.rb | 13 ++++ lib/beam_up/cli.rb | 10 ++-- lib/beam_up/progress.rb | 82 ++++++++++++++++++++++++++ lib/beam_up/providers/neocities.rb | 5 ++ lib/beam_up/providers/netlify.rb | 7 ++- lib/beam_up/providers/s3_compatible.rb | 8 ++- lib/beam_up/providers/sftp.rb | 8 ++- lib/beam_up/providers/statichost.rb | 2 + lib/beam_up/providers/transporter.rb | 7 ++- test/beam_up/cli_test.rb | 6 +- test/beam_up/progress_test.rb | 54 +++++++++++++++++ 11 files changed, 189 insertions(+), 13 deletions(-) create mode 100644 lib/beam_up/progress.rb create mode 100644 test/beam_up/progress_test.rb diff --git a/lib/beam_up.rb b/lib/beam_up.rb index 07e3167..5307bd5 100644 --- a/lib/beam_up.rb +++ b/lib/beam_up.rb @@ -5,6 +5,7 @@ require "beam_up/providers" require "beam_up/configuration" require "beam_up/result" +require "beam_up/progress" require "beam_up/core" require "beam_up/cli" @@ -22,6 +23,18 @@ module BeamUp } class << self + attr_accessor :progress + + def with_progress + self.progress = Progress.new + + yield + ensure + progress&.finish + + self.progress = nil + end + def configure(&block) = Core.configure(&block) def config_file=(path) diff --git a/lib/beam_up/cli.rb b/lib/beam_up/cli.rb index ed36221..2a3c8c9 100644 --- a/lib/beam_up/cli.rb +++ b/lib/beam_up/cli.rb @@ -82,12 +82,14 @@ def deploy_or_help def deploy input = parse_options - beamed = BeamUp.deploy! input, provider: @provider, config_file: @config_file + BeamUp.with_progress do + beamed = BeamUp.deploy! input, provider: @provider, config_file: @config_file - puts beamed.message - puts "Deploy ID: #{beamed.deploy_id}" if beamed.deploy_id + puts beamed.message + puts "Deploy ID: #{beamed.deploy_id}" if beamed.deploy_id - exit(1) unless beamed.success? + exit(1) unless beamed.success? + end end def scotty diff --git a/lib/beam_up/progress.rb b/lib/beam_up/progress.rb new file mode 100644 index 0000000..0e22e4c --- /dev/null +++ b/lib/beam_up/progress.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +module BeamUp + class Progress + def initialize + @mutex = Thread::Mutex.new + @current = 0 + @total = nil + @type = nil + @spinner_index = 0 + end + attr_reader :total, :current, :type + + def start(type:, total:) + @mutex.synchronize do + @type = type + @total = total + @current = 0 + @spinner_index = 0 + + render + end + end + + def tick(bytes: nil) + @mutex.synchronize do + @current += (@type == :bytes) ? bytes.to_i : 1 + + render + end + end + + def finish + @mutex.synchronize do + return if @total.nil? + + stop_render + + @total = nil + @current = nil + @type = nil + end + end + + private + + SPINNERS = %w[⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇] + + def render + return unless tty? + + spinner = SPINNERS[@spinner_index % SPINNERS.length] + @spinner_index += 1 + + line = if @type == :bytes + "#{spinner} #{formatted(@current)} of #{formatted(@total)}" + else + "#{spinner} #{@current} of #{@total}" + end + + $stdout.write("\r#{line}") + + $stdout.flush + end + + def stop_render + $stdout.write("\r#{" " * 80}\r") + + $stdout.flush + end + + def tty? = $stdout.tty? + + def formatted(bytes) + return format("%.1fGB", bytes.to_f / 1_073_741_824) if bytes >= 1_073_741_824 + return format("%.1fMB", bytes.to_f / 1_048_576) if bytes >= 1_048_576 + return format("%.1fKB", bytes.to_f / 1024) if bytes >= 1024 + + "#{bytes}B" + end + end +end diff --git a/lib/beam_up/providers/neocities.rb b/lib/beam_up/providers/neocities.rb index b09917c..dabaa5b 100644 --- a/lib/beam_up/providers/neocities.rb +++ b/lib/beam_up/providers/neocities.rb @@ -28,6 +28,9 @@ def validate! def deploy!(path) @path = path + files = files_to_deploy + BeamUp.progress&.start(type: :files, total: files.count) + upload_files Result.new( @@ -37,6 +40,8 @@ def deploy!(path) ) rescue => error Result.new(provider: "Neocities", error: error.message) + ensure + BeamUp.progress&.finish end private diff --git a/lib/beam_up/providers/netlify.rb b/lib/beam_up/providers/netlify.rb index 552ae8d..f84eb19 100644 --- a/lib/beam_up/providers/netlify.rb +++ b/lib/beam_up/providers/netlify.rb @@ -62,7 +62,9 @@ def upload(files, response) required_shas = response["required"] || [] return if required_shas.empty? - required_shas.each.with_index(1) do |sha, index| + BeamUp.progress&.start(type: :files, total: required_shas.count) + + required_shas.each do |sha| file_path = file_map_from(files)[sha] next if file_path.nil? || file_path.empty? @@ -70,7 +72,10 @@ def upload(files, response) escaped_path = CGI.escape(relative_path.delete_prefix("/")) put("/deploys/#{response["id"]}/files/#{escaped_path}", File.read(file_path)) + BeamUp.progress&.tick end + ensure + BeamUp.progress&.finish end def message diff --git a/lib/beam_up/providers/s3_compatible.rb b/lib/beam_up/providers/s3_compatible.rb index c989990..527ad17 100644 --- a/lib/beam_up/providers/s3_compatible.rb +++ b/lib/beam_up/providers/s3_compatible.rb @@ -8,8 +8,12 @@ class S3Compatible < Base def deploy!(path) @path = path - files_to_deploy.each do |file| + files = files_to_deploy + BeamUp.progress&.start(type: :files, total: files.count) + + files.each do |file| upload file.delete_prefix("#{@path}/"), file + BeamUp.progress&.tick end Result.new( @@ -19,6 +23,8 @@ def deploy!(path) ) rescue => error Result.new(provider: provider_name, error: error.message) + ensure + BeamUp.progress&.finish end private diff --git a/lib/beam_up/providers/sftp.rb b/lib/beam_up/providers/sftp.rb index 35fd698..83c03bc 100644 --- a/lib/beam_up/providers/sftp.rb +++ b/lib/beam_up/providers/sftp.rb @@ -38,9 +38,13 @@ def deploy!(path) } options[:keys] = [@configuration.key] if @configuration.key + files = files_to_deploy + BeamUp.progress&.start(type: :files, total: files.count) + Net::SFTP.start(@configuration.host, @configuration.username, options) do |sftp| - files_to_deploy.each do |file| + files.each do |file| upload sftp, file + BeamUp.progress&.tick end end @@ -53,6 +57,8 @@ def deploy!(path) raise ConfigurationError, "SFTP requires net-sftp gem. Install with: gem install net-sftp (or add to Gemfile)" rescue => error Result.new(provider: "SFTP", error: error.message) + ensure + BeamUp.progress&.finish end private diff --git a/lib/beam_up/providers/statichost.rb b/lib/beam_up/providers/statichost.rb index 71a4718..4342509 100644 --- a/lib/beam_up/providers/statichost.rb +++ b/lib/beam_up/providers/statichost.rb @@ -30,6 +30,7 @@ def deploy!(path) @path = path zipped_file = create_zip path + BeamUp.progress&.start(type: :bytes, total: zipped_file.size) response = upload zipped_file Result.new( @@ -41,6 +42,7 @@ def deploy!(path) Result.new(provider: "Statichost", error: error.message) ensure zipped_file&.close! + BeamUp.progress&.finish end private diff --git a/lib/beam_up/providers/transporter.rb b/lib/beam_up/providers/transporter.rb index 7d212f1..3754a59 100644 --- a/lib/beam_up/providers/transporter.rb +++ b/lib/beam_up/providers/transporter.rb @@ -24,8 +24,7 @@ def deploy!(path) @path = path files = files_to_deploy - puts "Energizing… 🚀" - puts "Matter stream detected: #{files.length} files" + BeamUp.progress&.start(type: :files, total: files.length) FileUtils.mkdir_p(@configuration.target_directory) @@ -36,7 +35,7 @@ def deploy!(path) FileUtils.mkdir_p(File.dirname(target_path)) FileUtils.cp(file, target_path) - puts " Beaming: #{relative_path}" + BeamUp.progress&.tick end puts "Transport complete. Files materialized at: #{@configuration.target_directory}" @@ -48,6 +47,8 @@ def deploy!(path) ) rescue => error Result.new(provider: "Transporter", error: error.message) + ensure + BeamUp.progress&.finish end end end diff --git a/test/beam_up/cli_test.rb b/test/beam_up/cli_test.rb index 307c285..a127faa 100644 --- a/test/beam_up/cli_test.rb +++ b/test/beam_up/cli_test.rb @@ -174,7 +174,7 @@ def test_deploy_with_folder_path cli.run end - assert_includes output, "Energizing" + assert_includes output, "Transport complete" assert File.exist?("./deployed/index.html") end @@ -214,7 +214,7 @@ def test_deploy_with_provider_override cli.run end - assert_includes output, "Matter stream detected" + assert_includes output, "Transport complete" assert File.exist?("./beamed/index.html") end @@ -234,7 +234,7 @@ def test_deploy_with_to_alias cli.run end - assert_includes output, "Matter stream detected" + assert_includes output, "Transport complete" assert File.exist?("./beamed/index.html") end diff --git a/test/beam_up/progress_test.rb b/test/beam_up/progress_test.rb new file mode 100644 index 0000000..903c558 --- /dev/null +++ b/test/beam_up/progress_test.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require "test_helper" + +module BeamUp + class ProgressTest < Minitest::Test + def test_start_sets_total_and_type + progress = BeamUp::Progress.new + + progress.start(type: :files, total: 47) + + assert_equal 47, progress.total + assert_equal :files, progress.type + end + + def test_start_with_bytes_type + progress = BeamUp::Progress.new + + progress.start(type: :bytes, total: 1_048_576) + + assert_equal 1_048_576, progress.total + assert_equal :bytes, progress.type + end + + def test_tick_increments_current_for_files + progress = BeamUp::Progress.new + + progress.start(type: :files, total: 10) + progress.tick + + assert_equal 1, progress.current + end + + def test_tick_adds_bytes_for_bytes_type + progress = BeamUp::Progress.new + + progress.start(type: :bytes, total: 1_000_000) + progress.tick(bytes: 1024) + + assert_equal 1024, progress.current + end + + def test_finish_resets_state + progress = BeamUp::Progress.new + progress.start(type: :files, total: 10) + + progress.tick + progress.finish + + assert_nil progress.total + assert_nil progress.current + end + end +end