From bcd498eb15ea38a46802f93594527548175acb13 Mon Sep 17 00:00:00 2001 From: Brad Simpson Date: Sun, 29 Mar 2026 11:29:35 -0600 Subject: [PATCH 1/2] Fail fast when multiple themes or menus are active in a build --- grunt/tasks/build.js | 1 + grunt/tasks/check-plugins.js | 20 ++++++++++++++++++++ grunt/tasks/dev.js | 1 + 3 files changed, 22 insertions(+) create mode 100644 grunt/tasks/check-plugins.js diff --git a/grunt/tasks/build.js b/grunt/tasks/build.js index 2a9543412..9b738f506 100644 --- a/grunt/tasks/build.js +++ b/grunt/tasks/build.js @@ -5,6 +5,7 @@ module.exports = function(grunt) { grunt.registerTask('build', 'Creates a production-ready build of the course', [ '_log-vars', 'check-json', + 'check-plugins', 'clean:output', 'build-config', 'tracking-insert', diff --git a/grunt/tasks/check-plugins.js b/grunt/tasks/check-plugins.js new file mode 100644 index 000000000..dd197f071 --- /dev/null +++ b/grunt/tasks/check-plugins.js @@ -0,0 +1,20 @@ +module.exports = function(grunt) { + const Helpers = require('../helpers')(grunt); + grunt.registerTask('check-plugins', 'Checks that only one theme and one menu are active in the build', function() { + const excludes = grunt.config('excludes') || []; + const singletonTypes = ['theme', 'menu']; + + singletonTypes.forEach(function(type) { + const installed = Helpers.getInstalledPluginsByType(type); + const active = installed.filter(name => !excludes.includes(name)); + + if (active.length <= 1) return; + + grunt.fail.fatal( + `More than one ${type} is active in this build: ${active.join(', ')}.\n` + + `Add the extras to build.excludes in config.json, e.g.:\n` + + ` "build": { "excludes": ["${active.slice(1).join('", "')}"] }` + ); + }); + }); +}; diff --git a/grunt/tasks/dev.js b/grunt/tasks/dev.js index 7c7335add..0797144fe 100644 --- a/grunt/tasks/dev.js +++ b/grunt/tasks/dev.js @@ -5,6 +5,7 @@ module.exports = function(grunt) { grunt.registerTask('dev', 'Creates a developer-friendly build of the course', [ '_log-vars', 'check-json', + 'check-plugins', 'build-config', 'tracking-insert', 'copy', From d4f5780453685b6e30cadeb06d2c9921a09a0042 Mon Sep 17 00:00:00 2001 From: Brad Simpson Date: Sun, 29 Mar 2026 12:47:19 -0600 Subject: [PATCH 2/2] Themes only, chain from check-json for rub compatibility - Remove menu validation (multi-menu courses are intentional) - Chain check-plugins from check-json so rub-cli also runs the check - Respect build.includes, productionExcludes, and --theme CLI flag --- grunt/tasks/check-json.js | 1 + grunt/tasks/check-plugins.js | 33 ++++++++++++++++++++------------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/grunt/tasks/check-json.js b/grunt/tasks/check-json.js index 383b25507..b4d09bd6b 100644 --- a/grunt/tasks/check-json.js +++ b/grunt/tasks/check-json.js @@ -3,6 +3,7 @@ module.exports = function(grunt) { grunt.registerTask('check-json', 'Validates the course json, checks for duplicate IDs, and that each element has a parent', function() { // validates JSON files grunt.task.run('jsonlint'); + grunt.task.run('check-plugins'); const data = Helpers.getFramework().getData(); data.checkIds(); }); diff --git a/grunt/tasks/check-plugins.js b/grunt/tasks/check-plugins.js index dd197f071..745c32d34 100644 --- a/grunt/tasks/check-plugins.js +++ b/grunt/tasks/check-plugins.js @@ -1,20 +1,27 @@ module.exports = function(grunt) { const Helpers = require('../helpers')(grunt); - grunt.registerTask('check-plugins', 'Checks that only one theme and one menu are active in the build', function() { - const excludes = grunt.config('excludes') || []; - const singletonTypes = ['theme', 'menu']; + grunt.registerTask('check-plugins', 'Checks that only one theme is active in the build', function() { + // A specific theme was selected via CLI flag (e.g. --theme=X), + // so only that one will be compiled regardless of what's installed + if (grunt.config('theme') !== '**') return; - singletonTypes.forEach(function(type) { - const installed = Helpers.getInstalledPluginsByType(type); - const active = installed.filter(name => !excludes.includes(name)); + const includes = grunt.config('includes'); + const excludes = [ + ...(grunt.config('excludes') || []), + ...(grunt.config('type') === 'production' ? (grunt.config('productionExcludes') || []) : []) + ]; - if (active.length <= 1) return; + const installed = Helpers.getInstalledPluginsByType('theme'); + const active = includes + ? installed.filter(name => includes.includes(name)) + : installed.filter(name => !excludes.includes(name)); - grunt.fail.fatal( - `More than one ${type} is active in this build: ${active.join(', ')}.\n` + - `Add the extras to build.excludes in config.json, e.g.:\n` + - ` "build": { "excludes": ["${active.slice(1).join('", "')}"] }` - ); - }); + if (active.length <= 1) return; + + grunt.fail.fatal( + `More than one theme is active in this build: ${active.join(', ')}.\n` + + `Add the extras to build.excludes in config.json, e.g.:\n` + + ` "build": { "excludes": ["${active.slice(1).join('", "')}"] }` + ); }); };