Skip to content

Commit

Permalink
add gem release --github
Browse files Browse the repository at this point in the history
  • Loading branch information
svenfuchs authored and PikachuEXE committed Jul 5, 2019
1 parent da53b88 commit 37b1fcd
Show file tree
Hide file tree
Showing 11 changed files with 285 additions and 8 deletions.
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ group :test do
gem 'rspec'
gem 'simplecov'
gem 'coveralls'
gem 'webmock'
end
1 change: 1 addition & 0 deletions lib/gem/release/cmds.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require 'gem/release/cmds/bootstrap'
require 'gem/release/cmds/bump'
require 'gem/release/cmds/gemspec'
require 'gem/release/cmds/github'
require 'gem/release/cmds/release'
require 'gem/release/cmds/runner'
require 'gem/release/cmds/tag'
Expand Down
101 changes: 101 additions & 0 deletions lib/gem/release/cmds/github.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
require 'gem/release/cmds/base'
require 'gem/release/context/github'

module Gem
module Release
module Cmds
class Github < Base
summary "Creates a GitHub release for the current version."

description <<-str.split("\n").map(&:lstrip).join("\n")
Creates a GitHub release for the current version.
Requires a tag `v[version]` to be present or --tag to be given.
str

DEFAULTS = {
tag: false,
}

DESCR = {
tag: 'Shortcut for running the `gem tag` command',
name: 'Name of the release (defaults to "[gem name] [version]")',
descr: 'Description of the release',
repo: "Full name of the repository on GitHub, e.g. svenfuchs/gem-release (defaults to the repo name from the gemspec's homepage if this is a GitHub URL)",
token: 'GitHub OAuth token'
}

opt '-d', '--description DESCRIPTION', descr(:desc) do |value|
opts[:descr] = value
end

opt '-r', '--repo REPO', descr(:repo) do |value|
opts[:repo] = value
end

opt '-t', '--token TOKEN', descr(:token) do |value|
opts[:token] = value
end

MSGS = {
release: 'Creating GitHub release for %s version %s',
no_tag: 'Tag %s does not exist. Run `gem tag` or pass `--tag`.',
no_repo: 'Could not determine the repository name. Please pass `--repo REPO`, or set homepage or metadata[:github_url] to the GitHub repository URL in the gemspec.'
}

def run
in_gem_dirs do
announce :release, gem.name, tag_name
validate
release
end
end

private

def validate
abort :no_tag, tag_name unless tagged?
abort :no_token unless token
end

def tagged?
git.tags.include?(tag_name)
end

def release
Context::Github.new(repo, data).release
end

def data
{
version: gem.version,
tag_name: tag_name,
name: "#{gem.name} #{tag_name}",
descr: descr,
token: token
}
end

def tag_name
"v#{gem.version}"
end

def repo
opts[:repo] || repo_from(gem.spec.homepage) || repo_from(gem.spec.metadata[:github_url]) || abort(:no_repo)
end

def repo_from(url)
url && url =~ %r(https://github\.com/(.*/.*)) && $1
end

def token
opts[:token]
end

def descr
opts[:descr]
end
end
end
end
end
22 changes: 16 additions & 6 deletions lib/gem/release/cmds/release.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ class Release < Base
host: 'Push to a compatible host other than rubygems.org',
key: 'Use the API key from ~/.gem/credentials',
tag: 'Shortcut for running the `gem tag` command',
push: 'Push tag to the remote git repository',
push: 'Push tag to the remote git repository',
github: 'Create a GitHub release',
recurse: 'Recurse into directories that contain gemspec files'
}.freeze

Expand All @@ -48,6 +49,10 @@ class Release < Base
opts[:tag] = value
end

opt '-g', '--github', descr(:github) do |value|
opts[:github] = value
end

opt '-p', '--push', descr(:push) do |value|
opts[:push] = value
end
Expand All @@ -73,7 +78,8 @@ def run
validate
release
end
tag if opts[:tag]
tag if opts[:tag]
github if opts[:github]
end

private
Expand All @@ -90,10 +96,6 @@ def release
cleanup
end

def tag
Tag.new(context, args, opts).run
end

def build
gem_cmd :build, gem.spec_filename
end
Expand All @@ -102,6 +104,14 @@ def push
gem_cmd :push, gem.filename, *push_args
end

def tag
Tag.new(context, args, opts).run
end

def github
Github.new(context, args, opts).run
end

def push_args
args = [:key, :host].map { |opt| ["--#{opt}", opts[opt]] if opts[opt] }
args << "--quiet" if quiet?
Expand Down
12 changes: 10 additions & 2 deletions lib/gem/release/context/gemspec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ def initialize(*)
@filename = name && "#{name}.gemspec" || filenames.first
end

def exists?
filename && File.exist?(filename)
end

def gem_name
gemspec.name if gemspec
end
Expand All @@ -21,8 +25,12 @@ def gem_filename
gemspec.file_name if gemspec
end

def exists?
filename && File.exist?(filename)
def metadata
gemspec && gemspec.metadata || {}
end

def homepage
gemspec.homepage if gemspec
end

private
Expand Down
57 changes: 57 additions & 0 deletions lib/gem/release/context/github.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
require 'json'
require 'gem/release/helper/http'

module Gem
module Release
class Context
class Github
include Helper::Http

URL = 'https://api.github.com/repos/%s/releases'

MSGS = {
error: 'GitHub returned %s (body: %p)'
}

attr_reader :repo, :data

def initialize(repo, data)
@repo = repo
@data = data
end

def release
resp = post(url, body, headers)
status, body = resp
raise Abort, MSGS[:error] % [status, body] unless status == 200
end

private

def url
URL % repo
end

def body
JSON.dump(
tag_name: data[:tag_name],
name: data[:name],
body: data[:descr],
prerelease: pre?(data[:version])
)
end

def headers
{
'User-Agent' => "gem-release/v#{::Gem::Release::VERSION}",
'Content-Type' => 'text/json'
}
end

def pre?(version)
Version::Number.new(version).pre?
end
end
end
end
end
37 changes: 37 additions & 0 deletions lib/gem/release/helper/http.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
require 'net/http'
require 'uri'

module Gem
module Release
module Helper
module Http
class Client < Struct.new(:method, :url, :body, :headers)
def request
req = const.new(uri.request_uri, headers)
req.body = body if body
resp = client.request(req)
[resp.code.to_i, resp.body]
end

private

def uri
@uri ||= URI.parse(url)
end

def client
Net::HTTP.new(uri.host, uri.port)
end

def const
Net::HTTP.const_get(method.to_s.capitalize)
end
end

def post(url, body = nil, headers = {})
Client.new(:post, url, body, headers).request
end
end
end
end
end
4 changes: 4 additions & 0 deletions lib/gem/release/version/number.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ def bump
parts.join(stage_delim)
end

def pre?
!!parts[4]
end

private

def specific?
Expand Down
35 changes: 35 additions & 0 deletions spec/gem/release/cmds/github_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
describe Gem::Release::Cmds::Github do
let(:args) { [] }
let(:opts) { { repo: 'foo/bar', token: 'token' } }
let(:body) { '{"tag_name":"v1.0.0","name":"foo-bar v1.0.0","body":null,"prerelease":false}' }
let(:status) { 200 }

cwd 'foo-bar'
gemspec 'foo-bar'

before { context.git.tags << 'v1.0.0' }
before { stub_request(:post, 'http://api.github.com:443/repos/foo/bar/releases').with(body: body).to_return(status: status) }

describe 'by default' do
run_cmd

it { should_not run_cmd 'git push --tags origin' }
it { should output 'Creating GitHub release for foo-bar version v1.0.0' }
it { should output 'All is good, thanks my friend.' }
end

describe 'not tagged' do
before { context.git.tags.clear }
it { expect { run }.to raise_error('Tag v1.0.0 does not exist. Run `gem tag` or pass `--tag`. Aborting.') }
end

describe 'invalid token' do
let(:status) { 401 }
it { expect { run }.to raise_error('GitHub returned 401 (body: "")') }
end

describe 'not found' do
let(:status) { 404 }
it { expect { run }.to raise_error('GitHub returned 404 (body: "")') }
end
end
20 changes: 20 additions & 0 deletions spec/gem/release/cmds/release_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,26 @@
it { should run_cmd 'git tag -am "tag v1.0.0" v1.0.0' }
end

describe 'given --github' do
let(:opts) { { github: true, repo: 'foo/bar', token: 'token' } }

let(:body) { '{"tag_name":"v1.0.0","name":"foo-bar v1.0.0","body":null,"prerelease":false}' }
let(:status) { 200 }

gemspec 'foo-bar'

before { context.git.tags << 'v1.0.0' }
before { stub_request(:post, 'http://api.github.com:443/repos/foo/bar/releases').with(body: body).to_return(status: status) }

describe 'by default' do
run_cmd

it { should_not run_cmd 'git push --tags origin' }
it { should output 'Creating GitHub release for foo-bar version v1.0.0' }
it { should output 'All is good, thanks my friend.' }
end
end

describe 'given --quiet' do
let(:opts) { { quiet: true } }

Expand Down
3 changes: 3 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
require 'support/now'
require 'support/run'

require 'webmock'
require 'webmock/rspec'

ENV.delete_if { |key, _| key.start_with?('GEM_RELEASE') }

Kernel.send(:undef_method, :system)
Expand Down

0 comments on commit 37b1fcd

Please sign in to comment.