DEV: Add options to theme install rake task - more options (#9394)

DEV: Add options to theme install rake task - more options (#9394)

diff --git a/app/services/themes_install_task.rb b/app/services/themes_install_task.rb
index 8466e7b..36ece33 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 bce2136..8a90196 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:
 #
-# theme_name: https://github.com/example/theme.git
+# == 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 == ''
+
+  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(yml)
+  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 cabf942..2410a5e 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({})
-      end
+    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
 
-    context 'with options' do
-      subject { described_class.new(options) }
-      let(:options) { { 'url' => github_repo, 'branch' => branch } }
+    let :scss_data do
+      "@font-face { font-family: magic; src: url($font)}; body {color: $color; content: $name;}"
+    end
 
-      it 'configures the url' do
-        expect(subject.url).to eq github_repo
-      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
 
-      it 'initializes options' do

[... diff too long, it was truncated ...]

GitHub sha: f07c4a78

This commit appears in #9394 which was approved by eviltrout. It was merged by markvanlan.

This commit has been mentioned on Discourse Meta. There might be relevant details there:

https://meta.discourse.org/t/theme-development-in-ci-cd-fashion/148714/4