Class: Browsable::Config
- Inherits:
-
Object
- Object
- Browsable::Config
- Defined in:
- lib/browsable/config.rb
Overview
Resolves the effective configuration for an audit run.
browsable runs fully zero-config: when no config file is present every value below is inferred. A config file only exists to override defaults.
Resolution precedence (highest wins) is applied by the caller:
1. CLI flags (handled in CLI)
2. config file (loaded here)
3. inferred Rails (allow_browser policy, read here)
4. gem defaults (DEFAULTS below)
Constant Summary collapse
- DEFAULTS =
{ "target" => { "source" => "allow_browsers", # allow_browsers | browserslist | manual "manual_query" => "defaults" }, "sources" => { "stylesheets" => ["app/assets/stylesheets/**/*.{css,scss}"], "builds" => ["app/assets/builds/**/*.css"], "views" => ["app/views/**/*.{html.erb,turbo_stream.erb}", "app/components/**/*.{rb,html.erb}"], "javascript" => ["app/javascript/**/*.{js,mjs}"], "importmap" => true, "public" => ["public/**/*.{html,css,js}"], "custom" => [] }, "severity" => { "baseline_newly_available" => "warning", "baseline_limited" => "error", "below_target" => "error" }, "ignore" => { "features" => [], "files" => [] } }.freeze
- CONFIG_FILENAMES =
Discovery order for an implicit config file, relative to the project root.
["config/browsable.yml", ".browsable.yml"].freeze
Instance Attribute Summary collapse
-
#config_file ⇒ Object
readonly
Returns the value of attribute config_file.
-
#data ⇒ Object
readonly
Returns the value of attribute data.
-
#detected_policy ⇒ Object
readonly
Returns the value of attribute detected_policy.
-
#policy_note ⇒ Object
readonly
Returns the value of attribute policy_note.
-
#root ⇒ Object
readonly
Returns the value of attribute root.
Class Method Summary collapse
- .deep_merge(base, override) ⇒ Object
-
.load(root:, path: nil) ⇒ Object
Load and merge configuration for a project rooted at ‘root`.
- .locate_file(root, explicit) ⇒ Object
- .parse_file(path) ⇒ Object
Instance Method Summary collapse
-
#file_present? ⇒ Boolean
True when an explicit config file was found and loaded.
- #ignore_features ⇒ Object
- #ignore_files ⇒ Object
- #importmap_enabled? ⇒ Boolean
-
#initialize(root:, data:, config_file: nil) ⇒ Config
constructor
A new instance of Config.
- #severity ⇒ Object
- #sources ⇒ Object
-
#target ⇒ Object
Resolve the browser-support Target implied by this config.
-
#target_notes ⇒ Object
Informational caveats about the resolved target — so the user is never left guessing why a particular set of browsers is (or isn’t) audited.
-
#unconstrained_browsers ⇒ Object
Major browsers an explicit allow_browser hash neither pins to a version nor blocks.
Constructor Details
#initialize(root:, data:, config_file: nil) ⇒ Config
Returns a new instance of Config.
92 93 94 95 96 97 98 99 100 101 |
# File 'lib/browsable/config.rb', line 92 def initialize(root:, data:, config_file: nil) @root = root @data = data @config_file = config_file # PolicyDetector statically resolves the Rails allow_browser policy. # `policy_note` is set when a call was found but could not be resolved. result = PolicyDetector.call(root) @detected_policy = result.policy @policy_note = result.note end |
Instance Attribute Details
#config_file ⇒ Object (readonly)
Returns the value of attribute config_file.
46 47 48 |
# File 'lib/browsable/config.rb', line 46 def config_file @config_file end |
#data ⇒ Object (readonly)
Returns the value of attribute data.
46 47 48 |
# File 'lib/browsable/config.rb', line 46 def data @data end |
#detected_policy ⇒ Object (readonly)
Returns the value of attribute detected_policy.
46 47 48 |
# File 'lib/browsable/config.rb', line 46 def detected_policy @detected_policy end |
#policy_note ⇒ Object (readonly)
Returns the value of attribute policy_note.
46 47 48 |
# File 'lib/browsable/config.rb', line 46 def policy_note @policy_note end |
#root ⇒ Object (readonly)
Returns the value of attribute root.
46 47 48 |
# File 'lib/browsable/config.rb', line 46 def root @root end |
Class Method Details
.deep_merge(base, override) ⇒ Object
82 83 84 85 86 87 88 89 90 |
# File 'lib/browsable/config.rb', line 82 def self.deep_merge(base, override) base.merge(override) do |_key, base_val, override_val| if base_val.is_a?(Hash) && override_val.is_a?(Hash) deep_merge(base_val, override_val) else override_val end end end |
.load(root:, path: nil) ⇒ Object
Load and merge configuration for a project rooted at ‘root`.
52 53 54 55 56 57 58 |
# File 'lib/browsable/config.rb', line 52 def self.load(root:, path: nil) root = File.(root) config_file = locate_file(root, path) file_data = config_file ? parse_file(config_file) : {} merged = deep_merge(DEFAULTS, file_data) new(root: root, data: merged, config_file: config_file) end |
.locate_file(root, explicit) ⇒ Object
60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/browsable/config.rb', line 60 def self.locate_file(root, explicit) if explicit full = File.(explicit, root) raise ConfigError, "Config file not found: #{explicit}" unless File.file?(full) return full end CONFIG_FILENAMES .map { |name| File.join(root, name) } .find { |candidate| File.file?(candidate) } end |
.parse_file(path) ⇒ Object
73 74 75 76 77 78 79 80 |
# File 'lib/browsable/config.rb', line 73 def self.parse_file(path) loaded = YAML.safe_load_file(path) || {} raise ConfigError, "#{path} must contain a YAML mapping" unless loaded.is_a?(Hash) loaded rescue Psych::SyntaxError => e raise ConfigError, "Could not parse #{path}: #{e.}" end |
Instance Method Details
#file_present? ⇒ Boolean
True when an explicit config file was found and loaded.
161 |
# File 'lib/browsable/config.rb', line 161 def file_present? = !config_file.nil? |
#ignore_features ⇒ Object
105 |
# File 'lib/browsable/config.rb', line 105 def ignore_features = Array(data.dig("ignore", "features")) |
#ignore_files ⇒ Object
106 |
# File 'lib/browsable/config.rb', line 106 def ignore_files = Array(data.dig("ignore", "files")) |
#importmap_enabled? ⇒ Boolean
107 |
# File 'lib/browsable/config.rb', line 107 def importmap_enabled? = sources.fetch("importmap", true) != false |
#severity ⇒ Object
104 |
# File 'lib/browsable/config.rb', line 104 def severity = data.fetch("severity") |
#sources ⇒ Object
103 |
# File 'lib/browsable/config.rb', line 103 def sources = data.fetch("sources") |
#target ⇒ Object
Resolve the browser-support Target implied by this config.
110 111 112 113 114 115 116 117 118 119 120 121 |
# File 'lib/browsable/config.rb', line 110 def target cfg = data.fetch("target") case cfg["source"] when "manual" Target.new(cfg.fetch("manual_query", "defaults")) when "browserslist" # Defer entirely to the project's browserslist config (.browserslistrc). Target.new("defaults") else # "allow_browsers" (the default) detected_policy ? Target.from_rails_policy(detected_policy) : Target.new("defaults") end end |
#target_notes ⇒ Object
Informational caveats about the resolved target — so the user is never left guessing why a particular set of browsers is (or isn’t) audited.
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/browsable/config.rb', line 135 def target_notes notes = [] inferring = data.dig("target", "source") == "allow_browsers" # No allow_browser policy at all — explain the browserslist defaults fallback. if inferring && detected_policy.nil? && policy_note.nil? notes << "No allow_browser policy was found in ApplicationController, so browsable " \ "is auditing against the browserslist `defaults` baseline. Add an " \ "allow_browser call, or set `target:` in config/browsable.yml, to pick the " \ "browsers to audit against explicitly." end # A partial hash policy — explain the browsers Rails leaves unconstrained. if unconstrained_browsers.any? pinned = detected_policy.keys.join(", ") omitted = unconstrained_browsers.join(", ") notes << "Your allow_browser policy pins a version only for #{pinned}. Rails leaves " \ "every browser you don't list (#{omitted}) allowed at any version, so " \ "browsable audits only #{pinned}. Add a `target:` block to " \ "config/browsable.yml to audit the others." end notes end |
#unconstrained_browsers ⇒ Object
Major browsers an explicit allow_browser hash neither pins to a version nor blocks. Rails allows these at any version — it only ever blocks a browser it was given a minimum (or ‘false`) for — so browsable has no floor to audit them against. Empty unless the policy is an explicit hash.
127 128 129 130 131 |
# File 'lib/browsable/config.rb', line 127 def unconstrained_browsers return [] unless detected_policy.is_a?(Hash) Target::MODERN.keys - detected_policy.keys end |