bridgetown-core 1.2.0 → 1.3.0.beta2

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/bridgetown-core.gemspec +21 -22
  3. data/lib/bridgetown-core/collection.rb +1 -1
  4. data/lib/bridgetown-core/commands/build.rb +3 -0
  5. data/lib/bridgetown-core/commands/console.rb +0 -1
  6. data/lib/bridgetown-core/commands/esbuild/esbuild.config.js +12 -2
  7. data/lib/bridgetown-core/commands/esbuild/esbuild.defaults.js.erb +64 -18
  8. data/lib/bridgetown-core/commands/esbuild/jsconfig.json +10 -0
  9. data/lib/bridgetown-core/commands/esbuild/setup.rb +1 -0
  10. data/lib/bridgetown-core/commands/esbuild/update.rb +3 -0
  11. data/lib/bridgetown-core/commands/new.rb +2 -0
  12. data/lib/bridgetown-core/configuration/configuration_dsl.rb +1 -1
  13. data/lib/bridgetown-core/configuration.rb +13 -12
  14. data/lib/bridgetown-core/configurations/is-land.rb +15 -0
  15. data/lib/bridgetown-core/configurations/lit/lit-components-entry.js +1 -1
  16. data/lib/bridgetown-core/configurations/lit.rb +9 -54
  17. data/lib/bridgetown-core/configurations/purgecss.rb +1 -1
  18. data/lib/bridgetown-core/configurations/ruby2js/ruby2js.rb +10 -0
  19. data/lib/bridgetown-core/configurations/ruby2js.rb +12 -40
  20. data/lib/bridgetown-core/configurations/turbo.rb +17 -7
  21. data/lib/bridgetown-core/errors.rb +10 -1
  22. data/lib/bridgetown-core/filters/localization_filters.rb +11 -0
  23. data/lib/bridgetown-core/helpers.rb +34 -0
  24. data/lib/bridgetown-core/plugin_manager.rb +0 -24
  25. data/lib/bridgetown-core/rack/boot.rb +13 -1
  26. data/lib/bridgetown-core/rack/routes.rb +40 -6
  27. data/lib/bridgetown-core/readers/layout_reader.rb +2 -2
  28. data/lib/bridgetown-core/tags/dsd.rb +15 -0
  29. data/lib/bridgetown-core/tags/l.rb +14 -0
  30. data/lib/bridgetown-core/utils/aux.rb +2 -0
  31. data/lib/bridgetown-core/utils/loaders_manager.rb +7 -0
  32. data/lib/bridgetown-core/utils.rb +52 -5
  33. data/lib/bridgetown-core/version.rb +2 -2
  34. data/lib/bridgetown-core/watcher.rb +15 -8
  35. data/lib/bridgetown-core.rb +12 -0
  36. data/lib/roda/plugins/bridgetown_server.rb +140 -0
  37. data/lib/site_template/Gemfile.erb +6 -3
  38. data/lib/site_template/frontend/javascript/index.js.erb +10 -1
  39. data/lib/site_template/package.json.erb +6 -6
  40. data/lib/site_template/server/roda_app.rb +4 -2
  41. data/lib/site_template/src/_posts/0000-00-00-welcome-to-bridgetown.md.erb +1 -1
  42. metadata +16 -27
  43. data/lib/bridgetown-core/commands/serve/servlet.rb +0 -68
  44. data/lib/bridgetown-core/commands/serve.rb +0 -253
  45. data/lib/bridgetown-core/rack/roda.rb +0 -157
  46. data/lib/roda/plugins/bridgetown_boot.rb +0 -25
@@ -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
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bridgetown
4
+ module Filters
5
+ module LocalizationFilters
6
+ def l(input)
7
+ I18n.l(input.to_s)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -135,6 +135,14 @@ module Bridgetown
135
135
  I18n.send :t, *args, **kwargs
136
136
  end
137
137
 
138
+ # Forward all arguments to I18n.l method
139
+ #
140
+ # @return [String] the localized string
141
+ # @see I18n
142
+ def l(*args, **kwargs)
143
+ I18n.send :l, *args, **kwargs
144
+ end
145
+
138
146
  # For template contexts where ActiveSupport's output safety is loaded, we
139
147
  # can ensure a string has been marked safe
140
148
  #
@@ -221,6 +229,32 @@ module Bridgetown
221
229
  end
222
230
  end
223
231
 
232
+ def dsd(input = nil, &block)
233
+ tmpl_content = block.nil? ? input.to_s : view.capture(&block)
234
+
235
+ Bridgetown::Utils.dsd_tag(tmpl_content)
236
+ end
237
+
238
+ def dsd_style
239
+ tmpl_path = caller_locations(1, 2).find do |loc|
240
+ loc.label.include?("method_missing").!
241
+ end&.path
242
+
243
+ return unless tmpl_path # virtually guaranteed not to happen
244
+
245
+ tmpl_basename = File.basename(tmpl_path, ".*")
246
+ style_path = File.join(File.dirname(tmpl_path), "#{tmpl_basename}.dsd.css")
247
+
248
+ unless File.file?(style_path)
249
+ raise Bridgetown::Errors::FatalException, "Missing stylesheet at #{style_path}"
250
+ end
251
+
252
+ style_tag = site.tmp_cache["dsd_style:#{style_path}"] ||=
253
+ "<style>#{File.read(style_path)}</style>"
254
+
255
+ style_tag.html_safe
256
+ end
257
+
224
258
  private
225
259
 
226
260
  # Covert an underscored value into a dashed string.
@@ -206,30 +206,6 @@ module Bridgetown
206
206
  sorted_plugin_files.each do |plugin_file|
207
207
  self.class.add_registered_plugin plugin_file
208
208
  end
209
- next if site.config[:plugins_use_zeitwerk]
210
-
211
- Deprecator.deprecation_message(
212
- "The `plugins_use_zeitwerk' configuration option will be removed in the next version " \
213
- "of Bridgetown (aka will be permanently set to \"true\")"
214
- )
215
- Bridgetown::Utils::RequireGems.require_with_graceful_fail(sorted_plugin_files)
216
- end
217
- end
218
-
219
- # Reloads .rb plugin files via the watcher
220
- # DEPRECATED (not necessary with Zeitwerk)
221
- #
222
- # @return [void]
223
- def reload_plugin_files
224
- return if site.config[:plugins_use_zeitwerk]
225
-
226
- plugins_path.each do |plugin_search_path|
227
- plugin_files = Utils.safe_glob(plugin_search_path, File.join("**", "*.rb"))
228
- Array(plugin_files).each do |name|
229
- Bridgetown.logger.debug "Reloading:", name.to_s
230
- self.class.add_registered_plugin name
231
- load name
232
- end
233
209
  end
234
210
  end
235
211
 
@@ -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
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bridgetown
4
+ module Tags
5
+ class DSDTag < Liquid::Block
6
+ def render(_context)
7
+ template_content = super
8
+
9
+ Bridgetown::Utils.dsd_tag(template_content)
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ Liquid::Template.register_tag("dsd", Bridgetown::Tags::DSDTag)
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bridgetown
4
+ module Tags
5
+ class LocalizationTag < Liquid::Tag
6
+ def render(_context)
7
+ key = @markup.strip
8
+ I18n.l(key)
9
+ end
10
+ end
11
+ end
12
+ end
13
+
14
+ Liquid::Template.register_tag("l", Bridgetown::Tags::LocalizationTag)
@@ -29,6 +29,8 @@ module Bridgetown
29
29
  loop do
30
30
  line = rd.gets
31
31
  line.to_s.lines.map(&:chomp).each do |message|
32
+ next if name == "Frontend" && %r{ELIFECYCLE.*?Command failed}.match?(message)
33
+
32
34
  output = +""
33
35
  output << with_color(color, "[#{name}] ") if color
34
36
  output << message
@@ -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
@@ -60,6 +62,9 @@ module Bridgetown
60
62
  end
61
63
  loader.enable_reloading if reloading_enabled?(load_path)
62
64
  loader.ignore(File.join(load_path, "**", "*.js.rb"))
65
+ loader.ignore(
66
+ File.join(File.expand_path(config[:islands_dir], config[:source]), "routes")
67
+ )
63
68
  config.autoloader_collapsed_paths.each do |collapsed_path|
64
69
  next unless collapsed_path.starts_with?(load_path)
65
70
 
@@ -75,6 +80,8 @@ module Bridgetown
75
80
  end
76
81
 
77
82
  def reload_loaders
83
+ FileUtils.rm_f(Bridgetown.build_errors_path)
84
+
78
85
  @loaders.each do |load_path, loader|
79
86
  next unless reloading_enabled?(load_path)
80
87
 
@@ -412,7 +412,7 @@ module Bridgetown
412
412
  end&.last
413
413
  end
414
414
 
415
- return log_frontend_asset_error(site, asset_type) if asset_path.nil?
415
+ return log_frontend_asset_error(site, "`#{asset_type}' asset") if asset_path.nil?
416
416
 
417
417
  static_frontend_path site, [asset_path]
418
418
  end
@@ -429,14 +429,16 @@ module Bridgetown
429
429
 
430
430
  def log_frontend_asset_error(site, asset_type)
431
431
  site.data[:__frontend_asset_errors] ||= {}
432
- site.data[:__frontend_asset_errors][asset_type] ||=
432
+ site.data[:__frontend_asset_errors][asset_type] ||= begin
433
+ Bridgetown.logger.warn("#{frontend_bundler_type}:", "The #{asset_type} could not be found.")
433
434
  Bridgetown.logger.warn(
434
435
  "#{frontend_bundler_type}:",
435
- "There was an error parsing your #{asset_type} file. \
436
- Please check your #{asset_type} file for any errors."
436
+ "Double-check your frontend config or re-run `bin/bridgetown frontend:build'"
437
437
  )
438
+ true
439
+ end
438
440
 
439
- "MISSING_#{frontend_bundler_type.upcase}_MANIFEST_FILE"
441
+ "MISSING_#{frontend_bundler_type.upcase}_ASSET"
440
442
  end
441
443
 
442
444
  def frontend_bundler_type(cwd = Dir.pwd)
@@ -449,6 +451,27 @@ module Bridgetown
449
451
  end
450
452
  end
451
453
 
454
+ def update_esbuild_autogenerated_config(config)
455
+ defaults_file = File.join(config[:root_dir], "config", "esbuild.defaults.js")
456
+ return unless File.exist?(defaults_file)
457
+
458
+ config_hash = {
459
+ source: Pathname.new(config[:source]).relative_path_from(config[:root_dir]),
460
+ destination: Pathname.new(config[:destination]).relative_path_from(config[:root_dir]),
461
+ componentsDir: config[:components_dir],
462
+ islandsDir: config[:islands_dir],
463
+ }
464
+
465
+ defaults_file_contents = File.read(defaults_file)
466
+ File.write(
467
+ defaults_file,
468
+ defaults_file_contents.sub(
469
+ %r{(const autogeneratedBridgetownConfig = ){\n.*?}}m,
470
+ "\\1#{JSON.pretty_generate config_hash}"
471
+ )
472
+ )
473
+ end
474
+
452
475
  def default_github_branch_name(repo_url)
453
476
  repo_match = Bridgetown::Commands::Actions::GITHUB_REPO_REGEX.match(repo_url)
454
477
  api_endpoint = "https://api.github.com/repos/#{repo_match[1]}"
@@ -466,6 +489,7 @@ module Bridgetown
466
489
  function startReloadConnection() {
467
490
  const evtSource = new EventSource("#{site.base_path(strip_slash_only: true)}/_bridgetown/live_reload")
468
491
  evtSource.onmessage = event => {
492
+ if (document.querySelector("#bridgetown-build-error")) document.querySelector("#bridgetown-build-error").close()
469
493
  if (event.data == "reloaded!") {
470
494
  location.reload()
471
495
  } else {
@@ -477,6 +501,23 @@ module Bridgetown
477
501
  }
478
502
  }
479
503
  }
504
+ evtSource.addEventListener("builderror", event => {
505
+ let dialog = document.querySelector("#bridgetown-build-error")
506
+ if (!dialog) {
507
+ dialog = document.createElement("dialog")
508
+ dialog.id = "bridgetown-build-error"
509
+ dialog.style.borderColor = "red"
510
+ dialog.style.fontSize = "110%"
511
+ dialog.innerHTML = `
512
+ <p style="color:red">There was an error when building the site:</p>
513
+ <output><pre></pre></output>
514
+ <p><small>Check your Bridgetown logs for further details.</small></p>
515
+ `
516
+ document.body.appendChild(dialog)
517
+ dialog.showModal()
518
+ }
519
+ dialog.querySelector("pre").textContent = JSON.parse(event.data)
520
+ })
480
521
  evtSource.onerror = event => {
481
522
  if (evtSource.readyState === 2) {
482
523
  // reconnect with new object
@@ -505,6 +546,12 @@ module Bridgetown
505
546
  end
506
547
  end
507
548
 
549
+ def dsd_tag(input, shadow_root_mode: :open)
550
+ raise ArgumentError unless [:open, :closed].include? shadow_root_mode
551
+
552
+ %(<template shadowrootmode="#{shadow_root_mode}">#{input}</template>).html_safe
553
+ end
554
+
508
555
  private
509
556
 
510
557
  def merge_values(target, overwrite)
@@ -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.beta2"
5
+ CODE_NAME = "Kelly Butte"
6
6
  end
@@ -55,7 +55,7 @@ module Bridgetown
55
55
  # Start a listener to watch for changes and call {#reload_site}
56
56
  #
57
57
  # @param (see #watch)
58
- def listen(site, options)
58
+ def listen(site, options) # rubocop:disable Metrics/MethodLength
59
59
  bundling_path = site.frontend_bundling_path
60
60
  FileUtils.mkdir_p(bundling_path)
61
61
  Listen.to(
@@ -66,12 +66,17 @@ module Bridgetown
66
66
  force_polling: options["force_polling"]
67
67
  ) do |modified, added, removed|
68
68
  c = modified + added + removed
69
+
70
+ # NOTE: inexplicably, this matcher doesn't work with the Listen gem, so
71
+ # we have to run it here manually
72
+ c.reject! { component_frontend_matcher(options).match? _1 }
69
73
  n = c.length
74
+ next if n.zero?
70
75
 
71
76
  unless site.ssr?
72
77
  Bridgetown.logger.info(
73
78
  "Reloading…",
74
- "#{n} file#{"s" if c.length > 1} changed at #{Time.now.strftime("%Y-%m-%d %H:%M:%S")}"
79
+ "#{n} file#{"s" if n > 1} changed at #{Time.now.strftime("%Y-%m-%d %H:%M:%S")}"
75
80
  )
76
81
  c.each { |path| Bridgetown.logger.info "", "- #{path["#{site.root_dir}/".length..]}" }
77
82
  end
@@ -93,7 +98,6 @@ module Bridgetown
93
98
  catch :halt do
94
99
  Bridgetown::Hooks.trigger :site, :pre_reload, site, paths
95
100
  Bridgetown::Hooks.clear_reloadable_hooks
96
- site.plugin_manager.reload_plugin_files
97
101
  site.loaders_manager.reload_loaders
98
102
  Bridgetown::Hooks.trigger :site, :post_reload, site, paths
99
103
 
@@ -106,7 +110,7 @@ module Bridgetown
106
110
  end
107
111
  Bridgetown.logger.info "Done! 🎉", "#{"Completed".bold.green} in less than " \
108
112
  "#{(Time.now - time).ceil(2)} seconds."
109
- rescue StandardError => e
113
+ rescue StandardError, SyntaxError => e
110
114
  Bridgetown::Errors.print_build_error(e, trace: options[:trace])
111
115
  end
112
116
  Bridgetown.logger.info ""
@@ -124,12 +128,15 @@ module Bridgetown
124
128
  end
125
129
 
126
130
  def custom_excludes(options)
127
- Array(options["exclude"]).map { |e| Bridgetown.sanitized_path(options["source"], e) }
131
+ Array(options["exclude"]).map { |e| Bridgetown.sanitized_path(options["root_dir"], e) }
128
132
  end
129
133
 
130
- def component_frontend_matcher
131
- %r{_components/.*?(\.js|\.jsx|\.js\.rb|\.css)$}
134
+ # rubocop:disable Layout/LineLength
135
+ def component_frontend_matcher(options)
136
+ @fematcher ||=
137
+ %r{(#{options[:components_dir]}|#{options[:islands_dir]})/(?:[^.]+|\.(?!dsd))+(\.js|\.jsx|\.js\.rb|\.css)$}
132
138
  end
139
+ # rubocop:enable Layout/LineLength
133
140
 
134
141
  def to_exclude(options)
135
142
  [
@@ -162,7 +169,7 @@ module Bridgetown
162
169
  rescue ArgumentError
163
170
  # Could not find a relative path
164
171
  end
165
- end + [component_frontend_matcher] + [%r!^\.bridgetown-metadata!]
172
+ end
166
173
  end
167
174
 
168
175
  def sleep_forever
@@ -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