From f07c4a781c6c054e5d2dc05ab6c0fd0b6cbb0645 Mon Sep 17 00:00:00 2001 From: Mark VanLandingham Date: Tue, 14 Apr 2020 09:17:00 -0500 Subject: [PATCH] DEV: Add options to theme install rake task - more options (#9394) --- app/services/themes_install_task.rb | 44 ++++--- lib/tasks/themes.rake | 43 ++++--- spec/services/themes_spec.rb | 171 +++++++++++++++++++--------- 3 files changed, 171 insertions(+), 87 deletions(-) diff --git a/app/services/themes_install_task.rb b/app/services/themes_install_task.rb index 8466e7b8cb7..36ece33dea5 100644 --- a/app/services/themes_install_task.rb +++ b/app/services/themes_install_task.rb @@ -1,18 +1,17 @@ # frozen_string_literal: true class ThemesInstallTask - def self.install(yml) - counts = { installed: 0, updated: 0, skipped: 0, errors: 0 } + def self.install(themes) + counts = { installed: 0, updated: 0, errors: 0 } log = [] - themes = YAML::load(yml) - themes.each do |theme| - name = theme[0] - val = theme[1] + themes.each do |name, val| installer = new(val) + next if installer.url.nil? if installer.theme_exists? - log << "#{name}: is already installed" - counts[:skipped] += 1 + installer.update + log << "#{name}: is already installed. Updating from remote." + counts[:updated] += 1 else begin installer.install @@ -32,7 +31,8 @@ class ThemesInstallTask def initialize(url_or_options = nil) if url_or_options.is_a?(Hash) - @url = url_or_options.fetch("url") + url_or_options.deep_symbolize_keys! + @url = url_or_options.fetch(:url, nil) @options = url_or_options else @url = url_or_options @@ -41,14 +41,28 @@ class ThemesInstallTask end def theme_exists? - RemoteTheme - .where(remote_url: url) - .where(branch: options.fetch("branch", nil)) - .exists? + @remote_theme = RemoteTheme.find_by(remote_url: @url, branch: @options.fetch(:branch, nil)) + @theme = @remote_theme&.theme + @theme.present? end def install - theme = RemoteTheme.import_theme(url, Discourse.system_user, private_key: options["private_key"], branch: options["branch"]) - theme.set_default! if options.fetch("default", false) + @theme = RemoteTheme.import_theme(@url, Discourse.system_user, private_key: @options[:private_key], branch: @options[:branch]) + @theme.set_default! if @options.fetch(:default, false) + add_component_to_all_themes + end + + def update + @remote_theme.update_from_remote + add_component_to_all_themes + end + + def add_component_to_all_themes + return if (!@options.fetch(:add_to_all_themes, false) || !@theme.component) + + Theme.where(component: false).each do |parent_theme| + next if ChildTheme.where(parent_theme_id: parent_theme.id, child_theme_id: @theme.id).exists? + parent_theme.add_relative_theme!(:child, @theme) + end end end diff --git a/lib/tasks/themes.rake b/lib/tasks/themes.rake index bce21369d25..8a90196faf9 100644 --- a/lib/tasks/themes.rake +++ b/lib/tasks/themes.rake @@ -2,31 +2,41 @@ require 'yaml' -# == YAML file format # # 2 different formats are accepted: # +# == JSON format +# +# bin/rake themes:install -- '--{"discourse-something": "https://github.com/discourse/discourse-something"}' +# OR +# bin/rake themes:install -- '--{"discourse-something": {"url": "https://github.com/discourse/discourse-something", default: true}}' +# +# == YAML file formats +# # theme_name: https://github.com/example/theme.git -# +# OR # theme_name: -# url: https://github.com/example/theme.git -# branch: abc -# private_key: ... -# default: true +# url: https://github.com/example/theme_name.git +# branch: "master" +# private_key: "" +# default: false +# add_to_all_themes: false # only for components - install on every theme # -# In the second form, only the url is required. +# In the first form, only the url is required. # desc "Install themes & theme components" -task "themes:install" => :environment do - yml = (STDIN.tty?) ? '' : STDIN.read - if yml == '' - puts - puts "Please specify a themes yml file" - puts "Example: rake themes:install < themes.yml" - exit 1 - end +task "themes:install" => :environment do |task, args| + theme_args = (STDIN.tty?) ? '' : STDIN.read + use_json = theme_args == '' - log, counts = ThemesInstallTask.install(yml) + theme_args = begin + use_json ? JSON.parse(ARGV.last.gsub('--', '')) : YAML::load(theme_args) + rescue + puts use_json ? "Invalid JSON input. \n#{ARGV.last}" : "Invalid YML: \n#{theme_args}" + exit 1 + end + + log, counts = ThemesInstallTask.install(theme_args) puts log @@ -34,7 +44,6 @@ task "themes:install" => :environment do puts "Results:" puts " Installed: #{counts[:installed]}" puts " Updated: #{counts[:updated]}" - puts " Skipped: #{counts[:skipped]}" puts " Errors: #{counts[:errors]}" if counts[:errors] > 0 diff --git a/spec/services/themes_spec.rb b/spec/services/themes_spec.rb index cabf942df11..2410a5e04c0 100644 --- a/spec/services/themes_spec.rb +++ b/spec/services/themes_spec.rb @@ -8,75 +8,136 @@ describe ThemesInstallTask do Discourse::Application.load_tasks end - let(:github_repo) { 'https://github.com/example/theme.git' } - let(:branch) { 'master' } - describe '.new' do - context 'with url' do - subject { described_class.new(github_repo) } - - it 'configures the url' do - expect(subject.url).to eq github_repo + def setup_git_repo(files) + dir = Dir.tmpdir + repo_dir = "#{dir}/#{SecureRandom.hex}" + `mkdir #{repo_dir}` + `cd #{repo_dir} && git init . ` + `cd #{repo_dir} && git config user.email 'someone@cool.com'` + `cd #{repo_dir} && git config user.name 'The Cool One'` + `cd #{repo_dir} && git config commit.gpgsign 'false'` + files.each do |name, data| + FileUtils.mkdir_p(Pathname.new("#{repo_dir}/#{name}").dirname) + File.write("#{repo_dir}/#{name}", data) + `cd #{repo_dir} && git add #{name}` end + `cd #{repo_dir} && git commit -am 'first commit'` + repo_dir + end - it 'initializes without options' do - expect(subject.options).to eq({}) + THEME_NAME = "awesome theme" + + def about_json(love_color: "FAFAFA", tertiary_low_color: "FFFFFF", color_scheme_name: "Amazing", about_url: "https://www.site.com/about", component: false) + <<~JSON + { + "name": "#{THEME_NAME}", + "about_url": "#{about_url}", + "license_url": "https://www.site.com/license", + "theme_version": "1.0", + "minimum_discourse_version": "1.0.0", + "assets": { + "font": "assets/font.woff2" + }, + "component": "#{component}", + "color_schemes": { + "#{color_scheme_name}": { + "love": "#{love_color}", + "tertiary-low": "#{tertiary_low_color}" + } + }, + "modifiers": { + "serialize_topic_excerpts": true + } + } + JSON + end + + let :scss_data do + "@font-face { font-family: magic; src: url($font)}; body {color: $color; content: $name;}" + end + + let :theme_repo do + setup_git_repo( + "about.json" => about_json, + "desktop/desktop.scss" => scss_data, + "scss/oldpath.scss" => ".class2{color:blue}", + "stylesheets/file.scss" => ".class1{color:red}", + "stylesheets/empty.scss" => "", + "javascripts/discourse/controllers/test.js.es6" => "console.log('test');", + "common/header.html" => "I AM HEADER", + "common/random.html" => "I AM SILLY", + "common/embedded.scss" => "EMBED", + "assets/font.woff2" => "FAKE FONT", + "settings.yaml" => "boolean_setting: true", + "locales/en.yml" => "sometranslations" + ) + end + + let :component_repo do + setup_git_repo( + "about.json" => about_json(component: true), + "desktop/desktop.scss" => scss_data, + "scss/oldpath.scss" => ".class2{color:blue}", + "stylesheets/file.scss" => ".class1{color:red}", + "stylesheets/empty.scss" => "", + "javascripts/discourse/controllers/test.js.es6" => "console.log('test');", + "common/header.html" => "I AM HEADER", + "common/random.html" => "I AM SILLY", + "common/embedded.scss" => "EMBED", + "assets/font.woff2" => "FAKE FONT", + "settings.yaml" => "boolean_setting: true", + "locales/en.yml" => "sometranslations" + ) + end + + after do + `rm -fr #{theme_repo}` + `rm -fr #{component_repo}` + end + + it 'gracefully fails' do + ThemesInstallTask.install("nothing": "fail!") + expect(Theme.where(name: "fail!").exists?).to eq(false) + end + + describe "no options" do + it 'installs a theme' do + ThemesInstallTask.install("some_theme": theme_repo) + expect(Theme.where(name: THEME_NAME).exists?).to eq(true) end end - context 'with options' do - subject { described_class.new(options) } - let(:options) { { 'url' => github_repo, 'branch' => branch } } - - it 'configures the url' do - expect(subject.url).to eq github_repo + describe "with options" do + it 'installs a theme from only a url' do + ThemesInstallTask.install({ "some_theme": { "url": theme_repo } }) + expect(Theme.where(name: THEME_NAME).exists?).to eq(true) end - it 'initializes options' do - expect(subject.options).to eq("url" => github_repo, "branch" => branch) - end - end - end - - describe '#theme_exists?' do - let(:theme) { Fabricate(:theme) } - subject { described_class.new(options) } - - context 'without branch' do - let(:options) { github_repo } - - it 'returns true when a branchless theme exists' do - theme.create_remote_theme(remote_url: github_repo) - expect(subject.theme_exists?).to be true + it 'does not set the theme to default if the key/value is not present' do + ThemesInstallTask.install({ "some_theme": { "url": theme_repo } }) + theme = Theme.find_by(name: THEME_NAME) + expect(theme.default?).to eq(false) end - it 'returns false when the url exists but with a branch' do - theme.create_remote_theme(remote_url: github_repo, branch: branch) - expect(subject.theme_exists?).to be false + it 'sets the theme to default if the key/value is true' do + ThemesInstallTask.install({ "some_theme": { "url": theme_repo, default: true } }) + theme = Theme.find_by(name: THEME_NAME) + expect(theme.default?).to eq(true) end - it 'returns false when it doesnt exist' do - theme.create_remote_theme(remote_url: 'https://github.com/example/different_theme.git') - expect(subject.theme_exists?).to be false - end - end - - context 'with branch' do - let(:options) { { 'url' => github_repo, 'branch' => branch } } - - it 'returns false when a branchless theme exists' do - theme.create_remote_theme(remote_url: github_repo) - expect(subject.theme_exists?).to be false + it 'installs theme components, but does not add them to themes' do + ThemesInstallTask.install({ "some_theme": { "url": component_repo } }) + theme = Theme.find_by(name: THEME_NAME) + expect(theme.component).to eq(true) end - it 'returns true when the url exists with a branch' do - theme.create_remote_theme(remote_url: github_repo, branch: branch) - expect(subject.theme_exists?).to be true - end - - it 'returns false when it doesnt exist' do - theme.create_remote_theme(remote_url: 'https://github.com/example/different_theme.git') - expect(subject.theme_exists?).to be false + it 'adds component to all themes if "add_to_all_themes" is true' do + ThemesInstallTask.install({ "some_theme": { "url": component_repo, add_to_all_themes: true } }) + theme = Theme.find_by(name: THEME_NAME) + Theme.where(component: false).each do |parent_theme| + expect(ChildTheme.find_by(parent_theme_id: parent_theme.id, child_theme_id: theme.id).nil?).to eq(false) + end end end end