simple-httpd 0.0.4 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/.rubocop.yml +9 -0
- data/.tm_properties +1 -0
- data/Gemfile +21 -1
- data/Makefile +9 -0
- data/README.md +87 -2
- data/Rakefile +5 -0
- data/VERSION +1 -1
- data/bin/simple-httpd +13 -0
- data/examples/README.md +41 -0
- data/examples/ex1/ex1_helpers.rb +5 -0
- data/examples/ex1/root.rb +11 -0
- data/examples/ex2/README.txt +1 -0
- data/examples/ex2/ex2_helpers.rb +5 -0
- data/examples/ex2/helpers.rb +15 -0
- data/examples/ex2/info.rb +4 -0
- data/examples/ex2/root.rb +3 -0
- data/examples/ex3/example_service.rb +13 -0
- data/examples/services/example_service.rb +25 -0
- data/examples/services/explicit_example_service.rb +18 -0
- data/examples/v2/api.js +1 -0
- data/examples/v2/jobs.rb +13 -0
- data/examples/v2/root.rb +3 -0
- data/examples/v2/v2_helpers.rb +5 -0
- data/lib/simple-service.rb +3 -0
- data/lib/simple/httpd.rb +99 -25
- data/lib/simple/httpd/base_controller.rb +2 -2
- data/lib/simple/httpd/base_controller/error_handling.rb +45 -17
- data/lib/simple/httpd/base_controller/json.rb +15 -8
- data/lib/simple/httpd/cli.rb +99 -0
- data/lib/simple/httpd/helpers.rb +54 -0
- data/lib/simple/httpd/mount_spec.rb +106 -0
- data/lib/simple/httpd/rack.rb +17 -0
- data/lib/simple/httpd/rack/dynamic_mount.rb +66 -0
- data/lib/simple/httpd/rack/merger.rb +28 -0
- data/lib/simple/httpd/rack/static_mount.rb +50 -0
- data/lib/simple/httpd/server.rb +69 -0
- data/lib/simple/httpd/service.rb +70 -0
- data/lib/simple/httpd/version.rb +1 -1
- data/lib/simple/service.rb +69 -0
- data/lib/simple/service/action.rb +78 -0
- data/lib/simple/service/context.rb +46 -0
- data/scripts/release +2 -0
- data/scripts/release.rb +91 -0
- data/simple-httpd.gemspec +9 -19
- data/spec/simple/httpd/base_controller/httpd_cors_spec.rb +15 -0
- data/spec/simple/httpd/base_controller/httpd_debug_spec.rb +11 -0
- data/spec/simple/httpd/base_controller/httpd_x_processing_copy.rb +15 -0
- data/spec/simple/httpd/base_spec.rb +16 -0
- data/spec/simple/httpd/dynamic_mounting_spec.rb +33 -0
- data/spec/simple/httpd/helpers_spec.rb +15 -0
- data/spec/simple/httpd/rspec_httpd_spec.rb +17 -0
- data/spec/simple/httpd/services/service_explicit_spec.rb +34 -0
- data/spec/simple/httpd/services/service_spec.rb +34 -0
- data/spec/simple/httpd/static_mounting_spec.rb +13 -0
- data/spec/spec_helper.rb +30 -6
- data/spec/support/004_simplecov.rb +3 -12
- metadata +61 -84
- data/lib/simple/httpd/app.rb +0 -84
- data/lib/simple/httpd/app/file_server.rb +0 -19
- data/spec/simple/httpd/version_spec.rb +0 -10
- data/tasks/release.rake +0 -104
@@ -0,0 +1,28 @@
|
|
1
|
+
# A simple file server middleware
|
2
|
+
class Simple::Httpd::Rack::Merger
|
3
|
+
Rack = Simple::Httpd::Rack
|
4
|
+
|
5
|
+
# returns an app that merges other apps
|
6
|
+
def self.build(apps)
|
7
|
+
return apps.first if apps.length == 1
|
8
|
+
|
9
|
+
new(apps)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def initialize(apps)
|
15
|
+
@apps = apps
|
16
|
+
end
|
17
|
+
|
18
|
+
public
|
19
|
+
|
20
|
+
def call(env)
|
21
|
+
@apps.each do |app|
|
22
|
+
status, body, headers = app.call(env)
|
23
|
+
return [status, body, headers] unless status == 404
|
24
|
+
end
|
25
|
+
|
26
|
+
Rack.error 404, "No such action"
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# A simple file server middleware
|
2
|
+
class Simple::Httpd::Rack::StaticMount
|
3
|
+
Rack = ::Simple::Httpd::Rack
|
4
|
+
|
5
|
+
EXTENSIONS = %w(.txt .md .js .css .png .jpeg .jpg)
|
6
|
+
|
7
|
+
def self.build(mount_point, path)
|
8
|
+
static_files = static_files(path)
|
9
|
+
return nil if static_files.empty?
|
10
|
+
|
11
|
+
::Simple::Httpd.logger.info do
|
12
|
+
"#{mount_point}: serving #{static_files.count} static file(s)"
|
13
|
+
end
|
14
|
+
|
15
|
+
new(path, static_files)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.static_files(path)
|
19
|
+
Dir.chdir(path) do
|
20
|
+
pattern = "**/*{" + EXTENSIONS.join(",") + "}"
|
21
|
+
Dir.glob(pattern)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader :mount_point, :path
|
26
|
+
|
27
|
+
def initialize(path, static_files)
|
28
|
+
@path = path
|
29
|
+
@static_files = Set.new(static_files)
|
30
|
+
end
|
31
|
+
|
32
|
+
def call(env)
|
33
|
+
request_path = env["PATH_INFO"]
|
34
|
+
if serve_file?(request_path)
|
35
|
+
file_path = request_path[1..-1]
|
36
|
+
env["PATH_INFO"] = file_path
|
37
|
+
file_server.call(env)
|
38
|
+
else
|
39
|
+
Rack.error 404, "No such file"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def file_server
|
44
|
+
@file_server ||= ::Rack::File.new(path)
|
45
|
+
end
|
46
|
+
|
47
|
+
def serve_file?(request_path)
|
48
|
+
@static_files.include?(request_path[1..-1])
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
class Simple::Httpd
|
2
|
+
module Server
|
3
|
+
extend self
|
4
|
+
|
5
|
+
module NullLogger # :nodoc:
|
6
|
+
extend self
|
7
|
+
|
8
|
+
def <<(msg); end
|
9
|
+
end
|
10
|
+
|
11
|
+
def listen!(app, environment: "development", host: nil, port:, logger: nil)
|
12
|
+
expect! app != nil
|
13
|
+
|
14
|
+
host ||= "127.0.0.1"
|
15
|
+
URI("http://#{host}:#{port}") # validate host and port
|
16
|
+
|
17
|
+
logger ||= ::Simple::Httpd.logger
|
18
|
+
|
19
|
+
prepare_logger!(logger)
|
20
|
+
logger.info "Starting httpd server on http://#{host}:#{port}/"
|
21
|
+
|
22
|
+
app = ::Rack::Lint.new(app) if environment != "production"
|
23
|
+
|
24
|
+
# re/AccessLog: the AccessLog setting points WEBrick's access logging to the
|
25
|
+
# NullLogger object.
|
26
|
+
#
|
27
|
+
# Instead we'll use a combination of Rack::CommonLogger (see Simple::Httpd.app),
|
28
|
+
# and sinatra's logger (see Simple::Httpd::BaseController).
|
29
|
+
::Rack::Server.start app: app,
|
30
|
+
Host: host,
|
31
|
+
Port: port,
|
32
|
+
environment: environment,
|
33
|
+
Logger: logger,
|
34
|
+
AccessLog: [[NullLogger, ""]]
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
# When Webrick is being shut down via SIGTERM - which we do at least during
|
40
|
+
# rspec-httpd triggered runs - it sends a fatal message to the logger. We catch
|
41
|
+
# it - to "downgrade" it to INFO - but we still abort.
|
42
|
+
def prepare_logger!(logger)
|
43
|
+
def logger.fatal(msg, &block)
|
44
|
+
if msg.is_a?(SignalException) && msg.signo == ::Signal.list["TERM"]
|
45
|
+
if %w(test development).include?(::Simple::Httpd.env)
|
46
|
+
info "Received SIGTERM: hard killing server (due to running in #{::Simple::Httpd.env.inspect} environment)"
|
47
|
+
Simple::Httpd::Server.exit!
|
48
|
+
else
|
49
|
+
info "Received SIGTERM: shutting down server..."
|
50
|
+
exit 1
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
super
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
public
|
59
|
+
|
60
|
+
def exit!(exit_status = 1)
|
61
|
+
# Run SimpleCov if exists, and if this is the PID that started SimpleCov in the first place.
|
62
|
+
if defined?(SimpleCov) && SimpleCov.pid == Process.pid
|
63
|
+
SimpleCov.process_result(SimpleCov.result, 0)
|
64
|
+
end
|
65
|
+
|
66
|
+
Kernel.exit! exit_status
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require "simple-service"
|
2
|
+
|
3
|
+
class Simple::Httpd::Service
|
4
|
+
module ControllerAdapter
|
5
|
+
def mount_service(service)
|
6
|
+
@service = service
|
7
|
+
|
8
|
+
instance_eval do
|
9
|
+
yield(service)
|
10
|
+
end
|
11
|
+
ensure
|
12
|
+
@service = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def get(path, opts = {}, &block)
|
16
|
+
service_route?("GET", path, opts, &block) || super
|
17
|
+
end
|
18
|
+
|
19
|
+
def post(path, opts = {}, &block)
|
20
|
+
service_route?("POST", path, opts, &block) || super
|
21
|
+
end
|
22
|
+
|
23
|
+
def put(path, opts = {}, &block)
|
24
|
+
service_route?("PUT", path, opts, &block) || super
|
25
|
+
end
|
26
|
+
|
27
|
+
def delete(path, opts = {}, &block)
|
28
|
+
service_route?("DELETE", path, opts, &block) || super
|
29
|
+
end
|
30
|
+
|
31
|
+
def head(path, opts = {}, &block)
|
32
|
+
service_route?("HEAD", path, opts, &block) || super
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def service_route?(verb, path, opts, &block)
|
38
|
+
return false unless @service
|
39
|
+
return false if block
|
40
|
+
return false unless opts.empty?
|
41
|
+
return false unless path.is_a?(Hash) && path.size == 1
|
42
|
+
|
43
|
+
path, action_name = *path.first
|
44
|
+
|
45
|
+
# Verify existence of this action.
|
46
|
+
@service.fetch_action(action_name)
|
47
|
+
|
48
|
+
# get service reference into binding, to make it available for the route
|
49
|
+
# definition.
|
50
|
+
service = @service
|
51
|
+
|
52
|
+
# define sinatra route.
|
53
|
+
route(verb, path) do
|
54
|
+
result = service.call(action_name, parsed_body, params, context: context)
|
55
|
+
json(result)
|
56
|
+
end
|
57
|
+
|
58
|
+
true
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
module Helpers
|
63
|
+
def context
|
64
|
+
@context ||= ::Simple::Service::Context.new
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
::Simple::Httpd::BaseController.extend(ControllerAdapter)
|
69
|
+
::Simple::Httpd::BaseController.helpers(Helpers)
|
70
|
+
end
|
data/lib/simple/httpd/version.rb
CHANGED
@@ -0,0 +1,69 @@
|
|
1
|
+
module Simple::Service
|
2
|
+
class ArgumentError < ::ArgumentError
|
3
|
+
end
|
4
|
+
end
|
5
|
+
|
6
|
+
require_relative "service/action"
|
7
|
+
require_relative "service/context"
|
8
|
+
|
9
|
+
module Simple::Service
|
10
|
+
def self.included(klass)
|
11
|
+
klass.extend ClassMethods
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.context
|
15
|
+
Thread.current[:"Simple::Service.context"]
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.with_context(ctx)
|
19
|
+
old_ctx = Thread.current[:"Simple::Service.context"]
|
20
|
+
Thread.current[:"Simple::Service.context"] = ctx
|
21
|
+
yield
|
22
|
+
ensure
|
23
|
+
Thread.current[:"Simple::Service.context"] = old_ctx
|
24
|
+
end
|
25
|
+
|
26
|
+
module ClassMethods
|
27
|
+
def actions
|
28
|
+
@actions ||= Action.build_all(service_module: self)
|
29
|
+
end
|
30
|
+
|
31
|
+
def build_service_instance
|
32
|
+
service_instance = Object.new
|
33
|
+
service_instance.extend self
|
34
|
+
service_instance
|
35
|
+
end
|
36
|
+
|
37
|
+
def fetch_action(action_name)
|
38
|
+
actions.fetch(action_name) do
|
39
|
+
informal = "service #{self} has these actions: #{actions.keys.sort.map(&:inspect).join(", ")}"
|
40
|
+
raise "No such action #{action_name.inspect}; #{informal}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def call(action_name, arguments, params, context: nil)
|
45
|
+
::Simple::Service.with_context(context) do
|
46
|
+
fetch_action(action_name).invoke(arguments, params)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Resolves a service by name. Returns nil if the name does not refer to a service,
|
52
|
+
# or the service module otherwise.
|
53
|
+
def self.resolve(str)
|
54
|
+
return unless str =~ /^[A-Z][A-Za-z0-9_]*(::[A-Z][A-Za-z0-9_]*)*$/
|
55
|
+
|
56
|
+
service = resolve_constant(str)
|
57
|
+
|
58
|
+
return unless service.is_a?(Module)
|
59
|
+
return unless service.include?(::Simple::Service)
|
60
|
+
|
61
|
+
service
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.resolve_constant(str)
|
65
|
+
const_get(str)
|
66
|
+
rescue NameError
|
67
|
+
nil
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Simple::Service
|
2
|
+
class Action
|
3
|
+
ArgumentError = ::Simple::Service::ArgumentError
|
4
|
+
|
5
|
+
IDENTIFIER_PATTERN = "[a-z][a-z0-9_]*"
|
6
|
+
IDENTIFIER_REGEXP = Regexp.compile("\\A#{IDENTIFIER_PATTERN}\\z")
|
7
|
+
|
8
|
+
def self.build_all(service_module:)
|
9
|
+
service_module.public_instance_methods(false)
|
10
|
+
.grep(IDENTIFIER_REGEXP)
|
11
|
+
.inject({}) { |hsh, name| hsh.update name => Action.new(service_module, name) }
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :service
|
15
|
+
attr_reader :name
|
16
|
+
attr_reader :arguments
|
17
|
+
attr_reader :parameters
|
18
|
+
|
19
|
+
def initialize(service, name)
|
20
|
+
instance_method = service.instance_method(name)
|
21
|
+
|
22
|
+
@service = service
|
23
|
+
@name = name
|
24
|
+
@arguments = []
|
25
|
+
@parameters = []
|
26
|
+
|
27
|
+
instance_method.parameters.each do |kind, parameter_name|
|
28
|
+
case kind
|
29
|
+
when :req, :opt then @arguments << parameter_name
|
30
|
+
when :keyreq, :key then @parameters << parameter_name
|
31
|
+
else
|
32
|
+
raise ArgumentError, "#{full_name}: no support for #{kind.inspect} arguments, w/parameter #{parameter_name}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# build a service_instance and run the action, with arguments constructed from
|
38
|
+
# args_hsh and params_hsh
|
39
|
+
def invoke(args_hsh, params_hsh)
|
40
|
+
args_hsh ||= {}
|
41
|
+
params_hsh ||= {}
|
42
|
+
|
43
|
+
# build arguments array
|
44
|
+
args = extract_arguments(args_hsh)
|
45
|
+
args << extract_parameters(params_hsh) unless parameters.empty?
|
46
|
+
|
47
|
+
# run the action. Note: public_send is only
|
48
|
+
# an extra safeguard; since actions are already built off public methods
|
49
|
+
# there should be no way to call a private service method.
|
50
|
+
service_instance = service.build_service_instance
|
51
|
+
service_instance.public_send(@name, *args)
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def extract_arguments(args_hsh)
|
57
|
+
arguments.map do |name|
|
58
|
+
args_hsh.fetch(name.to_s) { raise ArgumentError, "Missing argument in request body: #{name}" }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def extract_parameters(params_hsh)
|
63
|
+
# Note: in contrast to arguments that are being read from the body parameters that
|
64
|
+
# are not submitted are being ignored (and filled in by +nil+).
|
65
|
+
#
|
66
|
+
# Note 2: The parameter names **must** be Symbols, not Strings, otherwise
|
67
|
+
# the service_instance.send invocation later would not fill in keyword
|
68
|
+
# arguments from the parameters hash.
|
69
|
+
parameters.inject({}) do |hsh, parameter|
|
70
|
+
hsh.update parameter => params_hsh.fetch(parameter, nil)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def full_name
|
75
|
+
"#{service}##{name}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Simple::Service
|
2
|
+
class Context
|
3
|
+
def initialize
|
4
|
+
@hsh = {}
|
5
|
+
end
|
6
|
+
|
7
|
+
IDENTIFIER_PATTERN = "[a-z][a-z0-9_]*"
|
8
|
+
IDENTIFIER_REGEXP = Regexp.compile("\\A#{IDENTIFIER_PATTERN}\\z")
|
9
|
+
ASSIGNMENT_REGEXP = Regexp.compile("\\A(#{IDENTIFIER_PATTERN})=\\z")
|
10
|
+
|
11
|
+
def [](key)
|
12
|
+
key = key.to_sym
|
13
|
+
@hsh[key]
|
14
|
+
end
|
15
|
+
|
16
|
+
def []=(key, value)
|
17
|
+
key = key.to_sym
|
18
|
+
existing_value = @hsh[key]
|
19
|
+
|
20
|
+
unless existing_value.nil? || existing_value == value
|
21
|
+
raise "Cannot overwrite existing context setting #{key.inspect}"
|
22
|
+
end
|
23
|
+
|
24
|
+
@hsh[key] = value
|
25
|
+
end
|
26
|
+
|
27
|
+
def method_missing(sym, *args, &block)
|
28
|
+
if block
|
29
|
+
super
|
30
|
+
elsif args.count == 0 && sym =~ IDENTIFIER_REGEXP
|
31
|
+
self[sym]
|
32
|
+
elsif args.count == 1 && sym =~ ASSIGNMENT_REGEXP
|
33
|
+
self[$1.to_sym] = args.first
|
34
|
+
else
|
35
|
+
super
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def respond_to_missing?(sym, include_private = false)
|
40
|
+
return true if IDENTIFIER_REGEXP.maptch?(sym)
|
41
|
+
return true if ASSIGNMENT_REGEXP.maptch?(sym)
|
42
|
+
|
43
|
+
super
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/scripts/release
ADDED
data/scripts/release.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# -- helpers ------------------------------------------------------------------
|
4
|
+
|
5
|
+
def sys(cmd)
|
6
|
+
STDERR.puts "> #{cmd}"
|
7
|
+
system cmd
|
8
|
+
return true if $?.success?
|
9
|
+
|
10
|
+
STDERR.puts "> #{cmd} returned with exitstatus #{$?.exitstatus}"
|
11
|
+
$?.success?
|
12
|
+
end
|
13
|
+
|
14
|
+
def sys!(cmd, error: nil)
|
15
|
+
return true if sys(cmd)
|
16
|
+
STDERR.puts error if error
|
17
|
+
exit 1
|
18
|
+
end
|
19
|
+
|
20
|
+
def die!(msg)
|
21
|
+
STDERR.puts msg
|
22
|
+
exit 1
|
23
|
+
end
|
24
|
+
|
25
|
+
ROOT = File.expand_path("#{File.dirname(__FILE__)}/..")
|
26
|
+
|
27
|
+
GEMSPEC = Dir.glob("*.gemspec").first || die!("Missing gemspec file.")
|
28
|
+
|
29
|
+
# -- Version reading and bumping ----------------------------------------------
|
30
|
+
|
31
|
+
module Version
|
32
|
+
extend self
|
33
|
+
|
34
|
+
VERSION_FILE = "#{Dir.getwd}/VERSION"
|
35
|
+
|
36
|
+
def read_version
|
37
|
+
version = File.exist?(VERSION_FILE) ? File.read(VERSION_FILE) : "0.0.1"
|
38
|
+
version.chomp!
|
39
|
+
raise "Invalid version number in #{VERSION_FILE}" unless version =~ /^\d+\.\d+\.\d+$/
|
40
|
+
version
|
41
|
+
end
|
42
|
+
|
43
|
+
def auto_version_bump
|
44
|
+
old_version_number = read_version
|
45
|
+
old = old_version_number.split('.')
|
46
|
+
|
47
|
+
current = old[0..-2] << old[-1].next
|
48
|
+
current.join('.')
|
49
|
+
end
|
50
|
+
|
51
|
+
def bump_version
|
52
|
+
next_version = ENV["VERSION"] || auto_version_bump
|
53
|
+
File.open(VERSION_FILE, "w") { |io| io.write next_version }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# -- check, bump, release a new gem version -----------------------------------
|
58
|
+
|
59
|
+
Dir.chdir ROOT
|
60
|
+
$BASE_BRANCH = ENV['BRANCH'] || 'master'
|
61
|
+
|
62
|
+
# ENV["BUNDLE_GEMFILE"] = "#{Dir.getwd}/Gemfile"
|
63
|
+
# sys! "bundle install"
|
64
|
+
|
65
|
+
sys! "git diff --exit-code > /dev/null", error: 'There are unstaged changes in your working directory'
|
66
|
+
sys! "git diff --cached --exit-code > /dev/null", error: 'There are staged but uncommitted changes'
|
67
|
+
|
68
|
+
sys! "git checkout #{$BASE_BRANCH}"
|
69
|
+
sys! "git pull"
|
70
|
+
|
71
|
+
Version.bump_version
|
72
|
+
version = Version.read_version
|
73
|
+
|
74
|
+
sys! "git add VERSION"
|
75
|
+
sys! "git commit -m \"bump gem to v#{version}\""
|
76
|
+
sys! "git tag -a v#{version} -m \"Tag #{version}\""
|
77
|
+
|
78
|
+
sys! "gem build #{GEMSPEC}"
|
79
|
+
|
80
|
+
sys! "git push origin #{$BASE_BRANCH}"
|
81
|
+
sys! 'git push --tags --force'
|
82
|
+
sys! "gem push #{Dir.glob('*.gem').first}"
|
83
|
+
|
84
|
+
sys! "mkdir -p pkg"
|
85
|
+
sys! "mv *.gem pkg"
|
86
|
+
|
87
|
+
STDERR.puts <<-MSG
|
88
|
+
================================================================================
|
89
|
+
Thank you for releasing a new gem version. You made my day.
|
90
|
+
================================================================================
|
91
|
+
MSG
|