bridgetown-core 2.1.2 → 2.2.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7211217cbf20e2389a43565b4ccc0c140d50266ab43378c1f4cc12968ab26c7a
4
- data.tar.gz: 3a760b37f3f19f2b998c97f8ba02458dad733ba8d714fb89d1db9c2d685a3eff
3
+ metadata.gz: e9715f653a27d94059baa731a1857efa33b69db2206ba2d18b8f4222765f093b
4
+ data.tar.gz: 0214baa25fa3ed326df7e62f24a3996f018c6586cc5e75b5882d91594bc26eb0
5
5
  SHA512:
6
- metadata.gz: 895f9f4b5a00b006513b765d4f01c4cb604952358d5b9779a98755dc86a56b472bf765f498f4129a25998dce70a75dad798306ba543401bfaa01183c78d6f5ba
7
- data.tar.gz: f4c0fb51cd8bb20367d47af49c5a6b6b8f52eb2b4cbe1dbf8df863e511f254f834196a02af3a92b47b113828169224d3f79067c6f9319b221ab7b84f6389a16b
6
+ metadata.gz: 7e74294c2a04ab34b80ea4ebe0e917dfc0d23b6b53003c355f25739a3d01ddc33eae1eb722b0cc3489c84d33dd3adfcc49dc2d497785932f4e6c0ee14b5ead75
7
+ data.tar.gz: 614ef83083050fcff1e2a8585dc06591917448273bcf95f29c9aaeba5650dc263d10a49652c9d1e29594543fc7465ebb7cbb8035c57e96ea60d8512a79f0e489
data/bin/bridgetown CHANGED
@@ -31,7 +31,7 @@ custom_commands_file = File.expand_path("config/custom_commands.rb", Dir.pwd)
31
31
  require custom_commands_file if File.exist?(custom_commands_file)
32
32
 
33
33
  begin
34
- Bridgetown::Commands::Application.parse(ARGV).call
34
+ Bridgetown::Commands::Application.parse(ARGV.dup).call
35
35
  rescue Samovar::MissingValueError => e
36
36
  puts "#{"Command error:".bold.red} #{e.message.to_s.yellow}\n\n"
37
37
  e.command.print_usage
@@ -39,7 +39,7 @@ Gem::Specification.new do |spec|
39
39
  spec.add_dependency("csv", "~> 3.2")
40
40
  spec.add_dependency("erubi", "~> 1.9")
41
41
  spec.add_dependency("faraday", "~> 2.0")
42
- spec.add_dependency("faraday-follow_redirects", "~> 0.3")
42
+ spec.add_dependency("faraday-follow_redirects", "~> 0.5")
43
43
  spec.add_dependency("freyia", ">= 0.5")
44
44
  spec.add_dependency("i18n", "~> 1.0")
45
45
  spec.add_dependency("irb", ">= 1.14")
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "bridgetown-core/commands/start"
4
+
3
5
  module Bridgetown
4
6
  module Commands
5
7
  class Build < Bridgetown::Command
@@ -9,6 +11,7 @@ module Bridgetown
9
11
 
10
12
  options do
11
13
  BuildOptions.include_options(self)
14
+ Start::StartOptions.include_options(self) if ARGV[0] == "start"
12
15
  option "-w/--watch", "Watch for changes and rebuild"
13
16
  end
14
17
 
@@ -16,11 +16,10 @@ const autogeneratedBridgetownConfig = {
16
16
  "islandsDir": "_islands"
17
17
  }
18
18
 
19
- import path from "path"
20
- import fsLib from "fs"
19
+ import path from "node:path"
20
+ import fsLib from "node:fs"
21
21
  const fs = fsLib.promises
22
- import { pathToFileURL, fileURLToPath } from "url"
23
- import { glob } from "glob"
22
+ import { pathToFileURL, fileURLToPath } from "node:url"
24
23
  import postcss from "postcss"
25
24
  import postCssImport from "postcss-import"
26
25
  import readCache from "read-cache"
@@ -72,7 +71,7 @@ const importGlobPlugin = (options, bridgetownConfig) => ({
72
71
  })
73
72
 
74
73
  build.onLoad({ filter: /.*/, namespace: "import-glob" }, async (args) => {
75
- const files = glob.sync(args.pluginData.path, {
74
+ const files = fsLib.globSync(args.pluginData.path, {
76
75
  cwd: args.pluginData.resolveDir,
77
76
  })
78
77
  .filter(module => options.excludeFilter ? !options.excludeFilter.test(module) : true)
@@ -317,7 +316,7 @@ export default async (esbuildOptions, ...args) => {
317
316
  const entryPoints = esbuildOptions.entryPoints || ["./frontend/javascript/index.js"]
318
317
  if (esbuildOptions.entryPoints) delete esbuildOptions.entryPoints
319
318
 
320
- const islands = glob.sync(`./${bridgetownConfig.source}/${bridgetownConfig.islandsDir}/*.{js,js.rb}`).map(item => `./${item}`)
319
+ const islands = fsLib.globSync(`./${bridgetownConfig.source}/${bridgetownConfig.islandsDir}/*.{js,js.rb}`).map(item => `./${item}`)
321
320
  const islandsAsObject = islands.reduce((obj, str) => obj[str] = str, {})
322
321
 
323
322
  esbuild.context({
@@ -16,7 +16,8 @@ end
16
16
  gsub_file "postcss.config.js", "module.exports =", "export default"
17
17
  gsub_file "esbuild.config.js", 'const build = require("./config/esbuild.defaults.js")',
18
18
  'import build from "./config/esbuild.defaults.js"'
19
- add_npm_package "esbuild@latest glob@latest" unless Bridgetown.env.test?
19
+ add_npm_package "esbuild@latest" unless Bridgetown.env.test?
20
+ remove_npm_package "glob"
20
21
 
21
22
  say "\n🎉 esbuild configuration updated successfully!"
22
23
  say "You may need to add `$styles/` to the front of your main CSS imports."
@@ -5,7 +5,7 @@ module Bridgetown
5
5
  class Help < Bridgetown::Command
6
6
  self.description = "Show detailed command usage information and exit"
7
7
 
8
- one :command, "The name of a Bridgetown command", required: true
8
+ one :command, "The name of a Bridgetown command", required: false
9
9
 
10
10
  def call
11
11
  found_command = parent.class.table[:command].commands[command]
@@ -14,7 +14,7 @@ module Bridgetown
14
14
 
15
15
  return if found_command
16
16
 
17
- puts "Unknown command: #{command}\n\n"
17
+ puts "Unknown command: #{command}\n\n" if command
18
18
  parent.print_usage
19
19
  end
20
20
  end
@@ -18,6 +18,7 @@ module Bridgetown
18
18
  "Comma separated list of bundled configurations to perform"
19
19
  option "-h/--help", "Print help for the new command"
20
20
  option "-t/--templates <erb|serbea|liquid>", "Preferred template engine (defaults to ERB)"
21
+ option "-s/--server <falcon|puma>", "The Rack-compliant web server to install"
21
22
  option "--force", "Force creation even if PATH already exists"
22
23
  option "--skip-bundle", "Skip 'bundle install'"
23
24
  option "--skip-npm", "Skip 'npm install'"
@@ -25,6 +26,7 @@ module Bridgetown
25
26
  end
26
27
 
27
28
  DOCSURL = "https://bridgetownrb.com/docs"
29
+ SUPPORTED_RACK_SERVERS = %w[falcon puma].freeze
28
30
 
29
31
  def self.exit_on_failure?
30
32
  false
@@ -83,6 +85,10 @@ module Bridgetown
83
85
  !options[:use_sass]
84
86
  end
85
87
 
88
+ def rack_server_option
89
+ SUPPORTED_RACK_SERVERS.find { _1 == options[:server] } || "falcon"
90
+ end
91
+
86
92
  def disable_postcss?
87
93
  # TODO: add option not to use postcss/sass at all
88
94
  false
@@ -105,6 +111,8 @@ module Bridgetown
105
111
  template("src/posts.md.erb", "src/posts.md")
106
112
  copy_file("frontend/styles/syntax-highlighting.css")
107
113
 
114
+ setup_rack_server
115
+
108
116
  case options[:templates]
109
117
  when "serbea"
110
118
  setup_serbea_templates
@@ -144,6 +152,14 @@ module Bridgetown
144
152
  RUBY
145
153
  end
146
154
 
155
+ def setup_rack_server
156
+ if rack_server_option == "puma"
157
+ template "config/puma.erb", "config/puma.rb"
158
+ else
159
+ template "config/falcon.erb", "config/falcon.rb"
160
+ end
161
+ end
162
+
147
163
  def configure_sass
148
164
  template("postcss.config.js.erb", "postcss.config.js") unless disable_postcss?
149
165
  copy_file("frontend/styles/index.css", "frontend/styles/index.scss")
@@ -15,10 +15,6 @@ module Bridgetown
15
15
  server.to_s.split("::").last
16
16
  end
17
17
 
18
- def using_puma?
19
- name == "Puma"
20
- end
21
-
22
18
  def serveable?
23
19
  server
24
20
  true
@@ -29,6 +25,19 @@ module Bridgetown
29
25
 
30
26
  module Commands
31
27
  class Start < Bridgetown::Command
28
+ module StartOptions
29
+ def self.include_options(klass)
30
+ klass.option "-P/--port <NUM>",
31
+ "Serve your site on the specified port. Defaults to 4000",
32
+ type: Integer
33
+ klass.option "-B/--bind <IP>", "IP address for the server to bind to", default: "0.0.0.0"
34
+ klass.option "--skip-frontend",
35
+ "Don't load the frontend bundler (always true for production)"
36
+ klass.option "--skip-live-reload",
37
+ "Don't use the live reload functionality (always true for production)"
38
+ end
39
+ end
40
+
32
41
  include ConfigurationOverridable
33
42
  include Freyia::Setup
34
43
  include Inclusive
@@ -37,13 +46,7 @@ module Bridgetown
37
46
 
38
47
  options do
39
48
  BuildOptions.include_options(self)
40
- option "-P/--port <NUM>",
41
- "Serve your site on the specified port. Defaults to 4000",
42
- type: Integer
43
- option "-B/--bind <IP>", "IP address for the server to bind to", default: "0.0.0.0"
44
- option "--skip-frontend", "Don't load the frontend bundler (always true for production)"
45
- option "--skip-live-reload",
46
- "Don't use the live reload functionality (always true for production)"
49
+ StartOptions.include_options(self)
47
50
  end
48
51
 
49
52
  def call # rubocop:disable Metrics
@@ -58,7 +61,7 @@ module Bridgetown
58
61
  # Load Bridgetown configuration into thread memory
59
62
  bt_options = configuration_with_overrides(options)
60
63
  bt_options.port = port = load_env_and_determine_port(bt_options, options)
61
- # TODO: support Puma serving HTTPS directly?
64
+ # TODO: support Falcon serving HTTPS directly
62
65
  bt_bound_url = "http://#{bt_options.bind}:#{port}"
63
66
 
64
67
  # Set a local site URL in the config if one is not available
@@ -66,6 +69,10 @@ module Bridgetown
66
69
  bt_options.url = bt_bound_url.sub("0.0.0.0", "localhost")
67
70
  end
68
71
 
72
+ # Temporary hack to silence `Errno::EPIPE: Broken pipe` errors in dev.
73
+ # Only happens when using Falcon with HTTP/1.1.
74
+ # When we can use Falcon with HTTP/2 in dev, we can remove this line.
75
+ ENV["CONSOLE_ERROR"] = "Async::Task"
69
76
  Bridgetown::Server.new({
70
77
  Host: bt_options.bind,
71
78
  Port: port,
@@ -74,7 +81,7 @@ module Bridgetown
74
81
  if server.serveable?
75
82
  pid_tracker.create_pid_dir
76
83
 
77
- bt_options.skip_live_reload ||= !server.using_puma?
84
+ bt_options.skip_live_reload ||= false
78
85
 
79
86
  build_args = ["-w"] + Array(ARGV[1..])
80
87
  build_pid = Process.fork { Bridgetown::Commands::Build[*build_args].() }
@@ -2,23 +2,16 @@
2
2
 
3
3
  module Bridgetown
4
4
  module Localizable
5
+ # Sort items by their locale's position in the configured available_locales list.
6
+ # Items with unknown locales sort last.
7
+ def self.sort_by_locale(items, available_locales)
8
+ items.sort_by { |item| available_locales.index(item.data.locale) || Float::INFINITY }
9
+ end
10
+
5
11
  def all_locales
6
- result_set = case self
7
- when Bridgetown::Resource::Base
8
- collection.resources
9
- when Bridgetown::GeneratedPage
10
- site.generated_pages
11
- else
12
- []
13
- end
14
-
15
- matching_resources = result_set.select do |item|
16
- matches_resource?(item)
17
- end
18
-
19
- matching_resources.sort_by do |item|
20
- site.config.available_locales.index item.data.locale
21
- end
12
+ return @all_locales if @all_locales
13
+
14
+ @all_locales = site.tmp_cache.dig(:locale_index, locale_index_key) || find_matching_locales
22
15
  end
23
16
 
24
17
  def matches_resource?(item)
@@ -32,5 +25,28 @@ module Bridgetown
32
25
  def localeless_path
33
26
  relative_path.gsub(%r{\A#{data.locale}/}, "")
34
27
  end
28
+
29
+ # Key used to group locale variants of the same content in the locale index.
30
+ # Uses the same matching criteria as matches_resource? (slug + path identity).
31
+ def locale_index_key
32
+ slug = data.slug
33
+ return unless slug
34
+
35
+ path_key = relative_path.is_a?(String) ? localeless_path : relative_path.parent.to_s
36
+ prefix = respond_to?(:collection) ? collection.label : "generated"
37
+
38
+ "#{prefix}:#{slug}:#{path_key}"
39
+ end
40
+
41
+ private
42
+
43
+ def find_matching_locales
44
+ result_set = respond_to?(:collection) ? collection.resources : site.generated_pages
45
+
46
+ Bridgetown::Localizable.sort_by_locale(
47
+ result_set.select { |item| matches_resource?(item) },
48
+ site.config.available_locales
49
+ )
50
+ end
35
51
  end
36
52
  end
@@ -9,6 +9,7 @@ class Bridgetown::Site
9
9
  # @see Document
10
10
  def render
11
11
  Bridgetown::Hooks.trigger :site, :pre_render, self
12
+ build_locale_index
12
13
  execute_inline_ruby_for_layouts!
13
14
  render_resources
14
15
  generated_pages.each(&:transform!)
@@ -97,6 +98,26 @@ class Bridgetown::Site
97
98
  )
98
99
  end
99
100
 
101
+ # Builds a lookup index grouping resources by their locale-independent identity
102
+ # (collection + slug + localeless path). This turns `all_locales` from an O(n)
103
+ # linear scan into an O(1) hash lookup per resource, which is critical for sites
104
+ # with many localized pages.
105
+ #
106
+ # @return [void]
107
+ def build_locale_index
108
+ groups = Hash.new { |h, k| h[k] = [] }
109
+
110
+ collections.each_value.flat_map(&:resources).concat(generated_pages).each do |item|
111
+ key = item.locale_index_key
112
+ groups[key] << item if key
113
+ end
114
+
115
+ locale_order = config.available_locales
116
+ tmp_cache[:locale_index] = groups.transform_values do |items|
117
+ Bridgetown::Localizable.sort_by_locale(items, locale_order).freeze
118
+ end.freeze
119
+ end
120
+
100
121
  # Renders all resources
101
122
  #
102
123
  # @return [void]
@@ -45,7 +45,6 @@ class Bridgetown::Site
45
45
  end
46
46
 
47
47
  def ssr_first_read
48
- Bridgetown::Hooks.trigger :site, :pre_read, self
49
48
  defaults_reader.tap do |d|
50
49
  d.path_defaults.clear
51
50
  d.read
@@ -244,23 +244,41 @@ module Bridgetown
244
244
  # Control the behavior of Bridgetown's live reload functionality in development
245
245
  # @param bool [Boolean] - default: `true`
246
246
 
247
- # @!method exclude(files_list)
248
- # Exclude source directories and/or files from the build conversion
249
- # @param files_list [Array<String>]
247
+ # Exclude source directories and/or files from the build conversion
248
+ # @param files_list [Array<String>] - you can pass multiple string arguments
249
+ # in lieu of an array
250
+ def exclude(*files_list)
251
+ files_list = Array(files_list[0]) if files_list.length == 1
252
+ get("exclude").tap do |value|
253
+ files_list.each { value << _1 }
254
+ end
255
+ end
250
256
 
251
- # @!method include(files_list)
252
- # Force inclusion of directories and/or files in the conversion (e.g. starting with
253
- # underscores or dots)
254
- # @param files_list [Array<String>]
257
+ # Force inclusion of directories and/or files in the conversion (e.g. starting with
258
+ # underscores or dots)
259
+ # @param files_list [Array<String>] - you can pass multiple string arguments
260
+ # in lieu of an array
261
+ def include(*files_list)
262
+ files_list = Array(files_list[0]) if files_list.length == 1
263
+ get("include").tap do |value|
264
+ files_list.each { value << _1 }
265
+ end
266
+ end
255
267
 
256
- # @!method keep_files(files_list)
257
- # Files to keep when clobbering the site destination (aka not generated in typical
258
- # Bridgetown builds)
259
- # @param files_list [Array<String>]
268
+ # Files to keep when clobbering the site destination (aka not generated in typical
269
+ # Bridgetown builds)
270
+ # @param files_list [Array<String>] - you can pass multiple string arguments
271
+ # in lieu of an array
272
+ def keep_files(*files_list)
273
+ files_list = Array(files_list[0]) if files_list.length == 1
274
+ get("keep_files").tap do |value|
275
+ files_list.each { value << _1 }
276
+ end
277
+ end
260
278
 
261
279
  # @!method autoload_paths
262
- # Add paths to the Zeitwerk autoloader. Use a `config.defaults << "..."` syntax or a more
263
- # advanced hash config
280
+ # Add paths to the Zeitwerk autoloader. Use a `config.autoload_paths << "..."` syntax or
281
+ # a more advanced hash config
264
282
  # @example Add a new path for autoloading and eager load on boot
265
283
  # config.autoload_paths << {
266
284
  # path: "loadme",
@@ -6,7 +6,7 @@ module Bridgetown
6
6
  using Bridgetown::Refinements
7
7
 
8
8
  # Built-in initializer list which isn't Gem-backed:
9
- REQUIRE_DENYLIST = %i(external_sources parse_routes ssr) # rubocop:disable Style/MutableConstant
9
+ REQUIRE_DENYLIST = %i(external_sources parse_routes ssr wikilinks) # rubocop:disable Style/MutableConstant
10
10
 
11
11
  Initializer = Struct.new(:name, :block, :completed) do
12
12
  def to_s
@@ -79,6 +79,7 @@ module Bridgetown
79
79
  site:,
80
80
  page: payload["page"],
81
81
  cached_partials: self.class.cached_partials,
82
+ resource: document,
82
83
  },
83
84
  strict_filters: site.config["liquid"]["strict_filters"],
84
85
  strict_variables: site.config["liquid"]["strict_variables"],
@@ -25,6 +25,13 @@ module Bridgetown
25
25
  .htm
26
26
  ).freeze
27
27
 
28
+ # NOTE: these are no-op methods, only here to provide "compatibility" with
29
+ # `Bridgetown::Resource::Base` in edge case rendering contexts. There's
30
+ # actually no case when a GeneratedPage will have a linkage with the Roda
31
+ # app because GeneratedPage instances are only used in static builds.
32
+ def roda_app=(*); end
33
+ def roda_app = nil
34
+
28
35
  # Initialize a new GeneratedPage
29
36
  #
30
37
  # @param site [Bridgetown::Site]
@@ -30,7 +30,7 @@ module Bridgetown
30
30
  @site = site || Bridgetown::Current.site
31
31
 
32
32
  # duck typing for Liquid context
33
- @context = Context.new({ site: })
33
+ @context = Context.new({ site: self.site })
34
34
  end
35
35
 
36
36
  def asset_path(asset_type)
@@ -114,29 +114,39 @@ module Bridgetown
114
114
  file_to_check = Bridgetown.live_reload_path
115
115
  errors_file = Bridgetown.build_errors_path
116
116
 
117
+ # rubocop:disable Metrics/BlockLength
117
118
  app.request.get "_bridgetown/live_reload" do
118
119
  @_mod = File.exist?(file_to_check) ? File.stat(file_to_check).mtime.to_i : 0
119
120
  event_stream = proc do |stream|
120
- Thread.new do
121
- loop do
122
- new_mod = File.exist?(file_to_check) ? File.stat(file_to_check).mtime.to_i : 0
123
-
124
- if @_mod < new_mod
125
- stream.write "data: reloaded!\n\n"
126
- break
127
- elsif File.exist?(errors_file)
128
- stream.write "event: builderror\ndata: #{File.read(errors_file).to_json}\n\n"
129
- else
130
- stream.write "data: #{new_mod}\n\n"
131
- end
132
-
133
- sleep sleep_interval
134
- rescue Errno::EPIPE # User refreshed the page
121
+ # We don't need to do this when using Falcon.
122
+ # Puma hangs with Ctrl+C without this manual interruption.
123
+ # Very very hacky, but it's only in dev.
124
+ # https://github.com/puma/puma/issues/3569
125
+ unless defined? Falcon
126
+ trap(:INT) do
127
+ stream.close
128
+ raise Interrupt
129
+ end
130
+ end
131
+
132
+ loop do
133
+ new_mod = File.exist?(file_to_check) ? File.stat(file_to_check).mtime.to_i : 0
134
+
135
+ if @_mod < new_mod
136
+ stream.write "data: reloaded!\n\n"
135
137
  break
138
+ elsif File.exist?(errors_file)
139
+ stream.write "event: builderror\ndata: #{File.read(errors_file).to_json}\n\n"
140
+ else
141
+ stream.write "data: #{new_mod}\n\n"
136
142
  end
137
- ensure
138
- stream.close
143
+
144
+ sleep sleep_interval
145
+ rescue Errno::EPIPE # User refreshed the page
146
+ break
139
147
  end
148
+ ensure
149
+ stream.close
140
150
  end
141
151
 
142
152
  app.request.halt [200, {
@@ -144,6 +154,7 @@ module Bridgetown
144
154
  "cache-control" => "no-cache",
145
155
  }, event_stream,]
146
156
  end
157
+ # rubocop:enable Metrics/BlockLength
147
158
  end
148
159
  end
149
160
 
@@ -23,6 +23,7 @@ module Bridgetown
23
23
  end
24
24
  end
25
25
 
26
+ # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
26
27
  def read_content_root(collection_name, content_dir)
27
28
  collection = site.collections[collection_name]
28
29
  unless collection
@@ -35,10 +36,16 @@ module Bridgetown
35
36
 
36
37
  Find.find(content_dir) do |path|
37
38
  if File.directory?(path)
38
- Find.prune if EntryFilter::SPECIAL_LEADING_CHAR_REGEX.match?(File.basename(path))
39
+ if rejected_by_external_sources_filter?(path, collection_name) ||
40
+ EntryFilter::SPECIAL_LEADING_CHAR_REGEX.match?(File.basename(path))
41
+ Find.prune
42
+ end
43
+
39
44
  next
40
45
  end
41
46
 
47
+ next if rejected_by_external_sources_filter?(path, collection_name)
48
+
42
49
  if File.symlink?(path)
43
50
  Bridgetown.logger.warn "Plugin content reader:", "Ignored symlinked asset: #{path}"
44
51
  else
@@ -46,6 +53,14 @@ module Bridgetown
46
53
  end
47
54
  end
48
55
  end
56
+ # rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
57
+
58
+ def rejected_by_external_sources_filter?(path, collection_name)
59
+ filter = site.config.external_sources_filters&.dig(collection_name)
60
+ return false unless filter
61
+
62
+ !filter.call(File.basename(path), path)
63
+ end
49
64
 
50
65
  # @param collection [Bridgetown::Collection]
51
66
  def read_content_file(content_dir, path, collection)
@@ -368,7 +368,7 @@ module Bridgetown
368
368
  correct_locale = origin_data["locale"] || origin_data[:locale] || data.locale
369
369
  model.attributes = origin_data
370
370
  model.attributes.locale = correct_locale.to_s == "multi" ? data.locale : correct_locale
371
- @relative_url = @absolute_url = nil # wipe memoizations
371
+ @relative_url = @absolute_url = @all_locales = nil # wipe memoizations
372
372
  read!
373
373
  tax_diff = past_values.any? { |k, v| @data.peek[k] != v }
374
374
 
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bridgetown
4
+ module Tags
5
+ class RubyRender < Liquid::Tag
6
+ using Bridgetown::Refinements
7
+
8
+ # @param tag_name [String] "ruby_render"
9
+ # @param input [String] The input to the tag: snake-case name of the
10
+ # Ruby component, plus initialize args. Example:
11
+ # '"card", title: "Hello", footer: "I am a card"'
12
+ # @param tokens [Hash] A hash of config tokens for Liquid
13
+ def initialize(tag_name, input, tokens)
14
+ super
15
+ @input = input
16
+
17
+ @attributes = {}
18
+ input.scan(Liquid::TagAttributes) do |key, value|
19
+ @attributes[key.to_sym] = parse_expression(value)
20
+ end
21
+ end
22
+
23
+ # @param context [Liquid::Context]
24
+ # @return [String]
25
+ def render(context)
26
+ view_context = Bridgetown::RubyTemplateView.new(context.registers[:resource])
27
+ component_class_name_snakecase = @input.split(",").first
28
+ component_class_name = component_class_name_snakecase.tr("\"'", "").camelize.strip
29
+ component_class = self.class.const_get(component_class_name)
30
+ @attributes.each do |key, value|
31
+ @attributes[key] = value.evaluate(context) if value.is_a?(Liquid::VariableLookup)
32
+ end
33
+ component_instance = component_class.new(**@attributes)
34
+
35
+ if component_instance.respond_to?(:render_in)
36
+ component_instance.render_in(view_context)
37
+ else
38
+ component_instance.to_s
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ Liquid::Template.register_tag("ruby_render", Bridgetown::Tags::RubyRender)
@@ -13,7 +13,7 @@ Bridgetown.initializer :ssr do |config, setup: nil, **options|
13
13
  end
14
14
  end
15
15
 
16
- Bridgetown.initializer :external_sources do |config, contents:|
16
+ Bridgetown.initializer :external_sources do |config, contents:, filters: nil|
17
17
  Bridgetown::ExternalSources = Module.new
18
18
 
19
19
  contents.each do |coll, path|
@@ -35,6 +35,13 @@ Bridgetown.initializer :external_sources do |config, contents:|
35
35
  contents.each_value do |path|
36
36
  config.additional_watch_paths << path
37
37
  end
38
+
39
+ if filters
40
+ config.external_sources_filters = {}
41
+ filters.each do |coll, filter|
42
+ config.external_sources_filters[coll] = filter
43
+ end
44
+ end
38
45
  end
39
46
 
40
47
  Bridgetown.initializer :parse_routes do |config|
@@ -73,3 +80,7 @@ Bridgetown.initializer :parse_routes do |config|
73
80
 
74
81
  File.write(File.join(config.root_dir, ".routes.json"), routing_tree.to_json(json_gen_opts))
75
82
  end
83
+
84
+ Bridgetown.initializer :wikilinks do |config|
85
+ Bridgetown::Utils::Wikilinks.setup_parsing_hook config
86
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bridgetown
4
+ module Utils
5
+ class Wikilinks
6
+ # @param config [Bridgetown::Configuration::ConfigurationDSL]
7
+ def self.setup_parsing_hook(config)
8
+ markdown_exts = config.markdown_ext.split(",").map { ".#{_1}" }
9
+
10
+ config.hook :resources, :pre_render, priority: :low do |resource|
11
+ next unless markdown_exts.include?(resource.relative_path.extname)
12
+ next if resource.data.bypass_wikilinks
13
+
14
+ Wikilinks.new(resource:).convert
15
+ end
16
+ end
17
+
18
+ # @param resource [Bridgetown::Resource::Base]
19
+ def initialize(resource:)
20
+ @resource = resource
21
+ @site = resource.site
22
+ end
23
+
24
+ # Sets the resource's new content to the parsed string where `[[Wiki-style Links]]` are
25
+ # turned into `[Wiki-style Links](...)`
26
+ def convert
27
+ @resource.content = parse_content(@resource.content)
28
+ end
29
+
30
+ # Parse all `[[wiki links]]` unless they're escaped, e.g. `\[[don't parse me bro]]`.
31
+ # You can use a pipe character to control the displayed text of the link:
32
+ # `[[Actual page title|Link text here]]`, and you can also add an anchor for the link:
33
+ # `[[Page Title#page_section_anchor]]`
34
+ # @param input [String]
35
+ # @return String
36
+ def parse_content(input)
37
+ input.gsub %r!(\\?)\[\[(.+?)\]\]! do
38
+ next "[[#{Regexp.last_match[2]}]]" if Regexp.last_match[1] == "\\"
39
+
40
+ search_title, printed_title =
41
+ Regexp.last_match[2].split("|").map(&:strip)
42
+ search_title, anchor = search_title.split("#")
43
+ anchor = "##{anchor}" if anchor
44
+ title = printed_title || search_title
45
+ found = @site.resources.find { _1.data.title == search_title }
46
+ if found
47
+ "[#{title}](#{found.relative_url}#{anchor}){:.wikilink}"
48
+ else
49
+ missing_title_strategy(title)
50
+ end
51
+ end
52
+ end
53
+
54
+ def missing_title_strategy(title)
55
+ # TODO: this should be configurable
56
+ "<u>#{title} (missing)</u>"
57
+ end
58
+ end
59
+ end
60
+ end
@@ -9,6 +9,7 @@ module Bridgetown
9
9
  autoload :RequireGems, "bridgetown-core/utils/require_gems"
10
10
  autoload :RubyExec, "bridgetown-core/utils/ruby_exec"
11
11
  autoload :SmartyPantsConverter, "bridgetown-core/utils/smarty_pants_converter"
12
+ autoload :Wikilinks, "bridgetown-core/utils/wikilinks"
12
13
 
13
14
  # Constants for use in #slugify
14
15
  SLUGIFY_MODES = %w(raw default pretty simple ascii latin).freeze
@@ -149,30 +150,30 @@ module Bridgetown
149
150
  # When mode is `pretty`, some non-alphabetic characters (`._~!$&'()+,;=@`)
150
151
  # are not replaced with hyphen.
151
152
  #
152
- # When mode is `ascii`, some everything else except ASCII characters
153
- # `a-z` (lowercase), `A-Z` (uppercase) and `0-9` (numbers) are not replaced with hyphen.
153
+ # When mode is `ascii`, everything else except ASCII characters
154
+ # `a-z` (lowercase), `A-Z` (uppercase) and `0-9` (numbers) are replaced with hyphen.
154
155
  #
155
156
  # When mode is `latin`, the input string is first preprocessed so that
156
157
  # any letters with accents are replaced with the plain letter. Afterwards,
157
158
  # it follows the `default` mode of operation.
158
159
  #
159
- # If `cased` is `true`, all uppercase letters in the result string are
160
+ # If `cased` is `false`, all uppercase letters in the result string are
160
161
  # replaced with their lowercase counterparts.
161
162
  #
162
163
  # @example
163
164
  # slugify("The _config.yml file")
164
165
  # # => "the-config-yml-file"
165
166
  #
166
- # slugify("The _config.yml file", "pretty")
167
+ # slugify("The _config.yml file", mode: "pretty")
167
168
  # # => "the-_config.yml-file"
168
169
  #
169
- # slugify("The _config.yml file", "pretty", true)
170
- # # => "The-_config.yml file"
170
+ # slugify("The _config.yml file", mode: "pretty", cased: true)
171
+ # # => "The-_config.yml-file"
171
172
  #
172
- # slugify("The _config.yml file", "ascii")
173
+ # slugify("The _config.yml file", mode: "ascii")
173
174
  # # => "the-config-yml-file"
174
175
  #
175
- # slugify("The _config.yml file", "latin")
176
+ # slugify("The _config.yml file", mode: "latin")
176
177
  # # => "the-config-yml-file"
177
178
  #
178
179
  # @param string [String] filename or title to slugify
@@ -30,9 +30,13 @@ gem "bridgetown", "~> <%= Bridgetown::VERSION %>"
30
30
  # Uncomment to add file-based dynamic routing to your project:
31
31
  # gem "bridgetown-routes", "~> <%= Bridgetown::VERSION %>"
32
32
 
33
- # Puma is the Rack-compatible web server used by Bridgetown
33
+ # The Rack-compliant web server
34
34
  # (you can optionally limit this to the "development" group)
35
+ <% if rack_server_option == "puma" %>
35
36
  gem "puma", "< 8"
37
+ <% else %>
38
+ gem "falcon"
39
+ <% end %>
36
40
 
37
41
  # Uncomment to use the Inspectors API to manipulate the output
38
42
  # of your HTML or XML resources:
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env falcon-host
2
+ # frozen_string_literal: true
3
+
4
+ # This file configures Falcon for production.
5
+ # It is ignored in development when using `bin/bt start`.
6
+ #
7
+ # Use `falcon host` to start Falcon in production.
8
+ # `bundle exec falcon host config/falcon.rb`
9
+
10
+ require "falcon/environment/rack"
11
+
12
+ service "<%= @site_name %>" do
13
+ include Falcon::Environment::Rack
14
+
15
+ rackup_path \
16
+ File.expand_path("config.ru", Pathname.new(root).join(".."))
17
+ port ENV.fetch("BRIDGETOWN_PORT") { 4000 }
18
+
19
+ endpoint do
20
+ Async::HTTP::Endpoint
21
+ .parse("http://localhost", port: port)
22
+ end
23
+ end
@@ -8,8 +8,7 @@
8
8
  "esbuild-dev": "node esbuild.config.js --watch"
9
9
  },
10
10
  "devDependencies": {
11
- "esbuild": "^0.25.11",
12
- "glob": "^11.0.3",
11
+ "esbuild": "^0.25.11"<%= "," unless disable_postcss? %>
13
12
  <%- unless disable_postcss? -%>
14
13
  "postcss": "^8.5.6",
15
14
  "postcss-flexbugs-fixes": "^5.0.2",
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bridgetown-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.2
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bridgetown Team
8
+ autorequire:
8
9
  bindir: bin
9
10
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
11
+ date: 2026-05-20 00:00:00.000000000 Z
11
12
  dependencies:
12
13
  - !ruby/object:Gem::Dependency
13
14
  name: addressable
@@ -71,14 +72,14 @@ dependencies:
71
72
  requirements:
72
73
  - - '='
73
74
  - !ruby/object:Gem::Version
74
- version: 2.1.2
75
+ version: 2.2.0
75
76
  type: :runtime
76
77
  prerelease: false
77
78
  version_requirements: !ruby/object:Gem::Requirement
78
79
  requirements:
79
80
  - - '='
80
81
  - !ruby/object:Gem::Version
81
- version: 2.1.2
82
+ version: 2.2.0
82
83
  - !ruby/object:Gem::Dependency
83
84
  name: csv
84
85
  requirement: !ruby/object:Gem::Requirement
@@ -127,14 +128,14 @@ dependencies:
127
128
  requirements:
128
129
  - - "~>"
129
130
  - !ruby/object:Gem::Version
130
- version: '0.3'
131
+ version: '0.5'
131
132
  type: :runtime
132
133
  prerelease: false
133
134
  version_requirements: !ruby/object:Gem::Requirement
134
135
  requirements:
135
136
  - - "~>"
136
137
  - !ruby/object:Gem::Version
137
- version: '0.3'
138
+ version: '0.5'
138
139
  - !ruby/object:Gem::Dependency
139
140
  name: freyia
140
141
  requirement: !ruby/object:Gem::Requirement
@@ -595,6 +596,7 @@ files:
595
596
  - lib/bridgetown-core/tags/live_reload_dev_js.rb
596
597
  - lib/bridgetown-core/tags/post_url.rb
597
598
  - lib/bridgetown-core/tags/render_content.rb
599
+ - lib/bridgetown-core/tags/ruby_render.rb
598
600
  - lib/bridgetown-core/tags/t.rb
599
601
  - lib/bridgetown-core/tags/with.rb
600
602
  - lib/bridgetown-core/tasks/bridgetown_tasks.rake
@@ -606,6 +608,7 @@ files:
606
608
  - lib/bridgetown-core/utils/require_gems.rb
607
609
  - lib/bridgetown-core/utils/ruby_exec.rb
608
610
  - lib/bridgetown-core/utils/smarty_pants_converter.rb
611
+ - lib/bridgetown-core/utils/wikilinks.rb
609
612
  - lib/bridgetown-core/watcher.rb
610
613
  - lib/bridgetown-core/yaml_parser.rb
611
614
  - lib/roda/plugins/bridgetown_server.rb
@@ -640,8 +643,9 @@ files:
640
643
  - lib/site_template/TEMPLATES/serbea/_partials/_footer.serb
641
644
  - lib/site_template/TEMPLATES/serbea/_partials/_head.serb
642
645
  - lib/site_template/config.ru
646
+ - lib/site_template/config/falcon.erb
643
647
  - lib/site_template/config/initializers.rb
644
- - lib/site_template/config/puma.rb
648
+ - lib/site_template/config/puma.erb
645
649
  - lib/site_template/frontend/javascript/index.js.erb
646
650
  - lib/site_template/frontend/styles/index.css
647
651
  - lib/site_template/frontend/styles/syntax-highlighting.css
@@ -671,6 +675,7 @@ metadata:
671
675
  changelog_uri: https://github.com/bridgetownrb/bridgetown/releases
672
676
  homepage_uri: https://www.bridgetownrb.com
673
677
  rubygems_mfa_required: 'true'
678
+ post_install_message:
674
679
  rdoc_options:
675
680
  - "--charset=UTF-8"
676
681
  require_paths:
@@ -686,7 +691,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
686
691
  - !ruby/object:Gem::Version
687
692
  version: '0'
688
693
  requirements: []
689
- rubygems_version: 3.7.2
694
+ rubygems_version: 3.5.22
695
+ signing_key:
690
696
  specification_version: 4
691
697
  summary: A next-generation, progressive site generator & fullstack framework, powered
692
698
  by Ruby
File without changes