Module: Rakit::Gem
- Defined in:
- lib/rakit/gem.rb
Class Method Summary collapse
-
.bump(gemspec_path) ⇒ Object
Bump the last digit of the version in the gemspec file (e.g. “0.1.0” -> “0.1.1”).
- .http_get_following_redirects(uri, limit: 5) ⇒ Object
-
.latest_remote_version(name) ⇒ Object
Return the latest version string for name on the remote, or nil on error.
-
.package(spec, out_dir) ⇒ Object
Build the gem for the given spec and move it into out_dir.
-
.parse_gem_basename(base) ⇒ Object
Parse “name-version” basename (no .gem) into [name, version].
-
.publish(gemspec_path) ⇒ Object
Publish the gem to rubygems.org.
-
.push(gem_path) ⇒ Object
Push the .gem at gem_path to rubygems.org.
- .version_published?(name, version) ⇒ Boolean
-
.version_published_gem_list?(name, version) ⇒ Boolean
Run ‘gem list NAME –remote` and check if version appears in the output.
-
.version_published_v1?(name, version) ⇒ Boolean
GET /api/v1/versions/name.json and check if version is in the list.
-
.version_published_v2?(name, version) ⇒ Boolean
GET /api/v2/rubygems/name/versions/version.json (follows redirects).
Class Method Details
.bump(gemspec_path) ⇒ Object
Bump the last digit of the version in the gemspec file (e.g. “0.1.0” -> “0.1.1”). Writes the file in place. Returns the new version string.
31 32 33 34 35 36 37 38 39 40 |
# File 'lib/rakit/gem.rb', line 31 def self.bump(gemspec_path) content = ::File.read(gemspec_path) content.sub!(/^(\s*s\.version\s*=\s*["'])([\d.]+)(["'])/) do segs = Regexp.last_match(2).split(".") segs[-1] = (segs[-1].to_i + 1).to_s "#{Regexp.last_match(1)}#{segs.join(".")}#{Regexp.last_match(3)}" end or raise "No s.version line found in #{gemspec_path}" ::File.write(gemspec_path, content) content[/s\.version\s*=\s*["']([^"']+)["']/, 1] end |
.http_get_following_redirects(uri, limit: 5) ⇒ Object
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
# File 'lib/rakit/gem.rb', line 144 def self.http_get_following_redirects(uri, limit: 5) raise ArgumentError, "redirect limit exceeded" if limit <= 0 require "net/http" response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https", open_timeout: 10, read_timeout: 10) do |http| request = Net::HTTP::Get.new(uri) request["User-Agent"] = "rakit (https://rubygems.org/gems/rakit)" http.request(request) end case response when Net::HTTPRedirection location = response["location"] next_uri = location.match?(/\Ahttps?:\/\//) ? URI(location) : URI.join(uri, location) http_get_following_redirects(next_uri, limit: limit - 1) else response end end |
.latest_remote_version(name) ⇒ Object
Return the latest version string for name on the remote, or nil on error.
93 94 95 96 97 98 99 100 101 102 103 104 105 |
# File 'lib/rakit/gem.rb', line 93 def self.latest_remote_version(name) out, _err, status = Open3.capture3("gem", "list", name, "--remote") return nil unless status.success? # Format: "name (1.0.0)" for latest only, or "name (1.0.0, 0.9.0)" with --all out.each_line do |line| next unless line.include?(name) if line =~ /\s*#{Regexp.escape(name)}\s*\(([^)]+)\)/ versions = Regexp.last_match(1).split(",").map(&:strip) return versions.max { |a, b| ::Gem::Version.new(a) <=> ::Gem::Version.new(b) } end end nil end |
.package(spec, out_dir) ⇒ Object
Build the gem for the given spec and move it into out_dir. Returns the path to the built .gem file.
12 13 14 15 16 17 |
# File 'lib/rakit/gem.rb', line 12 def self.package(spec, out_dir) FileUtils.mkdir_p(out_dir) gem_file = ::Gem::Package.build(spec) FileUtils.mv(gem_file, out_dir) ::File.join(out_dir, gem_file) end |
.parse_gem_basename(base) ⇒ Object
Parse “name-version” basename (no .gem) into [name, version]. Version is the last hyphen-separated segment that looks like a version (e.g. 0.1.5).
63 64 65 66 67 |
# File 'lib/rakit/gem.rb', line 63 def self.parse_gem_basename(base) # Match name (may contain hyphens) and version (digits and dots, optional pre-release suffix). m = base.match(/\A(.+)-(\d+(?:\.\d+)*(?:\.\w+)?)\z/) m ? [m[1], m[2]] : nil end |
.publish(gemspec_path) ⇒ Object
Publish the gem to rubygems.org. Loads the gemspec from gemspec_path and expects the .gem file in dirname(gemspec_path)/artifacts/. Run package first.
21 22 23 24 25 26 27 |
# File 'lib/rakit/gem.rb', line 21 def self.publish(gemspec_path) path = ::File.(gemspec_path) spec = ::Gem::Specification.load(path) out_dir = ::File.join(::File.dirname(path), "artifacts") gem_path = ::File.join(out_dir, "#{spec.full_name}.gem") push(gem_path) end |
.push(gem_path) ⇒ Object
Push the .gem at gem_path to rubygems.org. If that version is already published, warns and returns without pushing. Raises if the file is missing or if gem push fails.
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
# File 'lib/rakit/gem.rb', line 45 def self.push(gem_path) raise "Gem not found: #{gem_path}. Run rake package first." unless ::File.file?(gem_path) base = ::File.basename(gem_path, ".gem") name, version = parse_gem_basename(base) raise "Could not parse name/version from #{base}.gem" unless name && version if version_published?(name, version) warn "publish: Version #{version} of #{name} is already published on rubygems.org. Skipping push. Bump the version in the gemspec to publish again." return end success = system("gem", "push", gem_path) raise "gem push failed" unless success end |
.version_published?(name, version) ⇒ Boolean
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/rakit/gem.rb', line 69 def self.version_published?(name, version) # If a version >= this one is already published (e.g. latest is higher), treat as published. latest = latest_remote_version(name) return true if latest && ::Gem::Version.new(latest) >= ::Gem::Version.new(version) begin return true if version_published_gem_list?(name, version) rescue StandardError # try API fallbacks end begin return true if version_published_v2?(name, version) rescue StandardError # try v1 fallback end begin return true if version_published_v1?(name, version) rescue StandardError nil end false end |
.version_published_gem_list?(name, version) ⇒ Boolean
Run ‘gem list NAME –remote` and check if version appears in the output.
108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
# File 'lib/rakit/gem.rb', line 108 def self.version_published_gem_list?(name, version) out, err, status = Open3.capture3("gem", "list", name, "--remote") return false unless status.success? # Output format: "name (1.0.0, 0.9.0)" or "name (1.0.0)" combined = "#{out}#{err}" combined.each_line do |line| next unless line.include?(name) if line =~ /\s*#{Regexp.escape(name)}\s*\((.*)\)/ versions = Regexp.last_match(1).split(",").map(&:strip) return true if versions.include?(version) end end false end |
.version_published_v1?(name, version) ⇒ Boolean
GET /api/v1/versions/name.json and check if version is in the list
133 134 135 136 137 138 139 140 141 142 |
# File 'lib/rakit/gem.rb', line 133 def self.version_published_v1?(name, version) require "net/http" require "json" require "uri" uri = URI("https://rubygems.org/api/v1/versions/#{URI::DEFAULT_PARSER.escape(name)}.json") response = http_get_following_redirects(uri) return false unless response.is_a?(Net::HTTPSuccess) list = JSON.parse(response.body) list.is_a?(Array) && list.any? { |h| h["number"] == version } end |
.version_published_v2?(name, version) ⇒ Boolean
GET /api/v2/rubygems/name/versions/version.json (follows redirects)
124 125 126 127 128 129 130 |
# File 'lib/rakit/gem.rb', line 124 def self.version_published_v2?(name, version) require "net/http" require "uri" uri = URI("https://rubygems.org/api/v2/rubygems/#{URI::DEFAULT_PARSER.escape(name)}/versions/#{URI::DEFAULT_PARSER.escape(version)}.json") response = http_get_following_redirects(uri) response.is_a?(Net::HTTPSuccess) end |