Class: Cimas::ReleasePreflight
- Inherits:
-
Object
- Object
- Cimas::ReleasePreflight
- Defined in:
- lib/cimas/release_preflight.rb
Overview
Local maintainer-side preflight that mirrors the GHA preflight job in
metanorma/ci/.github/workflows/rubygems-release.yml. Runs against a
single repo's workspace clone before the maintainer fires the
workflow_dispatch to actually start the release chain. Catches the
cheap, deterministic failure modes (bundle resolve, gemspec errors,
missing credentials, already-published version) in ~30 sec locally,
so the maintainer never commits to the 2+ hour chain on a release
that was going to fail.
Companion to the GHA-side preflight introduced in metanorma/ci PR #313. Both protect against the same class of failures; the GHA preflight protects every release attempt automatically, this one protects the maintainer who runs it before firing.
runner: injects the process boundary for offline specs (production
omits it and uses the default shell runner).
Defined Under Namespace
Classes: ShellRunner
Instance Method Summary collapse
-
#initialize(command, runner: nil) ⇒ ReleasePreflight
constructor
A new instance of ReleasePreflight.
- #run ⇒ Object
Constructor Details
#initialize(command, runner: nil) ⇒ ReleasePreflight
Returns a new instance of ReleasePreflight.
38 39 40 41 |
# File 'lib/cimas/release_preflight.rb', line 38 def initialize(command, runner: nil) @command = command @runner = runner || ShellRunner.new end |
Instance Method Details
#run ⇒ Object
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/cimas/release_preflight.rb', line 43 def run repo_name = @command.config["target_repo"] repo = @command.repo_by_name(repo_name) if repo.nil? raise "[ERROR] #{repo_name} is not in cimas.yml. Run with --config-path pointing at the correct config." end repo_dir = File.join(@command.repos_path, repo_name) unless File.exist?(repo_dir) && File.exist?(File.join(repo_dir, ".git")) raise "[ERROR] #{repo_name} is not present in #{@command.repos_path}. Run `cimas setup` first." end puts "=== cimas release-preflight: #{repo_name} ===" puts " workspace: #{repo_dir}" puts "" failures = [] Dir.chdir(repo_dir) do puts "[1/4] Fresh bundle install (Gemfile.lock removed first)..." File.delete("Gemfile.lock") if File.exist?("Gemfile.lock") unless @runner.system!("bundle install --jobs 4 --retry 3") failures << "bundle install" puts " ✗ bundle install failed" else puts " ✓ bundle install succeeded" end puts "" puts "[2/4] Gem build (validates gemspec)..." gemspec_file = Dir["*.gemspec"].first if gemspec_file.nil? puts " ⚠️ No .gemspec file found in repo root; skipping gem build check" else if @runner.system!("gem build #{gemspec_file}") puts " ✓ gem build succeeded" Dir["*.gem"].each { |f| File.delete(f) } else failures << "gem build" puts " ✗ gem build failed" end end puts "" puts "[3/4] Verify publish credentials available..." if @runner.credentials_present? puts " ✓ ~/.gem/credentials present (API-key publish path viable)" elsif @runner.api_key_present? puts " ✓ RUBYGEMS_API_KEY env var present" else puts " ⚠️ No ~/.gem/credentials and no RUBYGEMS_API_KEY env var." puts " OIDC Trusted Publishing may still work in CI, but local `gem push` will fail." puts " Configure ~/.gem/credentials if you intend to publish from this machine." end puts "" puts "[4/4] Version awareness..." gem_spec = gemspec_file ? Gem::Specification.load(gemspec_file) : nil if gem_spec gem_name = gem_spec.name gem_version = gem_spec.version.to_s remote_list = @runner.capture( "gem list --remote --exact --version \"#{gem_version}\" \"#{gem_name}\" 2>/dev/null", ) if remote_list.include?("#{gem_name} (#{gem_version})") puts " ℹ️ #{gem_name} #{gem_version} is already on rubygems.org" puts " With next_version=skip, the idempotent guard will skip the actual gem push." puts " If you intended to ship NEW code, fire with next_version=patch (or major/minor)." else puts " ✓ #{gem_name} #{gem_version} is NOT yet on rubygems — clean to publish" latest_list = @runner.capture( "gem list --remote --exact \"#{gem_name}\" 2>/dev/null", ) if latest_match = latest_list.match(/#{Regexp.escape(gem_name)} \(([0-9.]+)/) puts " Latest published: #{gem_name} #{latest_match[1]}" else puts " (No previous public version found.)" end end else puts " (skipped — no gemspec to identify)" end puts "" end puts "=== Result ===" if failures.empty? puts "✓ All preflight checks passed for #{repo_name}." puts " Safe to fire: gh workflow run release.yml --repo metanorma/#{repo_name} --field next_version=patch" else puts "✗ Preflight FAILED for #{repo_name}: #{failures.join(', ')}" puts " Do NOT fire the release workflow until the failures above are fixed." exit 1 end end |