Skip to content

Commit fc5c6bb

Browse files
committed
Create automation to check for new Ruby versions and create PRs
1 parent 05996c7 commit fc5c6bb

16 files changed

+2135
-201
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
name: Check for New Ruby Versions
2+
3+
on:
4+
schedule:
5+
- cron: '0 2 * * *' # Daily at 2 AM UTC
6+
workflow_dispatch: # Allow manual triggers
7+
8+
permissions:
9+
contents: write
10+
pull-requests: write
11+
issues: write
12+
13+
jobs:
14+
check-versions:
15+
runs-on: ubuntu-latest
16+
steps:
17+
- name: Checkout repository
18+
uses: actions/checkout@v4
19+
20+
- name: Set up Ruby
21+
uses: ruby/setup-ruby@v1
22+
with:
23+
ruby-version: '3.3'
24+
bundler-cache: true
25+
26+
- name: Check for new versions and create PR
27+
id: check-and-create
28+
env:
29+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
30+
run: |
31+
PR_NUMBER=$(bin/check-and-create-pr)
32+
33+
if [ -n "$PR_NUMBER" ]; then
34+
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
35+
fi
36+
37+
- name: Enable auto-merge
38+
if: steps.check-and-create.outputs.pr_number != ''
39+
env:
40+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
41+
run: |
42+
echo "Enabling auto-merge for PR #${{ steps.check-and-create.outputs.pr_number }}..."
43+
gh pr merge ${{ steps.check-and-create.outputs.pr_number }} --auto --squash
44+
45+
- name: Create issue on failure
46+
if: failure()
47+
uses: actions/github-script@v7
48+
with:
49+
script: |
50+
await github.rest.issues.create({
51+
owner: context.repo.owner,
52+
repo: context.repo.repo,
53+
title: '🔴 Ruby Version Check Failed',
54+
body: `The automated Ruby version check workflow failed.
55+
56+
**Run:** ${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}
57+
58+
**Job:** ${context.job}
59+
60+
Please check the workflow logs for details.`,
61+
labels: ['automation', 'bug', 'ruby-versions']
62+
});

Gemfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,7 @@
33
source "https://rubygems.org"
44

55
gem "minitest"
6+
gem "mocha"
7+
gem "octokit"
68
gem "rake"
9+
gem "webmock"

Gemfile.lock

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,76 @@
11
GEM
22
remote: https://rubygems.org/
33
specs:
4+
addressable (2.8.8)
5+
public_suffix (>= 2.0.2, < 8.0)
6+
bigdecimal (4.0.1)
7+
crack (1.0.1)
8+
bigdecimal
9+
rexml
10+
faraday (2.14.0)
11+
faraday-net_http (>= 2.0, < 3.5)
12+
json
13+
logger
14+
faraday-net_http (3.4.2)
15+
net-http (~> 0.5)
16+
hashdiff (1.2.1)
17+
json (2.18.0)
18+
logger (1.7.0)
419
minitest (6.0.1)
520
prism (~> 1.5)
21+
mocha (3.0.1)
22+
ruby2_keywords (>= 0.0.5)
23+
net-http (0.9.1)
24+
uri (>= 0.11.1)
25+
octokit (10.0.0)
26+
faraday (>= 1, < 3)
27+
sawyer (~> 0.9)
628
prism (1.9.0)
29+
public_suffix (7.0.2)
730
rake (13.3.1)
31+
rexml (3.4.4)
32+
ruby2_keywords (0.0.5)
33+
sawyer (0.9.3)
34+
addressable (>= 2.3.5)
35+
faraday (>= 0.17.3, < 3)
36+
uri (1.1.1)
37+
webmock (3.26.1)
38+
addressable (>= 2.8.0)
39+
crack (>= 0.3.2)
40+
hashdiff (>= 0.4.0, < 2.0.0)
841

942
PLATFORMS
1043
aarch64-linux
1144
ruby
1245

1346
DEPENDENCIES
1447
minitest
48+
mocha
49+
octokit
1550
rake
51+
webmock
1652

1753
CHECKSUMS
54+
addressable (2.8.8) sha256=7c13b8f9536cf6364c03b9d417c19986019e28f7c00ac8132da4eb0fe393b057
55+
bigdecimal (4.0.1) sha256=8b07d3d065a9f921c80ceaea7c9d4ae596697295b584c296fe599dd0ad01c4a7
56+
crack (1.0.1) sha256=ff4a10390cd31d66440b7524eb1841874db86201d5b70032028553130b6d4c7e
57+
faraday (2.14.0) sha256=8699cfe5d97e55268f2596f9a9d5a43736808a943714e3d9a53e6110593941cd
58+
faraday-net_http (3.4.2) sha256=f147758260d3526939bf57ecf911682f94926a3666502e24c69992765875906c
59+
hashdiff (1.2.1) sha256=9c079dbc513dfc8833ab59c0c2d8f230fa28499cc5efb4b8dd276cf931457cd1
60+
json (2.18.0) sha256=b10506aee4183f5cf49e0efc48073d7b75843ce3782c68dbeb763351c08fd505
61+
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
1862
minitest (6.0.1) sha256=7854c74f48e2e975969062833adc4013f249a4b212f5e7b9d5c040bf838d54bb
63+
mocha (3.0.1) sha256=74f82c5959e3df6cfa2757d60aa420dcb151ac7c5eb61dcebf1cd4e368ed7469
64+
net-http (0.9.1) sha256=25ba0b67c63e89df626ed8fac771d0ad24ad151a858af2cc8e6a716ca4336996
65+
octokit (10.0.0) sha256=82e99a539b7637b7e905e6d277bb0c1a4bed56735935cc33db6da7eae49a24e8
1966
prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85
67+
public_suffix (7.0.2) sha256=9114090c8e4e7135c1fd0e7acfea33afaab38101884320c65aaa0ffb8e26a857
2068
rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c
69+
rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142
70+
ruby2_keywords (0.0.5) sha256=ffd13740c573b7301cf7a2e61fc857b2a8e3d3aff32545d6f8300d8bae10e3ef
71+
sawyer (0.9.3) sha256=0d0f19298408047037638639fe62f4794483fb04320269169bd41af2bdcf5e41
72+
uri (1.1.1) sha256=379fa58d27ffb1387eaada68c749d1426738bd0f654d812fcc07e7568f5c57c6
73+
webmock (3.26.1) sha256=4f696fb57c90a827c20aadb2d4f9058bbff10f7f043bd0d4c3f58791143b1cd7
2174

2275
BUNDLED WITH
2376
4.0.3

bin/add-ruby-version

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
#!/usr/bin/env ruby
22
# frozen_string_literal: true
33

4-
require_relative "../lib/add_ruby_version"
4+
require_relative "../lib/commands/add_ruby_version"
55

66
if ARGV.length != 1
77
warn "Usage: bin/add-ruby-version <ruby-version>"
88
warn "Example: bin/add-ruby-version 3.4.5"
99
exit 1
1010
end
1111

12-
result = AddRubyVersion.call(ARGV[0], working_dir: Dir.pwd)
12+
result = Commands::AddRubyVersion.call(ARGV[0], working_dir: Dir.pwd)
1313
exit(result[:success] ? 0 : 1)

bin/check-and-create-pr

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#!/usr/bin/env ruby
2+
# frozen_string_literal: true
3+
4+
# Check for new Ruby versions and create a PR if found
5+
#
6+
# Usage:
7+
# bin/check-and-create-pr [options]
8+
#
9+
# Options:
10+
# --dry-run Check for versions but don't create a PR
11+
# --help Show this help message
12+
#
13+
# Environment variables:
14+
# GITHUB_TOKEN - GitHub token for API requests (optional, but recommended)
15+
#
16+
# Exit codes:
17+
# 0 - Success (PR created or no new versions)
18+
# 1 - Error occurred
19+
20+
require_relative "../lib/commands/check_and_create_pr"
21+
22+
def print_help
23+
puts <<~HELP
24+
Check for new Ruby versions and create a PR if found
25+
26+
Usage:
27+
bin/check-and-create-pr [options]
28+
29+
Options:
30+
--dry-run Check for versions but don't create a PR
31+
--help Show this help message
32+
33+
Environment variables:
34+
GITHUB_TOKEN - GitHub token for API requests (optional, but recommended)
35+
36+
Examples:
37+
bin/check-and-create-pr # Check and create PR
38+
bin/check-and-create-pr --dry-run # Check only, don't create PR
39+
GITHUB_TOKEN=xxx bin/check-and-create-pr # Use specific token
40+
HELP
41+
end
42+
43+
# Parse arguments
44+
dry_run = false
45+
ARGV.each do |arg|
46+
case arg
47+
when "--dry-run"
48+
dry_run = true
49+
when "--help", "-h"
50+
print_help
51+
exit 0
52+
else
53+
warn "Unknown option: #{arg}"
54+
print_help
55+
exit 1
56+
end
57+
end
58+
59+
# Run the check and create PR
60+
# Use stderr for log messages so stdout only contains the PR number (for CI capture)
61+
result = Commands::CheckAndCreatePR.call(
62+
working_dir: Dir.pwd,
63+
output: $stderr,
64+
dry_run: dry_run
65+
)
66+
67+
# Output PR number to stdout if created (for use in CI)
68+
if result[:pr_created] && result[:pr_number]
69+
puts result[:pr_number]
70+
end
71+
72+
exit(result[:success] ? 0 : 1)

lib/commands/add_ruby_version.rb

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# frozen_string_literal: true
2+
3+
require_relative "../ruby_version_adder"
4+
require_relative "console"
5+
6+
module Commands
7+
# Add Ruby Version Command (Presentation Layer)
8+
#
9+
# This class provides a CLI-friendly interface for adding Ruby versions.
10+
# It wraps RubyVersionAdder with formatted output including colors and emojis.
11+
#
12+
# For silent/programmatic usage, use RubyVersionAdder directly.
13+
class AddRubyVersion
14+
include Console
15+
16+
class << self
17+
# Main entry point for adding a Ruby version with formatted output
18+
#
19+
# @param version [String] The Ruby version to add (e.g., "3.4.0")
20+
# @param working_dir [String] The directory to operate in
21+
# @param output [IO] Output stream for messages (default: $stdout)
22+
# @return [Hash] Result with :success, :files_modified, and :message keys
23+
def call(version, working_dir:, output: $stdout)
24+
new(version, working_dir: working_dir, output: output).call
25+
end
26+
end
27+
28+
attr_reader :version, :working_dir, :output
29+
30+
def initialize(version, working_dir:, output: $stdout)
31+
@version = version
32+
@working_dir = working_dir
33+
@output = output
34+
end
35+
36+
def call
37+
log_checking_configuration
38+
39+
# Get current default before running the service
40+
service = RubyVersionAdder::Service.new(version, working_dir: working_dir)
41+
42+
begin
43+
current_default = service.current_default_version
44+
log_version_info(current_default)
45+
rescue RubyVersionAdder::Error
46+
# Will be handled by the service call
47+
end
48+
49+
# Call the business logic service
50+
result = RubyVersionAdder.call(version, working_dir: working_dir)
51+
52+
if result[:success]
53+
log_success_result(result)
54+
else
55+
log_error(result[:error])
56+
end
57+
58+
result
59+
end
60+
61+
private
62+
63+
def log_checking_configuration
64+
log("Checking current configuration...", :cyan, emoji: :search)
65+
end
66+
67+
def log_version_info(current_default)
68+
log("Current default version: #{current_default}")
69+
log("New version to add: #{version}")
70+
end
71+
72+
def log_success_result(result)
73+
log("")
74+
log("Adding #{version} to #{RubyVersionAdder::VERSIONS_JSON_FILE}...", :blue, emoji: :edit)
75+
log("Successfully added to #{RubyVersionAdder::VERSIONS_JSON_FILE}", :green, emoji: :check)
76+
77+
if result[:default_updated]
78+
log_default_updated(result)
79+
else
80+
log_default_unchanged(result[:previous_default])
81+
end
82+
83+
log_final_success(result[:files_modified])
84+
end
85+
86+
def log_default_updated(result)
87+
log("")
88+
log("New version #{version} is newer than current default #{result[:previous_default]}", :yellow, emoji: :update)
89+
log("Updated default version to #{version}", :green, emoji: :check)
90+
log("Updated README default version to #{version}", :green, emoji: :check)
91+
92+
log("")
93+
log("Bumping feature version...", :yellow, emoji: :update)
94+
log("Feature version bumped from #{result[:old_feature_version]} to #{result[:new_feature_version]}", :green, emoji: :check)
95+
96+
log("")
97+
log("Updating test files...", :blue, emoji: :edit)
98+
RubyVersionAdder::TEST_FILES.each do |test_file|
99+
log("Updated #{test_file}", :green, emoji: :check)
100+
end
101+
end
102+
103+
def log_default_unchanged(previous_default)
104+
log("")
105+
log("New version #{version} is not newer than current default #{previous_default}", :cyan, emoji: :info)
106+
log("Default version remains unchanged")
107+
log("")
108+
log("Skipping test file updates (new version #{version} is not newer than current default #{previous_default})", :cyan, emoji: :info)
109+
end
110+
111+
def log_final_success(files_modified)
112+
log("")
113+
log("Successfully added Ruby version #{version}!", :green, emoji: :party)
114+
log("")
115+
log("Files modified:", :blue, emoji: :file)
116+
files_modified.each { |file| log(" • #{file}") }
117+
118+
log("")
119+
log("Next steps:", :magenta, emoji: :bulb)
120+
log(" 1. Review the changes: git diff")
121+
log(" 2. Commit the changes: git add . && git commit -m 'Add Ruby #{version}'")
122+
log(" 3. Push changes: git push")
123+
end
124+
125+
def log_error(error_message)
126+
log("Error: #{error_message}", :red, emoji: :error)
127+
end
128+
end
129+
end

0 commit comments

Comments
 (0)