From 9732c23d094bafa021260425a41cb13e78a4ca89 Mon Sep 17 00:00:00 2001 From: Igor Fedoronchuk Date: Thu, 1 Oct 2026 09:47:51 +0200 Subject: [PATCH] Compile the theme in CI and reject wrong-typed variables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The gem ships SCSS, not CSS, so nothing in this repo's build ever compiled the theme file. A change that only breaks under a particular variable override reached consumers with nothing to catch it — there was no CI at all. Two parts: * `rake css` (test/css_check.rb) compiles the theme together with ActiveAdmin's own stylesheets in five override configurations, and asserts three wrong-typed ones are rejected. Wired into a GitHub Actions workflow on Ruby 3.1 and 3.4. * Type guards on the five public variables. A wrong-typed override is legal SassScript, so it compiled without a word and emitted CSS the browser drops (`border-bottom: none solid #5ea3d3`, `padding: 10 12px`): the rule silently vanished and ActiveAdmin's own value reappeared. It now fails the build with the variable name and the value it got. No change to the generated CSS for any valid configuration. --- .github/workflows/ci.yml | 21 ++++++ Gemfile | 7 +- Rakefile | 6 ++ .../stylesheets/wigu/active_admin_theme.scss | 22 ++++++ test/css_check.rb | 73 +++++++++++++++++++ 5 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/ci.yml create mode 100644 test/css_check.rb diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..01d8f04 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,21 @@ +name: CI + +on: + push: + branches: [master] + pull_request: + +jobs: + css: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + ruby: ['3.1', '3.4'] + steps: + - uses: actions/checkout@v4 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + bundler-cache: true + - run: bundle exec rake css diff --git a/Gemfile b/Gemfile index 8e6b92b..b02e4a8 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,9 @@ source 'https://rubygems.org' -# Specify your gem's dependencies in activeadmin_theme.gemspec +# Specify your gem's dependencies in active_admin_theme.gemspec gemspec + +group :development, :test do + gem 'rake' + gem 'sassc' +end diff --git a/Rakefile b/Rakefile index 809eb56..6ae6529 100644 --- a/Rakefile +++ b/Rakefile @@ -1,2 +1,8 @@ require "bundler/gem_tasks" +desc "Compile the theme against ActiveAdmin and check the result" +task :css do + ruby "test/css_check.rb" +end + +task default: :css diff --git a/app/assets/stylesheets/wigu/active_admin_theme.scss b/app/assets/stylesheets/wigu/active_admin_theme.scss index dbaf73a..9f783a9 100644 --- a/app/assets/stylesheets/wigu/active_admin_theme.scss +++ b/app/assets/stylesheets/wigu/active_admin_theme.scss @@ -5,6 +5,28 @@ $skinMainSecondColor: #5ea3d3!default; $skinBorderRadius: 4px!default; $skinBorderWindowColor: #e6e9ee!default; $skinTablePadding: 10px!default; + +// A wrong-typed override is legal SassScript, so it compiles without a word and +// emits CSS the browser drops — `border-bottom: none solid #5ea3d3`, +// `padding: 10 12px`. The rule silently vanishes and ActiveAdmin's own value +// reappears. Fail the build with a readable message instead. +@each $name, $value in ( + skinBorderRadius: $skinBorderRadius, + skinTablePadding: $skinTablePadding +) { + @if type-of($value) != number or (unitless($value) and $value != 0) { + @error "$#{$name} must be a length with a unit (e.g. 10px), got `#{$value}`."; + } +} +@each $name, $value in ( + skinMainFirstColor: $skinMainFirstColor, + skinMainSecondColor: $skinMainSecondColor, + skinBorderWindowColor: $skinBorderWindowColor +) { + @if type-of($value) != color { + @error "$#{$name} must be a color (use `transparent`, not `none`), got `#{$value}`."; + } +} //HTML----------------------------------------------------------------------------------------------------------------// html { diff --git a/test/css_check.rb b/test/css_check.rb new file mode 100644 index 0000000..76968d9 --- /dev/null +++ b/test/css_check.rb @@ -0,0 +1,73 @@ +# Compiles the theme against ActiveAdmin's own stylesheets and checks the +# result. Run with `rake css`. +# +# The gem ships SCSS, not CSS, so nothing in this repo's build ever compiles +# the theme: a change that only breaks under a particular variable override +# reaches consumers unnoticed. This is the only thing here that does compile it. +require "sassc" + +module CssCheck + STYLESHEETS = File.expand_path("../app/assets/stylesheets", __dir__) + THEME = "wigu/active_admin_theme" + + # Overrides a host project would plausibly write. Each must compile and keep + # producing CSS the browser accepts. + GOOD = { + "defaults" => "", + "accent" => '$skinMainSecondColor: #0066cc;', + "dark chrome" => '$skinMainFirstColor: #111418; $skinBorderWindowColor: #b8babe;', + "square + wide" => '$skinBorderRadius: 0; $skinTablePadding: 14px;', + "rem radius" => '$skinBorderRadius: 0.25rem;', + }.freeze + + # Wrong-typed overrides. All of these are legal SassScript, so without the + # guards in the theme they compile silently and emit declarations the browser + # drops — the rule simply vanishes and ActiveAdmin's own value reappears, with + # nothing in the build output to say why. + BAD = { + "$skinBorderRadius without a unit" => '$skinBorderRadius: 4;', + "$skinTablePadding: none" => '$skinTablePadding: none;', + "$skinBorderWindowColor: none" => '$skinBorderWindowColor: none;', + }.freeze + + def self.load_paths + activeadmin = Gem::Specification.find_by_name("activeadmin").gem_dir + [File.join(activeadmin, "app/assets/stylesheets"), STYLESHEETS] + end + + def self.compile(overrides) + source = <<~SCSS + @import "active_admin/mixins"; + #{overrides} + @import "active_admin/base"; + @import "#{THEME}"; + SCSS + SassC::Engine.new(source, load_paths: load_paths, style: :expanded).render + end + + def self.run + failures = [] + + GOOD.each do |name, overrides| + compile(overrides) + rescue SassC::SyntaxError => e + failures << "#{name}: should compile, but does not — #{e.message.lines.first.strip}" + end + + BAD.each do |name, overrides| + compile(overrides) + failures << "#{name}: should be rejected with @error, but compiled silently" + rescue SassC::SyntaxError + # expected — the theme's type guards caught it + end + + if failures.empty? + puts "css_check: #{GOOD.size} overrides compile clean, #{BAD.size} bad ones rejected" + else + failures.each { |failure| warn "css_check: #{failure}" } + abort "css_check: #{failures.size} problem(s)" + end + end +end + +CssCheck.run if $PROGRAM_NAME == __FILE__