snowpack 1.0.0.alpha2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +3 -0
  4. data/.tool-versions +1 -0
  5. data/.travis.yml +16 -0
  6. data/Brewfile +2 -0
  7. data/CHANGELOG.md +25 -0
  8. data/Gemfile +13 -0
  9. data/LICENSE +21 -0
  10. data/README.md +116 -0
  11. data/Rakefile +6 -0
  12. data/bin/bootstrap +25 -0
  13. data/exe/snowpack +7 -0
  14. data/lib/snowpack/application.rb +98 -0
  15. data/lib/snowpack/application_cli.rb +39 -0
  16. data/lib/snowpack/cli/application/cli.rb +32 -0
  17. data/lib/snowpack/cli/application/command.rb +53 -0
  18. data/lib/snowpack/cli/application/commands/assets/clobber.rb +31 -0
  19. data/lib/snowpack/cli/application/commands/assets/compile.rb +32 -0
  20. data/lib/snowpack/cli/application/commands/console.rb +55 -0
  21. data/lib/snowpack/cli/application/commands/db/create.rb +33 -0
  22. data/lib/snowpack/cli/application/commands/db/create_migration.rb +38 -0
  23. data/lib/snowpack/cli/application/commands/db/drop.rb +33 -0
  24. data/lib/snowpack/cli/application/commands/db/migrate.rb +47 -0
  25. data/lib/snowpack/cli/application/commands/db/reset.rb +28 -0
  26. data/lib/snowpack/cli/application/commands/db/rollback.rb +54 -0
  27. data/lib/snowpack/cli/application/commands/db/sample_data.rb +41 -0
  28. data/lib/snowpack/cli/application/commands/db/seed.rb +41 -0
  29. data/lib/snowpack/cli/application/commands/db/structure/dump.rb +38 -0
  30. data/lib/snowpack/cli/application/commands/db/utils/database.rb +76 -0
  31. data/lib/snowpack/cli/application/commands/db/utils/database_config.rb +49 -0
  32. data/lib/snowpack/cli/application/commands/db/version.rb +37 -0
  33. data/lib/snowpack/cli/application/commands/generate/slice.rb +32 -0
  34. data/lib/snowpack/cli/application/commands/routes/update.rb +35 -0
  35. data/lib/snowpack/cli/application/commands.rb +27 -0
  36. data/lib/snowpack/cli/command.rb +48 -0
  37. data/lib/snowpack/cli/standalone/cli.rb +14 -0
  38. data/lib/snowpack/cli/standalone/commands/new.rb +33 -0
  39. data/lib/snowpack/cli/standalone/commands.rb +13 -0
  40. data/lib/snowpack/components/formalist.rb +27 -0
  41. data/lib/snowpack/components/persistence.rb +56 -0
  42. data/lib/snowpack/components.rb +5 -0
  43. data/lib/snowpack/console/context.rb +32 -0
  44. data/lib/snowpack/console/plugins/relation_readers.rb +17 -0
  45. data/lib/snowpack/console/plugins/slice_readers.rb +33 -0
  46. data/lib/snowpack/generator.rb +57 -0
  47. data/lib/snowpack/generators/application/generator.rb +67 -0
  48. data/lib/snowpack/generators/application/templates/.env-example.tt +10 -0
  49. data/lib/snowpack/generators/application/templates/.env.test-example.tt +1 -0
  50. data/lib/snowpack/generators/application/templates/.gitignore +20 -0
  51. data/lib/snowpack/generators/application/templates/.rspec +2 -0
  52. data/lib/snowpack/generators/application/templates/Brewfile +4 -0
  53. data/lib/snowpack/generators/application/templates/Gemfile.tt +46 -0
  54. data/lib/snowpack/generators/application/templates/Guardfile +7 -0
  55. data/lib/snowpack/generators/application/templates/Procfile.dev +3 -0
  56. data/lib/snowpack/generators/application/templates/Procfile.support +1 -0
  57. data/lib/snowpack/generators/application/templates/README.md.tt +45 -0
  58. data/lib/snowpack/generators/application/templates/Rakefile +5 -0
  59. data/lib/snowpack/generators/application/templates/bin/run +7 -0
  60. data/lib/snowpack/generators/application/templates/config/application.rb.tt +19 -0
  61. data/lib/snowpack/generators/application/templates/config/puma.rb.tt +14 -0
  62. data/lib/snowpack/generators/application/templates/config/routes.rb.tt +15 -0
  63. data/lib/snowpack/generators/application/templates/config.ru +5 -0
  64. data/lib/snowpack/generators/application/templates/db/migrate/.keep +0 -0
  65. data/lib/snowpack/generators/application/templates/db/sample_data.rb +1 -0
  66. data/lib/snowpack/generators/application/templates/db/seed.rb +1 -0
  67. data/lib/snowpack/generators/application/templates/lib/__application_path__/operation.rb.tt +12 -0
  68. data/lib/snowpack/generators/application/templates/lib/__application_path__/persistence/relations/.keep +0 -0
  69. data/lib/snowpack/generators/application/templates/lib/__application_path__/types.rb.tt +11 -0
  70. data/lib/snowpack/generators/application/templates/lib/__application_path__/web/action.rb.tt +56 -0
  71. data/lib/snowpack/generators/application/templates/lib/__application_path__/web/view/context.rb.tt +31 -0
  72. data/lib/snowpack/generators/application/templates/lib/hanami/action/csrf_protection.rb +225 -0
  73. data/lib/snowpack/generators/application/templates/log/.keep +0 -0
  74. data/lib/snowpack/generators/application/templates/package.json.tt +25 -0
  75. data/lib/snowpack/generators/application/templates/public/.keep +0 -0
  76. data/lib/snowpack/generators/application/templates/script/bootstrap +32 -0
  77. data/lib/snowpack/generators/application/templates/script/console +26 -0
  78. data/lib/snowpack/generators/application/templates/script/server +13 -0
  79. data/lib/snowpack/generators/application/templates/script/setup +27 -0
  80. data/lib/snowpack/generators/application/templates/script/support +9 -0
  81. data/lib/snowpack/generators/application/templates/script/test +19 -0
  82. data/lib/snowpack/generators/application/templates/script/update +13 -0
  83. data/lib/snowpack/generators/application/templates/spec/spec_helper.rb +13 -0
  84. data/lib/snowpack/generators/application/templates/spec/suite/.keep +0 -0
  85. data/lib/snowpack/generators/application/templates/spec/support/suite.rb +9 -0
  86. data/lib/snowpack/generators/application/templates/system/__application_path__/import.rb.tt +5 -0
  87. data/lib/snowpack/generators/application/templates/system/boot/assets.rb.tt +17 -0
  88. data/lib/snowpack/generators/application/templates/system/boot/logger.rb.tt +10 -0
  89. data/lib/snowpack/generators/application/templates/system/boot/monitor.rb.tt +9 -0
  90. data/lib/snowpack/generators/application/templates/system/boot/persistence.rb.tt +9 -0
  91. data/lib/snowpack/generators/application/templates/system/boot/settings.rb.tt +18 -0
  92. data/lib/snowpack/generators/application/templates/system/boot/web.rb.tt +9 -0
  93. data/lib/snowpack/generators/slice/generator.rb +62 -0
  94. data/lib/snowpack/generators/slice/templates/lib/__slice_path__/.keep +0 -0
  95. data/lib/snowpack/generators/slice/templates/lib/__slice_path__/web/action.rb.tt +10 -0
  96. data/lib/snowpack/generators/slice/templates/lib/__slice_path__/web/actions/.keep +0 -0
  97. data/lib/snowpack/generators/slice/templates/lib/__slice_path__/web/view.rb.tt +17 -0
  98. data/lib/snowpack/generators/slice/templates/lib/__slice_path__/web/views/.keep +0 -0
  99. data/lib/snowpack/generators/slice/templates/system/__slice_path__/import.rb.tt +9 -0
  100. data/lib/snowpack/generators/slice/templates/system/__slice_path__/slice.rb.tt +10 -0
  101. data/lib/snowpack/instrumentation/appsignal/appsignal_ext.rb +13 -0
  102. data/lib/snowpack/instrumentation/appsignal/que.rb +70 -0
  103. data/lib/snowpack/instrumentation/appsignal/rack.rb +84 -0
  104. data/lib/snowpack/roda/web.rb +47 -0
  105. data/lib/snowpack/slice.rb +85 -0
  106. data/lib/snowpack/test/suite.rb +164 -0
  107. data/lib/snowpack/test/tasks.rake +40 -0
  108. data/lib/snowpack/test_tasks.rb +4 -0
  109. data/lib/snowpack/types.rb +9 -0
  110. data/lib/snowpack/version.rb +5 -0
  111. data/lib/snowpack/view/part_builder.rb +45 -0
  112. data/lib/snowpack/view/parts/pager.rb +107 -0
  113. data/lib/snowpack/view/parts/paginated.rb +22 -0
  114. data/lib/snowpack/web/application.rb +38 -0
  115. data/lib/snowpack/web/assets.rb +56 -0
  116. data/lib/snowpack/web/endpoint_resolver.rb +66 -0
  117. data/lib/snowpack/web/form.rb +38 -0
  118. data/lib/snowpack/web/plugin.rb +44 -0
  119. data/lib/snowpack/web/rack_logger.rb +73 -0
  120. data/lib/snowpack/web/router.rb +58 -0
  121. data/lib/snowpack/web.rb +20 -0
  122. data/lib/snowpack.rb +27 -0
  123. data/snowpack.gemspec +34 -0
  124. metadata +327 -0
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rack"
4
+ require "hanami/controller"
5
+ require_relative "router"
6
+
7
+ module Snowpack
8
+ module Web
9
+ class Application
10
+ def initialize(application, &routes)
11
+ resolver = application.config.web.routing.endpoint_resolver.new(
12
+ application: application,
13
+ namespace: application.config.web.routing.action_key_namespace,
14
+ )
15
+
16
+ router = Router.new(
17
+ application: application,
18
+ endpoint_resolver: resolver,
19
+ &routes
20
+ )
21
+
22
+ @app = Rack::Builder.new do
23
+ use application[:rack_monitor]
24
+
25
+ router.middlewares.each do |(*middleware, block)|
26
+ use(*middleware, &block)
27
+ end
28
+
29
+ run router
30
+ end
31
+ end
32
+
33
+ def call(env)
34
+ @app.call(env)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,56 @@
1
+ require "yaml"
2
+ require "down"
3
+
4
+ module Snowpack
5
+ module Web
6
+ class Assets
7
+ attr_reader :root
8
+ attr_reader :precompiled
9
+ attr_reader :server_url
10
+
11
+ def initialize(root:, precompiled:, server_url: nil)
12
+ @root = root
13
+ @precompiled = precompiled
14
+ @server_url = server_url
15
+ end
16
+
17
+ def [](asset)
18
+ if precompiled
19
+ asset_path_from_manifest(asset)
20
+ else
21
+ asset_path_on_server(asset)
22
+ end
23
+ end
24
+
25
+ def read(asset)
26
+ path = self[asset]
27
+
28
+ if File.exist?("#{root}/public#{path}")
29
+ File.read("#{root}/public#{path}")
30
+ else
31
+ Down.open(path).read
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def asset_path_from_manifest(asset)
38
+ if (hashed_asset = manifest[asset])
39
+ "/assets/#{hashed_asset}"
40
+ end
41
+ end
42
+
43
+ def asset_path_on_server(asset)
44
+ "#{server_url}/assets/#{asset}"
45
+ end
46
+
47
+ def manifest
48
+ @manifest ||= YAML.load_file(manifest_path)
49
+ end
50
+
51
+ def manifest_path
52
+ "#{root}/public/assets/asset-manifest.json"
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ # FIXME: This is for the NotCallableEndpointError. It would be good if this
4
+ # could be require-able without having to bring in all the routing files
5
+ require "hanami/routing"
6
+
7
+ module Snowpack
8
+ module Web
9
+ class EndpointResolver
10
+ attr_reader :application
11
+ attr_reader :container
12
+ attr_reader :base_namespace
13
+
14
+ def initialize(application:, container: application, namespace:)
15
+ @application = application
16
+ @container = container
17
+ @base_namespace = namespace
18
+ end
19
+
20
+ def sliced(name)
21
+ raise "Slices can only be mounted from top-level application" unless application.respond_to?(:slices)
22
+
23
+ slice = application.slices[name]
24
+ return unless slice
25
+
26
+ self.class.new(
27
+ application: application,
28
+ container: slice,
29
+ namespace: base_namespace,
30
+ )
31
+ end
32
+
33
+ def call(name, namespace = nil, configuration = nil)
34
+ endpoint =
35
+ case name
36
+ when String
37
+ resolve_string_identifier(name, namespace, configuration)
38
+ when Class
39
+ name.respond_to?(:call) ? name : name.new
40
+ else
41
+ name
42
+ end
43
+
44
+ unless endpoint.respond_to?(:call)
45
+ raise Hanami::Routing::NotCallableEndpointError.new(endpoint)
46
+ end
47
+
48
+ endpoint
49
+ end
50
+
51
+ private
52
+
53
+ def resolve_string_identifier(name, namespace, configuration)
54
+ identifier = [base_namespace, namespace, name].compact.join(".")
55
+
56
+ container[identifier].yield_self { |endpoint|
57
+ if configuration && endpoint.class < Hanami::Action
58
+ endpoint.with(configuration: configuration)
59
+ else
60
+ endpoint
61
+ end
62
+ }
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ require "formalist"
6
+ require "formalist/elements/standard"
7
+
8
+ module Snowpack
9
+ module Web
10
+ class Form < ::Formalist::Form
11
+ setting :prefix
12
+
13
+ def self.prefix(value)
14
+ config.prefix = value
15
+ end
16
+
17
+ def prefix
18
+ self.class.config.prefix
19
+ end
20
+
21
+ def fill(input: {}, errors: {}, **args)
22
+ super(input: Hash(input), errors: errors, **args)
23
+ end
24
+
25
+ def to_h(config = {})
26
+ if config.any?
27
+ {ast: to_ast, prefix: prefix, config: {global: config}}
28
+ else
29
+ {ast: to_ast, prefix: prefix}
30
+ end
31
+ end
32
+
33
+ def to_s
34
+ JSON.generate(to_h)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/system/plugins"
4
+ require_relative "endpoint_resolver"
5
+ require_relative "rack_logger"
6
+
7
+ module Snowpack
8
+ module Web
9
+ module Plugin
10
+ def self.extended(system)
11
+ super
12
+
13
+ system.setting :web do
14
+ setting :routing do
15
+ setting :endpoint_resolver, EndpointResolver
16
+ setting :action_key_namespace, "web.actions"
17
+ end
18
+
19
+ setting :logging do
20
+ setting :filter_params, %w[_csrf password password_confirmation]
21
+ end
22
+ end
23
+
24
+ system.after :configure do
25
+ register_rack_monitor
26
+ attach_rack_logger
27
+ end
28
+ end
29
+
30
+ def register_rack_monitor
31
+ return self if key?(:rack_monitor)
32
+ register :rack_monitor, Dry::Monitor::Rack::Middleware.new(self[:notifications])
33
+ self
34
+ end
35
+
36
+ def attach_rack_logger
37
+ RackLogger.new(self[:logger], filter_params: config.web.logging.filter_params).attach(self[:rack_monitor])
38
+ self
39
+ end
40
+ end
41
+
42
+ Dry::System::Plugins.register :web, Plugin
43
+ end
44
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "rack/request"
5
+
6
+ module Snowpack
7
+ module Web
8
+ class RackLogger
9
+ attr_reader :logger
10
+ attr_reader :filter_params
11
+
12
+ def initialize(logger, filter_params: [])
13
+ @logger = logger
14
+ @filter_params = filter_params
15
+ end
16
+
17
+ def attach(rack_monitor)
18
+ rack_monitor.on :stop do |env:, status:, time:|
19
+ log_request env, status, time
20
+ end
21
+
22
+ rack_monitor.on :error do |event|
23
+ log_exception event[:exception]
24
+ end
25
+ end
26
+
27
+ def log_request(env, status, time)
28
+ request = Rack::Request.new(env)
29
+
30
+ params = request.GET
31
+ params = params.merge(Hash(request.POST))
32
+
33
+ # TODO: support both roda and hanami params
34
+ json_params = Hash(request.get_header("roda.json_params"))
35
+ params = params.merge(json_params)
36
+
37
+ data = {
38
+ method: request.request_method,
39
+ path: request.path,
40
+ for: request.get_header("REMOTE_ADDR"),
41
+ status: status,
42
+ duration: time,
43
+ params: filter(params),
44
+ }
45
+
46
+ logger.info JSON.generate(data)
47
+ end
48
+
49
+ def log_exception(e)
50
+ logger.error e.message
51
+ logger.error (e.backtrace).join("\n")
52
+ end
53
+
54
+ private
55
+
56
+ FILTERED = "[FILTERED]"
57
+
58
+ def filter(params)
59
+ params.each_with_object({}) do |(k, v), h|
60
+ if filter_params.include?(k)
61
+ h.update(k => FILTERED)
62
+ elsif v.is_a?(Hash)
63
+ h.update(k => filter_params(v))
64
+ elsif v.is_a?(Array)
65
+ h.update(k => v.map { |m| m.is_a?(Hash) ? filter_params(m) : m })
66
+ else
67
+ h[k] = v
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami/router"
4
+
5
+ module Snowpack
6
+ module Web
7
+ class Router < Hanami::Router
8
+ attr_reader :middlewares
9
+
10
+ def initialize(application:, **options, &block)
11
+ @application = application
12
+ @options = options
13
+ @middlewares = []
14
+
15
+ super(**options, &nil)
16
+ instance_exec(application, &block) if block
17
+ freeze overridden: true
18
+ end
19
+
20
+ # Do nothing when the superclass calls freeze during its own initialize
21
+ # (we need to do it later, after our instance_exec of the routes block)
22
+ def freeze(overridden: false)
23
+ super() if overridden
24
+ end
25
+
26
+ # Ensure we always return a rack-conformant result (sometimes we get a
27
+ # Hanami::Action::Response here, when we actually want the standard rack
28
+ # 3-element array)
29
+ def call(*)
30
+ super.to_a
31
+ end
32
+
33
+ def use(*args, &block)
34
+ middlewares << (args << block)
35
+ end
36
+
37
+ def mount(app, at:, host: nil, &block)
38
+ if app.is_a?(Symbol) && (sliced_resolver = @endpoint_resolver.sliced(app))
39
+ sliced_router = self.class.new(
40
+ application: @application.slices[app],
41
+ **@options,
42
+ endpoint_resolver: sliced_resolver,
43
+ &block
44
+ )
45
+
46
+ super(sliced_router, at: at, host: host)
47
+ else
48
+ super(app, at: at, host: host)
49
+ end
50
+ end
51
+
52
+ # Allow router objects to be mounted within themselves
53
+ def match?(env)
54
+ match_path?(env)
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../snowpack"
4
+ require_relative "web/application"
5
+
6
+ module Snowpack
7
+ module Web
8
+ def self.routes(&block)
9
+ if block
10
+ @routes = block
11
+ else
12
+ @routes
13
+ end
14
+ end
15
+
16
+ def self.application
17
+ @application ||= Application.new(Snowpack.application, &routes)
18
+ end
19
+ end
20
+ end
data/lib/snowpack.rb ADDED
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "snowpack/version"
4
+
5
+ module Snowpack
6
+ @_mutex = Mutex.new
7
+
8
+ def self.application
9
+ @_mutex.synchronize do
10
+ raise "Snowpack.application not configured" unless defined?(@_application)
11
+
12
+ @_application
13
+ end
14
+ end
15
+
16
+ def self.application?
17
+ defined?(@_application) && @_application
18
+ end
19
+
20
+ def self.application=(klass)
21
+ @_mutex.synchronize do
22
+ raise "Snowpack.application already configured" if defined?(@_application)
23
+
24
+ @_application = klass unless klass.name.nil?
25
+ end
26
+ end
27
+ end
data/snowpack.gemspec ADDED
@@ -0,0 +1,34 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'snowpack/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "snowpack"
7
+ spec.version = Snowpack::VERSION
8
+ spec.authors = ["Tim Riley", "Piotr Solnica"]
9
+ spec.email = ["tim@icelab.com.au", "piotr@icelab.com.au"]
10
+
11
+ spec.summary = "Lightweight application framework for Icelab"
12
+ spec.description = spec.summary
13
+ spec.homepage = "https://github.com/icelab/snowpack"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
16
+ f.match(%r{^(test|spec|features)/})
17
+ end
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_runtime_dependency "dry-inflector", "~> 0.1"
23
+ spec.add_runtime_dependency "dry-monitor", "~> 0.3.1"
24
+ spec.add_runtime_dependency "dry-system", "~> 0.12", ">= 0.12.0"
25
+ spec.add_runtime_dependency "dry-types", "~> 1.0"
26
+ spec.add_runtime_dependency "down", "~> 4.0"
27
+ spec.add_runtime_dependency "hanami-cli" # requires unstable branch from github in application Gemfile
28
+ spec.add_runtime_dependency "hanami-utils" # requires unstable branch from github in application Gemfile
29
+ spec.add_runtime_dependency "rack", "~> 2.0"
30
+
31
+ spec.add_development_dependency "bundler"
32
+ spec.add_development_dependency "rake", "~> 12.0"
33
+ spec.add_development_dependency "rspec", "~> 3.0"
34
+ end