hoboken 0.0.1 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/dependabot.yml +7 -0
- data/.github/workflows/ruby.yml +28 -0
- data/.rubocop.yml +33 -0
- data/.ruby-version +1 -0
- data/Gemfile +2 -0
- data/IDEAS.md +57 -0
- data/LICENSE.txt +1 -1
- data/README.md +21 -10
- data/Rakefile +14 -6
- data/bin/hoboken +2 -1
- data/hoboken.gemspec +53 -16
- data/lib/hoboken.rb +110 -23
- data/lib/hoboken/actions.rb +10 -6
- data/lib/hoboken/add_ons/github_action.rb +14 -0
- data/lib/hoboken/add_ons/heroku.rb +8 -23
- data/lib/hoboken/add_ons/internationalization.rb +10 -6
- data/lib/hoboken/add_ons/metrics.rb +39 -14
- data/lib/hoboken/add_ons/omniauth.rb +114 -47
- data/lib/hoboken/add_ons/rubocop.rb +40 -0
- data/lib/hoboken/add_ons/sequel.rb +76 -47
- data/lib/hoboken/add_ons/sprockets.rb +67 -65
- data/lib/hoboken/add_ons/travis.rb +6 -2
- data/lib/hoboken/add_ons/twbs.rb +80 -0
- data/lib/hoboken/generate.rb +112 -38
- data/lib/hoboken/templates/Gemfile.erb.tt +33 -10
- data/lib/hoboken/templates/README.md.tt +105 -35
- data/lib/hoboken/templates/Rakefile.tt +10 -22
- data/lib/hoboken/templates/classic.rb.tt +35 -8
- data/lib/hoboken/templates/config.ru.tt +5 -3
- data/lib/hoboken/templates/console.sh +5 -0
- data/lib/hoboken/templates/db.rb.tt +24 -0
- data/lib/hoboken/templates/github_action.tt +28 -0
- data/lib/hoboken/templates/gitignore +8 -1
- data/lib/hoboken/templates/metrics.rake.tt +10 -9
- data/lib/hoboken/templates/modular.rb.tt +40 -11
- data/lib/hoboken/templates/puma.rb.tt +21 -0
- data/lib/hoboken/templates/rspec.rake.tt +5 -0
- data/lib/hoboken/templates/rubocop.yml.tt +31 -0
- data/lib/hoboken/templates/sequel.rake +6 -4
- data/lib/hoboken/templates/server.sh +12 -0
- data/lib/hoboken/templates/setup.sh +7 -0
- data/lib/hoboken/templates/spec/app_spec.rb.tt +15 -0
- data/lib/hoboken/templates/spec/rack_matchers.rb.tt +56 -0
- data/lib/hoboken/templates/spec/spec_helper.rb.tt +41 -0
- data/lib/hoboken/templates/sprockets.rake +13 -7
- data/lib/hoboken/templates/sprockets_chain.rb +7 -3
- data/lib/hoboken/templates/sprockets_helper.rb +14 -10
- data/lib/hoboken/templates/support/rack_helpers.rb.tt +55 -0
- data/lib/hoboken/templates/support/rack_test_assertions.rb.tt +111 -0
- data/lib/hoboken/templates/test/test_helper.rb.tt +38 -27
- data/lib/hoboken/templates/test/unit/app_test.rb.tt +11 -3
- data/lib/hoboken/templates/test_unit.rake.tt +18 -0
- data/lib/hoboken/templates/views/index.erb.tt +10 -3
- data/lib/hoboken/templates/views/layout.erb.tt +4 -1
- data/lib/hoboken/version.rb +3 -1
- data/test/fixtures/Gemfile +3 -3
- data/test/fixtures/Gemfile.pristine +3 -2
- data/test/integration/add_on_test.rb +399 -136
- data/test/integration/generate_test.rb +170 -38
- data/test/test_helper.rb +54 -23
- data/test/unit/hoboken_actions_test.rb +70 -61
- metadata +441 -16
- data/.travis.yml +0 -5
- data/lib/hoboken/templates/test/support/rack_test_assertions.rb.tt +0 -92
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
workers_count = Integer(ENV['WEB_CONCURRENCY'] || 1)
|
4
|
+
threads_count = Integer(ENV['MAX_THREADS'] || 5)
|
5
|
+
threads threads_count, threads_count
|
6
|
+
|
7
|
+
port ENV['PORT'] || 9292
|
8
|
+
environment ENV['RACK_ENV'] || 'production'
|
9
|
+
|
10
|
+
if workers_count > 1
|
11
|
+
preload_app!
|
12
|
+
workers workers_count
|
13
|
+
|
14
|
+
# rubocop:disable Lint/EmptyBlock
|
15
|
+
before_fork do
|
16
|
+
end
|
17
|
+
|
18
|
+
on_worker_boot do
|
19
|
+
end
|
20
|
+
# rubocop:enable Lint/EmptyBlock
|
21
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require:
|
2
|
+
- rubocop-rake
|
3
|
+
<% if rspec? -%>
|
4
|
+
- rubocop-rspec
|
5
|
+
<% end -%>
|
6
|
+
|
7
|
+
AllCops:
|
8
|
+
Exclude:
|
9
|
+
- 'bin/*'
|
10
|
+
- 'tmp/*'
|
11
|
+
- 'vendor/bundle/**/*'
|
12
|
+
NewCops: enable
|
13
|
+
TargetRubyVersion: <%= RUBY_VERSION %>
|
14
|
+
|
15
|
+
Layout/SpaceAroundEqualsInParameterDefault:
|
16
|
+
EnforcedStyle: no_space
|
17
|
+
|
18
|
+
Metrics/ClassLength:
|
19
|
+
Max: 150
|
20
|
+
|
21
|
+
Metrics/MethodLength:
|
22
|
+
Max: 15
|
23
|
+
|
24
|
+
Layout/LineLength:
|
25
|
+
Max: 90
|
26
|
+
|
27
|
+
Style/BlockDelimiters:
|
28
|
+
EnforcedStyle: braces_for_chaining
|
29
|
+
|
30
|
+
Style/YodaCondition:
|
31
|
+
EnforcedStyle: require_for_equality_operators_only
|
@@ -1,19 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
namespace :db do
|
2
4
|
require 'sequel'
|
3
5
|
|
4
6
|
Sequel.extension :migration
|
5
|
-
|
7
|
+
db = Sequel.connect(ENV['DATABASE_URL'] || 'sqlite://db/development.db')
|
6
8
|
|
7
9
|
desc 'Migrate the database to latest version'
|
8
10
|
task :migrate do
|
9
|
-
Sequel::Migrator.run(
|
11
|
+
Sequel::Migrator.run(db, 'db/migrate')
|
10
12
|
puts '<= db:migrate executed'
|
11
13
|
end
|
12
14
|
|
13
15
|
desc 'Perform migration reset (full erase and migration up)'
|
14
16
|
task :reset do
|
15
|
-
Sequel::Migrator.run(
|
16
|
-
Sequel::Migrator.run(
|
17
|
+
Sequel::Migrator.run(db, 'db/migrate', target: 0)
|
18
|
+
Sequel::Migrator.run(db, 'db/migrate')
|
17
19
|
puts '<= db:reset executed'
|
18
20
|
end
|
19
21
|
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
set -euo pipefail
|
3
|
+
IFS=$'\n\t'
|
4
|
+
|
5
|
+
if gem list foreman -i
|
6
|
+
then
|
7
|
+
echo 'Foreman detected... starting server with Procfile'
|
8
|
+
rerun --background foreman start
|
9
|
+
else
|
10
|
+
echo 'Foreman not installed... starting server with rackup'
|
11
|
+
rerun --background rackup
|
12
|
+
fi
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rubocop:disable RSpec/DescribeClass
|
4
|
+
RSpec.describe 'GET /', rack: true do
|
5
|
+
before { get '/' }
|
6
|
+
|
7
|
+
it { expect(last_response).to have_http_status(:ok) }
|
8
|
+
<% if options[:api_only] -%>
|
9
|
+
it { expect(last_response).to have_content_type(:json) }
|
10
|
+
<% else -%>
|
11
|
+
it { expect(last_response).to have_content_type(:html) }
|
12
|
+
<% end -%>
|
13
|
+
it { expect(last_response.body).to include('Smoke test successful!') }
|
14
|
+
end
|
15
|
+
# rubocop:enable RSpec/DescribeClass
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec::Matchers.define :have_http_status do |expected|
|
4
|
+
match do |response|
|
5
|
+
response.status == response_codes[expected]
|
6
|
+
end
|
7
|
+
|
8
|
+
failure_message do |response|
|
9
|
+
'expected last response status to be ' \
|
10
|
+
"#{response_codes.fetch(expected, 'unknown')}, " \
|
11
|
+
"but was #{response.status}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def response_codes
|
15
|
+
{
|
16
|
+
ok: 200,
|
17
|
+
not_authorized: 401,
|
18
|
+
not_found: 404,
|
19
|
+
redirect: 302
|
20
|
+
}.freeze
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
RSpec::Matchers.define :have_content_type do |expected|
|
25
|
+
match do |response|
|
26
|
+
response.content_type == content_types[expected]
|
27
|
+
end
|
28
|
+
|
29
|
+
failure_message do |response|
|
30
|
+
'expected last response to have content type ' \
|
31
|
+
"'#{content_types.fetch(expected, 'unknown')}', " \
|
32
|
+
"but was '#{response.content_type}'"
|
33
|
+
end
|
34
|
+
|
35
|
+
def content_types
|
36
|
+
{
|
37
|
+
html: 'text/html;charset=utf-8',
|
38
|
+
json: 'application/json'
|
39
|
+
}.freeze
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
RSpec::Matchers.define :redirect_to do |expected|
|
44
|
+
match do |response|
|
45
|
+
path(response) == expected
|
46
|
+
end
|
47
|
+
|
48
|
+
failure_message do |response|
|
49
|
+
"expected last response to redirect to '#{expected}', " \
|
50
|
+
"but it redirected to '#{path(response)}'"
|
51
|
+
end
|
52
|
+
|
53
|
+
def path(response)
|
54
|
+
URI(response.location).path
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
ENV['RACK_ENV'] = 'test'
|
4
|
+
|
5
|
+
require 'bundler/setup'
|
6
|
+
|
7
|
+
require 'warning'
|
8
|
+
|
9
|
+
# Ignore all warnings in Gem dependencies
|
10
|
+
Gem.path.each { |path| Warning.ignore(//, path) }
|
11
|
+
|
12
|
+
require 'dotenv'
|
13
|
+
require 'rack/test'
|
14
|
+
require 'support/rack_helpers'
|
15
|
+
|
16
|
+
require_relative '../app'
|
17
|
+
|
18
|
+
Dotenv.load
|
19
|
+
|
20
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
21
|
+
RSpec.configure do |config|
|
22
|
+
config.include RackHelpers, rack: true
|
23
|
+
|
24
|
+
config.expect_with :rspec do |expectations|
|
25
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
26
|
+
end
|
27
|
+
|
28
|
+
config.mock_with :rspec do |mocks|
|
29
|
+
mocks.verify_partial_doubles = true
|
30
|
+
end
|
31
|
+
|
32
|
+
config.default_formatter = 'doc' if config.files_to_run.one?
|
33
|
+
config.disable_monkey_patching!
|
34
|
+
config.example_status_persistence_file_path = 'spec/examples.txt'
|
35
|
+
config.filter_run_when_matching :focus
|
36
|
+
config.order = :random
|
37
|
+
config.shared_context_metadata_behavior = :apply_to_host_groups
|
38
|
+
config.warnings = true
|
39
|
+
|
40
|
+
Kernel.srand config.seed
|
41
|
+
end
|
@@ -1,33 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rubocop:disable Metrics/BlockLength
|
1
4
|
namespace :assets do
|
2
5
|
require 'sprockets'
|
3
6
|
require 'uglifier'
|
4
7
|
require 'yui/compressor'
|
5
|
-
sprockets = Sprockets::Environment.new { |env| env.logger = Logger.new(
|
8
|
+
sprockets = Sprockets::Environment.new { |env| env.logger = Logger.new($stdout) }
|
6
9
|
sprockets.css_compressor = YUI::CssCompressor.new
|
7
|
-
sprockets.js_compressor = :
|
10
|
+
sprockets.js_compressor = Uglifier.new(harmony: true)
|
8
11
|
|
9
|
-
%w
|
12
|
+
%w[assets vendor].each do |f|
|
10
13
|
sprockets.append_path File.expand_path("../../#{f}", __FILE__)
|
11
14
|
end
|
12
15
|
|
13
|
-
output_path = File.expand_path('
|
16
|
+
output_path = File.expand_path('../public', __dir__)
|
14
17
|
|
18
|
+
desc 'Pre-compile CSS assets'
|
15
19
|
task :precompile_css do
|
16
20
|
asset = sprockets['styles.css']
|
17
21
|
outfile = Pathname.new(output_path).join('css/styles.css')
|
18
22
|
FileUtils.mkdir_p outfile.dirname
|
19
23
|
asset.write_to(outfile)
|
20
|
-
puts
|
24
|
+
puts 'successfully compiled css assets'
|
21
25
|
end
|
22
26
|
|
27
|
+
desc 'Pre-compile JavaScript assets'
|
23
28
|
task :precompile_js do
|
24
29
|
asset = sprockets['app.js']
|
25
30
|
outfile = Pathname.new(output_path).join('js/app.js')
|
26
31
|
FileUtils.mkdir_p outfile.dirname
|
27
32
|
asset.write_to(outfile)
|
28
|
-
puts
|
33
|
+
puts 'successfully compiled javascript assets'
|
29
34
|
end
|
30
35
|
|
31
36
|
desc 'precompile all assets'
|
32
|
-
task :
|
37
|
+
task precompile: %i[precompile_css precompile_js]
|
33
38
|
end
|
39
|
+
# rubocop:enable Metrics/BlockLength
|
@@ -1,6 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'sprockets'
|
2
4
|
|
3
5
|
module Middleware
|
6
|
+
# Sprockets Rack middleware.
|
7
|
+
#
|
4
8
|
class SprocketsChain
|
5
9
|
attr_reader :app, :prefix, :sprockets
|
6
10
|
|
@@ -12,15 +16,15 @@ module Middleware
|
|
12
16
|
end
|
13
17
|
|
14
18
|
def call(env)
|
15
|
-
path_info = env[
|
19
|
+
path_info = env['PATH_INFO']
|
16
20
|
if path_info =~ prefix
|
17
|
-
env[
|
21
|
+
env['PATH_INFO'].sub!(prefix, '')
|
18
22
|
sprockets.call(env)
|
19
23
|
else
|
20
24
|
app.call(env)
|
21
25
|
end
|
22
26
|
ensure
|
23
|
-
env[
|
27
|
+
env['PATH_INFO'] = path_info
|
24
28
|
end
|
25
29
|
end
|
26
30
|
end
|
@@ -1,13 +1,17 @@
|
|
1
|
-
|
2
|
-
module Sprockets
|
3
|
-
def stylesheet_tag(name)
|
4
|
-
folder = 'production' == ENV['RACK_ENV'] ? 'css' : 'assets'
|
5
|
-
"<link href='/#{folder}/#{name}.css' rel='stylesheet' type='text/css' />"
|
6
|
-
end
|
1
|
+
# frozen_string_literal: true
|
7
2
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
3
|
+
module Helpers
|
4
|
+
# Sprockets view helpers.
|
5
|
+
#
|
6
|
+
module Sprockets
|
7
|
+
def stylesheet_tag(name)
|
8
|
+
folder = 'production' == ENV['RACK_ENV'] ? 'css' : 'assets'
|
9
|
+
"<link href='/#{folder}/#{name}.css' rel='stylesheet' type='text/css' />"
|
10
|
+
end
|
11
|
+
|
12
|
+
def javascript_tag(name)
|
13
|
+
folder = 'production' == ENV['RACK_ENV'] ? 'js' : 'assets'
|
14
|
+
"<script type='text/javascript' src='/#{folder}/#{name}.js'></script>"
|
12
15
|
end
|
13
16
|
end
|
17
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
<% if 'test-unit' == options[:test_framework] -%>
|
4
|
+
require_relative 'rack_test_assertions'
|
5
|
+
<% end -%>
|
6
|
+
<% if 'rspec' == options[:test_framework] -%>
|
7
|
+
require_relative 'rack_matchers'
|
8
|
+
<% end -%>
|
9
|
+
|
10
|
+
# Helpers for running Rack-based tests or specs. Decorates normal Rack methods
|
11
|
+
# with an optional `:as` parameter. If present, the `rack.session` will
|
12
|
+
# include the value of the given user's ID, effectively signing them in for
|
13
|
+
# the request.
|
14
|
+
#
|
15
|
+
# Example
|
16
|
+
# ```
|
17
|
+
# # Find or create a user. You can name the class anything you'd like, the
|
18
|
+
# # only requirement is that it responds to a `id` method.
|
19
|
+
# user = User.new(id: 1) # or User.find, etc.
|
20
|
+
# get '/', as: user
|
21
|
+
# ```
|
22
|
+
#
|
23
|
+
# The above will add a `current_user` to the `rack.session` with a value of
|
24
|
+
# the user's ID.
|
25
|
+
#
|
26
|
+
module RackHelpers
|
27
|
+
include Rack::Test::Methods
|
28
|
+
<% if 'test-unit' == options[:test_framework] -%>
|
29
|
+
include Rack::Test::Assertions
|
30
|
+
<% end -%>
|
31
|
+
|
32
|
+
%w[get post put patch delete options].each do |type|
|
33
|
+
define_method(type) do |uri, params={}, env={}, &block|
|
34
|
+
extract_user!(params, env)
|
35
|
+
super uri, params, env, &block
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def extract_user!(params, env)
|
42
|
+
user = params.delete(:as)
|
43
|
+
return unless user
|
44
|
+
|
45
|
+
if env.key?('rack.session')
|
46
|
+
env['rack.session'].merge!(current_user: user.id)
|
47
|
+
else
|
48
|
+
env.merge!({ 'rack.session' => { current_user: user.id } })
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def app
|
53
|
+
Sinatra::Application
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
module Test
|
5
|
+
module Assertions
|
6
|
+
RESPONSE_CODES = {
|
7
|
+
ok: 200,
|
8
|
+
not_authorized: 401,
|
9
|
+
not_found: 404,
|
10
|
+
redirect: 302
|
11
|
+
}.freeze
|
12
|
+
|
13
|
+
CONTENT_TYPES = {
|
14
|
+
json: 'application/json',
|
15
|
+
html: 'text/html;charset=utf-8'
|
16
|
+
}.freeze
|
17
|
+
|
18
|
+
def assert_body_contains(expected, message=nil)
|
19
|
+
msg = build_message(
|
20
|
+
message,
|
21
|
+
"expected body to contain <?>\n#{last_response.body}",
|
22
|
+
expected
|
23
|
+
)
|
24
|
+
|
25
|
+
assert_block(msg) { last_response.body.include?(expected) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def assert_content_type(content_type)
|
29
|
+
unless CONTENT_TYPES.keys.include?(content_type)
|
30
|
+
raise ArgumentError, "unrecognized content_type (#{content_type})"
|
31
|
+
end
|
32
|
+
|
33
|
+
assert_equal CONTENT_TYPES[content_type], last_response.content_type
|
34
|
+
end
|
35
|
+
|
36
|
+
def assert_flash(type=:notice, message=nil)
|
37
|
+
msg = build_message(
|
38
|
+
message,
|
39
|
+
'expected <?> flash to exist, but was nil',
|
40
|
+
type.to_s
|
41
|
+
)
|
42
|
+
|
43
|
+
assert_block(msg) { last_request.env['rack.session']['flash'] }
|
44
|
+
end
|
45
|
+
|
46
|
+
def assert_flash_message(expected, type=:notice, message=nil)
|
47
|
+
assert_flash(type, message)
|
48
|
+
flash = last_request.env['rack.session']['flash'][type.to_s]
|
49
|
+
|
50
|
+
msg = build_message(
|
51
|
+
message,
|
52
|
+
'expected flash to be <?> but was <?>',
|
53
|
+
expected,
|
54
|
+
flash
|
55
|
+
)
|
56
|
+
|
57
|
+
assert_block(msg) { expected == flash }
|
58
|
+
end
|
59
|
+
|
60
|
+
def assert_has_session(message=nil)
|
61
|
+
msg = build_message(message, 'expected a valid session')
|
62
|
+
assert_block(msg) { last_request.env['rack.session'] }
|
63
|
+
end
|
64
|
+
|
65
|
+
def assert_session_has_key(key, message=nil)
|
66
|
+
assert_has_session
|
67
|
+
msg = build_message(message, 'expected session to have key named <?>', key)
|
68
|
+
assert_block(msg) { last_request.env['rack.session'].keys.include?(key.to_s) }
|
69
|
+
end
|
70
|
+
|
71
|
+
def assert_session(key, expected, message=nil)
|
72
|
+
assert_session_has_key(key)
|
73
|
+
actual = last_request.env['rack.session'][key.to_s]
|
74
|
+
msg = build_message(
|
75
|
+
message,
|
76
|
+
'expected session key <?> to be <?>, but was <?>',
|
77
|
+
key,
|
78
|
+
expected,
|
79
|
+
actual
|
80
|
+
)
|
81
|
+
|
82
|
+
assert_block(msg) { expected == actual }
|
83
|
+
end
|
84
|
+
|
85
|
+
def assert_response(expected, message=nil)
|
86
|
+
status = last_response.status
|
87
|
+
msg = build_message(
|
88
|
+
message,
|
89
|
+
'expected last response to be <?> but was <?>',
|
90
|
+
"#{RESPONSE_CODES[expected]}:#{expected}",
|
91
|
+
"#{status}:#{RESPONSE_CODES.key(status)}"
|
92
|
+
)
|
93
|
+
|
94
|
+
assert_block(msg) { status == RESPONSE_CODES[expected] }
|
95
|
+
end
|
96
|
+
|
97
|
+
def assert_redirected_to(expected, message=nil)
|
98
|
+
assert_response(:redirect)
|
99
|
+
actual = URI(last_response.location).path
|
100
|
+
msg = build_message(
|
101
|
+
message,
|
102
|
+
'expected to be redirected to <?> but was <?>',
|
103
|
+
expected,
|
104
|
+
actual
|
105
|
+
)
|
106
|
+
|
107
|
+
assert_block(msg) { expected == actual }
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|