snowpack 1.0.0.alpha2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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