bridgetown-core 1.2.0 → 1.3.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
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