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,sass}"], "builds" => ["app/assets/builds/**/*.css"], "views" => ["app/views/**/*.{html.erb,turbo_stream.erb}", "app/components/**/*.{rb,html.erb}"], # `app/javascript` is the Propshaft/importmap convention; # `app/assets/javascripts` is the Sprockets convention. Globbing both # is harmless on a Propshaft-only app (no files match) and lets the # CLI work on Sprockets apps with zero configuration. "javascript" => ["app/javascript/**/*.{js,mjs}", "app/assets/javascripts/**/*.{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.
97 98 99 100 101 102 103 104 105 106 |
# File 'lib/browsable/config.rb', line 97 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.
51 52 53 |
# File 'lib/browsable/config.rb', line 51 def config_file @config_file end |
#data ⇒ Object (readonly)
Returns the value of attribute data.
51 52 53 |
# File 'lib/browsable/config.rb', line 51 def data @data end |
#detected_policy ⇒ Object (readonly)
Returns the value of attribute detected_policy.
51 52 53 |
# File 'lib/browsable/config.rb', line 51 def detected_policy @detected_policy end |
#policy_note ⇒ Object (readonly)
Returns the value of attribute policy_note.
51 52 53 |
# File 'lib/browsable/config.rb', line 51 def policy_note @policy_note end |
#root ⇒ Object (readonly)
Returns the value of attribute root.
51 52 53 |
# File 'lib/browsable/config.rb', line 51 def root @root end |
Class Method Details
.deep_merge(base, override) ⇒ Object
87 88 89 90 91 92 93 94 95 |
# File 'lib/browsable/config.rb', line 87 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`.
57 58 59 60 61 62 63 |
# File 'lib/browsable/config.rb', line 57 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
65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/browsable/config.rb', line 65 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
78 79 80 81 82 83 84 85 |
# File 'lib/browsable/config.rb', line 78 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.
166 |
# File 'lib/browsable/config.rb', line 166 def file_present? = !config_file.nil? |
#ignore_features ⇒ Object
110 |
# File 'lib/browsable/config.rb', line 110 def ignore_features = Array(data.dig("ignore", "features")) |
#ignore_files ⇒ Object
111 |
# File 'lib/browsable/config.rb', line 111 def ignore_files = Array(data.dig("ignore", "files")) |
#importmap_enabled? ⇒ Boolean
112 |
# File 'lib/browsable/config.rb', line 112 def importmap_enabled? = sources.fetch("importmap", true) != false |
#severity ⇒ Object
109 |
# File 'lib/browsable/config.rb', line 109 def severity = data.fetch("severity") |
#sources ⇒ Object
108 |
# File 'lib/browsable/config.rb', line 108 def sources = data.fetch("sources") |
#target ⇒ Object
Resolve the browser-support Target implied by this config.
115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/browsable/config.rb', line 115 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.
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
# File 'lib/browsable/config.rb', line 140 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.
132 133 134 135 136 |
# File 'lib/browsable/config.rb', line 132 def unconstrained_browsers return [] unless detected_policy.is_a?(Hash) Target::MODERN.keys - detected_policy.keys end |