discourse_theme 0.4.0 → 0.5.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +34 -5
- data/.gitignore +0 -10
- data/.rubocop.yml +7 -1
- data/Gemfile +1 -0
- data/Guardfile +1 -0
- data/README.md +9 -3
- data/Rakefile +1 -0
- data/bin/discourse_theme +1 -0
- data/discourse_theme.gemspec +11 -9
- data/lib/discourse_theme.rb +10 -8
- data/lib/discourse_theme/cli.rb +48 -63
- data/lib/discourse_theme/client.rb +12 -11
- data/lib/discourse_theme/config.rb +1 -0
- data/lib/discourse_theme/downloader.rb +1 -0
- data/lib/discourse_theme/scaffold.rb +110 -41
- data/lib/discourse_theme/ui.rb +35 -0
- data/lib/discourse_theme/uploader.rb +6 -5
- data/lib/discourse_theme/version.rb +1 -1
- data/lib/discourse_theme/watcher.rb +7 -6
- metadata +76 -48
- data/.travis.yml +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 20a594e3cf6429ad7b721ca546710a1f57706a74bb0c8346301dea8b76a58cea
|
4
|
+
data.tar.gz: 477808a79184cc9251865a86a77ccfa33a64318a90a774207a5441b683499546
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8d5ad9c41ca00b3172167b1985cdb5da36ef6d8073ad6b4882f6760da05006f550a12447007d64fb8486e078245eab618979c80e18e78eaa4cc1487269dd780a
|
7
|
+
data.tar.gz: 7e40d9ec1727422c4875307b35b38a3d178330598c4a0b1c207779564fe8cf4dbbd07864e964e8e7eee1d5ffec47ad9cdc2821f28fd24ebe1bdb44677cd659cf
|
data/.github/workflows/ci.yml
CHANGED
@@ -1,21 +1,50 @@
|
|
1
1
|
name: CI
|
2
2
|
|
3
3
|
on:
|
4
|
+
pull_request:
|
4
5
|
push:
|
5
6
|
branches:
|
6
7
|
- master
|
7
|
-
|
8
|
-
- v*
|
8
|
+
- main
|
9
9
|
|
10
10
|
jobs:
|
11
|
+
build:
|
12
|
+
runs-on: ubuntu-latest
|
13
|
+
|
14
|
+
strategy:
|
15
|
+
matrix:
|
16
|
+
ruby:
|
17
|
+
- 2.5
|
18
|
+
- 2.6
|
19
|
+
- 2.7
|
20
|
+
- 3.0
|
21
|
+
|
22
|
+
steps:
|
23
|
+
- uses: actions/checkout@v2
|
24
|
+
|
25
|
+
- name: Setup ruby
|
26
|
+
uses: ruby/setup-ruby@v1
|
27
|
+
with:
|
28
|
+
ruby-version: ${{ matrix.ruby }}
|
29
|
+
bundler-cache: true
|
30
|
+
|
31
|
+
- name: Lint
|
32
|
+
run: bundle exec rubocop
|
33
|
+
|
34
|
+
- name: Tests
|
35
|
+
run: bundle exec rake test
|
36
|
+
|
11
37
|
publish:
|
12
|
-
if:
|
38
|
+
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')
|
39
|
+
needs: build
|
13
40
|
runs-on: ubuntu-latest
|
14
41
|
|
15
42
|
steps:
|
16
43
|
- uses: actions/checkout@v2
|
17
44
|
|
18
45
|
- name: Release Gem
|
19
|
-
uses:
|
46
|
+
uses: discourse/publish-rubygems-action@v2-beta
|
20
47
|
env:
|
21
|
-
RUBYGEMS_API_KEY: ${{secrets.RUBYGEMS_API_KEY}}
|
48
|
+
RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }}
|
49
|
+
GIT_EMAIL: team@discourse.org
|
50
|
+
GIT_NAME: discoursebot
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
data/Gemfile
CHANGED
data/Guardfile
CHANGED
data/README.md
CHANGED
@@ -20,11 +20,17 @@ For help run:
|
|
20
20
|
discourse_theme
|
21
21
|
```
|
22
22
|
|
23
|
-
|
23
|
+
### `discourse_theme new PATH`
|
24
24
|
|
25
|
-
|
25
|
+
Creates a new blank theme. The CLI will guide you through the process.
|
26
26
|
|
27
|
-
|
27
|
+
### `discourse_theme download PATH`
|
28
|
+
|
29
|
+
Downloads a theme from the server and stores in the designated directory.
|
30
|
+
|
31
|
+
### `discourse_theme watch PATH`
|
32
|
+
|
33
|
+
Monitors a theme or component for changes. When changed the program will synchronize the theme or component to your Discourse of choice.
|
28
34
|
|
29
35
|
## Contributing
|
30
36
|
|
data/Rakefile
CHANGED
data/bin/discourse_theme
CHANGED
data/discourse_theme.gemspec
CHANGED
@@ -23,18 +23,20 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
24
24
|
spec.require_paths = ["lib"]
|
25
25
|
|
26
|
+
spec.required_ruby_version = '>= 2.5.0'
|
27
|
+
|
28
|
+
spec.add_runtime_dependency "minitar", "~> 0.6"
|
29
|
+
spec.add_runtime_dependency "listen", "~> 3.1"
|
30
|
+
spec.add_runtime_dependency "multipart-post", "~> 2.0"
|
31
|
+
spec.add_runtime_dependency "tty-prompt", "~> 0.18"
|
32
|
+
spec.add_runtime_dependency "rubyzip", "~> 1.2"
|
33
|
+
|
26
34
|
spec.add_development_dependency "bundler", "~> 2.0"
|
27
|
-
spec.add_development_dependency "rake", "~>
|
35
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
28
36
|
spec.add_development_dependency "minitest", "~> 5.0"
|
29
37
|
spec.add_development_dependency "guard", "~> 2.14"
|
30
38
|
spec.add_development_dependency "guard-minitest", "~> 2.4"
|
31
39
|
spec.add_development_dependency "webmock", "~> 3.5"
|
32
|
-
|
33
|
-
spec.
|
34
|
-
spec.add_dependency "listen", "~> 3.1"
|
35
|
-
spec.add_dependency "multipart-post", "~> 2.0"
|
36
|
-
spec.add_dependency "tty-prompt", "~> 0.18"
|
37
|
-
spec.add_dependency "rubyzip", "~> 1.2"
|
38
|
-
|
39
|
-
spec.required_ruby_version = '>= 2.6.0'
|
40
|
+
spec.add_development_dependency "rubocop"
|
41
|
+
spec.add_development_dependency "rubocop-discourse"
|
40
42
|
end
|
data/lib/discourse_theme.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'fileutils'
|
2
3
|
require 'pathname'
|
3
4
|
require 'tempfile'
|
@@ -13,14 +14,15 @@ require 'json'
|
|
13
14
|
require 'yaml'
|
14
15
|
require 'tty/prompt'
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
17
|
+
require_relative 'discourse_theme/version'
|
18
|
+
require_relative 'discourse_theme/config'
|
19
|
+
require_relative 'discourse_theme/ui'
|
20
|
+
require_relative 'discourse_theme/cli'
|
21
|
+
require_relative 'discourse_theme/client'
|
22
|
+
require_relative 'discourse_theme/downloader'
|
23
|
+
require_relative 'discourse_theme/uploader'
|
24
|
+
require_relative 'discourse_theme/watcher'
|
25
|
+
require_relative 'discourse_theme/scaffold'
|
24
26
|
|
25
27
|
module DiscourseTheme
|
26
28
|
class ThemeError < StandardError; end
|
data/lib/discourse_theme/cli.rb
CHANGED
@@ -1,45 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module DiscourseTheme
|
2
3
|
class Cli
|
3
|
-
|
4
|
-
@@prompt = ::TTY::Prompt.new(help_color: :cyan)
|
5
|
-
@@pastel = Pastel.new
|
6
|
-
|
7
|
-
def self.yes?(message)
|
8
|
-
@@prompt.yes?(@@pastel.cyan("? ") + message)
|
9
|
-
end
|
10
|
-
|
11
|
-
def self.ask(message, default: nil)
|
12
|
-
@@prompt.ask(@@pastel.cyan("? ") + message, default: default)
|
13
|
-
end
|
14
|
-
|
15
|
-
def self.select(message, options)
|
16
|
-
@@prompt.select(@@pastel.cyan("? ") + message, options)
|
17
|
-
end
|
18
|
-
|
19
|
-
def self.info(message)
|
20
|
-
puts @@pastel.blue("i ") + message
|
21
|
-
end
|
22
|
-
|
23
|
-
def self.progress(message)
|
24
|
-
puts @@pastel.yellow("» ") + message
|
25
|
-
end
|
26
|
-
|
27
|
-
def self.error(message)
|
28
|
-
puts @@pastel.red("✘ #{message}")
|
29
|
-
end
|
30
|
-
|
31
|
-
def self.success(message)
|
32
|
-
puts @@pastel.green("✔ #{message}")
|
33
|
-
end
|
34
|
-
|
35
4
|
SETTINGS_FILE = File.expand_path("~/.discourse_theme")
|
36
5
|
|
37
6
|
def usage
|
38
7
|
puts "Usage: discourse_theme COMMAND [--reset]"
|
39
8
|
puts
|
40
|
-
puts "discourse_theme new DIR
|
41
|
-
puts "discourse_theme download DIR
|
42
|
-
puts "discourse_theme watch DIR
|
9
|
+
puts "discourse_theme new DIR - Creates a new theme in the designated directory"
|
10
|
+
puts "discourse_theme download DIR - Downloads a theme from the server and stores in the designated directory"
|
11
|
+
puts "discourse_theme watch DIR - Watches the theme directory and synchronizes with Discourse"
|
43
12
|
puts
|
44
13
|
puts "Use --reset to change the configuration for a directory"
|
45
14
|
exit 1
|
@@ -60,13 +29,12 @@ module DiscourseTheme
|
|
60
29
|
components = settings.components
|
61
30
|
|
62
31
|
if command == "new"
|
63
|
-
raise DiscourseTheme::ThemeError.new "'#{dir} is not empty" if Dir.exists?(dir) && !Dir.empty?(dir)
|
32
|
+
raise DiscourseTheme::ThemeError.new "'#{dir}' is not empty" if Dir.exists?(dir) && !Dir.empty?(dir)
|
33
|
+
raise DiscourseTheme::ThemeError.new "git is not installed" if !command?("git")
|
34
|
+
raise DiscourseTheme::ThemeError.new "yarn is not installed" if !command?("yarn")
|
35
|
+
|
64
36
|
DiscourseTheme::Scaffold.generate(dir)
|
65
|
-
|
66
|
-
args[0] = "watch"
|
67
|
-
Cli.progress "Running discourse_theme #{args.join(' ')}"
|
68
|
-
run(args)
|
69
|
-
end
|
37
|
+
watch_theme?(args)
|
70
38
|
elsif command == "watch"
|
71
39
|
raise DiscourseTheme::ThemeError.new "'#{dir} does not exist" unless Dir.exists?(dir)
|
72
40
|
client = DiscourseTheme::Client.new(dir, settings, reset: reset)
|
@@ -80,13 +48,13 @@ module DiscourseTheme
|
|
80
48
|
options["Create and sync with a new theme"] = :create
|
81
49
|
options["Select a different theme"] = :select
|
82
50
|
|
83
|
-
choice =
|
51
|
+
choice = UI.select('How would you like to sync this theme?', options.keys)
|
84
52
|
|
85
53
|
if options[choice] == :create
|
86
54
|
theme_id = nil
|
87
55
|
elsif options[choice] == :select
|
88
56
|
themes = render_theme_list(theme_list)
|
89
|
-
choice =
|
57
|
+
choice = UI.select('Which theme would you like to sync with?', themes)
|
90
58
|
theme_id = extract_theme_id(choice)
|
91
59
|
theme = theme_list.find { |t| t["id"] == theme_id }
|
92
60
|
end
|
@@ -101,27 +69,26 @@ module DiscourseTheme
|
|
101
69
|
options["Yes"] = :sync
|
102
70
|
options["No"] = :none
|
103
71
|
options = options.sort_by { |_, b| b == components.to_sym ? 0 : 1 }.to_h if components
|
104
|
-
choice =
|
72
|
+
choice = UI.select('Would you like to update child theme components?', options.keys)
|
105
73
|
settings.components = components = options[choice].to_s
|
106
74
|
end
|
107
75
|
|
108
76
|
uploader = DiscourseTheme::Uploader.new(dir: dir, client: client, theme_id: theme_id, components: components)
|
109
77
|
|
110
|
-
|
78
|
+
UI.progress "Uploading theme from #{dir}"
|
111
79
|
settings.theme_id = theme_id = uploader.upload_full_theme
|
112
80
|
|
113
|
-
|
114
|
-
|
81
|
+
UI.success "Theme uploaded (id:#{theme_id})"
|
82
|
+
UI.info "Preview: #{client.root}/?preview_theme_id=#{theme_id}"
|
115
83
|
if client.is_theme_creator
|
116
|
-
|
84
|
+
UI.info "Manage: #{client.root}/my/themes"
|
117
85
|
else
|
118
|
-
|
86
|
+
UI.info "Manage: #{client.root}/admin/customize/themes/#{theme_id}"
|
119
87
|
end
|
120
88
|
watcher = DiscourseTheme::Watcher.new(dir: dir, uploader: uploader)
|
121
89
|
|
122
|
-
|
90
|
+
UI.progress "Watching for changes in #{dir}..."
|
123
91
|
watcher.watch
|
124
|
-
|
125
92
|
elsif command == "download"
|
126
93
|
client = DiscourseTheme::Client.new(dir, settings, reset: reset)
|
127
94
|
downloader = DiscourseTheme::Downloader.new(dir: dir, client: client)
|
@@ -129,33 +96,51 @@ module DiscourseTheme
|
|
129
96
|
FileUtils.mkdir_p dir unless Dir.exists?(dir)
|
130
97
|
raise DiscourseTheme::ThemeError.new "'#{dir} is not empty" unless Dir.empty?(dir)
|
131
98
|
|
132
|
-
|
99
|
+
UI.progress "Loading theme list..."
|
133
100
|
themes = render_theme_list(client.get_themes_list)
|
134
101
|
|
135
|
-
choice =
|
102
|
+
choice = UI.select('Which theme would you like to download?', themes)
|
136
103
|
theme_id = extract_theme_id(choice)
|
137
104
|
|
138
|
-
|
105
|
+
UI.progress "Downloading theme into #{dir}"
|
139
106
|
|
140
107
|
downloader.download_theme(theme_id)
|
141
108
|
settings.theme_id = theme_id
|
142
109
|
|
143
|
-
|
110
|
+
UI.success "Theme downloaded"
|
144
111
|
|
145
|
-
|
146
|
-
args[0] = "watch"
|
147
|
-
Cli.progress "Running discourse_theme #{args.join(' ')}"
|
148
|
-
run(args)
|
149
|
-
end
|
112
|
+
watch_theme?(args)
|
150
113
|
else
|
151
114
|
usage
|
152
115
|
end
|
153
116
|
|
154
|
-
|
117
|
+
UI.progress "Exiting..."
|
155
118
|
rescue DiscourseTheme::ThemeError => e
|
156
|
-
|
119
|
+
UI.error "#{e.message}"
|
157
120
|
rescue Interrupt, TTY::Reader::InputInterrupt => e
|
158
|
-
|
121
|
+
UI.error "Interrupted"
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
def command?(cmd)
|
127
|
+
exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
|
128
|
+
ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
|
129
|
+
exts.each do |ext|
|
130
|
+
exe = File.join(path, "#{cmd}#{ext}")
|
131
|
+
return true if File.executable?(exe) && !File.directory?(exe)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
false
|
136
|
+
end
|
137
|
+
|
138
|
+
def watch_theme?(args)
|
139
|
+
if UI.yes?("Would you like to start 'watching' this theme?")
|
140
|
+
args[0] = "watch"
|
141
|
+
UI.progress "Running discourse_theme #{args.join(' ')}"
|
142
|
+
run(args)
|
143
|
+
end
|
159
144
|
end
|
160
145
|
|
161
146
|
def render_theme_list(themes)
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module DiscourseTheme
|
2
3
|
class Client
|
3
4
|
THEME_CREATOR_REGEX = /^https:\/\/theme-creator.discourse.org$/i
|
@@ -13,8 +14,8 @@ module DiscourseTheme
|
|
13
14
|
@is_theme_creator = !!(THEME_CREATOR_REGEX =~ @url)
|
14
15
|
|
15
16
|
if !self.class.has_needed_version?(discourse_version, "2.3.0.beta1")
|
16
|
-
|
17
|
-
|
17
|
+
UI.info "discourse_theme is designed for Discourse 2.3.0.beta1 or above"
|
18
|
+
UI.info "download will not function, and syncing destination will be unpredictable"
|
18
19
|
end
|
19
20
|
end
|
20
21
|
|
@@ -141,26 +142,26 @@ module DiscourseTheme
|
|
141
142
|
def guess_url(settings)
|
142
143
|
url = ENV['DISCOURSE_URL']
|
143
144
|
if url
|
144
|
-
|
145
|
+
UI.progress "Using #{url} from DISCOURSE_URL"
|
145
146
|
end
|
146
147
|
|
147
148
|
if !url && settings.url
|
148
149
|
url = settings.url
|
149
|
-
|
150
|
+
UI.progress "Using #{url} from #{DiscourseTheme::Cli::SETTINGS_FILE}"
|
150
151
|
end
|
151
152
|
|
152
153
|
if !url || @reset
|
153
|
-
url =
|
154
|
+
url = UI.ask("What is the root URL of your Discourse site?", default: url).strip
|
154
155
|
url = "http://#{url}" unless url =~ /^https?:\/\//
|
155
156
|
|
156
157
|
# maybe this is an HTTPS redirect
|
157
158
|
uri = URI.parse(url)
|
158
159
|
if URI::HTTP === uri && uri.port == 80 && is_https_redirect?(url)
|
159
|
-
|
160
|
+
UI.info "Detected that #{url} should be accessed over https"
|
160
161
|
url = url.sub("http", "https")
|
161
162
|
end
|
162
163
|
|
163
|
-
if
|
164
|
+
if UI.yes?("Would you like this site name stored in #{DiscourseTheme::Cli::SETTINGS_FILE}?")
|
164
165
|
settings.url = url
|
165
166
|
else
|
166
167
|
settings.url = nil
|
@@ -173,17 +174,17 @@ module DiscourseTheme
|
|
173
174
|
def guess_api_key(settings)
|
174
175
|
api_key = ENV['DISCOURSE_API_KEY']
|
175
176
|
if api_key
|
176
|
-
|
177
|
+
UI.progress "Using api key from DISCOURSE_API_KEY"
|
177
178
|
end
|
178
179
|
|
179
180
|
if !api_key && settings.api_key
|
180
181
|
api_key = settings.api_key
|
181
|
-
|
182
|
+
UI.progress "Using api key from #{DiscourseTheme::Cli::SETTINGS_FILE}"
|
182
183
|
end
|
183
184
|
|
184
185
|
if !api_key || @reset
|
185
|
-
api_key =
|
186
|
-
if
|
186
|
+
api_key = UI.ask("What is your API key?", default: api_key).strip
|
187
|
+
if UI.yes?("Would you like this API key stored in #{DiscourseTheme::Cli::SETTINGS_FILE}?")
|
187
188
|
settings.api_key = api_key
|
188
189
|
else
|
189
190
|
settings.api_key = nil
|
@@ -1,74 +1,143 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
1
5
|
module DiscourseTheme
|
2
6
|
class Scaffold
|
3
7
|
|
4
8
|
BLANK_FILES = %w{
|
5
9
|
common/common.scss
|
6
|
-
common/header.html
|
7
|
-
common/after_header.html
|
8
|
-
common/footer.html
|
9
|
-
common/head_tag.html
|
10
|
-
common/body_tag.html
|
11
|
-
common/embedded.scss
|
12
|
-
|
13
|
-
desktop/desktop.scss
|
14
|
-
desktop/header.html
|
15
|
-
desktop/after_header.html
|
16
|
-
desktop/footer.html
|
17
|
-
desktop/head_tag.html
|
18
|
-
desktop/body_tag.html
|
19
|
-
|
20
|
-
mobile/mobile.scss
|
21
|
-
mobile/header.html
|
22
|
-
mobile/after_header.html
|
23
|
-
mobile/footer.html
|
24
|
-
mobile/head_tag.html
|
25
|
-
mobile/body_tag.html
|
26
|
-
|
27
|
-
locales/en.yml
|
28
10
|
|
29
11
|
settings.yml
|
30
12
|
}
|
31
13
|
|
32
|
-
ABOUT_JSON =
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
"license_url": null,
|
37
|
-
"assets": {
|
38
|
-
},
|
39
|
-
"color_schemes": {
|
40
|
-
}
|
14
|
+
ABOUT_JSON = {
|
15
|
+
about_url: nil,
|
16
|
+
license_url: nil,
|
17
|
+
assets: {}
|
41
18
|
}
|
42
|
-
STR
|
43
19
|
|
44
20
|
HELP = <<~STR
|
45
21
|
Are you a bit lost? Be sure to read https://meta.discourse.org/t/how-to-develop-custom-themes/60848
|
46
22
|
STR
|
47
23
|
|
48
24
|
GIT_IGNORE = <<~STR
|
49
|
-
|
50
|
-
|
51
|
-
|
25
|
+
.discourse-site
|
26
|
+
node_modules
|
27
|
+
HELP
|
28
|
+
STR
|
29
|
+
|
30
|
+
API_INITIALIZER = <<~STR
|
31
|
+
import { apiInitializer } from "discourse/lib/api";
|
32
|
+
|
33
|
+
export default apiInitializer("0.11.1", api => {
|
34
|
+
console.log("hello world from api initializer!");
|
35
|
+
});
|
36
|
+
STR
|
37
|
+
|
38
|
+
PACKAGE_JSON = <<~STR
|
39
|
+
{
|
40
|
+
"author": "#AUTHOR",
|
41
|
+
"license": "MIT",
|
42
|
+
"devDependencies": {
|
43
|
+
"eslint-config-discourse": "latest"
|
44
|
+
}
|
45
|
+
}
|
46
|
+
STR
|
47
|
+
|
48
|
+
ESLINT_RC = <<~STR
|
49
|
+
{
|
50
|
+
"extends": "eslint-config-discourse"
|
51
|
+
}
|
52
|
+
STR
|
53
|
+
|
54
|
+
TEMPLATE_LINT_RC = <<~STR
|
55
|
+
module.exports = {
|
56
|
+
plugins: ["ember-template-lint-plugin-discourse"],
|
57
|
+
extends: "discourse:recommended",
|
58
|
+
};
|
59
|
+
STR
|
60
|
+
|
61
|
+
EN_YML = <<~YAML
|
62
|
+
en:
|
63
|
+
theme_metadata:
|
64
|
+
description: "#DESCRIPTION"
|
65
|
+
YAML
|
52
66
|
|
53
67
|
def self.generate(dir)
|
54
|
-
|
68
|
+
UI.progress "Generating a scaffold theme at #{dir}"
|
55
69
|
|
56
|
-
name =
|
70
|
+
name = loop do
|
71
|
+
input = UI.ask("What would you like to call your theme?").to_s.strip
|
72
|
+
if input.empty?
|
73
|
+
UI.error("Theme name cannot be empty")
|
74
|
+
else
|
75
|
+
break input
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
is_component = UI.yes?("Is this a component?")
|
57
80
|
|
58
81
|
FileUtils.mkdir_p dir
|
59
82
|
Dir.chdir dir do
|
60
|
-
|
83
|
+
author = loop do
|
84
|
+
input = UI.ask("Who is authoring the theme?", default: ENV['USER']).to_s.strip
|
85
|
+
if input.empty?
|
86
|
+
UI.error("Author cannot be empty")
|
87
|
+
else
|
88
|
+
break input
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
description = UI.ask("How would you describe this theme?").to_s.strip
|
93
|
+
|
94
|
+
UI.info "Creating about.json"
|
95
|
+
about_template = ABOUT_JSON.dup
|
96
|
+
about_template[:name] = name
|
97
|
+
if is_component
|
98
|
+
about_template[:component] = true
|
99
|
+
else
|
100
|
+
about_template[:color_schemes] = {}
|
101
|
+
end
|
102
|
+
File.write('about.json', JSON.pretty_generate(about_template))
|
103
|
+
|
104
|
+
UI.info "Creating HELP"
|
61
105
|
File.write('HELP', HELP)
|
106
|
+
|
107
|
+
UI.info "Creating package.json"
|
108
|
+
File.write('package.json', PACKAGE_JSON.sub("#AUTHOR", author))
|
109
|
+
|
110
|
+
UI.info "Creating .template-lintrc.js"
|
111
|
+
File.write('.template-lintrc.js', TEMPLATE_LINT_RC)
|
112
|
+
|
113
|
+
UI.info "Creating .eslintrc"
|
114
|
+
File.write('.eslintrc', ESLINT_RC)
|
115
|
+
|
116
|
+
UI.info "Creating .gitignore"
|
62
117
|
File.write('.gitignore', GIT_IGNORE)
|
63
118
|
|
119
|
+
locale = "locales/en.yml"
|
120
|
+
UI.info "Creating #{locale}"
|
121
|
+
FileUtils.mkdir_p(File.dirname(locale))
|
122
|
+
File.write(locale, EN_YML.sub("#DESCRIPTION", description))
|
123
|
+
|
124
|
+
encoded_name = name.downcase.gsub(/[^\w_-]+/, '_')
|
125
|
+
initializer = "javascripts/discourse/api-initializers/#{encoded_name}.js"
|
126
|
+
UI.info "Creating #{initializer}"
|
127
|
+
FileUtils.mkdir_p(File.dirname(initializer))
|
128
|
+
File.write(initializer, API_INITIALIZER)
|
129
|
+
|
64
130
|
BLANK_FILES.each do |f|
|
65
|
-
|
131
|
+
UI.info "Creating #{f}"
|
66
132
|
FileUtils.mkdir_p File.dirname(f)
|
67
133
|
FileUtils.touch f
|
68
134
|
end
|
69
135
|
|
70
|
-
|
71
|
-
puts `git init
|
136
|
+
UI.info "Initializing git repo"
|
137
|
+
puts `git init . --initial-branch=main`
|
138
|
+
|
139
|
+
UI.info "Installing dependencies"
|
140
|
+
puts `yarn`
|
72
141
|
end
|
73
142
|
end
|
74
143
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module DiscourseTheme
|
3
|
+
class UI
|
4
|
+
@@prompt = ::TTY::Prompt.new(help_color: :cyan)
|
5
|
+
@@pastel = Pastel.new
|
6
|
+
|
7
|
+
def self.yes?(message)
|
8
|
+
@@prompt.yes?(@@pastel.cyan("? ") + message)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.ask(message, default: nil)
|
12
|
+
@@prompt.ask(@@pastel.cyan("? ") + message, default: default)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.select(message, options)
|
16
|
+
@@prompt.select(@@pastel.cyan("? ") + message, options)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.info(message)
|
20
|
+
puts @@pastel.blue("i ") + message
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.progress(message)
|
24
|
+
puts @@pastel.yellow("» ") + message
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.error(message)
|
28
|
+
puts @@pastel.red("✘ #{message}")
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.success(message)
|
32
|
+
puts @@pastel.green("✔ #{message}")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module DiscourseTheme
|
2
3
|
class Uploader
|
3
4
|
|
@@ -33,9 +34,9 @@ module DiscourseTheme
|
|
33
34
|
json["theme"]["theme_fields"].each do |row|
|
34
35
|
if (error = row["error"]) && error.length > 0
|
35
36
|
count += 1
|
36
|
-
|
37
|
-
|
38
|
-
|
37
|
+
UI.error ""
|
38
|
+
UI.error "Error in #{row["target"]} #{row["name"]}: #{row["error"]}"
|
39
|
+
UI.error ""
|
39
40
|
end
|
40
41
|
end
|
41
42
|
count
|
@@ -58,7 +59,7 @@ module DiscourseTheme
|
|
58
59
|
response = @client.update_theme(@theme_id, args)
|
59
60
|
json = JSON.parse(response.body)
|
60
61
|
if diagnose_errors(json) != 0
|
61
|
-
|
62
|
+
UI.error "(end of errors)"
|
62
63
|
end
|
63
64
|
end
|
64
65
|
|
@@ -72,7 +73,7 @@ module DiscourseTheme
|
|
72
73
|
json = JSON.parse(response.body)
|
73
74
|
@theme_id = json["theme"]["id"]
|
74
75
|
if diagnose_errors(json) != 0
|
75
|
-
|
76
|
+
UI.error "(end of errors)"
|
76
77
|
end
|
77
78
|
@theme_id
|
78
79
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module DiscourseTheme
|
2
3
|
class Watcher
|
3
4
|
def self.return_immediately!
|
@@ -22,7 +23,7 @@ module DiscourseTheme
|
|
22
23
|
(resolved = resolve_file(modified[0]))
|
23
24
|
|
24
25
|
target, name, type_id = resolved
|
25
|
-
|
26
|
+
UI.progress "Fast updating #{target}.scss"
|
26
27
|
|
27
28
|
@uploader.upload_theme_field(
|
28
29
|
target: target,
|
@@ -33,17 +34,17 @@ module DiscourseTheme
|
|
33
34
|
else
|
34
35
|
count = modified.length + added.length + removed.length
|
35
36
|
if count > 1
|
36
|
-
|
37
|
+
UI.progress "Detected changes in #{count} files, uploading theme"
|
37
38
|
else
|
38
39
|
filename = modified[0] || added[0] || removed[0]
|
39
|
-
|
40
|
+
UI.progress "Detected changes in #{filename.gsub(@dir, '')}, uploading theme"
|
40
41
|
end
|
41
42
|
@uploader.upload_full_theme
|
42
43
|
end
|
43
|
-
|
44
|
+
UI.success "Done! Watching for changes..."
|
44
45
|
rescue DiscourseTheme::ThemeError => e
|
45
|
-
|
46
|
-
|
46
|
+
UI.error "#{e.message}"
|
47
|
+
UI.progress "Watching for changes..."
|
47
48
|
end
|
48
49
|
end
|
49
50
|
|
metadata
CHANGED
@@ -1,169 +1,197 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: discourse_theme
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sam Saffron
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-07-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: minitar
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
20
|
-
type: :
|
19
|
+
version: '0.6'
|
20
|
+
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '0.6'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: listen
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
34
|
-
type: :
|
33
|
+
version: '3.1'
|
34
|
+
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '3.1'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: multipart-post
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
48
|
-
type: :
|
47
|
+
version: '2.0'
|
48
|
+
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '2.0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: tty-prompt
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
62
|
-
type: :
|
61
|
+
version: '0.18'
|
62
|
+
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
68
|
+
version: '0.18'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: rubyzip
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '2
|
76
|
-
type: :
|
75
|
+
version: '1.2'
|
76
|
+
type: :runtime
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '2
|
82
|
+
version: '1.2'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
84
|
+
name: bundler
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
87
|
- - "~>"
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: '
|
89
|
+
version: '2.0'
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version: '
|
96
|
+
version: '2.0'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
|
-
name:
|
98
|
+
name: rake
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
101
|
- - "~>"
|
102
102
|
- !ruby/object:Gem::Version
|
103
|
-
version: '0
|
104
|
-
type: :
|
103
|
+
version: '13.0'
|
104
|
+
type: :development
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
|
-
version: '0
|
110
|
+
version: '13.0'
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
|
-
name:
|
112
|
+
name: minitest
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
114
114
|
requirements:
|
115
115
|
- - "~>"
|
116
116
|
- !ruby/object:Gem::Version
|
117
|
-
version: '
|
118
|
-
type: :
|
117
|
+
version: '5.0'
|
118
|
+
type: :development
|
119
119
|
prerelease: false
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
121
121
|
requirements:
|
122
122
|
- - "~>"
|
123
123
|
- !ruby/object:Gem::Version
|
124
|
-
version: '
|
124
|
+
version: '5.0'
|
125
125
|
- !ruby/object:Gem::Dependency
|
126
|
-
name:
|
126
|
+
name: guard
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
128
128
|
requirements:
|
129
129
|
- - "~>"
|
130
130
|
- !ruby/object:Gem::Version
|
131
|
-
version: '2.
|
132
|
-
type: :
|
131
|
+
version: '2.14'
|
132
|
+
type: :development
|
133
133
|
prerelease: false
|
134
134
|
version_requirements: !ruby/object:Gem::Requirement
|
135
135
|
requirements:
|
136
136
|
- - "~>"
|
137
137
|
- !ruby/object:Gem::Version
|
138
|
-
version: '2.
|
138
|
+
version: '2.14'
|
139
139
|
- !ruby/object:Gem::Dependency
|
140
|
-
name:
|
140
|
+
name: guard-minitest
|
141
141
|
requirement: !ruby/object:Gem::Requirement
|
142
142
|
requirements:
|
143
143
|
- - "~>"
|
144
144
|
- !ruby/object:Gem::Version
|
145
|
-
version: '
|
146
|
-
type: :
|
145
|
+
version: '2.4'
|
146
|
+
type: :development
|
147
147
|
prerelease: false
|
148
148
|
version_requirements: !ruby/object:Gem::Requirement
|
149
149
|
requirements:
|
150
150
|
- - "~>"
|
151
151
|
- !ruby/object:Gem::Version
|
152
|
-
version: '
|
152
|
+
version: '2.4'
|
153
153
|
- !ruby/object:Gem::Dependency
|
154
|
-
name:
|
154
|
+
name: webmock
|
155
155
|
requirement: !ruby/object:Gem::Requirement
|
156
156
|
requirements:
|
157
157
|
- - "~>"
|
158
158
|
- !ruby/object:Gem::Version
|
159
|
-
version: '
|
160
|
-
type: :
|
159
|
+
version: '3.5'
|
160
|
+
type: :development
|
161
161
|
prerelease: false
|
162
162
|
version_requirements: !ruby/object:Gem::Requirement
|
163
163
|
requirements:
|
164
164
|
- - "~>"
|
165
165
|
- !ruby/object:Gem::Version
|
166
|
-
version: '
|
166
|
+
version: '3.5'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: rubocop
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: rubocop-discourse
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - ">="
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '0'
|
188
|
+
type: :development
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - ">="
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0'
|
167
195
|
description: CLI helper for creating Discourse themes
|
168
196
|
email:
|
169
197
|
- sam.saffron@gmail.com
|
@@ -175,7 +203,6 @@ files:
|
|
175
203
|
- ".github/workflows/ci.yml"
|
176
204
|
- ".gitignore"
|
177
205
|
- ".rubocop.yml"
|
178
|
-
- ".travis.yml"
|
179
206
|
- CODE_OF_CONDUCT.md
|
180
207
|
- Gemfile
|
181
208
|
- Guardfile
|
@@ -190,6 +217,7 @@ files:
|
|
190
217
|
- lib/discourse_theme/config.rb
|
191
218
|
- lib/discourse_theme/downloader.rb
|
192
219
|
- lib/discourse_theme/scaffold.rb
|
220
|
+
- lib/discourse_theme/ui.rb
|
193
221
|
- lib/discourse_theme/uploader.rb
|
194
222
|
- lib/discourse_theme/version.rb
|
195
223
|
- lib/discourse_theme/watcher.rb
|
@@ -205,14 +233,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
205
233
|
requirements:
|
206
234
|
- - ">="
|
207
235
|
- !ruby/object:Gem::Version
|
208
|
-
version: 2.
|
236
|
+
version: 2.5.0
|
209
237
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
210
238
|
requirements:
|
211
239
|
- - ">="
|
212
240
|
- !ruby/object:Gem::Version
|
213
241
|
version: '0'
|
214
242
|
requirements: []
|
215
|
-
rubygems_version: 3.
|
243
|
+
rubygems_version: 3.1.6
|
216
244
|
signing_key:
|
217
245
|
specification_version: 4
|
218
246
|
summary: CLI helper for creating Discourse themes
|