Module: Abqari::SiteConfig
- Defined in:
- lib/abqari/site_config.rb
Overview
Lightweight schema check for config/site.yml. The goal is not full
schema validation β yagni for a single-author tool β but to catch
the typo class of bug:
manifest:
theme_colour: '#000' # π should be `theme_color`
β¦which silently no-ops and leaves the operator wondering why their PWA install icon doesn't pick up the brand colour.
Strategy: known top-level keys are allow-listed below. Unknown keys
warn by default (so existing sites don't break on upgrade), and
raise when ABQARI_STRICT_CONFIG=true. CI workflows that want the
gate enforced flip the env var. The plan is to flip the default to
raise once the warn-mode period has passed.
Reserved keys we tolerate without warning:
- `environments` is structural (per-env overrides).
- Anything beginning with `x_` is treated as a user extension
namespace, mirroring HTTP/MIME conventions. Lets a consuming
site park its own metadata in the same file without false-
positive warnings.
Constant Summary collapse
- DEFAULTS =
Static defaults applied to every site. User values in
config/site.ymldeep-merge over these β any user-set key wins. Only static defaults belong here; dynamic ones (minify=env == :production) stay in their callers.Adding a default here removes a
config.fetch('key', value)call elsewhere β one auditable place for the defaults the engine assumes, instead of scattered ternaries. { 'indexable' => true, 'block_ai_scraping' => true, 'syntax_highlighting' => true, 'erb_in_markdown' => false, 'theme_toggle' => true, # The default here (not buried in AssetPipeline) so EVERY reader # of config['theme'] agrees. When it lived pipeline-side, a site # with no `theme:` key built the minimal theme but the renderer's # theme-mtime scoping read '' β so theme CSS edits rebuilt assets # without re-rendering pages, and hot reload served stale hrefs. 'theme' => 'minimal' }.freeze
- KNOWN_KEYS =
Every top-level key the engine reads anywhere (rb + erb). Keep in lockstep with the readers β when adding a new top-level config key, add it here too. CI's audit catches drift.
%w[ author block_ai_scraping bundle_assets collections contact csp deploy description drafts environments erb_in_markdown folio fonts fonts_preload footer headers home icons images indexable indieweb llms locale manifest media minify nav newsletter og_images og_locale pagination plausible robots_disallow search security_contact share_image social speculation_rules syndication syntax_highlighting syntax_theme syntax_theme_dark taxonomies theme theme_toggle timezone title twitter url validate visualizations ].freeze
Class Method Summary collapse
- .deep_merge(base, override) ⇒ Object
- .validate!(config, strict: nil, env: nil) ⇒ Object
-
.validate_required!(config, strict:, env:) ⇒ Object
Keys that must be present for a correct build.
-
.validate_url_has_no_path!(url) ⇒ Object
Abqari emits root-relative asset and internal-link URLs (
/assets/β¦,/posts/β¦,/feed.xml), so aurl:with a path component (https://user.github.io/repo) produces a site whose every asset and link 404s under the prefix. -
.with_defaults(config) ⇒ Object
Deep-merge DEFAULTS under the user's config.
Class Method Details
.deep_merge(base, override) ⇒ Object
118 119 120 121 122 |
# File 'lib/abqari/site_config.rb', line 118 def deep_merge(base, override) base.merge(override) do |_, b, o| b.is_a?(Hash) && o.is_a?(Hash) ? deep_merge(b, o) : o end end |
.validate!(config, strict: nil, env: nil) ⇒ Object
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/abqari/site_config.rb', line 124 def validate!(config, strict: nil, env: nil) strict = ENV['ABQARI_STRICT_CONFIG'] == 'true' if strict.nil? validate_required!(config, strict: strict, env: env) unknown = config.keys.map(&:to_s).reject do |key| KNOWN_KEYS.include?(key) || key.start_with?('x_') end return if unknown.empty? # The advice differs by mode. The old message ended "β¦or set # ABQARI_STRICT_CONFIG=true to make this a hard error" in BOTH # branches β including the one raised *because* it was already set, # which told the user to enable the thing that had just fired. base = "Unknown config key(s) in site.yml: #{unknown.sort.join(', ')}. " \ 'Typos silently no-op; add an `x_` prefix for intentional user extensions' if strict raise UserError, "#{base} (ABQARI_STRICT_CONFIG is on, so this is fatal)." end Log.warn "#{base}, or set ABQARI_STRICT_CONFIG=true to make this a hard error." end |
.validate_required!(config, strict:, env:) ⇒ Object
Keys that must be present for a correct build. title feeds
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 |
# File 'lib/abqari/site_config.rb', line 156 def validate_required!(config, strict:, env:) if config['title'].to_s.strip.empty? Log.warn 'Missing `title` in site.yml β pages will render with an empty <title>.' end if config['url'].to_s.strip.empty? url_msg = 'Missing `url` in site.yml β canonical URLs, sitemap, and feeds ' \ 'need an absolute base URL. Set `url:` (per-environment under ' \ '`environments:` is fine).' if env.to_s == 'production' raise UserError, url_msg if strict Log.warn url_msg end return end validate_url_has_no_path!(config['url']) end |
.validate_url_has_no_path!(url) ⇒ Object
Abqari emits root-relative asset and internal-link URLs (/assets/β¦,
/posts/β¦, /feed.xml), so a url: with a path component
(https://user.github.io/repo) produces a site whose every asset
and link 404s under the prefix. Subdirectory/base-path deployment
is not supported; fail loudly at config time instead of shipping a
broken site. A bare host or a lone / path is fine.
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'lib/abqari/site_config.rb', line 182 def validate_url_has_no_path!(url) parsed = begin URI.parse(url.to_s) rescue URI::InvalidURIError nil # a malformed URL is a separate problem; don't crash here end return if parsed.nil? # Check the scheme FIRST. Without one, `URI.parse('example.com')` # puts the whole string in `path`, so the path check below fired # and reported that `url` "has a path component ("example.com")" β # technically true, humanly baffling, and pointing at the wrong # mistake. if parsed.scheme.to_s.empty? raise UserError, "`url` (#{url.inspect}) is missing a scheme. Use the full origin, " \ "e.g. \"https://#{url.to_s.sub(%r{\A/+}, '')}\"." end path = parsed.path.to_s return if path.empty? || path == '/' raise UserError, "`url` (#{url.inspect}) has a path component (#{path.inspect}). " \ 'Abqari does not support subdirectory/base-path deployment β every ' \ 'asset and internal link is root-relative and would 404 under a path ' \ 'prefix. Deploy at a domain root (a custom domain, or a `<user>.github.io` ' \ 'root repository).' end |
.with_defaults(config) ⇒ Object
Deep-merge DEFAULTS under the user's config. Returns a new hash;
the input isn't mutated. Use at boot to fold in static defaults
so call sites can do config['key'] instead of
config.fetch('key', sane_default).
114 115 116 |
# File 'lib/abqari/site_config.rb', line 114 def with_defaults(config) deep_merge(DEFAULTS, config || {}) end |