From 66116409f0122f6efadb03294776c1438c1237a3 Mon Sep 17 00:00:00 2001 From: eelco Date: Mon, 1 Jun 2026 13:16:26 +0200 Subject: [PATCH] GitHub Pages provider Add support for deploying static sites to GitHub Pages using git. The provider copies built files to a temporary directory, creates a clean commit, and force-pushes to the gh-pages branch. --- lib/beam_up.rb | 1 + lib/beam_up/providers.rb | 1 + lib/beam_up/providers/github_pages.rb | 117 ++++++++++++++++++++ test/beam_up/providers/github_pages_test.rb | 57 ++++++++++ 4 files changed, 176 insertions(+) create mode 100644 lib/beam_up/providers/github_pages.rb create mode 100644 test/beam_up/providers/github_pages_test.rb diff --git a/lib/beam_up.rb b/lib/beam_up.rb index 07e3167..fecb4d7 100644 --- a/lib/beam_up.rb +++ b/lib/beam_up.rb @@ -13,6 +13,7 @@ module BeamUp "aws_s3" => Providers::AwsS3, "bunny" => Providers::Bunny, "digital_ocean_spaces" => Providers::DigitalOceanSpaces, + "github_pages" => Providers::GitHubPages, "hetzner" => Providers::Hetzner, "neocities" => Providers::Neocities, "netlify" => Providers::Netlify, diff --git a/lib/beam_up/providers.rb b/lib/beam_up/providers.rb index c3599f4..4f1dbb7 100644 --- a/lib/beam_up/providers.rb +++ b/lib/beam_up/providers.rb @@ -4,6 +4,7 @@ require "beam_up/providers/aws_s3" require "beam_up/providers/bunny" require "beam_up/providers/digital_ocean_spaces" +require "beam_up/providers/github_pages" require "beam_up/providers/hetzner" require "beam_up/providers/neocities" require "beam_up/providers/netlify" diff --git a/lib/beam_up/providers/github_pages.rb b/lib/beam_up/providers/github_pages.rb new file mode 100644 index 0000000..cdc5d04 --- /dev/null +++ b/lib/beam_up/providers/github_pages.rb @@ -0,0 +1,117 @@ +# frozen_string_literal: true + +require "tmpdir" + +module BeamUp + module Providers + class GitHubPages < Base + class Config + def self.config_keys = %w[token branch] + + attr_accessor :token, :branch + + def initialize + @branch = "gh-pages" + end + + def with(options) + self.token = options[:token] + self.branch = options[:branch] || "gh-pages" + self + end + + def validate! = nil + end + + def deploy!(path) + @path = path + + verify_git_installed + owner, repo = parse_remote_url(current_remote_url) + + Dir.mktmpdir do |directory| + copy_files_to(directory) + run_git(directory, "init") + add_origin_remote(directory, owner, repo) + git_commit(directory) + git_push(directory) + end + + Result.new( + provider: "GitHub Pages", + deploy_id: Time.now.to_i.to_s, + url: pages_url(owner, repo) + ) + rescue => error + Result.new(provider: "GitHub Pages", error: error.message) + end + + private + + def verify_git_installed + raise ConfigurationError, "Git must be installed to deploy to GitHub Pages" unless system("git", "--version", out: File::NULL, err: File::NULL) + end + + def current_remote_url + output = `git remote get-url origin 2>/dev/null`.strip + raise ConfigurationError, "Could not detect GitHub remote. Ensure you are in a git repository with an origin remote." if output.empty? + + output + end + + def parse_remote_url(url) + if url.start_with?("https://") + parts = url.delete_prefix("https://").delete_prefix("github.com/").split("/") + owner = parts[0] + repo = parts[1].to_s.delete_suffix(".git").delete_suffix("/") + elsif url.start_with?("git@github.com:") + parts = url.delete_prefix("git@github.com:").split("/") + owner = parts[0] + repo = parts[1].to_s.delete_suffix(".git").delete_suffix("/") + else + raise ConfigurationError, "Unsupported remote URL format: #{url}" + end + + [owner, repo] + end + + def copy_files_to(directory) + files_to_deploy.each do |file| + relative_path = file.sub("#{@path}/", "") + target_path = File.join(directory, relative_path) + + FileUtils.mkdir_p(File.dirname(target_path)) + FileUtils.cp(file, target_path) + end + end + + def add_origin_remote(directory, owner, repo) + remote_url = if @configuration.token + "https://#{@configuration.token}@github.com/#{owner}/#{repo}.git" + else + "https://github.com/#{owner}/#{repo}.git" + end + + run_git(directory, "remote", "add", "origin", remote_url) + end + + def git_commit(directory) + run_git(directory, "add", ".") + run_git(directory, "commit", "-m", "Deploy #{Time.now}") + end + + def git_push(directory) + run_git(directory, "push", "--force", "origin", "HEAD:#{@configuration.branch}") + end + + def run_git(dir, *args) + command = ["git", "-C", dir, *args] + result = system(*command) + + raise DeploymentError, "Git command failed: #{command.join(" ")}" unless result + end + + def pages_url(owner, repo) = "https://#{owner}.github.io/#{repo}/" + end + end +end diff --git a/test/beam_up/providers/github_pages_test.rb b/test/beam_up/providers/github_pages_test.rb new file mode 100644 index 0000000..e4b36f1 --- /dev/null +++ b/test/beam_up/providers/github_pages_test.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require "test_helper" + +module BeamUp + module Providers + class GitHubPagesTest < Minitest::Test + def test_config_defaults + config = GitHubPages::Config.new + + assert_equal "gh-pages", config.branch + assert_nil config.token + end + + def test_config_accepts_custom_branch_and_token + config = GitHubPages::Config.new + config.with(token: "ghp_secret", branch: "docs") + + assert_equal "ghp_secret", config.token + assert_equal "docs", config.branch + end + + def test_parse_https_remote_url + provider = GitHubPages.new(GitHubPages::Config.new) + + owner, repo = provider.send(:parse_remote_url, "https://github.com/railsdesigner/beam_up.git") + + assert_equal "railsdesigner", owner + assert_equal "beam_up", repo + end + + def test_parse_https_remote_url_with_trailing_slash + provider = GitHubPages.new(GitHubPages::Config.new) + + owner, repo = provider.send(:parse_remote_url, "https://github.com/railsdesigner/beam_up/") + + assert_equal "railsdesigner", owner + assert_equal "beam_up", repo + end + + def test_parse_ssh_remote_url + provider = GitHubPages.new(GitHubPages::Config.new) + + owner, repo = provider.send(:parse_remote_url, "git@github.com:railsdesigner/beam_up.git") + + assert_equal "railsdesigner", owner + assert_equal "beam_up", repo + end + + def test_pages_url + provider = GitHubPages.new(GitHubPages::Config.new) + + assert_equal "https://railsdesigner.github.io/beam_up/", provider.send(:pages_url, "railsdesigner", "beam_up") + end + end + end +end