bridgetown-core 1.2.0 → 1.3.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 599f0ff57d4bac2146e5cacb12ccb8f2e7e02026815447c2db9cbf914ed9d3a2
4
- data.tar.gz: be29cede2daa248b4db41bb6a7eaeebb7d3533b54a5cf891922b0762a6ca47e0
3
+ metadata.gz: 9176399e9b750178476e2d57edb346989426407d5d40e8958c5be3901626f77f
4
+ data.tar.gz: a5130e5511c64acba6e6ee619a7529b49415e1c25b3e056cf4e709c4d78361da
5
5
  SHA512:
6
- metadata.gz: 739ed852e60fabf0b6b2d352c8ce8993d4751a1f6cfec67c0bb7ed4cc5f0af41bb82fb8dc3eb34f5806a421bcec38e9050cd0fbcb50249a0482f5804e19a1d27
7
- data.tar.gz: 97eea9cb28095624a8d47821f754df39790d8db46e62d79774371113c875fd914a796f3e445ad982e6f8de8e94d5babf7b8be1736c4bc93f3a9fe0a0f6862433
6
+ metadata.gz: 849bff55f1997483ce24f67536daec7bdd53ff016d7e4ab8937b0184892680eb59f159d3a3d26c22fbc63ef63486ddd1b793943cd21fead6c281f0d2d9698f69
7
+ data.tar.gz: e5c317edb3ae4e232cd2a56c885b44af5375d7daeff71f385e620d402cbe57c68cf32fdc6eb13508bb92e3e8dff254cf53e59f56790631692eee092c6eaa0463
@@ -31,26 +31,25 @@ Gem::Specification.new do |s|
31
31
 
32
32
  s.required_ruby_version = ">= 2.7.0"
33
33
 
34
- s.add_runtime_dependency("activemodel", [">= 6.0", "< 8.0"])
35
- s.add_runtime_dependency("activesupport", [">= 6.0", "< 8.0"])
36
- s.add_runtime_dependency("addressable", "~> 2.4")
37
- s.add_runtime_dependency("amazing_print", "~> 1.2")
38
- s.add_runtime_dependency("colorator", "~> 1.0")
39
- s.add_runtime_dependency("erubi", "~> 1.9")
40
- s.add_runtime_dependency("faraday", "~> 1.0")
41
- s.add_runtime_dependency("faraday_middleware", "~> 1.0")
42
- s.add_runtime_dependency("hash_with_dot_access", "~> 1.2")
43
- s.add_runtime_dependency("i18n", "~> 1.0")
44
- s.add_runtime_dependency("kramdown", "~> 2.1")
45
- s.add_runtime_dependency("kramdown-parser-gfm", "~> 1.0")
46
- s.add_runtime_dependency("liquid", "~> 5.0")
47
- s.add_runtime_dependency("listen", "~> 3.0")
48
- s.add_runtime_dependency("rake", ">= 13.0")
49
- s.add_runtime_dependency("roda", "~> 3.46")
50
- s.add_runtime_dependency("rouge", "~> 3.0")
51
- s.add_runtime_dependency("serbea", "~> 1.0")
52
- s.add_runtime_dependency("thor", "~> 1.1")
53
- s.add_runtime_dependency("tilt", "~> 2.0")
54
- s.add_runtime_dependency("webrick", "~> 1.7")
55
- s.add_runtime_dependency("zeitwerk", "~> 2.5")
34
+ s.add_runtime_dependency("activemodel", [">= 6.0", "< 8.0"])
35
+ s.add_runtime_dependency("activesupport", [">= 6.0", "< 8.0"])
36
+ s.add_runtime_dependency("addressable", "~> 2.4")
37
+ s.add_runtime_dependency("amazing_print", "~> 1.2")
38
+ s.add_runtime_dependency("colorator", "~> 1.0")
39
+ s.add_runtime_dependency("erubi", "~> 1.9")
40
+ s.add_runtime_dependency("faraday", "~> 2.0")
41
+ s.add_runtime_dependency("faraday-follow_redirects", "~> 0.3")
42
+ s.add_runtime_dependency("hash_with_dot_access", "~> 1.2")
43
+ s.add_runtime_dependency("i18n", "~> 1.0")
44
+ s.add_runtime_dependency("kramdown", "~> 2.1")
45
+ s.add_runtime_dependency("kramdown-parser-gfm", "~> 1.0")
46
+ s.add_runtime_dependency("liquid", "~> 5.0")
47
+ s.add_runtime_dependency("listen", "~> 3.0")
48
+ s.add_runtime_dependency("rake", ">= 13.0")
49
+ s.add_runtime_dependency("roda", "~> 3.46")
50
+ s.add_runtime_dependency("rouge", "~> 3.0")
51
+ s.add_runtime_dependency("serbea", "~> 1.0")
52
+ s.add_runtime_dependency("thor", "~> 1.1")
53
+ s.add_runtime_dependency("tilt", "~> 2.0")
54
+ s.add_runtime_dependency("zeitwerk", "~> 2.5")
56
55
  end
@@ -282,7 +282,7 @@ module Bridgetown
282
282
  if model_is_multi_locale?(model, model_relative_path)
283
283
  # If the model specifies a locales key, use that to determine the
284
284
  # the locale of each resource, otherwise fall back to `site.config.available_locales`
285
- locales = model.locales || site.config.available_locales
285
+ locales = model.attributes[:locales] || site.config.available_locales
286
286
 
287
287
  locales.each do |locale|
288
288
  model.locale = locale.to_sym
@@ -80,6 +80,8 @@ module Bridgetown
80
80
  after_install new_site_path, args.join(" "), options
81
81
  rescue ArgumentError => e
82
82
  say_status :alert, e.message, :red
83
+ ensure
84
+ self.class.created_site_dir = nil # reset afterwards, otherwise hanging tmp dirs in test
83
85
  end
84
86
 
85
87
  protected
@@ -49,7 +49,7 @@ module Bridgetown
49
49
  priority: Bridgetown::Hooks::DEFAULT_PRIORITY,
50
50
  &block
51
51
  )
52
- Bridgetown::Hooks.register_one(owner, event, priority: priority, &block)
52
+ Bridgetown::Hooks.register_one(owner, event, priority: priority, reloadable: false, &block)
53
53
  end
54
54
 
55
55
  def source_manifest(**kwargs)
@@ -31,7 +31,7 @@ create_builder "purgecss.rb" do
31
31
  manifest = JSON.parse(File.read(manifest_file))
32
32
 
33
33
  if Bridgetown::Utils.frontend_bundler_type == :esbuild
34
- css_file = manifest["styles/index.css"].split("/").last
34
+ css_file = (manifest["styles/index.css"] || manifest["styles/index.scss"]).split("/").last
35
35
  css_path = ["output", "_bridgetown", "static", css_file].join("/")
36
36
  else
37
37
  css_file = manifest["main.css"].split("/").last
@@ -15,10 +15,13 @@ module Bridgetown
15
15
  InvalidURLError = Class.new(FatalException)
16
16
  InvalidConfigurationError = Class.new(FatalException)
17
17
 
18
- def self.print_build_error(exc, trace: false, logger: Bridgetown.logger)
18
+ def self.print_build_error(exc, trace: false, logger: Bridgetown.logger, server: false) # rubocop:disable Metrics
19
19
  logger.error "Exception raised:", exc.class.to_s.bold
20
20
  logger.error exc.message.reset_ansi
21
21
 
22
+ build_errors_file = Bridgetown.build_errors_path if !server && Bridgetown::Current.site
23
+ build_errors_data = "#{exc.class}: #{exc.message}"
24
+
22
25
  trace_args = ["-t", "--trace"]
23
26
  print_trace_msg = true
24
27
  traces = if trace || ARGV.find { |arg| trace_args.include?(arg) }
@@ -29,6 +32,12 @@ module Bridgetown
29
32
  end
30
33
  traces.each_with_index do |backtrace_line, index|
31
34
  logger.error "#{index + 1}:", backtrace_line.reset_ansi
35
+ build_errors_data << "\n#{backtrace_line}" if index < 2
36
+ end
37
+
38
+ if build_errors_file
39
+ FileUtils.mkdir_p(File.dirname(build_errors_file))
40
+ File.write(build_errors_file, build_errors_data, mode: "w")
32
41
  end
33
42
 
34
43
  return unless print_trace_msg
@@ -8,12 +8,22 @@ require "roda/plugins/public"
8
8
  Bridgetown::Current.preloaded_configuration ||= Bridgetown.configuration
9
9
 
10
10
  require_relative "logger"
11
- require_relative "roda"
12
11
  require_relative "routes"
13
12
  require_relative "static_indexes"
14
13
 
15
14
  module Bridgetown
16
15
  module Rack
16
+ class Roda < ::Roda
17
+ def self.inherited(klass)
18
+ Bridgetown::Deprecator.deprecation_message(
19
+ "The `Bridgetown::Rack::Roda' class will be removed in favor of using the " \
20
+ "`bridgetown_server' plugin in a future version"
21
+ )
22
+ super
23
+ klass.plugin :bridgetown_server
24
+ end
25
+ end
26
+
17
27
  class << self
18
28
  # @return [Bridgetown::Utils::LoadersManager]
19
29
  attr_accessor :loaders_manager
@@ -56,6 +66,8 @@ module Bridgetown
56
66
  loader.reload
57
67
  loader.eager_load
58
68
  Bridgetown::Rack::Routes.reload_subclasses
69
+ rescue SyntaxError => e
70
+ Bridgetown::Errors.print_build_error(e)
59
71
  end.start
60
72
  end
61
73
  end
@@ -20,6 +20,37 @@ module Bridgetown
20
20
  }.freeze
21
21
 
22
22
  class << self
23
+ # rubocop:disable Bridgetown/NoPutsAllowed, Metrics/MethodLength
24
+ def print_routes
25
+ # TODO: this needs to be fully documented
26
+ routes = begin
27
+ JSON.parse(
28
+ File.read(
29
+ File.join(Bridgetown::Current.preloaded_configuration.root_dir, ".routes.json")
30
+ )
31
+ )
32
+ rescue StandardError
33
+ []
34
+ end
35
+ puts
36
+ puts "Routes:"
37
+ puts "======="
38
+ if routes.blank?
39
+ puts "No routes found. Have you commented all of your routes?"
40
+ puts "Documentation: https://github.com/jeremyevans/roda-route_list#basic-usage-"
41
+ end
42
+
43
+ routes.each do |route|
44
+ puts [
45
+ route["methods"]&.join("|") || "GET",
46
+ route["path"],
47
+ route["file"] ? "\n File: #{route["file"]}" : nil,
48
+ ].compact.join(" ")
49
+ end
50
+ puts
51
+ end
52
+ # rubocop:enable Bridgetown/NoPutsAllowed, Metrics/MethodLength
53
+
23
54
  # @return [Hash<String, Class(Routes)>]
24
55
  attr_accessor :tracked_subclasses
25
56
 
@@ -80,7 +111,7 @@ module Bridgetown
80
111
  # Initialize a new Routes instance and execute the route as part of the
81
112
  # Roda app request cycle
82
113
  #
83
- # @param roda_app [Bridgetown::Rack::Roda]
114
+ # @param roda_app [Roda]
84
115
  def merge(roda_app)
85
116
  return unless router_block
86
117
 
@@ -90,7 +121,7 @@ module Bridgetown
90
121
  # Start the Roda app request cycle. There are two different code paths
91
122
  # depending on if there's a site `base_path` configured
92
123
  #
93
- # @param roda_app [Bridgetown::Rack::Roda]
124
+ # @param roda_app [Roda]
94
125
  # @return [void]
95
126
  def start!(roda_app)
96
127
  if Bridgetown::Current.preloaded_configuration.base_path == "/"
@@ -112,7 +143,7 @@ module Bridgetown
112
143
  # run through all the Routes blocks. If the file-based router plugin
113
144
  # is available, kick off that request process next.
114
145
  #
115
- # @param roda_app [Bridgetown::Rack::Roda]
146
+ # @param roda_app [Roda]
116
147
  # @return [void]
117
148
  def load_all_routes(roda_app)
118
149
  roda_app.request.public
@@ -127,11 +158,12 @@ module Bridgetown
127
158
  end
128
159
  end
129
160
 
130
- # @param app [Bridgetown::Rack::Roda]
131
- def setup_live_reload(app) # rubocop:disable Metrics/AbcSize
161
+ # @param app [Roda]
162
+ def setup_live_reload(app) # rubocop:disable Metrics
132
163
  sleep_interval = 0.2
133
164
  file_to_check = File.join(Bridgetown::Current.preloaded_configuration.destination,
134
165
  "index.html")
166
+ errors_file = Bridgetown.build_errors_path
135
167
 
136
168
  app.request.get "_bridgetown/live_reload" do
137
169
  app.response["Content-Type"] = "text/event-stream"
@@ -146,6 +178,8 @@ module Bridgetown
146
178
  if @_mod < new_mod
147
179
  out << "data: reloaded!\n\n"
148
180
  break
181
+ elsif File.exist?(errors_file)
182
+ out << "event: builderror\ndata: #{File.read(errors_file).to_json}\n\n"
149
183
  else
150
184
  out << "data: #{new_mod}\n\n"
151
185
  end
@@ -157,7 +191,7 @@ module Bridgetown
157
191
  end
158
192
  end
159
193
 
160
- # @param roda_app [Bridgetown::Rack::Roda]
194
+ # @param roda_app [Roda]
161
195
  def initialize(roda_app)
162
196
  @_roda_app = roda_app
163
197
  end
@@ -11,13 +11,13 @@ module Bridgetown
11
11
 
12
12
  def read
13
13
  layout_entries.each do |layout_file|
14
- @layouts[layout_name(layout_file)] = \
14
+ @layouts[layout_name(layout_file)] =
15
15
  Layout.new(site, layout_directory, layout_file)
16
16
  end
17
17
 
18
18
  site.config.source_manifests.filter_map(&:layouts).each do |plugin_layouts|
19
19
  layout_entries(plugin_layouts).each do |layout_file|
20
- @layouts[layout_name(layout_file)] ||= \
20
+ @layouts[layout_name(layout_file)] ||=
21
21
  Layout.new(site, plugin_layouts, layout_file, from_plugin: true)
22
22
  end
23
23
  end
@@ -13,6 +13,8 @@ module Bridgetown
13
13
  @config = config
14
14
  @loaders = {}
15
15
  @root_dir = config.root_dir
16
+
17
+ FileUtils.rm_f(Bridgetown.build_errors_path)
16
18
  end
17
19
 
18
20
  def unload_loaders
@@ -75,6 +77,8 @@ module Bridgetown
75
77
  end
76
78
 
77
79
  def reload_loaders
80
+ FileUtils.rm_f(Bridgetown.build_errors_path)
81
+
78
82
  @loaders.each do |load_path, loader|
79
83
  next unless reloading_enabled?(load_path)
80
84
 
@@ -466,6 +466,7 @@ module Bridgetown
466
466
  function startReloadConnection() {
467
467
  const evtSource = new EventSource("#{site.base_path(strip_slash_only: true)}/_bridgetown/live_reload")
468
468
  evtSource.onmessage = event => {
469
+ if (document.querySelector("#bridgetown-build-error")) document.querySelector("#bridgetown-build-error").close()
469
470
  if (event.data == "reloaded!") {
470
471
  location.reload()
471
472
  } else {
@@ -477,6 +478,23 @@ module Bridgetown
477
478
  }
478
479
  }
479
480
  }
481
+ evtSource.addEventListener("builderror", event => {
482
+ let dialog = document.querySelector("#bridgetown-build-error")
483
+ if (!dialog) {
484
+ dialog = document.createElement("dialog")
485
+ dialog.id = "bridgetown-build-error"
486
+ dialog.style.borderColor = "red"
487
+ dialog.style.fontSize = "110%"
488
+ dialog.innerHTML = `
489
+ <p style="color:red">There was an error when building the site:</p>
490
+ <output><pre></pre></output>
491
+ <p><small>Check your Bridgetown logs for further details.</small></p>
492
+ `
493
+ document.body.appendChild(dialog)
494
+ dialog.showModal()
495
+ }
496
+ dialog.querySelector("pre").textContent = JSON.parse(event.data)
497
+ })
480
498
  evtSource.onerror = event => {
481
499
  if (evtSource.readyState === 2) {
482
500
  // reconnect with new object
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bridgetown
4
- VERSION = "1.2.0"
5
- CODE_NAME = "Bonny Slope"
4
+ VERSION = "1.3.0.beta1"
5
+ CODE_NAME = "Kelly Butte"
6
6
  end
@@ -106,7 +106,7 @@ module Bridgetown
106
106
  end
107
107
  Bridgetown.logger.info "Done! 🎉", "#{"Completed".bold.green} in less than " \
108
108
  "#{(Time.now - time).ceil(2)} seconds."
109
- rescue StandardError => e
109
+ rescue StandardError, SyntaxError => e
110
110
  Bridgetown::Errors.print_build_error(e, trace: options[:trace])
111
111
  end
112
112
  Bridgetown.logger.info ""
@@ -377,6 +377,18 @@ module Bridgetown
377
377
  File.join(base_directory, clean_path)
378
378
  end
379
379
  end
380
+
381
+ # When there's a build error, error details will be logged to a file which the dev server
382
+ # can read and pass along to the browser.
383
+ #
384
+ # @return [String] the path to the cached errors file
385
+ def build_errors_path
386
+ File.join(
387
+ (Bridgetown::Current.site&.config || Bridgetown::Current.preloaded_configuration).root_dir,
388
+ ".bridgetown-cache",
389
+ "build_errors.txt"
390
+ )
391
+ end
380
392
  end
381
393
  end
382
394
 
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Roda
4
+ module RodaPlugins
5
+ module BridgetownServer
6
+ SiteContext = Struct.new(:registers) # for use by Liquid-esque URL helpers
7
+
8
+ def self.load_dependencies(app) # rubocop:disable Metrics
9
+ unless Bridgetown::Current.preloaded_configuration
10
+ raise "You must supply a preloaded configuration before loading the Bridgetown Roda " \
11
+ "plugin"
12
+ end
13
+
14
+ app.plugin :initializers
15
+ app.plugin :method_override
16
+ app.plugin :all_verbs
17
+ app.plugin :hooks
18
+ app.plugin :common_logger, Bridgetown::Rack::Logger.new($stdout), method: :info
19
+ app.plugin :json
20
+ app.plugin :json_parser
21
+ app.plugin :indifferent_params
22
+ app.plugin :cookies
23
+ app.plugin :streaming
24
+ app.plugin :public, root: Bridgetown::Current.preloaded_configuration.destination
25
+ app.plugin :not_found do
26
+ output_folder = Bridgetown::Current.preloaded_configuration.destination
27
+ File.read(File.join(output_folder, "404.html"))
28
+ rescue Errno::ENOENT
29
+ "404 Not Found"
30
+ end
31
+ app.plugin :exception_page
32
+ app.plugin :error_handler do |e|
33
+ Bridgetown::Errors.print_build_error(
34
+ e, logger: Bridgetown::LogAdapter.new(self.class.opts[:common_logger]), server: true
35
+ )
36
+ next exception_page(e) if ENV.fetch("RACK_ENV", nil) == "development"
37
+
38
+ output_folder = Bridgetown::Current.preloaded_configuration.destination
39
+ File.read(File.join(output_folder, "500.html"))
40
+ rescue Errno::ENOENT
41
+ "500 Internal Server Error"
42
+ end
43
+
44
+ ExceptionPage.class_eval do # rubocop:disable Metrics/BlockLength
45
+ def self.css
46
+ <<~CSS
47
+ html * { padding:0; margin:0; }
48
+ body * { padding:10px 20px; }
49
+ body * * { padding:0; }
50
+ body { font-family: -apple-system, sans-serif; font-size: 90%; }
51
+ body>div { border-bottom:1px solid #ddd; }
52
+ code { font-family: ui-monospace, monospace; }
53
+ h1 { font-weight: bold; margin-block-end: .8em; }
54
+ h2 { margin-block-end:.8em; }
55
+ h2 span { font-size:80%; color:#f7f7db; font-weight:normal; }
56
+ h3 { margin:1em 0 .5em 0; }
57
+ h4 { margin:0 0 .5em 0; font-weight: normal; }
58
+ table {
59
+ border:1px solid #ccc; border-collapse: collapse; background:white; }
60
+ tbody td, tbody th { vertical-align:top; padding:2px 3px; }
61
+ thead th {
62
+ padding:1px 6px 1px 3px; background:#fefefe; text-align:left;
63
+ font-weight:normal; font-size:11px; border:1px solid #ddd; }
64
+ tbody th { text-align:right; opacity: 0.7; padding-right:.5em; }
65
+ table.vars { margin:5px 0 2px 40px; }
66
+ table.vars td, table.req td { font-family: ui-monospace, monospace; }
67
+ table td.code { width:100%;}
68
+ table td.code div { overflow:hidden; }
69
+ table.source th { color:#666; }
70
+ table.source td {
71
+ font-family: ui-monospace, monospace; white-space:pre; border-bottom:1px solid #eee; }
72
+ ul.traceback { list-style-type:none; }
73
+ ul.traceback li.frame { margin-bottom:1em; }
74
+ div.context { margin: 10px 0; }
75
+ div.context ol {
76
+ padding-left:30px; margin:0 10px; list-style-position: inside; }
77
+ div.context ol li {
78
+ font-family: ui-monospace, monospace; white-space:pre; color:#666; cursor:pointer; }
79
+ div.context ol.context-line li { color:black; background-color:#f7f7db; }
80
+ div.context ol.context-line li span { float: right; }
81
+ div.commands { margin-left: 40px; }
82
+ div.commands a { color:black; text-decoration:none; }
83
+ #summary { background: #1D453C; color: white; }
84
+ #summary h2 { font-weight: normal; color: white; }
85
+ #summary ul#quicklinks { list-style-type: none; margin-bottom: 2em; }
86
+ #summary ul#quicklinks li { float: left; padding: 0 1em; }
87
+ #summary ul#quicklinks>li+li { border-left: 1px #666 solid; }
88
+ #summary a { color: #f47c3c; }
89
+ #explanation { background:#eee; }
90
+ #traceback { background: white; }
91
+ #requestinfo { background:#f6f6f6; padding-left:120px; }
92
+ #summary table { border:none; background:transparent; }
93
+ #requestinfo h2, #requestinfo h3 { position:relative; margin-left:-100px; }
94
+ #requestinfo h3 { margin-bottom:-1em; }
95
+ .error { background: #ffc; }
96
+ .specific { color:#cc3300; font-weight:bold; }
97
+ CSS
98
+ end
99
+ end
100
+
101
+ app.before do
102
+ if self.class.opts[:bridgetown_site]
103
+ # The site had previously been initialized via the bridgetown_ssr plugin
104
+ Bridgetown::Current.sites[self.class.opts[:bridgetown_site].label] =
105
+ self.class.opts[:bridgetown_site]
106
+ @context ||= SiteContext.new({ site: self.class.opts[:bridgetown_site] })
107
+ end
108
+ Bridgetown::Current.preloaded_configuration ||=
109
+ self.class.opts[:bridgetown_preloaded_config]
110
+
111
+ request.root do
112
+ output_folder = Bridgetown::Current.preloaded_configuration.destination
113
+ File.read(File.join(output_folder, "index.html"))
114
+ rescue StandardError
115
+ response.status = 500
116
+ "<p>ERROR: cannot find <code>index.html</code> in the output folder.</p>"
117
+ end
118
+ end
119
+ end
120
+
121
+ Roda::RodaRequest.alias_method :_previous_roda_cookies, :cookies
122
+
123
+ module RequestMethods
124
+ # Monkeypatch Roda/Rack's Request object so it returns a hash which allows for
125
+ # indifferent access
126
+ def cookies
127
+ # TODO: maybe replace with a simpler hash that offers an overloaded `[]` method
128
+ _previous_roda_cookies.with_indifferent_access
129
+ end
130
+
131
+ # Starts up the Bridgetown routing system
132
+ def bridgetown
133
+ Bridgetown::Rack::Routes.start!(scope)
134
+ end
135
+ end
136
+ end
137
+
138
+ register_plugin :bridgetown_server, BridgetownServer
139
+ end
140
+ end
@@ -24,10 +24,13 @@ gem "bridgetown", "~> <%= Bridgetown::VERSION %>"
24
24
  # Uncomment to add file-based dynamic routing to your project:
25
25
  # gem "bridgetown-routes", "~> <%= Bridgetown::VERSION %>"
26
26
 
27
+ # Puma is the Rack-compatible web server used by Bridgetown
28
+ # (you can optionally limit this to the "development" group)
29
+ gem "puma", "< 7"
30
+
27
31
  # Uncomment to use the Inspectors API to manipulate the output
28
32
  # of your HTML or XML resources:
29
33
  # gem "nokogiri", "~> 1.13"
30
34
 
31
- # Puma is a Rack-compatible server used by Bridgetown
32
- # (you can optionally limit this to the "development" group)
33
- gem "puma", "~> 5.6"
35
+ # Or for faster parsing of HTML-only resources via Inspectors, use Nokolexbor:
36
+ # gem "nokolexbor", "~> 0.4"
@@ -2,9 +2,11 @@
2
2
  # on the concept of a routing tree. Bridgetown uses it for its development
3
3
  # server, but you can also run it in production for fast, dynamic applications.
4
4
  #
5
- # Learn more at: http://roda.jeremyevans.net
5
+ # Learn more at: https://www.bridgetownrb.com/docs/routes
6
+
7
+ class RodaApp < Roda
8
+ plugin :bridgetown_server
6
9
 
7
- class RodaApp < Bridgetown::Rack::Roda
8
10
  # Some Roda configuration is handled in the `config/initializers.rb` file.
9
11
  # But you can also add additional Roda configuration here if needed.
10
12
 
@@ -5,7 +5,7 @@ date: <%= Time.now.strftime('%Y-%m-%d %H:%M:%S %z') %>
5
5
  categories: updates
6
6
  ---
7
7
 
8
- You’ll find this post in your `_posts` directory. Go ahead and edit it and re-build the site to see your changes. You can rebuild the site in many different ways, but the most common way is to run `bridgetown serve`, which launches a web server and auto-regenerates your site when a file is updated.
8
+ You’ll find this post in your `_posts` directory. Go ahead and edit it and re-build the site to see your changes. You can rebuild the site in many different ways, but the most common way is to run `bin/bridgetown start`, which launches a web server and auto-regenerates your site when a file is updated.
9
9
 
10
10
  Bridgetown requires blog post files to be named according to the following format:
11
11
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bridgetown-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.3.0.beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bridgetown Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-01-25 00:00:00.000000000 Z
11
+ date: 2023-05-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -112,28 +112,28 @@ dependencies:
112
112
  requirements:
113
113
  - - "~>"
114
114
  - !ruby/object:Gem::Version
115
- version: '1.0'
115
+ version: '2.0'
116
116
  type: :runtime
117
117
  prerelease: false
118
118
  version_requirements: !ruby/object:Gem::Requirement
119
119
  requirements:
120
120
  - - "~>"
121
121
  - !ruby/object:Gem::Version
122
- version: '1.0'
122
+ version: '2.0'
123
123
  - !ruby/object:Gem::Dependency
124
- name: faraday_middleware
124
+ name: faraday-follow_redirects
125
125
  requirement: !ruby/object:Gem::Requirement
126
126
  requirements:
127
127
  - - "~>"
128
128
  - !ruby/object:Gem::Version
129
- version: '1.0'
129
+ version: '0.3'
130
130
  type: :runtime
131
131
  prerelease: false
132
132
  version_requirements: !ruby/object:Gem::Requirement
133
133
  requirements:
134
134
  - - "~>"
135
135
  - !ruby/object:Gem::Version
136
- version: '1.0'
136
+ version: '0.3'
137
137
  - !ruby/object:Gem::Dependency
138
138
  name: hash_with_dot_access
139
139
  requirement: !ruby/object:Gem::Requirement
@@ -302,20 +302,6 @@ dependencies:
302
302
  - - "~>"
303
303
  - !ruby/object:Gem::Version
304
304
  version: '2.0'
305
- - !ruby/object:Gem::Dependency
306
- name: webrick
307
- requirement: !ruby/object:Gem::Requirement
308
- requirements:
309
- - - "~>"
310
- - !ruby/object:Gem::Version
311
- version: '1.7'
312
- type: :runtime
313
- prerelease: false
314
- version_requirements: !ruby/object:Gem::Requirement
315
- requirements:
316
- - - "~>"
317
- - !ruby/object:Gem::Version
318
- version: '1.7'
319
305
  - !ruby/object:Gem::Dependency
320
306
  name: zeitwerk
321
307
  requirement: !ruby/object:Gem::Requirement
@@ -368,8 +354,6 @@ files:
368
354
  - lib/bridgetown-core/commands/new.rb
369
355
  - lib/bridgetown-core/commands/plugins.rb
370
356
  - lib/bridgetown-core/commands/registrations.rb
371
- - lib/bridgetown-core/commands/serve.rb
372
- - lib/bridgetown-core/commands/serve/servlet.rb
373
357
  - lib/bridgetown-core/commands/start.rb
374
358
  - lib/bridgetown-core/commands/webpack.rb
375
359
  - lib/bridgetown-core/commands/webpack/enable-postcss.rb
@@ -487,7 +471,6 @@ files:
487
471
  - lib/bridgetown-core/plugin_manager.rb
488
472
  - lib/bridgetown-core/rack/boot.rb
489
473
  - lib/bridgetown-core/rack/logger.rb
490
- - lib/bridgetown-core/rack/roda.rb
491
474
  - lib/bridgetown-core/rack/routes.rb
492
475
  - lib/bridgetown-core/rack/static_indexes.rb
493
476
  - lib/bridgetown-core/reader.rb
@@ -529,7 +512,7 @@ files:
529
512
  - lib/bridgetown-core/version.rb
530
513
  - lib/bridgetown-core/watcher.rb
531
514
  - lib/bridgetown-core/yaml_parser.rb
532
- - lib/roda/plugins/bridgetown_boot.rb
515
+ - lib/roda/plugins/bridgetown_server.rb
533
516
  - lib/roda/plugins/bridgetown_ssr.rb
534
517
  - lib/roda/plugins/initializers.rb
535
518
  - lib/roda/plugins/method_override.rb
@@ -601,9 +584,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
601
584
  version: 2.7.0
602
585
  required_rubygems_version: !ruby/object:Gem::Requirement
603
586
  requirements:
604
- - - ">="
587
+ - - ">"
605
588
  - !ruby/object:Gem::Version
606
- version: '0'
589
+ version: 1.3.1
607
590
  requirements: []
608
591
  rubygems_version: 3.1.4
609
592
  signing_key:
@@ -1,68 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "webrick"
4
-
5
- module Bridgetown
6
- module Commands
7
- class Serve
8
- class Servlet < WEBrick::HTTPServlet::FileHandler
9
- DEFAULTS = {
10
- "Cache-Control" => "private, max-age=0, proxy-revalidate, " \
11
- "no-store, no-cache, must-revalidate",
12
- }.freeze
13
-
14
- def initialize(server, root, callbacks)
15
- # So we can access them easily.
16
- @bridgetown_opts = server.config[:BridgetownOptions]
17
- set_defaults
18
- super
19
- end
20
-
21
- def search_index_file(req, res)
22
- super ||
23
- search_file(req, res, ".html") ||
24
- search_file(req, res, ".xhtml")
25
- end
26
-
27
- # Add the ability to tap file.html the same way that Nginx does on our
28
- # Docker images (or on GitHub Pages.) The difference is that we might end
29
- # up with a different preference on which comes first.
30
-
31
- def search_file(req, res, basename)
32
- # /file.* > /file/index.html > /file.html
33
- super ||
34
- super(req, res, "#{basename}.html") ||
35
- super(req, res, "#{basename}.xhtml")
36
- end
37
-
38
- # rubocop:disable Naming/MethodName
39
- def do_GET(req, res)
40
- rtn = super
41
-
42
- validate_and_ensure_charset(req, res)
43
- res.header.merge!(@headers)
44
- rtn
45
- end
46
- # rubocop:enable Naming/MethodName
47
-
48
- private
49
-
50
- def validate_and_ensure_charset(_req, res)
51
- key = res.header.keys.grep(%r!content-type!i).first
52
- typ = res.header[key]
53
-
54
- return if %r!;\s*charset=!.match?(typ)
55
-
56
- res.header[key] = "#{typ}; charset=#{@bridgetown_opts["encoding"]}"
57
- end
58
-
59
- def set_defaults
60
- hash_ = @bridgetown_opts.fetch("webrick", {}).fetch("headers", {})
61
- DEFAULTS.each_with_object(@headers = hash_) do |(key, val), hash|
62
- hash[key] = val unless hash.key?(key)
63
- end
64
- end
65
- end
66
- end
67
- end
68
- end
@@ -1,253 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Bridgetown
4
- module Commands
5
- class Serve < Thor::Group
6
- extend BuildOptions
7
- extend Summarizable
8
- include ConfigurationOverridable
9
-
10
- Registrations.register do
11
- register(Serve, "serve", "serve", Serve.summary)
12
- end
13
-
14
- class_option :host, aliases: "-H", desc: "Host to bind to"
15
- class_option :port, aliases: "-P", desc: "Port to listen on"
16
- class_option :detach,
17
- aliases: "-B",
18
- type: :boolean,
19
- desc: "Run the server in the background"
20
- class_option :ssl_cert, desc: "X.509 (SSL) certificate."
21
- class_option :ssl_key, desc: "X.509 (SSL) Private Key."
22
- class_option :show_dir_listing,
23
- type: :boolean,
24
- desc: "Show a directory listing instead of loading your index file."
25
- class_option :skip_initial_build,
26
- type: :boolean,
27
- desc: "Skips the initial site build which occurs before the server is started."
28
- class_option :watch,
29
- type: :boolean,
30
- aliases: "-w",
31
- default: true,
32
- desc: "Watch for changes and rebuild"
33
-
34
- def self.banner
35
- "bridgetown serve [options]"
36
- end
37
- summary "DEPRECATED (Serve your site locally using WEBrick)"
38
-
39
- DIRECTORY_INDEX = %w(
40
- index.htm
41
- index.html
42
- index.rhtml
43
- index.xht
44
- index.xhtml
45
- index.cgi
46
- index.xml
47
- index.json
48
- ).freeze
49
-
50
- def serve
51
- Bridgetown::Deprecator.deprecation_message(
52
- "WEBrick (serve) will be removed in favor of Puma (start) in the next Bridgetown release"
53
- )
54
-
55
- @mutex = Mutex.new
56
- @run_cond = ConditionVariable.new
57
- @running = false
58
-
59
- no_watch = options["watch"] == false
60
-
61
- options = Thor::CoreExt::HashWithIndifferentAccess.new(self.options)
62
- options["serving"] = true
63
- options["watch"] = true unless no_watch
64
-
65
- config = configuration_with_overrides(options, Bridgetown::Current.preloaded_configuration)
66
- if Bridgetown.environment == "development"
67
- default_url(config).tap do |url|
68
- options["url"] = url
69
- config.url = url
70
- end
71
- end
72
-
73
- invoke(Build, [], options)
74
- start_server
75
- end
76
-
77
- protected
78
-
79
- def start_server
80
- destination = Bridgetown::Current.preloaded_configuration.destination
81
- setup(destination)
82
-
83
- start_up_webrick(destination)
84
- end
85
-
86
- def setup(destination)
87
- require_relative "serve/servlet"
88
-
89
- FileUtils.mkdir_p(destination)
90
- return unless File.exist?(File.join(destination, "404.html"))
91
-
92
- WEBrick::HTTPResponse.class_eval do
93
- def create_error_page
94
- @header["Content-Type"] = "text/html; charset=UTF-8"
95
- @body = File.read(File.join(@config[:DocumentRoot], "404.html"))
96
- end
97
- end
98
- end
99
-
100
- def webrick_opts(opts)
101
- opts = {
102
- BridgetownOptions: opts,
103
- DoNotReverseLookup: true,
104
- MimeTypes: mime_types,
105
- DocumentRoot: opts["destination"],
106
- StartCallback: start_callback(opts["detach"]),
107
- StopCallback: stop_callback(opts["detach"]),
108
- BindAddress: opts["host"],
109
- Port: opts["port"],
110
- DirectoryIndex: DIRECTORY_INDEX,
111
- }
112
-
113
- opts[:DirectoryIndex] = [] if opts[:BridgetownOptions]["show_dir_listing"]
114
-
115
- enable_ssl(opts)
116
- enable_logging(opts)
117
- opts
118
- end
119
-
120
- def start_up_webrick(destination)
121
- opts = Bridgetown::Current.preloaded_configuration
122
- @server = WEBrick::HTTPServer.new(webrick_opts(opts)).tap { |o| o.unmount("") }
123
- @server.mount(opts["base_path"].to_s, Servlet, destination, file_handler_opts)
124
-
125
- Bridgetown.logger.info "Server address:", server_address(@server, opts)
126
- launch_browser @server, opts if opts["open_url"]
127
- boot_or_detach @server, opts
128
- end
129
-
130
- def shutdown
131
- @server.shutdown if running?
132
- end
133
-
134
- def default_url(config)
135
- format_url(
136
- config["ssl_cert"] && config["ssl_key"],
137
- config["host"] == "127.0.0.1" ? "localhost" : config["host"],
138
- config["port"]
139
- )
140
- end
141
-
142
- def format_url(ssl_enabled, address, port, baseurl = nil)
143
- format("%<prefix>s://%<address>s:%<port>i%<baseurl>s",
144
- prefix: ssl_enabled ? "https" : "http",
145
- address: address,
146
- port: port,
147
- baseurl: baseurl ? "#{baseurl}/" : "")
148
- end
149
-
150
- # Recreate NondisclosureName under utf-8 circumstance
151
- def file_handler_opts
152
- WEBrick::Config::FileHandler.merge(
153
- FancyIndexing: true,
154
- NondisclosureName: [
155
- ".ht*", "~*",
156
- ]
157
- )
158
- end
159
-
160
- def server_address(server, options = {})
161
- format_url(
162
- server.config[:SSLEnable],
163
- server.config[:BindAddress],
164
- server.config[:Port],
165
- options["baseurl"]
166
- )
167
- end
168
-
169
- # Keep in our area with a thread or detach the server as requested
170
- # by the user. This method determines what we do based on what you
171
- # ask us to do.
172
- def boot_or_detach(server, opts)
173
- if opts["detach"]
174
- pid = Process.fork do
175
- server.start
176
- end
177
-
178
- Process.detach(pid)
179
- Bridgetown.logger.info "Server detached with pid '#{pid}'.", \
180
- "Run `pkill -f bridgetown' or `kill -9 #{pid}' " \
181
- "to stop the server."
182
- else
183
- t = Thread.new { server.start }
184
- trap("INT") { server.shutdown }
185
- t.join
186
- end
187
- end
188
-
189
- # Make the stack verbose if the user requests it.
190
- def enable_logging(opts)
191
- opts[:AccessLog] = []
192
- level = WEBrick::Log.const_get(opts[:BridgetownOptions]["verbose"] ? :DEBUG : :WARN)
193
- opts[:Logger] = WEBrick::Log.new($stdout, level)
194
- end
195
-
196
- # Add SSL to the stack if the user triggers --enable-ssl and they
197
- # provide both types of certificates commonly needed. Raise if they
198
- # forget to add one of the certificates.
199
- def enable_ssl(opts)
200
- cert, key, src =
201
- opts[:BridgetownOptions].values_at("ssl_cert", "ssl_key", "source")
202
-
203
- return if cert.nil? && key.nil?
204
- raise "Missing --ssl_cert or --ssl_key. Both are required." unless cert && key
205
-
206
- require "openssl"
207
- require "webrick/https"
208
-
209
- opts[:SSLCertificate] = OpenSSL::X509::Certificate.new(read_file(src, cert))
210
- begin
211
- opts[:SSLPrivateKey] = OpenSSL::PKey::RSA.new(read_file(src, key))
212
- rescue StandardError
213
- raise unless defined?(OpenSSL::PKey::EC)
214
-
215
- opts[:SSLPrivateKey] = OpenSSL::PKey::EC.new(read_file(src, key))
216
- end
217
- opts[:SSLEnable] = true
218
- end
219
-
220
- def start_callback(detached)
221
- return if detached
222
-
223
- proc do
224
- @mutex.synchronize do
225
- @running = true
226
- Bridgetown.logger.info("Server running…", "press ctrl-c to stop.")
227
- @run_cond.broadcast
228
- end
229
- end
230
- end
231
-
232
- def stop_callback(detached)
233
- return if detached
234
-
235
- proc do
236
- @mutex.synchronize do
237
- @running = false
238
- @run_cond.broadcast
239
- end
240
- end
241
- end
242
-
243
- def mime_types
244
- file = File.expand_path("../mime.types", __dir__)
245
- WEBrick::HTTPUtils.load_mime_types(file)
246
- end
247
-
248
- def read_file(source_dir, file_path)
249
- File.read(Bridgetown.sanitized_path(source_dir, file_path))
250
- end
251
- end
252
- end
253
- end
@@ -1,157 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- unless Bridgetown::Current.preloaded_configuration
4
- raise "You must supply a preloaded configuration before loading Bridgetown's Roda superclass"
5
- end
6
-
7
- module Bridgetown
8
- module Rack
9
- class Roda < ::Roda
10
- class << self
11
- def inherited(klass)
12
- super
13
- klass.plugin :initializers
14
- end
15
-
16
- # rubocop:disable Bridgetown/NoPutsAllowed
17
- def print_routes
18
- # TODO: this needs to be fully documented
19
- routes = begin
20
- JSON.parse(
21
- File.read(
22
- File.join(Bridgetown::Current.preloaded_configuration.root_dir, ".routes.json")
23
- )
24
- )
25
- rescue StandardError
26
- []
27
- end
28
- puts
29
- puts "Routes:"
30
- puts "======="
31
- if routes.blank?
32
- puts "No routes found. Have you commented all of your routes?"
33
- puts "Documentation: https://github.com/jeremyevans/roda-route_list#basic-usage-"
34
- end
35
-
36
- routes.each do |route|
37
- puts [
38
- route["methods"]&.join("|") || "GET",
39
- route["path"],
40
- route["file"] ? "\n File: #{route["file"]}" : nil,
41
- ].compact.join(" ")
42
- end
43
- puts
44
- end
45
- # rubocop:enable Bridgetown/NoPutsAllowed
46
- end
47
-
48
- SiteContext = Struct.new(:registers) # for use by Liquid-esque URL helpers
49
-
50
- plugin :method_override
51
- plugin :all_verbs
52
- plugin :hooks
53
- plugin :common_logger, Bridgetown::Rack::Logger.new($stdout), method: :info
54
- plugin :json
55
- plugin :json_parser
56
- plugin :indifferent_params
57
- plugin :cookies
58
- plugin :streaming
59
- plugin :bridgetown_boot
60
- plugin :public, root: Bridgetown::Current.preloaded_configuration.destination
61
- plugin :not_found do
62
- output_folder = Bridgetown::Current.preloaded_configuration.destination
63
- File.read(File.join(output_folder, "404.html"))
64
- rescue Errno::ENOENT
65
- "404 Not Found"
66
- end
67
- plugin :exception_page
68
- plugin :error_handler do |e|
69
- Bridgetown::Errors.print_build_error(
70
- e, logger: Bridgetown::LogAdapter.new(self.class.opts[:common_logger])
71
- )
72
- next exception_page(e) if ENV.fetch("RACK_ENV", nil) == "development"
73
-
74
- output_folder = Bridgetown::Current.preloaded_configuration.destination
75
- File.read(File.join(output_folder, "500.html"))
76
- rescue Errno::ENOENT
77
- "500 Internal Server Error"
78
- end
79
-
80
- ::Roda::RodaPlugins::ExceptionPage.class_eval do
81
- def self.css
82
- <<~CSS
83
- html * { padding:0; margin:0; }
84
- body * { padding:10px 20px; }
85
- body * * { padding:0; }
86
- body { font-family: -apple-system, sans-serif; font-size: 90%; }
87
- body>div { border-bottom:1px solid #ddd; }
88
- code { font-family: ui-monospace, monospace; }
89
- h1 { font-weight: bold; margin-block-end: .8em; }
90
- h2 { margin-block-end:.8em; }
91
- h2 span { font-size:80%; color:#f7f7db; font-weight:normal; }
92
- h3 { margin:1em 0 .5em 0; }
93
- h4 { margin:0 0 .5em 0; font-weight: normal; }
94
- table {
95
- border:1px solid #ccc; border-collapse: collapse; background:white; }
96
- tbody td, tbody th { vertical-align:top; padding:2px 3px; }
97
- thead th {
98
- padding:1px 6px 1px 3px; background:#fefefe; text-align:left;
99
- font-weight:normal; font-size:11px; border:1px solid #ddd; }
100
- tbody th { text-align:right; opacity: 0.7; padding-right:.5em; }
101
- table.vars { margin:5px 0 2px 40px; }
102
- table.vars td, table.req td { font-family: ui-monospace, monospace; }
103
- table td.code { width:100%;}
104
- table td.code div { overflow:hidden; }
105
- table.source th { color:#666; }
106
- table.source td {
107
- font-family: ui-monospace, monospace; white-space:pre; border-bottom:1px solid #eee; }
108
- ul.traceback { list-style-type:none; }
109
- ul.traceback li.frame { margin-bottom:1em; }
110
- div.context { margin: 10px 0; }
111
- div.context ol {
112
- padding-left:30px; margin:0 10px; list-style-position: inside; }
113
- div.context ol li {
114
- font-family: ui-monospace, monospace; white-space:pre; color:#666; cursor:pointer; }
115
- div.context ol.context-line li { color:black; background-color:#f7f7db; }
116
- div.context ol.context-line li span { float: right; }
117
- div.commands { margin-left: 40px; }
118
- div.commands a { color:black; text-decoration:none; }
119
- #summary { background: #1D453C; color: white; }
120
- #summary h2 { font-weight: normal; color: white; }
121
- #summary ul#quicklinks { list-style-type: none; margin-bottom: 2em; }
122
- #summary ul#quicklinks li { float: left; padding: 0 1em; }
123
- #summary ul#quicklinks>li+li { border-left: 1px #666 solid; }
124
- #summary a { color: #f47c3c; }
125
- #explanation { background:#eee; }
126
- #traceback { background: white; }
127
- #requestinfo { background:#f6f6f6; padding-left:120px; }
128
- #summary table { border:none; background:transparent; }
129
- #requestinfo h2, #requestinfo h3 { position:relative; margin-left:-100px; }
130
- #requestinfo h3 { margin-bottom:-1em; }
131
- .error { background: #ffc; }
132
- .specific { color:#cc3300; font-weight:bold; }
133
- CSS
134
- end
135
- end
136
-
137
- before do
138
- if self.class.opts[:bridgetown_site]
139
- # The site had previously been initialized via the bridgetown_ssr plugin
140
- Bridgetown::Current.sites[self.class.opts[:bridgetown_site].label] =
141
- self.class.opts[:bridgetown_site]
142
- @context ||= SiteContext.new({ site: self.class.opts[:bridgetown_site] })
143
- end
144
- Bridgetown::Current.preloaded_configuration ||=
145
- self.class.opts[:bridgetown_preloaded_config]
146
-
147
- request.root do
148
- output_folder = Bridgetown::Current.preloaded_configuration.destination
149
- File.read(File.join(output_folder, "index.html"))
150
- rescue StandardError
151
- response.status = 500
152
- "<p>ERROR: cannot find <code>index.html</code> in the output folder.</p>"
153
- end
154
- end
155
- end
156
- end
157
- end
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Roda
4
- module RodaPlugins
5
- module BridgetownBoot
6
- Roda::RodaRequest.alias_method :_previous_roda_cookies, :cookies
7
-
8
- module RequestMethods
9
- # Monkeypatch Roda/Rack's Request object so it returns a hash which allows for
10
- # indifferent access
11
- def cookies
12
- # TODO: maybe replace with a simpler hash that offers an overloaded `[]` method
13
- _previous_roda_cookies.with_indifferent_access
14
- end
15
-
16
- # Starts up the Bridgetown routing system
17
- def bridgetown
18
- Bridgetown::Rack::Routes.start!(scope)
19
- end
20
- end
21
- end
22
-
23
- register_plugin :bridgetown_boot, BridgetownBoot
24
- end
25
- end